Source code

Revision control

Copy as Markdown

Other Tools

/* 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/. */
#ifndef mozilla_dom_shadowroot_h_
#define mozilla_dom_shadowroot_h_
#include "mozilla/BindgenUniquePtr.h"
#include "mozilla/DOMEventTargetHelper.h"
#include "mozilla/ServoBindingTypes.h"
#include "mozilla/dom/DocumentBinding.h"
#include "mozilla/dom/DocumentFragment.h"
#include "mozilla/dom/DocumentOrShadowRoot.h"
#include "mozilla/dom/NameSpaceConstants.h"
#include "mozilla/dom/ShadowRootBinding.h"
#include "nsCOMPtr.h"
#include "nsCycleCollectionParticipant.h"
#include "nsStubMutationObserver.h"
#include "nsTHashtable.h"
class nsAtom;
class nsIContent;
class nsIPrincipal;
enum class CustomElementRegistryState : uint8_t;
namespace mozilla {
struct StyleAuthorStyles;
struct StyleRuleChange;
class EventChainPreVisitor;
class ServoStyleRuleMap;
enum class StyleRuleChangeKind : uint32_t;
enum class BuiltInStyleSheet : uint8_t;
namespace css {
class Rule;
}
namespace dom {
class CSSImportRule;
class CustomElementRegistry;
class Element;
class HTMLInputElement;
class OwningTrustedHTMLOrNullIsEmptyString;
class TrustedHTMLOrString;
class TrustedHTMLOrNullIsEmptyString;
#define SHADOW_ROOT_FLAG_BIT(n_) \
NODE_FLAG_BIT(NODE_TYPE_SPECIFIC_BITS_OFFSET + (n_))
// ShadowRoot-specific flags
enum : uint32_t {
// Mode: open (0) or closed (1)
SHADOW_ROOT_MODE_CLOSED = SHADOW_ROOT_FLAG_BIT(0),
// Whether focus is delegated
SHADOW_ROOT_DELEGATES_FOCUS = SHADOW_ROOT_FLAG_BIT(1),
// Slot assignment: named (0) or manual (1)
SHADOW_ROOT_SLOT_ASSIGNMENT_MANUAL = SHADOW_ROOT_FLAG_BIT(2),
SHADOW_ROOT_IS_DECLARATIVE = SHADOW_ROOT_FLAG_BIT(3),
SHADOW_ROOT_IS_CLONABLE = SHADOW_ROOT_FLAG_BIT(4),
SHADOW_ROOT_IS_SERIALIZABLE = SHADOW_ROOT_FLAG_BIT(5),
SHADOW_ROOT_IS_AVAILABLE_TO_ELEMENT_INTERNALS = SHADOW_ROOT_FLAG_BIT(6),
// Whether this is the <details> internal shadow tree
SHADOW_ROOT_IS_DETAILS_SHADOW_TREE = SHADOW_ROOT_FLAG_BIT(7),
// 2-bit field encoding the shadow root's custom element registry state.
// See CustomElementRegistryState for the possible values.
SHADOWROOT_CUSTOM_ELEMENT_REGISTRY_LOW_BIT = SHADOW_ROOT_FLAG_BIT(8),
SHADOWROOT_CUSTOM_ELEMENT_REGISTRY_MASK =
SHADOW_ROOT_FLAG_BIT(8) | SHADOW_ROOT_FLAG_BIT(9),
// Remaining bits are unused
SHADOW_ROOT_FLAGS_BITS_USED = 10
};
#undef SHADOW_ROOT_FLAG_BIT
// Make sure we have space for our bits
ASSERT_NODE_FLAGS_SPACE(NODE_TYPE_SPECIFIC_BITS_OFFSET +
SHADOW_ROOT_FLAGS_BITS_USED);
class ShadowRoot final : public DocumentFragment, public DocumentOrShadowRoot {
friend class DocumentOrShadowRoot;
using Declarative = Element::ShadowRootDeclarative;
using IsClonable = Element::ShadowRootClonable;
using IsSerializable = Element::ShadowRootSerializable;
public:
NS_IMPL_FROMNODE_HELPER(ShadowRoot, IsShadowRoot());
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ShadowRoot, DocumentFragment)
NS_DECL_ISUPPORTS_INHERITED
ShadowRoot(Element* aElement, ShadowRootMode aMode,
Element::DelegatesFocus aDelegatesFocus,
SlotAssignmentMode aSlotAssignment, IsClonable aClonable,
IsSerializable aIsSerializable, Declarative aDeclarative,
already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo);
void AddSizeOfExcludingThis(nsWindowSizes&, size_t* aNodeSize) const final;
// Try to reassign an element or text to a slot.
void MaybeReassignContent(nsIContent& aElementOrText);
// Called when an element is inserted as a direct child of our host. Tries to
// slot the child in one of our slots.
void MaybeSlotHostChild(nsIContent&);
// Called when a direct child of our host is removed. Tries to un-slot the
// child from the currently-assigned slot, if any.
void MaybeUnslotHostChild(nsIContent&);
// Shadow DOM v1
Element* Host() const {
MOZ_ASSERT(GetHost(),
"ShadowRoot always has a host, how did we create "
"this ShadowRoot?");
return GetHost();
}
ShadowRootMode Mode() const {
return HasFlag(SHADOW_ROOT_MODE_CLOSED) ? ShadowRootMode::Closed
: ShadowRootMode::Open;
}
bool DelegatesFocus() const { return HasFlag(SHADOW_ROOT_DELEGATES_FOCUS); }
SlotAssignmentMode SlotAssignment() const {
return HasFlag(SHADOW_ROOT_SLOT_ASSIGNMENT_MANUAL)
? SlotAssignmentMode::Manual
: SlotAssignmentMode::Named;
}
bool Clonable() const { return HasFlag(SHADOW_ROOT_IS_CLONABLE); }
bool IsClosed() const { return HasFlag(SHADOW_ROOT_MODE_CLOSED); }
bool Serializable() const { return HasFlag(SHADOW_ROOT_IS_SERIALIZABLE); }
void RemoveSheetFromStyles(StyleSheet&);
void RuleAdded(StyleSheet&, css::Rule&);
void RuleRemoved(StyleSheet&, css::Rule&);
void RuleChanged(StyleSheet&, css::Rule*, const StyleRuleChange&);
void ImportRuleLoaded(StyleSheet&);
void SheetCloned(StyleSheet&);
void StyleSheetApplicableStateChanged(StyleSheet&);
// Adds a built-in author style-sheet to the shadow tree.
void AppendBuiltInStyleSheet(BuiltInStyleSheet);
/**
* Clones internal state, for example stylesheets, of aOther to 'this'.
*/
void CloneInternalDataFrom(ShadowRoot* aOther);
void InsertSheetAt(size_t aIndex, StyleSheet&);
// Calls UnbindFromTree for each of our kids, and also flags us as no longer
// being connected.
void Unbind();
// Only intended for UA widgets / special shadow roots, or for handling
// failure cases when adopting (see BlastSubtreeToPieces).
//
// Forgets our shadow host and unbinds all our kids.
void Unattach();
// Calls BindToTree on each of our kids, and also maybe flags us as being
// connected.
nsresult Bind();
/**
* Explicitly invalidates the style and layout of the flattened-tree subtree
* rooted at the element.
*
* You need to use this whenever the flat tree is going to be shuffled in a
* way that layout doesn't understand via the usual ContentInserted /
* ContentAppended / ContentRemoved notifications. For example, if removing an
* element will cause a change in the flat tree such that other element will
* start showing up (like fallback content), this method needs to be called on
* an ancestor of that element.
*
* It is important that this runs _before_ actually shuffling the flat tree
* around, so that layout knows the actual tree that it needs to invalidate.
*/
void InvalidateStyleAndLayoutOnSubtree(Element*);
private:
void InsertSheetIntoAuthorData(size_t aIndex, StyleSheet&,
const nsTArray<RefPtr<StyleSheet>>&);
void AppendStyleSheet(StyleSheet& aSheet) {
InsertSheetAt(SheetCount(), aSheet);
}
bool IsDetailsShadowTree() const {
return HasFlag(SHADOW_ROOT_IS_DETAILS_SHADOW_TREE);
}
/**
* Represents the insertion point in a slot for a given node.
*/
struct SlotInsertionPoint {
HTMLSlotElement* mSlot = nullptr;
Maybe<uint32_t> mIndex;
SlotInsertionPoint() = default;
SlotInsertionPoint(HTMLSlotElement* aSlot, const Maybe<uint32_t>& aIndex)
: mSlot(aSlot), mIndex(aIndex) {}
};
/**
* Return the assignment corresponding to the content node at this particular
* point in time.
*
* It's the caller's responsibility to actually call InsertAssignedNode /
* AppendAssignedNode in the slot as needed.
*/
SlotInsertionPoint SlotInsertionPointFor(nsIContent&);
/**
* Returns the effective slot name for a given slottable. In most cases, this
* is just the value of the slot attribute, if any, or the empty string, but
* this also deals with the <details> shadow tree specially.
*/
void GetSlotNameFor(const nsIContent&, nsAString&) const;
/**
* Re-assign the current main summary if it has changed.
*
* Must be called only if IsDetailsShadowTree() is true.
*/
enum class SummaryChangeReason { Deletion, Insertion };
void MaybeReassignMainSummary(SummaryChangeReason);
public:
void AddSlot(HTMLSlotElement* aSlot);
void RemoveSlot(HTMLSlotElement* aSlot);
bool HasSlots() const { return !mSlotMap.IsEmpty(); };
HTMLSlotElement* GetDefaultSlot() const {
SlotArray* list = mSlotMap.Get(u""_ns);
return list ? (*list).AsSpan()[0] : nullptr;
}
void PartAdded(const Element&);
void PartRemoved(const Element&);
IMPL_EVENT_HANDLER(slotchange);
const nsTArray<const Element*>& Parts() const { return mParts; }
const StyleAuthorStyles* GetServoStyles() const { return mServoStyles.get(); }
StyleAuthorStyles* GetServoStyles() { return mServoStyles.get(); }
mozilla::ServoStyleRuleMap& ServoStyleRuleMap();
JSObject* WrapNode(JSContext*, JS::Handle<JSObject*> aGivenProto) final;
void NodeInfoChanged(Document* aOldDoc) override;
void AddToIdTable(Element* aElement, nsAtom* aId);
void RemoveFromIdTable(Element* aElement, nsAtom* aId);
// WebIDL methods.
using mozilla::dom::DocumentOrShadowRoot::GetElementById;
Element* GetActiveElement();
/**
* These methods allow UA Widget to insert DOM elements into the Shadow ROM
* without putting their DOM reflectors to content scope first.
* The inserted DOM will have their reflectors in the UA Widget scope.
*/
nsINode* ImportNodeAndAppendChildAt(nsINode& aParentNode, nsINode& aNode,
bool aDeep, mozilla::ErrorResult& rv);
nsINode* CreateElementAndAppendChildAt(nsINode& aParentNode,
const nsAString& aTagName,
mozilla::ErrorResult& rv);
bool IsUAWidget() const { return HasBeenInUAWidget(); }
void SetIsUAWidget() {
MOZ_ASSERT(!HasChildren());
SetIsNativeAnonymousRoot();
SetFlags(NODE_HAS_BEEN_IN_UA_WIDGET);
}
bool IsAvailableToElementInternals() const {
return HasFlag(SHADOW_ROOT_IS_AVAILABLE_TO_ELEMENT_INTERNALS);
}
void SetAvailableToElementInternals() {
SetFlags(SHADOW_ROOT_IS_AVAILABLE_TO_ELEMENT_INTERNALS);
}
CustomElementRegistryState GetCustomElementRegistryState() const {
return static_cast<CustomElementRegistryState>(
(GetFlags() & SHADOWROOT_CUSTOM_ELEMENT_REGISTRY_MASK) /
SHADOWROOT_CUSTOM_ELEMENT_REGISTRY_LOW_BIT);
}
void SetCustomElementRegistryState(CustomElementRegistryState aState) {
UnsetFlags(SHADOWROOT_CUSTOM_ELEMENT_REGISTRY_MASK);
SetFlags(static_cast<uint32_t>(aState) *
SHADOWROOT_CUSTOM_ELEMENT_REGISTRY_LOW_BIT);
}
bool HasCustomElementRegistry() const {
return GetCustomElementRegistryState() !=
CustomElementRegistryState::Global;
}
void SetCustomElementRegistry(CustomElementRegistry* aRegistry);
void SetKeepCustomElementRegistryNull();
CustomElementRegistry* GetCustomElementRegistry();
void GetEventTargetParent(EventChainPreVisitor& aVisitor) override;
bool IsDeclarative() const { return HasFlag(SHADOW_ROOT_IS_DECLARATIVE); }
void SetIsDeclarative(Declarative aIsDeclarative) {
SetIsDeclarative(aIsDeclarative == Declarative::Yes);
}
void SetIsDeclarative(bool aIsDeclarative) {
if (aIsDeclarative) {
SetFlags(SHADOW_ROOT_IS_DECLARATIVE);
} else {
UnsetFlags(SHADOW_ROOT_IS_DECLARATIVE);
}
}
void SetHTML(const nsAString& aInnerHTML, const SetHTMLOptions& aOptions,
ErrorResult& aError);
MOZ_CAN_RUN_SCRIPT
void SetHTMLUnsafe(const TrustedHTMLOrString& aHTML,
const SetHTMLUnsafeOptions& aOptions,
nsIPrincipal* aSubjectPrincipal, ErrorResult& aError);
// @param aInnerHTML will always be of type `NullIsEmptyString`.
void GetInnerHTML(OwningTrustedHTMLOrNullIsEmptyString& aInnerHTML);
MOZ_CAN_RUN_SCRIPT void SetInnerHTML(
const TrustedHTMLOrNullIsEmptyString& aInnerHTML,
nsIPrincipal* aSubjectPrincipal, ErrorResult& aError);
void GetHTML(const GetHTMLOptions& aOptions, nsAString& aResult);
bool HasReferenceTarget() const { return mReferenceTarget; }
void GetReferenceTarget(nsAString& aResult) const {
if (!mReferenceTarget) {
aResult.SetIsVoid(true);
return;
}
mReferenceTarget->ToString(aResult);
}
nsAtom* ReferenceTarget() const { return mReferenceTarget; }
void SetReferenceTarget(const nsAString& aValue) {
if (aValue.IsVoid()) {
return SetReferenceTarget(nullptr);
}
SetReferenceTarget(NS_Atomize(aValue));
}
void SetReferenceTarget(RefPtr<nsAtom> aTarget);
Element* GetReferenceTargetElement() const {
return mReferenceTarget ? GetElementById(mReferenceTarget) : nullptr;
}
protected:
// FIXME(emilio): This will need to become more fine-grained.
void ApplicableRulesChanged();
virtual ~ShadowRoot();
// Make sure that the first field is pointer-aligned so it doesn't get packed
// in the base class' padding, since otherwise rust-bindgen can't generate
// correct bindings for it, see
// The computed data from the style sheets.
BindgenUniquePtr<StyleAuthorStyles> mServoStyles;
UniquePtr<mozilla::ServoStyleRuleMap> mStyleRuleMap;
using SlotArray = TreeOrderedArray<HTMLSlotElement*>;
// Map from name of slot to an array of all slots in the shadow DOM with with
// the given name. The slots are stored as a weak pointer because the elements
// are in the shadow tree and should be kept alive by its parent.
nsClassHashtable<nsStringHashKey, SlotArray> mSlotMap;
// Unordered array of all elements that have a part attribute in this shadow
// tree.
nsTArray<const Element*> mParts;
RefPtr<nsAtom> mReferenceTarget;
static bool ReferenceTargetIDTargetChanged(Element* aOldElement,
Element* aNewElement, void* aData);
static bool RecursiveReferenceTargetChanged(void* aData);
void NotifyReferenceTargetChangedObservers();
nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override;
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_shadowroot_h_