Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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 <inttypes.h>
#include <initializer_list>
#include <new>
#include "DOMIntersectionObserver.h"
#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/ComputedStyle.h"
#include "mozilla/ContentEvents.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/DeclarationBlock.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/InternalMutationEvent.h"
#include "mozilla/Likely.h"
#include "mozilla/LinkedList.h"
#include "mozilla/LookAndFeel.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/RelativeTo.h"
#include "mozilla/ScrollOrigin.h"
#include "mozilla/ScrollTypes.h"
#include "mozilla/ServoStyleConsts.h"
#include "mozilla/ServoStyleConstsInlines.h"
#include "mozilla/SizeOfState.h"
#include "mozilla/StaticAnalysisFunctions.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/StaticPrefs_full_screen_api.h"
#include "mozilla/TextControlElement.h"
#include "mozilla/TextEvents.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/CustomElementRegistry.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/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/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 "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 "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 "nsIScreenManager.h"
#include "nsIScriptError.h"
#include "nsIScrollableFrame.h"
#include "nsISpeculativeConnect.h"
#include "nsISupports.h"
#include "nsISupportsUtils.h"
#include "nsIURI.h"
#include "nsLayoutUtils.h"
#include "nsLineBox.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, 64);
#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 {
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() &&
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() &&
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;
}
ElementState Element::IntrinsicState() const {
return IsEditable() ? ElementState::READWRITE : ElementState::READONLY;
}
void Element::NotifyStateChange(ElementState aStates) {
if (aStates.IsEmpty()) {
return;
}
if (Document* doc = GetComposedDoc()) {
nsAutoScriptBlocker scriptBlocker;
doc->ElementStateChanged(this, aStates);
}
}
void Element::UpdateLinkState(ElementState aState) {
MOZ_ASSERT(!aState.HasAtLeastOneOfStates(~ElementState::VISITED_OR_UNVISITED),
"Unexpected link state bits");
mState = (mState & ~ElementState::VISITED_OR_UNVISITED) | aState;
}
void Element::UpdateState(bool aNotify) {
ElementState oldState = mState;
mState =
IntrinsicState() | (oldState & ElementState::EXTERNALLY_MANAGED_STATES);
if (aNotify) {
ElementState changedStates = oldState ^ mState;
if (!changedStates.IsEmpty()) {
Document* doc = GetComposedDoc();
if (doc) {
nsAutoScriptBlocker scriptBlocker;
doc->ElementStateChanged(this, changedStates);
}
}
}
}
} // 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);
if (aNotify) {
UpdateState(aNotify);
} else {
// Avoid calling UpdateState in this very common case, because
// this gets called for pretty much every single element on
// insertion into the document and UpdateState can be slow for
// some kinds of elements even when not notifying.
if (IsEditable()) {
RemoveStatesSilently(ElementState::READONLY);
AddStatesSilently(ElementState::READWRITE);
} else {
RemoveStatesSilently(ElementState::READWRITE);
AddStatesSilently(ElementState::READONLY);
}
}
}
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(); }
nsDOMTokenList* Element::ClassList() {
Element::nsDOMSlots* slots = DOMSlots();
if (!slots->mClassList) {
slots->mClassList = new nsDOMTokenList(this, nsGkAtoms::_class);
}
return slots->mClassList;
}
nsDOMTokenList* Element::Part() {
Element::nsDOMSlots* slots = DOMSlots();
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);
}
nsIScrollableFrame* Element::GetScrollFrame(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 (nsIScrollableFrame* scrollFrame = frame->GetScrollTargetFrame()) {
MOZ_ASSERT(!OwnerDoc()->IsScrollingElement(this),
"How can we have a scrollframe if we're the "
"scrollingElement for our document?");
return scrollFrame;
}
}
Document* doc = OwnerDoc();
// Note: This IsScrollingElement() call can flush frames, if we're the body of
// a quirks mode document.
bool isScrollingElement = doc->IsScrollingElement(this);
// Now reget *aStyledFrame if the caller asked for it, because that frame
// flush can kill it.
if (aFrame) {
*aFrame = GetPrimaryFrame(FlushType::None);
}
if (isScrollingElement) {
// Our scroll info should map to the root scrollable frame if there is one.
if (PresShell* presShell = doc->GetPresShell()) {
return presShell->GetRootScrollFrameAsScrollable();
}
}
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;
}
if (f->IsHiddenByContentVisibilityOnAnyAncestor(
nsIFrame::IncludeContentVisibility::Hidden)) {
// 2. If a shadow-including ancestor of this has content-visibility: hidden,
// return false.
return false;
}
if (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.mCheckVisibilityCSS && !f->StyleVisibility()->IsVisible()) {
// 4. If the checkVisibilityCSS dictionary member of options is true, and
// this is invisible, return false.
return false;
}
// 5. 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::EndGuard_:
MOZ_FALLTHROUGH_ASSERT("Unexpected block direction value");
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::Scroll(const CSSIntPoint& aScroll,
const ScrollOptions& aOptions) {
nsIScrollableFrame* sf = GetScrollFrame();
if (sf) {
ScrollMode scrollMode = sf->IsSmoothScroll(aOptions.mBehavior)
? ScrollMode::SmoothMsd
: ScrollMode::Instant;
sf->ScrollToCSSPixels(aScroll, scrollMode);
}
}
void Element::Scroll(double aXScroll, double aYScroll) {
// Convert -Inf, Inf, and NaN to 0; otherwise, convert by C-style cast.
auto scrollPos = CSSIntPoint::Truncate(mozilla::ToZeroIfNonfinite(aXScroll),
mozilla::ToZeroIfNonfinite(aYScroll));
Scroll(scrollPos, ScrollOptions());
}
void Element::Scroll(const ScrollToOptions& aOptions) {
nsIScrollableFrame* sf = GetScrollFrame();
if (sf) {
CSSIntPoint scrollPos = sf->GetScrollPositionCSSPixels();
if (aOptions.mLeft.WasPassed()) {
scrollPos.x = mozilla::ToZeroIfNonfinite(aOptions.mLeft.Value());
}
if (aOptions.mTop.WasPassed()) {
scrollPos.y = mozilla::ToZeroIfNonfinite(aOptions.mTop.Value());
}
Scroll(scrollPos, aOptions);
}
}
void Element::ScrollTo(double aXScroll, double aYScroll) {
Scroll(aXScroll, aYScroll);
}
void Element::ScrollTo(const ScrollToOptions& aOptions) { Scroll(aOptions); }
void Element::ScrollBy(double aXScrollDif, double aYScrollDif) {
nsIScrollableFrame* sf = GetScrollFrame();
if (sf) {
ScrollToOptions options;
options.mLeft.Construct(aXScrollDif);
options.mTop.Construct(aYScrollDif);
ScrollBy(options);
}
}
void Element::ScrollBy(const ScrollToOptions& aOptions) {
nsIScrollableFrame* sf = GetScrollFrame();
if (sf) {
CSSIntPoint scrollDelta;
if (aOptions.mLeft.WasPassed()) {
scrollDelta.x = mozilla::ToZeroIfNonfinite(aOptions.mLeft.Value());
}
if (aOptions.mTop.WasPassed()) {
scrollDelta.y = mozilla::ToZeroIfNonfinite(aOptions.mTop.Value());
}
ScrollMode scrollMode = sf->IsSmoothScroll(aOptions.mBehavior)
? ScrollMode::SmoothMsd
: ScrollMode::Instant;
sf->ScrollByCSSPixels(scrollDelta, scrollMode);
}
}
int32_t Element::ScrollTop() {
nsIScrollableFrame* sf = GetScrollFrame();
return sf ? sf->GetScrollPositionCSSPixels().y.value : 0;
}
void Element::SetScrollTop(int32_t aScrollTop) {
// When aScrollTop 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 aScrollTop is nonzero, we need to flush layout because we need to figure
// out what our real scrollTopMax is.
FlushType flushType = aScrollTop == 0 ? FlushType::Frames : FlushType::Layout;
nsIScrollableFrame* sf = GetScrollFrame(nullptr, flushType);
if (sf) {
ScrollMode scrollMode =
sf->IsSmoothScroll() ? ScrollMode::SmoothMsd : ScrollMode::Instant;
sf->ScrollToCSSPixels(
CSSIntPoint(sf->GetScrollPositionCSSPixels().x, aScrollTop),
scrollMode);
}
}
int32_t Element::ScrollLeft() {
nsIScrollableFrame* sf = GetScrollFrame();
return sf ? sf->GetScrollPositionCSSPixels().x.value : 0;
}
void Element::SetScrollLeft(int32_t aScrollLeft) {
// We can't assume things here based on the value of aScrollLeft, because
// 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.
nsIScrollableFrame* sf = GetScrollFrame();
if (sf) {
ScrollMode scrollMode =
sf->IsSmoothScroll() ? ScrollMode::SmoothMsd : ScrollMode::Instant;
sf->ScrollToCSSPixels(
CSSIntPoint(aScrollLeft, sf->GetScrollPositionCSSPixels().y),
scrollMode);
}
}
void Element::MozScrollSnap() {
nsIScrollableFrame* sf = GetScrollFrame(nullptr, FlushType::None);
if (sf) {
sf->ScrollSnap();
}
}
int32_t Element::ScrollTopMin() {
nsIScrollableFrame* sf = GetScrollFrame();
if (!sf) {
return 0;
}
return CSSPixel::FromAppUnits(sf->GetScrollRange().y).Rounded();
}
int32_t Element::ScrollTopMax() {
nsIScrollableFrame* sf = GetScrollFrame();
if (!sf) {
return 0;
}
return CSSPixel::FromAppUnits(sf->GetScrollRange().YMost()).Rounded();
}
int32_t Element::ScrollLeftMin() {
nsIScrollableFrame* sf = GetScrollFrame();
if (!sf) {
return 0;
}
return CSSPixel::FromAppUnits(sf->GetScrollRange().x).Rounded();
}
int32_t Element::ScrollLeftMax() {
nsIScrollableFrame* sf = GetScrollFrame();
if (!sf) {
return 0;
}
return CSSPixel::FromAppUnits(sf->GetScrollRange().XMost()).Rounded();
}
static nsSize GetScrollRectSizeForOverflowVisibleFrame(nsIFrame* aFrame) {
if (!aFrame || aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
return nsSize(0, 0);
}
nsRect paddingRect = aFrame->GetPaddingRectRelativeToSelf();
OverflowAreas overflowAreas(paddingRect, paddingRect);
// Add the scrollable overflow areas of children (if any) to the paddingRect.
// 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.
nsLayoutUtils::UnionChildOverflow(aFrame, overflowAreas);
// Make sure that an empty padding-rect's edges are included, by adding
// the padding-rect in again with UnionEdges.
nsRect overflowRect =
overflowAreas.ScrollableOverflow().UnionEdges(paddingRect);
return nsLayoutUtils::GetScrolledRect(aFrame, overflowRect,
paddingRect.Size(),
aFrame->StyleVisibility()->mDirection)
.Size();
}
int32_t Element::ScrollHeight() {
nsIFrame* frame;
nsIScrollableFrame* sf = GetScrollFrame(&frame);
nscoord height;
if (sf) {
height = sf->GetScrollRange().Height() + sf->GetScrollPortRect().Height();
} else {
height = GetScrollRectSizeForOverflowVisibleFrame(frame).height;
}
return nsPresContext::AppUnitsToIntCSSPixels(height);
}
int32_t Element::ScrollWidth() {
nsIFrame* frame;
nsIScrollableFrame* sf = GetScrollFrame(&frame);
nscoord width;
if (sf) {
width = sf->GetScrollRange().Width() + sf->GetScrollPortRect().Width();
} else {
width = GetScrollRectSizeForOverflowVisibleFrame(frame).width;
}
return nsPresContext::AppUnitsToIntCSSPixels(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 (nsIScrollableFrame* sf = GetScrollFrame(&frame)) {
nsRect scrollPort = sf->GetScrollPortRect();
if (!sf->IsRootScrollFrameOfDocument()) {
MOZ_ASSERT(frame);
nsIFrame* scrollableAsFrame = do_QueryFrame(sf);
// 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 != scrollableAsFrame) {
scrollPort.MoveBy(scrollableAsFrame->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 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->IsFrameOfType(nsIFrame::eReplaced))) {
// Special case code to make client area work even when there isn't
// a scroll view, see bug 180552, bug 227567.
return frame->GetPaddingRect() - frame->GetPositionIgnoringScrolling();
}
// SVG nodes reach here and just return 0
return nsRect(0, 0, 0, 0);
}
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() {
nsIFrame* frame = GetPrimaryFrame(FlushType::Layout);
if (!frame) {
return nullptr;
}
nsCOMPtr<nsIScreenManager> screenMgr =
do_GetService("@mozilla.org/gfx/screenmanager;1");
if (!screenMgr) {
return nullptr;
}
nsPresContext* pc = frame->PresContext();
const CSSIntRect rect = frame->GetScreenRect();
DesktopRect desktopRect = rect * pc->CSSToDevPixelScale() /
pc->DeviceContext()->GetDesktopToDeviceScale();
return screenMgr->ScreenForRect(DesktopIntRect::Round(desktopRect));
}
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::RECTS_ACCOUNT_FOR_TRANSFORMS);
return rectList.forget();
}
//----------------------------------------------------------------------
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(kNameSpaceID_None, nsGkAtoms::slot, aName);
}
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", 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::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;
}
already_AddRefed<ShadowRoot> Element::AttachShadow(const ShadowRootInit& aInit,
ErrorResult& aError) {
/**
* Step 1, 2, and 3.
*/
if (!CanAttachShadowDOM()) {
aError.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
return nullptr;
}
/**
* 4. If this is a shadow host, then throw a "NotSupportedError" DOMException.
*/
if (GetShadowRoot()) {
aError.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
return nullptr;
}
if (StaticPrefs::dom_webcomponents_shadowdom_report_usage()) {
OwnerDoc()->ReportShadowDOMUsage();
}
return AttachShadowWithoutNameChecks(aInit.mMode,
DelegatesFocus(aInit.mDelegatesFocus),
aInit.mSlotAssignment);
}
already_AddRefed<ShadowRoot> Element::AttachShadowWithoutNameChecks(
ShadowRootMode aMode, DelegatesFocus aDelegatesFocus,
SlotAssignmentMode aSlotAssignment) {
nsAutoScriptBlocker scriptBlocker;
RefPtr<mozilla::dom::NodeInfo> nodeInfo =
mNodeInfo->NodeInfoManager()->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
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.
*/
auto* nim = nodeInfo->NodeInfoManager();
RefPtr<ShadowRoot> shadowRoot = new (nim) ShadowRoot(
this, aMode, aDelegatesFocus, aSlotAssignment, 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()->ShadowRootAttachedEventEnabled())) {
AsyncEventDispatcher* dispatcher = new AsyncEventDispatcher(
this, u"shadowrootattached"_ns, CanBubble::eYes,
ChromeOnlyDispatch::eYes, Composed::eYes);
dispatcher->PostDOMEvent();
}
/**
* 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 {
if (IsXULElement()) {
// XXX should be SetDOMStringToNull(aReturn);
// See bug 232598
// aReturn is already empty
} 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);