/* -*- 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 "mozilla/EditorBase.h"
#include "mozilla/EventDispatcher.h"
#include "mozilla/EventListenerManager.h"
#include "mozilla/EventStateManager.h"
#include "mozilla/HTMLEditor.h"
#include "mozilla/FocusModel.h"
#include "mozilla/IMEContentObserver.h"
#include "mozilla/IMEStateManager.h"
#include "mozilla/MappedDeclarationsBuilder.h"
#include "mozilla/Maybe.h"
#include "mozilla/MouseEvents.h"
#include "mozilla/PresShell.h"
#include "mozilla/TextEditor.h"
#include "mozilla/TextEvents.h"
#include "mozilla/StaticPrefs_accessibility.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/StaticPrefs_layout.h"
#include "mozilla/dom/AncestorIterator.h"
#include "mozilla/dom/FetchPriority.h"
#include "mozilla/dom/FormData.h"
#include "nsCaseTreatment.h"
#include "nscore.h"
#include "nsGenericHTMLElement.h"
#include "nsCOMPtr.h"
#include "nsAtom.h"
#include "nsQueryObject.h"
#include "mozilla/dom/BindContext.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/UnbindContext.h"
#include "nsPIDOMWindow.h"
#include "nsIFrameInlines.h"
#include "nsView.h"
#include "nsViewManager.h"
#include "nsIWidget.h"
#include "nsRange.h"
#include "nsPresContext.h"
#include "nsError.h"
#include "nsIPrincipal.h"
#include "nsContainerFrame.h"
#include "nsStyleUtil.h"
#include "ReferrerInfo.h"
#include "mozilla/PresState.h"
#include "nsILayoutHistoryState.h"
#include "nsHTMLParts.h"
#include "nsContentUtils.h"
#include "mozilla/dom/DirectionalityUtils.h"
#include "mozilla/dom/DocumentOrShadowRoot.h"
#include "nsString.h"
#include "nsGkAtoms.h"
#include "nsDOMCSSDeclaration.h"
#include "nsIFormControl.h"
#include "mozilla/dom/HTMLFormElement.h"
#include "nsFocusManager.h"
#include "nsDOMStringMap.h"
#include "nsDOMString.h"
#include "nsLayoutUtils.h"
#include "mozilla/dom/DocumentInlines.h"
#include "HTMLFieldSetElement.h"
#include "nsTextNode.h"
#include "HTMLBRElement.h"
#include "nsDOMMutationObserver.h"
#include "mozilla/Preferences.h"
#include "mozilla/dom/FromParser.h"
#include "mozilla/dom/Link.h"
#include "mozilla/dom/ScriptLoader.h"
#include "nsDOMTokenList.h"
#include "nsThreadUtils.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/MouseEventBinding.h"
#include "mozilla/dom/ToggleEvent.h"
#include "mozilla/dom/TouchEvent.h"
#include "mozilla/dom/InputEvent.h"
#include "mozilla/dom/InvokeEvent.h"
#include "mozilla/ErrorResult.h"
#include "nsHTMLDocument.h"
#include "nsGlobalWindowInner.h"
#include "mozilla/dom/HTMLBodyElement.h"
#include "imgIContainer.h"
#include "nsComputedDOMStyle.h"
#include "mozilla/dom/HTMLDialogElement.h"
#include "mozilla/dom/HTMLLabelElement.h"
#include "mozilla/dom/HTMLInputElement.h"
#include "mozilla/dom/CustomElementRegistry.h"
#include "mozilla/dom/ElementBinding.h"
#include "mozilla/dom/ElementInternals.h"
using namespace mozilla;
using namespace mozilla::dom;
static const uint8_t NS_INPUTMODE_NONE = 1;
static const uint8_t NS_INPUTMODE_TEXT = 2;
static const uint8_t NS_INPUTMODE_TEL = 3;
static const uint8_t NS_INPUTMODE_URL = 4;
static const uint8_t NS_INPUTMODE_EMAIL = 5;
static const uint8_t NS_INPUTMODE_NUMERIC = 6;
static const uint8_t NS_INPUTMODE_DECIMAL = 7;
static const uint8_t NS_INPUTMODE_SEARCH = 8;
static constexpr nsAttrValue::EnumTable kInputmodeTable[] = {
{
"none", NS_INPUTMODE_NONE},
{
"text", NS_INPUTMODE_TEXT},
{
"tel", NS_INPUTMODE_TEL},
{
"url", NS_INPUTMODE_URL},
{
"email", NS_INPUTMODE_EMAIL},
{
"numeric", NS_INPUTMODE_NUMERIC},
{
"decimal", NS_INPUTMODE_DECIMAL},
{
"search", NS_INPUTMODE_SEARCH},
{nullptr, 0}};
static const uint8_t NS_ENTERKEYHINT_ENTER = 1;
static const uint8_t NS_ENTERKEYHINT_DONE = 2;
static const uint8_t NS_ENTERKEYHINT_GO = 3;
static const uint8_t NS_ENTERKEYHINT_NEXT = 4;
static const uint8_t NS_ENTERKEYHINT_PREVIOUS = 5;
static const uint8_t NS_ENTERKEYHINT_SEARCH = 6;
static const uint8_t NS_ENTERKEYHINT_SEND = 7;
static constexpr nsAttrValue::EnumTable kEnterKeyHintTable[] = {
{
"enter", NS_ENTERKEYHINT_ENTER},
{
"done", NS_ENTERKEYHINT_DONE},
{
"go", NS_ENTERKEYHINT_GO},
{
"next", NS_ENTERKEYHINT_NEXT},
{
"previous", NS_ENTERKEYHINT_PREVIOUS},
{
"search", NS_ENTERKEYHINT_SEARCH},
{
"send", NS_ENTERKEYHINT_SEND},
{nullptr, 0}};
static const uint8_t NS_AUTOCAPITALIZE_NONE = 1;
static const uint8_t NS_AUTOCAPITALIZE_SENTENCES = 2;
static const uint8_t NS_AUTOCAPITALIZE_WORDS = 3;
static const uint8_t NS_AUTOCAPITALIZE_CHARACTERS = 4;
static constexpr nsAttrValue::EnumTable kAutocapitalizeTable[] = {
{
"none", NS_AUTOCAPITALIZE_NONE},
{
"sentences", NS_AUTOCAPITALIZE_SENTENCES},
{
"words", NS_AUTOCAPITALIZE_WORDS},
{
"characters", NS_AUTOCAPITALIZE_CHARACTERS},
{
"off", NS_AUTOCAPITALIZE_NONE},
{
"on", NS_AUTOCAPITALIZE_SENTENCES},
{
"", 0},
{nullptr, 0}};
static const nsAttrValue::EnumTable* kDefaultAutocapitalize =
&kAutocapitalizeTable[1];
nsresult nsGenericHTMLElement::CopyInnerTo(Element* aDst) {
MOZ_ASSERT(!aDst->GetUncomposedDoc(),
"Should not CopyInnerTo an Element in a document");
auto reparse = aDst->OwnerDoc() == OwnerDoc() ? ReparseAttributes::No
: ReparseAttributes::Yes;
nsresult rv = Element::CopyInnerTo(aDst, reparse);
NS_ENSURE_SUCCESS(rv, rv);
// cloning a node must retain its internal nonce slot
nsString* nonce =
static_cast<nsString*>(GetProperty(nsGkAtoms::nonce));
if (nonce) {
static_cast<nsGenericHTMLElement*>(aDst)->SetNonce(*nonce);
}
return NS_OK;
}
static constexpr nsAttrValue::EnumTable kDirTable[] = {
{
"ltr", Directionality::Ltr},
{
"rtl", Directionality::Rtl},
{
"auto", Directionality::
Auto},
{nullptr, 0},
};
namespace {
// See <https://html.spec.whatwg.org/#the-popover-attribute>.
enum class PopoverAttributeKeyword : uint8_t {
Auto, EmptyString, Manual };
static constexpr
const char kPopoverAttributeValueAuto[] =
"auto";
static constexpr
const char kPopoverAttributeValueEmptyString[] =
"";
static constexpr
const char kPopoverAttributeValueManual[] =
"manual";
static constexpr nsAttrValue::EnumTable kPopoverTable[] = {
{kPopoverAttributeValueAuto, PopoverAttributeKeyword::
Auto},
{kPopoverAttributeValueEmptyString, PopoverAttributeKeyword::EmptyString},
{kPopoverAttributeValueManual, PopoverAttributeKeyword::Manual},
{nullptr, 0}};
// See <https://html.spec.whatwg.org/#the-popover-attribute>.
static const nsAttrValue::EnumTable* kPopoverTableInvalidValueDefault =
&kPopoverTable[2];
}
// namespace
void nsGenericHTMLElement::GetFetchPriority(nsAString& aFetchPriority)
const {
// <https://html.spec.whatwg.org/multipage/urls-and-fetching.html#fetch-priority-attributes>.
GetEnumAttr(nsGkAtoms::fetchpriority, kFetchPriorityAttributeValueAuto,
aFetchPriority);
}
/* static */
FetchPriority nsGenericHTMLElement::ToFetchPriority(
const nsAString& aValue) {
nsAttrValue attrValue;
ParseFetchPriority(aValue, attrValue);
MOZ_ASSERT(attrValue.Type() == nsAttrValue::eEnum);
return FetchPriority(attrValue.GetEnumValue());
}
namespace {
// <https://html.spec.whatwg.org/multipage/urls-and-fetching.html#fetch-priority-attributes>.
static constexpr nsAttrValue::EnumTable kFetchPriorityEnumTable[] = {
{kFetchPriorityAttributeValueHigh, FetchPriority::High},
{kFetchPriorityAttributeValueLow, FetchPriority::Low},
{kFetchPriorityAttributeValueAuto, FetchPriority::
Auto},
{nullptr, 0}};
// <https://html.spec.whatwg.org/multipage/urls-and-fetching.html#fetch-priority-attributes>.
static const nsAttrValue::EnumTable*
kFetchPriorityEnumTableInvalidValueDefault = &kFetchPriorityEnumTable[2];
}
// namespace
FetchPriority nsGenericHTMLElement::GetFetchPriority()
const {
const nsAttrValue* fetchpriorityAttribute =
GetParsedAttr(nsGkAtoms::fetchpriority);
if (fetchpriorityAttribute) {
MOZ_ASSERT(fetchpriorityAttribute->Type() == nsAttrValue::eEnum);
return FetchPriority(fetchpriorityAttribute->GetEnumValue());
}
return FetchPriority::
Auto;
}
/* static */
void nsGenericHTMLElement::ParseFetchPriority(
const nsAString& aValue,
nsAttrValue& aResult) {
aResult.ParseEnumValue(aValue, kFetchPriorityEnumTable,
false /* aCaseSensitive */,
kFetchPriorityEnumTableInvalidValueDefault);
}
void nsGenericHTMLElement::AddToNameTable(nsAtom* aName) {
MOZ_ASSERT(HasName(),
"Node doesn't have name?");
Document* doc = GetUncomposedDoc();
if (doc && !IsInNativeAnonymousSubtree()) {
doc->AddToNameTable(
this, aName);
}
}
void nsGenericHTMLElement::RemoveFromNameTable() {
if (HasName() && CanHaveName(NodeInfo()->NameAtom())) {
if (Document* doc = GetUncomposedDoc()) {
doc->RemoveFromNameTable(
this,
GetParsedAttr(nsGkAtoms::name)->GetAtomValue());
}
}
}
void nsGenericHTMLElement::GetAccessKeyLabel(nsString& aLabel) {
nsAutoString suffix;
GetAccessKey(suffix);
if (!suffix.IsEmpty()) {
EventStateManager::GetAccessKeyLabelPrefix(
this, aLabel);
aLabel.Append(suffix);
}
}
static bool IsOffsetParent(nsIFrame* aFrame) {
LayoutFrameType frameType = aFrame->Type();
if (frameType == LayoutFrameType::TableCell ||
frameType == LayoutFrameType::TableWrapper) {
// Per the IDL for Element, only td, th, and table are acceptable
// offsetParents apart from body or positioned elements; we need to check
// the content type as well as the frame type so we ignore anonymous tables
// created by an element with display: table-cell with no actual table
nsIContent* content = aFrame->GetContent();
return content->IsAnyOfHTMLElements(nsGkAtoms::table, nsGkAtoms::td,
nsGkAtoms::th);
}
return false;
}
struct OffsetResult {
Element* mParent = nullptr;
nsRect mRect;
};
static OffsetResult GetUnretargetedOffsetsFor(
const Element& aElement) {
nsIFrame* frame = aElement.GetPrimaryFrame();
if (!frame) {
return {};
}
nsIFrame* styleFrame = nsLayoutUtils::GetStyleFrame(frame);
nsIFrame* parent = frame->GetParent();
nsPoint origin(0, 0);
nsIContent* offsetParent = nullptr;
Element* docElement = aElement.GetComposedDoc()->GetRootElement();
nsIContent* content = frame->GetContent();
const auto effectiveZoom = frame->Style()->EffectiveZoom();
if (content &&
(content->IsHTMLElement(nsGkAtoms::body) || content == docElement)) {
parent = frame;
}
else {
const bool isPositioned = styleFrame->IsAbsPosContainingBlock();
const bool isAbsolutelyPositioned = frame->IsAbsolutelyPositioned();
origin += frame->GetPositionIgnoringScrolling();
for (; parent; parent = parent->GetParent()) {
content = parent->GetContent();
// Stop at the first ancestor that is positioned.
if (parent->IsAbsPosContainingBlock()) {
offsetParent = content;
break;
}
// WebKit-ism: offsetParent stops at zoom changes.
// See https://github.com/w3c/csswg-drafts/issues/10252
if (effectiveZoom != parent->Style()->EffectiveZoom()) {
offsetParent = content;
break;
}
// Add the parent's origin to our own to get to the
// right coordinate system.
const bool isOffsetParent = !isPositioned && IsOffsetParent(parent);
if (!isOffsetParent) {
origin += parent->GetPositionIgnoringScrolling();
}
if (content) {
// If we've hit the document element, break here.
if (content == docElement) {
break;
}
// Break if the ancestor frame type makes it suitable as offset parent
// and this element is *not* positioned or if we found the body element.
if (isOffsetParent || content->IsHTMLElement(nsGkAtoms::body)) {
offsetParent = content;
break;
}
}
}
if (isAbsolutelyPositioned && !offsetParent) {
// If this element is absolutely positioned, but we don't have
// an offset parent it means this element is an absolutely
// positioned child that's not nested inside another positioned
// element, in this case the element's frame's parent is the
// frame for the HTML element so we fail to find the body in the
// parent chain. We want the offset parent in this case to be
// the body, so we just get the body element from the document.
//
// We use GetBodyElement() here, not GetBody(), because we don't want to
// end up with framesets here.
offsetParent = aElement.GetComposedDoc()->GetBodyElement();
}
}
// Make the position relative to the padding edge.
if (parent) {
const nsStyleBorder* border = parent->StyleBorder();
origin.x -= border->GetComputedBorderWidth(eSideLeft);
origin.y -= border->GetComputedBorderWidth(eSideTop);
}
// Get the union of all rectangles in this and continuation frames.
// It doesn't really matter what we use as aRelativeTo here, since
// we only care about the size. We just have to use something non-null.
nsRect rcFrame = nsLayoutUtils::GetAllInFlowRectsUnion(frame, frame);
rcFrame.MoveTo(origin);
return {Element::FromNodeOrNull(offsetParent), rcFrame};
}
static bool ShouldBeRetargeted(
const Element& aReferenceElement,
const Element& aElementToMaybeRetarget) {
ShadowRoot* shadow = aElementToMaybeRetarget.GetContainingShadow();
if (!shadow) {
return false;
}
for (ShadowRoot* scope = aReferenceElement.GetContainingShadow(); scope;
scope = scope->Host()->GetContainingShadow()) {
if (scope == shadow) {
return false;
}
}
return true;
}
Element* nsGenericHTMLElement::GetOffsetRect(CSSIntRect& aRect) {
aRect = CSSIntRect();
nsIFrame* frame = GetPrimaryFrame(FlushType::Layout);
if (!frame) {
return nullptr;
}
OffsetResult thisResult = GetUnretargetedOffsetsFor(*
this);
nsRect rect = thisResult.mRect;
Element* parent = thisResult.mParent;
while (parent && ShouldBeRetargeted(*
this, *parent)) {
OffsetResult result = GetUnretargetedOffsetsFor(*parent);
rect += result.mRect.TopLeft();
parent = result.mParent;
}
aRect = CSSIntRect::FromAppUnitsRounded(
frame->Style()->EffectiveZoom().Unzoom(rect));
return parent;
}
bool nsGenericHTMLElement::Spellcheck() {
// Has the state has been explicitly set?
nsIContent* node;
for (node =
this; node; node = node->GetParent()) {
if (node->IsHTMLElement()) {
static Element::AttrValuesArray strings[] = {nsGkAtoms::_
true,
nsGkAtoms::_
false, nullptr};
switch (node->AsElement()->FindAttrValueIn(
kNameSpaceID_None, nsGkAtoms::spellcheck, strings, eCaseMatters)) {
case 0:
// spellcheck = "true"
return true;
case 1:
// spellcheck = "false"
return false;
}
}
}
// contenteditable/designMode are spellchecked by default
if (IsEditable()) {
return true;
}
// Is this a chrome element?
if (nsContentUtils::IsChromeDoc(OwnerDoc())) {
return false;
// Not spellchecked by default
}
// Anything else that's not a form control is not spellchecked by default
const nsIFormControl* formControl = GetAsFormControl();
if (!formControl) {
return false;
// Not spellchecked by default
}
// Is this a multiline plaintext input?
auto controlType = formControl->ControlType();
if (controlType == FormControlType::Textarea) {
return true;
// Spellchecked by default
}
// Is this anything other than an input text?
// Other inputs are not spellchecked.
if (controlType != FormControlType::InputText) {
return false;
// Not spellchecked by default
}
// Does the user want input text spellchecked by default?
// NOTE: Do not reflect a pref value of 0 back to the DOM getter.
// The web page should not know if the user has disabled spellchecking.
// We'll catch this in the editor itself.
int32_t spellcheckLevel = StaticPrefs::layout_spellcheckDefault();
return spellcheckLevel == 2;
// "Spellcheck multi- and single-line"
}
bool nsGenericHTMLElement::Autocorrect()
const {
return !AttrValueIs(kNameSpaceID_None, nsGkAtoms::autocorrect, nsGkAtoms::OFF,
eIgnoreCase);
}
bool nsGenericHTMLElement::InNavQuirksMode(Document* aDoc) {
return aDoc && aDoc->GetCompatibilityMode() == eCompatibility_NavQuirks;
}
void nsGenericHTMLElement::UpdateEditableState(
bool aNotify) {
// XXX Should we do this only when in a document?
ContentEditableState state = GetContentEditableState();
if (state != ContentEditableState::Inherit) {
SetEditableFlag(IsEditableState(state));
UpdateReadOnlyState(aNotify);
return;
}
nsStyledElement::UpdateEditableState(aNotify);
}
nsresult nsGenericHTMLElement::BindToTree(BindContext& aContext,
nsINode& aParent) {
nsresult rv = nsGenericHTMLElementBase::BindToTree(aContext, aParent);
NS_ENSURE_SUCCESS(rv, rv);
if (IsInComposedDoc()) {
RegUnRegAccessKey(
true);
}
if (IsInUncomposedDoc()) {
if (HasName() && CanHaveName(NodeInfo()->NameAtom())) {
aContext.OwnerDoc().AddToNameTable(
this, GetParsedAttr(nsGkAtoms::name)->GetAtomValue());
}
}
if (HasFlag(NODE_IS_EDITABLE) &&
HasContentEditableAttrTrueOrPlainTextOnly() && IsInComposedDoc()) {
aContext.OwnerDoc().ChangeContentEditableCount(
this, +1);
}
// Hide any nonce from the DOM, but keep the internal value of the
// nonce by copying and resetting the internal nonce value.
if (HasFlag(NODE_HAS_NONCE_AND_HEADER_CSP) && IsInComposedDoc() &&
OwnerDoc()->GetBrowsingContext()) {
nsContentUtils::AddScriptRunner(NS_NewRunnableFunction(
"nsGenericHTMLElement::ResetNonce::Runnable",
[self = RefPtr<nsGenericHTMLElement>(
this)]() {
nsAutoString nonce;
self->GetNonce(nonce);
self->SetAttr(kNameSpaceID_None, nsGkAtoms::nonce, u
""_ns,
true);
self->SetNonce(nonce);
}));
}
// We need to consider a labels element is moved to another subtree
// with different root, it needs to update labels list and its root
// as well.
nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots();
if (slots && slots->mLabelsList) {
slots->mLabelsList->MaybeResetRoot(SubtreeRoot());
}
return rv;
}
void nsGenericHTMLElement::UnbindFromTree(UnbindContext& aContext) {
if (IsInComposedDoc()) {
// https://html.spec.whatwg.org/#dom-trees:hide-popover-algorithm
// If removedNode's popover attribute is not in the no popover state, then
// run the hide popover algorithm given removedNode, false, false, and
// false.
if (GetPopoverData()) {
HidePopoverWithoutRunningScript();
}
RegUnRegAccessKey(
false);
}
RemoveFromNameTable();
if (HasContentEditableAttrTrueOrPlainTextOnly()) {
if (Document* doc = GetComposedDoc()) {
doc->ChangeContentEditableCount(
this, -1);
}
}
nsStyledElement::UnbindFromTree(aContext);
// Invalidate .labels list. It will be repopulated when used the next time.
nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots();
if (slots && slots->mLabelsList) {
slots->mLabelsList->MaybeResetRoot(SubtreeRoot());
}
}
HTMLFormElement* nsGenericHTMLElement::FindAncestorForm(
HTMLFormElement* aCurrentForm) {
NS_ASSERTION(!HasAttr(nsGkAtoms::form) || IsHTMLElement(nsGkAtoms::img),
"FindAncestorForm should not be called if @form is set!");
if (IsInNativeAnonymousSubtree()) {
return nullptr;
}
nsIContent* content =
this;
while (content) {
// If the current ancestor is a form, return it as our form
if (content->IsHTMLElement(nsGkAtoms::form)) {
#ifdef DEBUG
if (!nsContentUtils::IsInSameAnonymousTree(
this, content)) {
// It's possible that we started unbinding at |content| or
// some ancestor of it, and |content| and |this| used to all be
// anonymous. Check for this the hard way.
for (nsIContent* child =
this; child != content;
child = child->GetParent()) {
NS_ASSERTION(child->ComputeIndexInParentContent().isSome(),
"Walked too far?");
}
}
#endif
return static_cast<HTMLFormElement*>(content);
}
nsIContent* prevContent = content;
content = prevContent->GetParent();
if (!content && aCurrentForm) {
// We got to the root of the subtree we're in, and we're being removed
// from the DOM (the only time we get into this method with a non-null
// aCurrentForm). Check whether aCurrentForm is in the same subtree. If
// it is, we want to return aCurrentForm, since this case means that
// we're one of those inputs-in-a-table that have a hacked mForm pointer
// and a subtree containing both us and the form got removed from the
// DOM.
if (aCurrentForm->IsInclusiveDescendantOf(prevContent)) {
return aCurrentForm;
}
}
}
return nullptr;
}
bool nsGenericHTMLElement::CheckHandleEventForAnchorsPreconditions(
EventChainVisitor& aVisitor) {
MOZ_ASSERT(nsCOMPtr<Link>(do_QueryObject(
this)),
"should be called only when |this| implements |Link|");
// When disconnected, only <a> should navigate away per
// https://html.spec.whatwg.org/#cannot-navigate
return IsInComposedDoc() || IsHTMLElement(nsGkAtoms::a);
}
void nsGenericHTMLElement::GetEventTargetParentForAnchors(
EventChainPreVisitor& aVisitor) {
nsGenericHTMLElementBase::GetEventTargetParent(aVisitor);
if (!CheckHandleEventForAnchorsPreconditions(aVisitor)) {
return;
}
GetEventTargetParentForLinks(aVisitor);
}
nsresult nsGenericHTMLElement::PostHandleEventForAnchors(
EventChainPostVisitor& aVisitor) {
if (!CheckHandleEventForAnchorsPreconditions(aVisitor)) {
return NS_OK;
}
return PostHandleEventForLinks(aVisitor);
}
bool nsGenericHTMLElement::IsHTMLLink(nsIURI** aURI)
const {
MOZ_ASSERT(aURI,
"Must provide aURI out param");
*aURI = GetHrefURIForAnchors().take();
// We promise out param is non-null if we return true, so base rv on it
return *aURI != nullptr;
}
already_AddRefed<nsIURI> nsGenericHTMLElement::GetHrefURIForAnchors()
const {
// This is used by the three Link implementations and
// nsHTMLStyleElement.
// Get href= attribute (relative URI).
// We use the nsAttrValue's copy of the URI string to avoid copying.
nsCOMPtr<nsIURI> uri;
GetURIAttr(nsGkAtoms::href, nullptr, getter_AddRefs(uri));
return uri.forget();
}
void nsGenericHTMLElement::BeforeSetAttr(int32_t aNamespaceID, nsAtom* aName,
const nsAttrValue* aValue,
bool aNotify) {
if (aNamespaceID == kNameSpaceID_None) {
if (aName == nsGkAtoms::accesskey) {
// Have to unregister before clearing flag. See UnregAccessKey
RegUnRegAccessKey(
false);
if (!aValue) {
UnsetFlags(NODE_HAS_ACCESSKEY);
}
}
else if (aName == nsGkAtoms::name) {
// Have to do this before clearing flag. See RemoveFromNameTable
RemoveFromNameTable();
if (!aValue || aValue->IsEmptyString()) {
ClearHasName();
}
}
else if (aName == nsGkAtoms::contenteditable) {
if (aValue) {
// Set this before the attribute is set so that any subclass code that
// runs before the attribute is set won't think we're missing a
// contenteditable attr when we actually have one.
SetMayHaveContentEditableAttr();
}
}
if (!aValue && IsEventAttributeName(aName)) {
if (EventListenerManager* manager = GetExistingListenerManager()) {
manager->RemoveEventHandler(GetEventNameForAttr(aName));
}
}
}
return nsGenericHTMLElementBase::BeforeSetAttr(aNamespaceID, aName, aValue,
aNotify);
}
namespace {
constexpr PopoverAttributeState ToPopoverAttributeState(
PopoverAttributeKeyword aPopoverAttributeKeyword) {
// See <https://html.spec.whatwg.org/#the-popover-attribute>.
switch (aPopoverAttributeKeyword) {
case PopoverAttributeKeyword::
Auto:
return PopoverAttributeState::
Auto;
case PopoverAttributeKeyword::EmptyString:
return PopoverAttributeState::
Auto;
case PopoverAttributeKeyword::Manual:
return PopoverAttributeState::Manual;
default: {
MOZ_ASSERT_UNREACHABLE();
return PopoverAttributeState::None;
}
}
}
}
// namespace
void nsGenericHTMLElement::AfterSetPopoverAttr() {
auto mapPopoverState = [](
const nsAttrValue* value) -> PopoverAttributeState {
if (value) {
MOZ_ASSERT(value->Type() == nsAttrValue::eEnum);
const auto popoverAttributeKeyword =
static_cast<PopoverAttributeKeyword>(value->GetEnumValue());
return ToPopoverAttributeState(popoverAttributeKeyword);
}
// The missing value default is the no popover state, see
// <https://html.spec.whatwg.org/multipage/popover.html#attr-popover>.
return PopoverAttributeState::None;
};
PopoverAttributeState newState =
mapPopoverState(GetParsedAttr(nsGkAtoms::popover));
const PopoverAttributeState oldState = GetPopoverAttributeState();
if (newState != oldState) {
PopoverPseudoStateUpdate(
false,
true);
if (IsPopoverOpen()) {
HidePopoverInternal(
/* aFocusPreviousElement = */ true,
/* aFireEvents = */ true, IgnoreErrors());
// Event handlers could have removed the popover attribute, or changed
// its value.
// https://github.com/whatwg/html/issues/9034
newState = mapPopoverState(GetParsedAttr(nsGkAtoms::popover));
}
if (newState == PopoverAttributeState::None) {
ClearPopoverData();
RemoveStates(ElementState::POPOVER_OPEN);
}
else {
// TODO: what if `HidePopoverInternal` called `ShowPopup()`?
EnsurePopoverData().SetPopoverAttributeState(newState);
}
}
}
void nsGenericHTMLElement::OnAttrSetButNotChanged(
int32_t aNamespaceID, nsAtom* aName,
const nsAttrValueOrString& aValue,
bool aNotify) {
if (aNamespaceID == kNameSpaceID_None && aName == nsGkAtoms::popovertarget) {
ClearExplicitlySetAttrElement(aName);
}
return nsGenericHTMLElementBase::OnAttrSetButNotChanged(aNamespaceID, aName,
aValue, aNotify);
}
void nsGenericHTMLElement::AfterSetAttr(int32_t aNamespaceID, nsAtom* aName,
const nsAttrValue* aValue,
const nsAttrValue* aOldValue,
nsIPrincipal* aMaybeScriptedPrincipal,
bool aNotify) {
if (aNamespaceID == kNameSpaceID_None) {
if (IsEventAttributeName(aName) && aValue) {
MOZ_ASSERT(aValue->Type() == nsAttrValue::eString,
"Expected string value for script body");
SetEventHandler(GetEventNameForAttr(aName), aValue->GetStringValue());
}
else if (aNotify && aName == nsGkAtoms::spellcheck) {
SyncEditorsOnSubtree(
this);
}
else if (aName == nsGkAtoms::popover) {
nsContentUtils::AddScriptRunner(
NewRunnableMethod(
"nsGenericHTMLElement::AfterSetPopoverAttr",
this,
&nsGenericHTMLElement::AfterSetPopoverAttr));
}
else if (aName == nsGkAtoms::popovertarget) {
ClearExplicitlySetAttrElement(aName);
}
else if (aName == nsGkAtoms::dir) {
auto dir = Directionality::Ltr;
// A boolean tracking whether we need to recompute our directionality.
// This needs to happen after we update our internal "dir" attribute
// state but before we call SetDirectionalityOnDescendants.
bool recomputeDirectionality =
false;
ElementState dirStates;
if (aValue && aValue->Type() == nsAttrValue::eEnum) {
SetHasValidDir();
dirStates |= ElementState::HAS_DIR_ATTR;
auto dirValue = Directionality(aValue->GetEnumValue());
if (dirValue == Directionality::
Auto) {
dirStates |= ElementState::HAS_DIR_ATTR_LIKE_AUTO;
}
else {
dir = dirValue;
SetDirectionality(dir, aNotify);
if (dirValue == Directionality::Ltr) {
dirStates |= ElementState::HAS_DIR_ATTR_LTR;
}
else {
MOZ_ASSERT(dirValue == Directionality::Rtl);
dirStates |= ElementState::HAS_DIR_ATTR_RTL;
}
}
}
else {
if (aValue) {
// We have a value, just not a valid one.
dirStates |= ElementState::HAS_DIR_ATTR;
}
ClearHasValidDir();
if (NodeInfo()->Equals(nsGkAtoms::bdi)) {
dirStates |= ElementState::HAS_DIR_ATTR_LIKE_AUTO;
}
else {
recomputeDirectionality =
true;
}
}
// Now figure out what's changed about our dir states.
ElementState oldDirStates = State() & ElementState::DIR_ATTR_STATES;
ElementState changedStates = dirStates ^ oldDirStates;
if (!changedStates.IsEmpty()) {
ToggleStates(changedStates, aNotify);
}
if (recomputeDirectionality) {
dir = RecomputeDirectionality(
this, aNotify);
}
SetDirectionalityOnDescendants(
this, dir, aNotify);
}
else if (aName == nsGkAtoms::contenteditable) {
const auto IsEditableExceptInherit = [](
const nsAttrValue& aValue) {
return aValue.Equals(EmptyString(), eCaseMatters) ||
aValue.Equals(u
"true"_ns, eIgnoreCase) ||
(StaticPrefs::
dom_element_contenteditable_plaintext_only_enabled() &&
aValue.Equals(u
"plaintext-only"_ns, eIgnoreCase));
};
// FYI: Now, both HasContentEditableAttrTrueOrPlainTextOnly() and
// HasContentEditableAttrFalse() return true. Therefore, we need to clear
// one of them or both of them.
int32_t editableCountDelta = 0;
if (aOldValue && IsEditableExceptInherit(*aOldValue)) {
editableCountDelta = -1;
ClearHasContentEditableAttrTrueOrPlainTextOnly();
}
if (!aValue) {
ClearMayHaveContentEditableAttr();
}
else if (IsEditableExceptInherit(*aValue)) {
++editableCountDelta;
SetHasContentEditableAttrTrueOrPlainTextOnly();
}
ChangeEditableState(editableCountDelta);
}
else if (aName == nsGkAtoms::accesskey) {
if (aValue && !aValue->Equals(u
""_ns, eIgnoreCase)) {
SetFlags(NODE_HAS_ACCESSKEY);
RegUnRegAccessKey(
true);
}
}
else if (aName == nsGkAtoms::inert) {
if (aValue) {
AddStates(ElementState::INERT);
}
else {
RemoveStates(ElementState::INERT);
}
}
else if (aName == nsGkAtoms::name) {
if (aValue && !aValue->Equals(u
""_ns, eIgnoreCase)) {
// This may not be quite right because we can have subclass code run
// before here. But in practice subclasses don't care about this flag,
// and in particular selector matching does not care. Otherwise we'd
// want to handle it like we handle id attributes (in PreIdMaybeChange
// and PostIdMaybeChange).
SetHasName();
if (CanHaveName(NodeInfo()->NameAtom())) {
AddToNameTable(aValue->GetAtomValue());
}
}
}
else if (aName == nsGkAtoms::inputmode ||
aName == nsGkAtoms::enterkeyhint) {
if (nsFocusManager::GetFocusedElementStatic() ==
this) {
if (
const nsPresContext* presContext =
GetPresContext(eForComposedDoc)) {
IMEContentObserver* observer =
IMEStateManager::GetActiveContentObserver();
if (observer && observer->IsObserving(*presContext,
this)) {
if (
const RefPtr<EditorBase> editorBase = GetExtantEditor()) {
IMEState newState;
editorBase->GetPreferredIMEState(&newState);
OwningNonNull<nsGenericHTMLElement> kungFuDeathGrip(*
this);
IMEStateManager::UpdateIMEState(
newState, kungFuDeathGrip, *editorBase,
{IMEStateManager::UpdateIMEStateOption::ForceUpdate,
IMEStateManager::UpdateIMEStateOption::
DontCommitComposition});
}
}
}
}
}
// The nonce will be copied over to an internal slot and cleared from the
// Element within BindToTree to avoid CSS Selector nonce exfiltration if
// the CSP list contains a header-delivered CSP.
if (nsGkAtoms::nonce == aName) {
if (aValue) {
SetNonce(aValue->GetStringValue());
if (OwnerDoc()->GetHasCSPDeliveredThroughHeader()) {
SetFlags(NODE_HAS_NONCE_AND_HEADER_CSP);
}
}
else {
RemoveNonce();
}
}
}
return nsGenericHTMLElementBase::AfterSetAttr(
aNamespaceID, aName, aValue, aOldValue, aMaybeScriptedPrincipal, aNotify);
}
EventListenerManager* nsGenericHTMLElement::GetEventListenerManagerForAttr(
nsAtom* aAttrName,
bool* aDefer) {
// Attributes on the body and frameset tags get set on the global object
if ((mNodeInfo->Equals(nsGkAtoms::body) ||
mNodeInfo->Equals(nsGkAtoms::frameset)) &&
// We only forward some event attributes from body/frameset to window
(0
#define EVENT(name_, id_, type_, struct_)
/* nothing */
#define FORWARDED_EVENT(name_, id_, type_, struct_) \
|| nsGkAtoms::on
##name_ == aAttrName
#define WINDOW_EVENT FORWARDED_EVENT
#include "mozilla/EventNameList.h" // IWYU pragma: keep
#undef WINDOW_EVENT
#undef FORWARDED_EVENT
#undef EVENT
)) {
nsPIDOMWindowInner* win;
// If we have a document, and it has a window, add the event
// listener on the window (the inner window). If not, proceed as
// normal.
// XXXbz sXBL/XBL2 issue: should we instead use GetComposedDoc() here,
// override BindToTree for those classes and munge event listeners there?
Document* document = OwnerDoc();
*aDefer =
false;
if ((win = document->GetInnerWindow())) {
nsCOMPtr<EventTarget> piTarget(do_QueryInterface(win));
return piTarget->GetOrCreateListenerManager();
}
return nullptr;
}
return nsGenericHTMLElementBase::GetEventListenerManagerForAttr(aAttrName,
aDefer);
}
#define EVENT(name_, id_, type_, struct_)
/* nothing; handled by nsINode */
#define FORWARDED_EVENT(name_, id_, type_, struct_) \
EventHandlerNonNull* nsGenericHTMLElement::GetOn
##name_() { \
if (IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset)) { \
/* XXXbz note to self: add tests for this! */ \
if (nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow()) { \
nsGlobalWindowInner* globalWin = nsGlobalWindowInner::Cast(win); \
return globalWin->GetOn
##name_(); \
} \
return nullptr; \
} \
\
return nsINode::GetOn
##name_(); \
} \
void nsGenericHTMLElement::SetOn
##name_(EventHandlerNonNull* handler) { \
if (IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset)) { \
nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow(); \
if (!win) { \
return; \
} \
\
nsGlobalWindowInner* globalWin = nsGlobalWindowInner::Cast(win); \
return globalWin->SetOn
##name_(handler); \
} \
\
return nsINode::SetOn
##name_(handler); \
}
#define ERROR_EVENT(name_, id_, type_, struct_) \
already_AddRefed<EventHandlerNonNull> nsGenericHTMLElement::GetOn
##name_() { \
if (IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset)) { \
/* XXXbz note to self: add tests for this! */ \
if (nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow()) { \
nsGlobalWindowInner* globalWin = nsGlobalWindowInner::Cast(win); \
OnErrorEventHandlerNonNull* errorHandler = globalWin->GetOn
##name_(); \
if (errorHandler) { \
RefPtr<EventHandlerNonNull> handler = \
new EventHandlerNonNull(errorHandler); \
return handler.forget(); \
} \
} \
return nullptr; \
} \
\
RefPtr<EventHandlerNonNull> handler = nsINode::GetOn
##name_(); \
return handler.forget(); \
} \
void nsGenericHTMLElement::SetOn
##name_(EventHandlerNonNull* handler) { \
if (IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset)) { \
nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow(); \
if (!win) { \
return; \
} \
\
nsGlobalWindowInner* globalWin = nsGlobalWindowInner::Cast(win); \
RefPtr<OnErrorEventHandlerNonNull> errorHandler; \
if (handler) { \
errorHandler =
new OnErrorEventHandlerNonNull(handler); \
} \
return globalWin->SetOn
##name_(errorHandler); \
} \
\
return nsINode::SetOn
##name_(handler); \
}
#include "mozilla/EventNameList.h" // IWYU pragma: keep
#undef ERROR_EVENT
#undef FORWARDED_EVENT
#undef EVENT
void nsGenericHTMLElement::GetBaseTarget(nsAString& aBaseTarget)
const {
OwnerDoc()->GetBaseTarget(aBaseTarget);
}
//----------------------------------------------------------------------
bool nsGenericHTMLElement::ParseAttribute(int32_t aNamespaceID,
nsAtom* aAttribute,
const nsAString& aValue,
nsIPrincipal* aMaybeScriptedPrincipal,
nsAttrValue& aResult) {
if (aNamespaceID == kNameSpaceID_None) {
if (aAttribute == nsGkAtoms::dir) {
return aResult.ParseEnumValue(aValue, kDirTable,
false);
}
if (aAttribute == nsGkAtoms::popover) {
return aResult.ParseEnumValue(aValue, kPopoverTable,
false,
kPopoverTableInvalidValueDefault);
}
if (aAttribute == nsGkAtoms::tabindex) {
return aResult.ParseIntValue(aValue);
}
if (aAttribute == nsGkAtoms::referrerpolicy) {
return ParseReferrerAttribute(aValue, aResult);
}
if (aAttribute == nsGkAtoms::name) {
// Store name as an atom. name="" means that the element has no name,
// not that it has an empty string as the name.
if (aValue.IsEmpty()) {
return false;
}
aResult.ParseAtom(aValue);
return true;
}
if (aAttribute == nsGkAtoms::contenteditable ||
aAttribute == nsGkAtoms::translate) {
aResult.ParseAtom(aValue);
return true;
}
if (aAttribute == nsGkAtoms::rel) {
aResult.ParseAtomArray(aValue);
return true;
}
if (aAttribute == nsGkAtoms::inputmode) {
return aResult.ParseEnumValue(aValue, kInputmodeTable,
false);
}
if (aAttribute == nsGkAtoms::enterkeyhint) {
return aResult.ParseEnumValue(aValue, kEnterKeyHintTable,
false);
}
if (aAttribute == nsGkAtoms::autocapitalize) {
return aResult.ParseEnumValue(aValue, kAutocapitalizeTable,
false);
}
}
return nsGenericHTMLElementBase::ParseAttribute(
aNamespaceID, aAttribute, aValue, aMaybeScriptedPrincipal, aResult);
}
bool nsGenericHTMLElement::ParseBackgroundAttribute(int32_t aNamespaceID,
nsAtom* aAttribute,
const nsAString& aValue,
nsAttrValue& aResult) {
if (aNamespaceID == kNameSpaceID_None &&
aAttribute == nsGkAtoms::background && !aValue.IsEmpty()) {
// Resolve url to an absolute url
Document* doc = OwnerDoc();
nsCOMPtr<nsIURI> uri;
nsresult rv = nsContentUtils::NewURIWithDocumentCharset(
getter_AddRefs(uri), aValue, doc, GetBaseURI());
if (NS_FAILED(rv)) {
return false;
}
aResult.SetTo(uri, &aValue);
return true;
}
return false;
}
bool nsGenericHTMLElement::IsAttributeMapped(
const nsAtom* aAttribute)
const {
static const MappedAttributeEntry*
const map[] = {sCommonAttributeMap};
return FindAttributeDependence(aAttribute, map);
}
nsMapRuleToAttributesFunc nsGenericHTMLElement::GetAttributeMappingFunction()
const {
return &MapCommonAttributesInto;
}
static constexpr nsAttrValue::EnumTable kDivAlignTable[] = {
{
"left", StyleTextAlign::MozLeft},
{
"right", StyleTextAlign::MozRight},
{
"center", StyleTextAlign::MozCenter},
{
"middle", StyleTextAlign::MozCenter},
{
"justify", StyleTextAlign::Justify},
{nullptr, 0}};
static constexpr nsAttrValue::EnumTable kFrameborderTable[] = {
{
"yes", FrameBorderProperty::Yes},
{
"no", FrameBorderProperty::No},
{
"1", FrameBorderProperty::One},
{
"0", FrameBorderProperty::Zero},
{nullptr, 0}};
// TODO(emilio): Nobody uses the parsed attribute here.
static constexpr nsAttrValue::EnumTable kScrollingTable[] = {
{
"yes", ScrollingAttribute::Yes},
{
"no", ScrollingAttribute::No},
{
"on", ScrollingAttribute::On},
{
"off", ScrollingAttribute::Off},
{
"scroll", ScrollingAttribute::Scroll},
{
"noscroll", ScrollingAttribute::Noscroll},
{
"auto", ScrollingAttribute::
Auto},
{nullptr, 0}};
static constexpr nsAttrValue::EnumTable kTableVAlignTable[] = {
{
"top", StyleVerticalAlignKeyword::Top},
{
"middle", StyleVerticalAlignKeyword::Middle},
{
"bottom", StyleVerticalAlignKeyword::Bottom},
{
"baseline", StyleVerticalAlignKeyword::Baseline},
{nullptr, 0}};
bool nsGenericHTMLElement::ParseAlignValue(
const nsAString& aString,
nsAttrValue& aResult) {
static constexpr nsAttrValue::EnumTable kAlignTable[] = {
{
"left", StyleTextAlign::Left},
{
"right", StyleTextAlign::Right},
{
"top", StyleVerticalAlignKeyword::Top},
{
"middle", StyleVerticalAlignKeyword::MozMiddleWithBaseline},
// Intentionally not bottom.
{
"bottom", StyleVerticalAlignKeyword::Baseline},
{
"center", StyleVerticalAlignKeyword::MozMiddleWithBaseline},
{
"baseline", StyleVerticalAlignKeyword::Baseline},
{
"texttop", StyleVerticalAlignKeyword::TextTop},
{
"absmiddle", StyleVerticalAlignKeyword::Middle},
{
"abscenter", StyleVerticalAlignKeyword::Middle},
{
"absbottom", StyleVerticalAlignKeyword::Bottom},
{nullptr, 0}};
static_assert(uint8_t(StyleTextAlign::Left) !=
uint8_t(StyleVerticalAlignKeyword::Top) &&
uint8_t(StyleTextAlign::Left) !=
uint8_t(StyleVerticalAlignKeyword::MozMiddleWithBaseline) &&
uint8_t(StyleTextAlign::Left) !=
uint8_t(StyleVerticalAlignKeyword::Baseline) &&
uint8_t(StyleTextAlign::Left) !=
uint8_t(StyleVerticalAlignKeyword::TextTop) &&
uint8_t(StyleTextAlign::Left) !=
uint8_t(StyleVerticalAlignKeyword::Middle) &&
uint8_t(StyleTextAlign::Left) !=
uint8_t(StyleVerticalAlignKeyword::Bottom));
static_assert(uint8_t(StyleTextAlign::Right) !=
uint8_t(StyleVerticalAlignKeyword::Top) &&
uint8_t(StyleTextAlign::Right) !=
uint8_t(StyleVerticalAlignKeyword::MozMiddleWithBaseline) &&
uint8_t(StyleTextAlign::Right) !=
uint8_t(StyleVerticalAlignKeyword::Baseline) &&
uint8_t(StyleTextAlign::Right) !=
uint8_t(StyleVerticalAlignKeyword::TextTop) &&
uint8_t(StyleTextAlign::Right) !=
uint8_t(StyleVerticalAlignKeyword::Middle) &&
uint8_t(StyleTextAlign::Right) !=
uint8_t(StyleVerticalAlignKeyword::Bottom));
return aResult.ParseEnumValue(aString, kAlignTable,
false);
}
//----------------------------------------
static constexpr nsAttrValue::EnumTable kTableHAlignTable[] = {
{
"left", StyleTextAlign::Left},
{
"right", StyleTextAlign::Right},
{
"center", StyleTextAlign::Center},
{
"justify", StyleTextAlign::Justify},
{nullptr, 0}};
bool nsGenericHTMLElement::ParseTableHAlignValue(
const nsAString& aString,
nsAttrValue& aResult) {
return aResult.ParseEnumValue(aString, kTableHAlignTable,
false);
}
//----------------------------------------
// This table is used for td, th, tr, col, thead, tbody and tfoot.
static constexpr nsAttrValue::EnumTable kTableCellHAlignTable[] = {
{
"left", StyleTextAlign::MozLeft},
{
"right", StyleTextAlign::MozRight},
{
"center", StyleTextAlign::MozCenter},
{
"justify", StyleTextAlign::Justify},
{
"middle", StyleTextAlign::MozCenter},
{
"absmiddle", StyleTextAlign::Center},
{nullptr, 0}};
bool nsGenericHTMLElement::ParseTableCellHAlignValue(
const nsAString& aString,
nsAttrValue& aResult) {
return aResult.ParseEnumValue(aString, kTableCellHAlignTable,
false);
}
//----------------------------------------
bool nsGenericHTMLElement::ParseTableVAlignValue(
const nsAString& aString,
nsAttrValue& aResult) {
return aResult.ParseEnumValue(aString, kTableVAlignTable,
false);
}
bool nsGenericHTMLElement::ParseDivAlignValue(
const nsAString& aString,
nsAttrValue& aResult) {
return aResult.ParseEnumValue(aString, kDivAlignTable,
false);
}
bool nsGenericHTMLElement::ParseImageAttribute(nsAtom* aAttribute,
const nsAString& aString,
nsAttrValue& aResult) {
if (aAttribute == nsGkAtoms::width || aAttribute == nsGkAtoms::height ||
aAttribute == nsGkAtoms::hspace || aAttribute == nsGkAtoms::vspace) {
return aResult.ParseHTMLDimension(aString);
}
if (aAttribute == nsGkAtoms::border) {
return aResult.ParseNonNegativeIntValue(aString);
}
return false;
}
bool nsGenericHTMLElement::ParseReferrerAttribute(
const nsAString& aString,
nsAttrValue& aResult) {
using mozilla::dom::ReferrerInfo;
static constexpr nsAttrValue::EnumTable kReferrerPolicyTable[] = {
{GetEnumString(ReferrerPolicy::No_referrer).get(),
static_cast<int16_t>(ReferrerPolicy::No_referrer)},
{GetEnumString(ReferrerPolicy::Origin).get(),
static_cast<int16_t>(ReferrerPolicy::Origin)},
{GetEnumString(ReferrerPolicy::Origin_when_cross_origin).get(),
static_cast<int16_t>(ReferrerPolicy::Origin_when_cross_origin)},
{GetEnumString(ReferrerPolicy::No_referrer_when_downgrade).get(),
static_cast<int16_t>(ReferrerPolicy::No_referrer_when_downgrade)},
{GetEnumString(ReferrerPolicy::Unsafe_url).get(),
static_cast<int16_t>(ReferrerPolicy::Unsafe_url)},
{GetEnumString(ReferrerPolicy::Strict_origin).get(),
static_cast<int16_t>(ReferrerPolicy::Strict_origin)},
{GetEnumString(ReferrerPolicy::Same_origin).get(),
static_cast<int16_t>(ReferrerPolicy::Same_origin)},
{GetEnumString(ReferrerPolicy::Strict_origin_when_cross_origin).get(),
static_cast<int16_t>(ReferrerPolicy::Strict_origin_when_cross_origin)},
{nullptr, ReferrerPolicy::_empty}};
return aResult.ParseEnumValue(aString, kReferrerPolicyTable,
false);
}
bool nsGenericHTMLElement::ParseFrameborderValue(
const nsAString& aString,
nsAttrValue& aResult) {
return aResult.ParseEnumValue(aString, kFrameborderTable,
false);
}
bool nsGenericHTMLElement::ParseScrollingValue(
const nsAString& aString,
nsAttrValue& aResult) {
return aResult.ParseEnumValue(aString, kScrollingTable,
false);
}
static inline void MapLangAttributeInto(MappedDeclarationsBuilder& aBuilder) {
const nsAttrValue* langValue = aBuilder.GetAttr(nsGkAtoms::lang);
if (!langValue) {
return;
}
MOZ_ASSERT(langValue->Type() == nsAttrValue::eAtom);
aBuilder.SetIdentAtomValueIfUnset(eCSSProperty__x_lang,
langValue->GetAtomValue());
if (!aBuilder.PropertyIsSet(eCSSProperty_text_emphasis_position)) {
const nsAtom* lang = langValue->GetAtomValue();
if (nsStyleUtil::MatchesLanguagePrefix(lang, u
"zh")) {
aBuilder.SetKeywordValue(eCSSProperty_text_emphasis_position,
StyleTextEmphasisPosition::UNDER._0);
}
else if (nsStyleUtil::MatchesLanguagePrefix(lang, u
"ja") ||
nsStyleUtil::MatchesLanguagePrefix(lang, u
"mn")) {
// This branch is currently no part of the spec.
// See bug 1040668 comment 69 and comment 75.
aBuilder.SetKeywordValue(eCSSProperty_text_emphasis_position,
StyleTextEmphasisPosition::OVER._0);
}
}
}
/**
* Handle attributes common to all html elements
*/
void nsGenericHTMLElement::MapCommonAttributesIntoExceptHidden(
MappedDeclarationsBuilder& aBuilder) {
MapLangAttributeInto(aBuilder);
}
void nsGenericHTMLElement::MapCommonAttributesInto(
MappedDeclarationsBuilder& aBuilder) {
MapCommonAttributesIntoExceptHidden(aBuilder);
if (!aBuilder.PropertyIsSet(eCSSProperty_display)) {
if (aBuilder.GetAttr(nsGkAtoms::hidden)) {
aBuilder.SetKeywordValue(eCSSProperty_display, StyleDisplay::None._0);
}
}
}
/* static */
const nsGenericHTMLElement::MappedAttributeEntry
nsGenericHTMLElement::sCommonAttributeMap[] = {{nsGkAtoms::contenteditable},
{nsGkAtoms::lang},
{nsGkAtoms::hidden},
{nullptr}};
/* static */
const Element::MappedAttributeEntry
nsGenericHTMLElement::sImageMarginSizeAttributeMap[] = {{nsGkAtoms::width},
{nsGkAtoms::height},
{nsGkAtoms::hspace},
{nsGkAtoms::vspace},
{nullptr}};
/* static */
const Element::MappedAttributeEntry
nsGenericHTMLElement::sImageAlignAttributeMap[] = {{nsGkAtoms::align},
{nullptr}};
/* static */
const Element::MappedAttributeEntry
nsGenericHTMLElement::sDivAlignAttributeMap[] = {{nsGkAtoms::align},
{nullptr}};
/* static */
const Element::MappedAttributeEntry
nsGenericHTMLElement::sImageBorderAttributeMap[] = {{nsGkAtoms::border},
{nullptr}};
/* static */
const Element::MappedAttributeEntry
nsGenericHTMLElement::sBackgroundAttributeMap[] = {
{nsGkAtoms::background}, {nsGkAtoms::bgcolor}, {nullptr}};
/* static */
const Element::MappedAttributeEntry
nsGenericHTMLElement::sBackgroundColorAttributeMap[] = {
{nsGkAtoms::bgcolor}, {nullptr}};
void nsGenericHTMLElement::MapImageAlignAttributeInto(
MappedDeclarationsBuilder& aBuilder) {
const nsAttrValue* value = aBuilder.GetAttr(nsGkAtoms::align);
if (value && value->Type() == nsAttrValue::eEnum) {
int32_t align = value->GetEnumValue();
if (!aBuilder.PropertyIsSet(eCSSProperty_float)) {
if (align == uint8_t(StyleTextAlign::Left)) {
aBuilder.SetKeywordValue(eCSSProperty_float, StyleFloat::Left);
}
else if (align == uint8_t(StyleTextAlign::Right)) {
aBuilder.SetKeywordValue(eCSSProperty_float, StyleFloat::Right);
}
}
if (!aBuilder.PropertyIsSet(eCSSProperty_vertical_align)) {
switch (align) {
case uint8_t(StyleTextAlign::Left):
case uint8_t(StyleTextAlign::Right):
break;
default:
aBuilder.SetKeywordValue(eCSSProperty_vertical_align, align);
break;
}
}
}
}
void nsGenericHTMLElement::MapDivAlignAttributeInto(
MappedDeclarationsBuilder& aBuilder) {
if (!aBuilder.PropertyIsSet(eCSSProperty_text_align)) {
// align: enum
const nsAttrValue* value = aBuilder.GetAttr(nsGkAtoms::align);
if (value && value->Type() == nsAttrValue::eEnum)
aBuilder.SetKeywordValue(eCSSProperty_text_align, value->GetEnumValue());
}
}
void nsGenericHTMLElement::MapVAlignAttributeInto(
MappedDeclarationsBuilder& aBuilder) {
if (!aBuilder.PropertyIsSet(eCSSProperty_vertical_align)) {
// align: enum
const nsAttrValue* value = aBuilder.GetAttr(nsGkAtoms::valign);
if (value && value->Type() == nsAttrValue::eEnum)
aBuilder.SetKeywordValue(eCSSProperty_vertical_align,
value->GetEnumValue());
}
}
void nsGenericHTMLElement::MapDimensionAttributeInto(
MappedDeclarationsBuilder& aBuilder, nsCSSPropertyID aProp,
const nsAttrValue& aValue) {
MOZ_ASSERT(!aBuilder.PropertyIsSet(aProp),
"Why mapping the same property twice?");
if (aValue.Type() == nsAttrValue::eInteger) {
return aBuilder.SetPixelValue(aProp, aValue.GetIntegerValue());
}
if (aValue.Type() == nsAttrValue::ePercent) {
return aBuilder.SetPercentValue(aProp, aValue.GetPercentValue());
}
if (aValue.Type() == nsAttrValue::eDoubleValue) {
return aBuilder.SetPixelValue(aProp, aValue.GetDoubleValue());
}
}
void nsGenericHTMLElement::MapImageMarginAttributeInto(
MappedDeclarationsBuilder& aBuilder) {
// hspace: value
if (
const nsAttrValue* value = aBuilder.GetAttr(nsGkAtoms::hspace)) {
MapDimensionAttributeInto(aBuilder, eCSSProperty_margin_left, *value);
MapDimensionAttributeInto(aBuilder, eCSSProperty_margin_right, *value);
}
// vspace: value
if (
const nsAttrValue* value = aBuilder.GetAttr(nsGkAtoms::vspace)) {
MapDimensionAttributeInto(aBuilder, eCSSProperty_margin_top, *value);
MapDimensionAttributeInto(aBuilder, eCSSProperty_margin_bottom, *value);
}
}
void nsGenericHTMLElement::MapWidthAttributeInto(
MappedDeclarationsBuilder& aBuilder) {
if (
const nsAttrValue* value = aBuilder.GetAttr(nsGkAtoms::width)) {
MapDimensionAttributeInto(aBuilder, eCSSProperty_width, *value);
}
}
void nsGenericHTMLElement::MapHeightAttributeInto(
MappedDeclarationsBuilder& aBuilder) {
if (
const nsAttrValue* value = aBuilder.GetAttr(nsGkAtoms::height)) {
MapDimensionAttributeInto(aBuilder, eCSSProperty_height, *value);
}
}
void nsGenericHTMLElement::DoMapAspectRatio(
const nsAttrValue& aWidth,
const nsAttrValue& aHeight,
MappedDeclarationsBuilder& aBuilder) {
Maybe<
double> w;
if (aWidth.Type() == nsAttrValue::eInteger) {
w.emplace(aWidth.GetIntegerValue());
}
else if (aWidth.Type() == nsAttrValue::eDoubleValue) {
w.emplace(aWidth.GetDoubleValue());
}
Maybe<
double> h;
if (aHeight.Type() == nsAttrValue::eInteger) {
h.emplace(aHeight.GetIntegerValue());
}
else if (aHeight.Type() == nsAttrValue::eDoubleValue) {
h.emplace(aHeight.GetDoubleValue());
}
if (w && h) {
aBuilder.SetAspectRatio(*w, *h);
}
}
void nsGenericHTMLElement::MapImageSizeAttributesInto(
MappedDeclarationsBuilder& aBuilder, MapAspectRatio aMapAspectRatio) {
auto* width = aBuilder.GetAttr(nsGkAtoms::width);
auto* height = aBuilder.GetAttr(nsGkAtoms::height);
if (width) {
MapDimensionAttributeInto(aBuilder, eCSSProperty_width, *width);
}
if (height) {
MapDimensionAttributeInto(aBuilder, eCSSProperty_height, *height);
}
if (aMapAspectRatio == MapAspectRatio::Yes && width && height) {
DoMapAspectRatio(*width, *height, aBuilder);
}
}
void nsGenericHTMLElement::MapAspectRatioInto(
MappedDeclarationsBuilder& aBuilder) {
auto* width = aBuilder.GetAttr(nsGkAtoms::width);
auto* height = aBuilder.GetAttr(nsGkAtoms::height);
if (width && height) {
DoMapAspectRatio(*width, *height, aBuilder);
}
}
void nsGenericHTMLElement::MapImageBorderAttributeInto(
MappedDeclarationsBuilder& aBuilder) {
// border: pixels
const nsAttrValue* value = aBuilder.GetAttr(nsGkAtoms::border);
if (!value)
return;
nscoord val = 0;
if (value->Type() == nsAttrValue::eInteger) val = value->GetIntegerValue();
aBuilder.SetPixelValueIfUnset(eCSSProperty_border_top_width, (
float)val);
aBuilder.SetPixelValueIfUnset(eCSSProperty_border_right_width, (
float)val);
aBuilder.SetPixelValueIfUnset(eCSSProperty_border_bottom_width, (
float)val);
aBuilder.SetPixelValueIfUnset(eCSSProperty_border_left_width, (
float)val);
aBuilder.SetKeywordValueIfUnset(eCSSProperty_border_top_style,
StyleBorderStyle::Solid);
aBuilder.SetKeywordValueIfUnset(eCSSProperty_border_right_style,
StyleBorderStyle::Solid);
aBuilder.SetKeywordValueIfUnset(eCSSProperty_border_bottom_style,
StyleBorderStyle::Solid);
aBuilder.SetKeywordValueIfUnset(eCSSProperty_border_left_style,
StyleBorderStyle::Solid);
aBuilder.SetCurrentColorIfUnset(eCSSProperty_border_top_color);
aBuilder.SetCurrentColorIfUnset(eCSSProperty_border_right_color);
aBuilder.SetCurrentColorIfUnset(eCSSProperty_border_bottom_color);
aBuilder.SetCurrentColorIfUnset(eCSSProperty_border_left_color);
}
void nsGenericHTMLElement::MapBackgroundInto(
MappedDeclarationsBuilder& aBuilder) {
if (!aBuilder.PropertyIsSet(eCSSProperty_background_image)) {
// background
if (
const nsAttrValue* value = aBuilder.GetAttr(nsGkAtoms::background)) {
aBuilder.SetBackgroundImage(*value);
}
}
}
void nsGenericHTMLElement::MapBGColorInto(MappedDeclarationsBuilder& aBuilder)
{
if (aBuilder.PropertyIsSet(eCSSProperty_background_color)) {
return;
}
const nsAttrValue* value = aBuilder.GetAttr(nsGkAtoms::bgcolor);
nscolor color;
if (value && value->GetColorValue(color)) {
aBuilder.SetColorValue(eCSSProperty_background_color, color);
}
}
void nsGenericHTMLElement::MapBackgroundAttributesInto(
MappedDeclarationsBuilder& aBuilder) {
MapBackgroundInto(aBuilder);
MapBGColorInto(aBuilder);
}
//----------------------------------------------------------------------
int32_t nsGenericHTMLElement::GetIntAttr(nsAtom* aAttr,
int32_t aDefault) const {
const nsAttrValue* attrVal = mAttrs.GetAttr(aAttr);
if (attrVal && attrVal->Type() == nsAttrValue::eInteger) {
return attrVal->GetIntegerValue();
}
return aDefault;
}
nsresult nsGenericHTMLElement::SetIntAttr(nsAtom* aAttr, int32_t aValue) {
nsAutoString value;
value.AppendInt(aValue);
return SetAttr(kNameSpaceID_None, aAttr, value, true);
}
uint32_t nsGenericHTMLElement::GetUnsignedIntAttr(nsAtom* aAttr,
uint32_t aDefault) const {
const nsAttrValue* attrVal = mAttrs.GetAttr(aAttr);
if (!attrVal || attrVal->Type() != nsAttrValue::eInteger) {
return aDefault;
}
return attrVal->GetIntegerValue();
}
uint32_t nsGenericHTMLElement::GetDimensionAttrAsUnsignedInt(
nsAtom* aAttr, uint32_t aDefault) const {
const nsAttrValue* attrVal = mAttrs.GetAttr(aAttr);
if (!attrVal) {
return aDefault;
}
if (attrVal->Type() == nsAttrValue::eInteger) {
return attrVal->GetIntegerValue();
}
if (attrVal->Type() == nsAttrValue::ePercent) {
// This is a nasty hack. When we parsed the value, we stored it as an
// ePercent, not eInteger, because there was a '%' after it in the string.
// But the spec says to basically re-parse the string as an integer.
// Luckily, we can just return the value we have stored. But
// GetPercentValue() divides it by 100, so we need to multiply it back.
return uint32_t(attrVal->GetPercentValue() * 100.0f);
}
if (attrVal->Type() == nsAttrValue::eDoubleValue) {
return uint32_t(attrVal->GetDoubleValue());
}
// Unfortunately, the set of values that are valid dimensions is not a
// superset of values that are valid unsigned ints. In particular "+100" is
// not a valid dimension, but should parse as the unsigned int "100". So if
// we got here and we don't have a valid dimension value, just try re-parsing
// the string we have as an integer.
nsAutoString val;
attrVal->ToString(val);
nsContentUtils::ParseHTMLIntegerResultFlags result;
int32_t parsedInt = nsContentUtils::ParseHTMLInteger(val, &result);
if ((result & nsContentUtils::eParseHTMLInteger_Error) || parsedInt < 0) {
return aDefault;
}
return parsedInt;
}
void nsGenericHTMLElement::GetURIAttr(nsAtom* aAttr, nsAtom* aBaseAttr,
nsAString& aResult) const {
nsCOMPtr<nsIURI> uri;
const nsAttrValue* attr = GetURIAttr(aAttr, aBaseAttr, getter_AddRefs(uri));
if (!attr) {
aResult.Truncate();
return;
}
if (!uri) {
// Just return the attr value
attr->ToString(aResult);
return;
}
nsAutoCString spec;
uri->GetSpec(spec);
CopyUTF8toUTF16(spec, aResult);
}
void nsGenericHTMLElement::GetURIAttr(nsAtom* aAttr, nsAtom* aBaseAttr,
nsACString& aResult) const {
nsCOMPtr<nsIURI> uri;
const nsAttrValue* attr = GetURIAttr(aAttr, aBaseAttr, getter_AddRefs(uri));
if (!attr) {
aResult.Truncate();
return;
}
if (!uri) {
// Just return the attr value
nsAutoString value;
attr->ToString(value);
CopyUTF16toUTF8(value, aResult);
return;
}
uri->GetSpec(aResult);
}
const nsAttrValue* nsGenericHTMLElement::GetURIAttr(nsAtom* aAttr,
nsAtom* aBaseAttr,
nsIURI** aURI) const {
*aURI = nullptr;
const nsAttrValue* attr = mAttrs.GetAttr(aAttr);
if (!attr) {
return nullptr;
}
nsCOMPtr<nsIURI> baseURI = GetBaseURI();
if (aBaseAttr) {
nsAutoString baseAttrValue;
if (GetAttr(aBaseAttr, baseAttrValue)) {
nsCOMPtr<nsIURI> baseAttrURI;
nsresult rv = nsContentUtils::NewURIWithDocumentCharset(
getter_AddRefs(baseAttrURI), baseAttrValue, OwnerDoc(), baseURI);
if (NS_FAILED(rv)) {
return attr;
}
baseURI.swap(baseAttrURI);
}
}
// Don't care about return value. If it fails, we still want to
// return true, and *aURI will be null.
nsContentUtils::NewURIWithDocumentCharset(aURI, attr->GetStringValue(),
OwnerDoc(), baseURI);
return attr;
}
bool nsGenericHTMLElement::IsContentEditable() const {
for (const auto* element : InclusiveAncestorsOfType<nsGenericHTMLElement>()) {
--> --------------------
--> maximum size reached
--> --------------------