/* -*- 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/. */
/*
* Base class for all element classes; this provides an implementation
* of DOM Core's Element, implements nsIContent, provides
* utility methods for subclasses, and so forth.
*/
#include "mozilla/dom/Element.h"
#include "mozilla/dom/ElementInlines.h"
#include <cstddef>
#include <inttypes.h>
#include <initializer_list>
#include <utility>
#include "DOMMatrix.h"
#include "ExpandedPrincipal.h"
#include "PresShellInlines.h"
#include "jsapi.h"
#include "mozAutoDocUpdate.h"
#include "mozilla/AnimationComparator.h"
#include "mozilla/AnimationTarget.h"
#include "mozilla/AsyncEventDispatcher.h"
#include "mozilla/CORSMode.h"
#include "mozilla/Components.h"
#include "mozilla/ComputedStyle.h"
#include "mozilla/ContentEvents.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/DeclarationBlock.h"
#include "mozilla/EditorBase.h"
#include "mozilla/EffectCompositor.h"
#include "mozilla/EffectSet.h"
#include "mozilla/ElementAnimationData.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/EventDispatcher.h"
#include "mozilla/EventListenerManager.h"
#include "mozilla/EventStateManager.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/FullscreenChange.h"
#include "mozilla/HTMLEditor.h"
#include "mozilla/InternalMutationEvent.h"
#include "mozilla/Likely.h"
#include "mozilla/LinkedList.h"
#include "mozilla/LookAndFeel.h"
#include "mozilla/MappedDeclarationsBuilder.h"
#include "mozilla/Maybe.h"
#include "mozilla/MouseEvents.h"
#include "mozilla/NotNull.h"
#include "mozilla/PointerLockManager.h"
#include "mozilla/PresShell.h"
#include "mozilla/PresShellForwards.h"
#include "mozilla/ReflowOutput.h"
#include "mozilla/RefPtr.h"
#include "mozilla/RelativeTo.h"
#include "mozilla/ScrollContainerFrame.h"
#include "mozilla/ScrollTypes.h"
#include "mozilla/ServoStyleConsts.h"
#include "mozilla/ServoStyleConstsInlines.h"
#include "mozilla/SizeOfState.h"
#include "mozilla/SourceLocation.h"
#include "mozilla/StaticAnalysisFunctions.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/StaticPrefs_full_screen_api.h"
#include "mozilla/StaticString.h"
#include "mozilla/TextControlElement.h"
#include "mozilla/TextEditor.h"
#include "mozilla/TextEvents.h"
#include "mozilla/Try.h"
#include "mozilla/TypedEnumBits.h"
#include "mozilla/Unused.h"
#include "mozilla/dom/AnimatableBinding.h"
#include "mozilla/dom/Animation.h"
#include "mozilla/dom/Attr.h"
#include "mozilla/dom/BindContext.h"
#include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/dom/CloseWatcher.h"
#include "mozilla/dom/CustomElementRegistry.h"
#include "mozilla/dom/CSPViolationData.h"
#include "mozilla/dom/DOMRect.h"
#include "mozilla/dom/DirectionalityUtils.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/DocumentFragment.h"
#include "mozilla/dom/DocumentInlines.h"
#include "mozilla/dom/DocumentTimeline.h"
#include "mozilla/dom/ElementBinding.h"
#include "mozilla/dom/Flex.h"
#include "mozilla/dom/FragmentOrElement.h"
#include "mozilla/dom/FromParser.h"
#include "mozilla/dom/Grid.h"
#include "mozilla/dom/HTMLDivElement.h"
#include "mozilla/dom/HTMLElement.h"
#include "mozilla/dom/HTMLParagraphElement.h"
#include "mozilla/dom/HTMLPreElement.h"
#include "mozilla/dom/HTMLSpanElement.h"
#include "mozilla/dom/HTMLTableCellElement.h"
#include "mozilla/dom/HTMLTemplateElement.h"
#include "mozilla/dom/KeyframeAnimationOptionsBinding.h"
#include "mozilla/dom/KeyframeEffect.h"
#include "mozilla/dom/MouseEvent.h"
#include "mozilla/dom/MouseEventBinding.h"
#include "mozilla/dom/MutationEventBinding.h"
#include "mozilla/dom/MutationObservers.h"
#include "mozilla/dom/NodeInfo.h"
#include "mozilla/dom/nsCSPUtils.h"
#include "mozilla/dom/PointerEventHandler.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/Sanitizer.h"
#include "mozilla/dom/SVGElement.h"
#include "mozilla/dom/ScriptLoader.h"
#include "mozilla/dom/ShadowRoot.h"
#include "mozilla/dom/Text.h"
#include "mozilla/dom/TrustedHTML.h"
#include "mozilla/dom/TrustedTypesConstants.h"
#include "mozilla/dom/TrustedTypeUtils.h"
#include "mozilla/dom/UnbindContext.h"
#include "mozilla/dom/ViewTransition.h"
#include "mozilla/dom/WindowBinding.h"
#include "mozilla/dom/XULCommandEvent.h"
#include "mozilla/dom/nsCSPContext.h"
#include "mozilla/gfx/BasePoint.h"
#include "mozilla/gfx/BaseRect.h"
#include "mozilla/gfx/BaseSize.h"
#include "mozilla/gfx/Matrix.h"
#include "mozilla/widget/Screen.h"
#include "nsAtom.h"
#include "nsAttrName.h"
#include "nsAttrValueInlines.h"
#include "nsAttrValueOrString.h"
#include "nsBaseHashtable.h"
#include "nsBlockFrame.h"
#include "nsCOMPtr.h"
#include "nsContentUtils.h"
#include "nsCSSPseudoElements.h"
#include "nsCompatibility.h"
#include "nsContainerFrame.h"
#include "nsContentList.h"
#include "nsContentListDeclarations.h"
#include "nsCoord.h"
#include "nsDOMAttributeMap.h"
#include "nsDOMCSSAttrDeclaration.h"
#include "nsDOMMutationObserver.h"
#include "nsDOMString.h"
#include "nsDOMStringMap.h"
#include "nsDOMTokenList.h"
#include "nsDocShell.h"
#include "nsError.h"
#include "nsFlexContainerFrame.h"
#include "nsFocusManager.h"
#include "nsFrameState.h"
#include "nsGenericHTMLElement.h"
#include "nsGkAtoms.h"
#include "nsGridContainerFrame.h"
#include "nsIContentSecurityPolicy.h"
#include "nsIAutoCompletePopup.h"
#include "nsIBrowser.h"
#include "nsIContentInlines.h"
#include "nsIDOMXULButtonElement.h"
#include "nsIDOMXULContainerElement.h"
#include "nsIDOMXULControlElement.h"
#include "nsIDOMXULMenuListElement.h"
#include "nsIDOMXULMultSelectCntrlEl.h"
#include "nsIDOMXULRadioGroupElement.h"
#include "nsIDOMXULRelatedElement.h"
#include "nsIDOMXULSelectCntrlEl.h"
#include "nsIDOMXULSelectCntrlItemEl.h"
#include "nsIDocShell.h"
#include "nsIFocusManager.h"
#include "nsIFrame.h"
#include "nsIGlobalObject.h"
#include "nsIIOService.h"
#include "nsIInterfaceRequestor.h"
#include "nsIMemoryReporter.h"
#include "nsIPrincipal.h"
#include "nsIScriptError.h"
#include "nsISpeculativeConnect.h"
#include "nsISupports.h"
#include "nsISupportsUtils.h"
#include "nsIURI.h"
#include "nsLayoutUtils.h"
#include "nsLineBox.h"
#include "nsLiteralString.h"
#include "nsNameSpaceManager.h"
#include "nsNodeInfoManager.h"
#include "nsPIDOMWindow.h"
#include "nsPoint.h"
#include "nsPresContext.h"
#include "nsQueryFrame.h"
#include "nsRefPtrHashtable.h"
#include "nsSize.h"
#include "nsString.h"
#include "nsStyleConsts.h"
#include "nsStyleStruct.h"
#include "nsStyledElement.h"
#include "nsTArray.h"
#include "nsTextNode.h"
#include "nsThreadUtils.h"
#include "nsViewManager.h"
#include "nsWindowSizes.h"
#include "nsXULElement.h"
#ifdef DEBUG
# include
"nsRange.h"
#endif
#ifdef ACCESSIBILITY
# include
"nsAccessibilityService.h"
#endif
using mozilla::gfx::Matrix4x4;
namespace mozilla::dom {
// Verify sizes of nodes. We use a template rather than a direct static
// assert so that the error message actually displays the sizes.
// On 32 bit systems the actual allocated size varies a bit between
// OSes/compilers.
//
// We need different numbers on certain build types to deal with the owning
// thread pointer that comes with the non-threadsafe refcount on
// nsIContent.
#ifdef MOZ_THREAD_SAFETY_OWNERSHIP_CHECKS_SUPPORTED
# define EXTRA_DOM_NODE_BYTES 8
#else
# define EXTRA_DOM_NODE_BYTES 0
#endif
#define ASSERT_NODE_SIZE(type, opt_size_64, opt_size_32) \
template <
int a,
int sizeOn64,
int sizeOn32> \
struct Check
##type
##Size { \
static_assert((
sizeof(
void*) == 8 && a == sizeOn64) || \
(
sizeof(
void*) == 4 && a <= sizeOn32), \
"DOM size changed"); \
}; \
Check
##type
##Size<
sizeof(type), opt_size_64 + EXTRA_DOM_NODE_BYTES, \
opt_size_32 + EXTRA_DOM_NODE_BYTES> \
g
##type
##CES;
// Note that mozjemalloc uses a 16 byte quantum, so 64, 80 and 128 are
// bucket sizes.
ASSERT_NODE_SIZE(Element, 128, 80);
ASSERT_NODE_SIZE(HTMLDivElement, 128, 80);
ASSERT_NODE_SIZE(HTMLElement, 128, 80);
ASSERT_NODE_SIZE(HTMLParagraphElement, 128, 80);
ASSERT_NODE_SIZE(HTMLPreElement, 128, 80);
ASSERT_NODE_SIZE(HTMLSpanElement, 128, 80);
ASSERT_NODE_SIZE(HTMLTableCellElement, 128, 80);
ASSERT_NODE_SIZE(Text, 120, 80);
#undef ASSERT_NODE_SIZE
#undef EXTRA_DOM_NODE_BYTES
}
// namespace mozilla::dom
nsAtom* nsIContent::DoGetID()
const {
MOZ_ASSERT(HasID(),
"Unexpected call");
MOZ_ASSERT(IsElement(),
"Only elements can have IDs");
return AsElement()->GetParsedAttr(nsGkAtoms::id)->GetAtomValue();
}
nsIFrame* nsIContent::GetPrimaryFrame(mozilla::FlushType aType) {
Document* doc = GetComposedDoc();
if (!doc) {
return nullptr;
}
// Cause a flush, so we get up-to-date frame information.
if (aType != mozilla::FlushType::None) {
doc->FlushPendingNotifications(aType);
}
auto* frame = GetPrimaryFrame();
if (!frame) {
return nullptr;
}
if (aType == mozilla::FlushType::Layout) {
frame->PresShell()->EnsureReflowIfFrameHasHiddenContent(frame);
frame = GetPrimaryFrame();
}
return frame;
}
namespace mozilla::dom {
const DOMTokenListSupportedToken Element::sSupportedBlockingValues[] = {
"render", nullptr};
nsDOMAttributeMap* Element::Attributes() {
nsDOMSlots* slots = DOMSlots();
if (!slots->mAttributeMap) {
slots->mAttributeMap =
new nsDOMAttributeMap(
this);
}
return slots->mAttributeMap;
}
void Element::SetPointerCapture(int32_t aPointerId, ErrorResult& aError) {
if (OwnerDoc()->ShouldResistFingerprinting(RFPTarget::PointerId) &&
aPointerId != PointerEventHandler::GetSpoofedPointerIdForRFP()) {
aError.ThrowNotFoundError(
"Invalid pointer id");
return;
}
const PointerInfo* pointerInfo =
PointerEventHandler::GetPointerInfo(aPointerId);
if (!pointerInfo) {
aError.ThrowNotFoundError(
"Invalid pointer id");
return;
}
if (!IsInComposedDoc()) {
aError.
Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
if (OwnerDoc()->GetPointerLockElement()) {
// Throw an exception 'InvalidStateError' while the page has a locked
// element.
aError.
Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
if (!pointerInfo->mActiveState ||
pointerInfo->mActiveDocument != OwnerDoc()) {
return;
}
PointerEventHandler::RequestPointerCaptureById(aPointerId,
this);
}
void Element::ReleasePointerCapture(int32_t aPointerId, ErrorResult& aError) {
if (OwnerDoc()->ShouldResistFingerprinting(RFPTarget::PointerId) &&
aPointerId != PointerEventHandler::GetSpoofedPointerIdForRFP()) {
aError.ThrowNotFoundError(
"Invalid pointer id");
return;
}
if (!PointerEventHandler::GetPointerInfo(aPointerId)) {
aError.ThrowNotFoundError(
"Invalid pointer id");
return;
}
if (HasPointerCapture(aPointerId)) {
PointerEventHandler::ReleasePointerCaptureById(aPointerId);
}
}
bool Element::HasPointerCapture(
long aPointerId) {
PointerCaptureInfo* pointerCaptureInfo =
PointerEventHandler::GetPointerCaptureInfo(aPointerId);
if (pointerCaptureInfo && pointerCaptureInfo->mPendingElement ==
this) {
return true;
}
return false;
}
const nsAttrValue* Element::GetSVGAnimatedClass()
const {
MOZ_ASSERT(MayHaveClass() && IsSVGElement(),
"Unexpected call");
return static_cast<
const SVGElement*>(
this)->GetAnimatedClassName();
}
NS_IMETHODIMP
Element::QueryInterface(REFNSIID aIID,
void** aInstancePtr) {
if (aIID.Equals(NS_GET_IID(Element))) {
NS_ADDREF_THIS();
*aInstancePtr =
this;
return NS_OK;
}
NS_ASSERTION(aInstancePtr,
"QueryInterface requires a non-NULL destination!");
nsresult rv = FragmentOrElement::QueryInterface(aIID, aInstancePtr);
if (NS_SUCCEEDED(rv)) {
return NS_OK;
}
return NS_NOINTERFACE;
}
void Element::NotifyStateChange(ElementState aStates) {
MOZ_ASSERT(!aStates.IsEmpty());
if (Document* doc = GetComposedDoc()) {
nsAutoScriptBlocker scriptBlocker;
doc->ElementStateChanged(
this, aStates);
}
}
}
// namespace mozilla::dom
void nsIContent::UpdateEditableState(
bool aNotify) {
if (IsInNativeAnonymousSubtree()) {
// Don't propagate the editable flag into native anonymous subtrees.
if (IsRootOfNativeAnonymousSubtree()) {
return;
}
// We allow setting the flag on NAC (explicitly, see
// nsTextControlFrame::CreateAnonymousContent for example), but not
// unsetting it.
//
// Otherwise, just the act of binding the NAC subtree into our non-anonymous
// parent would clear the flag, which is not good. As we shouldn't move NAC
// around, this is fine.
if (HasFlag(NODE_IS_EDITABLE)) {
return;
}
}
nsIContent* parent = GetParent();
SetEditableFlag(parent && parent->HasFlag(NODE_IS_EDITABLE));
}
namespace mozilla::dom {
void Element::UpdateEditableState(
bool aNotify) {
nsIContent::UpdateEditableState(aNotify);
UpdateReadOnlyState(aNotify);
}
bool Element::IsReadOnlyInternal()
const {
return !IsEditable(); }
void Element::UpdateReadOnlyState(
bool aNotify) {
auto oldState = State();
if (IsReadOnlyInternal()) {
RemoveStatesSilently(ElementState::READWRITE);
AddStatesSilently(ElementState::READONLY);
}
else {
RemoveStatesSilently(ElementState::READONLY);
AddStatesSilently(ElementState::READWRITE);
}
if (!aNotify) {
return;
}
const auto newState = State();
if (newState != oldState) {
NotifyStateChange(newState ^ oldState);
}
}
Maybe<int32_t> Element::GetTabIndexAttrValue() {
const nsAttrValue* attrVal = GetParsedAttr(nsGkAtoms::tabindex);
if (attrVal && attrVal->Type() == nsAttrValue::eInteger) {
return Some(attrVal->GetIntegerValue());
}
return Nothing();
}
int32_t Element::TabIndex() {
Maybe<int32_t> attrVal = GetTabIndexAttrValue();
if (attrVal.isSome()) {
return attrVal.value();
}
return TabIndexDefault();
}
void Element::Focus(
const FocusOptions& aOptions, CallerType aCallerType,
ErrorResult& aError) {
const RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager();
if (MOZ_UNLIKELY(!fm)) {
return;
}
const OwningNonNull<Element> kungFuDeathGrip(*
this);
// Also other browsers seem to have the hack to not re-focus (and flush) when
// the element is already focused.
// Until https://github.com/whatwg/html/issues/4512 is clarified, we'll
// maintain interoperatibility by not re-focusing, independent of aOptions.
// I.e., `focus({ preventScroll: true})` followed by `focus( { preventScroll:
// false })` won't re-focus.
if (fm->CanSkipFocus(
this)) {
fm->NotifyOfReFocus(kungFuDeathGrip);
fm->NeedsFlushBeforeEventHandling(
this);
return;
}
uint32_t fmFlags = nsFocusManager::ProgrammaticFocusFlags(aOptions);
if (aCallerType == CallerType::NonSystem) {
fmFlags |= nsIFocusManager::FLAG_NONSYSTEMCALLER;
}
aError = fm->SetFocus(kungFuDeathGrip, fmFlags);
}
void Element::SetTabIndex(int32_t aTabIndex, mozilla::ErrorResult& aError) {
nsAutoString value;
value.AppendInt(aTabIndex);
SetAttr(nsGkAtoms::tabindex, value, aError);
}
void Element::SetShadowRoot(ShadowRoot* aShadowRoot) {
nsExtendedDOMSlots* slots = ExtendedDOMSlots();
MOZ_ASSERT(!aShadowRoot || !slots->mShadowRoot,
"We shouldn't clear the shadow root without unbind first");
slots->mShadowRoot = aShadowRoot;
}
void Element::SetLastRememberedBSize(
float aBSize) {
ExtendedDOMSlots()->mLastRememberedBSize = Some(aBSize);
}
void Element::SetLastRememberedISize(
float aISize) {
ExtendedDOMSlots()->mLastRememberedISize = Some(aISize);
}
void Element::RemoveLastRememberedBSize() {
if (nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots()) {
slots->mLastRememberedBSize.reset();
}
}
void Element::RemoveLastRememberedISize() {
if (nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots()) {
slots->mLastRememberedISize.reset();
}
}
void Element::Blur(mozilla::ErrorResult& aError) {
if (!ShouldBlur(
this)) {
return;
}
Document* doc = GetComposedDoc();
if (!doc) {
return;
}
if (nsCOMPtr<nsPIDOMWindowOuter> win = doc->GetWindow()) {
if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
aError = fm->ClearFocus(win);
}
}
}
ElementState Element::StyleStateFromLocks()
const {
StyleStateLocks locksAndValues = LockedStyleStates();
ElementState locks = locksAndValues.mLocks;
ElementState values = locksAndValues.mValues;
ElementState state = (mState & ~locks) | (locks & values);
if (state.HasState(ElementState::VISITED)) {
return state & ~ElementState::UNVISITED;
}
if (state.HasState(ElementState::UNVISITED)) {
return state & ~ElementState::VISITED;
}
return state;
}
Element::StyleStateLocks Element::LockedStyleStates()
const {
StyleStateLocks* locks =
static_cast<StyleStateLocks*>(GetProperty(nsGkAtoms::lockedStyleStates));
if (locks) {
return *locks;
}
return StyleStateLocks();
}
void Element::NotifyStyleStateChange(ElementState aStates) {
if (RefPtr<Document> doc = GetComposedDoc()) {
if (RefPtr<PresShell> presShell = doc->GetPresShell()) {
nsAutoScriptBlocker scriptBlocker;
presShell->ElementStateChanged(doc,
this, aStates);
}
}
}
void Element::LockStyleStates(ElementState aStates,
bool aEnabled) {
StyleStateLocks* locks =
new StyleStateLocks(LockedStyleStates());
locks->mLocks |= aStates;
if (aEnabled) {
locks->mValues |= aStates;
}
else {
locks->mValues &= ~aStates;
}
if (aStates.HasState(ElementState::VISITED)) {
locks->mLocks &= ~ElementState::UNVISITED;
}
if (aStates.HasState(ElementState::UNVISITED)) {
locks->mLocks &= ~ElementState::VISITED;
}
SetProperty(nsGkAtoms::lockedStyleStates, locks,
nsINode::DeleteProperty<StyleStateLocks>);
SetHasLockedStyleStates();
NotifyStyleStateChange(aStates);
}
void Element::UnlockStyleStates(ElementState aStates) {
StyleStateLocks* locks =
new StyleStateLocks(LockedStyleStates());
locks->mLocks &= ~aStates;
if (locks->mLocks.IsEmpty()) {
RemoveProperty(nsGkAtoms::lockedStyleStates);
ClearHasLockedStyleStates();
delete locks;
}
else {
SetProperty(nsGkAtoms::lockedStyleStates, locks,
nsINode::DeleteProperty<StyleStateLocks>);
}
NotifyStyleStateChange(aStates);
}
void Element::ClearStyleStateLocks() {
StyleStateLocks locks = LockedStyleStates();
RemoveProperty(nsGkAtoms::lockedStyleStates);
ClearHasLockedStyleStates();
NotifyStyleStateChange(locks.mLocks);
}
/* virtual */
nsINode* Element::GetScopeChainParent()
const {
return OwnerDoc(); }
JSObject* Element::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
return Element_Binding::Wrap(aCx,
this, aGivenProto);
}
nsDOMTokenList* Element::ClassList() {
nsDOMSlots* slots = DOMSlots();
if (!slots->mClassList) {
slots->mClassList =
new nsDOMTokenList(
this, nsGkAtoms::_
class);
}
return slots->mClassList;
}
nsDOMTokenList* Element::Part() {
nsExtendedDOMSlots* slots = ExtendedDOMSlots();
if (!slots->mPart) {
slots->mPart =
new nsDOMTokenList(
this, nsGkAtoms::part);
}
return slots->mPart;
}
void Element::RecompileScriptEventListeners() {
for (uint32_t i = 0, count = mAttrs.AttrCount(); i < count; ++i) {
BorrowedAttrInfo attrInfo = mAttrs.AttrInfoAt(i);
// Eventlistenener-attributes are always in the null namespace
if (!attrInfo.mName->IsAtom()) {
continue;
}
nsAtom* attr = attrInfo.mName->Atom();
if (!IsEventAttributeName(attr)) {
continue;
}
nsAutoString value;
attrInfo.mValue->ToString(value);
SetEventHandler(GetEventNameForAttr(attr), value,
true);
}
}
void Element::GetAttributeNames(nsTArray<nsString>& aResult) {
uint32_t count = mAttrs.AttrCount();
for (uint32_t i = 0; i < count; ++i) {
const nsAttrName* name = mAttrs.AttrNameAt(i);
name->GetQualifiedName(*aResult.AppendElement());
}
}
already_AddRefed<nsIHTMLCollection> Element::GetElementsByTagName(
const nsAString& aLocalName) {
return NS_GetContentList(
this, kNameSpaceID_Unknown, aLocalName);
}
ScrollContainerFrame* Element::GetScrollContainerFrame(nsIFrame** aFrame,
FlushType aFlushType) {
nsIFrame* frame = GetPrimaryFrame(aFlushType);
if (aFrame) {
*aFrame = frame;
}
if (frame) {
if (frame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
// It's unclear what to return for SVG frames, so just return null.
return nullptr;
}
if (ScrollContainerFrame* scrollContainerFrame =
frame->GetScrollTargetFrame()) {
MOZ_ASSERT(!OwnerDoc()->IsScrollingElement(
this),
"How can we have a scroll container frame if we're the "
"scrollingElement for our document?");
return scrollContainerFrame;
}
}
Document* doc = OwnerDoc();
// Note: This IsScrollingElement() call can flush frames, if we're the body of
// a quirks mode document.
const bool isScrollingElement = doc->IsScrollingElement(
this);
if (isScrollingElement) {
// Our scroll info should map to the root scroll container frame if there is
// one.
if (PresShell* presShell = doc->GetPresShell()) {
if (ScrollContainerFrame* rootScrollContainerFrame =
presShell->GetRootScrollContainerFrame()) {
if (aFrame) {
*aFrame = rootScrollContainerFrame;
}
return rootScrollContainerFrame;
}
}
}
if (aFrame) {
// Re-get *aFrame if the caller asked for it, because that frame flush can
// kill it.
*aFrame = GetPrimaryFrame(FlushType::None);
}
return nullptr;
}
bool Element::CheckVisibility(
const CheckVisibilityOptions& aOptions) {
nsIFrame* f =
GetPrimaryFrame(aOptions.mFlush ? FlushType::Frames : FlushType::None);
if (!f) {
// 1. If this does not have an associated box, return false.
return false;
}
EnumSet includeContentVisibility = {
nsIFrame::IncludeContentVisibility::Hidden};
if (aOptions.mContentVisibilityAuto) {
includeContentVisibility += nsIFrame::IncludeContentVisibility::
Auto;
}
// Steps 2 and 5
if (f->IsHiddenByContentVisibilityOnAnyAncestor(includeContentVisibility)) {
// 2. If a shadow-including ancestor of this has content-visibility: hidden,
// return false.
// 5. If a shadow-including ancestor of this skips its content due to
// has content-visibility: auto, return false.
return false;
}
if ((aOptions.mOpacityProperty || aOptions.mCheckOpacity) &&
f->Style()->IsInOpacityZeroSubtree()) {
// 3. If the checkOpacity dictionary member of options is true, and this, or
// a shadow-including ancestor of this, has a computed opacity value of 0,
// return false.
return false;
}
if ((aOptions.mVisibilityProperty || aOptions.mCheckVisibilityCSS) &&
!f->StyleVisibility()->IsVisible()) {
// 4. If the checkVisibilityCSS dictionary member of options is true, and
// this is invisible, return false.
return false;
}
// 6. Return true
return true;
}
void Element::ScrollIntoView(
const BooleanOrScrollIntoViewOptions& aObject) {
if (aObject.IsScrollIntoViewOptions()) {
return ScrollIntoView(aObject.GetAsScrollIntoViewOptions());
}
MOZ_DIAGNOSTIC_ASSERT(aObject.IsBoolean());
ScrollIntoViewOptions options;
if (aObject.GetAsBoolean()) {
options.mBlock = ScrollLogicalPosition::Start;
options.mInline = ScrollLogicalPosition::Nearest;
}
else {
options.mBlock = ScrollLogicalPosition::End;
options.mInline = ScrollLogicalPosition::Nearest;
}
return ScrollIntoView(options);
}
void Element::ScrollIntoView(
const ScrollIntoViewOptions& aOptions) {
Document* document = GetComposedDoc();
if (!document) {
return;
}
// Get the presentation shell
RefPtr<PresShell> presShell = document->GetPresShell();
if (!presShell) {
return;
}
const auto ToWhereToScroll =
[](ScrollLogicalPosition aPosition) -> WhereToScroll {
switch (aPosition) {
case ScrollLogicalPosition::Start:
return WhereToScroll::Start;
case ScrollLogicalPosition::Center:
return WhereToScroll::Center;
case ScrollLogicalPosition::End:
return WhereToScroll::End;
case ScrollLogicalPosition::Nearest:
break;
}
return WhereToScroll::Nearest;
};
const auto block = ToWhereToScroll(aOptions.mBlock);
const auto inline_ = ToWhereToScroll(aOptions.mInline);
ScrollFlags scrollFlags =
ScrollFlags::ScrollOverflowHidden | ScrollFlags::TriggeredByScript;
if (aOptions.mBehavior == ScrollBehavior::Smooth) {
scrollFlags |= ScrollFlags::ScrollSmooth;
}
else if (aOptions.mBehavior == ScrollBehavior::
Auto) {
scrollFlags |= ScrollFlags::ScrollSmoothAuto;
}
// TODO: Propagate whether the axes are logical or not down (via scrollflags).
presShell->ScrollContentIntoView(
this, ScrollAxis(block, WhenToScroll::Always),
ScrollAxis(inline_, WhenToScroll::Always), scrollFlags);
}
void Element::ScrollTo(
double aXScroll,
double aYScroll) {
ScrollToOptions options;
options.mLeft.Construct(aXScroll);
options.mTop.Construct(aYScroll);
ScrollTo(options);
}
void Element::ScrollTo(
const ScrollToOptions& aOptions) {
// When the scroll top is 0, we don't need to flush layout to scroll to that
// point; we know 0 is always in range. At least we think so... But we do
// need to flush frames so we ensure we find the right scrollable frame if
// there is one. If it's nonzero, we need to flush layout because we need to
// figure out what our real scrollTopMax is.
//
// If we have a left value, we can't assume things based on it's value,
// depending on our direction and layout 0 may or may not be in our scroll
// range. So we need to flush layout no matter what then.
const bool needsLayoutFlush =
aOptions.mLeft.WasPassed() ||
(aOptions.mTop.WasPassed() && aOptions.mTop.Value() != 0.0);
nsIFrame* frame;
ScrollContainerFrame* sf = GetScrollContainerFrame(
&frame, needsLayoutFlush ? FlushType::Layout : FlushType::Frames);
if (!sf) {
return;
}
CSSIntPoint scrollPos = sf->GetRoundedScrollPositionCSSPixels();
if (aOptions.mLeft.WasPassed()) {
scrollPos.x = int32_t(mozilla::ToZeroIfNonfinite(
frame->Style()->EffectiveZoom().Zoom(aOptions.mLeft.Value())));
}
if (aOptions.mTop.WasPassed()) {
scrollPos.y = int32_t(mozilla::ToZeroIfNonfinite(
frame->Style()->EffectiveZoom().Zoom(aOptions.mTop.Value())));
}
ScrollMode scrollMode = sf->IsSmoothScroll(aOptions.mBehavior)
? ScrollMode::SmoothMsd
: ScrollMode::Instant;
sf->ScrollToCSSPixels(scrollPos, scrollMode);
}
void Element::ScrollBy(
double aXScrollDif,
double aYScrollDif) {
ScrollToOptions options;
options.mLeft.Construct(aXScrollDif);
options.mTop.Construct(aYScrollDif);
ScrollBy(options);
}
void Element::ScrollBy(
const ScrollToOptions& aOptions) {
nsIFrame* frame;
ScrollContainerFrame* sf = GetScrollContainerFrame(&frame);
if (!sf) {
return;
}
CSSIntPoint scrollDelta;
if (aOptions.mLeft.WasPassed()) {
scrollDelta.x = int32_t(mozilla::ToZeroIfNonfinite(
frame->Style()->EffectiveZoom().Zoom(aOptions.mLeft.Value())));
}
if (aOptions.mTop.WasPassed()) {
scrollDelta.y = int32_t(mozilla::ToZeroIfNonfinite(
frame->Style()->EffectiveZoom().Zoom(aOptions.mTop.Value())));
}
auto scrollMode = sf->IsSmoothScroll(aOptions.mBehavior)
? ScrollMode::SmoothMsd
: ScrollMode::Instant;
sf->ScrollByCSSPixels(scrollDelta, scrollMode);
}
int32_t Element::ScrollTop() {
return CSSPixel::FromAppUnitsRounded(GetScrollOrigin().y);
}
void Element::SetScrollTop(int32_t aScrollTop) {
ScrollToOptions options;
options.mTop.Construct(aScrollTop);
ScrollTo(options);
}
int32_t Element::ScrollLeft() {
return CSSPixel::FromAppUnitsRounded(GetScrollOrigin().x);
}
void Element::SetScrollLeft(int32_t aScrollLeft) {
ScrollToOptions options;
options.mLeft.Construct(aScrollLeft);
ScrollTo(options);
}
void Element::MozScrollSnap() {
if (ScrollContainerFrame* sf =
GetScrollContainerFrame(nullptr, FlushType::None)) {
sf->ScrollSnap();
}
}
nsRect Element::GetScrollRange() {
nsIFrame* frame;
ScrollContainerFrame* sf = GetScrollContainerFrame(&frame);
if (!sf) {
return nsRect();
}
return frame->Style()->EffectiveZoom().Unzoom(sf->GetScrollRange());
}
int32_t Element::ScrollTopMin() {
return CSSPixel::FromAppUnitsRounded(GetScrollRange().Y());
}
int32_t Element::ScrollTopMax() {
return CSSPixel::FromAppUnitsRounded(GetScrollRange().YMost());
}
int32_t Element::ScrollLeftMin() {
return CSSPixel::FromAppUnitsRounded(GetScrollRange().X());
}
int32_t Element::ScrollLeftMax() {
return CSSPixel::FromAppUnitsRounded(GetScrollRange().XMost());
}
static nsSize GetScrollRectSizeForOverflowVisibleFrame(nsIFrame* aFrame) {
if (!aFrame || aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
return nsSize();
}
// This matches WebKit and Blink, which in turn (apparently, according to
// their source) matched old IE.
const nsRect paddingRect = aFrame->GetPaddingRectRelativeToSelf();
const nsRect overflowRect = [&] {
OverflowAreas overflowAreas(paddingRect, paddingRect);
// Add the scrollable overflow areas of children (if any) to the
// paddingRect, as if aFrame was a scrolled frame. It's important to start
// with the paddingRect, otherwise if there are no children the overflow
// rect will be 0,0,0,0 which will force the point 0,0 to be included in the
// final rect.
aFrame->UnionChildOverflow(overflowAreas,
/* aAsIfScrolled = */ true);
// Make sure that an empty padding-rect's edges are included, by adding
// the padding-rect in again with UnionEdges.
return overflowAreas.ScrollableOverflow().UnionEdges(paddingRect);
}();
auto directions =
ScrollContainerFrame::ComputePerAxisScrollDirections(aFrame);
const nscoord height = directions.mToBottom
? overflowRect.YMost() - paddingRect.Y()
: paddingRect.YMost() - overflowRect.Y();
const nscoord width = directions.mToRight
? overflowRect.XMost() - paddingRect.X()
: paddingRect.XMost() - overflowRect.X();
return nsSize(width, height);
}
nsSize Element::GetScrollSize() {
nsIFrame* frame;
nsSize size;
if (ScrollContainerFrame* sf = GetScrollContainerFrame(&frame)) {
size = sf->GetScrollRange().Size() + sf->GetScrollPortRect().Size();
}
else {
size = GetScrollRectSizeForOverflowVisibleFrame(frame);
}
if (!frame) {
return size;
}
return frame->Style()->EffectiveZoom().Unzoom(size);
}
nsPoint Element::GetScrollOrigin() {
nsIFrame* frame;
ScrollContainerFrame* sf = GetScrollContainerFrame(&frame);
if (!sf) {
return nsPoint();
}
return frame->Style()->EffectiveZoom().Unzoom(sf->GetScrollPosition());
}
int32_t Element::ScrollHeight() {
return nsPresContext::AppUnitsToIntCSSPixels(GetScrollSize().height);
}
int32_t Element::ScrollWidth() {
return nsPresContext::AppUnitsToIntCSSPixels(GetScrollSize().width);
}
nsRect Element::GetClientAreaRect() {
Document* doc = OwnerDoc();
nsPresContext* presContext = doc->GetPresContext();
// We can avoid a layout flush if this is the scrolling element of the
// document, we have overlay scrollbars, and we aren't embedded in another
// document
if (presContext && presContext->UseOverlayScrollbars() &&
!doc->StyleOrLayoutObservablyDependsOnParentDocumentLayout() &&
doc->IsScrollingElement(
this)) {
if (PresShell* presShell = doc->GetPresShell()) {
// Ensure up to date dimensions, but don't reflow
if (RefPtr<nsViewManager> viewManager = presShell->GetViewManager()) {
viewManager->FlushDelayedResize();
}
return nsRect(nsPoint(), presContext->GetVisibleArea().Size());
}
}
nsIFrame* frame;
if (ScrollContainerFrame* sf = GetScrollContainerFrame(&frame)) {
nsRect scrollPort = sf->GetScrollPortRect();
if (!sf->IsRootScrollFrameOfDocument()) {
MOZ_ASSERT(frame);
// We want the offset to be relative to `frame`, not `sf`... Except for
// the root scroll frame, which is an ancestor of frame rather than a
// descendant and thus this wouldn't particularly make sense.
if (frame != sf) {
scrollPort.MoveBy(sf->GetOffsetTo(frame));
}
}
// The scroll port value might be expanded to the minimum scale size, we
// should limit the size to the ICB in such cases.
scrollPort.SizeTo(sf->GetLayoutSize());
return frame->Style()->EffectiveZoom().Unzoom(scrollPort);
}
if (frame &&
// The display check is OK even though we're not looking at the style
// frame, because the style frame only differs from "frame" for tables,
// and table wrappers have the same display as the table itself.
(!frame->StyleDisplay()->IsInlineFlow() || frame->IsReplaced())) {
// Special case code to make client area work even when there isn't
// a scroll view, see bug 180552, bug 227567.
return frame->Style()->EffectiveZoom().Unzoom(
frame->GetPaddingRect() - frame->GetPositionIgnoringScrolling());
}
// SVG nodes reach here and just return 0
return nsRect();
}
int32_t Element::ScreenX() {
nsIFrame* frame = GetPrimaryFrame(FlushType::Layout);
return frame ? frame->GetScreenRect().x : 0;
}
int32_t Element::ScreenY() {
nsIFrame* frame = GetPrimaryFrame(FlushType::Layout);
return frame ? frame->GetScreenRect().y : 0;
}
already_AddRefed<nsIScreen> Element::GetScreen() {
// Flush layout to guarantee that frames are created if needed, and preserve
// behavior.
Unused << GetPrimaryFrame(FlushType::Frames);
if (nsIWidget* widget = nsContentUtils::WidgetForContent(
this)) {
return widget->GetWidgetScreen();
}
return nullptr;
}
double Element::CurrentCSSZoom() {
nsIFrame* f = GetPrimaryFrame(FlushType::Frames);
if (!f) {
return 1.0;
}
return f->Style()->EffectiveZoom().ToFloat();
}
already_AddRefed<DOMRect> Element::GetBoundingClientRect() {
RefPtr<DOMRect> rect =
new DOMRect(ToSupports(OwnerDoc()));
nsIFrame* frame = GetPrimaryFrame(FlushType::Layout);
if (!frame) {
// display:none, perhaps? Return the empty rect
return rect.forget();
}
rect->SetLayoutRect(frame->GetBoundingClientRect());
return rect.forget();
}
already_AddRefed<DOMRectList> Element::GetClientRects() {
RefPtr<DOMRectList> rectList =
new DOMRectList(
this);
nsIFrame* frame = GetPrimaryFrame(FlushType::Layout);
if (!frame) {
// display:none, perhaps? Return an empty list
return rectList.forget();
}
nsLayoutUtils::RectListBuilder builder(rectList);
nsLayoutUtils::GetAllInFlowRects(
frame, nsLayoutUtils::GetContainingBlockForClientRect(frame), &builder,
nsLayoutUtils::GetAllInFlowRectsFlag::AccountForTransforms);
return rectList.forget();
}
const DOMTokenListSupportedToken Element::sAnchorAndFormRelValues[] = {
"noreferrer",
"noopener",
"opener", nullptr};
// https://html.spec.whatwg.org/multipage/urls-and-fetching.html#lazy-loading-attribute
static constexpr nsAttrValue::EnumTable kLoadingTable[] = {
{
"eager", Element::Loading::Eager},
{
"lazy", Element::Loading::Lazy},
{nullptr, 0}};
void Element::GetLoading(nsAString& aValue)
const {
GetEnumAttr(nsGkAtoms::loading, kLoadingTable[0].tag, aValue);
}
bool Element::ParseLoadingAttribute(
const nsAString& aValue,
nsAttrValue& aResult) {
return aResult.ParseEnumValue(aValue, kLoadingTable,
/* aCaseSensitive = */ false, kLoadingTable);
}
Element::Loading Element::LoadingState()
const {
const nsAttrValue* val = mAttrs.GetAttr(nsGkAtoms::loading);
if (!val) {
return Loading::Eager;
}
return static_cast<Loading>(val->GetEnumValue());
}
//----------------------------------------------------------------------
void Element::AddToIdTable(nsAtom* aId) {
NS_ASSERTION(HasID(),
"Node doesn't have an ID?");
if (IsInShadowTree()) {
ShadowRoot* containingShadow = GetContainingShadow();
containingShadow->AddToIdTable(
this, aId);
}
else {
Document* doc = GetUncomposedDoc();
if (doc && !IsInNativeAnonymousSubtree()) {
doc->AddToIdTable(
this, aId);
}
}
}
void Element::RemoveFromIdTable() {
if (!HasID()) {
return;
}
nsAtom* id = DoGetID();
if (IsInShadowTree()) {
ShadowRoot* containingShadow = GetContainingShadow();
// Check for containingShadow because it may have
// been deleted during unlinking.
if (containingShadow) {
containingShadow->RemoveFromIdTable(
this, id);
}
}
else {
Document* doc = GetUncomposedDoc();
if (doc && !IsInNativeAnonymousSubtree()) {
doc->RemoveFromIdTable(
this, id);
}
}
}
void Element::SetSlot(
const nsAString& aName, ErrorResult& aError) {
aError = SetAttr(kNameSpaceID_None, nsGkAtoms::slot, aName,
true);
}
void Element::GetSlot(nsAString& aName) { GetAttr(nsGkAtoms::slot, aName); }
// https://dom.spec.whatwg.org/#dom-element-shadowroot
ShadowRoot* Element::GetShadowRootByMode()
const {
/**
* 1. Let shadow be context object's shadow root.
* 2. If shadow is null or its mode is "closed", then return null.
*/
ShadowRoot* shadowRoot = GetShadowRoot();
if (!shadowRoot || shadowRoot->IsClosed()) {
return nullptr;
}
/**
* 3. Return shadow.
*/
return shadowRoot;
}
bool Element::CanAttachShadowDOM()
const {
/**
* If context object's namespace is not the HTML namespace,
* return false.
*
* Deviate from the spec here to allow shadow dom attachement to
* XUL elements.
*/
if (!IsHTMLElement() &&
!(IsXULElement() &&
nsContentUtils::AllowXULXBLForPrincipal(NodePrincipal()))) {
return false;
}
/**
* If context object's local name is not
* a valid custom element name, "article", "aside", "blockquote",
* "body", "div", "footer", "h1", "h2", "h3", "h4", "h5", "h6",
* "header", "main" "nav", "p", "section", "search", or "span",
* return false.
*/
nsAtom* nameAtom = NodeInfo()->NameAtom();
uint32_t namespaceID = NodeInfo()->NamespaceID();
if (!(nsContentUtils::IsCustomElementName(nameAtom, namespaceID) ||
nameAtom == nsGkAtoms::article || nameAtom == nsGkAtoms::aside ||
nameAtom == nsGkAtoms::blockquote || nameAtom == nsGkAtoms::body ||
nameAtom == nsGkAtoms::div || nameAtom == nsGkAtoms::footer ||
nameAtom == nsGkAtoms::h1 || nameAtom == nsGkAtoms::h2 ||
nameAtom == nsGkAtoms::h3 || nameAtom == nsGkAtoms::h4 ||
nameAtom == nsGkAtoms::h5 || nameAtom == nsGkAtoms::h6 ||
nameAtom == nsGkAtoms::header || nameAtom == nsGkAtoms::main ||
nameAtom == nsGkAtoms::nav || nameAtom == nsGkAtoms::p ||
nameAtom == nsGkAtoms::section || nameAtom == nsGkAtoms::search ||
nameAtom == nsGkAtoms::span)) {
return false;
}
/**
* 3. If context object’s local name is a valid custom element name, or
* context object’s is value is not null, then:
* If definition is not null and definition’s disable shadow is true, then
* return false.
*/
// It will always have CustomElementData when the element is a valid custom
// element or has is value.
if (CustomElementData* ceData = GetCustomElementData()) {
CustomElementDefinition* definition = ceData->GetCustomElementDefinition();
// If the definition is null, the element possible hasn't yet upgraded.
// Fallback to use LookupCustomElementDefinition to find its definition.
if (!definition) {
definition = nsContentUtils::LookupCustomElementDefinition(
NodeInfo()->GetDocument(), nameAtom, namespaceID,
ceData->GetCustomElementType());
}
if (definition && definition->mDisableShadow) {
return false;
}
}
return true;
}
// https://dom.spec.whatwg.org/#dom-element-attachshadow
already_AddRefed<ShadowRoot> Element::AttachShadow(
const ShadowRootInit& aInit,
ErrorResult& aError) {
/**
* Step 1, 2, and 3.
*/
if (!CanAttachShadowDOM()) {
aError.ThrowNotSupportedError(
"Unable to attach ShadowDOM");
return nullptr;
}
/**
* 4. If element is a shadow host, then:
*/
if (RefPtr<ShadowRoot> root = GetShadowRoot()) {
/**
* 1. Let currentShadowRoot be element’s shadow root.
*
* 2. If any of the following are true:
* currentShadowRoot’s declarative is false; or
* currentShadowRoot’s mode is not mode,
* then throw a "NotSupportedError" DOMException.
*/
if (!root->IsDeclarative() || root->Mode() != aInit.mMode) {
aError.ThrowNotSupportedError(
"Unable to re-attach to existing ShadowDOM");
return nullptr;
}
/**
* 3. Otherwise:
* 1. Remove all of currentShadowRoot’s children, in tree order.
* 2. Set currentShadowRoot’s declarative to false.
* 3. Return.
*/
root->ReplaceChildren(nullptr, aError);
root->SetIsDeclarative(ShadowRootDeclarative::No);
return root.forget();
}
if (StaticPrefs::dom_webcomponents_shadowdom_report_usage()) {
OwnerDoc()->ReportShadowDOMUsage();
}
return AttachShadowWithoutNameChecks(
aInit.mMode, DelegatesFocus(aInit.mDelegatesFocus), aInit.mSlotAssignment,
ShadowRootClonable(aInit.mClonable),
ShadowRootSerializable(aInit.mSerializable));
}
already_AddRefed<ShadowRoot> Element::AttachShadowWithoutNameChecks(
ShadowRootMode aMode, DelegatesFocus aDelegatesFocus,
SlotAssignmentMode aSlotAssignment, ShadowRootClonable aClonable,
ShadowRootSerializable aSerializable) {
nsAutoScriptBlocker scriptBlocker;
auto* nim = mNodeInfo->NodeInfoManager();
RefPtr<mozilla::dom::NodeInfo> nodeInfo =
nim->GetNodeInfo(nsGkAtoms::documentFragmentNodeName, nullptr,
kNameSpaceID_None, DOCUMENT_FRAGMENT_NODE);
// If there are no children, the flat tree is not changing due to the presence
// of the shadow root, so we don't need to invalidate style / layout.
//
// This is a minor optimization, but also works around nasty stuff like
// bug 1397876.
if (Document* doc = GetComposedDoc()) {
if (PresShell* presShell = doc->GetPresShell()) {
presShell->ShadowRootWillBeAttached(*
this);
}
}
/**
* 5. Let shadow be a new shadow root whose node document is
* context object's node document, host is context object,
* and mode is init's mode.
*/
RefPtr<ShadowRoot> shadowRoot =
new (nim)
ShadowRoot(
this, aMode, aDelegatesFocus, aSlotAssignment, aClonable,
aSerializable, ShadowRootDeclarative::No, nodeInfo.forget());
if (NodeOrAncestorHasDirAuto()) {
shadowRoot->SetAncestorHasDirAuto();
}
/**
* 7. If this’s custom element state is "precustomized" or "custom", then set
* shadow’s available to element internals to true.
*/
CustomElementData* ceData = GetCustomElementData();
if (ceData && (ceData->mState == CustomElementData::State::ePrecustomized ||
ceData->mState == CustomElementData::State::eCustom)) {
shadowRoot->SetAvailableToElementInternals();
}
/**
* 9. Set context object's shadow root to shadow.
*/
SetShadowRoot(shadowRoot);
// Dispatch a "shadowrootattached" event for devtools if needed.
if (MOZ_UNLIKELY(
nim->GetDocument()->DevToolsAnonymousAndShadowEventsEnabled())) {
AsyncEventDispatcher* dispatcher =
new AsyncEventDispatcher(
this, u
"shadowrootattached"_ns, CanBubble::eYes,
ChromeOnlyDispatch::eYes, Composed::eYes);
dispatcher->PostDOMEvent();
}
const LinkedList<AbstractRange>* ranges =
GetExistingClosestCommonInclusiveAncestorRanges();
if (ranges) {
for (
const AbstractRange* range : *ranges) {
if (range->MayCrossShadowBoundary()) {
MOZ_ASSERT(range->IsDynamicRange());
CrossShadowBoundaryRange* crossBoundaryRange =
range->AsDynamicRange()->GetCrossShadowBoundaryRange();
MOZ_ASSERT(crossBoundaryRange);
// We may have previously selected this node before it
// becomes a shadow host, so we need to reset the values
// in RangeBoundaries to accommodate the change.
crossBoundaryRange->NotifyNodeBecomesShadowHost(
this);
}
}
}
/**
* 10. Return shadow.
*/
return shadowRoot.forget();
}
void Element::AttachAndSetUAShadowRoot(NotifyUAWidgetSetup aNotify,
DelegatesFocus aDelegatesFocus) {
MOZ_DIAGNOSTIC_ASSERT(!CanAttachShadowDOM(),
"Cannot be used to attach UI shadow DOM");
if (OwnerDoc()->IsStaticDocument()) {
return;
}
if (!GetShadowRoot()) {
RefPtr<ShadowRoot> shadowRoot =
AttachShadowWithoutNameChecks(ShadowRootMode::Closed, aDelegatesFocus);
shadowRoot->SetIsUAWidget();
}
MOZ_ASSERT(GetShadowRoot()->IsUAWidget());
if (aNotify == NotifyUAWidgetSetup::Yes) {
NotifyUAWidgetSetupOrChange();
}
}
void Element::NotifyUAWidgetSetupOrChange() {
MOZ_ASSERT(IsInComposedDoc());
Document* doc = OwnerDoc();
if (doc->IsStaticDocument()) {
return;
}
// Schedule a runnable, ensure the event dispatches before
// returning to content script.
// This event cause UA Widget to construct or cause onchange callback
// of existing UA Widget to run; dispatching this event twice should not cause
// UA Widget to re-init.
nsContentUtils::AddScriptRunner(NS_NewRunnableFunction(
"Element::NotifyUAWidgetSetupOrChange::UAWidgetSetupOrChange",
[self = RefPtr<Element>(
this), doc = RefPtr<Document>(doc)]() {
nsContentUtils::DispatchChromeEvent(doc, self,
u
"UAWidgetSetupOrChange"_ns,
CanBubble::eYes, Cancelable::eNo);
}));
}
void Element::NotifyUAWidgetTeardown(UnattachShadowRoot aUnattachShadowRoot) {
MOZ_ASSERT(IsInComposedDoc());
if (!GetShadowRoot()) {
return;
}
MOZ_ASSERT(GetShadowRoot()->IsUAWidget());
if (aUnattachShadowRoot == UnattachShadowRoot::Yes) {
UnattachShadow();
}
Document* doc = OwnerDoc();
if (doc->IsStaticDocument()) {
return;
}
// The runnable will dispatch an event to tear down UA Widget.
nsContentUtils::AddScriptRunner(NS_NewRunnableFunction(
"Element::NotifyUAWidgetTeardownAndUnattachShadow::UAWidgetTeardown",
[self = RefPtr<Element>(
this), doc = RefPtr<Document>(doc)]() {
// Bail out if the element is being collected by CC
bool hasHadScriptObject =
true;
nsIScriptGlobalObject* scriptObject =
doc->GetScriptHandlingObject(hasHadScriptObject);
if (!scriptObject && hasHadScriptObject) {
return;
}
Unused << nsContentUtils::DispatchChromeEvent(
doc, self, u
"UAWidgetTeardown"_ns, CanBubble::eYes,
Cancelable::eNo);
}));
}
void Element::UnattachShadow() {
RefPtr<ShadowRoot> shadowRoot = GetShadowRoot();
if (!shadowRoot) {
return;
}
nsAutoScriptBlocker scriptBlocker;
if (RefPtr<Document> doc = GetComposedDoc()) {
if (PresShell* presShell = doc->GetPresShell()) {
presShell->DestroyFramesForAndRestyle(
this);
#ifdef ACCESSIBILITY
// We need to notify the accessibility service here explicitly because,
// even though we're going to reconstruct the _host_, the shadow root and
// its children are never really going to come back. We could plumb that
// further down to DestroyFramesForAndRestyle and add a new flag to
// nsCSSFrameConstructor::ContentRemoved or such, but this seems simpler
// instead.
if (nsAccessibilityService* accService = GetAccService()) {
accService->ContentRemoved(presShell, shadowRoot);
}
#endif
}
// ContentRemoved doesn't really run script in the cases we care about (it
// can only call ClearFocus when removing iframes and so on...)
[&]() MOZ_CAN_RUN_SCRIPT_BOUNDARY {
if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
fm->ContentRemoved(doc, shadowRoot);
}
}();
}
MOZ_ASSERT(!GetPrimaryFrame());
shadowRoot->Unattach();
SetShadowRoot(nullptr);
}
void Element::GetAttribute(
const nsAString& aName, DOMString& aReturn) {
const nsAttrValue* val = mAttrs.GetAttr(
aName,
IsHTMLElement() && IsInHTMLDocument() ? eIgnoreCase : eCaseMatters);
if (val) {
val->ToString(aReturn);
}
else {
aReturn.SetNull();
}
}
bool Element::ToggleAttribute(
const nsAString& aName,
const Optional<
bool>& aForce,
nsIPrincipal* aTriggeringPrincipal,
ErrorResult& aError) {
aError = nsContentUtils::CheckQName(aName,
false);
if (aError.Failed()) {
return false;
}
nsAutoString nameToUse;
const nsAttrName* name = InternalGetAttrNameFromQName(aName, &nameToUse);
if (!name) {
if (aForce.WasPassed() && !aForce.Value()) {
return false;
}
RefPtr<nsAtom> nameAtom = NS_AtomizeMainThread(nameToUse);
if (!nameAtom) {
aError.
Throw(NS_ERROR_OUT_OF_MEMORY);
return false;
}
aError = SetAttr(kNameSpaceID_None, nameAtom, u
""_ns, aTriggeringPrincipal,
true);
return true;
}
if (aForce.WasPassed() && aForce.Value()) {
return true;
}
// Hold a strong reference here so that the atom or nodeinfo doesn't go
// away during UnsetAttr. If it did UnsetAttr would be left with a
// dangling pointer as argument without knowing it.
nsAttrName tmp(*name);
aError = UnsetAttr(name->NamespaceID(), name->LocalName(),
true);
return false;
}
void Element::SetAttribute(
const nsAString& aName,
const nsAString& aValue,
nsIPrincipal* aTriggeringPrincipal,
ErrorResult& aError) {
aError = nsContentUtils::CheckQName(aName,
false);
if (aError.Failed()) {
return;
}
nsAutoString nameToUse;
const nsAttrName* name = InternalGetAttrNameFromQName(aName, &nameToUse);
if (!name) {
RefPtr<nsAtom> nameAtom = NS_AtomizeMainThread(nameToUse);
if (!nameAtom) {
aError.
Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
aError = SetAttr(kNameSpaceID_None, nameAtom, aValue, aTriggeringPrincipal,
true);
return;
}
aError = SetAttr(name->NamespaceID(), name->LocalName(), name->GetPrefix(),
aValue, aTriggeringPrincipal,
true);
}
void Element::RemoveAttribute(
const nsAString& aName, ErrorResult& aError) {
const nsAttrName* name = InternalGetAttrNameFromQName(aName);
if (!name) {
// If there is no canonical nsAttrName for this attribute name, then the
// attribute does not exist and we can't get its namespace ID and
// local name below, so we return early.
return;
}
// Hold a strong reference here so that the atom or nodeinfo doesn't go
// away during UnsetAttr. If it did UnsetAttr would be left with a
// dangling pointer as argument without knowing it.
nsAttrName tmp(*name);
aError = UnsetAttr(name->NamespaceID(), name->LocalName(),
true);
}
Attr* Element::GetAttributeNode(
const nsAString& aName) {
return Attributes()->GetNamedItem(aName);
}
already_AddRefed<Attr> Element::SetAttributeNode(Attr& aNewAttr,
ErrorResult& aError) {
return Attributes()->SetNamedItemNS(aNewAttr, aError);
}
already_AddRefed<Attr> Element::RemoveAttributeNode(Attr& aAttribute,
ErrorResult& aError) {
Element* elem = aAttribute.GetElement();
if (elem !=
this) {
aError.
Throw(NS_ERROR_DOM_NOT_FOUND_ERR);
return nullptr;
}
nsAutoString nameSpaceURI;
aAttribute.NodeInfo()->GetNamespaceURI(nameSpaceURI);
return Attributes()->RemoveNamedItemNS(
nameSpaceURI, aAttribute.NodeInfo()->LocalName(), aError);
}
void Element::GetAttributeNS(
const nsAString& aNamespaceURI,
const nsAString& aLocalName, nsAString& aReturn) {
int32_t nsid = nsNameSpaceManager::GetInstance()->GetNameSpaceID(
aNamespaceURI, nsContentUtils::IsChromeDoc(OwnerDoc()));
if (nsid == kNameSpaceID_Unknown) {
// Unknown namespace means no attribute.
SetDOMStringToNull(aReturn);
return;
}
RefPtr<nsAtom> name = NS_AtomizeMainThread(aLocalName);
bool hasAttr = GetAttr(nsid, name, aReturn);
if (!hasAttr) {
SetDOMStringToNull(aReturn);
}
}
void Element::SetAttributeNS(
const nsAString& aNamespaceURI,
const nsAString& aQualifiedName,
const nsAString& aValue,
nsIPrincipal* aTriggeringPrincipal,
ErrorResult& aError) {
RefPtr<mozilla::dom::NodeInfo> ni;
aError = nsContentUtils::GetNodeInfoFromQName(
aNamespaceURI, aQualifiedName, mNodeInfo->NodeInfoManager(),
ATTRIBUTE_NODE, getter_AddRefs(ni));
if (aError.Failed()) {
return;
}
aError = SetAttr(ni->NamespaceID(), ni->NameAtom(), ni->GetPrefixAtom(),
aValue, aTriggeringPrincipal,
true);
}
already_AddRefed<nsIPrincipal> Element::CreateDevtoolsPrincipal() {
// Return an ExpandedPrincipal that subsumes this Element's Principal,
// and expands this Element's CSP to allow the actions that devtools
// needs to perform.
AutoTArray<nsCOMPtr<nsIPrincipal>, 1> allowList = {NodePrincipal()};
RefPtr<ExpandedPrincipal> dtPrincipal = ExpandedPrincipal::Create(
allowList, NodePrincipal()->OriginAttributesRef());
if (nsIContentSecurityPolicy* csp = GetCsp()) {
RefPtr<nsCSPContext> dtCsp =
new nsCSPContext();
dtCsp->InitFromOther(
static_cast<nsCSPContext*>(csp));
dtCsp->SetSkipAllowInlineStyleCheck(
true);
dtPrincipal->SetCsp(dtCsp);
}
return dtPrincipal.forget();
}
void Element::SetAttribute(
const nsAString& aName,
const TrustedHTMLOrTrustedScriptOrTrustedScriptURLOrString& aValue,
nsIPrincipal* aTriggeringPrincipal, ErrorResult& aError) {
aError = nsContentUtils::CheckQName(aName,
false);
if (aError.Failed()) {
return;
}
nsAutoString nameToUse;
const nsAttrName* name = InternalGetAttrNameFromQName(aName, &nameToUse);
if (!name) {
RefPtr<nsAtom> nameAtom = NS_AtomizeMainThread(nameToUse);
Maybe<nsAutoString> compliantStringHolder;
const nsAString* compliantString =
TrustedTypeUtils::GetTrustedTypesCompliantAttributeValue(
*
this, nameAtom, kNameSpaceID_None, aValue, compliantStringHolder,
aError);
if (aError.Failed()) {
return;
}
aError = SetAttr(kNameSpaceID_None, nameAtom, *compliantString,
aTriggeringPrincipal,
true);
return;
}
Maybe<nsAutoString> compliantStringHolder;
RefPtr<nsAtom> attributeName = name->LocalName();
nsMutationGuard guard;
const nsAString* compliantString =
TrustedTypeUtils::GetTrustedTypesCompliantAttributeValue(
*
this, attributeName, name->NamespaceID(), aValue,
compliantStringHolder, aError);
if (aError.Failed()) {
return;
}
if (!guard.Mutated(0)) {
aError = SetAttr(name->NamespaceID(), name->LocalName(), name->GetPrefix(),
*compliantString, aTriggeringPrincipal,
true);
return;
}
// GetTrustedTypesCompliantAttributeValue may have modified mAttrs and made
// the result of InternalGetAttrNameFromQName above invalid. It may now return
// a different value, perhaps a nullptr. To be safe, just call the version of
// Element::SetAttribute accepting a string value.
SetAttribute(aName, *compliantString, aTriggeringPrincipal, aError);
}
void Element::SetAttributeNS(
const nsAString& aNamespaceURI,
const nsAString& aQualifiedName,
const TrustedHTMLOrTrustedScriptOrTrustedScriptURLOrString& aValue,
nsIPrincipal* aTriggeringPrincipal, ErrorResult& aError) {
RefPtr<mozilla::dom::NodeInfo> ni;
aError = nsContentUtils::GetNodeInfoFromQName(
aNamespaceURI, aQualifiedName, mNodeInfo->NodeInfoManager(),
ATTRIBUTE_NODE, getter_AddRefs(ni));
if (aError.Failed()) {
return;
}
Maybe<nsAutoString> compliantStringHolder;
RefPtr<nsAtom> attributeName = ni->NameAtom();
const nsAString* compliantString =
TrustedTypeUtils::GetTrustedTypesCompliantAttributeValue(
*
this, attributeName, ni->NamespaceID(), aValue,
compliantStringHolder, aError);
if (aError.Failed()) {
return;
}
aError = SetAttr(ni->NamespaceID(), ni->NameAtom(), ni->GetPrefixAtom(),
*compliantString, aTriggeringPrincipal,
true);
}
void Element::SetAttributeDevtools(
const nsAString& aName,
const nsAString& aValue,
ErrorResult& aError) {
// Run this through SetAttribute with a devtools-ready principal.
RefPtr<nsIPrincipal> dtPrincipal = CreateDevtoolsPrincipal();
SetAttribute(aName, aValue, dtPrincipal, aError);
}
void Element::SetAttributeDevtoolsNS(
const nsAString& aNamespaceURI,
const nsAString& aLocalName,
const nsAString& aValue,
ErrorResult& aError) {
// Run this through SetAttributeNS with a devtools-ready principal.
RefPtr<nsIPrincipal> dtPrincipal = CreateDevtoolsPrincipal();
SetAttributeNS(aNamespaceURI, aLocalName, aValue, dtPrincipal, aError);
}
void Element::RemoveAttributeNS(
const nsAString& aNamespaceURI,
const nsAString& aLocalName,
ErrorResult& aError) {
RefPtr<nsAtom> name = NS_AtomizeMainThread(aLocalName);
int32_t nsid = nsNameSpaceManager::GetInstance()->GetNameSpaceID(
aNamespaceURI, nsContentUtils::IsChromeDoc(OwnerDoc()));
if (nsid == kNameSpaceID_Unknown) {
// If the namespace ID is unknown, it means there can't possibly be an
// existing attribute. We would need a known namespace ID to pass into
// UnsetAttr, so we return early if we don't have one.
return;
}
aError = UnsetAttr(nsid, name,
true);
}
Attr* Element::GetAttributeNodeNS(
const nsAString& aNamespaceURI,
const nsAString& aLocalName) {
return GetAttributeNodeNSInternal(aNamespaceURI, aLocalName);
}
Attr* Element::GetAttributeNodeNSInternal(
const nsAString& aNamespaceURI,
const nsAString& aLocalName) {
return Attributes()->GetNamedItemNS(aNamespaceURI, aLocalName);
}
already_AddRefed<Attr> Element::SetAttributeNodeNS(Attr& aNewAttr,
ErrorResult& aError) {
return Attributes()->SetNamedItemNS(aNewAttr, aError);
}
already_AddRefed<nsIHTMLCollection> Element::GetElementsByTagNameNS(
const nsAString& aNamespaceURI,
const nsAString& aLocalName,
ErrorResult& aError) {
int32_t nameSpaceId = kNameSpaceID_Wildcard;
if (!aNamespaceURI.EqualsLiteral(
"*")) {
aError = nsNameSpaceManager::GetInstance()->RegisterNameSpace(aNamespaceURI,
nameSpaceId);
if (aError.Failed()) {
return nullptr;
}
}
NS_ASSERTION(nameSpaceId != kNameSpaceID_Unknown,
"Unexpected namespace ID!");
return NS_GetContentList(
this, nameSpaceId, aLocalName);
}
bool Element::HasAttributeNS(
const nsAString& aNamespaceURI,
const nsAString& aLocalName)
const {
int32_t nsid = nsNameSpaceManager::GetInstance()->GetNameSpaceID(
aNamespaceURI, nsContentUtils::IsChromeDoc(OwnerDoc()));
if (nsid == kNameSpaceID_Unknown) {
// Unknown namespace means no attr...
return false;
}
RefPtr<nsAtom> name = NS_AtomizeMainThread(aLocalName);
return HasAttr(nsid, name);
}
already_AddRefed<nsIHTMLCollection> Element::GetElementsByClassName(
const nsAString& aClassNames) {
return nsContentUtils::GetElementsByClassName(
this, aClassNames);
}
bool Element::HasSharedRoot(
const Element* aElement)
const {
nsINode* root = SubtreeRoot();
nsINode* attrSubtreeRoot = aElement->SubtreeRoot();
do {
if (root == attrSubtreeRoot) {
return true;
}
auto* shadow = ShadowRoot::FromNode(root);
if (!shadow || !shadow->GetHost()) {
break;
}
root = shadow->GetHost()->SubtreeRoot();
}
while (
true);
return false;
}
Element* Element::GetElementByIdInDocOrSubtree(nsAtom* aID)
const {
if (
auto* docOrShadowRoot = GetContainingDocumentOrShadowRoot()) {
return docOrShadowRoot->GetElementById(aID);
}
return nsContentUtils::MatchElementId(SubtreeRoot()->AsContent(), aID);
}
Element* Element::GetAttrAssociatedElement(nsAtom* aAttr)
const {
if (
const nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots()) {
nsWeakPtr weakAttrEl = slots->mExplicitlySetAttrElementMap.Get(aAttr);
if (nsCOMPtr<Element> attrEl = do_QueryReferent(weakAttrEl)) {
// If reflectedTarget's explicitly set attr-element |attrEl| is
// a descendant of any of element's shadow-including ancestors, then
// return |atrEl|.
if (HasSharedRoot(attrEl)) {
return attrEl;
}
return nullptr;
}
}
const nsAttrValue* value = GetParsedAttr(aAttr);
if (!value) {
return nullptr;
}
MOZ_ASSERT(value->Type() == nsAttrValue::eAtom,
"Attribute used for attr associated element must be parsed");
return GetElementByIdInDocOrSubtree(value->GetAtomValue());
}
void Element::GetAttrAssociatedElements(
nsAtom* aAttr,
bool* aUseCachedValue,
Nullable<nsTArray<RefPtr<Element>>>& aElements) {
MOZ_ASSERT(aElements.IsNull());
auto& [explicitlySetAttrElements, cachedAttrElements] =
ExtendedDOMSlots()->mAttrElementsMap.LookupOrInsert(aAttr);
// https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#attr-associated-elements
auto getAttrAssociatedElements =
[&, &explicitlySetAttrElements =
explicitlySetAttrElements]() -> Maybe<nsTArray<RefPtr<Element>>> {
nsTArray<RefPtr<Element>> elements;
if (explicitlySetAttrElements) {
// 3. If reflectedTarget's explicitly set attr-elements is not null
for (
const nsWeakPtr& weakEl : *explicitlySetAttrElements) {
// For each attrElement in reflectedTarget's explicitly set
// attr-elements:
if (nsCOMPtr<Element> attrEl = do_QueryReferent(weakEl)) {
// If attrElement is not a descendant of any of element's
// shadow-including ancestors, then continue.
if (!HasSharedRoot(attrEl)) {
continue;
}
// Append attrElement to elements.
elements.AppendElement(attrEl);
}
}
}
else {
// 4. Otherwise
// 1. Let contentAttributeValue be the result of running
// reflectedTarget's get the content attribute.
const nsAttrValue* value = GetParsedAttr(aAttr);
// 2. If contentAttributeValue is null, then return null.
if (!value) {
return Nothing();
}
// 3. Let tokens be contentAttributeValue, split on ASCII whitespace.
MOZ_ASSERT(value->Type() == nsAttrValue::eAtomArray ||
value->Type() == nsAttrValue::eAtom,
"Attribute used for attr associated elements must be parsed");
for (uint32_t i = 0; i < value->GetAtomCount(); i++) {
// For each id of tokens:
if (
auto* candidate = GetElementByIdInDocOrSubtree(
value->AtomAt(
static_cast<int32_t>(i)))) {
// Append candidate to elements.
elements.AppendElement(candidate);
}
}
}
return Some(std::move(elements));
};
// getter steps:
// 1. Let elements be the result of running this's get the attr-associated
// elements.
auto elements = getAttrAssociatedElements();
if (elements && elements == cachedAttrElements) {
// 2. If the contents of elements is equal to the contents of this's cached
// attr-associated elements, then return this's cached attr-associated
// elements object.
MOZ_ASSERT(!*aUseCachedValue);
*aUseCachedValue =
true;
return;
}
// 3. Let elementsAsFrozenArray be elements, converted to a FrozenArray<T>?.
// (the binding code takes aElements and returns it as a FrozenArray)
// 5. Set this's cached attr-associated elements object to
// elementsAsFrozenArray.
// (the binding code stores the attr-associated elements object in a slot)
// 6. Return elementsAsFrozenArray.
if (elements) {
--> --------------------
--> maximum size reached
--> --------------------