/* * 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 "ElementIterator.h" #include "EventLoop.h" #include "EventNames.h" #include "HTMLSlotElement.h" #include "HTMLSummaryElement.h" #include "LocalizedStrings.h" #include "MouseEvent.h" #include "RenderBlockFlow.h" #include "ShadowRoot.h" #include "SlotAssignment.h" #include "Text.h" #include #include namespace WebCore { WTF_MAKE_ISO_ALLOCATED_IMPL(HTMLDetailsElement); using namespace HTMLNames; static const AtomString& summarySlotName() { static MainThreadNeverDestroyed summarySlot("summarySlot"); return summarySlot; } class DetailsSlotAssignment final : public SlotAssignment { private: void hostChildElementDidChange(const Element&, ShadowRoot&) override; const AtomString& slotNameForHostChild(const Node&) const override; }; void DetailsSlotAssignment::hostChildElementDidChange(const Element& childElement, ShadowRoot& shadowRoot) { if (is(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); } else didChangeSlot(SlotAssignment::defaultSlotName(), shadowRoot); } const AtomString& DetailsSlotAssignment::slotNameForHostChild(const Node& child) const { auto& parent = *child.parentNode(); ASSERT(is(parent)); auto& details = downcast(parent); // The first summary child gets assigned to the summary slot. if (is(child)) { if (&child == childrenOfType(details).first()) return summarySlotName(); } return SlotAssignment::defaultSlotName(); } Ref HTMLDetailsElement::create(const QualifiedName& tagName, Document& document) { auto details = adoptRef(*new HTMLDetailsElement(tagName, document)); details->addShadowRoot(ShadowRoot::create(document, makeUnique())); return details; } HTMLDetailsElement::HTMLDetailsElement(const QualifiedName& tagName, Document& document) : HTMLElement(tagName, document) { ASSERT(hasTagName(detailsTag)); } RenderPtr HTMLDetailsElement::createElementRenderer(RenderStyle&& style, const RenderTreePosition&) { return createRenderer(*this, WTFMove(style)); } void HTMLDetailsElement::didAddUserAgentShadowRoot(ShadowRoot& root) { auto summarySlot = HTMLSlotElement::create(slotTag, document()); summarySlot->setAttributeWithoutSynchronization(nameAttr, summarySlotName()); m_summarySlot = summarySlot.ptr(); auto defaultSummary = HTMLSummaryElement::create(summaryTag, document()); defaultSummary->appendChild(Text::create(document(), defaultDetailsSummaryText())); m_defaultSummary = defaultSummary.ptr(); summarySlot->appendChild(defaultSummary); root.appendChild(summarySlot); m_defaultSlot = HTMLSlotElement::create(slotTag, document()); ASSERT(!m_isOpen); } bool HTMLDetailsElement::isActiveSummary(const HTMLSummaryElement& summary) const { if (!m_summarySlot->assignedNodes()) return &summary == m_defaultSummary; if (summary.parentNode() != this) return false; auto slot = makeRefPtr(shadowRoot()->findAssignedSlot(summary)); if (!slot) return false; return slot == m_summarySlot; } void HTMLDetailsElement::parseAttribute(const QualifiedName& name, const AtomString& value) { if (name == openAttr) { bool oldValue = m_isOpen; m_isOpen = !value.isNull(); if (oldValue != m_isOpen) { auto root = makeRefPtr(shadowRoot()); ASSERT(root); if (m_isOpen) root->appendChild(*m_defaultSlot); else root->removeChild(*m_defaultSlot); // https://html.spec.whatwg.org/#details-notification-task-steps if (m_isToggleEventTaskQueued) return; document().eventLoop().queueTask(TaskSource::DOMManipulation, [protectedThis = GCReachableRef { *this }] { protectedThis->dispatchEvent(Event::create(eventNames().toggleEvent, Event::CanBubble::No, Event::IsCancelable::No)); protectedThis->m_isToggleEventTaskQueued = false; }); m_isToggleEventTaskQueued = true; } } else HTMLElement::parseAttribute(name, value); } void HTMLDetailsElement::toggleOpen() { setBooleanAttribute(openAttr, !m_isOpen); // We need to post to the document because toggling this element will delete it. if (AXObjectCache* cache = document().existingAXObjectCache()) cache->postNotification(nullptr, &document(), AXObjectCache::AXExpandedChanged); } }