484bcb76c1e0012055b78748cc295e5665924781
[WebKit-https.git] / Source / WebCore / html / HTMLDetailsElement.cpp
1 /*
2  * Copyright (C) 2010, 2011 Nokia Corporation and/or its subsidiary(-ies)
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public License
15  * along with this library; see the file COPYING.LIB.  If not, write to
16  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  *
19  */
20
21 #include "config.h"
22 #include "HTMLDetailsElement.h"
23
24 #include "AXObjectCache.h"
25 #include "ElementIterator.h"
26 #include "EventNames.h"
27 #include "EventSender.h"
28 #include "HTMLSlotElement.h"
29 #include "HTMLSummaryElement.h"
30 #include "LocalizedStrings.h"
31 #include "MouseEvent.h"
32 #include "RenderBlockFlow.h"
33 #include "ShadowRoot.h"
34 #include "SlotAssignment.h"
35 #include "Text.h"
36 #include <wtf/NeverDestroyed.h>
37
38 namespace WebCore {
39
40 using namespace HTMLNames;
41
42 static DetailEventSender& detailToggleEventSender()
43 {
44     static NeverDestroyed<DetailEventSender> sharedToggleEventSender(eventNames().toggleEvent);
45     return sharedToggleEventSender;
46 }
47
48 static const AtomicString& summarySlotName()
49 {
50     static NeverDestroyed<AtomicString> summarySlot("summarySlot");
51     return summarySlot;
52 }
53
54 class DetailsSlotAssignment final : public SlotAssignment {
55 private:
56     void hostChildElementDidChange(const Element&, ShadowRoot&) override;
57     const AtomicString& slotNameForHostChild(const Node&) const override;
58 };
59
60 void DetailsSlotAssignment::hostChildElementDidChange(const Element& childElement, ShadowRoot& shadowRoot)
61 {
62     if (is<HTMLSummaryElement>(childElement)) {
63         // Don't check whether this is the first summary element
64         // since we don't know the answer when this function is called inside Element::removedFrom.
65         didChangeSlot(summarySlotName(), shadowRoot);
66     } else
67         didChangeSlot(SlotAssignment::defaultSlotName(), shadowRoot);
68 }
69
70 const AtomicString& DetailsSlotAssignment::slotNameForHostChild(const Node& child) const
71 {
72     auto& parent = *child.parentNode();
73     ASSERT(is<HTMLDetailsElement>(parent));
74     auto& details = downcast<HTMLDetailsElement>(parent);
75
76     // The first summary child gets assigned to the summary slot.
77     if (is<HTMLSummaryElement>(child)) {
78         if (&child == childrenOfType<HTMLSummaryElement>(details).first())
79             return summarySlotName();
80     }
81     return SlotAssignment::defaultSlotName();
82 }
83
84 Ref<HTMLDetailsElement> HTMLDetailsElement::create(const QualifiedName& tagName, Document& document)
85 {
86     auto details = adoptRef(*new HTMLDetailsElement(tagName, document));
87     details->addShadowRoot(ShadowRoot::create(document, std::make_unique<DetailsSlotAssignment>()));
88     return details;
89 }
90
91 HTMLDetailsElement::HTMLDetailsElement(const QualifiedName& tagName, Document& document)
92     : HTMLElement(tagName, document)
93 {
94     ASSERT(hasTagName(detailsTag));
95 }
96
97 HTMLDetailsElement::~HTMLDetailsElement()
98 {
99     detailToggleEventSender().cancelEvent(*this);
100 }
101
102 RenderPtr<RenderElement> HTMLDetailsElement::createElementRenderer(RenderStyle&& style, const RenderTreePosition&)
103 {
104     return createRenderer<RenderBlockFlow>(*this, WTFMove(style));
105 }
106
107 void HTMLDetailsElement::didAddUserAgentShadowRoot(ShadowRoot& root)
108 {
109     auto summarySlot = HTMLSlotElement::create(slotTag, document());
110     summarySlot->setAttributeWithoutSynchronization(nameAttr, summarySlotName());
111     m_summarySlot = summarySlot.ptr();
112
113     auto defaultSummary = HTMLSummaryElement::create(summaryTag, document());
114     defaultSummary->appendChild(Text::create(document(), defaultDetailsSummaryText()));
115     m_defaultSummary = defaultSummary.ptr();
116
117     summarySlot->appendChild(defaultSummary);
118     root.appendChild(summarySlot);
119
120     m_defaultSlot = HTMLSlotElement::create(slotTag, document());
121     ASSERT(!m_isOpen);
122 }
123
124 bool HTMLDetailsElement::isActiveSummary(const HTMLSummaryElement& summary) const
125 {
126     if (!m_summarySlot->assignedNodes())
127         return &summary == m_defaultSummary;
128
129     if (summary.parentNode() != this)
130         return false;
131
132     auto* slot = shadowRoot()->findAssignedSlot(summary);
133     if (!slot)
134         return false;
135     return slot == m_summarySlot;
136 }
137
138 void HTMLDetailsElement::dispatchPendingEvent(DetailEventSender* eventSender)
139 {
140     ASSERT_UNUSED(eventSender, eventSender == &detailToggleEventSender());
141     dispatchEvent(Event::create(eventNames().toggleEvent, false, false));
142 }
143
144 void HTMLDetailsElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
145 {
146     if (name == openAttr) {
147         bool oldValue = m_isOpen;
148         m_isOpen = !value.isNull();
149         if (oldValue != m_isOpen) {
150             auto* root = shadowRoot();
151             ASSERT(root);
152             if (m_isOpen)
153                 root->appendChild(*m_defaultSlot);
154             else
155                 root->removeChild(*m_defaultSlot);
156
157             // https://html.spec.whatwg.org/#details-notification-task-steps.
158             detailToggleEventSender().cancelEvent(*this);
159             detailToggleEventSender().dispatchEventSoon(*this);
160         }
161     } else
162         HTMLElement::parseAttribute(name, value);
163 }
164
165
166 void HTMLDetailsElement::toggleOpen()
167 {
168     setAttributeWithoutSynchronization(openAttr, m_isOpen ? nullAtom() : emptyAtom());
169
170     // We need to post to the document because toggling this element will delete it.
171     if (AXObjectCache* cache = document().existingAXObjectCache())
172         cache->postNotification(nullptr, &document(), AXObjectCache::AXExpandedChanged);
173 }
174
175 }