blob: fcb1604f87850edb8a7118bf81d40ae56c6e10eb [file] [log] [blame]
/*
* Copyright (C) 2010, 2011 Nokia Corporation and/or its subsidiary(-ies)
* Copyright (C) 2018 Apple Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
*/
#include "config.h"
#include "HTMLDetailsElement.h"
#include "AXObjectCache.h"
#include "ContainerNodeInlines.h"
#include "DocumentInlines.h"
#include "ElementChildIteratorInlines.h"
#include "ElementRareData.h"
#include "EventLoop.h"
#include "EventNames.h"
#include "HTMLSlotElement.h"
#include "HTMLStyleElement.h"
#include "HTMLSummaryElement.h"
#include "LocalizedStrings.h"
#include "MouseEvent.h"
#include "ShadowRoot.h"
#include "ShouldNotFireMutationEventsScope.h"
#include "SlotAssignment.h"
#include "Text.h"
#include "ToggleEvent.h"
#include "ToggleEventTask.h"
#include "TypedElementDescendantIteratorInlines.h"
#include "UserAgentParts.h"
#include "UserAgentStyle.h"
#include "UserAgentStyleSheets.h"
#include <wtf/NeverDestroyed.h>
#include <wtf/TZoneMallocInlines.h>
namespace WebCore {
WTF_MAKE_TZONE_OR_ISO_ALLOCATED_IMPL(HTMLDetailsElement);
using namespace HTMLNames;
static const AtomString& summarySlotName()
{
static MainThreadNeverDestroyed<const AtomString> summarySlot("summarySlot"_s);
return summarySlot;
}
class DetailsSlotAssignment final : public NamedSlotAssignment {
private:
void hostChildElementDidChange(const Element&, ShadowRoot&) override;
const AtomString& slotNameForHostChild(const Node&) const override;
};
void DetailsSlotAssignment::hostChildElementDidChange(const Element& childElement, ShadowRoot& shadowRoot)
{
if (is<HTMLSummaryElement>(childElement)) {
// Don't check whether this is the first summary element
// since we don't know the answer when this function is called inside Element::removedFrom.
didChangeSlot(summarySlotName(), shadowRoot);
if (RefPtr associatedDetails = dynamicDowncast<HTMLDetailsElement>(shadowRoot.host())) {
if (CheckedPtr cache = associatedDetails->protectedDocument()->existingAXObjectCache())
cache->onDetailsSummarySlotChange(*associatedDetails);
}
} else
didChangeSlot(NamedSlotAssignment::defaultSlotName(), shadowRoot);
}
const AtomString& DetailsSlotAssignment::slotNameForHostChild(const Node& child) const
{
Ref details = downcast<HTMLDetailsElement>(*child.parentNode());
// The first summary child gets assigned to the summary slot.
if (is<HTMLSummaryElement>(child)) {
if (&child == childrenOfType<HTMLSummaryElement>(details).first())
return summarySlotName();
}
return NamedSlotAssignment::defaultSlotName();
}
Ref<HTMLDetailsElement> HTMLDetailsElement::create(const QualifiedName& tagName, Document& document)
{
auto details = adoptRef(*new HTMLDetailsElement(tagName, document));
details->addShadowRoot(ShadowRoot::create(document, makeUnique<DetailsSlotAssignment>()));
return details;
}
HTMLDetailsElement::HTMLDetailsElement(const QualifiedName& tagName, Document& document)
: HTMLElement(tagName, document)
{
ASSERT(hasTagName(detailsTag));
}
HTMLDetailsElement::~HTMLDetailsElement() = default;
void HTMLDetailsElement::didAddUserAgentShadowRoot(ShadowRoot& root)
{
Ref document = this->document();
Ref summarySlot = HTMLSlotElement::create(slotTag, document);
summarySlot->setAttributeWithoutSynchronization(nameAttr, summarySlotName());
Ref defaultSummary = HTMLSummaryElement::create(summaryTag, document);
defaultSummary->appendChild(Text::create(document, defaultDetailsSummaryText()));
m_defaultSummary = defaultSummary.get();
summarySlot->appendChild(defaultSummary);
root.appendChild(summarySlot);
m_summarySlot = WTFMove(summarySlot);
Ref defaultSlot = HTMLSlotElement::create(slotTag, document);
defaultSlot->setUserAgentPart(UserAgentParts::detailsContent());
ASSERT(!hasAttributeWithoutSynchronization(openAttr));
defaultSlot->setInlineStyleProperty(CSSPropertyContentVisibility, CSSValueHidden);
defaultSlot->setInlineStyleProperty(CSSPropertyDisplay, CSSValueBlock);
root.appendChild(defaultSlot);
m_defaultSlot = WTFMove(defaultSlot);
static MainThreadNeverDestroyed<const String> stylesheet(StringImpl::createWithoutCopying(detailsElementShadowUserAgentStyleSheet));
Ref style = HTMLStyleElement::create(HTMLNames::styleTag, document, false);
style->setTextContent(String { stylesheet });
root.appendChild(WTFMove(style));
}
bool HTMLDetailsElement::isActiveSummary(const HTMLSummaryElement& summary) const
{
RefPtr summarySlot = m_summarySlot.get();
if (!summarySlot->assignedNodes())
return &summary == m_defaultSummary;
if (summary.parentNode() != this)
return false;
RefPtr slot = protectedShadowRoot()->findAssignedSlot(summary);
return slot && slot == summarySlot.get();
}
void HTMLDetailsElement::queueDetailsToggleEventTask(ToggleState oldState, ToggleState newState)
{
if (!m_toggleEventTask)
m_toggleEventTask = ToggleEventTask::create(*this);
RefPtr { m_toggleEventTask }->queue(oldState, newState);
}
void HTMLDetailsElement::attributeChanged(const QualifiedName& name, const AtomString& oldValue, const AtomString& newValue, AttributeModificationReason attributeModificationReason)
{
HTMLElement::attributeChanged(name, oldValue, newValue, attributeModificationReason);
if (name == openAttr) {
if (oldValue != newValue) {
RefPtr root = shadowRoot();
RefPtr defaultSlot = m_defaultSlot;
ASSERT(root);
if (!newValue.isNull()) {
defaultSlot->removeInlineStyleProperty(CSSPropertyContentVisibility);
queueDetailsToggleEventTask(ToggleState::Closed, ToggleState::Open);
if (!attributeWithoutSynchronization(nameAttr).isEmpty()) {
ShouldNotFireMutationEventsScope scope(document());
for (auto& otherDetailsElement : otherElementsInNameGroup())
otherDetailsElement->removeAttribute(openAttr);
}
} else {
defaultSlot->setInlineStyleProperty(CSSPropertyContentVisibility, CSSValueHidden);
queueDetailsToggleEventTask(ToggleState::Open, ToggleState::Closed);
}
}
} else
ensureDetailsExclusivityAfterMutation();
}
Node::InsertedIntoAncestorResult HTMLDetailsElement::insertedIntoAncestor(InsertionType insertionType, ContainerNode& parentOfInsertedTree)
{
HTMLElement::insertedIntoAncestor(insertionType, parentOfInsertedTree);
if (!insertionType.connectedToDocument)
return InsertedIntoAncestorResult::Done;
return InsertedIntoAncestorResult::NeedsPostInsertionCallback;
}
void HTMLDetailsElement::didFinishInsertingNode()
{
ensureDetailsExclusivityAfterMutation();
}
Vector<RefPtr<HTMLDetailsElement>> HTMLDetailsElement::otherElementsInNameGroup()
{
Vector<RefPtr<HTMLDetailsElement>> otherElementsInNameGroup;
const auto& detailElementName = attributeWithoutSynchronization(nameAttr);
for (Ref element : descendantsOfType<HTMLDetailsElement>(rootNode())) {
if (element.ptr() != this && element->attributeWithoutSynchronization(nameAttr) == detailElementName)
otherElementsInNameGroup.append(WTFMove(element));
}
return otherElementsInNameGroup;
}
void HTMLDetailsElement::ensureDetailsExclusivityAfterMutation()
{
if (hasAttributeWithoutSynchronization(openAttr) && !attributeWithoutSynchronization(nameAttr).isEmpty()) {
ShouldNotFireMutationEventsScope scope(document());
for (auto& otherDetailsElement : otherElementsInNameGroup()) {
if (otherDetailsElement->hasAttributeWithoutSynchronization(openAttr)) {
toggleOpen();
break;
}
}
}
}
void HTMLDetailsElement::toggleOpen()
{
setBooleanAttribute(HTMLNames::openAttr, !hasAttributeWithoutSynchronization(HTMLNames::openAttr));
}
} // namespace WebCore