blob: bfa66f3d2e0d8f04ed9679737a5f2e4c5fd10b8a [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/html/custom/custom_element.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/qualified_name.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/html/custom/ce_reactions_scope.h"
#include "third_party/blink/renderer/core/html/custom/custom_element_definition.h"
#include "third_party/blink/renderer/core/html/custom/custom_element_reaction_factory.h"
#include "third_party/blink/renderer/core/html/custom/custom_element_reaction_stack.h"
#include "third_party/blink/renderer/core/html/custom/custom_element_registry.h"
#include "third_party/blink/renderer/core/html/custom/v0_custom_element.h"
#include "third_party/blink/renderer/core/html/custom/v0_custom_element_registration_context.h"
#include "third_party/blink/renderer/core/html/html_element.h"
#include "third_party/blink/renderer/core/html/html_unknown_element.h"
#include "third_party/blink/renderer/core/html_element_factory.h"
#include "third_party/blink/renderer/core/html_element_type_helpers.h"
#include "third_party/blink/renderer/platform/wtf/text/atomic_string_hash.h"
namespace blink {
CustomElementRegistry* CustomElement::Registry(const Element& element) {
return Registry(element.GetDocument());
}
CustomElementRegistry* CustomElement::Registry(const Document& document) {
if (LocalDOMWindow* window = document.ExecutingWindow())
return window->customElements();
return nullptr;
}
static CustomElementDefinition* DefinitionForElementWithoutCheck(
const Element& element) {
DCHECK_EQ(element.GetCustomElementState(), CustomElementState::kCustom);
return element.GetCustomElementDefinition();
}
CustomElementDefinition* CustomElement::DefinitionForElement(
const Element* element) {
if (!element ||
element->GetCustomElementState() != CustomElementState::kCustom)
return nullptr;
return DefinitionForElementWithoutCheck(*element);
}
Vector<AtomicString>& CustomElement::EmbedderCustomElementNames() {
DEFINE_STATIC_LOCAL(Vector<AtomicString>, names, ());
return names;
}
void CustomElement::AddEmbedderCustomElementName(const AtomicString& name) {
DCHECK_EQ(name, name.LowerASCII());
DCHECK(Document::IsValidName(name)) << name;
DCHECK_EQ(HTMLElementType::kHTMLUnknownElement, htmlElementTypeForTag(name))
<< name;
DCHECK(!IsValidName(name, false)) << name;
if (EmbedderCustomElementNames().Contains(name))
return;
EmbedderCustomElementNames().push_back(name);
}
void CustomElement::AddEmbedderCustomElementNameForTesting(
const AtomicString& name,
ExceptionState& exception_state) {
if (name != name.LowerASCII() || !Document::IsValidName(name) ||
HTMLElementType::kHTMLUnknownElement != htmlElementTypeForTag(name) ||
IsValidName(name, false)) {
exception_state.ThrowDOMException(DOMExceptionCode::kSyntaxError,
"Name cannot be used");
return;
}
AddEmbedderCustomElementName(name);
}
bool CustomElement::IsHyphenatedSpecElementName(const AtomicString& name) {
// Even if Blink does not implement one of the related specs, (for
// example annotation-xml is from MathML, which Blink does not
// implement) we must prohibit using the name because that is
// required by the HTML spec which we *do* implement. Don't remove
// names from this list without removing them from the HTML spec
// first.
DEFINE_STATIC_LOCAL(HashSet<AtomicString>, hyphenated_spec_element_names,
({
"annotation-xml", "color-profile", "font-face",
"font-face-src", "font-face-uri", "font-face-format",
"font-face-name", "missing-glyph",
}));
return hyphenated_spec_element_names.Contains(name);
}
bool CustomElement::ShouldCreateCustomElement(const AtomicString& name) {
return IsValidName(name);
}
bool CustomElement::ShouldCreateCustomElement(const QualifiedName& tag_name) {
return ShouldCreateCustomElement(tag_name.LocalName()) &&
tag_name.NamespaceURI() == html_names::xhtmlNamespaceURI;
}
bool CustomElement::ShouldCreateCustomizedBuiltinElement(
const AtomicString& local_name) {
return htmlElementTypeForTag(local_name) !=
HTMLElementType::kHTMLUnknownElement;
}
bool CustomElement::ShouldCreateCustomizedBuiltinElement(
const QualifiedName& tag_name) {
return ShouldCreateCustomizedBuiltinElement(tag_name.LocalName()) &&
tag_name.NamespaceURI() == html_names::xhtmlNamespaceURI;
}
static CustomElementDefinition* DefinitionFor(
const Document& document,
const CustomElementDescriptor desc) {
if (CustomElementRegistry* registry = CustomElement::Registry(document))
return registry->DefinitionFor(desc);
return nullptr;
}
// https://dom.spec.whatwg.org/#concept-create-element
HTMLElement* CustomElement::CreateCustomElement(Document& document,
const QualifiedName& tag_name,
CreateElementFlags flags) {
DCHECK(ShouldCreateCustomElement(tag_name)) << tag_name;
// 4. Let definition be the result of looking up a custom element
// definition given document, namespace, localName, and is.
if (auto* definition = DefinitionFor(
document, CustomElementDescriptor(tag_name.LocalName(),
tag_name.LocalName()))) {
DCHECK(definition->Descriptor().IsAutonomous());
// 6. Otherwise, if definition is non-null, then:
return definition->CreateElement(document, tag_name, flags);
}
// 7. Otherwise:
return ToHTMLElement(
CreateUncustomizedOrUndefinedElementTemplate<kQNameIsValid>(
document, tag_name, flags, g_null_atom));
}
// Step 7 of https://dom.spec.whatwg.org/#concept-create-element in
// addition to Custom Element V0 handling.
template <CustomElement::CreateUUCheckLevel level>
Element* CustomElement::CreateUncustomizedOrUndefinedElementTemplate(
Document& document,
const QualifiedName& tag_name,
const CreateElementFlags flags,
const AtomicString& is_value) {
if (level == kQNameIsValid) {
DCHECK(is_value.IsNull());
DCHECK(ShouldCreateCustomElement(tag_name)) << tag_name;
}
Element* element;
if (V0CustomElement::IsValidName(tag_name.LocalName()) &&
document.RegistrationContext()) {
element = document.RegistrationContext()->CreateCustomTagElement(document,
tag_name);
} else {
// 7.1. Let interface be the element interface for localName and namespace.
// 7.2. Set result to a new element that implements interface, with ...
element = document.CreateRawElement(tag_name, flags);
if (level == kCheckAll && !is_value.IsNull()) {
element->SetIsValue(is_value);
if (flags.IsCustomElementsV0())
V0CustomElementRegistrationContext::SetTypeExtension(element, is_value);
}
}
// 7.3. If namespace is the HTML namespace, and either localName is a
// valid custom element name or is is non-null, then set result’s
// custom element state to "undefined".
if (level == kQNameIsValid)
element->SetCustomElementState(CustomElementState::kUndefined);
else if (tag_name.NamespaceURI() == html_names::xhtmlNamespaceURI &&
(CustomElement::IsValidName(tag_name.LocalName()) ||
!is_value.IsNull()))
element->SetCustomElementState(CustomElementState::kUndefined);
return element;
}
Element* CustomElement::CreateUncustomizedOrUndefinedElement(
Document& document,
const QualifiedName& tag_name,
const CreateElementFlags flags,
const AtomicString& is_value) {
return CreateUncustomizedOrUndefinedElementTemplate<kCheckAll>(
document, tag_name, flags, is_value);
}
HTMLElement* CustomElement::CreateFailedElement(Document& document,
const QualifiedName& tag_name) {
DCHECK(ShouldCreateCustomElement(tag_name));
// "create an element for a token":
// https://html.spec.whatwg.org/multipage/syntax.html#create-an-element-for-the-token
// 7. If this step throws an exception, let element be instead a new element
// that implements HTMLUnknownElement, with no attributes, namespace set to
// given namespace, namespace prefix set to null, custom element state set
// to "failed", and node document set to document.
HTMLElement* element = HTMLUnknownElement::Create(tag_name, document);
element->SetCustomElementState(CustomElementState::kFailed);
return element;
}
void CustomElement::Enqueue(Element* element, CustomElementReaction* reaction) {
// To enqueue an element on the appropriate element queue
// https://html.spec.whatwg.org/multipage/scripting.html#enqueue-an-element-on-the-appropriate-element-queue
// If the custom element reactions stack is not empty, then
// Add element to the current element queue.
if (CEReactionsScope* current = CEReactionsScope::Current()) {
current->EnqueueToCurrentQueue(element, reaction);
return;
}
// If the custom element reactions stack is empty, then
// Add element to the backup element queue.
CustomElementReactionStack::Current().EnqueueToBackupQueue(element, reaction);
}
void CustomElement::EnqueueConnectedCallback(Element* element) {
CustomElementDefinition* definition =
DefinitionForElementWithoutCheck(*element);
if (definition->HasConnectedCallback())
definition->EnqueueConnectedCallback(element);
}
void CustomElement::EnqueueDisconnectedCallback(Element* element) {
CustomElementDefinition* definition =
DefinitionForElementWithoutCheck(*element);
if (definition->HasDisconnectedCallback())
definition->EnqueueDisconnectedCallback(element);
}
void CustomElement::EnqueueAdoptedCallback(Element* element,
Document* old_owner,
Document* new_owner) {
DCHECK_EQ(element->GetCustomElementState(), CustomElementState::kCustom);
CustomElementDefinition* definition =
DefinitionForElementWithoutCheck(*element);
if (definition->HasAdoptedCallback())
definition->EnqueueAdoptedCallback(element, old_owner, new_owner);
}
void CustomElement::EnqueueAttributeChangedCallback(
Element* element,
const QualifiedName& name,
const AtomicString& old_value,
const AtomicString& new_value) {
CustomElementDefinition* definition =
DefinitionForElementWithoutCheck(*element);
if (definition->HasAttributeChangedCallback(name))
definition->EnqueueAttributeChangedCallback(element, name, old_value,
new_value);
}
void CustomElement::EnqueueFormAssociatedCallback(
Element& element,
HTMLFormElement* nullable_form) {
auto& definition = *DefinitionForElementWithoutCheck(element);
if (definition.HasFormAssociatedCallback()) {
Enqueue(&element, &CustomElementReactionFactory::CreateFormAssociated(
definition, nullable_form));
}
}
void CustomElement::EnqueueFormResetCallback(Element& element) {
auto& definition = *DefinitionForElementWithoutCheck(element);
if (definition.HasFormResetCallback()) {
Enqueue(&element,
&CustomElementReactionFactory::CreateFormReset(definition));
}
}
void CustomElement::EnqueueDisabledStateChangedCallback(Element& element,
bool is_disabled) {
auto& definition = *DefinitionForElementWithoutCheck(element);
if (definition.HasDisabledStateChangedCallback()) {
Enqueue(&element, &CustomElementReactionFactory::CreateDisabledStateChanged(
definition, is_disabled));
}
}
void CustomElement::TryToUpgrade(Element* element,
bool upgrade_invisible_elements) {
// Try to upgrade an element
// https://html.spec.whatwg.org/multipage/scripting.html#concept-try-upgrade
DCHECK_EQ(element->GetCustomElementState(), CustomElementState::kUndefined);
CustomElementRegistry* registry = CustomElement::Registry(*element);
if (!registry)
return;
const AtomicString& is_value = element->IsValue();
if (CustomElementDefinition* definition =
registry->DefinitionFor(CustomElementDescriptor(
is_value.IsNull() ? element->localName() : is_value,
element->localName())))
definition->EnqueueUpgradeReaction(element, upgrade_invisible_elements);
else
registry->AddCandidate(element);
}
} // namespace blink