summaryrefslogtreecommitdiffstats
path: root/dom/base/Element.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dom/base/Element.cpp4485
1 files changed, 4485 insertions, 0 deletions
diff --git a/dom/base/Element.cpp b/dom/base/Element.cpp
new file mode 100644
index 0000000000..c049e80cef
--- /dev/null
+++ b/dom/base/Element.cpp
@@ -0,0 +1,4485 @@
+/* -*- 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/ErrorResult.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/EventListenerManager.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/EventStates.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/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/ElementInlines.h"
+#include "mozilla/dom/Flex.h"
+#include "mozilla/dom/FromParser.h"
+#include "mozilla/dom/Grid.h"
+#include "mozilla/dom/HTMLDivElement.h"
+#include "mozilla/dom/HTMLParagraphElement.h"
+#include "mozilla/dom/HTMLPreElement.h"
+#include "mozilla/dom/HTMLSpanElement.h"
+#include "mozilla/dom/HTMLTableCellElement.h"
+#include "mozilla/dom/KeyframeAnimationOptionsBinding.h"
+#include "mozilla/dom/KeyframeEffect.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/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/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 "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"
+
+#ifdef MOZ_XUL
+# include "nsXULElement.h"
+#endif /* MOZ_XUL */
+
+#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(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);
+ }
+
+ return GetPrimaryFrame();
+}
+
+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 (nsContentUtils::ShouldResistFingerprinting(GetComposedDoc()) &&
+ 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 (nsContentUtils::ShouldResistFingerprinting(GetComposedDoc()) &&
+ 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;
+}
+
+EventStates Element::IntrinsicState() const {
+ return IsEditable() ? NS_EVENT_STATE_READWRITE : NS_EVENT_STATE_READONLY;
+}
+
+void Element::NotifyStateChange(EventStates aStates) {
+ Document* doc = GetComposedDoc();
+ if (doc) {
+ nsAutoScriptBlocker scriptBlocker;
+ doc->ContentStateChanged(this, aStates);
+ }
+}
+
+void Element::UpdateLinkState(EventStates aState) {
+ MOZ_ASSERT(!aState.HasAtLeastOneOfStates(
+ ~(NS_EVENT_STATE_VISITED | NS_EVENT_STATE_UNVISITED)),
+ "Unexpected link state bits");
+ mState =
+ (mState & ~(NS_EVENT_STATE_VISITED | NS_EVENT_STATE_UNVISITED)) | aState;
+}
+
+void Element::UpdateState(bool aNotify) {
+ EventStates oldState = mState;
+ mState = IntrinsicState() | (oldState & EXTERNALLY_MANAGED_STATES);
+ if (aNotify) {
+ EventStates changedStates = oldState ^ mState;
+ if (!changedStates.IsEmpty()) {
+ Document* doc = GetComposedDoc();
+ if (doc) {
+ nsAutoScriptBlocker scriptBlocker;
+ doc->ContentStateChanged(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(NS_EVENT_STATE_READONLY);
+ AddStatesSilently(NS_EVENT_STATE_READWRITE);
+ } else {
+ RemoveStatesSilently(NS_EVENT_STATE_READWRITE);
+ AddStatesSilently(NS_EVENT_STATE_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) {
+ RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager();
+ if (!fm) {
+ return;
+ }
+ // 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(*this);
+ fm->NeedsFlushBeforeEventHandling(this);
+ return;
+ }
+ uint32_t fmFlags = nsFocusManager::FocusOptionsToFocusManagerFlags(aOptions);
+ if (aCallerType == CallerType::NonSystem) {
+ fmFlags |= nsIFocusManager::FLAG_NONSYSTEMCALLER;
+ }
+ aError = fm->SetFocus(this, 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::Blur(mozilla::ErrorResult& aError) {
+ if (!ShouldBlur(this)) {
+ return;
+ }
+
+ Document* doc = GetComposedDoc();
+ if (!doc) {
+ return;
+ }
+
+ nsPIDOMWindowOuter* win = doc->GetWindow();
+ nsFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (win && fm) {
+ aError = fm->ClearFocus(win);
+ }
+}
+
+EventStates Element::StyleStateFromLocks() const {
+ StyleStateLocks locksAndValues = LockedStyleStates();
+ EventStates locks = locksAndValues.mLocks;
+ EventStates values = locksAndValues.mValues;
+ EventStates state = (mState & ~locks) | (locks & values);
+
+ if (state.HasState(NS_EVENT_STATE_VISITED)) {
+ return state & ~NS_EVENT_STATE_UNVISITED;
+ }
+ if (state.HasState(NS_EVENT_STATE_UNVISITED)) {
+ return state & ~NS_EVENT_STATE_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(EventStates aStates) {
+ Document* doc = GetComposedDoc();
+ if (doc) {
+ RefPtr<PresShell> presShell = doc->GetPresShell();
+ if (presShell) {
+ nsAutoScriptBlocker scriptBlocker;
+ presShell->ContentStateChanged(doc, this, aStates);
+ }
+ }
+}
+
+void Element::LockStyleStates(EventStates aStates, bool aEnabled) {
+ StyleStateLocks* locks = new StyleStateLocks(LockedStyleStates());
+
+ locks->mLocks |= aStates;
+ if (aEnabled) {
+ locks->mValues |= aStates;
+ } else {
+ locks->mValues &= ~aStates;
+ }
+
+ if (aStates.HasState(NS_EVENT_STATE_VISITED)) {
+ locks->mLocks &= ~NS_EVENT_STATE_UNVISITED;
+ }
+ if (aStates.HasState(NS_EVENT_STATE_UNVISITED)) {
+ locks->mLocks &= ~NS_EVENT_STATE_VISITED;
+ }
+
+ SetProperty(nsGkAtoms::lockedStyleStates, locks,
+ nsINode::DeleteProperty<StyleStateLocks>);
+ SetHasLockedStyleStates();
+
+ NotifyStyleStateChange(aStates);
+}
+
+void Element::UnlockStyleStates(EventStates 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::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;
+ }
+
+ // menu frames implement GetScrollTargetFrame but we don't want
+ // to use it here. Similar for comboboxes.
+ LayoutFrameType type = frame->Type();
+ if (type != LayoutFrameType::Menu &&
+ type != LayoutFrameType::ComboboxControl) {
+ nsIScrollableFrame* scrollFrame = frame->GetScrollTargetFrame();
+ if (scrollFrame) {
+ 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 = OwnerDoc()->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;
+}
+
+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;
+ }
+
+ WhereToScroll whereToScrollVertically = kScrollToCenter;
+ switch (aOptions.mBlock) {
+ case ScrollLogicalPosition::Start:
+ whereToScrollVertically = kScrollToTop;
+ break;
+ case ScrollLogicalPosition::Center:
+ whereToScrollVertically = kScrollToCenter;
+ break;
+ case ScrollLogicalPosition::End:
+ whereToScrollVertically = kScrollToBottom;
+ break;
+ case ScrollLogicalPosition::Nearest:
+ whereToScrollVertically = kScrollMinimum;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected ScrollLogicalPosition value");
+ }
+
+ WhereToScroll whereToScrollHorizontally = kScrollToCenter;
+ switch (aOptions.mInline) {
+ case ScrollLogicalPosition::Start:
+ whereToScrollHorizontally = kScrollToLeft;
+ break;
+ case ScrollLogicalPosition::Center:
+ whereToScrollHorizontally = kScrollToCenter;
+ break;
+ case ScrollLogicalPosition::End:
+ whereToScrollHorizontally = kScrollToRight;
+ break;
+ case ScrollLogicalPosition::Nearest:
+ whereToScrollHorizontally = kScrollMinimum;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected ScrollLogicalPosition value");
+ }
+
+ ScrollFlags scrollFlags =
+ ScrollFlags::ScrollOverflowHidden | ScrollFlags::ScrollSnap;
+ if (aOptions.mBehavior == ScrollBehavior::Smooth) {
+ scrollFlags |= ScrollFlags::ScrollSmooth;
+ } else if (aOptions.mBehavior == ScrollBehavior::Auto) {
+ scrollFlags |= ScrollFlags::ScrollSmoothAuto;
+ }
+
+ presShell->ScrollContentIntoView(
+ this, ScrollAxis(whereToScrollVertically, WhenToScroll::Always),
+ ScrollAxis(whereToScrollHorizontally, 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,
+ mozilla::ScrollOrigin::Relative);
+ }
+}
+
+int32_t Element::ScrollTop() {
+ nsIScrollableFrame* sf = GetScrollFrame();
+ return sf ? sf->GetScrollPositionCSSPixels().y : 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 : 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
+ bool overlayScrollbars =
+ LookAndFeel::GetInt(LookAndFeel::IntID::UseOverlayScrollbars) != 0;
+ bool rootContentDocument =
+ presContext && presContext->IsRootContentDocument();
+ if (overlayScrollbars && rootContentDocument &&
+ doc->IsScrollingElement(this)) {
+ // We will always have a pres shell if we have a pres context, and we will
+ // only get here if we have a pres context from the root content document
+ // check
+ PresShell* presShell = doc->GetPresShell();
+
+ // Ensure up to date dimensions, but don't reflow
+ RefPtr<nsViewManager> viewManager = presShell->GetViewManager();
+ if (viewManager) {
+ viewManager->FlushDelayedResize(false);
+ }
+ 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);
+}
+
+already_AddRefed<DOMRect> Element::GetBoundingClientRect() {
+ RefPtr<DOMRect> rect = new DOMRect(this);
+
+ nsIFrame* frame = GetPrimaryFrame(FlushType::Layout);
+ if (!frame) {
+ // display:none, perhaps? Return the empty rect
+ return rect.forget();
+ }
+
+ nsRect r = nsLayoutUtils::GetAllInFlowRectsUnion(
+ frame, nsLayoutUtils::GetContainingBlockForClientRect(frame),
+ nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS);
+ rect->SetLayoutRect(r);
+ 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);
+}
+
+// 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", 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.
+ CustomElementData* ceData = GetCustomElementData();
+ if (StaticPrefs::dom_webcomponents_formAssociatedCustomElement_enabled() &&
+ ceData) {
+ 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) {
+ /**
+ * 1. If context object's namespace is not the HTML namespace,
+ * then throw a "NotSupportedError" DOMException.
+ * 2. If context object's local name is not valid to attach shadow DOM to,
+ * then throw a "NotSupportedError" DOMException.
+ */
+ if (!CanAttachShadowDOM()) {
+ aError.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return nullptr;
+ }
+
+ /**
+ * 4. If context object is a shadow host, then throw
+ * an "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);
+}
+
+already_AddRefed<ShadowRoot> Element::AttachShadowWithoutNameChecks(
+ ShadowRootMode aMode) {
+ 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
+ // bug 1397876.
+ if (HasChildren()) {
+ if (Document* doc = GetComposedDoc()) {
+ if (PresShell* presShell = doc->GetPresShell()) {
+ presShell->DestroyFramesForAndRestyle(this);
+ }
+ }
+ MOZ_ASSERT(!GetPrimaryFrame());
+ }
+
+ /**
+ * 4. 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, nodeInfo.forget());
+
+ if (NodeOrAncestorHasDirAuto()) {
+ shadowRoot->SetAncestorHasDirAuto();
+ }
+
+ /**
+ * 5. Set context object's shadow root to shadow.
+ */
+ SetShadowRoot(shadowRoot);
+
+ // Dispatch a "shadowrootattached" event for devtools.
+ {
+ AsyncEventDispatcher* dispatcher = new AsyncEventDispatcher(
+ this, u"shadowrootattached"_ns, CanBubble::eYes,
+ ChromeOnlyDispatch::eYes, Composed::eYes);
+ dispatcher->PostDOMEvent();
+ }
+
+ /**
+ * 6. Return shadow.
+ */
+ return shadowRoot.forget();
+}
+
+void Element::AttachAndSetUAShadowRoot(NotifyUAWidgetSetup aNotify) {
+ MOZ_DIAGNOSTIC_ASSERT(!CanAttachShadowDOM(),
+ "Cannot be used to attach UI shadow DOM");
+ if (OwnerDoc()->IsStaticDocument()) {
+ return;
+ }
+
+ if (!GetShadowRoot()) {
+ RefPtr<ShadowRoot> shadowRoot =
+ AttachShadowWithoutNameChecks(ShadowRootMode::Closed);
+ 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() {
+ ShadowRoot* shadowRoot = GetShadowRoot();
+ if (!shadowRoot) {
+ return;
+ }
+
+ nsAutoScriptBlocker scriptBlocker;
+
+ if (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
+ }
+ }
+ MOZ_ASSERT(!GetPrimaryFrame());
+
+ shadowRoot->Unattach();
+ SetShadowRoot(nullptr);
+
+ // Beware shadowRoot could be dead after this call.
+}
+
+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);
+
+ 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 = nsContentUtils::NameSpaceManager()->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::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 = nsContentUtils::NameSpaceManager()->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 = nsContentUtils::NameSpaceManager()->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 = nsContentUtils::NameSpaceManager()->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);
+}
+
+void Element::GetElementsWithGrid(nsTArray<RefPtr<Element>>& aElements) {
+ nsINode* cur = this;
+ while (cur) {
+ if (cur->IsElement()) {
+ Element* elem = cur->AsElement();
+
+ if (elem->GetPrimaryFrame()) {
+ // See if this has a GridContainerFrame. Use the same method that
+ // nsGridContainerFrame uses, which deals with some edge cases.
+ if (nsGridContainerFrame::GetGridContainerFrame(
+ elem->GetPrimaryFrame())) {
+ aElements.AppendElement(elem);
+ }
+
+ // This element has a frame, so allow the traversal to go through
+ // the children.
+ cur = cur->GetNextNode(this);
+ continue;
+ }
+ }
+
+ // Either this isn't an element, or it has no frame. Continue with the
+ // traversal but ignore all the children.
+ cur = cur->GetNextNonChildNode(this);
+ }
+}
+
+bool Element::HasVisibleScrollbars() {
+ nsIScrollableFrame* scrollFrame = GetScrollFrame();
+ return scrollFrame && (!scrollFrame->GetScrollbarVisibility().isEmpty());
+}
+
+nsresult Element::BindToTree(BindContext& aContext, nsINode& aParent) {
+ MOZ_ASSERT(aParent.IsContent() || aParent.IsDocument(),
+ "Must have content or document parent!");
+ MOZ_ASSERT(aParent.OwnerDoc() == OwnerDoc(),
+ "Must have the same owner document");
+ MOZ_ASSERT(OwnerDoc() == &aContext.OwnerDoc(), "These should match too");
+ MOZ_ASSERT(!IsInUncomposedDoc(), "Already have a document. Unbind first!");
+ MOZ_ASSERT(!IsInComposedDoc(), "Already have a document. Unbind first!");
+ // Note that as we recurse into the kids, they'll have a non-null parent. So
+ // only assert if our parent is _changing_ while we have a parent.
+ MOZ_ASSERT(!GetParentNode() || &aParent == GetParentNode(),
+ "Already have a parent. Unbind first!");
+
+ const bool hadParent = !!GetParentNode();
+
+ if (aParent.IsInNativeAnonymousSubtree()) {
+ SetFlags(NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE);
+ }
+ if (aParent.HasFlag(NODE_HAS_BEEN_IN_UA_WIDGET)) {
+ SetFlags(NODE_HAS_BEEN_IN_UA_WIDGET);
+ }
+ if (IsRootOfNativeAnonymousSubtree()) {
+ aParent.SetMayHaveAnonymousChildren();
+ }
+
+ // Now set the parent.
+ mParent = &aParent;
+ if (!hadParent && aParent.IsContent()) {
+ SetParentIsContent(true);
+ NS_ADDREF(mParent);
+ }
+ MOZ_ASSERT(!!GetParent() == aParent.IsContent());
+
+ MOZ_ASSERT(!HasAnyOfFlags(Element::kAllServoDescendantBits));
+
+ // Finally, set the document
+ if (aParent.IsInUncomposedDoc() || aParent.IsInShadowTree()) {
+ // We no longer need to track the subtree pointer (and in fact we'll assert
+ // if we do this any later).
+ ClearSubtreeRootPointer();
+ SetIsConnected(aParent.IsInComposedDoc());
+
+ if (aParent.IsInUncomposedDoc()) {
+ SetIsInDocument();
+ } else {
+ SetFlags(NODE_IS_IN_SHADOW_TREE);
+ MOZ_ASSERT(aParent.IsContent() &&
+ aParent.AsContent()->GetContainingShadow());
+ ExtendedDOMSlots()->mContainingShadow =
+ aParent.AsContent()->GetContainingShadow();
+ }
+ // Clear the lazy frame construction bits.
+ UnsetFlags(NODE_NEEDS_FRAME | NODE_DESCENDANTS_NEED_FRAMES);
+ } else {
+ // If we're not in the doc and not in a shadow tree,
+ // update our subtree pointer.
+ SetSubtreeRootPointer(aParent.SubtreeRoot());
+ }
+
+ if (IsInComposedDoc()) {
+ // Connected callback must be enqueued whenever a custom element becomes
+ // connected.
+ if (CustomElementData* data = GetCustomElementData()) {
+ if (data->mState == CustomElementData::State::eCustom) {
+ nsContentUtils::EnqueueLifecycleCallback(
+ ElementCallbackType::eConnected, this);
+ } else {
+ // Step 7.7.2.2 https://dom.spec.whatwg.org/#concept-node-insert
+ nsContentUtils::TryToUpgradeElement(this);
+ }
+ }
+ }
+
+ // This has to be here, rather than in nsGenericHTMLElement::BindToTree,
+ // because it has to happen after updating the parent pointer, but before
+ // recursively binding the kids.
+ if (IsHTMLElement()) {
+ SetDirOnBind(this, nsIContent::FromNode(aParent));
+ }
+
+ UpdateEditableState(false);
+
+ // Call BindToTree on shadow root children.
+ nsresult rv;
+ if (ShadowRoot* shadowRoot = GetShadowRoot()) {
+ rv = shadowRoot->Bind();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Now recurse into our kids. Ensure this happens after binding the shadow
+ // root so that directionality of slots is updated.
+ {
+ for (nsIContent* child = GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ rv = child->BindToTree(aContext, *this);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ MutationObservers::NotifyParentChainChanged(this);
+ if (!hadParent && IsRootOfNativeAnonymousSubtree()) {
+ MutationObservers::NotifyNativeAnonymousChildListChange(this, false);
+ }
+
+ // Ensure we only run this once, in the case we move the ShadowRoot around.
+ if (aContext.SubtreeRootChanges()) {
+ if (HasPartAttribute()) {
+ if (ShadowRoot* shadow = GetContainingShadow()) {
+ shadow->PartAdded(*this);
+ }
+ }
+ if (HasID()) {
+ AddToIdTable(DoGetID());
+ }
+ HandleShadowDOMRelatedInsertionSteps(hadParent);
+ }
+
+ if (MayHaveStyle() && !IsXULElement()) {
+ // XXXbz if we already have a style attr parsed, this won't do
+ // anything... need to fix that.
+ // If MayHaveStyle() is true, we must be an nsStyledElement
+ static_cast<nsStyledElement*>(this)->ReparseStyleAttribute(
+ /* aForceInDataDoc = */ false);
+ }
+
+ // FIXME(emilio): Why is this needed? The element shouldn't even be styled in
+ // the first place, we should style it properly eventually.
+ //
+ // Also, if this _is_ needed, then it's wrong and should use GetComposedDoc()
+ // to account for Shadow DOM.
+ if (aParent.IsInUncomposedDoc() && MayHaveAnimations()) {
+ PseudoStyleType pseudoType = GetPseudoElementType();
+ if ((pseudoType == PseudoStyleType::NotPseudo ||
+ pseudoType == PseudoStyleType::before ||
+ pseudoType == PseudoStyleType::after ||
+ pseudoType == PseudoStyleType::marker) &&
+ EffectSet::GetEffectSet(this, pseudoType)) {
+ if (nsPresContext* presContext = aContext.OwnerDoc().GetPresContext()) {
+ presContext->EffectCompositor()->RequestRestyle(
+ this, pseudoType, EffectCompositor::RestyleType::Standard,
+ EffectCompositor::CascadeLevel::Animations);
+ }
+ }
+ }
+
+ // XXXbz script execution during binding can trigger some of these
+ // postcondition asserts.... But we do want that, since things will
+ // generally be quite broken when that happens.
+ MOZ_ASSERT(OwnerDoc() == aParent.OwnerDoc(), "Bound to wrong document");
+ MOZ_ASSERT(IsInComposedDoc() == aContext.InComposedDoc());
+ MOZ_ASSERT(IsInUncomposedDoc() == aContext.InUncomposedDoc());
+ MOZ_ASSERT(&aParent == GetParentNode(), "Bound to wrong parent node");
+ MOZ_ASSERT(aParent.IsInUncomposedDoc() == IsInUncomposedDoc());
+ MOZ_ASSERT(aParent.IsInComposedDoc() == IsInComposedDoc());
+ MOZ_ASSERT(aParent.IsInShadowTree() == IsInShadowTree());
+ MOZ_ASSERT(aParent.SubtreeRoot() == SubtreeRoot());
+ return NS_OK;
+}
+
+bool WillDetachFromShadowOnUnbind(const Element& aElement, bool aNullParent) {
+ // If our parent still is in a shadow tree by now, and we're not removing
+ // ourselves from it, then we're still going to be in a shadow tree after
+ // this.
+ return aElement.IsInShadowTree() &&
+ (aNullParent || !aElement.GetParent()->IsInShadowTree());
+}
+
+void Element::UnbindFromTree(bool aNullParent) {
+ HandleShadowDOMRelatedRemovalSteps(aNullParent);
+
+ const bool detachingFromShadow =
+ WillDetachFromShadowOnUnbind(*this, aNullParent);
+ // Make sure to only remove from the ID table if our subtree root is actually
+ // changing.
+ if (IsInUncomposedDoc() || detachingFromShadow) {
+ RemoveFromIdTable();
+ }
+
+ if (detachingFromShadow && HasPartAttribute()) {
+ if (ShadowRoot* shadow = GetContainingShadow()) {
+ shadow->PartRemoved(*this);
+ }
+ }
+
+ // Make sure to unbind this node before doing the kids
+ Document* document = GetComposedDoc();
+
+ if (HasPointerLock()) {
+ Document::UnlockPointer();
+ }
+ if (mState.HasState(NS_EVENT_STATE_FULLSCREEN)) {
+ // The element being removed is an ancestor of the fullscreen element,
+ // exit fullscreen state.
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns,
+ OwnerDoc(), nsContentUtils::eDOM_PROPERTIES,
+ "RemovedFullscreenElement");
+ // Fully exit fullscreen.
+ Document::ExitFullscreenInDocTree(OwnerDoc());
+ }
+
+ if (HasServoData()) {
+ MOZ_ASSERT(document);
+ MOZ_ASSERT(IsInNativeAnonymousSubtree());
+ }
+
+ if (document) {
+ ClearServoData(document);
+ }
+
+ if (aNullParent) {
+ if (IsRootOfNativeAnonymousSubtree()) {
+ MutationObservers::NotifyNativeAnonymousChildListChange(this, true);
+ }
+
+ if (GetParent()) {
+ RefPtr<nsINode> p;
+ p.swap(mParent);
+ } else {
+ mParent = nullptr;
+ }
+ SetParentIsContent(false);
+ }
+
+#ifdef DEBUG
+ // If we can get access to the PresContext, then we sanity-check that
+ // we're not leaving behind a pointer to ourselves as the PresContext's
+ // cached provider of the viewport's scrollbar styles.
+ if (document) {
+ nsPresContext* presContext = document->GetPresContext();
+ if (presContext) {
+ MOZ_ASSERT(this != presContext->GetViewportScrollStylesOverrideElement(),
+ "Leaving behind a raw pointer to this element (as having "
+ "propagated scrollbar styles) - that's dangerous...");
+ }
+ }
+
+# ifdef ACCESSIBILITY
+ MOZ_ASSERT(!GetAccService() || !GetAccService()->HasAccessible(this),
+ "An accessible for this element still exists!");
+# endif
+#endif
+
+ // Ensure that CSS transitions don't continue on an element at a
+ // different place in the tree (even if reinserted before next
+ // animation refresh).
+ //
+ // We need to delete the properties while we're still in document
+ // (if we were in document) so that they can look up the
+ // PendingAnimationTracker on the document and remove their animations,
+ // and so they can find their pres context for dispatching cancel events.
+ //
+ // FIXME (Bug 522599): Need a test for this.
+ if (MayHaveAnimations()) {
+ RemoveProperty(nsGkAtoms::transitionsOfBeforeProperty);
+ RemoveProperty(nsGkAtoms::transitionsOfAfterProperty);
+ RemoveProperty(nsGkAtoms::transitionsOfMarkerProperty);
+ RemoveProperty(nsGkAtoms::transitionsProperty);
+ RemoveProperty(nsGkAtoms::animationsOfBeforeProperty);
+ RemoveProperty(nsGkAtoms::animationsOfAfterProperty);
+ RemoveProperty(nsGkAtoms::animationsOfMarkerProperty);
+ RemoveProperty(nsGkAtoms::animationsProperty);
+ if (document) {
+ if (nsPresContext* presContext = document->GetPresContext()) {
+ // We have to clear all pending restyle requests for the animations on
+ // this element to avoid unnecessary restyles when we re-attached this
+ // element.
+ presContext->EffectCompositor()->ClearRestyleRequestsFor(this);
+ }
+ }
+ }
+
+ ClearInDocument();
+ SetIsConnected(false);
+ if (HasElementCreatedFromPrototypeAndHasUnmodifiedL10n()) {
+ if (document) {
+ document->mL10nProtoElements.Remove(this);
+ }
+ ClearElementCreatedFromPrototypeAndHasUnmodifiedL10n();
+ }
+
+ if (aNullParent || !mParent->IsInShadowTree()) {
+ UnsetFlags(NODE_IS_IN_SHADOW_TREE);
+
+ // Begin keeping track of our subtree root.
+ SetSubtreeRootPointer(aNullParent ? this : mParent->SubtreeRoot());
+ }
+
+ if (nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots()) {
+ if (aNullParent || !mParent->IsInShadowTree()) {
+ slots->mContainingShadow = nullptr;
+ }
+ }
+
+ if (document) {
+ // Disconnected must be enqueued whenever a connected custom element becomes
+ // disconnected.
+ CustomElementData* data = GetCustomElementData();
+ if (data) {
+ if (data->mState == CustomElementData::State::eCustom) {
+ nsContentUtils::EnqueueLifecycleCallback(
+ ElementCallbackType::eDisconnected, this);
+ } else {
+ // Remove an unresolved custom element that is a candidate for upgrade
+ // when a custom element is disconnected.
+ nsContentUtils::UnregisterUnresolvedElement(this);
+ }
+ }
+ }
+
+ // This has to be here, rather than in nsGenericHTMLElement::UnbindFromTree,
+ // because it has to happen after unsetting the parent pointer, but before
+ // recursively unbinding the kids.
+ if (IsHTMLElement()) {
+ ResetDir(this);
+ }
+
+ for (nsIContent* child = GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ // Note that we pass false for aNullParent here, since we don't want
+ // the kids to forget us.
+ child->UnbindFromTree(false);
+ }
+
+ MutationObservers::NotifyParentChainChanged(this);
+
+ // Unbind children of shadow root.
+ if (ShadowRoot* shadowRoot = GetShadowRoot()) {
+ shadowRoot->Unbind();
+ }
+
+ MOZ_ASSERT(!HasAnyOfFlags(kAllServoDescendantBits));
+ MOZ_ASSERT(!document || document->GetServoRestyleRoot() != this);
+}
+
+UniquePtr<SMILAttr> Element::GetAnimatedAttr(int32_t aNamespaceID,
+ nsAtom* aName) {
+ return nullptr;
+}
+
+nsDOMCSSAttributeDeclaration* Element::SMILOverrideStyle() {
+ Element::nsExtendedDOMSlots* slots = ExtendedDOMSlots();
+
+ if (!slots->mSMILOverrideStyle) {
+ slots->mSMILOverrideStyle = new nsDOMCSSAttributeDeclaration(this, true);
+ }
+
+ return slots->mSMILOverrideStyle;
+}
+
+DeclarationBlock* Element::GetSMILOverrideStyleDeclaration() {
+ Element::nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots();
+ return slots ? slots->mSMILOverrideStyleDeclaration.get() : nullptr;
+}
+
+void Element::SetSMILOverrideStyleDeclaration(DeclarationBlock& aDeclaration) {
+ ExtendedDOMSlots()->mSMILOverrideStyleDeclaration = &aDeclaration;
+
+ // Only need to request a restyle if we're in a document. (We might not
+ // be in a document, if we're clearing animation effects on a target node
+ // that's been detached since the previous animation sample.)
+ if (Document* doc = GetComposedDoc()) {
+ if (PresShell* presShell = doc->GetPresShell()) {
+ presShell->RestyleForAnimation(this, RestyleHint::RESTYLE_SMIL);
+ }
+ }
+}
+
+bool Element::IsLabelable() const { return false; }
+
+bool Element::IsInteractiveHTMLContent() const { return false; }
+
+DeclarationBlock* Element::GetInlineStyleDeclaration() const {
+ if (!MayHaveStyle()) {
+ return nullptr;
+ }
+ const nsAttrValue* attrVal = mAttrs.GetAttr(nsGkAtoms::style);
+
+ if (attrVal && attrVal->Type() == nsAttrValue::eCSSDeclaration) {
+ return attrVal->GetCSSDeclarationValue();
+ }
+
+ return nullptr;
+}
+
+const nsMappedAttributes* Element::GetMappedAttributes() const {
+ return mAttrs.GetMapped();
+}
+
+void Element::InlineStyleDeclarationWillChange(MutationClosureData& aData) {
+ MOZ_ASSERT_UNREACHABLE("Element::InlineStyleDeclarationWillChange");
+}
+
+nsresult Element::SetInlineStyleDeclaration(DeclarationBlock& aDeclaration,
+ MutationClosureData& aData) {
+ MOZ_ASSERT_UNREACHABLE("Element::SetInlineStyleDeclaration");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP_(bool)
+Element::IsAttributeMapped(const nsAtom* aAttribute) const { return false; }
+
+nsChangeHint Element::GetAttributeChangeHint(const nsAtom* aAttribute,
+ int32_t aModType) const {
+ return nsChangeHint(0);
+}
+
+bool Element::FindAttributeDependence(const nsAtom* aAttribute,
+ const MappedAttributeEntry* const aMaps[],
+ uint32_t aMapCount) {
+ for (uint32_t mapindex = 0; mapindex < aMapCount; ++mapindex) {
+ for (const MappedAttributeEntry* map = aMaps[mapindex]; map->attribute;
+ ++map) {
+ if (aAttribute == map->attribute) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+already_AddRefed<mozilla::dom::NodeInfo> Element::GetExistingAttrNameFromQName(
+ const nsAString& aStr) const {
+ const nsAttrName* name = InternalGetAttrNameFromQName(aStr);
+ if (!name) {
+ return nullptr;
+ }
+
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo;
+ if (name->IsAtom()) {
+ nodeInfo = mNodeInfo->NodeInfoManager()->GetNodeInfo(
+ name->Atom(), nullptr, kNameSpaceID_None, ATTRIBUTE_NODE);
+ } else {
+ nodeInfo = name->NodeInfo();
+ }
+
+ return nodeInfo.forget();
+}
+
+// static
+bool Element::ShouldBlur(nsIContent* aContent) {
+ // Determine if the current element is focused, if it is not focused
+ // then we should not try to blur
+ Document* document = aContent->GetComposedDoc();
+ if (!document) return false;
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = document->GetWindow();
+ if (!window) return false;
+
+ nsCOMPtr<nsPIDOMWindowOuter> focusedFrame;
+ nsIContent* contentToBlur = nsFocusManager::GetFocusedDescendant(
+ window, nsFocusManager::eOnlyCurrentWindow, getter_AddRefs(focusedFrame));
+ if (contentToBlur == aContent) return true;
+
+ // if focus on this element would get redirected, then check the redirected
+ // content as well when blurring.
+ return (contentToBlur &&
+ nsFocusManager::GetRedirectedFocus(aContent) == contentToBlur);
+}
+
+bool Element::IsNodeOfType(uint32_t aFlags) const { return false; }
+
+/* static */
+nsresult Element::DispatchEvent(nsPresContext* aPresContext,
+ WidgetEvent* aEvent, nsIContent* aTarget,
+ bool aFullDispatch, nsEventStatus* aStatus) {
+ MOZ_ASSERT(aTarget, "Must have target");
+ MOZ_ASSERT(aEvent, "Must have source event");
+ MOZ_ASSERT(aStatus, "Null out param?");
+
+ if (!aPresContext) {
+ return NS_OK;
+ }
+
+ RefPtr<PresShell> presShell = aPresContext->GetPresShell();
+ if (!presShell) {
+ return NS_OK;
+ }
+
+ if (aFullDispatch) {
+ return presShell->HandleEventWithTarget(aEvent, nullptr, aTarget, aStatus);
+ }
+
+ return presShell->HandleDOMEventWithTarget(aTarget, aEvent, aStatus);
+}
+
+/* static */
+nsresult Element::DispatchClickEvent(nsPresContext* aPresContext,
+ WidgetInputEvent* aSourceEvent,
+ nsIContent* aTarget, bool aFullDispatch,
+ const EventFlags* aExtraEventFlags,
+ nsEventStatus* aStatus) {
+ MOZ_ASSERT(aTarget, "Must have target");
+ MOZ_ASSERT(aSourceEvent, "Must have source event");
+ MOZ_ASSERT(aStatus, "Null out param?");
+
+ WidgetMouseEvent event(aSourceEvent->IsTrusted(), eMouseClick,
+ aSourceEvent->mWidget, WidgetMouseEvent::eReal);
+ event.mRefPoint = aSourceEvent->mRefPoint;
+ uint32_t clickCount = 1;
+ float pressure = 0;
+ uint32_t pointerId = 0; // Use the default value here.
+ uint16_t inputSource = 0;
+ WidgetMouseEvent* sourceMouseEvent = aSourceEvent->AsMouseEvent();
+ if (sourceMouseEvent) {
+ clickCount = sourceMouseEvent->mClickCount;
+ pressure = sourceMouseEvent->mPressure;
+ pointerId = sourceMouseEvent->pointerId;
+ inputSource = sourceMouseEvent->mInputSource;
+ } else if (aSourceEvent->mClass == eKeyboardEventClass) {
+ event.mFlags.mIsPositionless = true;
+ inputSource = MouseEvent_Binding::MOZ_SOURCE_KEYBOARD;
+ }
+ event.mPressure = pressure;
+ event.mClickCount = clickCount;
+ event.pointerId = pointerId;
+ event.mInputSource = inputSource;
+ event.mModifiers = aSourceEvent->mModifiers;
+ if (aExtraEventFlags) {
+ // Be careful not to overwrite existing flags!
+ event.mFlags.Union(*aExtraEventFlags);
+ }
+
+ return DispatchEvent(aPresContext, &event, aTarget, aFullDispatch, aStatus);
+}
+
+//----------------------------------------------------------------------
+nsresult Element::LeaveLink(nsPresContext* aPresContext) {
+ if (!aPresContext || !aPresContext->Document()->LinkHandlingEnabled()) {
+ return NS_OK;
+ }
+ nsIDocShell* shell = aPresContext->Document()->GetDocShell();
+ if (!shell) {
+ return NS_OK;
+ }
+ return nsDocShell::Cast(shell)->OnLeaveLink();
+}
+
+void Element::SetEventHandler(nsAtom* aEventName, const nsAString& aValue,
+ bool aDefer) {
+ Document* ownerDoc = OwnerDoc();
+ if (ownerDoc->IsLoadedAsData()) {
+ // Make this a no-op rather than throwing an error to avoid
+ // the error causing problems setting the attribute.
+ return;
+ }
+
+ MOZ_ASSERT(aEventName, "Must have event name!");
+ bool defer = true;
+ EventListenerManager* manager =
+ GetEventListenerManagerForAttr(aEventName, &defer);
+ if (!manager) {
+ return;
+ }
+
+ defer = defer && aDefer; // only defer if everyone agrees...
+ manager->SetEventHandler(aEventName, aValue, defer,
+ !nsContentUtils::IsChromeDoc(ownerDoc), this);
+}
+
+//----------------------------------------------------------------------
+
+const nsAttrName* Element::InternalGetAttrNameFromQName(
+ const nsAString& aStr, nsAutoString* aNameToUse) const {
+ MOZ_ASSERT(!aNameToUse || aNameToUse->IsEmpty());
+ const nsAttrName* val = nullptr;
+ if (IsHTMLElement() && IsInHTMLDocument()) {
+ nsAutoString lower;
+ nsAutoString& outStr = aNameToUse ? *aNameToUse : lower;
+ nsContentUtils::ASCIIToLower(aStr, outStr);
+ val = mAttrs.GetExistingAttrNameFromQName(outStr);
+ if (val) {
+ outStr.Truncate();
+ }
+ } else {
+ val = mAttrs.GetExistingAttrNameFromQName(aStr);
+ if (!val && aNameToUse) {
+ *aNameToUse = aStr;
+ }
+ }
+
+ return val;
+}
+
+bool Element::MaybeCheckSameAttrVal(int32_t aNamespaceID, const nsAtom* aName,
+ const nsAtom* aPrefix,
+ const nsAttrValueOrString& aValue,
+ bool aNotify, nsAttrValue& aOldValue,
+ uint8_t* aModType, bool* aHasListeners,
+ bool* aOldValueSet) {
+ bool modification = false;
+ *aHasListeners =
+ aNotify && nsContentUtils::HasMutationListeners(
+ this, NS_EVENT_BITS_MUTATION_ATTRMODIFIED, this);
+ *aOldValueSet = false;
+
+ // If we have no listeners and aNotify is false, we are almost certainly
+ // coming from the content sink and will almost certainly have no previous
+ // value. Even if we do, setting the value is cheap when we have no
+ // listeners and don't plan to notify. The check for aNotify here is an
+ // optimization, the check for *aHasListeners is a correctness issue.
+ if (*aHasListeners || aNotify) {
+ BorrowedAttrInfo info(GetAttrInfo(aNamespaceID, aName));
+ if (info.mValue) {
+ // Check whether the old value is the same as the new one. Note that we
+ // only need to actually _get_ the old value if we have listeners or
+ // if the element is a custom element (because it may have an
+ // attribute changed callback).
+ if (*aHasListeners || GetCustomElementData()) {
+ // Need to store the old value.
+ //
+ // If the current attribute value contains a pointer to some other data
+ // structure that gets updated in the process of setting the attribute
+ // we'll no longer have the old value of the attribute. Therefore, we
+ // should serialize the attribute value now to keep a snapshot.
+ //
+ // We have to serialize the value anyway in order to create the
+ // mutation event so there's no cost in doing it now.
+ aOldValue.SetToSerialized(*info.mValue);
+ *aOldValueSet = true;
+ }
+ bool valueMatches = aValue.EqualsAsStrings(*info.mValue);
+ if (valueMatches && aPrefix == info.mName->GetPrefix()) {
+ return true;
+ }
+ modification = true;
+ }
+ }
+ *aModType = modification
+ ? static_cast<uint8_t>(MutationEvent_Binding::MODIFICATION)
+ : static_cast<uint8_t>(MutationEvent_Binding::ADDITION);
+ return false;
+}
+
+bool Element::OnlyNotifySameValueSet(int32_t aNamespaceID, nsAtom* aName,
+ nsAtom* aPrefix,
+ const nsAttrValueOrString& aValue,
+ bool aNotify, nsAttrValue& aOldValue,
+ uint8_t* aModType, bool* aHasListeners,
+ bool* aOldValueSet) {
+ if (!MaybeCheckSameAttrVal(aNamespaceID, aName, aPrefix, aValue, aNotify,
+ aOldValue, aModType, aHasListeners,
+ aOldValueSet)) {
+ return false;
+ }
+
+ nsAutoScriptBlocker scriptBlocker;
+ MutationObservers::NotifyAttributeSetToCurrentValue(this, aNamespaceID,
+ aName);
+ return true;
+}
+
+nsresult Element::SetSingleClassFromParser(nsAtom* aSingleClassName) {
+ // Keep this in sync with SetAttr and SetParsedAttr below.
+
+ nsAttrValue value(aSingleClassName);
+
+ Document* document = GetComposedDoc();
+ mozAutoDocUpdate updateBatch(document, false);
+
+ // In principle, BeforeSetAttr should be called here if a node type
+ // existed that wanted to do something special for class, but there
+ // is no such node type, so calling SetMayHaveClass() directly.
+ SetMayHaveClass();
+
+ return SetAttrAndNotify(kNameSpaceID_None, nsGkAtoms::_class,
+ nullptr, // prefix
+ nullptr, // old value
+ value, nullptr,
+ static_cast<uint8_t>(MutationEvent_Binding::ADDITION),
+ false, // hasListeners
+ false, // notify
+ kCallAfterSetAttr, document, updateBatch);
+}
+
+nsresult Element::SetAttr(int32_t aNamespaceID, nsAtom* aName, nsAtom* aPrefix,
+ const nsAString& aValue,
+ nsIPrincipal* aSubjectPrincipal, bool aNotify) {
+ // Keep this in sync with SetParsedAttr below and SetSingleClassFromParser
+ // above.
+
+ NS_ENSURE_ARG_POINTER(aName);
+ NS_ASSERTION(aNamespaceID != kNameSpaceID_Unknown,
+ "Don't call SetAttr with unknown namespace");
+
+ uint8_t modType;
+ bool hasListeners;
+ nsAttrValueOrString value(aValue);
+ nsAttrValue oldValue;
+ bool oldValueSet;
+
+ if (OnlyNotifySameValueSet(aNamespaceID, aName, aPrefix, value, aNotify,
+ oldValue, &modType, &hasListeners, &oldValueSet)) {
+ return OnAttrSetButNotChanged(aNamespaceID, aName, value, aNotify);
+ }
+
+ // Hold a script blocker while calling ParseAttribute since that can call
+ // out to id-observers
+ Document* document = GetComposedDoc();
+ mozAutoDocUpdate updateBatch(document, aNotify);
+
+ if (aNotify) {
+ MutationObservers::NotifyAttributeWillChange(this, aNamespaceID, aName,
+ modType);
+ }
+
+ nsresult rv = BeforeSetAttr(aNamespaceID, aName, &value, aNotify);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAttrValue attrValue;
+ if (!ParseAttribute(aNamespaceID, aName, aValue, aSubjectPrincipal,
+ attrValue)) {
+ attrValue.SetTo(aValue);
+ }
+
+ PreIdMaybeChange(aNamespaceID, aName, &value);
+
+ return SetAttrAndNotify(aNamespaceID, aName, aPrefix,
+ oldValueSet ? &oldValue : nullptr, attrValue,
+ aSubjectPrincipal, modType, hasListeners, aNotify,
+ kCallAfterSetAttr, document, updateBatch);
+}
+
+nsresult Element::SetParsedAttr(int32_t aNamespaceID, nsAtom* aName,
+ nsAtom* aPrefix, nsAttrValue& aParsedValue,
+ bool aNotify) {
+ // Keep this in sync with SetAttr and SetSingleClassFromParser above
+
+ NS_ENSURE_ARG_POINTER(aName);
+ NS_ASSERTION(aNamespaceID != kNameSpaceID_Unknown,
+ "Don't call SetAttr with unknown namespace");
+
+ uint8_t modType;
+ bool hasListeners;
+ nsAttrValueOrString value(aParsedValue);
+ nsAttrValue oldValue;
+ bool oldValueSet;
+
+ if (OnlyNotifySameValueSet(aNamespaceID, aName, aPrefix, value, aNotify,
+ oldValue, &modType, &hasListeners, &oldValueSet)) {
+ return OnAttrSetButNotChanged(aNamespaceID, aName, value, aNotify);
+ }
+
+ Document* document = GetComposedDoc();
+ mozAutoDocUpdate updateBatch(document, aNotify);
+
+ if (aNotify) {
+ MutationObservers::NotifyAttributeWillChange(this, aNamespaceID, aName,
+ modType);
+ }
+
+ nsresult rv = BeforeSetAttr(aNamespaceID, aName, &value, aNotify);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ PreIdMaybeChange(aNamespaceID, aName, &value);
+
+ return SetAttrAndNotify(aNamespaceID, aName, aPrefix,
+ oldValueSet ? &oldValue : nullptr, aParsedValue,
+ nullptr, modType, hasListeners, aNotify,
+ kCallAfterSetAttr, document, updateBatch);
+}
+
+nsresult Element::SetAttrAndNotify(
+ int32_t aNamespaceID, nsAtom* aName, nsAtom* aPrefix,
+ const nsAttrValue* aOldValue, nsAttrValue& aParsedValue,
+ nsIPrincipal* aSubjectPrincipal, uint8_t aModType, bool aFireMutation,
+ bool aNotify, bool aCallAfterSetAttr, Document* aComposedDocument,
+ const mozAutoDocUpdate&) {
+ nsresult rv;
+ nsMutationGuard::DidMutate();
+
+ // Copy aParsedValue for later use since it will be lost when we call
+ // SetAndSwapMappedAttr below
+ nsAttrValue valueForAfterSetAttr;
+ if (aCallAfterSetAttr || GetCustomElementData()) {
+ valueForAfterSetAttr.SetTo(aParsedValue);
+ }
+
+ bool hadValidDir = false;
+ bool hadDirAuto = false;
+ bool oldValueSet;
+
+ if (aNamespaceID == kNameSpaceID_None) {
+ if (aName == nsGkAtoms::dir) {
+ hadValidDir = HasValidDir() || IsHTMLElement(nsGkAtoms::bdi);
+ hadDirAuto = HasDirAuto(); // already takes bdi into account
+ }
+
+ // XXXbz Perhaps we should push up the attribute mapping function
+ // stuff to Element?
+ if (!IsAttributeMapped(aName) ||
+ !SetAndSwapMappedAttribute(aName, aParsedValue, &oldValueSet, &rv)) {
+ rv = mAttrs.SetAndSwapAttr(aName, aParsedValue, &oldValueSet);
+ }
+ } else {
+ RefPtr<mozilla::dom::NodeInfo> ni;
+ ni = mNodeInfo->NodeInfoManager()->GetNodeInfo(aName, aPrefix, aNamespaceID,
+ ATTRIBUTE_NODE);
+
+ rv = mAttrs.SetAndSwapAttr(ni, aParsedValue, &oldValueSet);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ PostIdMaybeChange(aNamespaceID, aName, &valueForAfterSetAttr);
+
+ // If the old value owns its own data, we know it is OK to keep using it.
+ // oldValue will be null if there was no previously set value
+ const nsAttrValue* oldValue;
+ if (aParsedValue.StoresOwnData()) {
+ if (oldValueSet) {
+ oldValue = &aParsedValue;
+ } else {
+ oldValue = nullptr;
+ }
+ } else {
+ // No need to conditionally assign null here. If there was no previously
+ // set value for the attribute, aOldValue will already be null.
+ oldValue = aOldValue;
+ }
+
+ if (HasElementCreatedFromPrototypeAndHasUnmodifiedL10n() &&
+ aNamespaceID == kNameSpaceID_None &&
+ (aName == nsGkAtoms::datal10nid || aName == nsGkAtoms::datal10nargs)) {
+ ClearElementCreatedFromPrototypeAndHasUnmodifiedL10n();
+ if (aComposedDocument) {
+ aComposedDocument->mL10nProtoElements.Remove(this);
+ }
+ }
+
+ const CustomElementData* data = GetCustomElementData();
+ if (data && data->mState == CustomElementData::State::eCustom) {
+ CustomElementDefinition* definition = data->GetCustomElementDefinition();
+ MOZ_ASSERT(definition, "Should have a valid CustomElementDefinition");
+
+ if (definition->IsInObservedAttributeList(aName)) {
+ RefPtr<nsAtom> oldValueAtom;
+ if (oldValue) {
+ oldValueAtom = oldValue->GetAsAtom();
+ } else {
+ // If there is no old value, get the value of the uninitialized
+ // attribute that was swapped with aParsedValue.
+ oldValueAtom = aParsedValue.GetAsAtom();
+ }
+ RefPtr<nsAtom> newValueAtom = valueForAfterSetAttr.GetAsAtom();
+ nsAutoString ns;
+ nsContentUtils::NameSpaceManager()->GetNameSpaceURI(aNamespaceID, ns);
+
+ LifecycleCallbackArgs args = {nsDependentAtomString(aName),
+ aModType == MutationEvent_Binding::ADDITION
+ ? VoidString()
+ : nsDependentAtomString(oldValueAtom),
+ nsDependentAtomString(newValueAtom),
+ (ns.IsEmpty() ? VoidString() : ns)};
+
+ nsContentUtils::EnqueueLifecycleCallback(
+ ElementCallbackType::eAttributeChanged, this, &args, nullptr,
+ definition);
+ }
+ }
+
+ if (aCallAfterSetAttr) {
+ rv = AfterSetAttr(aNamespaceID, aName, &valueForAfterSetAttr, oldValue,
+ aSubjectPrincipal, aNotify);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aNamespaceID == kNameSpaceID_None && aName == nsGkAtoms::dir) {
+ OnSetDirAttr(this, &valueForAfterSetAttr, hadValidDir, hadDirAuto,
+ aNotify);
+ }
+ }
+
+ UpdateState(aNotify);
+
+ if (aNotify) {
+ // Don't pass aOldValue to AttributeChanged since it may not be reliable.
+ // Callers only compute aOldValue under certain conditions which may not
+ // be triggered by all nsIMutationObservers.
+ MutationObservers::NotifyAttributeChanged(
+ this, aNamespaceID, aName, aModType,
+ aParsedValue.StoresOwnData() ? &aParsedValue : nullptr);
+ }
+
+ if (aFireMutation) {
+ InternalMutationEvent mutation(true, eLegacyAttrModified);
+
+ nsAutoString ns;
+ nsContentUtils::NameSpaceManager()->GetNameSpaceURI(aNamespaceID, ns);
+ Attr* attrNode =
+ GetAttributeNodeNSInternal(ns, nsDependentAtomString(aName));
+ mutation.mRelatedNode = attrNode;
+
+ mutation.mAttrName = aName;
+ nsAutoString newValue;
+ GetAttr(aNamespaceID, aName, newValue);
+ if (!newValue.IsEmpty()) {
+ mutation.mNewAttrValue = NS_Atomize(newValue);
+ }
+ if (oldValue && !oldValue->IsEmptyString()) {
+ mutation.mPrevAttrValue = oldValue->GetAsAtom();
+ }
+ mutation.mAttrChange = aModType;
+
+ mozAutoSubtreeModified subtree(OwnerDoc(), this);
+ (new AsyncEventDispatcher(this, mutation))->RunDOMEventWhenSafe();
+ }
+
+ return NS_OK;
+}
+
+bool Element::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute,
+ const nsAString& aValue,
+ nsIPrincipal* aMaybeScriptedPrincipal,
+ nsAttrValue& aResult) {
+ if (aAttribute == nsGkAtoms::lang) {
+ aResult.ParseAtom(aValue);
+ return true;
+ }
+
+ if (aNamespaceID == kNameSpaceID_None) {
+ if (aAttribute == nsGkAtoms::_class || aAttribute == nsGkAtoms::part) {
+ aResult.ParseAtomArray(aValue);
+ return true;
+ }
+
+ if (aAttribute == nsGkAtoms::exportparts) {
+ aResult.ParsePartMapping(aValue);
+ return true;
+ }
+
+ if (aAttribute == nsGkAtoms::id) {
+ // Store id as an atom. id="" means that the element has no id,
+ // not that it has an emptystring as the id.
+ if (aValue.IsEmpty()) {
+ return false;
+ }
+ aResult.ParseAtom(aValue);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool Element::SetAndSwapMappedAttribute(nsAtom* aName, nsAttrValue& aValue,
+ bool* aValueWasSet, nsresult* aRetval) {
+ *aRetval = NS_OK;
+ return false;
+}
+
+nsresult Element::BeforeSetAttr(int32_t aNamespaceID, nsAtom* aName,
+ const nsAttrValueOrString* aValue,
+ bool aNotify) {
+ if (aNamespaceID == kNameSpaceID_None) {
+ if (aName == nsGkAtoms::_class && aValue) {
+ // Note: This flag is asymmetrical. It is never unset and isn't exact.
+ // If it is ever made to be exact, we probably need to handle this
+ // similarly to how ids are handled in PreIdMaybeChange and
+ // PostIdMaybeChange.
+ // Note that SetSingleClassFromParser inlines BeforeSetAttr and
+ // calls SetMayHaveClass directly. Making a subclass take action
+ // on the class attribute in a BeforeSetAttr override would
+ // require revising SetSingleClassFromParser.
+ SetMayHaveClass();
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult Element::AfterSetAttr(int32_t aNamespaceID, nsAtom* aName,
+ const nsAttrValue* aValue,
+ const nsAttrValue* aOldValue,
+ nsIPrincipal* aMaybeScriptedPrincipal,
+ bool aNotify) {
+ if (aNamespaceID == kNameSpaceID_None) {
+ if (aName == nsGkAtoms::part) {
+ bool isPart = !!aValue;
+ if (HasPartAttribute() != isPart) {
+ SetHasPartAttribute(isPart);
+ if (ShadowRoot* shadow = GetContainingShadow()) {
+ if (isPart) {
+ shadow->PartAdded(*this);
+ } else {
+ shadow->PartRemoved(*this);
+ }
+ }
+ }
+ MOZ_ASSERT(HasPartAttribute() == isPart);
+ } else if (aName == nsGkAtoms::slot && GetParent()) {
+ if (ShadowRoot* shadow = GetParent()->GetShadowRoot()) {
+ shadow->MaybeReassignElement(*this);
+ }
+ }
+ }
+ return NS_OK;
+}
+
+void Element::PreIdMaybeChange(int32_t aNamespaceID, nsAtom* aName,
+ const nsAttrValueOrString* aValue) {
+ if (aNamespaceID != kNameSpaceID_None || aName != nsGkAtoms::id) {
+ return;
+ }
+ RemoveFromIdTable();
+}
+
+void Element::PostIdMaybeChange(int32_t aNamespaceID, nsAtom* aName,
+ const nsAttrValue* aValue) {
+ if (aNamespaceID != kNameSpaceID_None || aName != nsGkAtoms::id) {
+ return;
+ }
+
+ // id="" means that the element has no id, not that it has an empty
+ // string as the id.
+ if (aValue && !aValue->IsEmptyString()) {
+ SetHasID();
+ AddToIdTable(aValue->GetAtomValue());
+ } else {
+ ClearHasID();
+ }
+}
+
+nsresult Element::OnAttrSetButNotChanged(int32_t aNamespaceID, nsAtom* aName,
+ const nsAttrValueOrString& aValue,
+ bool aNotify) {
+ const CustomElementData* data = GetCustomElementData();
+ if (data && data->mState == CustomElementData::State::eCustom) {
+ CustomElementDefinition* definition = data->GetCustomElementDefinition();
+ MOZ_ASSERT(definition, "Should have a valid CustomElementDefinition");
+
+ if (definition->IsInObservedAttributeList(aName)) {
+ nsAutoString ns;
+ nsContentUtils::NameSpaceManager()->GetNameSpaceURI(aNamespaceID, ns);
+
+ nsAutoString value(aValue.String());
+ LifecycleCallbackArgs args = {nsDependentAtomString(aName), value, value,
+ (ns.IsEmpty() ? VoidString() : ns)};
+
+ nsContentUtils::EnqueueLifecycleCallback(
+ ElementCallbackType::eAttributeChanged, this, &args, nullptr,
+ definition);
+ }
+ }
+
+ return NS_OK;
+}
+
+EventListenerManager* Element::GetEventListenerManagerForAttr(nsAtom* aAttrName,
+ bool* aDefer) {
+ *aDefer = true;
+ return GetOrCreateListenerManager();
+}
+
+bool Element::GetAttr(int32_t aNameSpaceID, const nsAtom* aName,
+ nsAString& aResult) const {
+ DOMString str;
+ bool haveAttr = GetAttr(aNameSpaceID, aName, str);
+ str.ToString(aResult);
+ return haveAttr;
+}
+
+int32_t Element::FindAttrValueIn(int32_t aNameSpaceID, const nsAtom* aName,
+ AttrValuesArray* aValues,
+ nsCaseTreatment aCaseSensitive) const {
+ NS_ASSERTION(aName, "Must have attr name");
+ NS_ASSERTION(aNameSpaceID != kNameSpaceID_Unknown, "Must have namespace");
+ NS_ASSERTION(aValues, "Null value array");
+
+ const nsAttrValue* val = mAttrs.GetAttr(aName, aNameSpaceID);
+ if (val) {
+ for (int32_t i = 0; aValues[i]; ++i) {
+ if (val->Equals(aValues[i], aCaseSensitive)) {
+ return i;
+ }
+ }
+ return ATTR_VALUE_NO_MATCH;
+ }
+ return ATTR_MISSING;
+}
+
+nsresult Element::UnsetAttr(int32_t aNameSpaceID, nsAtom* aName, bool aNotify) {
+ NS_ASSERTION(nullptr != aName, "must have attribute name");
+
+ int32_t index = mAttrs.IndexOfAttr(aName, aNameSpaceID);
+ if (index < 0) {
+ return NS_OK;
+ }
+
+ Document* document = GetComposedDoc();
+ mozAutoDocUpdate updateBatch(document, aNotify);
+
+ if (aNotify) {
+ MutationObservers::NotifyAttributeWillChange(
+ this, aNameSpaceID, aName, MutationEvent_Binding::REMOVAL);
+ }
+
+ nsresult rv = BeforeSetAttr(aNameSpaceID, aName, nullptr, aNotify);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMutationListeners =
+ aNotify && nsContentUtils::HasMutationListeners(
+ this, NS_EVENT_BITS_MUTATION_ATTRMODIFIED, this);
+
+ PreIdMaybeChange(aNameSpaceID, aName, nullptr);
+
+ // Grab the attr node if needed before we remove it from the attr map
+ RefPtr<Attr> attrNode;
+ if (hasMutationListeners) {
+ nsAutoString ns;
+ nsContentUtils::NameSpaceManager()->GetNameSpaceURI(aNameSpaceID, ns);
+ attrNode = GetAttributeNodeNSInternal(ns, nsDependentAtomString(aName));
+ }
+
+ // Clear the attribute out from attribute map.
+ nsDOMSlots* slots = GetExistingDOMSlots();
+ if (slots && slots->mAttributeMap) {
+ slots->mAttributeMap->DropAttribute(aNameSpaceID, aName);
+ }
+
+ // The id-handling code, and in the future possibly other code, need to
+ // react to unexpected attribute changes.
+ nsMutationGuard::DidMutate();
+
+ bool hadValidDir = false;
+ bool hadDirAuto = false;
+
+ if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::dir) {
+ hadValidDir = HasValidDir() || IsHTMLElement(nsGkAtoms::bdi);
+ hadDirAuto = HasDirAuto(); // already takes bdi into account
+ }
+
+ nsAttrValue oldValue;
+ rv = mAttrs.RemoveAttrAt(index, oldValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ PostIdMaybeChange(aNameSpaceID, aName, nullptr);
+
+ const CustomElementData* data = GetCustomElementData();
+ if (data && data->mState == CustomElementData::State::eCustom) {
+ CustomElementDefinition* definition = data->GetCustomElementDefinition();
+ MOZ_ASSERT(definition, "Should have a valid CustomElementDefinition");
+
+ if (definition->IsInObservedAttributeList(aName)) {
+ nsAutoString ns;
+ nsContentUtils::NameSpaceManager()->GetNameSpaceURI(aNameSpaceID, ns);
+
+ RefPtr<nsAtom> oldValueAtom = oldValue.GetAsAtom();
+ LifecycleCallbackArgs args = {
+ nsDependentAtomString(aName), nsDependentAtomString(oldValueAtom),
+ VoidString(), (ns.IsEmpty() ? VoidString() : ns)};
+
+ nsContentUtils::EnqueueLifecycleCallback(
+ ElementCallbackType::eAttributeChanged, this, &args, nullptr,
+ definition);
+ }
+ }
+
+ rv = AfterSetAttr(aNameSpaceID, aName, nullptr, &oldValue, nullptr, aNotify);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ UpdateState(aNotify);
+
+ if (aNotify) {
+ // We can always pass oldValue here since there is no new value which could
+ // have corrupted it.
+ MutationObservers::NotifyAttributeChanged(
+ this, aNameSpaceID, aName, MutationEvent_Binding::REMOVAL, &oldValue);
+ }
+
+ if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::dir) {
+ OnSetDirAttr(this, nullptr, hadValidDir, hadDirAuto, aNotify);
+ }
+
+ if (hasMutationListeners) {
+ InternalMutationEvent mutation(true, eLegacyAttrModified);
+
+ mutation.mRelatedNode = attrNode;
+ mutation.mAttrName = aName;
+
+ nsAutoString value;
+ oldValue.ToString(value);
+ if (!value.IsEmpty()) mutation.mPrevAttrValue = NS_Atomize(value);
+ mutation.mAttrChange = MutationEvent_Binding::REMOVAL;
+
+ mozAutoSubtreeModified subtree(OwnerDoc(), this);
+ (new AsyncEventDispatcher(this, mutation))->RunDOMEventWhenSafe();
+ }
+
+ return NS_OK;
+}
+
+void Element::DescribeAttribute(uint32_t index,
+ nsAString& aOutDescription) const {
+ // name
+ mAttrs.AttrNameAt(index)->GetQualifiedName(aOutDescription);
+
+ // value
+ aOutDescription.AppendLiteral("=\"");
+ nsAutoString value;
+ mAttrs.AttrAt(index)->ToString(value);
+ for (uint32_t i = value.Length(); i > 0; --i) {
+ if (value[i - 1] == char16_t('"')) value.Insert(char16_t('\\'), i - 1);
+ }
+ aOutDescription.Append(value);
+ aOutDescription.Append('"');
+}
+
+#ifdef DEBUG
+void Element::ListAttributes(FILE* out) const {
+ uint32_t index, count = mAttrs.AttrCount();
+ for (index = 0; index < count; index++) {
+ nsAutoString attributeDescription;
+ DescribeAttribute(index, attributeDescription);
+
+ fputs(" ", out);
+ fputs(NS_LossyConvertUTF16toASCII(attributeDescription).get(), out);
+ }
+}
+
+void Element::List(FILE* out, int32_t aIndent, const nsCString& aPrefix) const {
+ int32_t indent;
+ for (indent = aIndent; --indent >= 0;) fputs(" ", out);
+
+ fputs(aPrefix.get(), out);
+
+ fputs(NS_LossyConvertUTF16toASCII(mNodeInfo->QualifiedName()).get(), out);
+
+ fprintf(out, "@%p", (void*)this);
+
+ ListAttributes(out);
+
+ fprintf(out, " state=[%llx]",
+ static_cast<unsigned long long>(State().GetInternalValue()));
+ fprintf(out, " flags=[%08x]", static_cast<unsigned int>(GetFlags()));
+ if (IsClosestCommonInclusiveAncestorForRangeInSelection()) {
+ const LinkedList<nsRange>* ranges =
+ GetExistingClosestCommonInclusiveAncestorRanges();
+ int32_t count = 0;
+ if (ranges) {
+ // Can't use range-based iteration on a const LinkedList, unfortunately.
+ for (const nsRange* r = ranges->getFirst(); r; r = r->getNext()) {
+ ++count;
+ }
+ }
+ fprintf(out, " ranges:%d", count);
+ }
+ fprintf(out, " primaryframe=%p", static_cast<void*>(GetPrimaryFrame()));
+ fprintf(out, " refcount=%" PRIuPTR "<", mRefCnt.get());
+
+ nsIContent* child = GetFirstChild();
+ if (child) {
+ fputs("\n", out);
+
+ for (; child; child = child->GetNextSibling()) {
+ child->List(out, aIndent + 1);
+ }
+
+ for (indent = aIndent; --indent >= 0;) fputs(" ", out);
+ }
+
+ fputs(">\n", out);
+}
+
+void Element::DumpContent(FILE* out, int32_t aIndent, bool aDumpAll) const {
+ int32_t indent;
+ for (indent = aIndent; --indent >= 0;) fputs(" ", out);
+
+ const nsString& buf = mNodeInfo->QualifiedName();
+ fputs("<", out);
+ fputs(NS_LossyConvertUTF16toASCII(buf).get(), out);
+
+ if (aDumpAll) ListAttributes(out);
+
+ fputs(">", out);
+
+ if (aIndent) fputs("\n", out);
+
+ for (nsIContent* child = GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ int32_t indent = aIndent ? aIndent + 1 : 0;
+ child->DumpContent(out, indent, aDumpAll);
+ }
+ for (indent = aIndent; --indent >= 0;) fputs(" ", out);
+ fputs("</", out);
+ fputs(NS_LossyConvertUTF16toASCII(buf).get(), out);
+ fputs(">", out);
+
+ if (aIndent) fputs("\n", out);
+}
+#endif
+
+void Element::Describe(nsAString& aOutDescription) const {
+ aOutDescription.Append(mNodeInfo->QualifiedName());
+ aOutDescription.AppendPrintf("@%p", (void*)this);
+
+ uint32_t index, count = mAttrs.AttrCount();
+ for (index = 0; index < count; index++) {
+ aOutDescription.Append(' ');
+ nsAutoString attributeDescription;
+ DescribeAttribute(index, attributeDescription);
+ aOutDescription.Append(attributeDescription);
+ }
+}
+
+bool Element::CheckHandleEventForLinksPrecondition(EventChainVisitor& aVisitor,
+ nsIURI** aURI) const {
+ if (aVisitor.mEventStatus == nsEventStatus_eConsumeNoDefault ||
+ (!aVisitor.mEvent->IsTrusted() &&
+ (aVisitor.mEvent->mMessage != eMouseClick) &&
+ (aVisitor.mEvent->mMessage != eKeyPress) &&
+ (aVisitor.mEvent->mMessage != eLegacyDOMActivate)) ||
+ aVisitor.mEvent->mFlags.mMultipleActionsPrevented) {
+ return false;
+ }
+
+ // Make sure we actually are a link
+ return IsLink(aURI);
+}
+
+void Element::GetEventTargetParentForLinks(EventChainPreVisitor& aVisitor) {
+ // Optimisation: return early if this event doesn't interest us.
+ // IMPORTANT: this switch and the switch below it must be kept in sync!
+ switch (aVisitor.mEvent->mMessage) {
+ case eMouseOver:
+ case eFocus:
+ case eMouseOut:
+ case eBlur:
+ break;
+ default:
+ return;
+ }
+
+ // Make sure we meet the preconditions before continuing
+ nsCOMPtr<nsIURI> absURI;
+ if (!CheckHandleEventForLinksPrecondition(aVisitor, getter_AddRefs(absURI))) {
+ return;
+ }
+
+ // We do the status bar updates in GetEventTargetParent so that the status bar
+ // gets updated even if the event is consumed before we have a chance to set
+ // it.
+ switch (aVisitor.mEvent->mMessage) {
+ // Set the status bar similarly for mouseover and focus
+ case eMouseOver:
+ aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
+ [[fallthrough]];
+ case eFocus: {
+ InternalFocusEvent* focusEvent = aVisitor.mEvent->AsFocusEvent();
+ if (!focusEvent || !focusEvent->mIsRefocus) {
+ nsAutoString target;
+ GetLinkTarget(target);
+ nsContentUtils::TriggerLink(this, absURI, target,
+ /* click */ false, /* isTrusted */ true);
+ // Make sure any ancestor links don't also TriggerLink
+ aVisitor.mEvent->mFlags.mMultipleActionsPrevented = true;
+ }
+ break;
+ }
+ case eMouseOut:
+ aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
+ [[fallthrough]];
+ case eBlur: {
+ nsresult rv = LeaveLink(aVisitor.mPresContext);
+ if (NS_SUCCEEDED(rv)) {
+ aVisitor.mEvent->mFlags.mMultipleActionsPrevented = true;
+ }
+ break;
+ }
+
+ default:
+ // switch not in sync with the optimization switch earlier in this
+ // function
+ MOZ_ASSERT_UNREACHABLE("switch statements not in sync");
+ }
+}
+
+nsresult Element::PostHandleEventForLinks(EventChainPostVisitor& aVisitor) {
+ // Optimisation: return early if this event doesn't interest us.
+ // IMPORTANT: this switch and the switch below it must be kept in sync!
+ switch (aVisitor.mEvent->mMessage) {
+ case eMouseDown:
+ case eMouseClick:
+ case eLegacyDOMActivate:
+ case eKeyPress:
+ break;
+ default:
+ return NS_OK;
+ }
+
+ // Make sure we meet the preconditions before continuing
+ nsCOMPtr<nsIURI> absURI;
+ if (!CheckHandleEventForLinksPrecondition(aVisitor, getter_AddRefs(absURI))) {
+ return NS_OK;
+ }
+
+ nsresult rv = NS_OK;
+
+ switch (aVisitor.mEvent->mMessage) {
+ case eMouseDown: {
+ if (aVisitor.mEvent->AsMouseEvent()->mButton == MouseButton::ePrimary &&
+ OwnerDoc()->LinkHandlingEnabled()) {
+ aVisitor.mEvent->mFlags.mMultipleActionsPrevented = true;
+
+ if (IsInComposedDoc()) {
+ if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
+ RefPtr<Element> kungFuDeathGrip(this);
+ fm->SetFocus(kungFuDeathGrip, nsIFocusManager::FLAG_BYMOUSE |
+ nsIFocusManager::FLAG_NOSCROLL);
+ }
+ }
+
+ if (aVisitor.mPresContext) {
+ EventStateManager::SetActiveManager(
+ aVisitor.mPresContext->EventStateManager(), this);
+ }
+
+ // OK, we're pretty sure we're going to load, so warm up a speculative
+ // connection to be sure we have one ready when we open the channel.
+ if (nsIDocShell* shell = OwnerDoc()->GetDocShell()) {
+ nsCOMPtr<nsISpeculativeConnect> sc =
+ do_QueryInterface(nsContentUtils::GetIOService());
+ nsCOMPtr<nsIInterfaceRequestor> ir = do_QueryInterface(shell);
+ sc->SpeculativeConnect(absURI, NodePrincipal(), ir);
+ }
+ }
+ } break;
+
+ case eMouseClick: {
+ WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
+ if (mouseEvent->IsLeftClickEvent()) {
+ if (mouseEvent->IsControl() || mouseEvent->IsMeta() ||
+ mouseEvent->IsAlt() || mouseEvent->IsShift()) {
+ break;
+ }
+
+ // The default action is simply to dispatch DOMActivate
+ nsEventStatus status = nsEventStatus_eIgnore;
+ // DOMActive event should be trusted since the activation is actually
+ // occurred even if the cause is an untrusted click event.
+ InternalUIEvent actEvent(true, eLegacyDOMActivate, mouseEvent);
+ actEvent.mDetail = 1;
+
+ rv = EventDispatcher::Dispatch(this, aVisitor.mPresContext, &actEvent,
+ nullptr, &status);
+ if (NS_SUCCEEDED(rv)) {
+ aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
+ }
+ }
+ break;
+ }
+ case eLegacyDOMActivate: {
+ if (aVisitor.mEvent->mOriginalTarget == this) {
+ nsAutoString target;
+ GetLinkTarget(target);
+ const InternalUIEvent* activeEvent = aVisitor.mEvent->AsUIEvent();
+ MOZ_ASSERT(activeEvent);
+ nsContentUtils::TriggerLink(this, absURI, target, /* click */ true,
+ activeEvent->IsTrustable());
+ aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
+ }
+ } break;
+
+ case eKeyPress: {
+ WidgetKeyboardEvent* keyEvent = aVisitor.mEvent->AsKeyboardEvent();
+ if (keyEvent && keyEvent->mKeyCode == NS_VK_RETURN) {
+ nsEventStatus status = nsEventStatus_eIgnore;
+ rv = DispatchClickEvent(MOZ_KnownLive(aVisitor.mPresContext), keyEvent,
+ this, false, nullptr, &status);
+ if (NS_SUCCEEDED(rv)) {
+ aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
+ }
+ }
+ } break;
+
+ default:
+ // switch not in sync with the optimization switch earlier in this
+ // function
+ MOZ_ASSERT_UNREACHABLE("switch statements not in sync");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return rv;
+}
+
+void Element::GetLinkTarget(nsAString& aTarget) { aTarget.Truncate(); }
+
+static nsStaticAtom* const sPropertiesToTraverseAndUnlink[] = {
+ nsGkAtoms::dirAutoSetBy, nullptr};
+
+// static
+nsStaticAtom* const* Element::HTMLSVGPropertiesToTraverseAndUnlink() {
+ return sPropertiesToTraverseAndUnlink;
+}
+
+nsresult Element::CopyInnerTo(Element* aDst, ReparseAttributes aReparse) {
+ nsresult rv = aDst->mAttrs.EnsureCapacityToClone(mAttrs);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ const bool reparse = aReparse == ReparseAttributes::Yes;
+
+ uint32_t count = mAttrs.AttrCount();
+ for (uint32_t i = 0; i < count; ++i) {
+ BorrowedAttrInfo info = mAttrs.AttrInfoAt(i);
+ const nsAttrName* name = info.mName;
+ const nsAttrValue* value = info.mValue;
+ if (value->Type() == nsAttrValue::eCSSDeclaration) {
+ MOZ_ASSERT(name->Equals(nsGkAtoms::style, kNameSpaceID_None));
+ // We still clone CSS attributes, even in the `reparse` (cross-document)
+ // case. https://github.com/w3c/webappsec-csp/issues/212
+ nsAttrValue valueCopy(*value);
+ rv = aDst->SetParsedAttr(name->NamespaceID(), name->LocalName(),
+ name->GetPrefix(), valueCopy, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ value->GetCSSDeclarationValue()->SetImmutable();
+ } else if (reparse) {
+ nsAutoString valStr;
+ value->ToString(valStr);
+ rv = aDst->SetAttr(name->NamespaceID(), name->LocalName(),
+ name->GetPrefix(), valStr, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ nsAttrValue valueCopy(*value);
+ rv = aDst->SetParsedAttr(name->NamespaceID(), name->LocalName(),
+ name->GetPrefix(), valueCopy, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ dom::NodeInfo* dstNodeInfo = aDst->NodeInfo();
+ if (CustomElementData* data = GetCustomElementData()) {
+ // The cloned node may be a custom element that may require
+ // enqueing upgrade reaction.
+ if (nsAtom* typeAtom = data->GetCustomElementType()) {
+ aDst->SetCustomElementData(new CustomElementData(typeAtom));
+ MOZ_ASSERT(dstNodeInfo->NameAtom()->Equals(dstNodeInfo->LocalName()));
+ CustomElementDefinition* definition =
+ nsContentUtils::LookupCustomElementDefinition(
+ dstNodeInfo->GetDocument(), dstNodeInfo->NameAtom(),
+ dstNodeInfo->NamespaceID(), typeAtom);
+ if (definition) {
+ nsContentUtils::EnqueueUpgradeReaction(aDst, definition);
+ }
+ }
+ }
+
+ if (dstNodeInfo->GetDocument()->IsStaticDocument()) {
+ // Propagate :defined state to the static clone.
+ if (State().HasState(NS_EVENT_STATE_DEFINED)) {
+ aDst->SetDefined(true);
+ }
+ }
+
+ return NS_OK;
+}
+
+Element* Element::Closest(const nsACString& aSelector, ErrorResult& aResult) {
+ const RawServoSelectorList* list = ParseSelectorList(aSelector, aResult);
+ if (!list) {
+ return nullptr;
+ }
+
+ return const_cast<Element*>(Servo_SelectorList_Closest(this, list));
+}
+
+bool Element::Matches(const nsACString& aSelector, ErrorResult& aResult) {
+ const RawServoSelectorList* list = ParseSelectorList(aSelector, aResult);
+ if (!list) {
+ return false;
+ }
+
+ return Servo_SelectorList_Matches(this, list);
+}
+
+static const nsAttrValue::EnumTable kCORSAttributeTable[] = {
+ // Order matters here
+ // See ParseCORSValue
+ {"anonymous", CORS_ANONYMOUS},
+ {"use-credentials", CORS_USE_CREDENTIALS},
+ {nullptr, 0}};
+
+/* static */
+void Element::ParseCORSValue(const nsAString& aValue, nsAttrValue& aResult) {
+ DebugOnly<bool> success =
+ aResult.ParseEnumValue(aValue, kCORSAttributeTable, false,
+ // default value is anonymous if aValue is
+ // not a value we understand
+ &kCORSAttributeTable[0]);
+ MOZ_ASSERT(success);
+}
+
+/* static */
+CORSMode Element::StringToCORSMode(const nsAString& aValue) {
+ if (aValue.IsVoid()) {
+ return CORS_NONE;
+ }
+
+ nsAttrValue val;
+ Element::ParseCORSValue(aValue, val);
+ return CORSMode(val.GetEnumValue());
+}
+
+/* static */
+CORSMode Element::AttrValueToCORSMode(const nsAttrValue* aValue) {
+ if (!aValue) {
+ return CORS_NONE;
+ }
+
+ return CORSMode(aValue->GetEnumValue());
+}
+
+/**
+ * Returns nullptr if requests for fullscreen are allowed in the current
+ * context. Requests are only allowed if the user initiated them (like with
+ * a mouse-click or key press), unless this check has been disabled by
+ * setting the pref "full-screen-api.allow-trusted-requests-only" to false
+ * or if the caller is privileged. Feature policy may also deny requests.
+ * If fullscreen is not allowed, a key for the error message is returned.
+ */
+static const char* GetFullscreenError(CallerType aCallerType,
+ Document* aDocument) {
+ MOZ_ASSERT(aDocument);
+
+ // Privileged callers can always request fullscreen
+ if (aCallerType == CallerType::System) {
+ return nullptr;
+ }
+
+ if (const char* error = aDocument->GetFullscreenError(aCallerType)) {
+ return error;
+ }
+
+ // Bypass user interaction checks if preference is set
+ if (!StaticPrefs::full_screen_api_allow_trusted_requests_only()) {
+ return nullptr;
+ }
+
+ if (!aDocument->ConsumeTransientUserGestureActivation()) {
+ return "FullscreenDeniedNotInputDriven";
+ }
+
+ // Entering full-screen on mouse mouse event is only allowed with left mouse
+ // button
+ if (StaticPrefs::full_screen_api_mouse_event_allow_left_button_only() &&
+ (EventStateManager::sCurrentMouseBtn == MouseButton::eMiddle ||
+ EventStateManager::sCurrentMouseBtn == MouseButton::eSecondary)) {
+ return "FullscreenDeniedMouseEventOnlyLeftBtn";
+ }
+
+ return nullptr;
+}
+
+void Element::SetCapture(bool aRetargetToElement) {
+ // If there is already an active capture, ignore this request. This would
+ // occur if a splitter, frame resizer, etc had already captured and we don't
+ // want to override those.
+ if (!PresShell::GetCapturingContent()) {
+ PresShell::SetCapturingContent(
+ this, CaptureFlags::PreventDragStart |
+ (aRetargetToElement ? CaptureFlags::RetargetToElement
+ : CaptureFlags::None));
+ }
+}
+
+void Element::SetCaptureAlways(bool aRetargetToElement) {
+ PresShell::SetCapturingContent(
+ this, CaptureFlags::PreventDragStart | CaptureFlags::IgnoreAllowedState |
+ (aRetargetToElement ? CaptureFlags::RetargetToElement
+ : CaptureFlags::None));
+}
+
+void Element::ReleaseCapture() {
+ if (PresShell::GetCapturingContent() == this) {
+ PresShell::ReleaseCapturingContent();
+ }
+}
+
+already_AddRefed<Promise> Element::RequestFullscreen(CallerType aCallerType,
+ ErrorResult& aRv) {
+ auto request = FullscreenRequest::Create(this, aCallerType, aRv);
+ RefPtr<Promise> promise = request->GetPromise();
+
+ // Only grant fullscreen requests if this is called from inside a trusted
+ // event handler (i.e. inside an event handler for a user initiated event).
+ // This stops the fullscreen from being abused similar to the popups of old,
+ // and it also makes it harder for bad guys' script to go fullscreen and
+ // spoof the browser chrome/window and phish logins etc.
+ // Note that requests for fullscreen inside a web app's origin are exempt
+ // from this restriction.
+ if (const char* error = GetFullscreenError(aCallerType, OwnerDoc())) {
+ request->Reject(error);
+ } else {
+ OwnerDoc()->AsyncRequestFullscreen(std::move(request));
+ }
+ return promise.forget();
+}
+
+void Element::RequestPointerLock(CallerType aCallerType) {
+ OwnerDoc()->RequestPointerLock(this, aCallerType);
+}
+
+already_AddRefed<Flex> Element::GetAsFlexContainer() {
+ // We need the flex frame to compute additional info, and use
+ // that annotated version of the frame.
+ nsFlexContainerFrame* flexFrame =
+ nsFlexContainerFrame::GetFlexFrameWithComputedInfo(
+ GetPrimaryFrame(FlushType::Layout));
+
+ if (flexFrame) {
+ RefPtr<Flex> flex = new Flex(this, flexFrame);
+ return flex.forget();
+ }
+ return nullptr;
+}
+
+void Element::GetGridFragments(nsTArray<RefPtr<Grid>>& aResult) {
+ nsGridContainerFrame* frame =
+ nsGridContainerFrame::GetGridFrameWithComputedInfo(
+ GetPrimaryFrame(FlushType::Layout));
+
+ // If we get a nsGridContainerFrame from the prior call,
+ // all the next-in-flow frames will also be nsGridContainerFrames.
+ while (frame) {
+ aResult.AppendElement(new Grid(this, frame));
+ frame = static_cast<nsGridContainerFrame*>(frame->GetNextInFlow());
+ }
+}
+
+bool Element::HasGridFragments() {
+ return !!nsGridContainerFrame::GetGridFrameWithComputedInfo(
+ GetPrimaryFrame(FlushType::Layout));
+}
+
+already_AddRefed<DOMMatrixReadOnly> Element::GetTransformToAncestor(
+ Element& aAncestor) {
+ nsIFrame* primaryFrame = GetPrimaryFrame();
+ nsIFrame* ancestorFrame = aAncestor.GetPrimaryFrame();
+
+ Matrix4x4 transform;
+ if (primaryFrame) {
+ // If aAncestor is not actually an ancestor of this (including nullptr),
+ // then the call to GetTransformToAncestor will return the transform
+ // all the way up through the parent chain.
+ transform = nsLayoutUtils::GetTransformToAncestor(RelativeTo{primaryFrame},
+ RelativeTo{ancestorFrame},
+ nsIFrame::IN_CSS_UNITS)
+ .GetMatrix();
+ }
+
+ DOMMatrixReadOnly* matrix = new DOMMatrix(this, transform);
+ RefPtr<DOMMatrixReadOnly> result(matrix);
+ return result.forget();
+}
+
+already_AddRefed<DOMMatrixReadOnly> Element::GetTransformToParent() {
+ nsIFrame* primaryFrame = GetPrimaryFrame();
+
+ Matrix4x4 transform;
+ if (primaryFrame) {
+ nsIFrame* parentFrame = primaryFrame->GetParent();
+ transform = nsLayoutUtils::GetTransformToAncestor(RelativeTo{primaryFrame},
+ RelativeTo{parentFrame},
+ nsIFrame::IN_CSS_UNITS)
+ .GetMatrix();
+ }
+
+ DOMMatrixReadOnly* matrix = new DOMMatrix(this, transform);
+ RefPtr<DOMMatrixReadOnly> result(matrix);
+ return result.forget();
+}
+
+already_AddRefed<DOMMatrixReadOnly> Element::GetTransformToViewport() {
+ nsIFrame* primaryFrame = GetPrimaryFrame();
+ Matrix4x4 transform;
+ if (primaryFrame) {
+ transform =
+ nsLayoutUtils::GetTransformToAncestor(
+ RelativeTo{primaryFrame},
+ RelativeTo{nsLayoutUtils::GetDisplayRootFrame(primaryFrame)},
+ nsIFrame::IN_CSS_UNITS)
+ .GetMatrix();
+ }
+
+ DOMMatrixReadOnly* matrix = new DOMMatrix(this, transform);
+ RefPtr<DOMMatrixReadOnly> result(matrix);
+ return result.forget();
+}
+
+already_AddRefed<Animation> Element::Animate(
+ JSContext* aContext, JS::Handle<JSObject*> aKeyframes,
+ const UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions,
+ ErrorResult& aError) {
+ nsCOMPtr<nsIGlobalObject> ownerGlobal = GetOwnerGlobal();
+ if (!ownerGlobal) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+ GlobalObject global(aContext, ownerGlobal->GetGlobalJSObject());
+ MOZ_ASSERT(!global.Failed());
+
+ // KeyframeEffect constructor doesn't follow the standard Xray calling
+ // convention and needs to be called in caller's compartment.
+ // This should match to RunConstructorInCallerCompartment attribute in
+ // KeyframeEffect.webidl.
+ RefPtr<KeyframeEffect> effect =
+ KeyframeEffect::Constructor(global, this, aKeyframes, aOptions, aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ // Animation constructor follows the standard Xray calling convention and
+ // needs to be called in the target element's realm.
+ JSAutoRealm ar(aContext, global.Get());
+
+ AnimationTimeline* timeline = OwnerDoc()->Timeline();
+ RefPtr<Animation> animation = Animation::Constructor(
+ global, effect, Optional<AnimationTimeline*>(timeline), aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ if (aOptions.IsKeyframeAnimationOptions()) {
+ animation->SetId(aOptions.GetAsKeyframeAnimationOptions().mId);
+ }
+
+ animation->Play(aError, Animation::LimitBehavior::AutoRewind);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ return animation.forget();
+}
+
+void Element::GetAnimations(const GetAnimationsOptions& aOptions,
+ nsTArray<RefPtr<Animation>>& aAnimations) {
+ if (Document* doc = GetComposedDoc()) {
+ // We don't need to explicitly flush throttled animations here, since
+ // updating the animation style of elements will never affect the set of
+ // running animations and it's only the set of running animations that is
+ // important here.
+ //
+ // NOTE: Any changes to the flags passed to the following call should
+ // be reflected in the flags passed in DocumentOrShadowRoot::GetAnimations
+ // too.
+ doc->FlushPendingNotifications(
+ ChangesToFlush(FlushType::Style, false /* flush animations */));
+ }
+
+ GetAnimationsWithoutFlush(aOptions, aAnimations);
+}
+
+void Element::GetAnimationsWithoutFlush(
+ const GetAnimationsOptions& aOptions,
+ nsTArray<RefPtr<Animation>>& aAnimations) {
+ Element* elem = this;
+ PseudoStyleType pseudoType = PseudoStyleType::NotPseudo;
+ // For animations on generated-content elements, the animations are stored
+ // on the parent element.
+ if (IsGeneratedContentContainerForBefore()) {
+ elem = GetParentElement();
+ pseudoType = PseudoStyleType::before;
+ } else if (IsGeneratedContentContainerForAfter()) {
+ elem = GetParentElement();
+ pseudoType = PseudoStyleType::after;
+ } else if (IsGeneratedContentContainerForMarker()) {
+ elem = GetParentElement();
+ pseudoType = PseudoStyleType::marker;
+ }
+
+ if (!elem) {
+ return;
+ }
+
+ if (!aOptions.mSubtree || pseudoType == PseudoStyleType::before ||
+ pseudoType == PseudoStyleType::after ||
+ pseudoType == PseudoStyleType::marker) {
+ GetAnimationsUnsorted(elem, pseudoType, aAnimations);
+ } else {
+ for (nsIContent* node = this; node; node = node->GetNextNode(this)) {
+ if (!node->IsElement()) {
+ continue;
+ }
+ Element* element = node->AsElement();
+ Element::GetAnimationsUnsorted(element, PseudoStyleType::NotPseudo,
+ aAnimations);
+ Element::GetAnimationsUnsorted(element, PseudoStyleType::before,
+ aAnimations);
+ Element::GetAnimationsUnsorted(element, PseudoStyleType::after,
+ aAnimations);
+ Element::GetAnimationsUnsorted(element, PseudoStyleType::marker,
+ aAnimations);
+ }
+ }
+ aAnimations.Sort(AnimationPtrComparator<RefPtr<Animation>>());
+}
+
+/* static */
+void Element::GetAnimationsUnsorted(Element* aElement,
+ PseudoStyleType aPseudoType,
+ nsTArray<RefPtr<Animation>>& aAnimations) {
+ MOZ_ASSERT(aPseudoType == PseudoStyleType::NotPseudo ||
+ aPseudoType == PseudoStyleType::after ||
+ aPseudoType == PseudoStyleType::before ||
+ aPseudoType == PseudoStyleType::marker,
+ "Unsupported pseudo type");
+ MOZ_ASSERT(aElement, "Null element");
+
+ EffectSet* effects = EffectSet::GetEffectSet(aElement, aPseudoType);
+ if (!effects) {
+ return;
+ }
+
+ for (KeyframeEffect* effect : *effects) {
+ MOZ_ASSERT(effect && effect->GetAnimation(),
+ "Only effects associated with an animation should be "
+ "added to an element's effect set");
+ Animation* animation = effect->GetAnimation();
+
+ MOZ_ASSERT(animation->IsRelevant(),
+ "Only relevant animations should be added to an element's "
+ "effect set");
+ aAnimations.AppendElement(animation);
+ }
+}
+
+void Element::CloneAnimationsFrom(const Element& aOther) {
+ AnimationTimeline* const timeline = OwnerDoc()->Timeline();
+ MOZ_ASSERT(timeline, "Timeline has not been set on the document yet");
+ // Iterate through all pseudo types and copy the effects from each of the
+ // other element's effect sets into this element's effect set.
+ for (PseudoStyleType pseudoType :
+ {PseudoStyleType::NotPseudo, PseudoStyleType::before,
+ PseudoStyleType::after, PseudoStyleType::marker}) {
+ // If the element has an effect set for this pseudo type (or not pseudo)
+ // then copy the effects and animation properties.
+ if (EffectSet* const effects =
+ EffectSet::GetEffectSet(&aOther, pseudoType)) {
+ EffectSet* const clonedEffects =
+ EffectSet::GetOrCreateEffectSet(this, pseudoType);
+ for (KeyframeEffect* const effect : *effects) {
+ // Clone the effect.
+ RefPtr<KeyframeEffect> clonedEffect = new KeyframeEffect(
+ OwnerDoc(), OwningAnimationTarget{this, pseudoType}, *effect);
+
+ // Clone the animation
+ RefPtr<Animation> clonedAnimation = Animation::ClonePausedAnimation(
+ OwnerDoc()->GetParentObject(), *effect->GetAnimation(),
+ *clonedEffect, *timeline);
+ clonedEffects->AddEffect(*clonedEffect);
+ }
+ }
+ }
+}
+
+void Element::GetInnerHTML(nsAString& aInnerHTML, OOMReporter& aError) {
+ GetMarkup(false, aInnerHTML);
+}
+
+void Element::SetInnerHTML(const nsAString& aInnerHTML,
+ nsIPrincipal* aSubjectPrincipal,
+ ErrorResult& aError) {
+ SetInnerHTMLInternal(aInnerHTML, aError);
+}
+
+void Element::GetOuterHTML(nsAString& aOuterHTML) {
+ GetMarkup(true, aOuterHTML);
+}
+
+void Element::SetOuterHTML(const nsAString& aOuterHTML, ErrorResult& aError) {
+ nsCOMPtr<nsINode> parent = GetParentNode();
+ if (!parent) {
+ return;
+ }
+
+ if (parent->NodeType() == DOCUMENT_NODE) {
+ aError.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
+ return;
+ }
+
+ if (OwnerDoc()->IsHTMLDocument()) {
+ nsAtom* localName;
+ int32_t namespaceID;
+ if (parent->IsElement()) {
+ localName = parent->NodeInfo()->NameAtom();
+ namespaceID = parent->NodeInfo()->NamespaceID();
+ } else {
+ NS_ASSERTION(
+ parent->NodeType() == DOCUMENT_FRAGMENT_NODE,
+ "How come the parent isn't a document, a fragment or an element?");
+ localName = nsGkAtoms::body;
+ namespaceID = kNameSpaceID_XHTML;
+ }
+ RefPtr<DocumentFragment> fragment = new (OwnerDoc()->NodeInfoManager())
+ DocumentFragment(OwnerDoc()->NodeInfoManager());
+ nsContentUtils::ParseFragmentHTML(
+ aOuterHTML, fragment, localName, namespaceID,
+ OwnerDoc()->GetCompatibilityMode() == eCompatibility_NavQuirks, true);
+ parent->ReplaceChild(*fragment, *this, aError);
+ return;
+ }
+
+ nsCOMPtr<nsINode> context;
+ if (parent->IsElement()) {
+ context = parent;
+ } else {
+ NS_ASSERTION(
+ parent->NodeType() == DOCUMENT_FRAGMENT_NODE,
+ "How come the parent isn't a document, a fragment or an element?");
+ RefPtr<mozilla::dom::NodeInfo> info =
+ OwnerDoc()->NodeInfoManager()->GetNodeInfo(
+ nsGkAtoms::body, nullptr, kNameSpaceID_XHTML, ELEMENT_NODE);
+ context = NS_NewHTMLBodyElement(info.forget(), FROM_PARSER_FRAGMENT);
+ }
+
+ RefPtr<DocumentFragment> fragment = nsContentUtils::CreateContextualFragment(
+ context, aOuterHTML, true, aError);
+ if (aError.Failed()) {
+ return;
+ }
+ parent->ReplaceChild(*fragment, *this, aError);
+}
+
+enum nsAdjacentPosition { eBeforeBegin, eAfterBegin, eBeforeEnd, eAfterEnd };
+
+void Element::InsertAdjacentHTML(const nsAString& aPosition,
+ const nsAString& aText, ErrorResult& aError) {
+ nsAdjacentPosition position;
+ if (aPosition.LowerCaseEqualsLiteral("beforebegin")) {
+ position = eBeforeBegin;
+ } else if (aPosition.LowerCaseEqualsLiteral("afterbegin")) {
+ position = eAfterBegin;
+ } else if (aPosition.LowerCaseEqualsLiteral("beforeend")) {
+ position = eBeforeEnd;
+ } else if (aPosition.LowerCaseEqualsLiteral("afterend")) {
+ position = eAfterEnd;
+ } else {
+ aError.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+ return;
+ }
+
+ nsCOMPtr<nsIContent> destination;
+ if (position == eBeforeBegin || position == eAfterEnd) {
+ destination = GetParent();
+ if (!destination) {
+ aError.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
+ return;
+ }
+ } else {
+ destination = this;
+ }
+
+ Document* doc = OwnerDoc();
+
+ // Needed when insertAdjacentHTML is used in combination with contenteditable
+ mozAutoDocUpdate updateBatch(doc, true);
+ nsAutoScriptLoaderDisabler sld(doc);
+
+ // Batch possible DOMSubtreeModified events.
+ mozAutoSubtreeModified subtree(doc, nullptr);
+
+ // Parse directly into destination if possible
+ if (doc->IsHTMLDocument() && !OwnerDoc()->MayHaveDOMMutationObservers() &&
+ (position == eBeforeEnd || (position == eAfterEnd && !GetNextSibling()) ||
+ (position == eAfterBegin && !GetFirstChild()))) {
+ int32_t oldChildCount = destination->GetChildCount();
+ int32_t contextNs = destination->GetNameSpaceID();
+ nsAtom* contextLocal = destination->NodeInfo()->NameAtom();
+ if (contextLocal == nsGkAtoms::html && contextNs == kNameSpaceID_XHTML) {
+ // For compat with IE6 through IE9. Willful violation of HTML5 as of
+ // 2011-04-06. CreateContextualFragment does the same already.
+ // Spec bug: http://www.w3.org/Bugs/Public/show_bug.cgi?id=12434
+ contextLocal = nsGkAtoms::body;
+ }
+ aError = nsContentUtils::ParseFragmentHTML(
+ aText, destination, contextLocal, contextNs,
+ doc->GetCompatibilityMode() == eCompatibility_NavQuirks, true);
+ // HTML5 parser has notified, but not fired mutation events.
+ nsContentUtils::FireMutationEventsForDirectParsing(doc, destination,
+ oldChildCount);
+ return;
+ }
+
+ // couldn't parse directly
+ RefPtr<DocumentFragment> fragment = nsContentUtils::CreateContextualFragment(
+ destination, aText, true, aError);
+ if (aError.Failed()) {
+ return;
+ }
+
+ // Suppress assertion about node removal mutation events that can't have
+ // listeners anyway, because no one has had the chance to register mutation
+ // listeners on the fragment that comes from the parser.
+ nsAutoScriptBlockerSuppressNodeRemoved scriptBlocker;
+
+ nsAutoMutationBatch mb(destination, true, false);
+ switch (position) {
+ case eBeforeBegin:
+ destination->InsertBefore(*fragment, this, aError);
+ break;
+ case eAfterBegin:
+ static_cast<nsINode*>(this)->InsertBefore(*fragment, GetFirstChild(),
+ aError);
+ break;
+ case eBeforeEnd:
+ static_cast<nsINode*>(this)->AppendChild(*fragment, aError);
+ break;
+ case eAfterEnd:
+ destination->InsertBefore(*fragment, GetNextSibling(), aError);
+ break;
+ }
+}
+
+nsINode* Element::InsertAdjacent(const nsAString& aWhere, nsINode* aNode,
+ ErrorResult& aError) {
+ if (aWhere.LowerCaseEqualsLiteral("beforebegin")) {
+ nsCOMPtr<nsINode> parent = GetParentNode();
+ if (!parent) {
+ return nullptr;
+ }
+ parent->InsertBefore(*aNode, this, aError);
+ } else if (aWhere.LowerCaseEqualsLiteral("afterbegin")) {
+ nsCOMPtr<nsINode> refNode = GetFirstChild();
+ static_cast<nsINode*>(this)->InsertBefore(*aNode, refNode, aError);
+ } else if (aWhere.LowerCaseEqualsLiteral("beforeend")) {
+ static_cast<nsINode*>(this)->AppendChild(*aNode, aError);
+ } else if (aWhere.LowerCaseEqualsLiteral("afterend")) {
+ nsCOMPtr<nsINode> parent = GetParentNode();
+ if (!parent) {
+ return nullptr;
+ }
+ nsCOMPtr<nsINode> refNode = GetNextSibling();
+ parent->InsertBefore(*aNode, refNode, aError);
+ } else {
+ aError.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+ return nullptr;
+ }
+
+ return aError.Failed() ? nullptr : aNode;
+}
+
+Element* Element::InsertAdjacentElement(const nsAString& aWhere,
+ Element& aElement,
+ ErrorResult& aError) {
+ nsINode* newNode = InsertAdjacent(aWhere, &aElement, aError);
+ MOZ_ASSERT(!newNode || newNode->IsElement());
+
+ return newNode ? newNode->AsElement() : nullptr;
+}
+
+void Element::InsertAdjacentText(const nsAString& aWhere,
+ const nsAString& aData, ErrorResult& aError) {
+ RefPtr<nsTextNode> textNode = OwnerDoc()->CreateTextNode(aData);
+ InsertAdjacent(aWhere, textNode, aError);
+}
+
+TextEditor* Element::GetTextEditorInternal() {
+ TextControlElement* textControlElement = TextControlElement::FromNode(this);
+ return textControlElement ? MOZ_KnownLive(textControlElement)->GetTextEditor()
+ : nullptr;
+}
+
+nsresult Element::SetBoolAttr(nsAtom* aAttr, bool aValue) {
+ if (aValue) {
+ return SetAttr(kNameSpaceID_None, aAttr, u""_ns, true);
+ }
+
+ return UnsetAttr(kNameSpaceID_None, aAttr, true);
+}
+
+void Element::GetEnumAttr(nsAtom* aAttr, const char* aDefault,
+ nsAString& aResult) const {
+ GetEnumAttr(aAttr, aDefault, aDefault, aResult);
+}
+
+void Element::GetEnumAttr(nsAtom* aAttr, const char* aDefaultMissing,
+ const char* aDefaultInvalid,
+ nsAString& aResult) const {
+ const nsAttrValue* attrVal = mAttrs.GetAttr(aAttr);
+
+ aResult.Truncate();
+
+ if (!attrVal) {
+ if (aDefaultMissing) {
+ AppendASCIItoUTF16(nsDependentCString(aDefaultMissing), aResult);
+ } else {
+ SetDOMStringToNull(aResult);
+ }
+ } else {
+ if (attrVal->Type() == nsAttrValue::eEnum) {
+ attrVal->GetEnumString(aResult, true);
+ } else if (aDefaultInvalid) {
+ AppendASCIItoUTF16(nsDependentCString(aDefaultInvalid), aResult);
+ }
+ }
+}
+
+void Element::SetOrRemoveNullableStringAttr(nsAtom* aName,
+ const nsAString& aValue,
+ ErrorResult& aError) {
+ if (DOMStringIsNull(aValue)) {
+ UnsetAttr(aName, aError);
+ } else {
+ SetAttr(aName, aValue, aError);
+ }
+}
+
+Directionality Element::GetComputedDirectionality() const {
+ if (nsIFrame* frame = GetPrimaryFrame()) {
+ return frame->StyleVisibility()->mDirection == StyleDirection::Ltr
+ ? eDir_LTR
+ : eDir_RTL;
+ }
+
+ return GetDirectionality();
+}
+
+float Element::FontSizeInflation() {
+ nsIFrame* frame = GetPrimaryFrame();
+ if (!frame) {
+ return -1.0;
+ }
+
+ if (nsLayoutUtils::FontSizeInflationEnabled(frame->PresContext())) {
+ return nsLayoutUtils::FontSizeInflationFor(frame);
+ }
+
+ return 1.0;
+}
+
+void Element::GetImplementedPseudoElement(nsAString& aPseudo) const {
+ PseudoStyleType pseudoType = GetPseudoElementType();
+ if (pseudoType == PseudoStyleType::NotPseudo) {
+ return SetDOMStringToNull(aPseudo);
+ }
+ nsDependentAtomString pseudo(nsCSSPseudoElements::GetPseudoAtom(pseudoType));
+
+ // We want to use the modern syntax (::placeholder, etc), but the atoms only
+ // contain one semi-colon.
+ MOZ_ASSERT(pseudo.Length() > 2 && pseudo[0] == ':' && pseudo[1] != ':');
+
+ aPseudo.Truncate();
+ aPseudo.SetCapacity(pseudo.Length() + 1);
+ aPseudo.Append(':');
+ aPseudo.Append(pseudo);
+}
+
+ReferrerPolicy Element::GetReferrerPolicyAsEnum() const {
+ if (IsHTMLElement()) {
+ return ReferrerPolicyFromAttr(GetParsedAttr(nsGkAtoms::referrerpolicy));
+ }
+ return ReferrerPolicy::_empty;
+}
+
+ReferrerPolicy Element::ReferrerPolicyFromAttr(
+ const nsAttrValue* aValue) const {
+ if (aValue && aValue->Type() == nsAttrValue::eEnum) {
+ return ReferrerPolicy(aValue->GetEnumValue());
+ }
+ return ReferrerPolicy::_empty;
+}
+
+already_AddRefed<nsDOMStringMap> Element::Dataset() {
+ nsDOMSlots* slots = DOMSlots();
+
+ if (!slots->mDataset) {
+ // mDataset is a weak reference so assignment will not AddRef.
+ // AddRef is called before returning the pointer.
+ slots->mDataset = new nsDOMStringMap(this);
+ }
+
+ RefPtr<nsDOMStringMap> ret = slots->mDataset;
+ return ret.forget();
+}
+
+void Element::ClearDataset() {
+ nsDOMSlots* slots = GetExistingDOMSlots();
+
+ MOZ_ASSERT(slots && slots->mDataset,
+ "Slots should exist and dataset should not be null.");
+ slots->mDataset = nullptr;
+}
+
+enum nsPreviousIntersectionThreshold {
+ eUninitialized = -2,
+ eNonIntersecting = -1
+};
+
+static void IntersectionObserverPropertyDtor(void* aObject,
+ nsAtom* aPropertyName,
+ void* aPropertyValue,
+ void* aData) {
+ auto* element = static_cast<Element*>(aObject);
+ auto* observers = static_cast<IntersectionObserverList*>(aPropertyValue);
+ for (auto iter = observers->Iter(); !iter.Done(); iter.Next()) {
+ DOMIntersectionObserver* observer = iter.Key();
+ observer->UnlinkTarget(*element);
+ }
+ delete observers;
+}
+
+void Element::RegisterIntersectionObserver(DOMIntersectionObserver* aObserver) {
+ IntersectionObserverList* observers = static_cast<IntersectionObserverList*>(
+ GetProperty(nsGkAtoms::intersectionobserverlist));
+
+ if (!observers) {
+ observers = new IntersectionObserverList();
+ observers->Put(aObserver, eUninitialized);
+ SetProperty(nsGkAtoms::intersectionobserverlist, observers,
+ IntersectionObserverPropertyDtor, /* aTransfer = */ true);
+ return;
+ }
+
+ observers->LookupForAdd(aObserver).OrInsert([]() {
+ // Value can be:
+ // -2: Makes sure next calculated threshold always differs, leading to a
+ // notification task being scheduled.
+ // -1: Non-intersecting.
+ // >= 0: Intersecting, valid index of aObserver->mThresholds.
+ return eUninitialized;
+ });
+}
+
+void Element::UnregisterIntersectionObserver(
+ DOMIntersectionObserver* aObserver) {
+ auto* observers = static_cast<IntersectionObserverList*>(
+ GetProperty(nsGkAtoms::intersectionobserverlist));
+ if (observers) {
+ observers->Remove(aObserver);
+ if (observers->IsEmpty()) {
+ RemoveProperty(nsGkAtoms::intersectionobserverlist);
+ }
+ }
+}
+
+void Element::UnlinkIntersectionObservers() {
+ // IntersectionObserverPropertyDtor takes care of the hard work.
+ RemoveProperty(nsGkAtoms::intersectionobserverlist);
+}
+
+bool Element::UpdateIntersectionObservation(DOMIntersectionObserver* aObserver,
+ int32_t aThreshold) {
+ auto* observers = static_cast<IntersectionObserverList*>(
+ GetProperty(nsGkAtoms::intersectionobserverlist));
+ if (!observers) {
+ return false;
+ }
+ bool updated = false;
+ if (auto entry = observers->Lookup(aObserver)) {
+ updated = entry.Data() != aThreshold;
+ entry.Data() = aThreshold;
+ }
+ return updated;
+}
+
+template <class T>
+void Element::GetCustomInterface(nsGetterAddRefs<T> aResult) {
+ nsCOMPtr<nsISupports> iface = CustomElementRegistry::CallGetCustomInterface(
+ this, NS_GET_TEMPLATE_IID(T));
+ if (iface) {
+ if (NS_SUCCEEDED(CallQueryInterface(iface, static_cast<T**>(aResult)))) {
+ return;
+ }
+ }
+}
+
+void Element::ClearServoData(Document* aDoc) {
+ MOZ_ASSERT(aDoc);
+ if (HasServoData()) {
+ Servo_Element_ClearData(this);
+ } else {
+ UnsetFlags(kAllServoDescendantBits | NODE_NEEDS_FRAME);
+ }
+ // Since this element is losing its servo data, nothing under it may have
+ // servo data either, so we can forget restyles rooted at this element. This
+ // is necessary for correctness, since we invoke ClearServoData in various
+ // places where an element's flattened tree parent changes, and such a change
+ // may also make an element invalid to be used as a restyle root.
+ if (aDoc->GetServoRestyleRoot() == this) {
+ aDoc->ClearServoRestyleRoot();
+ }
+}
+
+void Element::SetCustomElementData(CustomElementData* aData) {
+ SetHasCustomElementData();
+
+ if (aData->mState != CustomElementData::State::eCustom) {
+ SetDefined(false);
+ }
+
+ nsExtendedDOMSlots* slots = ExtendedDOMSlots();
+ MOZ_ASSERT(!slots->mCustomElementData,
+ "Custom element data may not be changed once set.");
+#if DEBUG
+ // We assert only XUL usage, since web may pass whatever as 'is' value
+ if (NodeInfo()->NamespaceID() == kNameSpaceID_XUL) {
+ nsAtom* name = NodeInfo()->NameAtom();
+ nsAtom* type = aData->GetCustomElementType();
+ // Check to see if the tag name is a dashed name.
+ if (nsContentUtils::IsNameWithDash(name)) {
+ // Assert that a tag name with dashes is always an autonomous custom
+ // element.
+ MOZ_ASSERT(type == name);
+ } else {
+ // Could still be an autonomous custom element with a non-dashed tag name.
+ // Need the check below for sure.
+ if (type != name) {
+ // Assert that the name of the built-in custom element type is always
+ // a dashed name.
+ MOZ_ASSERT(nsContentUtils::IsNameWithDash(type));
+ }
+ }
+ }
+#endif
+ slots->mCustomElementData = aData;
+}
+
+CustomElementDefinition* Element::GetCustomElementDefinition() const {
+ CustomElementData* data = GetCustomElementData();
+ if (!data) {
+ return nullptr;
+ }
+
+ return data->GetCustomElementDefinition();
+}
+
+void Element::SetCustomElementDefinition(CustomElementDefinition* aDefinition) {
+ CustomElementData* data = GetCustomElementData();
+ MOZ_ASSERT(data);
+
+ data->SetCustomElementDefinition(aDefinition);
+}
+
+already_AddRefed<nsIDOMXULButtonElement> Element::AsXULButton() {
+ nsCOMPtr<nsIDOMXULButtonElement> value;
+ GetCustomInterface(getter_AddRefs(value));
+ return value.forget();
+}
+
+already_AddRefed<nsIDOMXULContainerElement> Element::AsXULContainer() {
+ nsCOMPtr<nsIDOMXULContainerElement> value;
+ GetCustomInterface(getter_AddRefs(value));
+ return value.forget();
+}
+
+already_AddRefed<nsIDOMXULContainerItemElement> Element::AsXULContainerItem() {
+ nsCOMPtr<nsIDOMXULContainerItemElement> value;
+ GetCustomInterface(getter_AddRefs(value));
+ return value.forget();
+}
+
+already_AddRefed<nsIDOMXULControlElement> Element::AsXULControl() {
+ nsCOMPtr<nsIDOMXULControlElement> value;
+ GetCustomInterface(getter_AddRefs(value));
+ return value.forget();
+}
+
+already_AddRefed<nsIDOMXULMenuListElement> Element::AsXULMenuList() {
+ nsCOMPtr<nsIDOMXULMenuListElement> value;
+ GetCustomInterface(getter_AddRefs(value));
+ return value.forget();
+}
+
+already_AddRefed<nsIDOMXULMultiSelectControlElement>
+Element::AsXULMultiSelectControl() {
+ nsCOMPtr<nsIDOMXULMultiSelectControlElement> value;
+ GetCustomInterface(getter_AddRefs(value));
+ return value.forget();
+}
+
+already_AddRefed<nsIDOMXULRadioGroupElement> Element::AsXULRadioGroup() {
+ nsCOMPtr<nsIDOMXULRadioGroupElement> value;
+ GetCustomInterface(getter_AddRefs(value));
+ return value.forget();
+}
+
+already_AddRefed<nsIDOMXULRelatedElement> Element::AsXULRelated() {
+ nsCOMPtr<nsIDOMXULRelatedElement> value;
+ GetCustomInterface(getter_AddRefs(value));
+ return value.forget();
+}
+
+already_AddRefed<nsIDOMXULSelectControlElement> Element::AsXULSelectControl() {
+ nsCOMPtr<nsIDOMXULSelectControlElement> value;
+ GetCustomInterface(getter_AddRefs(value));
+ return value.forget();
+}
+
+already_AddRefed<nsIDOMXULSelectControlItemElement>
+Element::AsXULSelectControlItem() {
+ nsCOMPtr<nsIDOMXULSelectControlItemElement> value;
+ GetCustomInterface(getter_AddRefs(value));
+ return value.forget();
+}
+
+already_AddRefed<nsIBrowser> Element::AsBrowser() {
+ nsCOMPtr<nsIBrowser> value;
+ GetCustomInterface(getter_AddRefs(value));
+ return value.forget();
+}
+
+already_AddRefed<nsIAutoCompletePopup> Element::AsAutoCompletePopup() {
+ nsCOMPtr<nsIAutoCompletePopup> value;
+ GetCustomInterface(getter_AddRefs(value));
+ return value.forget();
+}
+
+nsPresContext* Element::GetPresContext(PresContextFor aFor) {
+ // Get the document
+ Document* doc =
+ (aFor == eForComposedDoc) ? GetComposedDoc() : GetUncomposedDoc();
+ if (doc) {
+ return doc->GetPresContext();
+ }
+
+ return nullptr;
+}
+
+MOZ_DEFINE_MALLOC_SIZE_OF(ServoElementMallocSizeOf)
+MOZ_DEFINE_MALLOC_ENCLOSING_SIZE_OF(ServoElementMallocEnclosingSizeOf)
+
+void Element::AddSizeOfExcludingThis(nsWindowSizes& aSizes,
+ size_t* aNodeSize) const {
+ FragmentOrElement::AddSizeOfExcludingThis(aSizes, aNodeSize);
+ *aNodeSize += mAttrs.SizeOfExcludingThis(aSizes.mState.mMallocSizeOf);
+
+ if (HasServoData()) {
+ // Measure the ElementData object itself.
+ aSizes.mLayoutElementDataObjects +=
+ aSizes.mState.mMallocSizeOf(mServoData.Get());
+
+ // Measure mServoData, excluding the ComputedValues. This measurement
+ // counts towards the element's size. We use ServoElementMallocSizeOf and
+ // ServoElementMallocEnclosingSizeOf rather than |aState.mMallocSizeOf| to
+ // better distinguish in DMD's output the memory measured within Servo
+ // code.
+ *aNodeSize += Servo_Element_SizeOfExcludingThisAndCVs(
+ ServoElementMallocSizeOf, ServoElementMallocEnclosingSizeOf,
+ &aSizes.mState.mSeenPtrs, this);
+
+ // Now measure just the ComputedValues (and style structs) under
+ // mServoData. This counts towards the relevant fields in |aSizes|.
+ if (auto* style = Servo_Element_GetMaybeOutOfDateStyle(this)) {
+ if (!aSizes.mState.HaveSeenPtr(style)) {
+ style->AddSizeOfIncludingThis(aSizes, &aSizes.mLayoutComputedValuesDom);
+ }
+
+ for (size_t i = 0; i < PseudoStyle::kEagerPseudoCount; i++) {
+ if (auto* style = Servo_Element_GetMaybeOutOfDatePseudoStyle(this, i)) {
+ if (!aSizes.mState.HaveSeenPtr(style)) {
+ style->AddSizeOfIncludingThis(aSizes,
+ &aSizes.mLayoutComputedValuesDom);
+ }
+ }
+ }
+ }
+ }
+}
+
+#ifdef DEBUG
+static bool BitsArePropagated(const Element* aElement, uint32_t aBits,
+ nsINode* aRestyleRoot) {
+ const Element* curr = aElement;
+ while (curr) {
+ if (curr == aRestyleRoot) {
+ return true;
+ }
+ if (!curr->HasAllFlags(aBits)) {
+ return false;
+ }
+ nsINode* parentNode = curr->GetParentNode();
+ curr = curr->GetFlattenedTreeParentElementForStyle();
+ MOZ_ASSERT_IF(!curr,
+ parentNode == aElement->OwnerDoc() ||
+ parentNode == parentNode->OwnerDoc()->GetRootElement());
+ }
+ return true;
+}
+#endif
+
+static inline void AssertNoBitsPropagatedFrom(nsINode* aRoot) {
+#ifdef DEBUG
+ if (!aRoot || !aRoot->IsElement()) {
+ return;
+ }
+
+ auto* element = aRoot->GetFlattenedTreeParentElementForStyle();
+ while (element) {
+ MOZ_ASSERT(!element->HasAnyOfFlags(Element::kAllServoDescendantBits));
+ element = element->GetFlattenedTreeParentElementForStyle();
+ }
+#endif
+}
+
+// Sets `aBits` on `aElement` and all of its flattened-tree ancestors up to and
+// including aStopAt or the root element (whichever is encountered first), and
+// as long as `aBitsToStopAt` isn't found anywhere in the chain.
+static inline Element* PropagateBits(Element* aElement, uint32_t aBits,
+ nsINode* aStopAt, uint32_t aBitsToStopAt) {
+ Element* curr = aElement;
+ while (curr && !curr->HasAllFlags(aBitsToStopAt)) {
+ curr->SetFlags(aBits);
+ if (curr == aStopAt) {
+ break;
+ }
+ curr = curr->GetFlattenedTreeParentElementForStyle();
+ }
+
+ if (aBitsToStopAt != aBits && curr) {
+ curr->SetFlags(aBits);
+ }
+
+ return curr;
+}
+
+// Notes that a given element is "dirty" with respect to the given descendants
+// bit (which may be one of dirty descendants, dirty animation descendants, or
+// need frame construction for descendants).
+//
+// This function operates on the dirty element itself, despite the fact that the
+// bits are generally used to describe descendants. This allows restyle roots
+// to be scoped as tightly as possible. On the first call to NoteDirtyElement
+// since the last restyle, we don't set any descendant bits at all, and just set
+// the element as the restyle root.
+//
+// Because the style traversal handles multiple tasks (styling,
+// animation-ticking, and lazy frame construction), there are potentially three
+// separate kinds of dirtiness to track. Rather than maintaining three separate
+// restyle roots, we use a single root, and always bubble it up to be the
+// nearest common ancestor of all the dirty content in the tree. This means that
+// we need to track the types of dirtiness that the restyle root corresponds to,
+// so SetServoRestyleRoot accepts a bitfield along with an element.
+//
+// The overall algorithm is as follows:
+// * When the first dirty element is noted, we just set as the restyle root.
+// * When additional dirty elements are noted, we propagate the given bit up
+// the tree, until we either reach the restyle root or the document root.
+// * If we reach the document root, we then propagate the bits associated with
+// the restyle root up the tree until we cross the path of the new root. Once
+// we find this common ancestor, we record it as the restyle root, and then
+// clear the bits between the new restyle root and the document root.
+// * If we have dirty content beneath multiple "document style traversal roots"
+// (which are the main DOM + each piece of document-level native-anoymous
+// content), we set the restyle root to the nsINode of the document itself.
+// This is the bail-out case where we traverse everything.
+//
+// Note that, since we track a root, we try to optimize the case where an
+// element under the current root is dirtied, that's why we don't trivially use
+// `nsContentUtils::GetCommonFlattenedTreeAncestorForStyle`.
+static void NoteDirtyElement(Element* aElement, uint32_t aBits) {
+ MOZ_ASSERT(aElement->IsInComposedDoc());
+
+ // Check the existing root early on, since it may allow us to short-circuit
+ // before examining the parent chain.
+ Document* doc = aElement->GetComposedDoc();
+ nsINode* existingRoot = doc->GetServoRestyleRoot();
+ if (existingRoot == aElement) {
+ doc->SetServoRestyleRootDirtyBits(doc->GetServoRestyleRootDirtyBits() |
+ aBits);
+ return;
+ }
+
+ nsINode* parent = aElement->GetFlattenedTreeParentNodeForStyle();
+ if (!parent) {
+ // The element is not in the flattened tree, bail.
+ return;
+ }
+
+ if (MOZ_LIKELY(parent->IsElement())) {
+ // If our parent is unstyled, we can inductively assume that it will be
+ // traversed when the time is right, and that the traversal will reach us
+ // when it happens. Nothing left to do.
+ if (!parent->AsElement()->HasServoData()) {
+ return;
+ }
+
+ // Similarly, if our parent already has the bit we're propagating, we can
+ // assume everything is already set up.
+ if (parent->HasAllFlags(aBits)) {
+ return;
+ }
+
+ // If the parent is styled but is display:none, we're done.
+ //
+ // We can't check for a frame here, since <frame> elements inside <frameset>
+ // still need to generate a frame, even if they're display: none. :(
+ //
+ // The servo traversal doesn't keep style data under display: none subtrees,
+ // so in order for it to not need to cleanup each time anything happens in a
+ // display: none subtree, we keep it clean.
+ //
+ // Also, we can't be much more smarter about using the parent's frame in
+ // order to avoid work here, because since the style system keeps style data
+ // in, e.g., subtrees under a leaf frame, missing restyles and such in there
+ // has observable behavior via getComputedStyle, for example.
+ if (Servo_Element_IsDisplayNone(parent->AsElement())) {
+ return;
+ }
+ }
+
+ if (PresShell* presShell = doc->GetPresShell()) {
+ presShell->EnsureStyleFlush();
+ }
+
+ MOZ_ASSERT(parent->IsElement() || parent == doc);
+
+ // The bit checks below rely on this to arrive to useful conclusions about the
+ // shape of the tree.
+ AssertNoBitsPropagatedFrom(existingRoot);
+
+ // If there's no existing restyle root, or if the root is already aElement,
+ // just note the root+bits and return.
+ if (!existingRoot) {
+ doc->SetServoRestyleRoot(aElement, aBits);
+ return;
+ }
+
+ // There is an existing restyle root - walk up the tree from our element,
+ // propagating bits as we go.
+ const bool reachedDocRoot =
+ !parent->IsElement() ||
+ !PropagateBits(parent->AsElement(), aBits, existingRoot, aBits);
+
+ uint32_t existingBits = doc->GetServoRestyleRootDirtyBits();
+ if (!reachedDocRoot || existingRoot == doc) {
+ // We're a descendant of the existing root. All that's left to do is to
+ // make sure the bit we propagated is also registered on the root.
+ doc->SetServoRestyleRoot(existingRoot, existingBits | aBits);
+ } else {
+ // We reached the root without crossing the pre-existing restyle root. We
+ // now need to find the nearest common ancestor, so climb up from the
+ // existing root, extending bits along the way.
+ Element* rootParent = existingRoot->GetFlattenedTreeParentElementForStyle();
+ // We can stop at the first occurrence of `aBits` in order to find the
+ // common ancestor.
+ if (Element* commonAncestor =
+ PropagateBits(rootParent, existingBits, aElement, aBits)) {
+ MOZ_ASSERT(commonAncestor == aElement ||
+ commonAncestor ==
+ nsContentUtils::GetCommonFlattenedTreeAncestorForStyle(
+ aElement, rootParent));
+
+ // We found a common ancestor. Make that the new style root, and clear the
+ // bits between the new style root and the document root.
+ doc->SetServoRestyleRoot(commonAncestor, existingBits | aBits);
+ Element* curr = commonAncestor;
+ while ((curr = curr->GetFlattenedTreeParentElementForStyle())) {
+ MOZ_ASSERT(curr->HasAllFlags(aBits));
+ curr->UnsetFlags(aBits);
+ }
+ AssertNoBitsPropagatedFrom(commonAncestor);
+ } else {
+ // We didn't find a common ancestor element. That means we're descended
+ // from two different document style roots, so the common ancestor is the
+ // document.
+ doc->SetServoRestyleRoot(doc, existingBits | aBits);
+ }
+ }
+
+ // See the comment in Document::SetServoRestyleRoot about the !IsElement()
+ // check there. Same justification here.
+ MOZ_ASSERT(aElement == doc->GetServoRestyleRoot() ||
+ !doc->GetServoRestyleRoot()->IsElement() ||
+ nsContentUtils::ContentIsFlattenedTreeDescendantOfForStyle(
+ aElement, doc->GetServoRestyleRoot()));
+ MOZ_ASSERT(aElement == doc->GetServoRestyleRoot() ||
+ !doc->GetServoRestyleRoot()->IsElement() || !parent->IsElement() ||
+ BitsArePropagated(parent->AsElement(), aBits,
+ doc->GetServoRestyleRoot()));
+ MOZ_ASSERT(doc->GetServoRestyleRootDirtyBits() & aBits);
+}
+
+void Element::NoteDirtySubtreeForServo() {
+ MOZ_ASSERT(IsInComposedDoc());
+ MOZ_ASSERT(HasServoData());
+
+ Document* doc = GetComposedDoc();
+ nsINode* existingRoot = doc->GetServoRestyleRoot();
+ uint32_t existingBits =
+ existingRoot ? doc->GetServoRestyleRootDirtyBits() : 0;
+
+ if (existingRoot && existingRoot->IsElement() && existingRoot != this &&
+ nsContentUtils::ContentIsFlattenedTreeDescendantOfForStyle(
+ existingRoot->AsElement(), this)) {
+ PropagateBits(
+ existingRoot->AsElement()->GetFlattenedTreeParentElementForStyle(),
+ existingBits, this, existingBits);
+
+ doc->ClearServoRestyleRoot();
+ }
+
+ NoteDirtyElement(this,
+ existingBits | ELEMENT_HAS_DIRTY_DESCENDANTS_FOR_SERVO);
+}
+
+void Element::NoteDirtyForServo() {
+ NoteDirtyElement(this, ELEMENT_HAS_DIRTY_DESCENDANTS_FOR_SERVO);
+}
+
+void Element::NoteAnimationOnlyDirtyForServo() {
+ NoteDirtyElement(this,
+ ELEMENT_HAS_ANIMATION_ONLY_DIRTY_DESCENDANTS_FOR_SERVO);
+}
+
+void Element::NoteDescendantsNeedFramesForServo() {
+ // Since lazy frame construction can be required for non-element nodes, this
+ // Note() method operates on the parent of the frame-requiring content, unlike
+ // the other Note() methods above (which operate directly on the element that
+ // needs processing).
+ NoteDirtyElement(this, NODE_DESCENDANTS_NEED_FRAMES);
+ SetFlags(NODE_DESCENDANTS_NEED_FRAMES);
+}
+
+double Element::FirstLineBoxBSize() const {
+ const nsBlockFrame* frame = do_QueryFrame(GetPrimaryFrame());
+ if (!frame) {
+ return 0.0;
+ }
+ nsBlockFrame::ConstLineIterator line = frame->LinesBegin();
+ nsBlockFrame::ConstLineIterator lineEnd = frame->LinesEnd();
+ return line != lineEnd
+ ? nsPresContext::AppUnitsToDoubleCSSPixels(line->BSize())
+ : 0.0;
+}
+
+// static
+nsAtom* Element::GetEventNameForAttr(nsAtom* aAttr) {
+ if (aAttr == nsGkAtoms::onwebkitanimationend) {
+ return nsGkAtoms::onwebkitAnimationEnd;
+ }
+ if (aAttr == nsGkAtoms::onwebkitanimationiteration) {
+ return nsGkAtoms::onwebkitAnimationIteration;
+ }
+ if (aAttr == nsGkAtoms::onwebkitanimationstart) {
+ return nsGkAtoms::onwebkitAnimationStart;
+ }
+ if (aAttr == nsGkAtoms::onwebkittransitionend) {
+ return nsGkAtoms::onwebkitTransitionEnd;
+ }
+ return aAttr;
+}
+
+} // namespace mozilla::dom