/* 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 "mozilla/dom/HTMLHeadingElement.h"
#include "mozilla/MappedDeclarationsBuilder.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/dom/ElementInlines.h"
#include "mozilla/dom/HTMLHeadingElementBinding.h"
#include "nsGkAtoms.h"
NS_IMPL_NS_NEW_HTML_ELEMENT(Heading)
namespace mozilla::dom {
HTMLHeadingElement::~HTMLHeadingElement() =
default;
NS_IMPL_ELEMENT_CLONE(HTMLHeadingElement)
JSObject* HTMLHeadingElement::WrapNode(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) {
return HTMLHeadingElement_Binding::Wrap(aCx,
this, aGivenProto);
}
bool HTMLHeadingElement::ParseAttribute(int32_t aNamespaceID,
nsAtom* aAttribute,
const nsAString& aValue,
nsIPrincipal* aMaybeScriptedPrincipal,
nsAttrValue& aResult) {
if (aAttribute == nsGkAtoms::align && aNamespaceID == kNameSpaceID_None) {
return ParseDivAlignValue(aValue, aResult);
}
return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
aMaybeScriptedPrincipal, aResult);
}
// https://html.spec.whatwg.org/#get-an-element's-computed-heading-offset
uint32_t HTMLHeadingElement::ComputedLevel()
const {
nsAtom* name = NodeInfo()->NameAtom();
// 1. Let level be 0.
uint32_t level =
0;
// XXX: We also compute max to skip unecessary tree traversals
uint32_t max =
0;
// 2. If element's local name is h1, then set level to 1.
if (name == nsGkAtoms::h1) {
level =
1;
max =
8;
}
// 3. If element's local name is h2, then set level to 2.
else if (name == nsGkAtoms::h2) {
level =
2;
max =
7;
}
// 4. If element's local name is h3, then set level to 3.
else if (name == nsGkAtoms::h3) {
level =
3;
max =
6;
}
// 5. If element's local name is h4, then set level to 4.
else if (name == nsGkAtoms::h4) {
level =
4;
max =
5;
}
// 6. If element's local name is h5, then set level to 5.
else if (name == nsGkAtoms::h5) {
level =
5;
max =
4;
}
// 7. If element's local name is h6, then set level to 6.
else if (name == nsGkAtoms::h6) {
level =
6;
max =
3;
}
// 8. Assert: level is not zero.
MOZ_ASSERT(level !=
0);
MOZ_ASSERT(max !=
0);
// 9. Increment level by the result of getting an element's computed heading
// offset given element.
if (StaticPrefs::dom_headingoffset_enabled()) {
level += GetComputedHeadingOffset(max);
}
// 10. If level is greater than 9, then return 9.
// 11. Return level.
return level >
9 ?
9 : level;
}
// https://html.spec.whatwg.org/#get-an-element's-computed-heading-offset
uint32_t HTMLHeadingElement::GetComputedHeadingOffset(uint32_t aMax)
const {
// 1. Let offset be 0.
uint32_t offset =
0;
// 2. Let inclusiveAncestor be element.
const Element* inclusiveAncestor =
this;
// 3. While inclusiveAncestor is not null:
while (inclusiveAncestor) {
const auto* element =
nsGenericHTMLElement::FromNodeOrNull(inclusiveAncestor);
// 3.1. Let nextOffset be 0.
// 3.2. If inclusiveAncestor is an HTML element and has a
// headingoffset attribute, then parse its value using the rules for
// parsing non-negative integers.
//
// If the result of parsing the value is not an error, then set
// nextOffset to that value.
// 3.3. Increment offset by nextOffset.
if (element) {
offset += element->HeadingOffset();
}
/// XXX: We can return at this point and prevent tree traversals if we've
/// already accumulated the max possible value
if (offset >= aMax) {
return aMax;
}
// 3.4. If inclusiveAncestor is an HTML element and has a headingreset
// attribute, then return offset.
if (element && element->HeadingReset()) {
return offset;
}
// 3.5. Set inclusiveAncestor to the parent node of inclusiveAncestor within
// the flat tree.
inclusiveAncestor = inclusiveAncestor->GetFlattenedTreeParentElement();
}
// 4. Return offset.
return offset;
}
void HTMLHeadingElement::UpdateLevel(
bool aNotify) {
AutoStateChangeNotifier notifier(*
this, aNotify);
RemoveStatesSilently(ElementState::HEADING_LEVEL_BITS);
uint64_t level = ComputedLevel();
// ElementState has 4 bits for the heading level, but they are not the LMB,
// so we need to shift the given level up to those bits.
MOZ_ASSERT(level >
0 && level <
16,
"ComputedLevel() must fit into 4 bits!");
uint64_t bits = (level << HEADING_LEVEL_OFFSET);
MOZ_ASSERT((bits & ElementState::HEADING_LEVEL_BITS.bits) == bits);
AddStatesSilently(ElementState(bits));
}
void HTMLHeadingElement::MapAttributesIntoRule(
MappedDeclarationsBuilder& aBuilder) {
nsGenericHTMLElement::MapDivAlignAttributeInto(aBuilder);
nsGenericHTMLElement::MapCommonAttributesInto(aBuilder);
}
NS_IMETHODIMP_(
bool)
HTMLHeadingElement::IsAttributeMapped(
const nsAtom* aAttribute)
const {
static const MappedAttributeEntry*
const map[] = {sDivAlignAttributeMap,
sCommonAttributeMap};
return FindAttributeDependence(aAttribute, map);
}
nsMapRuleToAttributesFunc HTMLHeadingElement::GetAttributeMappingFunction()
const {
return &MapAttributesIntoRule;
}
}
// namespace mozilla::dom