Bug 591737 - Construct details and summary in nsCSSFrameConstructor. r=bz
authorTing-Yu Lin <tlin@mozilla.com>
Tue, 02 Feb 2016 17:39:49 +0800
changeset 319400 e62deaa9a7332df75b2e0f12d8f9331e43dd0d82
parent 319399 a4f84794091c8857ab78ee07400c5423937c4097
child 319401 d5e3c2b54b0e2483a89c5d32ea2a3489437a35f7
push id5913
push userjlund@mozilla.com
push dateMon, 25 Apr 2016 16:57:49 +0000
treeherdermozilla-beta@dcaf0a6fa115 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz
bugs591737
milestone47.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 591737 - Construct details and summary in nsCSSFrameConstructor. r=bz
dom/html/HTMLDetailsElement.cpp
dom/html/HTMLDetailsElement.h
dom/html/HTMLSummaryElement.cpp
dom/html/HTMLSummaryElement.h
layout/base/nsCSSFrameConstructor.cpp
layout/base/nsCSSFrameConstructor.h
layout/generic/DetailsFrame.cpp
layout/generic/DetailsFrame.h
layout/generic/SummaryFrame.cpp
layout/generic/SummaryFrame.h
layout/generic/nsBlockFrame.cpp
layout/generic/nsBlockFrame.h
layout/style/html.css
--- a/dom/html/HTMLDetailsElement.cpp
+++ b/dom/html/HTMLDetailsElement.cpp
@@ -13,16 +13,30 @@ namespace mozilla {
 namespace dom {
 
 HTMLDetailsElement::~HTMLDetailsElement()
 {
 }
 
 NS_IMPL_ELEMENT_CLONE(HTMLDetailsElement)
 
+nsIContent*
+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;
+    }
+  }
+  return nullptr;
+}
+
 nsChangeHint
 HTMLDetailsElement::GetAttributeChangeHint(const nsIAtom* aAttribute,
                                            int32_t aModType) const
 {
   nsChangeHint hint =
     nsGenericHTMLElement::GetAttributeChangeHint(aAttribute, aModType);
   if (aAttribute == nsGkAtoms::open) {
     NS_UpdateHint(hint, nsChangeHint_ReconstructFrame);
--- a/dom/html/HTMLDetailsElement.h
+++ b/dom/html/HTMLDetailsElement.h
@@ -24,16 +24,18 @@ public:
 
   explicit HTMLDetailsElement(already_AddRefed<NodeInfo>& aNodeInfo)
     : nsGenericHTMLElement(aNodeInfo)
   {
   }
 
   NS_IMPL_FROMCONTENT_HTML_WITH_TAG(HTMLDetailsElement, details)
 
+  nsIContent* GetFirstSummary() const;
+
   nsresult Clone(NodeInfo* aNodeInfo, nsINode** aResult) const override;
 
   nsChangeHint GetAttributeChangeHint(const nsIAtom* aAttribute,
                                       int32_t aModType) const override;
 
   // HTMLDetailsElement WebIDL
   bool Open() const { return GetBoolAttr(nsGkAtoms::open); }
 
--- a/dom/html/HTMLSummaryElement.cpp
+++ b/dom/html/HTMLSummaryElement.cpp
@@ -1,28 +1,46 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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 "mozilla/dom/HTMLSummaryElement.h"
 
+#include "mozilla/dom/HTMLDetailsElement.h"
 #include "mozilla/dom/HTMLElementBinding.h"
 
 NS_IMPL_NS_NEW_HTML_ELEMENT(Summary)
 
 namespace mozilla {
 namespace dom {
 
 HTMLSummaryElement::~HTMLSummaryElement()
 {
 }
 
 NS_IMPL_ELEMENT_CLONE(HTMLSummaryElement)
 
+bool
+HTMLSummaryElement::IsMainSummary() const
+{
+  HTMLDetailsElement* details = GetDetails();
+  if (!details) {
+    return false;
+  }
+
+  return details->GetFirstSummary() == this || IsRootOfNativeAnonymousSubtree();
+}
+
+HTMLDetailsElement*
+HTMLSummaryElement::GetDetails() const
+{
+  return HTMLDetailsElement::FromContentOrNull(GetParent());
+}
+
 JSObject*
 HTMLSummaryElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return HTMLElementBinding::Wrap(aCx, this, aGivenProto);
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/html/HTMLSummaryElement.h
+++ b/dom/html/HTMLSummaryElement.h
@@ -25,16 +25,24 @@ public:
     : nsGenericHTMLElement(aNodeInfo)
   {
   }
 
   NS_IMPL_FROMCONTENT_HTML_WITH_TAG(HTMLSummaryElement, summary)
 
   nsresult Clone(NodeInfo* aNodeInfo, nsINode** aResult) const override;
 
+  // Return true if this is the first summary element child of a details or the
+  // default summary element generated by DetailsFrame.
+  bool IsMainSummary() const;
+
+  // Return the details element which contains this summary. Otherwise return
+  // nullptr if there is no such details element.
+  HTMLDetailsElement* GetDetails() const;
+
 protected:
   virtual ~HTMLSummaryElement();
 
   JSObject* WrapNode(JSContext* aCx,
                      JS::Handle<JSObject*> aGivenProto) override;
 };
 
 } // namespace dom
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -8,17 +8,19 @@
  * construction of a frame tree that is nearly isomorphic to the content
  * tree and updating of that tree in response to dynamic changes
  */
 
 #include "nsCSSFrameConstructor.h"
 
 #include "mozilla/AutoRestore.h"
 #include "mozilla/DebugOnly.h"
+#include "mozilla/dom/HTMLDetailsElement.h"
 #include "mozilla/dom/HTMLSelectElement.h"
+#include "mozilla/dom/HTMLSummaryElement.h"
 #include "mozilla/EventStates.h"
 #include "mozilla/Likely.h"
 #include "mozilla/LinkedList.h"
 #include "nsAbsoluteContainingBlock.h"
 #include "nsIAtom.h"
 #include "nsIFrameInlines.h"
 #include "nsGkAtoms.h"
 #include "nsPresContext.h"
@@ -85,16 +87,18 @@
 #include "nsFirstLetterFrame.h"
 #include "nsGfxScrollFrame.h"
 #include "nsPageFrame.h"
 #include "nsSimplePageSequenceFrame.h"
 #include "nsTableOuterFrame.h"
 #include "nsIScrollableFrame.h"
 #include "nsBackdropFrame.h"
 #include "nsTransitionManager.h"
+#include "DetailsFrame.h"
+#include "SummaryFrame.h"
 
 #ifdef MOZ_XUL
 #include "nsIRootBox.h"
 #endif
 #ifdef ACCESSIBILITY
 #include "nsAccessibilityService.h"
 #endif
 
@@ -3289,16 +3293,68 @@ nsCSSFrameConstructor::ConstructFieldSet
   fieldsetFrame->SetInitialChildList(kPrincipalList, fieldsetKids);
 
   fieldsetFrame->AddStateBits(NS_FRAME_MAY_HAVE_GENERATED_CONTENT);
 
   // Our new frame returned is the outer frame, which is the fieldset frame.
   return fieldsetFrame;
 }
 
+nsIFrame*
+nsCSSFrameConstructor::ConstructDetailsFrame(nsFrameConstructorState& aState,
+                                             FrameConstructionItem& aItem,
+                                             nsContainerFrame* aParentFrame,
+                                             const nsStyleDisplay* aStyleDisplay,
+                                             nsFrameItems& aFrameItems)
+{
+  nsIContent* const content = aItem.mContent;
+  nsStyleContext* const styleContext = aItem.mStyleContext;
+  nsContainerFrame* geometricParent =
+    aState.GetGeometricParent(aStyleDisplay, aParentFrame);
+
+  nsContainerFrame* detailsFrame = NS_NewDetailsFrame(mPresShell, styleContext);
+  nsIFrame* frameToReturn = nullptr;
+
+  // Build a scroll frame to wrap details frame if necessary.
+  if (aStyleDisplay->IsScrollableOverflow()) {
+    nsContainerFrame* scrollFrame = nullptr;
+
+    RefPtr<nsStyleContext> detailsStyle =
+      BeginBuildingScrollFrame(aState, content, styleContext, geometricParent ,
+                               nsCSSAnonBoxes::scrolledContent, false,
+                               scrollFrame);
+
+    aState.AddChild(scrollFrame, aFrameItems, content, styleContext,
+                    aParentFrame);
+
+    nsFrameItems scrollFrameItems;
+    ConstructBlock(aState, content, scrollFrame, scrollFrame,
+                   detailsStyle, &detailsFrame, scrollFrameItems,
+                   aStyleDisplay->IsAbsPosContainingBlock(scrollFrame) ?
+                     scrollFrame : nullptr,
+                   aItem.mPendingBinding);
+
+    MOZ_ASSERT(scrollFrameItems.OnlyChild() == detailsFrame);
+
+    FinishBuildingScrollFrame(scrollFrame, detailsFrame);
+
+    frameToReturn = scrollFrame;
+  } else {
+    ConstructBlock(aState, content, geometricParent, aParentFrame, styleContext,
+                   &detailsFrame, aFrameItems,
+                   aStyleDisplay->IsAbsPosContainingBlock(detailsFrame) ?
+                     detailsFrame : nullptr,
+                   aItem.mPendingBinding);
+
+    frameToReturn = detailsFrame;
+  }
+
+  return frameToReturn;
+}
+
 static nsIFrame*
 FindAncestorWithGeneratedContentPseudo(nsIFrame* aFrame)
 {
   for (nsIFrame* f = aFrame->GetParent(); f; f = f->GetParent()) {
     NS_ASSERTION(f->IsGeneratedContentFrame(),
                  "should not have exited generated content");
     nsIAtom* pseudo = f->StyleContext()->GetPseudo();
     if (pseudo == nsCSSPseudoElements::before ||
@@ -3476,16 +3532,23 @@ nsCSSFrameConstructor::FindHTMLData(Elem
     // <legend> is only special inside fieldset, we only check the frame tree
     // parent because the content tree parent may not be a <fieldset> due to
     // display:contents, Shadow DOM, or XBL. For floated or absolutely
     // positioned legends we want to construct by display type and
     // not do special legend stuff.
     return nullptr;
   }
 
+  if (aTag == nsGkAtoms::summary &&
+      (!aParentFrame || aParentFrame->GetType() != nsGkAtoms::detailsFrame)) {
+    // <summary> is special only if it is a direct child of <details>. If it
+    // isn't, construct it as a normal block frame instead of a summary frame.
+    return nullptr;
+  }
+
   static const FrameConstructionDataByTag sHTMLData[] = {
     SIMPLE_TAG_CHAIN(img, nsCSSFrameConstructor::FindImgData),
     SIMPLE_TAG_CHAIN(mozgeneratedcontentimage,
                      nsCSSFrameConstructor::FindImgData),
     { &nsGkAtoms::br,
       FCDATA_DECL(FCDATA_IS_LINE_PARTICIPANT | FCDATA_IS_LINE_BREAK,
                   NS_NewBRFrame) },
     SIMPLE_TAG_CREATE(wbr, NS_NewWBRFrame),
@@ -3505,17 +3568,21 @@ nsCSSFrameConstructor::FindHTMLData(Elem
     { &nsGkAtoms::button,
       FCDATA_WITH_WRAPPING_BLOCK(FCDATA_ALLOW_BLOCK_STYLES,
                                  NS_NewHTMLButtonControlFrame,
                                  nsCSSAnonBoxes::buttonContent) },
     SIMPLE_TAG_CHAIN(canvas, nsCSSFrameConstructor::FindCanvasData),
     SIMPLE_TAG_CREATE(video, NS_NewHTMLVideoFrame),
     SIMPLE_TAG_CREATE(audio, NS_NewHTMLVideoFrame),
     SIMPLE_TAG_CREATE(progress, NS_NewProgressFrame),
-    SIMPLE_TAG_CREATE(meter, NS_NewMeterFrame)
+    SIMPLE_TAG_CREATE(meter, NS_NewMeterFrame),
+    COMPLEX_TAG_CREATE(details, &nsCSSFrameConstructor::ConstructDetailsFrame),
+    { &nsGkAtoms::summary,
+      FCDATA_DECL(FCDATA_ALLOW_BLOCK_STYLES | FCDATA_MAY_NEED_SCROLLFRAME,
+                  NS_NewSummaryFrame) }
   };
 
   return FindDataByTag(aTag, aElement, aStyleContext, sHTMLData,
                        ArrayLength(sHTMLData));
 }
 
 /* static */
 const nsCSSFrameConstructor::FrameConstructionData*
@@ -5560,16 +5627,28 @@ nsCSSFrameConstructor::AddFrameConstruct
       if (!isText) {
         SetAsUndisplayedContent(aState, aItems, aContent, styleContext,
                                 isGeneratedContent);
       }
       return;
     }
   }
 
+  // When constructing a child of a non-open <details>, create only the frame
+  // for the main <summary> element, and skip other elements.
+  auto* details = HTMLDetailsElement::FromContentOrNull(parent);
+  if (details && !details->Open()) {
+    auto* summary = HTMLSummaryElement::FromContentOrNull(aContent);
+    if (!summary || !summary->IsMainSummary()) {
+      SetAsUndisplayedContent(aState, aItems, aContent, styleContext,
+                              isGeneratedContent);
+      return;
+    }
+  }
+
   bool isPopup = false;
   // Try to find frame construction data for this content
   const FrameConstructionData* data;
   if (isText) {
     data = FindTextData(aParentFrame);
     if (!data) {
       // Nothing to do here; suppressed text inside SVG
       return;
@@ -7163,20 +7242,21 @@ nsCSSFrameConstructor::ContentAppended(n
     // to the trailing inline if it's empty; stop at the block.
     parentFrame = GetLastIBSplitSibling(parentFrame, false);
   }
 
   // Get continuation that parents the last child.  This MUST be done
   // before the AdjustAppendParentForAfterContent call.
   parentFrame = nsLayoutUtils::LastContinuationWithChild(parentFrame);
 
-  // We should never get here with fieldsets, since they have multiple
-  // insertion points.
-  NS_ASSERTION(parentFrame->GetType() != nsGkAtoms::fieldSetFrame,
-               "Unexpected parent");
+  // We should never get here with fieldsets or details, since they have
+  // multiple insertion points.
+  MOZ_ASSERT(parentFrame->GetType() != nsGkAtoms::fieldSetFrame &&
+             parentFrame->GetType() != nsGkAtoms::detailsFrame,
+             "Parent frame should not be fieldset or details!");
 
   // Deal with possible :after generated content on the parent
   nsIFrame* parentAfterFrame;
   parentFrame =
     ::AdjustAppendParentForAfterContent(this, insertion.mContainer, parentFrame,
                                         aFirstNewContent, &parentAfterFrame);
 
   // Create some new frames
@@ -7615,16 +7695,32 @@ nsCSSFrameConstructor::ContentRangeInser
     // to locate this legend in the inserted frames and extract it.
     LAYOUT_PHASE_TEMP_EXIT();
     nsresult rv = RecreateFramesForContent(insertion.mParentFrame->GetContent(), false,
                                            REMOVE_FOR_RECONSTRUCTION, nullptr);
     LAYOUT_PHASE_TEMP_REENTER();
     return rv;
   }
 
+  // We should only get here with details when doing a single insertion because
+  // we treat details frame as if it has multiple insertion points.
+  MOZ_ASSERT(isSingleInsert || frameType != nsGkAtoms::detailsFrame);
+  if (frameType == nsGkAtoms::detailsFrame) {
+    // When inserting an element into <details>, just reframe the details frame
+    // and let it figure out where the element should be laid out. It might seem
+    // expensive to recreate the entire details frame, but it's the simplest way
+    // to handle the insertion.
+    LAYOUT_PHASE_TEMP_EXIT();
+    nsresult rv =
+      RecreateFramesForContent(insertion.mParentFrame->GetContent(), false,
+                               REMOVE_FOR_RECONSTRUCTION, nullptr);
+    LAYOUT_PHASE_TEMP_REENTER();
+    return rv;
+  }
+
   // Don't construct kids of leaves
   if (insertion.mParentFrame->IsLeaf()) {
     // Clear lazy bits so we don't try to construct again.
     ClearLazyBits(aStartChild, aEndChild);
     return NS_OK;
   }
 
   if (insertion.mParentFrame->IsFrameOfType(nsIFrame::eMathML)) {
@@ -8774,16 +8870,22 @@ nsCSSFrameConstructor::CreateContinuingF
     newFrame = NS_NewRubyFrame(shell, styleContext);
     newFrame->Init(content, aParentFrame, aFrame);
   } else if (nsGkAtoms::rubyBaseContainerFrame == frameType) {
     newFrame = NS_NewRubyBaseContainerFrame(shell, styleContext);
     newFrame->Init(content, aParentFrame, aFrame);
   } else if (nsGkAtoms::rubyTextContainerFrame == frameType) {
     newFrame = NS_NewRubyTextContainerFrame(shell, styleContext);
     newFrame->Init(content, aParentFrame, aFrame);
+  } else if (nsGkAtoms::detailsFrame == frameType) {
+    newFrame = NS_NewDetailsFrame(shell, styleContext);
+    newFrame->Init(content, aParentFrame, aFrame);
+  } else if (nsGkAtoms::summaryFrame == frameType) {
+    newFrame = NS_NewSummaryFrame(shell, styleContext);
+    newFrame->Init(content, aParentFrame, aFrame);
   } else {
     NS_RUNTIMEABORT("unexpected frame type");
   }
 
   // Init() set newFrame to be a fluid continuation of aFrame.
   // If we want a non-fluid continuation, we need to call SetPrevContinuation()
   // to reset NS_FRAME_IS_FLUID_CONTINUATION.
   if (!aIsFluid) {
@@ -8944,16 +9046,23 @@ nsCSSFrameConstructor::GetInsertionPoint
 
   // Fieldset frames have multiple normal flow child frame lists so handle it
   // the same as if it had multiple content insertion points.
   if (insertion.mParentFrame &&
       insertion.mParentFrame->GetType() == nsGkAtoms::fieldSetFrame) {
     insertion.mMultiple = true;
   }
 
+  // A details frame moves the first summary frame to be its first child, so we
+  // treat it as if it has multiple content insertion points.
+  if (insertion.mParentFrame &&
+      insertion.mParentFrame->GetType() == nsGkAtoms::detailsFrame) {
+    insertion.mMultiple = true;
+  }
+
   return insertion;
 }
 
 // Capture state for the frame tree rooted at the frame associated with the
 // content object, aContent
 void
 nsCSSFrameConstructor::CaptureStateForFramesOf(nsIContent* aContent,
                                                nsILayoutHistoryState* aHistoryState)
@@ -9095,16 +9204,27 @@ nsCSSFrameConstructor::MaybeRecreateCont
       aFrame->GetParent()->GetType() == nsGkAtoms::fieldSetFrame) {
     // When we remove the legend for a fieldset, we should reframe
     // the fieldset to ensure another legend is used, if there is one
     *aResult = RecreateFramesForContent(aFrame->GetParent()->GetContent(), false,
                                         aFlags, aDestroyedFramesFor);
     return true;
   }
 
+  if (insertionFrame && insertionFrame->GetType() == nsGkAtoms::summaryFrame &&
+      aFrame->GetParent()->GetType() == nsGkAtoms::detailsFrame) {
+    // When removing a summary frame, we should reframe the parent details frame
+    // to ensure that another summary is used or the default summary is
+    // generated.
+    *aResult = RecreateFramesForContent(aFrame->GetParent()->GetContent(),
+                                        false, REMOVE_FOR_RECONSTRUCTION,
+                                        aDestroyedFramesFor);
+    return true;
+  }
+
   // Now check for possibly needing to reconstruct due to a pseudo parent
   nsIFrame* inFlowFrame =
     (aFrame->GetStateBits() & NS_FRAME_OUT_OF_FLOW) ?
       GetPlaceholderFrameFor(aFrame) : aFrame;
   MOZ_ASSERT(inFlowFrame, "How did that happen?");
   MOZ_ASSERT(inFlowFrame == inFlowFrame->FirstContinuation(),
              "placeholder for primary frame has previous continuations?");
   nsIFrame* parent = inFlowFrame->GetParent();
@@ -11414,17 +11534,19 @@ nsCSSFrameConstructor::ConstructBlock(ns
                                       nsStyleContext*          aStyleContext,
                                       nsContainerFrame**       aNewFrame,
                                       nsFrameItems&            aFrameItems,
                                       nsIFrame*                aPositionedFrameForAbsPosContainer,
                                       PendingBinding*          aPendingBinding)
 {
   // Create column wrapper if necessary
   nsContainerFrame* blockFrame = *aNewFrame;
-  NS_ASSERTION(blockFrame->GetType() == nsGkAtoms::blockFrame, "not a block frame?");
+  NS_ASSERTION((blockFrame->GetType() == nsGkAtoms::blockFrame ||
+                blockFrame->GetType() == nsGkAtoms::detailsFrame),
+               "not a block frame nor a details frame?");
   nsContainerFrame* parent = aParentFrame;
   RefPtr<nsStyleContext> blockStyle = aStyleContext;
   const nsStyleColumn* columns = aStyleContext->StyleColumn();
 
   if (columns->mColumnCount != NS_STYLE_COLUMN_COUNT_AUTO
       || columns->mColumnWidth.GetUnit() != eStyleUnit_Auto) {
     nsContainerFrame* columnSetFrame =
       NS_NewColumnSetFrame(mPresShell, aStyleContext, nsFrameState(0));
--- a/layout/base/nsCSSFrameConstructor.h
+++ b/layout/base/nsCSSFrameConstructor.h
@@ -1292,16 +1292,24 @@ private:
   // ConstructFieldSetFrame puts the new frame in aFrameItems and
   // handles the kids of the fieldset
   nsIFrame* ConstructFieldSetFrame(nsFrameConstructorState& aState,
                                    FrameConstructionItem&   aItem,
                                    nsContainerFrame*        aParentFrame,
                                    const nsStyleDisplay*    aStyleDisplay,
                                    nsFrameItems&            aFrameItems);
 
+  // ConstructDetailsFrame puts the new frame in aFrameItems and
+  // handles the kids of the details.
+  nsIFrame* ConstructDetailsFrame(nsFrameConstructorState& aState,
+                                  FrameConstructionItem& aItem,
+                                  nsContainerFrame* aParentFrame,
+                                  const nsStyleDisplay* aStyleDisplay,
+                                  nsFrameItems& aFrameItems);
+
   // aParentFrame might be null.  If it is, that means it was an
   // inline frame.
   static const FrameConstructionData* FindTextData(nsIFrame* aParentFrame);
 
   void ConstructTextFrame(const FrameConstructionData* aData,
                           nsFrameConstructorState& aState,
                           nsIContent*              aContent,
                           nsContainerFrame*        aParentFrame,
--- a/layout/generic/DetailsFrame.cpp
+++ b/layout/generic/DetailsFrame.cpp
@@ -36,8 +36,47 @@ DetailsFrame::~DetailsFrame()
 {
 }
 
 nsIAtom*
 DetailsFrame::GetType() const
 {
   return nsGkAtoms::detailsFrame;
 }
+
+void
+DetailsFrame::SetInitialChildList(ChildListID aListID, nsFrameList& aChildList)
+{
+  if (aListID == kPrincipalList) {
+    auto* details = HTMLDetailsElement::FromContent(GetContent());
+    bool isOpen = details->Open();
+
+    if (isOpen) {
+      // If details is open, the first summary needs to be rendered as if it is
+      // the first child.
+      for (nsIFrame* child : aChildList) {
+        auto* realFrame = nsPlaceholderFrame::GetRealFrameFor(child);
+        auto* cif = realFrame->GetContentInsertionFrame();
+        if (cif && cif->GetType() == nsGkAtoms::summaryFrame) {
+          // Take out the first summary frame and insert it to the beginning of
+          // the list.
+          aChildList.RemoveFrame(child);
+          aChildList.InsertFrame(nullptr, nullptr, child);
+          break;
+        }
+      }
+    }
+
+#ifdef DEBUG
+    nsIFrame* realFrame =
+      nsPlaceholderFrame::GetRealFrameFor(isOpen ?
+                                          aChildList.FirstChild() :
+                                          aChildList.OnlyChild());
+    MOZ_ASSERT(realFrame, "Principal list of details should not be empty!");
+    nsIFrame* summaryFrame = realFrame->GetContentInsertionFrame();
+    MOZ_ASSERT(summaryFrame->GetType() == nsGkAtoms::summaryFrame,
+               "The frame should be summary frame!");
+#endif
+
+  }
+
+  nsBlockFrame::SetInitialChildList(aListID, aChildList);
+}
--- a/layout/generic/DetailsFrame.h
+++ b/layout/generic/DetailsFrame.h
@@ -30,11 +30,14 @@ public:
   nsIAtom* GetType() const override;
 
 #ifdef DEBUG_FRAME_DUMP
   nsresult GetFrameName(nsAString& aResult) const override
   {
     return MakeFrameName(NS_LITERAL_STRING("Details"), aResult);
   }
 #endif
+
+  void SetInitialChildList(ChildListID aListID,
+                           nsFrameList& aChildList) override;
 };
 
 #endif // DetailsFrame_h
--- a/layout/generic/SummaryFrame.cpp
+++ b/layout/generic/SummaryFrame.cpp
@@ -25,8 +25,26 @@ SummaryFrame::~SummaryFrame()
 {
 }
 
 nsIAtom*
 SummaryFrame::GetType() const
 {
   return nsGkAtoms::summaryFrame;
 }
+
+void
+SummaryFrame::SetInitialChildList(ChildListID aListID, nsFrameList& aChildList)
+{
+  nsBlockFrame::SetInitialChildList(aListID, aChildList);
+
+  // Construct the disclosure triangle if it's the main summary. We leverage the
+  // list-item property and nsBulletFrame to draw the triangle. Need to set
+  // list-style-type for :moz-list-bullet in html.css.
+  // TODO: Bug 1221416 for styling the disclosure triangle.
+  if (aListID == kPrincipalList) {
+    auto* summary = HTMLSummaryElement::FromContent(GetContent());
+    if (summary->IsMainSummary() &&
+        StyleDisplay()->mDisplay != NS_STYLE_DISPLAY_LIST_ITEM) {
+      CreateBulletFrameForListItem(true, true);
+    }
+  }
+}
--- a/layout/generic/SummaryFrame.h
+++ b/layout/generic/SummaryFrame.h
@@ -25,11 +25,14 @@ public:
   nsIAtom* GetType() const override;
 
 #ifdef DEBUG_FRAME_DUMP
   nsresult GetFrameName(nsAString& aResult) const override
   {
     return MakeFrameName(NS_LITERAL_STRING("Summary"), aResult);
   }
 #endif
+
+  void SetInitialChildList(ChildListID aListID,
+                           nsFrameList& aChildList) override;
 };
 
 #endif // SummaryFrame_h
--- a/layout/generic/nsBlockFrame.cpp
+++ b/layout/generic/nsBlockFrame.cpp
@@ -6821,17 +6821,16 @@ nsBlockFrame::SetInitialChildList(ChildL
                                   nsFrameList&    aChildList)
 {
   if (kFloatList == aListID) {
     mFloats.SetFrames(aChildList);
   } else if (kPrincipalList == aListID) {
     NS_ASSERTION((GetStateBits() & (NS_BLOCK_FRAME_HAS_INSIDE_BULLET |
                                     NS_BLOCK_FRAME_HAS_OUTSIDE_BULLET)) == 0,
                  "how can we have a bullet already?");
-    nsPresContext* presContext = PresContext();
 
 #ifdef DEBUG
     // The only times a block that is an anonymous box is allowed to have a
     // first-letter frame are when it's the block inside a non-anonymous cell,
     // the block inside a fieldset, button or column set, or a scrolled content
     // block, except for <select>.  Note that this means that blocks which are
     // the anonymous block in {ib} splits do NOT get first-letter frames.
     // Note that NS_BLOCK_HAS_FIRST_LETTER_STYLE gets set on all continuations
@@ -6844,22 +6843,22 @@ nsBlockFrame::SetInitialChildList(ChildL
        pseudo == nsCSSAnonBoxes::fieldsetContent ||
        pseudo == nsCSSAnonBoxes::buttonContent ||
        pseudo == nsCSSAnonBoxes::columnContent ||
        (pseudo == nsCSSAnonBoxes::scrolledContent &&
         GetParent()->GetType() != nsGkAtoms::listControlFrame) ||
        pseudo == nsCSSAnonBoxes::mozSVGText) &&
       GetType() != nsGkAtoms::comboboxControlFrame &&
       !IsFrameOfType(eMathML) &&
-      RefPtr<nsStyleContext>(GetFirstLetterStyle(presContext)) != nullptr;
+      RefPtr<nsStyleContext>(GetFirstLetterStyle(PresContext())) != nullptr;
     NS_ASSERTION(haveFirstLetterStyle ==
                  ((mState & NS_BLOCK_HAS_FIRST_LETTER_STYLE) != 0),
                  "NS_BLOCK_HAS_FIRST_LETTER_STYLE state out of sync");
 #endif
-    
+
     AddFrames(aChildList, nullptr);
 
     // Create a list bullet if this is a list-item. Note that this is
     // done here so that RenumberLists will work (it needs the bullets
     // to store the bullet numbers).  Also note that due to various
     // wrapper frames (scrollframes, columns) we want to use the
     // outermost (primary, ideally, but it's not set yet when we get
     // here) frame of our content for the display check.  On the other
@@ -6877,52 +6876,64 @@ nsBlockFrame::SetInitialChildList(ChildL
       possibleListItem = parent;
     }
     if (NS_STYLE_DISPLAY_LIST_ITEM ==
           possibleListItem->StyleDisplay()->mDisplay &&
         !GetPrevInFlow()) {
       // Resolve style for the bullet frame
       const nsStyleList* styleList = StyleList();
       CounterStyle* style = styleList->GetCounterStyle();
-      nsCSSPseudoElements::Type pseudoType = style->IsBullet() ?
-        nsCSSPseudoElements::ePseudo_mozListBullet :
-        nsCSSPseudoElements::ePseudo_mozListNumber;
-
-      nsIPresShell *shell = presContext->PresShell();
-
-      nsStyleContext* parentStyle =
-        CorrectStyleParentFrame(this,
-          nsCSSPseudoElements::GetPseudoAtom(pseudoType))->StyleContext();
-      RefPtr<nsStyleContext> kidSC = shell->StyleSet()->
-        ResolvePseudoElementStyle(mContent->AsElement(), pseudoType,
-                                  parentStyle, nullptr);
-
-      // Create bullet frame
-      nsBulletFrame* bullet = new (shell) nsBulletFrame(kidSC);
-      bullet->Init(mContent, this, nullptr);
-
-      // If the list bullet frame should be positioned inside then add
-      // it to the flow now.
-      if (NS_STYLE_LIST_STYLE_POSITION_INSIDE ==
-            styleList->mListStylePosition) {
-        nsFrameList bulletList(bullet, bullet);
-        AddFrames(bulletList, nullptr);
-        Properties().Set(InsideBulletProperty(), bullet);
-        AddStateBits(NS_BLOCK_FRAME_HAS_INSIDE_BULLET);
-      } else {
-        nsFrameList* bulletList = new (shell) nsFrameList(bullet, bullet);
-        Properties().Set(OutsideBulletProperty(), bulletList);
-        AddStateBits(NS_BLOCK_FRAME_HAS_OUTSIDE_BULLET);
-      }
+
+      CreateBulletFrameForListItem(
+        style->IsBullet(),
+        styleList->mListStylePosition == NS_STYLE_LIST_STYLE_POSITION_INSIDE);
     }
   } else {
     nsContainerFrame::SetInitialChildList(aListID, aChildList);
   }
 }
 
+void
+nsBlockFrame::CreateBulletFrameForListItem(bool aCreateBulletList,
+                                           bool aListStylePositionInside)
+{
+  nsIPresShell* shell = PresContext()->PresShell();
+
+  nsCSSPseudoElements::Type pseudoType = aCreateBulletList ?
+    nsCSSPseudoElements::ePseudo_mozListBullet :
+    nsCSSPseudoElements::ePseudo_mozListNumber;
+
+  nsStyleContext* parentStyle =
+    CorrectStyleParentFrame(this,
+                            nsCSSPseudoElements::GetPseudoAtom(pseudoType))->
+    StyleContext();
+
+  RefPtr<nsStyleContext> kidSC = shell->StyleSet()->
+    ResolvePseudoElementStyle(mContent->AsElement(), pseudoType,
+                              parentStyle, nullptr);
+
+  // Create bullet frame
+  nsBulletFrame* bullet = new (shell) nsBulletFrame(kidSC);
+  bullet->Init(mContent, this, nullptr);
+
+  // If the list bullet frame should be positioned inside then add
+  // it to the flow now.
+  if (aListStylePositionInside) {
+
+    nsFrameList bulletList(bullet, bullet);
+    AddFrames(bulletList, nullptr);
+    Properties().Set(InsideBulletProperty(), bullet);
+    AddStateBits(NS_BLOCK_FRAME_HAS_INSIDE_BULLET);
+  } else {
+    nsFrameList* bulletList = new (shell) nsFrameList(bullet, bullet);
+    Properties().Set(OutsideBulletProperty(), bulletList);
+    AddStateBits(NS_BLOCK_FRAME_HAS_OUTSIDE_BULLET);
+  }
+}
+
 bool
 nsBlockFrame::BulletIsEmpty() const
 {
   NS_ASSERTION(mContent->GetPrimaryFrame()->StyleDisplay()->mDisplay ==
                  NS_STYLE_DISPLAY_LIST_ITEM && HasOutsideBullet(),
                "should only care when we have an outside bullet");
   const nsStyleList* list = StyleList();
   return list->GetCounterStyle()->IsNone() &&
--- a/layout/generic/nsBlockFrame.h
+++ b/layout/generic/nsBlockFrame.h
@@ -449,16 +449,25 @@ protected:
    * This is necessary for backwards-compatibility, because most visual
    * pages use logical order for form controls so that they will
    * display correctly on native widgets in OSs with Bidi support
    * @param aPresContext the pres context
    * @return whether the frame is a BIDI form control
    */
   bool IsVisualFormControl(nsPresContext* aPresContext);
 
+  /**
+   * Helper function to create bullet frame.
+   * @param aCreateBulletList true to create bullet list; otherwise number list.
+   * @param aListStylePositionInside true to put the list position inside;
+   * otherwise outside.
+   */
+  void CreateBulletFrameForListItem(bool aCreateBulletList,
+                                    bool aListStylePositionInside);
+
 public:
   /**
    * Does all the real work for removing aDeletedFrame
    * -- finds the line containing aDeletedFrame
    * -- removes all aDeletedFrame next-in-flows (or all continuations,
    * if REMOVE_FIXED_CONTINUATIONS is given)
    * -- marks lines dirty as needed
    * -- marks textruns dirty (unless FRAMES_ARE_EMPTY is given, in which
--- a/layout/style/html.css
+++ b/layout/style/html.css
@@ -95,27 +95,29 @@ bdo[dir="auto"] {
   unicode-bidi: -moz-isolate-override;
 }
 textarea[dir="auto"], pre[dir="auto"] { unicode-bidi: -moz-plaintext; }
 
 /* blocks */
 
 article,
 aside,
+details,
 div,
 dt,
 figcaption,
 footer,
 form,
 header,
 hgroup,
 html,
 main,
 nav,
-section {
+section,
+summary {
   display: block;
 }
 
 body {
   display: block;
   margin: 8px;
 }
 
@@ -762,16 +764,28 @@ audio:not([controls]) {
   transform: translate(0) !important;
 }
 
 video > .caption-box {
   position: relative;
   overflow: hidden;
 }
 
+/* details & summary */
+
+details > summary::-moz-list-bullet {
+  list-style-type: disclosure-closed;
+  /* Prevent elements in summary being selected when clicking on the triangle. */
+  -moz-user-select: none;
+}
+
+details[open] > summary::-moz-list-bullet {
+  list-style-type: disclosure-open;
+}
+
 /* emulation of non-standard HTML <marquee> tag */
 marquee {
   inline-size: -moz-available;
   display: inline-block;
   vertical-align: text-bottom;
   text-align: start;
   -moz-binding: url('chrome://xbl-marquee/content/xbl-marquee.xml#marquee-horizontal');
 }