gecko-dev/dom/html/nsGenericHTMLElement.cpp
Emilio Cobos Álvarez 1c92ed74cb Bug 1839255 - Clean up a bit the mapped attribute set-up. r=smaug
This doesn't change behavior on its own. It could be split up a bit more
if needed though over all it shouldn't be hard to follow.

Unify nsHTMLStyleSheet and nsHTMLCSSStyleSheet into AttributeStyles
because the fact that we have two right now is pretty silly. They are
also not stylesheets (they used to be pre-stylo).

Differential Revision: https://phabricator.services.mozilla.com/D181414
2023-06-20 10:02:44 +00:00

3603 lines
124 KiB
C++

/* -*- 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/IMEContentObserver.h"
#include "mozilla/IMEStateManager.h"
#include "mozilla/MappedDeclarations.h"
#include "mozilla/Maybe.h"
#include "mozilla/MouseEvents.h"
#include "mozilla/PresShell.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/TextEditor.h"
#include "mozilla/TextEvents.h"
#include "mozilla/StaticPrefs_html5.h"
#include "mozilla/StaticPrefs_accessibility.h"
#include "mozilla/dom/FormData.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 "nsMappedAttributes.h"
#include "nsPIDOMWindow.h"
#include "nsIFrameInlines.h"
#include "nsIScrollableFrame.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 "nsITextControlFrame.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/ErrorResult.h"
#include "nsHTMLDocument.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 const 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 const 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 const 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 const nsAttrValue::EnumTable kDirTable[] = {
{"ltr", eDir_LTR}, {"rtl", eDir_RTL}, {"auto", eDir_Auto}, {nullptr, 0}};
namespace {
// See <https://html.spec.whatwg.org/#the-popover-attribute>.
enum class PopoverAttributeKeyword : uint8_t { Auto, EmptyString, Manual };
static const char* kPopoverAttributeValueAuto = "auto";
static const char* kPopoverAttributeValueEmptyString = "";
static const char* kPopoverAttributeValueManual = "manual";
static const 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::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::Table) {
// 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;
CSSIntRect 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();
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;
}
// 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),
CSSIntRect::FromAppUnitsRounded(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();
if (!GetPrimaryFrame(FlushType::Layout)) {
return nullptr;
}
OffsetResult thisResult = GetUnretargetedOffsetsFor(*this);
aRect = thisResult.mRect;
Element* parent = thisResult.mParent;
while (parent && ShouldBeRetargeted(*this, *parent)) {
OffsetResult result = GetUnretargetedOffsetsFor(*parent);
aRect += result.mRect.TopLeft();
parent = result.mParent;
}
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
nsCOMPtr<nsIFormControl> formControl = do_QueryObject(this);
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 = Preferences::GetInt("layout.spellcheckDefault", 1);
return spellcheckLevel == 2; // "Spellcheck multi- and single-line"
}
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?
ContentEditableTristate value = GetContentEditableValue();
if (value != eInherit) {
DoSetEditableFlag(!!value, aNotify);
return;
}
nsStyledElement::UpdateEditableState(aNotify);
}
ElementState nsGenericHTMLElement::IntrinsicState() const {
ElementState state = nsGenericHTMLElementBase::IntrinsicState();
if (GetDirectionality() == eDir_RTL) {
state |= ElementState::RTL;
state &= ~ElementState::LTR;
} else { // at least for HTML, directionality is exclusively LTR or RTL
NS_ASSERTION(GetDirectionality() == eDir_LTR,
"HTML element's directionality must be either RTL or LTR");
state |= ElementState::LTR;
state &= ~ElementState::RTL;
}
return state;
}
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) && GetContentEditableValue() == eTrue &&
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(bool aNullParent) {
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 (GetContentEditableValue() == eTrue) {
Document* doc = GetComposedDoc();
if (doc) {
doc->ChangeContentEditableCount(this, -1);
}
}
nsStyledElement::UnbindFromTree(aNullParent);
// 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(kNameSpaceID_None, 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() {
const nsAttrValue* newValue = GetParsedAttr(nsGkAtoms::popover);
const PopoverAttributeState newState = [&newValue]() {
if (newValue) {
MOZ_ASSERT(newValue->Type() == nsAttrValue::eEnum);
const auto popoverAttributeKeyword =
static_cast<PopoverAttributeKeyword>(newValue->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;
}();
const PopoverAttributeState oldState = GetPopoverAttributeState();
if (newState != oldState) {
EnsurePopoverData().SetPopoverAttributeState(newState);
HidePopoverInternal(/* aFocusPreviousElement = */ true,
/* aFireEvents = */ true, IgnoreErrors());
// In case `HidePopoverInternal` changed the state, keep the corresponding
// changes and don't overwrite anything here.
if (newState == GetPopoverAttributeState()) {
if (newState == PopoverAttributeState::None) {
// `HidePopoverInternal` above didn't remove the element from the top
// layer, because in that call, the element's popover attribute state
// was already `None`. Revisit this, when the spec is corrected
// (bug 1835811).
OwnerDoc()->RemovePopoverFromTopLayer(*this);
ClearPopoverData();
RemoveStates(ElementState::POPOVER_OPEN);
} else {
// TODO: what if `HidePopoverInternal` called `ShowPopup()`?
PopoverPseudoStateUpdate(false, true);
}
}
}
}
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 &&
StaticPrefs::dom_element_popover_enabled()) {
nsContentUtils::AddScriptRunner(
NewRunnableMethod("nsGenericHTMLElement::AfterSetPopoverAttr", this,
&nsGenericHTMLElement::AfterSetPopoverAttr));
} else if (aName == nsGkAtoms::dir) {
Directionality dir = eDir_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;
// We don't want to have to keep getting the "dir" attribute in
// IntrinsicState, so we manually recompute our dir-related event states
// here and send the relevant update notifications.
ElementState dirStates;
if (aValue && aValue->Type() == nsAttrValue::eEnum) {
SetHasValidDir();
dirStates |= ElementState::HAS_DIR_ATTR;
Directionality dirValue = (Directionality)aValue->GetEnumValue();
if (dirValue == eDir_Auto) {
dirStates |= ElementState::HAS_DIR_ATTR_LIKE_AUTO;
} else {
dir = dirValue;
SetDirectionality(dir, aNotify);
if (dirValue == eDir_LTR) {
dirStates |= ElementState::HAS_DIR_ATTR_LTR;
} else {
MOZ_ASSERT(dirValue == eDir_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;
ToggleStates(changedStates, aNotify);
if (recomputeDirectionality) {
dir = RecomputeDirectionality(this, aNotify);
}
SetDirectionalityOnDescendants(this, dir, aNotify);
} else if (aName == nsGkAtoms::contenteditable) {
int32_t editableCountDelta = 0;
if (aOldValue && (aOldValue->Equals(u"true"_ns, eIgnoreCase) ||
aOldValue->Equals(u""_ns, eIgnoreCase))) {
editableCountDelta = -1;
}
if (aValue && (aValue->Equals(u"true"_ns, eIgnoreCase) ||
aValue->Equals(u""_ns, eIgnoreCase))) {
++editableCountDelta;
}
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 &&
StaticPrefs::html5_inert_enabled()) {
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 &&
StaticPrefs::dom_forms_inputmode()) ||
(aName == nsGkAtoms::enterkeyhint &&
StaticPrefs::dom_forms_enterkeyhint())) {
nsPIDOMWindowOuter* window = OwnerDoc()->GetWindow();
if (window && window->GetFocusedElement() == this) {
if (IMEContentObserver* observer =
IMEStateManager::GetActiveContentObserver()) {
if (const nsPresContext* presContext =
GetPresContext(eForComposedDoc)) {
if (observer->IsManaging(*presContext, this)) {
if (RefPtr<EditorBase> editor =
nsContentUtils::GetActiveEditor(window)) {
IMEState newState;
editor->GetPreferredIMEState(&newState);
OwningNonNull<nsGenericHTMLElement> kungFuDeathGrip(*this);
IMEStateManager::UpdateIMEState(
newState, kungFuDeathGrip, *editor,
{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 &&
StaticPrefs::dom_element_popover_enabled()) {
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;
}
nsIFormControlFrame* nsGenericHTMLElement::GetFormControlFrame(
bool aFlushFrames) {
auto flushType = aFlushFrames ? FlushType::Frames : FlushType::None;
nsIFrame* frame = GetPrimaryFrame(flushType);
if (!frame) {
return nullptr;
}
if (nsIFormControlFrame* f = do_QueryFrame(frame)) {
return f;
}
// If we have generated content, the primary frame will be a wrapper frame...
// Our real frame will be in its child list.
//
// FIXME(emilio): I don't think that's true... See bug 155957 for test-cases
// though, we should figure out whether this is still needed.
for (nsIFrame* kid : frame->PrincipalChildList()) {
if (nsIFormControlFrame* f = do_QueryFrame(kid)) {
return f;
}
}
return nullptr;
}
static const nsAttrValue::EnumTable kDivAlignTable[] = {
{"left", StyleTextAlign::MozLeft},
{"right", StyleTextAlign::MozRight},
{"center", StyleTextAlign::MozCenter},
{"middle", StyleTextAlign::MozCenter},
{"justify", StyleTextAlign::Justify},
{nullptr, 0}};
static const 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 const 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 const 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 const 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 const nsAttrValue::EnumTable kTableHAlignTable[] = {
{"left", StyleTextAlign::Left}, {"right", StyleTextAlign::Right},
{"center", StyleTextAlign::Center}, {"char", StyleTextAlign::Char},
{"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 const nsAttrValue::EnumTable kTableCellHAlignTable[] = {
{"left", StyleTextAlign::MozLeft},
{"right", StyleTextAlign::MozRight},
{"center", StyleTextAlign::MozCenter},
{"char", StyleTextAlign::Char},
{"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 const nsAttrValue::EnumTable kReferrerPolicyTable[] = {
{ReferrerInfo::ReferrerPolicyToString(ReferrerPolicy::No_referrer),
static_cast<int16_t>(ReferrerPolicy::No_referrer)},
{ReferrerInfo::ReferrerPolicyToString(ReferrerPolicy::Origin),
static_cast<int16_t>(ReferrerPolicy::Origin)},
{ReferrerInfo::ReferrerPolicyToString(
ReferrerPolicy::Origin_when_cross_origin),
static_cast<int16_t>(ReferrerPolicy::Origin_when_cross_origin)},
{ReferrerInfo::ReferrerPolicyToString(
ReferrerPolicy::No_referrer_when_downgrade),
static_cast<int16_t>(ReferrerPolicy::No_referrer_when_downgrade)},
{ReferrerInfo::ReferrerPolicyToString(ReferrerPolicy::Unsafe_url),
static_cast<int16_t>(ReferrerPolicy::Unsafe_url)},
{ReferrerInfo::ReferrerPolicyToString(ReferrerPolicy::Strict_origin),
static_cast<int16_t>(ReferrerPolicy::Strict_origin)},
{ReferrerInfo::ReferrerPolicyToString(ReferrerPolicy::Same_origin),
static_cast<int16_t>(ReferrerPolicy::Same_origin)},
{ReferrerInfo::ReferrerPolicyToString(
ReferrerPolicy::Strict_origin_when_cross_origin),
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(const nsMappedAttributes* aAttributes,
MappedDeclarations& aDecls) {
const nsAttrValue* langValue = aAttributes->GetAttr(nsGkAtoms::lang);
if (!langValue) {
return;
}
MOZ_ASSERT(langValue->Type() == nsAttrValue::eAtom);
aDecls.SetIdentAtomValueIfUnset(eCSSProperty__x_lang,
langValue->GetAtomValue());
if (!aDecls.PropertyIsSet(eCSSProperty_text_emphasis_position)) {
const nsAtom* lang = langValue->GetAtomValue();
if (nsStyleUtil::MatchesLanguagePrefix(lang, u"zh")) {
aDecls.SetKeywordValue(eCSSProperty_text_emphasis_position,
StyleTextEmphasisPosition::UNDER.bits);
} 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.
aDecls.SetKeywordValue(eCSSProperty_text_emphasis_position,
StyleTextEmphasisPosition::OVER.bits);
}
}
}
/**
* Handle attributes common to all html elements
*/
void nsGenericHTMLElement::MapCommonAttributesIntoExceptHidden(
const nsMappedAttributes* aAttributes, MappedDeclarations& aDecls) {
if (!aDecls.PropertyIsSet(eCSSProperty__moz_user_modify)) {
const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::contenteditable);
if (value) {
if (value->Equals(nsGkAtoms::_empty, eCaseMatters) ||
value->Equals(nsGkAtoms::_true, eIgnoreCase)) {
aDecls.SetKeywordValue(eCSSProperty__moz_user_modify,
StyleUserModify::ReadWrite);
} else if (value->Equals(nsGkAtoms::_false, eIgnoreCase)) {
aDecls.SetKeywordValue(eCSSProperty__moz_user_modify,
StyleUserModify::ReadOnly);
}
}
}
MapLangAttributeInto(aAttributes, aDecls);
}
void nsGenericHTMLElement::MapCommonAttributesInto(
const nsMappedAttributes* aAttributes, MappedDeclarations& aDecls) {
MapCommonAttributesIntoExceptHidden(aAttributes, aDecls);
if (!aDecls.PropertyIsSet(eCSSProperty_display)) {
if (aAttributes->IndexOfAttr(nsGkAtoms::hidden) >= 0) {
aDecls.SetKeywordValue(eCSSProperty_display, StyleDisplay::None);
}
}
}
/* 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(
const nsMappedAttributes* aAttributes, MappedDeclarations& aDecls) {
const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::align);
if (value && value->Type() == nsAttrValue::eEnum) {
int32_t align = value->GetEnumValue();
if (!aDecls.PropertyIsSet(eCSSProperty_float)) {
if (align == uint8_t(StyleTextAlign::Left)) {
aDecls.SetKeywordValue(eCSSProperty_float, StyleFloat::Left);
} else if (align == uint8_t(StyleTextAlign::Right)) {
aDecls.SetKeywordValue(eCSSProperty_float, StyleFloat::Right);
}
}
if (!aDecls.PropertyIsSet(eCSSProperty_vertical_align)) {
switch (align) {
case uint8_t(StyleTextAlign::Left):
case uint8_t(StyleTextAlign::Right):
break;
default:
aDecls.SetKeywordValue(eCSSProperty_vertical_align, align);
break;
}
}
}
}
void nsGenericHTMLElement::MapDivAlignAttributeInto(
const nsMappedAttributes* aAttributes, MappedDeclarations& aDecls) {
if (!aDecls.PropertyIsSet(eCSSProperty_text_align)) {
// align: enum
const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::align);
if (value && value->Type() == nsAttrValue::eEnum)
aDecls.SetKeywordValue(eCSSProperty_text_align, value->GetEnumValue());
}
}
void nsGenericHTMLElement::MapVAlignAttributeInto(
const nsMappedAttributes* aAttributes, MappedDeclarations& aDecls) {
if (!aDecls.PropertyIsSet(eCSSProperty_vertical_align)) {
// align: enum
const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::valign);
if (value && value->Type() == nsAttrValue::eEnum)
aDecls.SetKeywordValue(eCSSProperty_vertical_align,
value->GetEnumValue());
}
}
static void MapDimensionAttributeInto(MappedDeclarations& aDecls,
nsCSSPropertyID aProp,
const nsAttrValue& aValue) {
MOZ_ASSERT(!aDecls.PropertyIsSet(aProp),
"Why mapping the same property twice?");
if (aValue.Type() == nsAttrValue::eInteger) {
return aDecls.SetPixelValue(aProp, aValue.GetIntegerValue());
}
if (aValue.Type() == nsAttrValue::ePercent) {
return aDecls.SetPercentValue(aProp, aValue.GetPercentValue());
}
if (aValue.Type() == nsAttrValue::eDoubleValue) {
return aDecls.SetPixelValue(aProp, aValue.GetDoubleValue());
}
}
void nsGenericHTMLElement::MapImageMarginAttributeInto(
const nsMappedAttributes* aAttributes, MappedDeclarations& aDecls) {
const nsAttrValue* value;
// hspace: value
value = aAttributes->GetAttr(nsGkAtoms::hspace);
if (value) {
MapDimensionAttributeInto(aDecls, eCSSProperty_margin_left, *value);
MapDimensionAttributeInto(aDecls, eCSSProperty_margin_right, *value);
}
// vspace: value
value = aAttributes->GetAttr(nsGkAtoms::vspace);
if (value) {
MapDimensionAttributeInto(aDecls, eCSSProperty_margin_top, *value);
MapDimensionAttributeInto(aDecls, eCSSProperty_margin_bottom, *value);
}
}
void nsGenericHTMLElement::MapWidthAttributeInto(
const nsMappedAttributes* aAttributes, MappedDeclarations& aDecls) {
if (const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::width)) {
MapDimensionAttributeInto(aDecls, eCSSProperty_width, *value);
}
}
void nsGenericHTMLElement::MapHeightAttributeInto(
const nsMappedAttributes* aAttributes, MappedDeclarations& aDecls) {
if (const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::height)) {
MapDimensionAttributeInto(aDecls, eCSSProperty_height, *value);
}
}
static void DoMapAspectRatio(const nsAttrValue& aWidth,
const nsAttrValue& aHeight,
MappedDeclarations& aDecls) {
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) {
aDecls.SetAspectRatio(*w, *h);
}
}
void nsGenericHTMLElement::MapImageSizeAttributesInto(
const nsMappedAttributes* aAttributes, MappedDeclarations& aDecls,
MapAspectRatio aMapAspectRatio) {
auto* width = aAttributes->GetAttr(nsGkAtoms::width);
auto* height = aAttributes->GetAttr(nsGkAtoms::height);
if (width) {
MapDimensionAttributeInto(aDecls, eCSSProperty_width, *width);
}
if (height) {
MapDimensionAttributeInto(aDecls, eCSSProperty_height, *height);
}
if (aMapAspectRatio == MapAspectRatio::Yes && width && height) {
DoMapAspectRatio(*width, *height, aDecls);
}
}
void nsGenericHTMLElement::MapPictureSourceSizeAttributesInto(
const nsMappedAttributes* aAttributes, MappedDeclarations& aDecls) {
const auto* width = aAttributes->GetAttr(nsGkAtoms::width);
const auto* height = aAttributes->GetAttr(nsGkAtoms::height);
if (!width && !height) {
return;
}
// We should set the missing property values with auto value to make sure it
// overrides the declaraion created by the presentation attributes of
// HTMLImageElement. This can make sure we compute the ratio-dependent axis
// size properly by the natural aspect-ratio of the image.
//
// Note: The spec doesn't specify this, so we follow the implementation in
// other browsers.
// Spec issue: https://github.com/whatwg/html/issues/8178.
if (width) {
MapDimensionAttributeInto(aDecls, eCSSProperty_width, *width);
} else {
aDecls.SetAutoValue(eCSSProperty_width);
}
if (height) {
MapDimensionAttributeInto(aDecls, eCSSProperty_height, *height);
} else {
aDecls.SetAutoValue(eCSSProperty_height);
}
if (width && height) {
DoMapAspectRatio(*width, *height, aDecls);
} else {
aDecls.SetAutoValue(eCSSProperty_aspect_ratio);
}
}
void nsGenericHTMLElement::MapAspectRatioInto(
const nsMappedAttributes* aAttributes, MappedDeclarations& aDecls) {
auto* width = aAttributes->GetAttr(nsGkAtoms::width);
auto* height = aAttributes->GetAttr(nsGkAtoms::height);
if (width && height) {
DoMapAspectRatio(*width, *height, aDecls);
}
}
void nsGenericHTMLElement::MapImageBorderAttributeInto(
const nsMappedAttributes* aAttributes, MappedDeclarations& aDecls) {
// border: pixels
const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::border);
if (!value) return;
nscoord val = 0;
if (value->Type() == nsAttrValue::eInteger) val = value->GetIntegerValue();
aDecls.SetPixelValueIfUnset(eCSSProperty_border_top_width, (float)val);
aDecls.SetPixelValueIfUnset(eCSSProperty_border_right_width, (float)val);
aDecls.SetPixelValueIfUnset(eCSSProperty_border_bottom_width, (float)val);
aDecls.SetPixelValueIfUnset(eCSSProperty_border_left_width, (float)val);
aDecls.SetKeywordValueIfUnset(eCSSProperty_border_top_style,
StyleBorderStyle::Solid);
aDecls.SetKeywordValueIfUnset(eCSSProperty_border_right_style,
StyleBorderStyle::Solid);
aDecls.SetKeywordValueIfUnset(eCSSProperty_border_bottom_style,
StyleBorderStyle::Solid);
aDecls.SetKeywordValueIfUnset(eCSSProperty_border_left_style,
StyleBorderStyle::Solid);
aDecls.SetCurrentColorIfUnset(eCSSProperty_border_top_color);
aDecls.SetCurrentColorIfUnset(eCSSProperty_border_right_color);
aDecls.SetCurrentColorIfUnset(eCSSProperty_border_bottom_color);
aDecls.SetCurrentColorIfUnset(eCSSProperty_border_left_color);
}
void nsGenericHTMLElement::MapBackgroundInto(
const nsMappedAttributes* aAttributes, MappedDeclarations& aDecls) {
if (!aDecls.PropertyIsSet(eCSSProperty_background_image)) {
// background
nsAttrValue* value =
const_cast<nsAttrValue*>(aAttributes->GetAttr(nsGkAtoms::background));
if (value) {
aDecls.SetBackgroundImage(*value);
}
}
}
void nsGenericHTMLElement::MapBGColorInto(const nsMappedAttributes* aAttributes,
MappedDeclarations& aDecls) {
if (!aDecls.PropertyIsSet(eCSSProperty_background_color)) {
const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::bgcolor);
nscolor color;
if (value && value->GetColorValue(color)) {
aDecls.SetColorValue(eCSSProperty_background_color, color);
}
}
}
void nsGenericHTMLElement::MapBackgroundAttributesInto(
const nsMappedAttributes* aAttributes, MappedDeclarations& aDecls) {
MapBackgroundInto(aAttributes, aDecls);
MapBGColorInto(aAttributes, aDecls);
}
//----------------------------------------------------------------------
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;
bool hadAttr = GetURIAttr(aAttr, aBaseAttr, getter_AddRefs(uri));
if (!hadAttr) {
aResult.Truncate();
return;
}
if (!uri) {
// Just return the attr value
GetAttr(kNameSpaceID_None, aAttr, aResult);
return;
}
nsAutoCString spec;
uri->GetSpec(spec);
CopyUTF8toUTF16(spec, aResult);
}
bool nsGenericHTMLElement::GetURIAttr(nsAtom* aAttr, nsAtom* aBaseAttr,
nsIURI** aURI) const {
*aURI = nullptr;
const nsAttrValue* attr = mAttrs.GetAttr(aAttr);
if (!attr) {
return false;
}
nsCOMPtr<nsIURI> baseURI = GetBaseURI();
if (aBaseAttr) {
nsAutoString baseAttrValue;
if (GetAttr(kNameSpaceID_None, aBaseAttr, baseAttrValue)) {
nsCOMPtr<nsIURI> baseAttrURI;
nsresult rv = nsContentUtils::NewURIWithDocumentCharset(
getter_AddRefs(baseAttrURI), baseAttrValue, OwnerDoc(), baseURI);
if (NS_FAILED(rv)) {
return true;
}
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 true;
}
bool nsGenericHTMLElement::IsLabelable() const {
return IsAnyOfHTMLElements(nsGkAtoms::progress, nsGkAtoms::meter);
}
/* static */
bool nsGenericHTMLElement::MatchLabelsElement(Element* aElement,
int32_t aNamespaceID,
nsAtom* aAtom, void* aData) {
HTMLLabelElement* element = HTMLLabelElement::FromNode(aElement);
return element && element->GetControl() == aData;
}
already_AddRefed<nsINodeList> nsGenericHTMLElement::Labels() {
MOZ_ASSERT(IsLabelable(),
"Labels() only allow labelable elements to use it.");
nsExtendedDOMSlots* slots = ExtendedDOMSlots();
if (!slots->mLabelsList) {
slots->mLabelsList =
new nsLabelsNodeList(SubtreeRoot(), MatchLabelsElement, nullptr, this);
}
RefPtr<nsLabelsNodeList> labels = slots->mLabelsList;
return labels.forget();
}
// static
bool nsGenericHTMLElement::LegacyTouchAPIEnabled(JSContext* aCx,
JSObject* aGlobal) {
return TouchEvent::LegacyAPIEnabled(aCx, aGlobal);
}
bool nsGenericHTMLElement::IsFormControlDefaultFocusable(
bool aWithMouse) const {
if (!aWithMouse) {
return true;
}
switch (StaticPrefs::accessibility_mouse_focuses_formcontrol()) {
case 0:
return false;
case 1:
return true;
default:
return !IsInChromeDocument();
}
}
//----------------------------------------------------------------------
nsGenericHTMLFormElement::nsGenericHTMLFormElement(
already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
: nsGenericHTMLElement(std::move(aNodeInfo)) {
// We should add the ElementState::ENABLED bit here as needed, but
// that depends on our type, which is not initialized yet. So we
// have to do this in subclasses.
}
void nsGenericHTMLFormElement::ClearForm(bool aRemoveFromForm,
bool aUnbindOrDelete) {
MOZ_ASSERT(IsFormAssociatedElement());
HTMLFormElement* form = GetFormInternal();
NS_ASSERTION((form != nullptr) == HasFlag(ADDED_TO_FORM),
"Form control should have had flag set correctly");
if (!form) {
return;
}
if (aRemoveFromForm) {
nsAutoString nameVal, idVal;
GetAttr(nsGkAtoms::name, nameVal);
GetAttr(nsGkAtoms::id, idVal);
form->RemoveElement(this, true);
if (!nameVal.IsEmpty()) {
form->RemoveElementFromTable(this, nameVal);
}
if (!idVal.IsEmpty()) {
form->RemoveElementFromTable(this, idVal);
}
}
UnsetFlags(ADDED_TO_FORM);
SetFormInternal(nullptr, false);
AfterClearForm(aUnbindOrDelete);
UpdateState(true);
}
nsresult nsGenericHTMLFormElement::BindToTree(BindContext& aContext,
nsINode& aParent) {
nsresult rv = nsGenericHTMLElement::BindToTree(aContext, aParent);
NS_ENSURE_SUCCESS(rv, rv);
if (IsFormAssociatedElement()) {
// If @form is set, the element *has* to be in a composed document,
// otherwise it wouldn't be possible to find an element with the
// corresponding id. If @form isn't set, the element *has* to have a parent,
// otherwise it wouldn't be possible to find a form ancestor. We should not
// call UpdateFormOwner if none of these conditions are fulfilled.
if (HasAttr(kNameSpaceID_None, nsGkAtoms::form) ? IsInComposedDoc()
: aParent.IsContent()) {
UpdateFormOwner(true, nullptr);
}
}
// Set parent fieldset which should be used for the disabled state.
UpdateFieldSet(false);
return NS_OK;
}
void nsGenericHTMLFormElement::UnbindFromTree(bool aNullParent) {
if (IsFormAssociatedElement()) {
if (HTMLFormElement* form = GetFormInternal()) {
// Might need to unset form
if (aNullParent) {
// No more parent means no more form
ClearForm(true, true);
} else {
// Recheck whether we should still have an form.
if (HasAttr(kNameSpaceID_None, nsGkAtoms::form) ||
!FindAncestorForm(form)) {
ClearForm(true, true);
} else {
UnsetFlags(MAYBE_ORPHAN_FORM_ELEMENT);
}
}
if (!GetFormInternal()) {
// Our novalidate state might have changed
UpdateState(false);
}
}
// We have to remove the form id observer if there was one.
// We will re-add one later if needed (during bind to tree).
if (nsContentUtils::HasNonEmptyAttr(this, kNameSpaceID_None,
nsGkAtoms::form)) {
RemoveFormIdObserver();
}
}
nsGenericHTMLElement::UnbindFromTree(aNullParent);
// The element might not have a fieldset anymore.
UpdateFieldSet(false);
}
void nsGenericHTMLFormElement::BeforeSetAttr(int32_t aNameSpaceID,
nsAtom* aName,
const nsAttrValue* aValue,
bool aNotify) {
if (aNameSpaceID == kNameSpaceID_None && IsFormAssociatedElement()) {
nsAutoString tmp;
HTMLFormElement* form = GetFormInternal();
// remove the control from the hashtable as needed
if (form && (aName == nsGkAtoms::name || aName == nsGkAtoms::id)) {
GetAttr(kNameSpaceID_None, aName, tmp);
if (!tmp.IsEmpty()) {
form->RemoveElementFromTable(this, tmp);
}
}
if (form && aName == nsGkAtoms::type) {
GetAttr(kNameSpaceID_None, nsGkAtoms::name, tmp);
if (!tmp.IsEmpty()) {
form->RemoveElementFromTable(this, tmp);
}
GetAttr(kNameSpaceID_None, nsGkAtoms::id, tmp);
if (!tmp.IsEmpty()) {
form->RemoveElementFromTable(this, tmp);
}
form->RemoveElement(this, false);
}
if (aName == nsGkAtoms::form) {
// If @form isn't set or set to the empty string, there were no observer
// so we don't have to remove it.
if (nsContentUtils::HasNonEmptyAttr(this, kNameSpaceID_None,
nsGkAtoms::form)) {
// The current form id observer is no longer needed.
// A new one may be added in AfterSetAttr.
RemoveFormIdObserver();
}
}
}
return nsGenericHTMLElement::BeforeSetAttr(aNameSpaceID, aName, aValue,
aNotify);
}
void nsGenericHTMLFormElement::AfterSetAttr(
int32_t aNameSpaceID, nsAtom* aName, const nsAttrValue* aValue,
const nsAttrValue* aOldValue, nsIPrincipal* aMaybeScriptedPrincipal,
bool aNotify) {
if (aNameSpaceID == kNameSpaceID_None && IsFormAssociatedElement()) {
HTMLFormElement* form = GetFormInternal();
// add the control to the hashtable as needed
if (form && (aName == nsGkAtoms::name || aName == nsGkAtoms::id) &&
aValue && !aValue->IsEmptyString()) {
MOZ_ASSERT(aValue->Type() == nsAttrValue::eAtom,
"Expected atom value for name/id");
form->AddElementToTable(this,
nsDependentAtomString(aValue->GetAtomValue()));
}
if (form && aName == nsGkAtoms::type) {
nsAutoString tmp;
GetAttr(kNameSpaceID_None, nsGkAtoms::name, tmp);
if (!tmp.IsEmpty()) {
form->AddElementToTable(this, tmp);
}
GetAttr(kNameSpaceID_None, nsGkAtoms::id, tmp);
if (!tmp.IsEmpty()) {
form->AddElementToTable(this, tmp);
}
form->AddElement(this, false, aNotify);
}
if (aName == nsGkAtoms::form) {
// We need a new form id observer.
DocumentOrShadowRoot* docOrShadow =
GetUncomposedDocOrConnectedShadowRoot();
if (docOrShadow) {
Element* formIdElement = nullptr;
if (aValue && !aValue->IsEmptyString()) {
formIdElement = AddFormIdObserver();
}
// Because we have a new @form value (or no more @form), we have to
// update our form owner.
UpdateFormOwner(false, formIdElement);
}
}
}
return nsGenericHTMLElement::AfterSetAttr(
aNameSpaceID, aName, aValue, aOldValue, aMaybeScriptedPrincipal, aNotify);
}
void nsGenericHTMLFormElement::ForgetFieldSet(nsIContent* aFieldset) {
MOZ_DIAGNOSTIC_ASSERT(IsFormAssociatedElement());
if (GetFieldSetInternal() == aFieldset) {
SetFieldSetInternal(nullptr);
}
}
Element* nsGenericHTMLFormElement::AddFormIdObserver() {
MOZ_ASSERT(IsFormAssociatedElement());
nsAutoString formId;
DocumentOrShadowRoot* docOrShadow = GetUncomposedDocOrConnectedShadowRoot();
GetAttr(kNameSpaceID_None, nsGkAtoms::form, formId);
NS_ASSERTION(!formId.IsEmpty(),
"@form value should not be the empty string!");
RefPtr<nsAtom> atom = NS_Atomize(formId);
return docOrShadow->AddIDTargetObserver(atom, FormIdUpdated, this, false);
}
void nsGenericHTMLFormElement::RemoveFormIdObserver() {
MOZ_ASSERT(IsFormAssociatedElement());
DocumentOrShadowRoot* docOrShadow = GetUncomposedDocOrConnectedShadowRoot();
if (!docOrShadow) {
return;
}
nsAutoString formId;
GetAttr(kNameSpaceID_None, nsGkAtoms::form, formId);
NS_ASSERTION(!formId.IsEmpty(),
"@form value should not be the empty string!");
RefPtr<nsAtom> atom = NS_Atomize(formId);
docOrShadow->RemoveIDTargetObserver(atom, FormIdUpdated, this, false);
}
/* static */
bool nsGenericHTMLFormElement::FormIdUpdated(Element* aOldElement,
Element* aNewElement,
void* aData) {
nsGenericHTMLFormElement* element =
static_cast<nsGenericHTMLFormElement*>(aData);
NS_ASSERTION(element->IsHTMLElement(), "aData should be an HTML element");
element->UpdateFormOwner(false, aNewElement);
return true;
}
bool nsGenericHTMLFormElement::IsElementDisabledForEvents(WidgetEvent* aEvent,
nsIFrame* aFrame) {
MOZ_ASSERT(aEvent);
// Allow dispatch of CustomEvent and untrusted Events.
if (!aEvent->IsTrusted()) {
return false;
}
switch (aEvent->mMessage) {
case eAnimationStart:
case eAnimationEnd:
case eAnimationIteration:
case eAnimationCancel:
case eFormChange:
case eMouseMove:
case eMouseOver:
case eMouseOut:
case eMouseEnter:
case eMouseLeave:
case ePointerMove:
case ePointerOver:
case ePointerOut:
case ePointerEnter:
case ePointerLeave:
case eTransitionCancel:
case eTransitionEnd:
case eTransitionRun:
case eTransitionStart:
case eWheel:
case eLegacyMouseLineOrPageScroll:
case eLegacyMousePixelScroll:
return false;
case eFocus:
case eBlur:
case eFocusIn:
case eFocusOut:
case eKeyPress:
case eKeyUp:
case eKeyDown:
if (StaticPrefs::dom_forms_always_allow_key_and_focus_events_enabled()) {
return false;
}
[[fallthrough]];
case ePointerDown:
case ePointerUp:
case ePointerCancel:
case ePointerGotCapture:
case ePointerLostCapture:
if (StaticPrefs::dom_forms_always_allow_pointer_events_enabled()) {
return false;
}
[[fallthrough]];
default:
break;
}
if (aEvent->mSpecifiedEventType == nsGkAtoms::oninput) {
return false;
}
// FIXME(emilio): This poking at the style of the frame is slightly bogus
// unless we flush before every event, which we don't really want to do.
if (aFrame && aFrame->StyleUI()->UserInput() == StyleUserInput::None) {
return true;
}
return IsDisabled();
}
void nsGenericHTMLFormElement::UpdateFormOwner(bool aBindToTree,
Element* aFormIdElement) {
MOZ_ASSERT(IsFormAssociatedElement());
MOZ_ASSERT(!aBindToTree || !aFormIdElement,
"aFormIdElement shouldn't be set if aBindToTree is true!");
bool needStateUpdate = false;
if (!aBindToTree) {
HTMLFormElement* form = GetFormInternal();
needStateUpdate = form && form->IsDefaultSubmitElement(this);
ClearForm(true, false);
}
// We have to get form again since the above ClearForm() call might update the
// form value.
HTMLFormElement* oldForm = GetFormInternal();
if (!oldForm) {
// If @form is set, we have to use that to find the form.
nsAutoString formId;
if (GetAttr(kNameSpaceID_None, nsGkAtoms::form, formId)) {
if (!formId.IsEmpty()) {
Element* element = nullptr;
if (aBindToTree) {
element = AddFormIdObserver();
} else {
element = aFormIdElement;
}
NS_ASSERTION(!IsInComposedDoc() ||
element == GetUncomposedDocOrConnectedShadowRoot()
->GetElementById(formId),
"element should be equals to the current element "
"associated with the id in @form!");
if (element && element->IsHTMLElement(nsGkAtoms::form) &&
nsContentUtils::IsInSameAnonymousTree(this, element)) {
SetFormInternal(static_cast<HTMLFormElement*>(element), aBindToTree);
}
}
} else {
// We now have a parent, so we may have picked up an ancestor form. Search
// for it. Note that if form is already set we don't want to do this,
// because that means someone (probably the content sink) has already set
// it to the right value. Also note that even if being bound here didn't
// change our parent, we still need to search, since our parent chain
// probably changed _somewhere_.
SetFormInternal(FindAncestorForm(), aBindToTree);
}
}
HTMLFormElement* form = GetFormInternal();
if (form && !HasFlag(ADDED_TO_FORM)) {
// Now we need to add ourselves to the form
nsAutoString nameVal, idVal;
GetAttr(kNameSpaceID_None, nsGkAtoms::name, nameVal);
GetAttr(kNameSpaceID_None, nsGkAtoms::id, idVal);
SetFlags(ADDED_TO_FORM);
// Notify only if we just found this form.
form->AddElement(this, true, oldForm == nullptr);
if (!nameVal.IsEmpty()) {
form->AddElementToTable(this, nameVal);
}
if (!idVal.IsEmpty()) {
form->AddElementToTable(this, idVal);
}
}
if (form != oldForm || needStateUpdate) {
UpdateState(true);
}
}
void nsGenericHTMLFormElement::UpdateFieldSet(bool aNotify) {
if (IsInNativeAnonymousSubtree() || !IsFormAssociatedElement()) {
MOZ_ASSERT_IF(IsFormAssociatedElement(), !GetFieldSetInternal());
return;
}
nsIContent* parent = nullptr;
nsIContent* prev = nullptr;
HTMLFieldSetElement* fieldset = GetFieldSetInternal();
for (parent = GetParent(); parent;
prev = parent, parent = parent->GetParent()) {
HTMLFieldSetElement* parentFieldset = HTMLFieldSetElement::FromNode(parent);
if (parentFieldset && (!prev || parentFieldset->GetFirstLegend() != prev)) {
if (fieldset == parentFieldset) {
// We already have the right fieldset;
return;
}
if (fieldset) {
fieldset->RemoveElement(this);
}
SetFieldSetInternal(parentFieldset);
parentFieldset->AddElement(this);
// The disabled state may have changed
FieldSetDisabledChanged(aNotify);
return;
}
}
// No fieldset found.
if (fieldset) {
fieldset->RemoveElement(this);
SetFieldSetInternal(nullptr);
// The disabled state may have changed
FieldSetDisabledChanged(aNotify);
}
}
void nsGenericHTMLFormElement::UpdateDisabledState(bool aNotify) {
if (!CanBeDisabled()) {
return;
}
HTMLFieldSetElement* fieldset = GetFieldSetInternal();
const bool isDisabled =
HasAttr(nsGkAtoms::disabled) || (fieldset && fieldset->IsDisabled());
const ElementState disabledStates =
isDisabled ? ElementState::DISABLED : ElementState::ENABLED;
ElementState oldDisabledStates = State() & ElementState::DISABLED_STATES;
ElementState changedStates = disabledStates ^ oldDisabledStates;
if (!changedStates.IsEmpty()) {
ToggleStates(changedStates, aNotify);
if (DoesReadOnlyApply()) {
// :disabled influences :read-only / :read-write.
UpdateState(aNotify);
}
}
}
void nsGenericHTMLFormElement::FieldSetDisabledChanged(bool aNotify) {
UpdateDisabledState(aNotify);
}
//----------------------------------------------------------------------
void nsGenericHTMLElement::Click(CallerType aCallerType) {
if (HandlingClick()) {
return;
}
// There are two notions of disabled.
// "disabled":
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#attr-fe-disabled
// "actually disabled":
// https://html.spec.whatwg.org/multipage/semantics-other.html#concept-element-disabled
// click() reads the former but IsDisabled() is for the latter. <fieldset> is
// included only in the latter, so we exclude it here.
// XXX(krosylight): What about <optgroup>? And should we add a separate method
// for this?
if (IsDisabled() &&
!(mNodeInfo->Equals(nsGkAtoms::fieldset) &&
StaticPrefs::dom_forms_fieldset_disable_only_descendants_enabled())) {
return;
}
// Strong in case the event kills it
nsCOMPtr<Document> doc = GetComposedDoc();
RefPtr<nsPresContext> context;
if (doc) {
context = doc->GetPresContext();
}
SetHandlingClick();
// Mark this event trusted if Click() is called from system code.
WidgetMouseEvent event(aCallerType == CallerType::System, eMouseClick,
nullptr, WidgetMouseEvent::eReal);
event.mFlags.mIsPositionless = true;
event.mInputSource = MouseEvent_Binding::MOZ_SOURCE_UNKNOWN;
EventDispatcher::Dispatch(static_cast<nsIContent*>(this), context, &event);
ClearHandlingClick();
}
bool nsGenericHTMLElement::IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable,
int32_t* aTabIndex) {
if (ShadowRoot* root = GetShadowRoot()) {
if (root->DelegatesFocus()) {
*aIsFocusable = false;
return true;
}
}
Document* doc = GetComposedDoc();
if (!doc || IsInDesignMode()) {
// In designMode documents we only allow focusing the document.
if (aTabIndex) {
*aTabIndex = -1;
}
*aIsFocusable = false;
return true;
}
int32_t tabIndex = TabIndex();
bool disabled = false;
bool disallowOverridingFocusability = true;
Maybe<int32_t> attrVal = GetTabIndexAttrValue();
if (IsEditableRoot()) {
// Editable roots should always be focusable.
disallowOverridingFocusability = true;
// Ignore the disabled attribute in editable contentEditable/designMode
// roots.
if (attrVal.isNothing()) {
// The default value for tabindex should be 0 for editable
// contentEditable roots.
tabIndex = 0;
}
} else {
disallowOverridingFocusability = false;
// Just check for disabled attribute on form controls
disabled = IsDisabled();
if (disabled) {
tabIndex = -1;
}
}
if (aTabIndex) {
*aTabIndex = tabIndex;
}
// If a tabindex is specified at all, or the default tabindex is 0, we're
// focusable
*aIsFocusable = (tabIndex >= 0 || (!disabled && attrVal.isSome()));
return disallowOverridingFocusability;
}
Result<bool, nsresult> nsGenericHTMLElement::PerformAccesskey(
bool aKeyCausesActivation, bool aIsTrustedEvent) {
RefPtr<nsPresContext> presContext = GetPresContext(eForComposedDoc);
if (!presContext) {
return Err(NS_ERROR_UNEXPECTED);
}
// It's hard to say what HTML4 wants us to do in all cases.
bool focused = true;
if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
fm->SetFocus(this, nsIFocusManager::FLAG_BYKEY);
// Return true if the element became the current focus within its window.
nsPIDOMWindowOuter* window = OwnerDoc()->GetWindow();
focused = window && window->GetFocusedElement() == this;
}
if (aKeyCausesActivation) {
// Click on it if the users prefs indicate to do so.
AutoHandlingUserInputStatePusher userInputStatePusher(aIsTrustedEvent);
AutoPopupStatePusher popupStatePusher(
aIsTrustedEvent ? PopupBlocker::openAllowed : PopupBlocker::openAbused);
DispatchSimulatedClick(this, aIsTrustedEvent, presContext);
return focused;
}
// If the accesskey won't cause the activation and the focus isn't changed,
// either. Return error so EventStateManager would try to find next element
// to handle the accesskey.
return focused ? Result<bool, nsresult>{focused} : Err(NS_ERROR_ABORT);
}
void nsGenericHTMLElement::HandleKeyboardActivation(
EventChainPostVisitor& aVisitor) {
MOZ_ASSERT(aVisitor.mEvent->HasKeyEventMessage());
MOZ_ASSERT(aVisitor.mEvent->IsTrusted());
// If focused element is different from this element, it may be editable.
// In that case, associated editor for the element should handle the keyboard
// instead. Therefore, if this is not the focused element, we should not
// handle the event here. Note that this element may be an editing host,
// i.e., focused and editable. In the case, keyboard events should be
// handled by the focused element instead of associated editor because
// Chrome handles the case so. For compatibility with Chrome, we follow them.
if (nsFocusManager::GetFocusedElementStatic() != this) {
return;
}
const auto message = aVisitor.mEvent->mMessage;
const WidgetKeyboardEvent* keyEvent = aVisitor.mEvent->AsKeyboardEvent();
if (nsEventStatus_eIgnore != aVisitor.mEventStatus) {
if (message == eKeyUp && keyEvent->mKeyCode == NS_VK_SPACE) {
// Unset the flag even if the event is default-prevented or something.
UnsetFlags(HTML_ELEMENT_ACTIVE_FOR_KEYBOARD);
}
return;
}
bool shouldActivate = false;
switch (message) {
case eKeyDown:
if (keyEvent->ShouldWorkAsSpaceKey()) {
SetFlags(HTML_ELEMENT_ACTIVE_FOR_KEYBOARD);
}
return;
case eKeyPress:
shouldActivate = keyEvent->mKeyCode == NS_VK_RETURN;
if (keyEvent->ShouldWorkAsSpaceKey()) {
// Consume 'space' key to prevent scrolling the page down.
aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
}
break;
case eKeyUp:
shouldActivate = keyEvent->ShouldWorkAsSpaceKey() &&
HasFlag(HTML_ELEMENT_ACTIVE_FOR_KEYBOARD);
if (shouldActivate) {
UnsetFlags(HTML_ELEMENT_ACTIVE_FOR_KEYBOARD);
}
break;
default:
MOZ_ASSERT_UNREACHABLE("why didn't we bail out earlier?");
break;
}
if (!shouldActivate) {
return;
}
RefPtr<nsPresContext> presContext = aVisitor.mPresContext;
DispatchSimulatedClick(this, aVisitor.mEvent->IsTrusted(), presContext);
aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
}
nsresult nsGenericHTMLElement::DispatchSimulatedClick(
nsGenericHTMLElement* aElement, bool aIsTrusted,
nsPresContext* aPresContext) {
WidgetMouseEvent event(aIsTrusted, eMouseClick, nullptr,
WidgetMouseEvent::eReal);
event.mInputSource = MouseEvent_Binding::MOZ_SOURCE_KEYBOARD;
event.mFlags.mIsPositionless = true;
// TODO: Bug 1506441
return EventDispatcher::Dispatch(MOZ_KnownLive(ToSupports(aElement)),
aPresContext, &event);
}
already_AddRefed<EditorBase> nsGenericHTMLElement::GetAssociatedEditor() {
// If contenteditable is ever implemented, it might need to do something
// different here?
RefPtr<TextEditor> textEditor = GetTextEditorInternal();
return textEditor.forget();
}
// static
void nsGenericHTMLElement::SyncEditorsOnSubtree(nsIContent* content) {
/* Sync this node */
nsGenericHTMLElement* element = FromNode(content);
if (element) {
if (RefPtr<EditorBase> editorBase = element->GetAssociatedEditor()) {
editorBase->SyncRealTimeSpell();
}
}
/* Sync all children */
for (nsIContent* child = content->GetFirstChild(); child;
child = child->GetNextSibling()) {
SyncEditorsOnSubtree(child);
}
}
bool nsGenericHTMLElement::IsEditableRoot() const {
if (!IsInComposedDoc()) {
return false;
}
if (IsInDesignMode()) {
return false;
}
if (GetContentEditableValue() != eTrue) {
return false;
}
nsIContent* parent = GetParent();
return !parent || !parent->HasFlag(NODE_IS_EDITABLE);
}
static void MakeContentDescendantsEditable(nsIContent* aContent) {
// If aContent is not an element, we just need to update its
// internal editable state and don't need to notify anyone about
// that. For elements, we need to send a ElementStateChanged
// notification.
if (!aContent->IsElement()) {
aContent->UpdateEditableState(false);
return;
}
Element* element = aContent->AsElement();
element->UpdateEditableState(true);
for (nsIContent* child = aContent->GetFirstChild(); child;
child = child->GetNextSibling()) {
if (!child->IsElement() ||
!child->AsElement()->HasAttr(kNameSpaceID_None,
nsGkAtoms::contenteditable)) {
MakeContentDescendantsEditable(child);
}
}
}
void nsGenericHTMLElement::ChangeEditableState(int32_t aChange) {
Document* document = GetComposedDoc();
if (!document) {
return;
}
Document::EditingState previousEditingState = Document::EditingState::eOff;
if (aChange != 0) {
document->ChangeContentEditableCount(this, aChange);
previousEditingState = document->GetEditingState();
}
// MakeContentDescendantsEditable is going to call ElementStateChanged for
// this element and all descendants if editable state has changed.
// We might as well wrap it all in one script blocker.
nsAutoScriptBlocker scriptBlocker;
MakeContentDescendantsEditable(this);
// If the document already had contenteditable and JS adds new
// contenteditable, that might cause changing editing host to current editing
// host's ancestor. In such case, HTMLEditor needs to know that
// synchronously to update selection limitter.
// Additionally, elements in shadow DOM is not editable in the normal cases,
// but if its content has `contenteditable`, only in it can be ediable.
// So we don't need to notify HTMLEditor of this change only when we're not
// in shadow DOM and the composed document is in design mode.
if (IsInDesignMode() && !IsInShadowTree() && aChange > 0 &&
previousEditingState == Document::EditingState::eContentEditable) {
if (HTMLEditor* htmlEditor =
nsContentUtils::GetHTMLEditor(document->GetPresContext())) {
htmlEditor->NotifyEditingHostMaybeChanged();
}
}
}
//----------------------------------------------------------------------
nsGenericHTMLFormControlElement::nsGenericHTMLFormControlElement(
already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo, FormControlType aType)
: nsGenericHTMLFormElement(std::move(aNodeInfo)),
nsIFormControl(aType),
mForm(nullptr),
mFieldSet(nullptr) {}
nsGenericHTMLFormControlElement::~nsGenericHTMLFormControlElement() {
if (mFieldSet) {
mFieldSet->RemoveElement(this);
}
// Check that this element doesn't know anything about its form at this point.
NS_ASSERTION(!mForm, "mForm should be null at this point!");
}
NS_IMPL_ISUPPORTS_INHERITED(nsGenericHTMLFormControlElement,
nsGenericHTMLFormElement, nsIFormControl)
nsINode* nsGenericHTMLFormControlElement::GetScopeChainParent() const {
return mForm ? mForm : nsGenericHTMLElement::GetScopeChainParent();
}
void nsGenericHTMLFormControlElement::SaveSubtreeState() {
SaveState();
nsGenericHTMLFormElement::SaveSubtreeState();
}
nsIContent::IMEState nsGenericHTMLFormControlElement::GetDesiredIMEState() {
TextEditor* textEditor = GetTextEditorInternal();
if (!textEditor) {
return nsGenericHTMLFormElement::GetDesiredIMEState();
}
IMEState state;
nsresult rv = textEditor->GetPreferredIMEState(&state);
if (NS_FAILED(rv)) {
return nsGenericHTMLFormElement::GetDesiredIMEState();
}
return state;
}
void nsGenericHTMLFormControlElement::UnbindFromTree(bool aNullParent) {
// Save state before doing anything
SaveState();
nsGenericHTMLFormElement::UnbindFromTree(aNullParent);
}
void nsGenericHTMLFormControlElement::GetAutocapitalize(
nsAString& aValue) const {
if (nsContentUtils::HasNonEmptyAttr(this, kNameSpaceID_None,
nsGkAtoms::autocapitalize)) {
nsGenericHTMLFormElement::GetAutocapitalize(aValue);
return;
}
if (mForm && IsAutocapitalizeInheriting()) {
mForm->GetAutocapitalize(aValue);
}
}
bool nsGenericHTMLFormControlElement::IsHTMLFocusable(bool aWithMouse,
bool* aIsFocusable,
int32_t* aTabIndex) {
if (nsGenericHTMLFormElement::IsHTMLFocusable(aWithMouse, aIsFocusable,
aTabIndex)) {
return true;
}
*aIsFocusable = *aIsFocusable && IsFormControlDefaultFocusable(aWithMouse);
return false;
}
void nsGenericHTMLFormControlElement::GetEventTargetParent(
EventChainPreVisitor& aVisitor) {
if (aVisitor.mEvent->IsTrusted() && (aVisitor.mEvent->mMessage == eFocus ||
aVisitor.mEvent->mMessage == eBlur)) {
// We have to handle focus/blur event to change focus states in
// PreHandleEvent to prevent it breaks event target chain creation.
aVisitor.mWantsPreHandleEvent = true;
}
nsGenericHTMLFormElement::GetEventTargetParent(aVisitor);
}
nsresult nsGenericHTMLFormControlElement::PreHandleEvent(
EventChainVisitor& aVisitor) {
if (aVisitor.mEvent->IsTrusted()) {
switch (aVisitor.mEvent->mMessage) {
case eFocus: {
// Check to see if focus has bubbled up from a form control's
// child textfield or button. If that's the case, don't focus
// this parent file control -- leave focus on the child.
nsIFormControlFrame* formControlFrame = GetFormControlFrame(true);
if (formControlFrame &&
aVisitor.mEvent->mOriginalTarget == static_cast<nsINode*>(this)) {
formControlFrame->SetFocus(true, true);
}
break;
}
case eBlur: {
nsIFormControlFrame* formControlFrame = GetFormControlFrame(true);
if (formControlFrame) {
formControlFrame->SetFocus(false, false);
}
break;
}
default:
break;
}
}
return nsGenericHTMLFormElement::PreHandleEvent(aVisitor);
}
HTMLFieldSetElement* nsGenericHTMLFormControlElement::GetFieldSet() {
return GetFieldSetInternal();
}
void nsGenericHTMLFormControlElement::SetForm(HTMLFormElement* aForm) {
MOZ_ASSERT(aForm, "Don't pass null here");
NS_ASSERTION(!mForm,
"We don't support switching from one non-null form to another.");
SetFormInternal(aForm, false);
}
void nsGenericHTMLFormControlElement::ClearForm(bool aRemoveFromForm,
bool aUnbindOrDelete) {
nsGenericHTMLFormElement::ClearForm(aRemoveFromForm, aUnbindOrDelete);
}
ElementState nsGenericHTMLFormControlElement::IntrinsicState() const {
// If you add attribute-dependent states here, you need to add them to
// AfterSetAttr too. And add them to AfterSetAttr for all subclasses that
// implement IntrinsicState() and are affected by that attribute.
ElementState state = nsGenericHTMLFormElement::IntrinsicState();
if (mForm && mForm->IsDefaultSubmitElement(this)) {
NS_ASSERTION(IsSubmitControl(),
"Default submit element that isn't a submit control.");
// We are the default submit element (:default)
state |= ElementState::DEFAULT;
}
// Make the text controls read-write
if (!state.HasState(ElementState::READWRITE) && DoesReadOnlyApply()) {
if (!GetBoolAttr(nsGkAtoms::readonly) && !IsDisabled()) {
state |= ElementState::READWRITE;
state &= ~ElementState::READONLY;
}
}
return state;
}
bool nsGenericHTMLFormControlElement::IsLabelable() const {
auto type = ControlType();
return (IsInputElement(type) && type != FormControlType::InputHidden) ||
IsButtonElement(type) || type == FormControlType::Output ||
type == FormControlType::Select || type == FormControlType::Textarea;
}
bool nsGenericHTMLFormControlElement::CanBeDisabled() const {
auto type = ControlType();
// It's easier to test the types that _cannot_ be disabled
return type != FormControlType::Object && type != FormControlType::Output;
}
bool nsGenericHTMLFormControlElement::DoesReadOnlyApply() const {
auto type = ControlType();
if (!IsInputElement(type) && type != FormControlType::Textarea) {
return false;
}
switch (type) {
case FormControlType::InputHidden:
case FormControlType::InputButton:
case FormControlType::InputImage:
case FormControlType::InputReset:
case FormControlType::InputSubmit:
case FormControlType::InputRadio:
case FormControlType::InputFile:
case FormControlType::InputCheckbox:
case FormControlType::InputRange:
case FormControlType::InputColor:
return false;
#ifdef DEBUG
case FormControlType::Textarea:
case FormControlType::InputText:
case FormControlType::InputPassword:
case FormControlType::InputSearch:
case FormControlType::InputTel:
case FormControlType::InputEmail:
case FormControlType::InputUrl:
case FormControlType::InputNumber:
case FormControlType::InputDate:
case FormControlType::InputTime:
case FormControlType::InputMonth:
case FormControlType::InputWeek:
case FormControlType::InputDatetimeLocal:
return true;
default:
MOZ_ASSERT_UNREACHABLE("Unexpected input type in DoesReadOnlyApply()");
return true;
#else // DEBUG
default:
return true;
#endif // DEBUG
}
}
void nsGenericHTMLFormControlElement::SetFormInternal(HTMLFormElement* aForm,
bool aBindToTree) {
if (aForm) {
BeforeSetForm(aBindToTree);
}
// keep a *weak* ref to the form here
mForm = aForm;
}
HTMLFormElement* nsGenericHTMLFormControlElement::GetFormInternal() const {
return mForm;
}
HTMLFieldSetElement* nsGenericHTMLFormControlElement::GetFieldSetInternal()
const {
return mFieldSet;
}
void nsGenericHTMLFormControlElement::SetFieldSetInternal(
HTMLFieldSetElement* aFieldset) {
mFieldSet = aFieldset;
}
void nsGenericHTMLFormControlElement::UpdateRequiredState(bool aIsRequired,
bool aNotify) {
#ifdef DEBUG
auto type = ControlType();
#endif
MOZ_ASSERT(IsInputElement(type) || type == FormControlType::Select ||
type == FormControlType::Textarea,
"This should be called only on types that @required applies");
#ifdef DEBUG
if (HTMLInputElement* input = HTMLInputElement::FromNode(this)) {
MOZ_ASSERT(
input->DoesRequiredApply(),
"This should be called only on input types that @required applies");
}
#endif
ElementState requiredStates;
if (aIsRequired) {
requiredStates |= ElementState::REQUIRED;
} else {
requiredStates |= ElementState::OPTIONAL_;
}
ElementState oldRequiredStates = State() & ElementState::REQUIRED_STATES;
ElementState changedStates = requiredStates ^ oldRequiredStates;
if (!changedStates.IsEmpty()) {
ToggleStates(changedStates, aNotify);
}
}
bool nsGenericHTMLFormControlElement::IsAutocapitalizeInheriting() const {
auto type = ControlType();
return IsInputElement(type) || IsButtonElement(type) ||
type == FormControlType::Fieldset || type == FormControlType::Output ||
type == FormControlType::Select || type == FormControlType::Textarea;
}
nsresult nsGenericHTMLFormControlElement::SubmitDirnameDir(
FormData* aFormData) {
// Submit dirname=dir if element has non-empty dirname attribute
if (HasAttr(kNameSpaceID_None, nsGkAtoms::dirname)) {
nsAutoString dirname;
GetAttr(kNameSpaceID_None, nsGkAtoms::dirname, dirname);
if (!dirname.IsEmpty()) {
const Directionality eDir = GetDirectionality();
MOZ_ASSERT(eDir == eDir_RTL || eDir == eDir_LTR,
"The directionality of an element is either ltr or rtl");
const nsString dir = eDir == eDir_LTR ? u"ltr"_ns : u"rtl"_ns;
return aFormData->AddNameValuePair(dirname, dir);
}
}
return NS_OK;
}
//----------------------------------------------------------------------
static const nsAttrValue::EnumTable kPopoverTargetActionTable[] = {
{"toggle", PopoverTargetAction::Toggle},
{"show", PopoverTargetAction::Show},
{"hide", PopoverTargetAction::Hide},
{nullptr, 0}};
static const nsAttrValue::EnumTable* kPopoverTargetActionDefault =
&kPopoverTargetActionTable[0];
nsGenericHTMLFormControlElementWithState::
nsGenericHTMLFormControlElementWithState(
already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
FromParser aFromParser, FormControlType aType)
: nsGenericHTMLFormControlElement(std::move(aNodeInfo), aType),
mControlNumber(!!(aFromParser & FROM_PARSER_NETWORK)
? OwnerDoc()->GetNextControlNumber()
: -1) {
mStateKey.SetIsVoid(true);
}
bool nsGenericHTMLFormControlElementWithState::ParseAttribute(
int32_t aNamespaceID, nsAtom* aAttribute, const nsAString& aValue,
nsIPrincipal* aMaybeScriptedPrincipal, nsAttrValue& aResult) {
if (aNamespaceID == kNameSpaceID_None &&
StaticPrefs::dom_element_popover_enabled()) {
if (aAttribute == nsGkAtoms::popovertargetaction) {
return aResult.ParseEnumValue(aValue, kPopoverTargetActionTable, false,
kPopoverTargetActionDefault);
}
if (aAttribute == nsGkAtoms::popovertarget) {
aResult.ParseAtom(aValue);
return true;
}
}
return nsGenericHTMLFormControlElement::ParseAttribute(
aNamespaceID, aAttribute, aValue, aMaybeScriptedPrincipal, aResult);
}
mozilla::dom::Element*
nsGenericHTMLFormControlElementWithState::GetPopoverTargetElement() const {
return GetAttrAssociatedElement(nsGkAtoms::popovertarget);
}
void nsGenericHTMLFormControlElementWithState::SetPopoverTargetElement(
mozilla::dom::Element* aElement) {
ExplicitlySetAttrElement(nsGkAtoms::popovertarget, aElement);
}
void nsGenericHTMLFormControlElementWithState::HandlePopoverTargetAction() {
RefPtr<nsGenericHTMLElement> target = GetEffectivePopoverTargetElement();
if (!target) {
return;
}
auto action = PopoverTargetAction::Toggle;
if (const nsAttrValue* value =
GetParsedAttr(nsGkAtoms::popovertargetaction)) {
MOZ_ASSERT(value->Type() == nsAttrValue::eEnum);
action = static_cast<PopoverTargetAction>(value->GetEnumValue());
}
bool canHide = action == PopoverTargetAction::Hide ||
action == PopoverTargetAction::Toggle;
bool canShow = action == PopoverTargetAction::Show ||
action == PopoverTargetAction::Toggle;
if (canHide && target->IsPopoverOpen()) {
target->HidePopover(IgnoreErrors());
} else if (canShow && !target->IsPopoverOpen()) {
target->ShowPopoverInternal(this, IgnoreErrors());
}
}
void nsGenericHTMLFormControlElementWithState::GenerateStateKey() {
// Keep the key if already computed
if (!mStateKey.IsVoid()) {
return;
}
Document* doc = GetUncomposedDoc();
if (!doc) {
mStateKey.Truncate();
return;
}
// Generate the state key
nsContentUtils::GenerateStateKey(this, doc, mStateKey);
// If the state key is blank, this is anonymous content or for whatever
// reason we are not supposed to save/restore state: keep it as such.
if (!mStateKey.IsEmpty()) {
// Add something unique to content so layout doesn't muck us up.
mStateKey += "-C";
}
}
PresState* nsGenericHTMLFormControlElementWithState::GetPrimaryPresState() {
if (mStateKey.IsEmpty()) {
return nullptr;
}
nsCOMPtr<nsILayoutHistoryState> history = GetLayoutHistory(false);
if (!history) {
return nullptr;
}
// Get the pres state for this key, if it doesn't exist, create one.
PresState* result = history->GetState(mStateKey);
if (!result) {
UniquePtr<PresState> newState = NewPresState();
result = newState.get();
history->AddState(mStateKey, std::move(newState));
}
return result;
}
already_AddRefed<nsILayoutHistoryState>
nsGenericHTMLFormControlElementWithState::GetLayoutHistory(bool aRead) {
nsCOMPtr<Document> doc = GetUncomposedDoc();
if (!doc) {
return nullptr;
}
//
// Get the history
//
nsCOMPtr<nsILayoutHistoryState> history = doc->GetLayoutHistoryState();
if (!history) {
return nullptr;
}
if (aRead && !history->HasStates()) {
return nullptr;
}
return history.forget();
}
bool nsGenericHTMLFormControlElementWithState::RestoreFormControlState() {
MOZ_ASSERT(!mStateKey.IsVoid(),
"GenerateStateKey must already have been called");
if (mStateKey.IsEmpty()) {
return false;
}
nsCOMPtr<nsILayoutHistoryState> history = GetLayoutHistory(true);
if (!history) {
return false;
}
// Get the pres state for this key
PresState* state = history->GetState(mStateKey);
if (state) {
bool result = RestoreState(state);
history->RemoveState(mStateKey);
return result;
}
return false;
}
void nsGenericHTMLFormControlElementWithState::NodeInfoChanged(
Document* aOldDoc) {
nsGenericHTMLFormControlElement::NodeInfoChanged(aOldDoc);
// We need to regenerate the state key now we're in a new document. Clearing
// mControlNumber means we stop considering this control to be parser
// inserted, and we'll generate a state key based on its position in the
// document rather than the order it was inserted into the document.
mControlNumber = -1;
mStateKey.SetIsVoid(true);
}
void nsGenericHTMLFormControlElementWithState::GetFormAction(nsString& aValue) {
auto type = ControlType();
if (!IsInputElement(type) && !IsButtonElement(type)) {
return;
}
if (!GetAttr(kNameSpaceID_None, nsGkAtoms::formaction, aValue) ||
aValue.IsEmpty()) {
Document* document = OwnerDoc();
nsIURI* docURI = document->GetDocumentURI();
if (docURI) {
nsAutoCString spec;
nsresult rv = docURI->GetSpec(spec);
if (NS_FAILED(rv)) {
return;
}
CopyUTF8toUTF16(spec, aValue);
}
} else {
GetURIAttr(nsGkAtoms::formaction, nullptr, aValue);
}
}
bool nsGenericHTMLElement::IsEventAttributeNameInternal(nsAtom* aName) {
return nsContentUtils::IsEventAttributeName(aName, EventNameType_HTML);
}
/**
* Construct a URI from a string, as an element.src attribute
* would be set to. Helper for the media elements.
*/
nsresult nsGenericHTMLElement::NewURIFromString(const nsAString& aURISpec,
nsIURI** aURI) {
NS_ENSURE_ARG_POINTER(aURI);
*aURI = nullptr;
nsCOMPtr<Document> doc = OwnerDoc();
nsresult rv = nsContentUtils::NewURIWithDocumentCharset(aURI, aURISpec, doc,
GetBaseURI());
NS_ENSURE_SUCCESS(rv, rv);
bool equal;
if (aURISpec.IsEmpty() && doc->GetDocumentURI() &&
NS_SUCCEEDED(doc->GetDocumentURI()->Equals(*aURI, &equal)) && equal) {
// Assume an element can't point to a fragment of its embedding
// document. Fail here instead of returning the recursive URI
// and waiting for the subsequent load to fail.
NS_RELEASE(*aURI);
return NS_ERROR_DOM_INVALID_STATE_ERR;
}
return NS_OK;
}
void nsGenericHTMLElement::GetInnerText(mozilla::dom::DOMString& aValue,
mozilla::ErrorResult& aError) {
// innerText depends on layout. For example, white space processing is
// something that happens during reflow and which must be reflected by
// innerText. So for:
//
// <div style="white-space:normal"> A B C </div>
//
// innerText should give "A B C".
//
// The approach taken here to avoid the expense of reflow is to flush style
// and then see whether it's necessary to flush layout afterwards. Flushing
// layout can be skipped if we can detect that the element or its descendants
// are not dirty.
// Obtain the composed doc to handle elements in Shadow DOM.
Document* doc = GetComposedDoc();
if (doc) {
doc->FlushPendingNotifications(FlushType::Style);
}
// Elements with `display: content` will not have a frame. To handle Shadow
// DOM, walk the flattened tree looking for parent frame.
nsIFrame* frame = GetPrimaryFrame();
if (IsDisplayContents()) {
for (Element* parent = GetFlattenedTreeParentElement(); parent;
parent = parent->GetFlattenedTreeParentElement()) {
frame = parent->GetPrimaryFrame();
if (frame) {
break;
}
}
}
// Check for dirty reflow roots in the subtree from targetFrame; this requires
// a reflow flush.
bool dirty = frame && frame->PresShell()->FrameIsAncestorOfDirtyRoot(frame);
// The way we do that is by checking whether the element has either of the two
// dirty bits (NS_FRAME_IS_DIRTY or NS_FRAME_HAS_DIRTY_DESCENDANTS) or if any
// ancestor has NS_FRAME_IS_DIRTY. We need to check for NS_FRAME_IS_DIRTY on
// ancestors since that is something that implies NS_FRAME_IS_DIRTY on all
// descendants.
dirty |= frame && frame->HasAnyStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
while (!dirty && frame) {
dirty |= frame->HasAnyStateBits(NS_FRAME_IS_DIRTY);
frame = frame->GetInFlowParent();
}
// Flush layout if we determined a reflow is required.
if (dirty && doc) {
doc->FlushPendingNotifications(FlushType::Layout);
}
if (!IsRendered()) {
GetTextContentInternal(aValue, aError);
} else {
nsRange::GetInnerTextNoFlush(aValue, aError, this);
}
}
static already_AddRefed<nsINode> TextToNode(const nsAString& aString,
nsNodeInfoManager* aNim) {
nsString str;
const char16_t* s = aString.BeginReading();
const char16_t* end = aString.EndReading();
RefPtr<DocumentFragment> fragment;
while (true) {
if (s != end && *s == '\r' && s + 1 != end && s[1] == '\n') {
// a \r\n pair should only generate one <br>, so just skip the \r
++s;
}
if (s == end || *s == '\r' || *s == '\n') {
if (!str.IsEmpty()) {
RefPtr<nsTextNode> textContent = new (aNim) nsTextNode(aNim);
textContent->SetText(str, true);
if (!fragment) {
if (s == end) {
return textContent.forget();
}
fragment = new (aNim) DocumentFragment(aNim);
}
fragment->AppendChildTo(textContent, true, IgnoreErrors());
}
if (s == end) {
break;
}
str.Truncate();
RefPtr<NodeInfo> ni = aNim->GetNodeInfo(
nsGkAtoms::br, nullptr, kNameSpaceID_XHTML, nsINode::ELEMENT_NODE);
auto* nim = ni->NodeInfoManager();
RefPtr<HTMLBRElement> br = new (nim) HTMLBRElement(ni.forget());
if (!fragment) {
if (s + 1 == end) {
return br.forget();
}
fragment = new (aNim) DocumentFragment(aNim);
}
fragment->AppendChildTo(br, true, IgnoreErrors());
} else {
str.Append(*s);
}
++s;
}
return fragment.forget();
}
void nsGenericHTMLElement::SetInnerText(const nsAString& aValue) {
RefPtr<nsINode> node = TextToNode(aValue, NodeInfo()->NodeInfoManager());
ReplaceChildren(node, IgnoreErrors());
}
// https://html.spec.whatwg.org/#merge-with-the-next-text-node
static void MergeWithNextTextNode(Text& aText, ErrorResult& aRv) {
RefPtr<Text> nextSibling = Text::FromNodeOrNull(aText.GetNextSibling());
if (!nextSibling) {
return;
}
nsAutoString data;
nextSibling->GetData(data);
aText.AppendData(data, aRv);
nextSibling->Remove();
}
// https://html.spec.whatwg.org/#dom-outertext
void nsGenericHTMLElement::SetOuterText(const nsAString& aValue,
ErrorResult& aRv) {
nsCOMPtr<nsINode> parent = GetParentNode();
if (!parent) {
return aRv.ThrowNoModificationAllowedError("Element has no parent");
}
RefPtr<nsINode> next = GetNextSibling();
RefPtr<nsINode> previous = GetPreviousSibling();
// Batch possible DOMSubtreeModified events.
mozAutoSubtreeModified subtree(OwnerDoc(), nullptr);
nsNodeInfoManager* nim = NodeInfo()->NodeInfoManager();
RefPtr<nsINode> node = TextToNode(aValue, nim);
if (!node) {
// This doesn't match the spec, see
// https://github.com/whatwg/html/issues/7508
node = new (nim) nsTextNode(nim);
}
parent->ReplaceChild(*node, *this, aRv);
if (aRv.Failed()) {
return;
}
if (next) {
if (RefPtr<Text> text = Text::FromNodeOrNull(next->GetPreviousSibling())) {
MergeWithNextTextNode(*text, aRv);
if (aRv.Failed()) {
return;
}
}
}
if (auto* text = Text::FromNodeOrNull(previous)) {
MergeWithNextTextNode(*text, aRv);
}
}
// This should be true when `:open` should match.
bool nsGenericHTMLElement::PopoverOpen() const {
if (PopoverData* popoverData = GetPopoverData()) {
return popoverData->GetPopoverVisibilityState() ==
PopoverVisibilityState::Showing;
}
return false;
}
// https://html.spec.whatwg.org/#check-popover-validity
bool nsGenericHTMLElement::CheckPopoverValidity(
PopoverVisibilityState aExpectedState, Document* aExpectedDocument,
ErrorResult& aRv) {
const PopoverData* data = GetPopoverData();
if (!data ||
data->GetPopoverAttributeState() == PopoverAttributeState::None) {
MOZ_ASSERT(!HasAttr(nsGkAtoms::popover));
aRv.ThrowNotSupportedError("Element is in the no popover state");
return false;
}
MOZ_ASSERT(HasAttr(nsGkAtoms::popover));
if (data->GetPopoverVisibilityState() != aExpectedState) {
return false;
}
if (!IsInComposedDoc()) {
aRv.ThrowInvalidStateError("Element is not connected");
return false;
}
if (aExpectedDocument && aExpectedDocument != OwnerDoc()) {
aRv.ThrowInvalidStateError("Element is moved to other document");
return false;
}
if (auto* dialog = HTMLDialogElement::FromNode(this)) {
if (dialog->IsInTopLayer()) {
aRv.ThrowInvalidStateError("Element is a modal <dialog> element");
return false;
}
}
if (State().HasState(ElementState::FULLSCREEN)) {
aRv.ThrowInvalidStateError("Element is fullscreen");
return false;
}
return true;
}
PopoverAttributeState nsGenericHTMLElement::GetPopoverAttributeState() const {
return GetPopoverData() ? GetPopoverData()->GetPopoverAttributeState()
: PopoverAttributeState::None;
}
void nsGenericHTMLElement::PopoverPseudoStateUpdate(bool aOpen, bool aNotify) {
ElementState newPopoverState;
if (aOpen) {
newPopoverState = ElementState::POPOVER_OPEN;
}
ElementState oldPopoverState = State() & ElementState::POPOVER_OPEN;
ElementState changedState = newPopoverState ^ oldPopoverState;
ToggleStates(changedState, aNotify);
}
bool nsGenericHTMLElement::FireToggleEvent(PopoverVisibilityState aOldState,
PopoverVisibilityState aNewState,
const nsAString& aType) {
auto stringForState = [](PopoverVisibilityState state) {
return state == PopoverVisibilityState::Hidden ? u"closed"_ns : u"open"_ns;
};
ToggleEventInit init;
init.mBubbles = false;
init.mOldState = stringForState(aOldState);
init.mNewState = stringForState(aNewState);
if (aType == u"beforetoggle"_ns &&
aNewState == PopoverVisibilityState::Showing) {
init.mCancelable = true;
} else {
init.mCancelable = false;
}
RefPtr<ToggleEvent> event = ToggleEvent::Constructor(this, aType, init);
event->SetTrusted(true);
event->SetTarget(this);
EventDispatcher::DispatchDOMEvent(MOZ_KnownLive(ToSupports(this)), nullptr,
event, nullptr, nullptr);
return event->DefaultPrevented();
}
// https://html.spec.whatwg.org/#queue-a-popover-toggle-event-task
void nsGenericHTMLElement::QueuePopoverEventTask(
PopoverVisibilityState aOldState) {
auto* data = GetPopoverData();
MOZ_ASSERT(data, "Should have popover data");
if (auto* queuedToggleEventTask = data->GetToggleEventTask()) {
aOldState = queuedToggleEventTask->GetOldState();
}
auto task =
MakeRefPtr<PopoverToggleEventTask>(do_GetWeakReference(this), aOldState);
data->SetToggleEventTask(task);
OwnerDoc()->Dispatch(TaskCategory::UI, task.forget());
}
void nsGenericHTMLElement::RunPopoverToggleEventTask(
PopoverToggleEventTask* aTask, PopoverVisibilityState aOldState) {
auto* data = GetPopoverData();
if (!data) {
return;
}
auto* popoverToggleEventTask = data->GetToggleEventTask();
if (!popoverToggleEventTask || aTask != popoverToggleEventTask) {
return;
}
data->ClearToggleEventTask();
// Intentionally ignore the return value here as only on open event the
// cancelable attribute is initialized to true for beforetoggle event.
FireToggleEvent(aOldState, data->GetPopoverVisibilityState(), u"toggle"_ns);
}
// https://html.spec.whatwg.org/#dom-showpopover
void nsGenericHTMLElement::ShowPopover(ErrorResult& aRv) {
return ShowPopoverInternal(nullptr, aRv);
}
void nsGenericHTMLElement::ShowPopoverInternal(
nsGenericHTMLFormControlElementWithState* aInvoker, ErrorResult& aRv) {
if (PopoverData* data = GetPopoverData()) {
data->SetInvoker(aInvoker);
}
if (!CheckPopoverValidity(PopoverVisibilityState::Hidden, nullptr, aRv)) {
return;
}
RefPtr<Document> document = OwnerDoc();
MOZ_ASSERT(!OwnerDoc()->TopLayerContains(*this));
bool wasShowingOrHiding = GetPopoverData()->IsShowingOrHiding();
GetPopoverData()->SetIsShowingOrHiding(true);
auto cleanupShowingFlag = MakeScopeExit([&]() {
if (auto* popoverData = GetPopoverData()) {
popoverData->SetIsShowingOrHiding(wasShowingOrHiding);
}
});
// Fire beforetoggle event and re-check popover validity.
if (FireToggleEvent(PopoverVisibilityState::Hidden,
PopoverVisibilityState::Showing, u"beforetoggle"_ns)) {
return;
}
if (!CheckPopoverValidity(PopoverVisibilityState::Hidden, document, aRv)) {
return;
}
bool shouldRestoreFocus = false;
nsWeakPtr originallyFocusedElement;
if (IsAutoPopover()) {
RefPtr<nsINode> ancestor = GetTopmostPopoverAncestor();
if (!ancestor) {
ancestor = document;
}
document->HideAllPopoversUntil(*ancestor, false,
/* aFireEvents = */ !wasShowingOrHiding);
// TODO: Handle if document changes, see
// https://github.com/whatwg/html/issues/9177
if (!IsAutoPopover() ||
!CheckPopoverValidity(PopoverVisibilityState::Hidden, document, aRv)) {
return;
}
shouldRestoreFocus = !document->GetTopmostAutoPopover();
// Let originallyFocusedElement be document's focused area of the document's
// DOM anchor.
if (nsIContent* unretargetedFocus =
document->GetUnretargetedFocusedContent()) {
originallyFocusedElement =
do_GetWeakReference(unretargetedFocus->AsElement());
}
}
document->AddPopoverToTopLayer(*this);
PopoverPseudoStateUpdate(true, true);
GetPopoverData()->SetPopoverVisibilityState(PopoverVisibilityState::Showing);
// Run the popover focusing steps given element.
FocusPopover();
if (shouldRestoreFocus &&
GetPopoverAttributeState() != PopoverAttributeState::None) {
GetPopoverData()->SetPreviouslyFocusedElement(originallyFocusedElement);
}
// Queue popover toggle event task.
QueuePopoverEventTask(PopoverVisibilityState::Hidden);
}
void nsGenericHTMLElement::HidePopoverWithoutRunningScript() {
HidePopoverInternal(/* aFocusPreviousElement = */ false,
/* aFireEvents = */ false, IgnoreErrors());
}
// https://html.spec.whatwg.org/#dom-hidepopover
void nsGenericHTMLElement::HidePopover(ErrorResult& aRv) {
HidePopoverInternal(/* aFocusPreviousElement = */ true,
/* aFireEvents = */ true, aRv);
}
void nsGenericHTMLElement::HidePopoverInternal(bool aFocusPreviousElement,
bool aFireEvents,
ErrorResult& aRv) {
OwnerDoc()->HidePopover(*this, aFocusPreviousElement, aFireEvents, aRv);
}
void nsGenericHTMLElement::ForgetPreviouslyFocusedElementAfterHidingPopover() {
auto* data = GetPopoverData();
MOZ_ASSERT(data, "Should have popover data");
data->SetPreviouslyFocusedElement(nullptr);
}
void nsGenericHTMLElement::FocusPreviousElementAfterHidingPopover() {
auto* data = GetPopoverData();
MOZ_ASSERT(data, "Should have popover data");
RefPtr<Element> control =
do_QueryReferent(data->GetPreviouslyFocusedElement().get());
data->SetPreviouslyFocusedElement(nullptr);
if (!control) {
return;
}
// Run the focusing steps for previouslyFocusedElement
FocusOptions options;
options.mPreventScroll = true;
control->Focus(options, CallerType::NonSystem, IgnoreErrors());
}
// https://html.spec.whatwg.org/multipage/popover.html#dom-togglepopover
void nsGenericHTMLElement::TogglePopover(const Optional<bool>& aForce,
ErrorResult& aRv) {
if (PopoverOpen() && (!aForce.WasPassed() || !aForce.Value())) {
HidePopover(aRv);
} else if (!aForce.WasPassed() || aForce.Value()) {
ShowPopover(aRv);
}
}
// https://html.spec.whatwg.org/multipage/popover.html#popover-focusing-steps
void nsGenericHTMLElement::FocusPopover() {
if (auto* dialog = HTMLDialogElement::FromNode(this)) {
return MOZ_KnownLive(dialog)->FocusDialog();
}
if (RefPtr<Document> doc = GetComposedDoc()) {
doc->FlushPendingNotifications(FlushType::Frames);
}
// This diverges from the spec a bit,
// see https://github.com/whatwg/html/pull/8998
RefPtr<Element> control =
GetBoolAttr(nsGkAtoms::autofocus)
? this
: GetFocusDelegate(false /* aWithMouse */, true /* aAutofocusOnly */);
if (!control) {
return;
}
FocusCandidate(*control, false /* aClearUpFocus */);
}
void nsGenericHTMLElement::FocusCandidate(Element& aControl,
bool aClearUpFocus) {
// 1) Run the focusing steps given control.
IgnoredErrorResult rv;
nsIFrame* frame = aControl.GetPrimaryFrame();
if (frame && frame->IsFocusable()) {
aControl.Focus(FocusOptions(), CallerType::NonSystem, rv);
if (rv.Failed()) {
return;
}
} else if (aClearUpFocus) {
if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
// Clear the focus which ends up making the body gets focused
nsCOMPtr<nsPIDOMWindowOuter> outerWindow = OwnerDoc()->GetWindow();
fm->ClearFocus(outerWindow);
}
}
// 2) Let topDocument be the active document of control's node document's
// browsing context's top-level browsing context.
// 3) If control's node document's origin is not the same as the origin of
// topDocument, then return.
BrowsingContext* bc = aControl.OwnerDoc()->GetBrowsingContext();
if (bc && bc->IsInProcess() && bc->SameOriginWithTop()) {
if (nsCOMPtr<nsIDocShell> docShell = bc->Top()->GetDocShell()) {
if (Document* topDocument = docShell->GetExtantDocument()) {
// 4) Empty topDocument's autofocus candidates.
// 5) Set topDocument's autofocus processed flag to true.
topDocument->SetAutoFocusFired();
}
}
}
}
already_AddRefed<ElementInternals> nsGenericHTMLElement::AttachInternals(
ErrorResult& aRv) {
// ElementInternals is only available on autonomous custom element, so throws
// an error by default. The spec steps are implemented in HTMLElement because
// ElementInternals needs to hold a pointer to HTMLElement in order to forward
// form operation to it.
aRv.ThrowNotSupportedError(nsPrintfCString(
"Cannot attach ElementInternals to a customized built-in or non-custom "
"element "
"'%s'",
NS_ConvertUTF16toUTF8(NodeInfo()->NameAtom()->GetUTF16String()).get()));
return nullptr;
}
ElementInternals* nsGenericHTMLElement::GetInternals() const {
if (CustomElementData* data = GetCustomElementData()) {
return data->GetElementInternals();
}
return nullptr;
}
bool nsGenericHTMLElement::IsFormAssociatedCustomElements() const {
if (CustomElementData* data = GetCustomElementData()) {
return data->IsFormAssociated();
}
return false;
}
void nsGenericHTMLElement::GetAutocapitalize(nsAString& aValue) const {
GetEnumAttr(nsGkAtoms::autocapitalize, nullptr, kDefaultAutocapitalize->tag,
aValue);
}
bool nsGenericHTMLElement::Translate() const {
if (const nsAttrValue* attr = mAttrs.GetAttr(nsGkAtoms::translate)) {
if (attr->IsEmptyString() || attr->Equals(nsGkAtoms::yes, eIgnoreCase)) {
return true;
}
if (attr->Equals(nsGkAtoms::no, eIgnoreCase)) {
return false;
}
}
return nsGenericHTMLElementBase::Translate();
}
void nsGenericHTMLElement::GetPopover(nsString& aPopover) const {
GetHTMLEnumAttr(nsGkAtoms::popover, aPopover);
if (aPopover.IsEmpty() && !DOMStringIsNull(aPopover)) {
aPopover.Assign(NS_ConvertUTF8toUTF16(kPopoverAttributeValueAuto));
}
}