HTMLDetailsElement.cpp (8014B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "mozilla/dom/HTMLDetailsElement.h" 8 9 #include "mozilla/BuiltInStyleSheets.h" 10 #include "mozilla/StaticPrefs_dom.h" 11 #include "mozilla/dom/HTMLDetailsElementBinding.h" 12 #include "mozilla/dom/HTMLSummaryElement.h" 13 #include "mozilla/dom/ShadowRoot.h" 14 #include "nsContentUtils.h" 15 #include "nsTextNode.h" 16 17 NS_IMPL_NS_NEW_HTML_ELEMENT(Details) 18 19 namespace mozilla::dom { 20 21 HTMLDetailsElement::~HTMLDetailsElement() = default; 22 23 NS_IMPL_ELEMENT_CLONE(HTMLDetailsElement) 24 25 HTMLDetailsElement::HTMLDetailsElement(already_AddRefed<NodeInfo>&& aNodeInfo) 26 : nsGenericHTMLElement(std::move(aNodeInfo)) { 27 SetupShadowTree(); 28 } 29 30 HTMLSummaryElement* HTMLDetailsElement::GetFirstSummary() const { 31 // XXX: Bug 1245032: Might want to cache the first summary element. 32 for (nsIContent* child = nsINode::GetFirstChild(); child; 33 child = child->GetNextSibling()) { 34 if (auto* summary = HTMLSummaryElement::FromNode(child)) { 35 return summary; 36 } 37 } 38 return nullptr; 39 } 40 41 void HTMLDetailsElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName, 42 const nsAttrValue* aValue, 43 const nsAttrValue* aOldValue, 44 nsIPrincipal* aMaybeScriptedPrincipal, 45 bool aNotify) { 46 if (aNameSpaceID == kNameSpaceID_None) { 47 if (aName == nsGkAtoms::open) { 48 bool wasOpen = !!aOldValue; 49 bool isOpen = !!aValue; 50 if (wasOpen != isOpen) { 51 auto stringForState = [](bool aOpen) { 52 return aOpen ? u"open"_ns : u"closed"_ns; 53 }; 54 nsAutoString oldState; 55 if (mToggleEventDispatcher) { 56 oldState.Truncate(); 57 static_cast<ToggleEvent*>(mToggleEventDispatcher->mEvent.get()) 58 ->GetOldState(oldState); 59 mToggleEventDispatcher->Cancel(); 60 } else { 61 oldState.Assign(stringForState(wasOpen)); 62 } 63 RefPtr<ToggleEvent> toggleEvent = 64 CreateToggleEvent(u"toggle"_ns, oldState, stringForState(isOpen), 65 Cancelable::eNo, nullptr); 66 mToggleEventDispatcher = 67 new AsyncEventDispatcher(this, toggleEvent.forget()); 68 mToggleEventDispatcher->PostDOMEvent(); 69 70 if (isOpen) { 71 CloseOtherElementsIfNeeded(); 72 } 73 SetStates(ElementState::OPEN, isOpen); 74 } 75 } else if (aName == nsGkAtoms::name) { 76 CloseElementIfNeeded(); 77 } 78 } 79 80 return nsGenericHTMLElement::AfterSetAttr( 81 aNameSpaceID, aName, aValue, aOldValue, aMaybeScriptedPrincipal, aNotify); 82 } 83 84 nsresult HTMLDetailsElement::BindToTree(BindContext& aContext, 85 nsINode& aParent) { 86 nsresult rv = nsGenericHTMLElement::BindToTree(aContext, aParent); 87 NS_ENSURE_SUCCESS(rv, rv); 88 89 CloseElementIfNeeded(); 90 91 return NS_OK; 92 } 93 94 void HTMLDetailsElement::SetupShadowTree() { 95 const bool kNotify = false; 96 AttachAndSetUAShadowRoot(NotifyUAWidgetSetup::No); 97 RefPtr<ShadowRoot> sr = GetShadowRoot(); 98 if (NS_WARN_IF(!sr)) { 99 return; 100 } 101 102 nsNodeInfoManager* nim = OwnerDoc()->NodeInfoManager(); 103 RefPtr<NodeInfo> slotNodeInfo = nim->GetNodeInfo( 104 nsGkAtoms::slot, nullptr, kNameSpaceID_XHTML, nsINode::ELEMENT_NODE); 105 sr->AppendBuiltInStyleSheet(BuiltInStyleSheet::Details); 106 { 107 RefPtr<nsGenericHTMLElement> slot = 108 NS_NewHTMLSlotElement(do_AddRef(slotNodeInfo)); 109 if (NS_WARN_IF(!slot)) { 110 return; 111 } 112 slot->SetAttr(kNameSpaceID_None, nsGkAtoms::name, 113 u"internal-main-summary"_ns, kNotify); 114 sr->AppendChildTo(slot, kNotify, IgnoreErrors()); 115 116 RefPtr<NodeInfo> summaryNodeInfo = nim->GetNodeInfo( 117 nsGkAtoms::summary, nullptr, kNameSpaceID_XHTML, nsINode::ELEMENT_NODE); 118 RefPtr<nsGenericHTMLElement> summary = 119 NS_NewHTMLSummaryElement(summaryNodeInfo.forget()); 120 if (NS_WARN_IF(!summary)) { 121 return; 122 } 123 124 nsAutoString defaultSummaryText; 125 nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES, 126 "DefaultSummary", OwnerDoc(), 127 defaultSummaryText); 128 RefPtr<nsTextNode> description = new (nim) nsTextNode(nim); 129 description->SetText(defaultSummaryText, kNotify); 130 summary->AppendChildTo(description, kNotify, IgnoreErrors()); 131 132 slot->AppendChildTo(summary, kNotify, IgnoreErrors()); 133 } 134 { 135 RefPtr<nsGenericHTMLElement> slot = 136 NS_NewHTMLSlotElement(slotNodeInfo.forget()); 137 if (NS_WARN_IF(!slot)) { 138 return; 139 } 140 if (StaticPrefs::layout_css_details_content_enabled()) { 141 slot->SetPseudoElementType(PseudoStyleType::detailsContent); 142 } 143 sr->AppendChildTo(slot, kNotify, IgnoreErrors()); 144 } 145 } 146 147 void HTMLDetailsElement::AsyncEventRunning(AsyncEventDispatcher* aEvent) { 148 if (mToggleEventDispatcher == aEvent) { 149 mToggleEventDispatcher = nullptr; 150 } 151 } 152 153 JSObject* HTMLDetailsElement::WrapNode(JSContext* aCx, 154 JS::Handle<JSObject*> aGivenProto) { 155 return HTMLDetailsElement_Binding::Wrap(aCx, this, aGivenProto); 156 } 157 158 bool HTMLDetailsElement::IsValidCommandAction(Command aCommand) const { 159 return nsGenericHTMLElement::IsValidCommandAction(aCommand) || 160 (StaticPrefs::dom_element_commandfor_on_details_enabled() && 161 (aCommand == Command::Toggle || aCommand == Command::Close || 162 aCommand == Command::Open)); 163 } 164 165 bool HTMLDetailsElement::HandleCommandInternal(Element* aSource, 166 Command aCommand, 167 ErrorResult& aRv) { 168 if (nsGenericHTMLElement::HandleCommandInternal(aSource, aCommand, aRv)) { 169 return true; 170 } 171 172 MOZ_ASSERT(StaticPrefs::dom_element_commandfor_on_details_enabled()); 173 if (aCommand == Command::Toggle) { 174 ToggleOpen(); 175 return true; 176 } 177 if (aCommand == Command::Close) { 178 if (Open()) { 179 SetOpen(false, IgnoreErrors()); 180 } 181 return true; 182 } 183 if (aCommand == Command::Open) { 184 if (!Open()) { 185 SetOpen(true, IgnoreErrors()); 186 } 187 return true; 188 } 189 190 return false; 191 } 192 193 void HTMLDetailsElement::CloseElementIfNeeded() { 194 if (!StaticPrefs::dom_details_group_enabled()) { 195 return; 196 } 197 198 if (!Open()) { 199 return; 200 } 201 202 if (!HasName()) { 203 return; 204 } 205 206 const RefPtr<nsAtom> name = GetParsedAttr(nsGkAtoms::name)->GetAsAtom(); 207 nsINode* const root = SubtreeRoot(); 208 for (nsINode* cur = root; cur; cur = cur->GetNextNode(root)) { 209 if (!cur->HasName()) { 210 continue; 211 } 212 if (auto* other = HTMLDetailsElement::FromNode(cur)) { 213 if (other != this && other->Open() && 214 other->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name, name, 215 eCaseMatters)) { 216 SetOpen(false, IgnoreErrors()); 217 break; 218 } 219 } 220 } 221 } 222 223 void HTMLDetailsElement::CloseOtherElementsIfNeeded() { 224 if (!StaticPrefs::dom_details_group_enabled()) { 225 return; 226 } 227 228 MOZ_ASSERT(Open()); 229 230 if (!HasName()) { 231 return; 232 } 233 234 const RefPtr<nsAtom> name = GetParsedAttr(nsGkAtoms::name)->GetAsAtom(); 235 nsINode* const root = SubtreeRoot(); 236 for (nsINode* cur = root; cur; cur = cur->GetNextNode(root)) { 237 if (!cur->HasName()) { 238 continue; 239 } 240 if (auto* other = HTMLDetailsElement::FromNode(cur)) { 241 if (other != this && other->Open() && 242 other->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name, name, 243 eCaseMatters)) { 244 RefPtr<HTMLDetailsElement> otherDetails = other; 245 otherDetails->SetOpen(false, IgnoreErrors()); 246 break; 247 } 248 } 249 } 250 } 251 252 } // namespace mozilla::dom