/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nsStyledElement.h" #include "mozAutoDocUpdate.h" #include "nsGkAtoms.h" #include "nsAttrValue.h" #include "nsAttrValueInlines.h" #include "mozilla/dom/BindContext.h" #include "mozilla/dom/CustomElementRegistry.h" #include "mozilla/dom/ElementInlines.h" #include "mozilla/dom/MutationEventBinding.h" #include "mozilla/dom/MutationObservers.h" #include "mozilla/InternalMutationEvent.h" #include "mozilla/StaticPrefs_dom.h" #include "nsDOMCSSDeclaration.h" #include "nsDOMCSSAttrDeclaration.h" #include "nsServiceManagerUtils.h" #include "mozilla/dom/Document.h" #include "mozilla/DeclarationBlock.h" #include "mozilla/css/Loader.h" #include "nsXULElement.h" #include "nsContentUtils.h" #include "nsStyleUtil.h" using namespace mozilla; using namespace mozilla::dom; // Use the CC variant of this, even though this class does not define // a new CC participant, to make QIing to the CC interfaces faster. NS_IMPL_QUERY_INTERFACE_CYCLE_COLLECTION_INHERITED(nsStyledElement, nsStyledElementBase, nsStyledElement) //---------------------------------------------------------------------- // nsIContent methods bool nsStyledElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute, const nsAString& aValue, nsIPrincipal* aMaybeScriptedPrincipal, nsAttrValue& aResult) { if (aAttribute == nsGkAtoms::style && aNamespaceID == kNameSpaceID_None) { ParseStyleAttribute(aValue, aMaybeScriptedPrincipal, aResult, false); return true; } return nsStyledElementBase::ParseAttribute(aNamespaceID, aAttribute, aValue, aMaybeScriptedPrincipal, aResult); } nsresult nsStyledElement::BeforeSetAttr(int32_t aNamespaceID, nsAtom* aName, const nsAttrValueOrString* aValue, bool aNotify) { if (aNamespaceID == kNameSpaceID_None) { if (aName == nsGkAtoms::style) { if (aValue) { SetMayHaveStyle(); } } } return nsStyledElementBase::BeforeSetAttr(aNamespaceID, aName, aValue, aNotify); } void nsStyledElement::InlineStyleDeclarationWillChange( MutationClosureData& aData) { MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript()); MOZ_ASSERT(OwnerDoc()->UpdateNestingLevel() > 0, "Should be inside document update!"); bool modification = false; if (MayHaveStyle()) { bool needsOldValue = !StaticPrefs::dom_mutation_events_cssom_disabled() && nsContentUtils::HasMutationListeners( this, NS_EVENT_BITS_MUTATION_ATTRMODIFIED, this); if (!needsOldValue) { CustomElementDefinition* definition = GetCustomElementDefinition(); if (definition && definition->IsInObservedAttributeList(nsGkAtoms::style)) { needsOldValue = true; } } if (needsOldValue) { nsAutoString oldValueStr; modification = GetAttr(kNameSpaceID_None, nsGkAtoms::style, oldValueStr); if (modification) { aData.mOldValue.emplace(); aData.mOldValue->SetTo(oldValueStr); } } else { modification = HasAttr(kNameSpaceID_None, nsGkAtoms::style); } } aData.mModType = modification ? static_cast(MutationEvent_Binding::MODIFICATION) : static_cast(MutationEvent_Binding::ADDITION); MutationObservers::NotifyAttributeWillChange( this, kNameSpaceID_None, nsGkAtoms::style, aData.mModType); // XXXsmaug In order to make attribute handling more consistent, consider to // call BeforeSetAttr and pass kCallAfterSetAttr to // SetAttrAndNotify in SetInlineStyleDeclaration. // Handling of mozAutoDocUpdate may require changes in that case. } nsresult nsStyledElement::SetInlineStyleDeclaration( DeclarationBlock& aDeclaration, MutationClosureData& aData) { MOZ_ASSERT(OwnerDoc()->UpdateNestingLevel(), "Should be inside document update!"); bool hasListeners = !StaticPrefs::dom_mutation_events_cssom_disabled() && nsContentUtils::HasMutationListeners( this, NS_EVENT_BITS_MUTATION_ATTRMODIFIED, this); nsAttrValue attrValue(do_AddRef(&aDeclaration), nullptr); SetMayHaveStyle(); Document* document = GetComposedDoc(); mozAutoDocUpdate updateBatch(document, true); return SetAttrAndNotify(kNameSpaceID_None, nsGkAtoms::style, nullptr, aData.mOldValue.ptrOr(nullptr), attrValue, nullptr, aData.mModType, hasListeners, true, kDontCallAfterSetAttr, document, updateBatch); } // --------------------------------------------------------------- // Others and helpers nsICSSDeclaration* nsStyledElement::Style() { Element::nsDOMSlots* slots = DOMSlots(); if (!slots->mStyle) { // Just in case... ReparseStyleAttribute(/* aForceInDataDoc */ true); slots->mStyle = new nsDOMCSSAttributeDeclaration(this, false); SetMayHaveStyle(); } return slots->mStyle; } nsresult nsStyledElement::ReparseStyleAttribute(bool aForceInDataDoc) { if (!MayHaveStyle()) { return NS_OK; } const nsAttrValue* oldVal = mAttrs.GetAttr(nsGkAtoms::style); if (oldVal && oldVal->Type() != nsAttrValue::eCSSDeclaration) { nsAttrValue attrValue; nsAutoString stringValue; oldVal->ToString(stringValue); ParseStyleAttribute(stringValue, nullptr, attrValue, aForceInDataDoc); // Don't bother going through SetInlineStyleDeclaration; we don't // want to fire off mutation events or document notifications anyway bool oldValueSet; nsresult rv = mAttrs.SetAndSwapAttr(nsGkAtoms::style, attrValue, &oldValueSet); NS_ENSURE_SUCCESS(rv, rv); } return NS_OK; } nsICSSDeclaration* nsStyledElement::GetExistingStyle() { Element::nsDOMSlots* slots = GetExistingDOMSlots(); if (!slots) { return nullptr; } return slots->mStyle; } void nsStyledElement::ParseStyleAttribute(const nsAString& aValue, nsIPrincipal* aMaybeScriptedPrincipal, nsAttrValue& aResult, bool aForceInDataDoc) { Document* doc = OwnerDoc(); bool isNativeAnon = IsInNativeAnonymousSubtree(); if (!isNativeAnon && !nsStyleUtil::CSPAllowsInlineStyle(this, doc, aMaybeScriptedPrincipal, 0, 0, aValue, nullptr)) return; if (aForceInDataDoc || !doc->IsLoadedAsData() || GetExistingStyle() || doc->IsStaticDocument()) { bool isCSS = true; // assume CSS until proven otherwise if (!isNativeAnon) { // native anonymous content always assumes CSS nsAutoString styleType; doc->GetHeaderData(nsGkAtoms::headerContentStyleType, styleType); if (!styleType.IsEmpty()) { isCSS = StringBeginsWith(styleType, u"text/css"_ns, nsASCIICaseInsensitiveStringComparator); } } if (isCSS && aResult.ParseStyleAttribute(aValue, aMaybeScriptedPrincipal, this)) { return; } } aResult.SetTo(aValue); } nsresult nsStyledElement::BindToTree(BindContext& aContext, nsINode& aParent) { nsresult rv = nsStyledElementBase::BindToTree(aContext, aParent); NS_ENSURE_SUCCESS(rv, rv); if (HasAttr(nsGkAtoms::autofocus) && aContext.AllowsAutoFocus() && (!IsSVGElement() || IsFocusable())) { aContext.OwnerDoc().SetAutoFocusElement(this); } return NS_OK; }