HTMLElement.cpp (16687B)
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/HTMLElement.h" 8 9 #include "mozilla/EventDispatcher.h" 10 #include "mozilla/PresState.h" 11 #include "mozilla/dom/CustomElementRegistry.h" 12 #include "mozilla/dom/ElementInternalsBinding.h" 13 #include "mozilla/dom/FormData.h" 14 #include "mozilla/dom/FromParser.h" 15 #include "mozilla/dom/HTMLElementBinding.h" 16 #include "nsContentUtils.h" 17 #include "nsGenericHTMLElement.h" 18 #include "nsILayoutHistoryState.h" 19 20 namespace mozilla::dom { 21 22 HTMLElement::HTMLElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo, 23 FromParser aFromParser) 24 : nsGenericHTMLFormElement(std::move(aNodeInfo)) { 25 if (NodeInfo()->Equals(nsGkAtoms::bdi)) { 26 AddStatesSilently(ElementState::HAS_DIR_ATTR_LIKE_AUTO); 27 } 28 29 InhibitRestoration(!(aFromParser & FROM_PARSER_NETWORK)); 30 } 31 32 NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLElement, nsGenericHTMLFormElement) 33 34 // QueryInterface implementation for HTMLElement 35 36 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(HTMLElement) 37 NS_INTERFACE_MAP_ENTRY_TEAROFF(nsIFormControl, GetElementInternals()) 38 NS_INTERFACE_MAP_ENTRY_TEAROFF(nsIConstraintValidation, GetElementInternals()) 39 NS_INTERFACE_MAP_END_INHERITING(nsGenericHTMLFormElement) 40 41 NS_IMPL_ADDREF_INHERITED(HTMLElement, nsGenericHTMLFormElement) 42 NS_IMPL_RELEASE_INHERITED(HTMLElement, nsGenericHTMLFormElement) 43 44 NS_IMPL_ELEMENT_CLONE(HTMLElement) 45 46 JSObject* HTMLElement::WrapNode(JSContext* aCx, 47 JS::Handle<JSObject*> aGivenProto) { 48 return dom::HTMLElement_Binding::Wrap(aCx, this, aGivenProto); 49 } 50 51 void HTMLElement::GetEventTargetParent(EventChainPreVisitor& aVisitor) { 52 if (IsDisabledForEvents(aVisitor.mEvent)) { 53 // Do not process any DOM events if the element is disabled 54 aVisitor.mCanHandle = false; 55 return; 56 } 57 58 nsGenericHTMLFormElement::GetEventTargetParent(aVisitor); 59 } 60 61 nsINode* HTMLElement::GetScopeChainParent() const { 62 if (IsFormAssociatedCustomElement()) { 63 if (auto* form = GetFormInternal()) { 64 return form; 65 } 66 } 67 return nsGenericHTMLFormElement::GetScopeChainParent(); 68 } 69 70 nsresult HTMLElement::BindToTree(BindContext& aContext, nsINode& aParent) { 71 nsresult rv = nsGenericHTMLFormElement::BindToTree(aContext, aParent); 72 NS_ENSURE_SUCCESS(rv, rv); 73 74 UpdateBarredFromConstraintValidation(); 75 UpdateValidityElementStates(false); 76 return rv; 77 } 78 79 void HTMLElement::UnbindFromTree(UnbindContext& aContext) { 80 nsGenericHTMLFormElement::UnbindFromTree(aContext); 81 82 UpdateBarredFromConstraintValidation(); 83 UpdateValidityElementStates(false); 84 } 85 86 void HTMLElement::DoneCreatingElement() { 87 if (MOZ_UNLIKELY(IsFormAssociatedElement())) { 88 MaybeRestoreFormAssociatedCustomElementState(); 89 } 90 } 91 92 void HTMLElement::SaveState() { 93 if (MOZ_LIKELY(!IsFormAssociatedElement())) { 94 return; 95 } 96 97 auto* internals = GetElementInternals(); 98 99 nsCString stateKey = internals->GetStateKey(); 100 if (stateKey.IsEmpty()) { 101 return; 102 } 103 104 nsCOMPtr<nsILayoutHistoryState> history = GetLayoutHistory(false); 105 if (!history) { 106 return; 107 } 108 109 // Get the pres state for this key, if it doesn't exist, create one. 110 PresState* result = history->GetState(stateKey); 111 if (!result) { 112 UniquePtr<PresState> newState = NewPresState(); 113 result = newState.get(); 114 history->AddState(stateKey, std::move(newState)); 115 } 116 117 const auto& state = internals->GetFormState(); 118 const auto& value = internals->GetFormSubmissionValue(); 119 result->contentData() = CustomElementTuple( 120 nsContentUtils::ConvertToCustomElementFormValue(value), 121 nsContentUtils::ConvertToCustomElementFormValue(state)); 122 } 123 124 void HTMLElement::MaybeRestoreFormAssociatedCustomElementState() { 125 MOZ_ASSERT(IsFormAssociatedElement()); 126 127 if (HasFlag(HTML_ELEMENT_INHIBIT_RESTORATION)) { 128 return; 129 } 130 131 auto* internals = GetElementInternals(); 132 if (internals->GetStateKey().IsEmpty()) { 133 Document* doc = GetUncomposedDoc(); 134 nsCString stateKey; 135 nsContentUtils::GenerateStateKey(this, doc, stateKey); 136 internals->SetStateKey(std::move(stateKey)); 137 138 RestoreFormAssociatedCustomElementState(); 139 } 140 } 141 142 void HTMLElement::RestoreFormAssociatedCustomElementState() { 143 MOZ_ASSERT(IsFormAssociatedElement()); 144 145 auto* internals = GetElementInternals(); 146 147 const nsCString& stateKey = internals->GetStateKey(); 148 if (stateKey.IsEmpty()) { 149 return; 150 } 151 nsCOMPtr<nsILayoutHistoryState> history = GetLayoutHistory(true); 152 if (!history) { 153 return; 154 } 155 PresState* result = history->GetState(stateKey); 156 if (!result) { 157 return; 158 } 159 auto& content = result->contentData(); 160 if (content.type() != PresContentData::TCustomElementTuple) { 161 return; 162 } 163 164 auto& ce = content.get_CustomElementTuple(); 165 nsCOMPtr<nsIGlobalObject> global = GetOwnerDocument()->GetOwnerGlobal(); 166 internals->RestoreFormValue( 167 nsContentUtils::ExtractFormAssociatedCustomElementValue(global, 168 ce.value()), 169 nsContentUtils::ExtractFormAssociatedCustomElementValue(global, 170 ce.state())); 171 } 172 173 void HTMLElement::InhibitRestoration(bool aShouldInhibit) { 174 if (aShouldInhibit) { 175 SetFlags(HTML_ELEMENT_INHIBIT_RESTORATION); 176 } else { 177 UnsetFlags(HTML_ELEMENT_INHIBIT_RESTORATION); 178 } 179 } 180 181 void HTMLElement::SetCustomElementDefinition( 182 CustomElementDefinition* aDefinition) { 183 nsGenericHTMLFormElement::SetCustomElementDefinition(aDefinition); 184 // Always create an ElementInternal for form-associated custom element as the 185 // Form related implementation lives in ElementInternal which implements 186 // nsIFormControl. It is okay for the attachElementInternal API as there is a 187 // separated flag for whether attachElementInternal is called. 188 if (aDefinition && !aDefinition->IsCustomBuiltIn() && 189 aDefinition->mFormAssociated) { 190 CustomElementData* data = GetCustomElementData(); 191 MOZ_ASSERT(data); 192 auto* internals = data->GetOrCreateElementInternals(this); 193 194 // This is for the case that script constructs a custom element directly, 195 // e.g. via new MyCustomElement(), where the upgrade steps won't be ran to 196 // update the disabled state in UpdateFormOwner(). 197 if (data->mState == CustomElementData::State::eCustom) { 198 UpdateDisabledState(true); 199 } else if (!HasFlag(HTML_ELEMENT_INHIBIT_RESTORATION)) { 200 internals->InitializeControlNumber(); 201 } 202 } 203 } 204 205 // https://html.spec.whatwg.org/commit-snapshots/53bc3803433e1c817918b83e8a84f3db900031dd/#dom-attachinternals 206 already_AddRefed<ElementInternals> HTMLElement::AttachInternals( 207 ErrorResult& aRv) { 208 CustomElementData* ceData = GetCustomElementData(); 209 210 // 1. If element's is value is not null, then throw a "NotSupportedError" 211 // DOMException. 212 if (nsAtom* isAtom = ceData ? ceData->GetIs(this) : nullptr) { 213 aRv.ThrowNotSupportedError(nsPrintfCString( 214 "Cannot attach ElementInternals to a customized built-in element " 215 "'%s'", 216 NS_ConvertUTF16toUTF8(isAtom->GetUTF16String()).get())); 217 return nullptr; 218 } 219 220 // 2. Let definition be the result of looking up a custom element definition 221 // given element's node document, its namespace, its local name, and null 222 // as is value. 223 nsAtom* nameAtom = NodeInfo()->NameAtom(); 224 CustomElementDefinition* definition = nullptr; 225 if (ceData) { 226 definition = ceData->GetCustomElementDefinition(); 227 228 // If the definition is null, the element possible hasn't yet upgraded. 229 // Fallback to use LookupCustomElementDefinition to find its definition. 230 if (!definition) { 231 definition = nsContentUtils::LookupCustomElementDefinition( 232 NodeInfo()->GetDocument(), nameAtom, NodeInfo()->NamespaceID(), 233 ceData->GetCustomElementType()); 234 } 235 } 236 237 // 3. If definition is null, then throw an "NotSupportedError" DOMException. 238 if (!definition) { 239 aRv.ThrowNotSupportedError(nsPrintfCString( 240 "Cannot attach ElementInternals to a non-custom element '%s'", 241 NS_ConvertUTF16toUTF8(nameAtom->GetUTF16String()).get())); 242 return nullptr; 243 } 244 245 // 4. If definition's disable internals is true, then throw a 246 // "NotSupportedError" DOMException. 247 if (definition->mDisableInternals) { 248 aRv.ThrowNotSupportedError(nsPrintfCString( 249 "AttachInternal() to '%s' is disabled by disabledFeatures", 250 NS_ConvertUTF16toUTF8(nameAtom->GetUTF16String()).get())); 251 return nullptr; 252 } 253 254 // If this is not a custom element, i.e. ceData is nullptr, we are unable to 255 // find a definition and should return earlier above. 256 MOZ_ASSERT(ceData); 257 258 // 5. If element's attached internals is true, then throw an 259 // "NotSupportedError" DOMException. 260 if (ceData->HasAttachedInternals()) { 261 aRv.ThrowNotSupportedError(nsPrintfCString( 262 "AttachInternals() has already been called from '%s'", 263 NS_ConvertUTF16toUTF8(nameAtom->GetUTF16String()).get())); 264 return nullptr; 265 } 266 267 // 6. If element's custom element state is not "precustomized" or "custom", 268 // then throw a "NotSupportedError" DOMException. 269 if (ceData->mState != CustomElementData::State::ePrecustomized && 270 ceData->mState != CustomElementData::State::eCustom) { 271 aRv.ThrowNotSupportedError( 272 R"(Custom element state is not "precustomized" or "custom".)"); 273 return nullptr; 274 } 275 276 // 7. Set element's attached internals to true. 277 ceData->AttachedInternals(); 278 279 // 8. Create a new ElementInternals instance targeting element, and return it. 280 return do_AddRef(ceData->GetOrCreateElementInternals(this)); 281 } 282 283 void HTMLElement::AfterClearForm(bool aUnbindOrDelete) { 284 // No need to enqueue formAssociated callback if we aren't releasing or 285 // unbinding from tree, UpdateFormOwner() will handle it. 286 if (aUnbindOrDelete) { 287 MOZ_ASSERT(IsFormAssociatedElement()); 288 nsContentUtils::EnqueueLifecycleCallback( 289 ElementCallbackType::eFormAssociated, this, {}); 290 } 291 } 292 293 void HTMLElement::UpdateFormOwner() { 294 MOZ_ASSERT(IsFormAssociatedElement()); 295 296 // If @form is set, the element *has* to be in a composed document, 297 // otherwise it wouldn't be possible to find an element with the 298 // corresponding id. If @form isn't set, the element *has* to have a parent, 299 // otherwise it wouldn't be possible to find a form ancestor. We should not 300 // call UpdateFormOwner if none of these conditions are fulfilled. 301 if (HasAttr(nsGkAtoms::form) ? IsInComposedDoc() : !!GetParent()) { 302 UpdateFormOwner(true, nullptr); 303 } 304 UpdateFieldSet(true); 305 UpdateDisabledState(true); 306 UpdateBarredFromConstraintValidation(); 307 UpdateValidityElementStates(true); 308 309 MaybeRestoreFormAssociatedCustomElementState(); 310 } 311 312 bool HTMLElement::IsDisabledForEvents(WidgetEvent* aEvent) { 313 if (IsFormAssociatedElement()) { 314 return IsElementDisabledForEvents(aEvent, GetPrimaryFrame()); 315 } 316 317 return false; 318 } 319 320 void HTMLElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName, 321 const nsAttrValue* aValue, 322 const nsAttrValue* aOldValue, 323 nsIPrincipal* aMaybeScriptedPrincipal, 324 bool aNotify) { 325 if (aNameSpaceID == kNameSpaceID_None && 326 (aName == nsGkAtoms::disabled || aName == nsGkAtoms::readonly)) { 327 if (aName == nsGkAtoms::disabled) { 328 // This *has* to be called *before* validity state check because 329 // UpdateBarredFromConstraintValidation depend on our disabled state. 330 UpdateDisabledState(aNotify); 331 } 332 if (aName == nsGkAtoms::readonly && !!aValue != !!aOldValue) { 333 UpdateReadOnlyState(aNotify); 334 } 335 UpdateBarredFromConstraintValidation(); 336 UpdateValidityElementStates(aNotify); 337 } 338 339 return nsGenericHTMLFormElement::AfterSetAttr( 340 aNameSpaceID, aName, aValue, aOldValue, aMaybeScriptedPrincipal, aNotify); 341 } 342 343 void HTMLElement::UpdateValidityElementStates(bool aNotify) { 344 AutoStateChangeNotifier notifier(*this, aNotify); 345 RemoveStatesSilently(ElementState::VALIDITY_STATES); 346 ElementInternals* internals = GetElementInternals(); 347 if (!internals || !internals->IsCandidateForConstraintValidation()) { 348 return; 349 } 350 if (internals->IsValid()) { 351 AddStatesSilently(ElementState::VALID | ElementState::USER_VALID); 352 } else { 353 AddStatesSilently(ElementState::INVALID | ElementState::USER_INVALID); 354 } 355 } 356 357 void HTMLElement::SetFormInternal(HTMLFormElement* aForm, bool aBindToTree) { 358 ElementInternals* internals = GetElementInternals(); 359 MOZ_ASSERT(internals); 360 internals->SetForm(aForm); 361 } 362 363 HTMLFormElement* HTMLElement::GetFormInternal() const { 364 ElementInternals* internals = GetElementInternals(); 365 MOZ_ASSERT(internals); 366 return internals->GetForm(); 367 } 368 369 void HTMLElement::SetFieldSetInternal(HTMLFieldSetElement* aFieldset) { 370 ElementInternals* internals = GetElementInternals(); 371 MOZ_ASSERT(internals); 372 internals->SetFieldSet(aFieldset); 373 } 374 375 HTMLFieldSetElement* HTMLElement::GetFieldSetInternal() const { 376 ElementInternals* internals = GetElementInternals(); 377 MOZ_ASSERT(internals); 378 return internals->GetFieldSet(); 379 } 380 381 bool HTMLElement::CanBeDisabled() const { return IsFormAssociatedElement(); } 382 383 void HTMLElement::UpdateDisabledState(bool aNotify) { 384 bool oldState = IsDisabled(); 385 nsGenericHTMLFormElement::UpdateDisabledState(aNotify); 386 if (oldState != IsDisabled()) { 387 MOZ_ASSERT(IsFormAssociatedElement()); 388 LifecycleCallbackArgs args; 389 args.mDisabled = !oldState; 390 nsContentUtils::EnqueueLifecycleCallback(ElementCallbackType::eFormDisabled, 391 this, args); 392 } 393 } 394 395 void HTMLElement::UpdateFormOwner(bool aBindToTree, Element* aFormIdElement) { 396 HTMLFormElement* oldForm = GetFormInternal(); 397 nsGenericHTMLFormElement::UpdateFormOwner(aBindToTree, aFormIdElement); 398 HTMLFormElement* newForm = GetFormInternal(); 399 if (newForm != oldForm) { 400 LifecycleCallbackArgs args; 401 args.mForm = newForm; 402 nsContentUtils::EnqueueLifecycleCallback( 403 ElementCallbackType::eFormAssociated, this, args); 404 } 405 } 406 407 bool HTMLElement::IsFormAssociatedElement() const { 408 CustomElementData* data = GetCustomElementData(); 409 return data && data->IsFormAssociated(); 410 } 411 412 void HTMLElement::FieldSetDisabledChanged(bool aNotify) { 413 // This *has* to be called *before* UpdateBarredFromConstraintValidation 414 // because this function depend on our disabled state. 415 nsGenericHTMLFormElement::FieldSetDisabledChanged(aNotify); 416 417 UpdateBarredFromConstraintValidation(); 418 UpdateValidityElementStates(aNotify); 419 } 420 421 ElementInternals* HTMLElement::GetElementInternals() const { 422 CustomElementData* data = GetCustomElementData(); 423 if (!data || !data->IsFormAssociated()) { 424 // If the element is not a form associated custom element, it should not be 425 // able to be QueryInterfaced to nsIFormControl and could not perform 426 // the form operation, either, so we return nullptr here. 427 return nullptr; 428 } 429 430 return data->GetElementInternals(); 431 } 432 433 nsIFormControl* HTMLElement::GetAsFormControl() { 434 return GetElementInternals(); 435 } 436 437 const nsIFormControl* HTMLElement::GetAsFormControl() const { 438 return GetElementInternals(); 439 } 440 441 void HTMLElement::UpdateBarredFromConstraintValidation() { 442 CustomElementData* data = GetCustomElementData(); 443 if (data && data->IsFormAssociated()) { 444 ElementInternals* internals = data->GetElementInternals(); 445 MOZ_ASSERT(internals); 446 internals->UpdateBarredFromConstraintValidation(); 447 } 448 } 449 450 } // namespace mozilla::dom 451 452 // Here, we expand 'NS_IMPL_NS_NEW_HTML_ELEMENT()' by hand. 453 // (Calling the macro directly (with no args) produces compiler warnings.) 454 nsGenericHTMLElement* NS_NewHTMLElement( 455 already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo, 456 mozilla::dom::FromParser aFromParser) { 457 RefPtr<mozilla::dom::NodeInfo> nodeInfo(aNodeInfo); 458 auto* nim = nodeInfo->NodeInfoManager(); 459 return new (nim) mozilla::dom::HTMLElement(nodeInfo.forget(), aFromParser); 460 } 461 462 // Distinct from the above in order to have function pointer that compared 463 // unequal to a function pointer to the above. 464 nsGenericHTMLElement* NS_NewCustomElement( 465 already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo, 466 mozilla::dom::FromParser aFromParser) { 467 RefPtr<mozilla::dom::NodeInfo> nodeInfo(aNodeInfo); 468 auto* nim = nodeInfo->NodeInfoManager(); 469 return new (nim) mozilla::dom::HTMLElement(nodeInfo.forget(), aFromParser); 470 }