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 <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/UnbindContext.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(); }
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};
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); }
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;
}
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
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);
}
}();
}