Bug 1308080 - Make <details> use a shadow tree as per spec. r=TYLin,smaug

The behavior changes match WebKit and Blink. I can look into upstreaming
some of these to WPT.

Differential Revision: https://phabricator.services.mozilla.com/D34754
This commit is contained in:
Emilio Cobos Álvarez 2022-09-16 14:54:12 +00:00
parent d9ee7049a3
commit 3fddb3b833
30 changed files with 255 additions and 448 deletions

View File

@ -13,7 +13,9 @@
#include "nsWindowSizes.h"
#include "mozilla/dom/DirectionalityUtils.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/HTMLDetailsElement.h"
#include "mozilla/dom/HTMLSlotElement.h"
#include "mozilla/dom/HTMLSummaryElement.h"
#include "mozilla/dom/Text.h"
#include "mozilla/dom/TreeOrderedArrayInlines.h"
#include "mozilla/EventDispatcher.h"
@ -57,6 +59,7 @@ ShadowRoot::ShadowRoot(Element* aElement, ShadowRootMode aMode,
mDelegatesFocus(aDelegatesFocus),
mSlotAssignment(aSlotAssignment),
mIsUAWidget(false),
mIsDetailsShadowTree(aElement->IsHTMLElement(nsGkAtoms::details)),
mIsAvailableToElementInternals(false) {
SetHost(aElement);
@ -251,9 +254,7 @@ void ShadowRoot::AddSlot(HTMLSlotElement* aSlot) {
for (nsIContent* child = GetHost()->GetFirstChild(); child;
child = child->GetNextSibling()) {
nsAutoString slotName;
if (auto* element = Element::FromNode(*child)) {
element->GetAttr(nsGkAtoms::slot, slotName);
}
GetSlotNameFor(*child, slotName);
if (!child->IsSlotable() || !slotName.Equals(name)) {
continue;
}
@ -581,6 +582,24 @@ void ShadowRoot::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
}
}
void ShadowRoot::GetSlotNameFor(const nsIContent& aContent,
nsAString& aName) const {
if (mIsDetailsShadowTree) {
const auto* summary = HTMLSummaryElement::FromNode(aContent);
if (summary && summary->IsMainSummary()) {
aName.AssignLiteral("internal-main-summary");
}
// Otherwise use the default slot.
return;
}
// Note that if slot attribute is missing, assign it to the first default
// slot, if exists.
if (const Element* element = Element::FromNode(aContent)) {
element->GetAttr(nsGkAtoms::slot, aName);
}
}
ShadowRoot::SlotInsertionPoint ShadowRoot::SlotInsertionPointFor(
nsIContent& aContent) {
HTMLSlotElement* slot = nullptr;
@ -592,11 +611,7 @@ ShadowRoot::SlotInsertionPoint ShadowRoot::SlotInsertionPointFor(
}
} else {
nsAutoString slotName;
// Note that if slot attribute is missing, assign it to the first default
// slot, if exists.
if (Element* element = Element::FromNode(aContent)) {
element->GetAttr(nsGkAtoms::slot, slotName);
}
GetSlotNameFor(aContent, slotName);
SlotArray* slots = mSlotMap.Get(slotName);
if (!slots) {
@ -701,6 +716,29 @@ void ShadowRoot::MaybeReassignContent(nsIContent& aElementOrText) {
}
}
void ShadowRoot::MaybeReassignMainSummary(SummaryChangeReason aReason) {
MOZ_ASSERT(mIsDetailsShadowTree);
if (aReason == SummaryChangeReason::Insertion) {
// We've inserted a summary element, may need to remove the existing one.
SlotArray* array = mSlotMap.Get(u"internal-main-summary"_ns);
MOZ_RELEASE_ASSERT(array && (*array)->Length() == 1);
HTMLSlotElement* slot = (*array)->ElementAt(0);
auto* summary = HTMLSummaryElement::FromNodeOrNull(
slot->AssignedNodes().SafeElementAt(0));
if (summary) {
MaybeReassignContent(*summary);
}
} else if (MOZ_LIKELY(GetHost())) {
// We need to null-check GetHost() in case we're unlinking already.
auto* details = HTMLDetailsElement::FromNode(Host());
MOZ_DIAGNOSTIC_ASSERT(details);
// We've removed a summary element, we may need to assign the new one.
if (HTMLSummaryElement* newMainSummary = details->GetFirstSummary()) {
MaybeReassignContent(*newMainSummary);
}
}
}
Element* ShadowRoot::GetActiveElement() {
return GetRetargetedFocusedElement();
}
@ -763,6 +801,10 @@ void ShadowRoot::MaybeUnslotHostChild(nsIContent& aChild) {
slot->RemoveAssignedNode(aChild);
slot->EnqueueSlotChangeEvent();
if (mIsDetailsShadowTree && aChild.IsHTMLElement(nsGkAtoms::summary)) {
MaybeReassignMainSummary(SummaryChangeReason::Deletion);
}
}
Element* ShadowRoot::GetFirstFocusable(bool aWithMouse) const {
@ -809,6 +851,10 @@ void ShadowRoot::MaybeSlotHostChild(nsIContent& aChild) {
return;
}
if (mIsDetailsShadowTree && aChild.IsHTMLElement(nsGkAtoms::summary)) {
MaybeReassignMainSummary(SummaryChangeReason::Insertion);
}
SlotInsertionPoint assignment = SlotInsertionPointFor(aChild);
if (!assignment.mSlot) {
return;

View File

@ -22,7 +22,6 @@
class nsAtom;
class nsIContent;
class nsXBLPrototypeBinding;
namespace mozilla {
@ -159,6 +158,21 @@ class ShadowRoot final : public DocumentFragment,
*/
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 mIsDetailsShadowTree is true.
*/
enum class SummaryChangeReason { Deletion, Insertion };
void MaybeReassignMainSummary(SummaryChangeReason);
public:
void AddSlot(HTMLSlotElement* aSlot);
void RemoveSlot(HTMLSlotElement* aSlot);
@ -297,6 +311,9 @@ class ShadowRoot final : public DocumentFragment,
bool mIsUAWidget : 1;
// Whether this is the <details> internal shadow tree.
bool mIsDetailsShadowTree : 1;
// https://dom.spec.whatwg.org/#shadowroot-available-to-element-internals
bool mIsAvailableToElementInternals : 1;

View File

@ -10721,7 +10721,7 @@ nsContentUtils::GetSubresourceCacheValidationInfo(nsIRequest* aRequest,
aURI->SchemeIs("moz-extension")) {
return true;
}
if (dom::IsChromeURI(aURI)) {
if (aURI->SchemeIs("chrome") || aURI->SchemeIs("resource")) {
return !StaticPrefs::nglayout_debug_disable_xul_cache();
}
return false;

View File

@ -7,6 +7,11 @@
#include "mozilla/dom/HTMLDetailsElement.h"
#include "mozilla/dom/HTMLDetailsElementBinding.h"
#include "mozilla/dom/HTMLSummaryElement.h"
#include "mozilla/dom/ShadowRoot.h"
#include "mozilla/ScopeExit.h"
#include "nsContentUtils.h"
#include "nsTextNode.h"
NS_IMPL_NS_NEW_HTML_ELEMENT(Details)
@ -16,33 +21,31 @@ HTMLDetailsElement::~HTMLDetailsElement() = default;
NS_IMPL_ELEMENT_CLONE(HTMLDetailsElement)
nsIContent* HTMLDetailsElement::GetFirstSummary() const {
HTMLDetailsElement::HTMLDetailsElement(already_AddRefed<NodeInfo>&& aNodeInfo)
: nsGenericHTMLElement(std::move(aNodeInfo)) {
SetupShadowTree();
}
HTMLSummaryElement* HTMLDetailsElement::GetFirstSummary() const {
// XXX: Bug 1245032: Might want to cache the first summary element.
for (nsIContent* child = nsINode::GetFirstChild(); child;
child = child->GetNextSibling()) {
if (child->IsHTMLElement(nsGkAtoms::summary)) {
return child;
if (auto* summary = HTMLSummaryElement::FromNode(child)) {
return summary;
}
}
return nullptr;
}
nsChangeHint HTMLDetailsElement::GetAttributeChangeHint(
const nsAtom* aAttribute, int32_t aModType) const {
nsChangeHint hint =
nsGenericHTMLElement::GetAttributeChangeHint(aAttribute, aModType);
if (aAttribute == nsGkAtoms::open) {
hint |= nsChangeHint_ReconstructFrame;
}
return hint;
}
nsresult HTMLDetailsElement::BeforeSetAttr(int32_t aNameSpaceID, nsAtom* aName,
const nsAttrValueOrString* aValue,
bool aNotify) {
nsresult HTMLDetailsElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
const nsAttrValue* aValue,
const nsAttrValue* aOldValue,
nsIPrincipal* aMaybeScriptedPrincipal,
bool aNotify) {
if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::open) {
bool setOpen = aValue != nullptr;
if (Open() != setOpen) {
bool wasOpen = !!aOldValue;
bool isOpen = !!aValue;
if (wasOpen != isOpen) {
if (mToggleEventDispatcher) {
mToggleEventDispatcher->Cancel();
}
@ -54,8 +57,70 @@ nsresult HTMLDetailsElement::BeforeSetAttr(int32_t aNameSpaceID, nsAtom* aName,
}
}
return nsGenericHTMLElement::BeforeSetAttr(aNameSpaceID, aName, aValue,
aNotify);
return nsGenericHTMLElement::AfterSetAttr(
aNameSpaceID, aName, aValue, aOldValue, aMaybeScriptedPrincipal, aNotify);
}
void HTMLDetailsElement::SetupShadowTree() {
const bool kNotify = false;
AttachAndSetUAShadowRoot(NotifyUAWidgetSetup::No);
RefPtr<ShadowRoot> sr = GetShadowRoot();
if (NS_WARN_IF(!sr)) {
return;
}
nsNodeInfoManager* nim = OwnerDoc()->NodeInfoManager();
RefPtr<NodeInfo> slotNodeInfo = nim->GetNodeInfo(
nsGkAtoms::slot, nullptr, kNameSpaceID_XHTML, nsINode::ELEMENT_NODE);
{
RefPtr<NodeInfo> linkNodeInfo = nim->GetNodeInfo(
nsGkAtoms::link, nullptr, kNameSpaceID_XHTML, nsINode::ELEMENT_NODE);
RefPtr<nsGenericHTMLElement> link =
NS_NewHTMLLinkElement(linkNodeInfo.forget());
if (NS_WARN_IF(!link)) {
return;
}
link->SetAttr(nsGkAtoms::rel, u"stylesheet"_ns, IgnoreErrors());
link->SetAttr(nsGkAtoms::href,
u"resource://content-accessible/details.css"_ns,
IgnoreErrors());
sr->AppendChildTo(link, kNotify, IgnoreErrors());
}
{
RefPtr<nsGenericHTMLElement> slot =
NS_NewHTMLSlotElement(do_AddRef(slotNodeInfo));
if (NS_WARN_IF(!slot)) {
return;
}
slot->SetAttr(kNameSpaceID_None, nsGkAtoms::name,
u"internal-main-summary"_ns, kNotify);
sr->AppendChildTo(slot, kNotify, IgnoreErrors());
RefPtr<NodeInfo> summaryNodeInfo = nim->GetNodeInfo(
nsGkAtoms::summary, nullptr, kNameSpaceID_XHTML, nsINode::ELEMENT_NODE);
RefPtr<nsGenericHTMLElement> summary =
NS_NewHTMLSummaryElement(summaryNodeInfo.forget());
if (NS_WARN_IF(!summary)) {
return;
}
nsAutoString defaultSummaryText;
nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
"DefaultSummary", defaultSummaryText);
RefPtr<nsTextNode> description = new (nim) nsTextNode(nim);
description->SetText(defaultSummaryText, kNotify);
summary->AppendChildTo(description, kNotify, IgnoreErrors());
slot->AppendChildTo(summary, kNotify, IgnoreErrors());
}
{
RefPtr<nsGenericHTMLElement> slot =
NS_NewHTMLSlotElement(slotNodeInfo.forget());
if (NS_WARN_IF(!slot)) {
return;
}
sr->AppendChildTo(slot, kNotify, IgnoreErrors());
}
}
void HTMLDetailsElement::AsyncEventRunning(AsyncEventDispatcher* aEvent) {

View File

@ -13,6 +13,8 @@
namespace mozilla::dom {
class HTMLSummaryElement;
// HTMLDetailsElement implements the <details> tag, which is used as a
// disclosure widget from which the user can obtain additional information or
// controls. Please see the spec for more information.
@ -22,22 +24,18 @@ class HTMLDetailsElement final : public nsGenericHTMLElement {
public:
using NodeInfo = mozilla::dom::NodeInfo;
explicit HTMLDetailsElement(already_AddRefed<NodeInfo>&& aNodeInfo)
: nsGenericHTMLElement(std::move(aNodeInfo)) {}
explicit HTMLDetailsElement(already_AddRefed<NodeInfo>&& aNodeInfo);
NS_IMPL_FROMNODE_HTML_WITH_TAG(HTMLDetailsElement, details)
nsIContent* GetFirstSummary() const;
HTMLSummaryElement* GetFirstSummary() const;
nsresult Clone(NodeInfo* aNodeInfo, nsINode** aResult) const override;
// Element
nsChangeHint GetAttributeChangeHint(const nsAtom* aAttribute,
int32_t aModType) const override;
nsresult BeforeSetAttr(int32_t aNameSpaceID, nsAtom* aName,
const nsAttrValueOrString* aValue,
bool aNotify) override;
nsresult AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
const nsAttrValue* aValue, const nsAttrValue* aOldValue,
nsIPrincipal* aMaybeScriptedPrincipal,
bool aNotify) override;
bool IsInteractiveHTMLContent() const override { return true; }
@ -48,16 +46,13 @@ class HTMLDetailsElement final : public nsGenericHTMLElement {
SetHTMLBoolAttr(nsGkAtoms::open, aOpen, aError);
}
void ToggleOpen() {
ErrorResult rv;
SetOpen(!Open(), rv);
rv.SuppressException();
}
void ToggleOpen() { SetOpen(!Open(), IgnoreErrors()); }
virtual void AsyncEventRunning(AsyncEventDispatcher* aEvent) override;
protected:
virtual ~HTMLDetailsElement();
void SetupShadowTree();
JSObject* WrapNode(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;

View File

@ -96,10 +96,14 @@ bool HTMLSummaryElement::IsMainSummary() const {
return false;
}
return details->GetFirstSummary() == this || IsRootOfNativeAnonymousSubtree();
return details->GetFirstSummary() == this ||
GetContainingShadow() == details->GetShadowRoot();
}
HTMLDetailsElement* HTMLSummaryElement::GetDetails() const {
if (HasBeenInUAWidget()) {
return HTMLDetailsElement::FromNodeOrNull(GetContainingShadowHost());
}
return HTMLDetailsElement::FromNodeOrNull(GetParent());
}

View File

@ -36,7 +36,7 @@ class HTMLSummaryElement final : public nsGenericHTMLElement {
int32_t TabIndexDefault() override;
// Return true if this is the first summary element child of a details or the
// default summary element generated by DetailsFrame.
// default summary element.
bool IsMainSummary() const;
// Return the details element which contains this summary. Otherwise return

View File

@ -19,7 +19,6 @@
#include "mozilla/dom/BindContext.h"
#include "mozilla/dom/BrowsingContext.h"
#include "mozilla/dom/GeneratedImageContent.h"
#include "mozilla/dom/HTMLDetailsElement.h"
#include "mozilla/dom/HTMLSelectElement.h"
#include "mozilla/dom/HTMLSharedListElement.h"
#include "mozilla/dom/HTMLSummaryElement.h"
@ -110,7 +109,6 @@
#include "nsIScrollableFrame.h"
#include "nsBackdropFrame.h"
#include "nsTransitionManager.h"
#include "DetailsFrame.h"
#include "nsIPopupContainer.h"
#ifdef ACCESSIBILITY
@ -1778,7 +1776,8 @@ void nsCSSFrameConstructor::CreateGeneratedContentItem(
aPseudoElement == PseudoStyleType::marker,
"unexpected aPseudoElement");
if (HasUAWidget(aOriginatingElement)) {
if (HasUAWidget(aOriginatingElement) &&
!aOriginatingElement.IsHTMLElement(nsGkAtoms::details)) {
return;
}
@ -3209,20 +3208,18 @@ nsIFrame* nsCSSFrameConstructor::ConstructFieldSetFrame(
return fieldsetFrame;
}
nsIFrame* nsCSSFrameConstructor::ConstructDetailsFrame(
nsIFrame* nsCSSFrameConstructor::ConstructDetails(
nsFrameConstructorState& aState, FrameConstructionItem& aItem,
nsContainerFrame* aParentFrame, const nsStyleDisplay* aStyleDisplay,
nsFrameList& aFrameList) {
if (!aStyleDisplay->IsScrollableOverflow()) {
return ConstructNonScrollableBlockWithConstructor(
aState, aItem, aParentFrame, aStyleDisplay, aFrameList,
NS_NewDetailsFrame);
return ConstructNonScrollableBlock(aState, aItem, aParentFrame,
aStyleDisplay, aFrameList);
}
// Build a scroll frame to wrap details frame if necessary.
return ConstructScrollableBlockWithConstructor(aState, aItem, aParentFrame,
aStyleDisplay, aFrameList,
NS_NewDetailsFrame);
// Build a scroll frame if necessary.
return ConstructScrollableBlock(aState, aItem, aParentFrame, aStyleDisplay,
aFrameList);
}
nsIFrame* nsCSSFrameConstructor::ConstructBlockRubyFrame(
@ -3460,8 +3457,7 @@ nsCSSFrameConstructor::FindHTMLData(const Element& aElement,
SIMPLE_TAG_CREATE(audio, NS_NewHTMLVideoFrame),
SIMPLE_TAG_CREATE(progress, NS_NewProgressFrame),
SIMPLE_TAG_CREATE(meter, NS_NewMeterFrame),
COMPLEX_TAG_CREATE(details,
&nsCSSFrameConstructor::ConstructDetailsFrame)};
COMPLEX_TAG_CREATE(details, &nsCSSFrameConstructor::ConstructDetails)};
return FindDataByTag(aElement, aStyle, sHTMLData, ArrayLength(sHTMLData));
}
@ -4583,15 +4579,6 @@ nsIFrame* nsCSSFrameConstructor::ConstructScrollableBlock(
nsFrameConstructorState& aState, FrameConstructionItem& aItem,
nsContainerFrame* aParentFrame, const nsStyleDisplay* aDisplay,
nsFrameList& aFrameList) {
return ConstructScrollableBlockWithConstructor(aState, aItem, aParentFrame,
aDisplay, aFrameList,
NS_NewBlockFormattingContext);
}
nsIFrame* nsCSSFrameConstructor::ConstructScrollableBlockWithConstructor(
nsFrameConstructorState& aState, FrameConstructionItem& aItem,
nsContainerFrame* aParentFrame, const nsStyleDisplay* aDisplay,
nsFrameList& aFrameList, BlockFrameCreationFunc aConstructor) {
nsIContent* const content = aItem.mContent;
ComputedStyle* const computedStyle = aItem.mComputedStyle;
@ -4603,7 +4590,8 @@ nsIFrame* nsCSSFrameConstructor::ConstructScrollableBlockWithConstructor(
// Create our block frame
// pass a temporary stylecontext, the correct one will be set later
nsContainerFrame* scrolledFrame = aConstructor(mPresShell, computedStyle);
nsContainerFrame* scrolledFrame =
NS_NewBlockFormattingContext(mPresShell, computedStyle);
// Make sure to AddChild before we call ConstructBlock so that we
// end up before our descendants in fixed-pos lists as needed.
@ -4626,14 +4614,6 @@ nsIFrame* nsCSSFrameConstructor::ConstructNonScrollableBlock(
nsFrameConstructorState& aState, FrameConstructionItem& aItem,
nsContainerFrame* aParentFrame, const nsStyleDisplay* aDisplay,
nsFrameList& aFrameList) {
return ConstructNonScrollableBlockWithConstructor(
aState, aItem, aParentFrame, aDisplay, aFrameList, NS_NewBlockFrame);
}
nsIFrame* nsCSSFrameConstructor::ConstructNonScrollableBlockWithConstructor(
nsFrameConstructorState& aState, FrameConstructionItem& aItem,
nsContainerFrame* aParentFrame, const nsStyleDisplay* aDisplay,
nsFrameList& aFrameList, BlockFrameCreationFunc aConstructor) {
ComputedStyle* const computedStyle = aItem.mComputedStyle;
// We want a block formatting context root in paginated contexts for
@ -4653,7 +4633,7 @@ nsIFrame* nsCSSFrameConstructor::ConstructNonScrollableBlockWithConstructor(
}
}
nsContainerFrame* newFrame = aConstructor(mPresShell, computedStyle);
nsContainerFrame* newFrame = NS_NewBlockFrame(mPresShell, computedStyle);
newFrame->AddStateBits(flags);
ConstructBlock(
aState, aItem.mContent,
@ -5280,35 +5260,6 @@ static bool ShouldSuppressFrameInSelect(const nsIContent* aParent,
return true;
}
static bool ShouldSuppressFrameInNonOpenDetails(
const HTMLDetailsElement* aDetails, ComputedStyle* aComputedStyle,
const nsIContent& aChild) {
if (!aDetails || aDetails->Open()) {
return false;
}
if (aChild.GetParent() != aDetails) {
return true;
}
auto* summary = HTMLSummaryElement::FromNode(aChild);
if (summary && summary->IsMainSummary()) {
return false;
}
// Don't suppress NAC, unless it's a ::before, inside ::marker, or ::after.
if (aChild.IsRootOfNativeAnonymousSubtree() &&
!(aChild.IsGeneratedContentContainerForMarker() &&
aComputedStyle->StyleList()->mListStylePosition ==
NS_STYLE_LIST_STYLE_POSITION_INSIDE) &&
!aChild.IsGeneratedContentContainerForBefore() &&
!aChild.IsGeneratedContentContainerForAfter()) {
return false;
}
return true;
}
const nsCSSFrameConstructor::FrameConstructionData*
nsCSSFrameConstructor::FindDataForContent(nsIContent& aContent,
ComputedStyle& aStyle,
@ -5458,16 +5409,6 @@ void nsCSSFrameConstructor::AddFrameConstructionItemsInternal(
return;
}
// When constructing a child of a non-open <details>, create only the frame
// for the main <summary> element, and skip other elements. This only applies
// to things that are not roots of native anonymous subtrees (except for
// ::before and ::after); we always want to create "internal" anonymous
// content.
auto* const details = HTMLDetailsElement::FromNodeOrNull(parent);
if (ShouldSuppressFrameInNonOpenDetails(details, aComputedStyle, *aContent)) {
return;
}
if (aContent->IsHTMLElement(nsGkAtoms::legend) && aParentFrame) {
const nsFieldSetFrame* const fs = GetFieldSetFrameFor(aParentFrame);
if (fs && !fs->GetLegend() && !aState.mHasRenderedLegend &&
@ -5512,16 +5453,6 @@ void nsCSSFrameConstructor::AddFrameConstructionItemsInternal(
AppendPageBreakItem(aContent, aItems);
}
if (details && details->Open()) {
const auto* const summary = HTMLSummaryElement::FromNode(aContent);
if (summary && summary->IsMainSummary()) {
// If details is open, the main summary needs to be rendered as if it is
// the first child, so add the item to the front of the item list.
item = aItems.PrependItem(this, data, aContent, do_AddRef(aComputedStyle),
aSuppressWhiteSpaceOptimizations);
}
}
if (!item) {
item = aItems.AppendItem(this, data, aContent, do_AddRef(aComputedStyle),
aSuppressWhiteSpaceOptimizations);
@ -8141,9 +8072,6 @@ nsIFrame* nsCSSFrameConstructor::CreateContinuingFrame(
} else if (LayoutFrameType::RubyTextContainer == frameType) {
newFrame = NS_NewRubyTextContainerFrame(mPresShell, computedStyle);
newFrame->Init(content, aParentFrame, aFrame);
} else if (LayoutFrameType::Details == frameType) {
newFrame = NS_NewDetailsFrame(mPresShell, computedStyle);
newFrame->Init(content, aParentFrame, aFrame);
} else {
MOZ_CRASH("unexpected frame type");
}
@ -8390,24 +8318,6 @@ bool nsCSSFrameConstructor::MaybeRecreateContainerForFrameRemoval(
return true;
}
if (parent && parent->IsDetailsFrame()) {
HTMLSummaryElement* summary =
HTMLSummaryElement::FromNode(aFrame->GetContent());
DetailsFrame* detailsFrame = static_cast<DetailsFrame*>(parent);
// Unlike adding summary element cases, we need to check children of the
// parent details frame since at this moment the summary element has been
// already removed from the parent details element's child list.
if (summary && detailsFrame->HasMainSummaryFrame(aFrame)) {
// When removing a summary, we should reframe the parent details frame to
// ensure that another summary is used or the default summary is
// generated.
TRACE("Details / Summary");
RecreateFramesForContent(parent->GetContent(), InsertionKind::Async);
return true;
}
}
// Now check for possibly needing to reconstruct due to a pseudo parent
// For the case of ruby pseudo parent, effectively, only pseudo rb/rt frame
// need to be checked here, since all other types of parent will be catched
@ -10588,9 +10498,7 @@ void nsCSSFrameConstructor::ConstructBlock(
// clang-format on
nsBlockFrame* blockFrame = do_QueryFrame(*aNewFrame);
MOZ_ASSERT(blockFrame &&
(blockFrame->IsBlockFrame() || blockFrame->IsDetailsFrame()),
"not a block frame nor a details frame?");
MOZ_ASSERT(blockFrame && blockFrame->IsBlockFrame(), "not a block frame?");
// Create column hierarchy if necessary.
const bool needsColumn =
@ -10691,9 +10599,8 @@ nsBlockFrame* nsCSSFrameConstructor::BeginBuildingColumns(
nsFrameConstructorState& aState, nsIContent* aContent,
nsContainerFrame* aParentFrame, nsContainerFrame* aColumnContent,
ComputedStyle* aComputedStyle) {
MOZ_ASSERT(
aColumnContent->IsBlockFrame() || aColumnContent->IsDetailsFrame(),
"aColumnContent should either be a block frame or a details frame.");
MOZ_ASSERT(aColumnContent->IsBlockFrame(),
"aColumnContent should be a block frame.");
MOZ_ASSERT(aComputedStyle->StyleColumn()->IsColumnContainerStyle(),
"No need to build a column hierarchy!");
@ -11279,17 +11186,6 @@ bool nsCSSFrameConstructor::WipeInsertionParent(nsContainerFrame* aFrame) {
return true;
}
// A <details> that's getting new children. When inserting
// elements into <details>, we reframe the <details> and let frame constructor
// move the main <summary> to the front when constructing the frame
// construction items.
if (auto* details =
HTMLDetailsElement::FromNodeOrNull(aFrame->GetContent())) {
TRACE("Details / Summary");
RecreateFramesForContent(details, InsertionKind::Async);
return true;
}
// Reframe the multi-column container whenever elements insert/append
// into it because we need to reconstruct column-span split.
if (aFrame->IsColumnSetWrapperFrame()) {
@ -11578,9 +11474,6 @@ bool nsCSSFrameConstructor::WipeContainingBlock(
// Situation #5 is a frame in multicol subtree that's getting new children.
if (aFrame->HasAnyStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR)) {
MOZ_ASSERT(!aFrame->IsDetailsFrame(),
"Inserting elements into <details> should have been reframed!");
bool anyColumnSpanItems = false;
for (FCItemIterator iter(aItems); !iter.IsDone(); iter.Next()) {
if (iter.item().mComputedStyle->StyleColumn()->IsColumnSpanStyle()) {

View File

@ -1381,13 +1381,12 @@ class nsCSSFrameConstructor final : public nsFrameManager {
const nsStyleDisplay* aStyleDisplay,
nsFrameList& aFrameList);
// ConstructDetailsFrame puts the new frame in aFrameList and
// handles the kids of the details.
nsIFrame* ConstructDetailsFrame(nsFrameConstructorState& aState,
FrameConstructionItem& aItem,
nsContainerFrame* aParentFrame,
const nsStyleDisplay* aStyleDisplay,
nsFrameList& aFrameList);
// <details> always creates a block per spec.
nsIFrame* ConstructDetails(nsFrameConstructorState& aState,
FrameConstructionItem& aItem,
nsContainerFrame* aParentFrame,
const nsStyleDisplay* aStyleDisplay,
nsFrameList& aFrameList);
// Creates a block frame wrapping an anonymous ruby frame.
nsIFrame* ConstructBlockRubyFrame(nsFrameConstructorState& aState,
@ -1579,15 +1578,6 @@ class nsCSSFrameConstructor final : public nsFrameManager {
const nsStyleDisplay* aDisplay,
nsFrameList& aFrameList);
/**
* Construct a scrollable block frame using the given block frame creation
* function.
*/
nsIFrame* ConstructScrollableBlockWithConstructor(
nsFrameConstructorState& aState, FrameConstructionItem& aItem,
nsContainerFrame* aParentFrame, const nsStyleDisplay* aDisplay,
nsFrameList& aFrameList, BlockFrameCreationFunc aConstructor);
/**
* Construct a non-scrollable block frame
*/
@ -1597,15 +1587,6 @@ class nsCSSFrameConstructor final : public nsFrameManager {
const nsStyleDisplay* aDisplay,
nsFrameList& aFrameList);
/**
* Construct a non-scrollable block frame using the given block frame creation
* function.
*/
nsIFrame* ConstructNonScrollableBlockWithConstructor(
nsFrameConstructorState& aState, FrameConstructionItem& aItem,
nsContainerFrame* aParentFrame, const nsStyleDisplay* aDisplay,
nsFrameList& aFrameList, BlockFrameCreationFunc aConstructor);
/**
* This adds FrameConstructionItem objects to aItemsToConstruct for the
* anonymous content returned by an nsIAnonymousContentCreator::

View File

@ -1,141 +0,0 @@
/* -*- 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/. */
#include "DetailsFrame.h"
#include "mozilla/Attributes.h"
#include "mozilla/PresShell.h"
#include "mozilla/dom/HTMLDetailsElement.h"
#include "mozilla/dom/HTMLSummaryElement.h"
#include "nsContentUtils.h"
#include "nsPlaceholderFrame.h"
#include "nsTextNode.h"
using namespace mozilla;
using namespace mozilla::dom;
NS_IMPL_FRAMEARENA_HELPERS(DetailsFrame)
NS_QUERYFRAME_HEAD(DetailsFrame)
NS_QUERYFRAME_ENTRY(DetailsFrame)
NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
NS_QUERYFRAME_TAIL_INHERITING(nsBlockFrame)
nsBlockFrame* NS_NewDetailsFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
return new (aPresShell) DetailsFrame(aStyle, aPresShell->GetPresContext());
}
namespace mozilla {
DetailsFrame::DetailsFrame(ComputedStyle* aStyle, nsPresContext* aPresContext)
: nsBlockFrame(aStyle, aPresContext, kClassID) {}
DetailsFrame::~DetailsFrame() = default;
void DetailsFrame::SetInitialChildList(ChildListID aListID,
nsFrameList& aChildList) {
#ifdef DEBUG
if (aListID == kPrincipalList) {
CheckValidMainSummary(aChildList);
}
#endif
nsBlockFrame::SetInitialChildList(aListID, aChildList);
}
#ifdef DEBUG
bool DetailsFrame::CheckValidMainSummary(const nsFrameList& aFrameList) const {
for (nsIFrame* child : aFrameList) {
if (child->IsGeneratedContentFrame()) {
continue;
}
HTMLSummaryElement* summary =
HTMLSummaryElement::FromNode(child->GetContent());
if (child == aFrameList.FirstChild()) {
if (summary && summary->IsMainSummary()) {
return true;
} else if (child->GetContent() == GetContent()) {
// The child frame's content is the same as our content, which means
// it's a kind of wrapper frame. Descend into its child list to find
// main summary.
if (CheckValidMainSummary(child->PrincipalChildList())) {
return true;
}
}
} else {
NS_ASSERTION(!summary || !summary->IsMainSummary(),
"Rest of the children are either not summary element "
"or are not the main summary!");
}
}
return false;
}
#endif
void DetailsFrame::DestroyFrom(nsIFrame* aDestructRoot,
PostDestroyData& aPostDestroyData) {
aPostDestroyData.AddAnonymousContent(mDefaultSummary.forget());
nsBlockFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
}
nsresult DetailsFrame::CreateAnonymousContent(
nsTArray<ContentInfo>& aElements) {
auto* details = HTMLDetailsElement::FromNode(GetContent());
if (details->GetFirstSummary()) {
return NS_OK;
}
// The <details> element lacks any direct <summary> child. Create a default
// <summary> element as an anonymous content.
nsNodeInfoManager* nodeInfoManager =
GetContent()->NodeInfo()->NodeInfoManager();
RefPtr<NodeInfo> nodeInfo = nodeInfoManager->GetNodeInfo(
nsGkAtoms::summary, nullptr, kNameSpaceID_XHTML, nsINode::ELEMENT_NODE);
mDefaultSummary = new (nodeInfoManager) HTMLSummaryElement(nodeInfo.forget());
nsAutoString defaultSummaryText;
nsContentUtils::GetMaybeLocalizedString(
nsContentUtils::eFORMS_PROPERTIES, "DefaultSummary",
GetContent()->OwnerDoc(), defaultSummaryText);
RefPtr<nsTextNode> description =
new (nodeInfoManager) nsTextNode(nodeInfoManager);
description->SetText(defaultSummaryText, false);
mDefaultSummary->AppendChildTo(description, false, IgnoreErrors());
aElements.AppendElement(mDefaultSummary);
return NS_OK;
}
void DetailsFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
uint32_t aFilter) {
if (mDefaultSummary) {
aElements.AppendElement(mDefaultSummary);
}
}
bool DetailsFrame::HasMainSummaryFrame(nsIFrame* aSummaryFrame) {
const ChildListIDs flowLists = {kPrincipalList, kOverflowList};
for (nsIFrame* frag = this; frag; frag = frag->GetNextInFlow()) {
for (const auto& [list, listID] : frag->ChildLists()) {
if (!flowLists.contains(listID)) {
continue;
}
for (nsIFrame* child : list) {
child = nsPlaceholderFrame::GetRealFrameFor(child);
// We skip any non-primary frames such as a list-style-position:inside
// bullet frame for the <details> itself.
if (!child->IsGeneratedContentFrame()) {
return aSummaryFrame == child;
}
}
}
}
return false;
}
} // namespace mozilla

View File

@ -1,67 +0,0 @@
/* -*- 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/. */
#ifndef DetailsFrame_h
#define DetailsFrame_h
#include "nsBlockFrame.h"
#include "nsIAnonymousContentCreator.h"
class nsContainerFrame;
namespace mozilla {
// DetailsFrame is generated by HTMLDetailsElement. See
// nsCSSFrameConstructor::ConstructDetailsFrame for the structure of a
// DetailsFrame.
//
class DetailsFrame final : public nsBlockFrame,
public nsIAnonymousContentCreator {
public:
NS_DECL_FRAMEARENA_HELPERS(DetailsFrame)
NS_DECL_QUERYFRAME
explicit DetailsFrame(ComputedStyle* aStyle, nsPresContext* aPresContext);
virtual ~DetailsFrame();
#ifdef DEBUG_FRAME_DUMP
nsresult GetFrameName(nsAString& aResult) const override {
return MakeFrameName(u"Details"_ns, aResult);
}
#endif
#ifdef DEBUG
// Check the frame of the main summary element is the first child in the frame
// list. Returns true if we found the main summary frame; false otherwise.
bool CheckValidMainSummary(const nsFrameList& aFrameList) const;
#endif
void SetInitialChildList(ChildListID aListID,
nsFrameList& aChildList) override;
void DestroyFrom(nsIFrame* aDestructRoot,
PostDestroyData& aPostDestroyData) override;
// nsIAnonymousContentCreator
nsresult CreateAnonymousContent(nsTArray<ContentInfo>& aElements) override;
void AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
uint32_t aFilter) override;
// Returns true if |aSummaryFrame| is the main summary (i.e. the first child
// of this details frame).
// This function is used when the summary element is removed from the parent
// details element since at that moment the summary element has been already
// removed from the details element children.
bool HasMainSummaryFrame(nsIFrame* aSummaryFrame);
private:
nsCOMPtr<nsIContent> mDefaultSummary;
};
} // namespace mozilla
#endif // DetailsFrame_h

View File

@ -7,7 +7,6 @@ from FrameClass import Frame, AbstractFrame, LEAF, NOT_LEAF, DYNAMIC_LEAF
FRAME_CLASSES = [
Frame("BRFrame", "Br", LEAF),
Frame("DetailsFrame", "Details", NOT_LEAF),
Frame("nsBCTableCellFrame", "TableCell", NOT_LEAF),
Frame("nsBackdropFrame", "Backdrop", LEAF),
Frame("nsBlockFrame", "Block", NOT_LEAF),

View File

@ -165,7 +165,6 @@ UNIFIED_SOURCES += [
"ColumnUtils.cpp",
"CSSAlignUtils.cpp",
"CSSOrderAwareFrameIterator.cpp",
"DetailsFrame.cpp",
"FrameChildList.cpp",
"MathMLTextRunFactory.cpp",
"nsAbsoluteContainingBlock.cpp",

View File

@ -157,8 +157,6 @@ nsIFrame* NS_NewDateTimeControlFrame(mozilla::PresShell* aPresShell,
mozilla::ComputedStyle* aStyle);
nsIFrame* NS_NewSearchControlFrame(mozilla::PresShell* aPresShell,
mozilla::ComputedStyle* aStyle);
nsBlockFrame* NS_NewDetailsFrame(mozilla::PresShell* aPresShell,
mozilla::ComputedStyle* aStyle);
// Table frame factories
class nsTableWrapperFrame;

View File

@ -0,0 +1,5 @@
<!doctype html>
<div id="details">
<div id="summary">Summary</div>
<p>This is the details.</p>
</div>

View File

@ -3,6 +3,9 @@
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<head>
<style>
summary {
list-style-type: none;
}
details::after {
content: "This is the details.";
/* Match the margins and display of <p> */

View File

@ -0,0 +1,5 @@
<!doctype html>
<div id="details">
<p>This is the details.</p>
<div id="summary">Summary</div>
</div>

View File

@ -3,6 +3,9 @@
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<head>
<style>
summary {
list-style-type: none;
}
details::before {
content: "This is the details.";
/* Match the margins and display of <p> */

View File

@ -3,11 +3,6 @@
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<html>
<style>
div#details::first-line {
color: blue;
}
</style>
<body>
<div id="details">
<span>Summary

View File

@ -7,7 +7,7 @@
<ol>
<li>First item
<div>
<summary>Summary
<summary style="list-style-position: inside">Summary
<ul>
<li>First unordered item in summary</li>
<li>Second unordered item in summary</li>

View File

@ -3,6 +3,9 @@
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<head>
<style>
summary {
list-style-type: none;
}
details::before {
content: "This is the details.";
/* Match the margins and display of <p> */

View File

@ -3,11 +3,6 @@
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<html>
<style>
div#details::first-line {
color: blue;
}
</style>
<body>
<div id="details">
<span>Summary

View File

@ -85,10 +85,10 @@ fuzzy-if(geckoview,0-7,0-1) == mouse-click-float-details.html open-float-details
fuzzy(0-4,0-1) == mouse-click-twice-float-details.html float-details.html # Bug 1316430
# Generated content bits
== details-after.html single-summary.html
== details-before.html single-summary.html
== details-after.html details-after-ref.html
== details-before.html details-before-ref.html
== open-details-after.html open-single-summary.html
== open-details-before.html open-single-summary.html
== open-details-before.html details-before-ref.html
# Move summary element
== move-float-summary-to-different-details.html move-float-summary-to-different-details-ref.html

View File

@ -108,6 +108,10 @@ mozilla::LazyLogModule sCssLoaderLog("nsCSSLoader");
static mozilla::LazyLogModule gSriPRLog("SRI");
static bool IsPrivilegedURI(nsIURI* aURI) {
return aURI->SchemeIs("chrome") || aURI->SchemeIs("resource");
}
#define LOG_ERROR(args) MOZ_LOG(sCssLoaderLog, mozilla::LogLevel::Error, args)
#define LOG_WARN(args) MOZ_LOG(sCssLoaderLog, mozilla::LogLevel::Warning, args)
#define LOG_DEBUG(args) MOZ_LOG(sCssLoaderLog, mozilla::LogLevel::Debug, args)
@ -169,7 +173,7 @@ bool SheetLoadDataHashKey::KeyEquals(const SheetLoadDataHashKey& aKey) const {
}
// Chrome URIs ignore everything else.
if (dom::IsChromeURI(mURI)) {
if (IsPrivilegedURI(mURI)) {
return true;
}
@ -1821,8 +1825,8 @@ Result<Loader::LoadSheetResult, nsresult> Loader::LoadStyleLink(
nsINode* requestingNode =
aInfo.mContent ? static_cast<nsINode*>(aInfo.mContent) : mDocument;
bool syncLoad = aInfo.mContent && aInfo.mContent->IsInUAWidget() &&
IsChromeURI(aInfo.mURI);
const bool syncLoad = aInfo.mContent && aInfo.mContent->IsInUAWidget() &&
IsPrivilegedURI(aInfo.mURI);
LOG((" Link sync load: '%s'", syncLoad ? "true" : "false"));
MOZ_ASSERT_IF(syncLoad, !aObserver);

View File

@ -271,6 +271,7 @@ RESOURCE_FILES += [
CONTENT_ACCESSIBLE_FILES += [
"ImageDocument.css",
"res/details.css",
"res/plaintext.css",
"res/searchfield-cancel.svg",
"res/viewsource.css",

View File

@ -0,0 +1,22 @@
/* 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/. */
slot:not([name]) {
display: none;
}
/* Either the fallback summary (inside the shadow tree), or the slotted main
summary. */
summary,
slot[name=internal-main-summary]::slotted(summary) {
display: list-item;
counter-increment: list-item 0;
list-style: disclosure-closed inside;
}
:host([open]) summary,
:host([open]) slot[name=internal-main-summary]::slotted(summary) {
list-style-type: disclosure-open;
}
:host([open]) slot:not([name]) {
display: revert;
}

View File

@ -782,27 +782,6 @@ video > .caption-box {
display direction, maybe related with bug 1558431. */
}
/* details & summary */
details > summary:-moz-native-anonymous {
/* TODO(krosylight): Remove this when fixing bug 1308080 */
user-select: none;
}
details > summary:is(:first-of-type, :-moz-native-anonymous) {
display: list-item;
counter-increment: list-item 0;
list-style: disclosure-closed inside;
}
details[open] > summary:is(:first-of-type, :-moz-native-anonymous) {
list-style-type: disclosure-open;
}
details > summary:first-of-type > *|* {
/* Cancel "list-style-position: inside" inherited from summary. */
list-style-position: initial;
}
/* <dialog> element styles */
dialog {

View File

@ -0,0 +1,7 @@
[multicol-span-all-dynamic-add-013.html]
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1791144
expected:
if os == "win": FAIL
if os == "mac": FAIL
PASS

View File

@ -1,2 +0,0 @@
[details-after.html]
expected: FAIL

View File

@ -1,2 +0,0 @@
[details-before.html]
expected: FAIL