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