Files
ladybird/Libraries/LibWeb/HTML/HTMLMetaElement.cpp
Shannon Booth fd44da6829 LibWeb/Bindings: Emit one bindings header and cpp per IDL
Previously, the LibWeb bindings generator would output multiple per
interface files like Prototype/Constructor/Namespace/GlobalMixin
depending on the contents of that IDL file.

This complicates the build system as it means that it does not know
what files will be generated without knowledge of the contents of that
IDL file.

Instead, for each IDL file only generate a single Bindings/<IDLFile>.h
and Bindings/<IDLFile>.cpp.
2026-04-21 07:36:13 +02:00

247 lines
11 KiB
C++

/*
* Copyright (c) 2020, the SerenityOS developers.
* Copyright (c) 2023, Luke Wilde <lukew@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/GenericLexer.h>
#include <LibWeb/Bindings/HTMLMetaElement.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/CSS/Parser/Parser.h>
#include <LibWeb/CSS/PropertyID.h>
#include <LibWeb/CSS/StyleValues/ColorSchemeStyleValue.h>
#include <LibWeb/CSS/StyleValues/ColorStyleValue.h>
#include <LibWeb/ContentSecurityPolicy/Directives/Names.h>
#include <LibWeb/ContentSecurityPolicy/PolicyList.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/HTML/HTMLHeadElement.h>
#include <LibWeb/HTML/HTMLMetaElement.h>
#include <LibWeb/HTML/PolicyContainers.h>
#include <LibWeb/Infra/CharacterTypes.h>
#include <LibWeb/Page/Page.h>
namespace Web::HTML {
GC_DEFINE_ALLOCATOR(HTMLMetaElement);
HTMLMetaElement::HTMLMetaElement(DOM::Document& document, DOM::QualifiedName qualified_name)
: HTMLElement(document, move(qualified_name))
{
}
HTMLMetaElement::~HTMLMetaElement() = default;
void HTMLMetaElement::initialize(JS::Realm& realm)
{
WEB_SET_PROTOTYPE_FOR_INTERFACE(HTMLMetaElement);
Base::initialize(realm);
}
Optional<HTMLMetaElement::HttpEquivAttributeState> HTMLMetaElement::http_equiv_state() const
{
auto value = get_attribute_value(HTML::AttributeNames::http_equiv);
#define __ENUMERATE_HTML_META_HTTP_EQUIV_ATTRIBUTE(keyword, state) \
if (value.equals_ignoring_ascii_case(keyword##sv)) \
return HTMLMetaElement::HttpEquivAttributeState::state;
ENUMERATE_HTML_META_HTTP_EQUIV_ATTRIBUTES
#undef __ENUMERATE_HTML_META_HTTP_EQUIV_ATTRIBUTE
return OptionalNone {};
}
void HTMLMetaElement::update_metadata(Optional<String> const& old_name)
{
if (name().has_value()) {
if (name()->equals_ignoring_ascii_case("theme-color"sv)) {
document().obtain_theme_color();
} else if (name()->equals_ignoring_ascii_case("color-scheme"sv)) {
document().obtain_supported_color_schemes();
} else if (name()->equals_ignoring_ascii_case("referrer"sv)) {
// 2. If element does not have a name attribute whose value is an ASCII case-insensitive match for "referrer", then return.
update_referrer_policy();
}
}
if (old_name.has_value()) {
if (old_name->equals_ignoring_ascii_case("theme-color"sv)) {
document().obtain_theme_color();
} else if (old_name->equals_ignoring_ascii_case("color-scheme"sv)) {
document().obtain_supported_color_schemes();
}
// NOTE: For historical reasons, unlike other standard metadata names, the processing model for referrer is not
// responsive to element removals, and does not use tree order. Only the most-recently-inserted or
// most-recently-modified meta element in this state has an effect.
}
}
// https://html.spec.whatwg.org/multipage/semantics.html#meta-referrer
void HTMLMetaElement::update_referrer_policy()
{
// 1. If element is not in a document tree, then return.
if (!in_a_document_tree())
return;
// 3. If element does not have a content attribute, or that attribute's value is the empty string, then return.
auto content = attribute(AttributeNames::content);
if (!content.has_value() || content->is_empty())
return;
// 4. Let value be the value of element's content attribute, converted to ASCII lowercase.
auto value = content->bytes_as_string_view();
// 5. If value is one of the values given in the first column of the following table, then set value to the value given in the second column:
ReferrerPolicy::ReferrerPolicy policy;
if (value.equals_ignoring_ascii_case("never"sv))
policy = ReferrerPolicy::ReferrerPolicy::NoReferrer;
else if (value.equals_ignoring_ascii_case("default"sv))
policy = ReferrerPolicy::DEFAULT_REFERRER_POLICY;
else if (value.equals_ignoring_ascii_case("always"sv))
policy = ReferrerPolicy::ReferrerPolicy::UnsafeURL;
else if (value.equals_ignoring_ascii_case("origin-when-crossorigin"sv))
policy = ReferrerPolicy::ReferrerPolicy::OriginWhenCrossOrigin;
// 6. If value is a referrer policy, then...
else if (auto parsed_policy = ReferrerPolicy::from_string(value); parsed_policy.has_value())
policy = *parsed_policy;
else
return;
// 6. ...set element's node document's policy container's referrer policy to policy.
document().policy_container()->referrer_policy = policy;
}
void HTMLMetaElement::inserted()
{
Base::inserted();
update_metadata();
// https://html.spec.whatwg.org/multipage/semantics.html#pragma-directives
// When a meta element is inserted into the document, if its http-equiv attribute is present and represents one of
// the above states, then the user agent must run the algorithm appropriate for that state, as described in the
// following list:
auto http_equiv = http_equiv_state();
if (http_equiv.has_value()) {
switch (http_equiv.value()) {
case HttpEquivAttributeState::EncodingDeclaration:
// https://html.spec.whatwg.org/multipage/semantics.html#attr-meta-http-equiv-content-type
// The Encoding declaration state is just an alternative form of setting the charset attribute: it is a character encoding declaration.
// This state's user agent requirements are all handled by the parsing section of the specification.
break;
case HttpEquivAttributeState::Refresh: {
// https://html.spec.whatwg.org/multipage/semantics.html#attr-meta-http-equiv-refresh
// 1. If the meta element has no content attribute, or if that attribute's value is the empty string, then return.
// 2. Let input be the value of the element's content attribute.
if (!has_attribute(AttributeNames::content))
break;
auto input = get_attribute_value(AttributeNames::content);
if (input.is_empty())
break;
// 3. Run the shared declarative refresh steps with the meta element's node document, input, and the meta element.
document().shared_declarative_refresh_steps(input, this);
break;
}
case HttpEquivAttributeState::SetCookie:
// https://html.spec.whatwg.org/multipage/semantics.html#attr-meta-http-equiv-set-cookie
// This pragma is non-conforming and has no effect.
// User agents are required to ignore this pragma.
break;
case HttpEquivAttributeState::XUACompatible:
// https://html.spec.whatwg.org/multipage/semantics.html#attr-meta-http-equiv-x-ua-compatible
// In practice, this pragma encourages Internet Explorer to more closely follow the specifications.
// For meta elements with an http-equiv attribute in the X-UA-Compatible state, the content attribute must have a value that is an ASCII case-insensitive match for the string "IE=edge".
// User agents are required to ignore this pragma.
break;
case HttpEquivAttributeState::ContentLanguage: {
// https://html.spec.whatwg.org/multipage/semantics.html#attr-meta-http-equiv-content-language
// 1. If the meta element has no content attribute, then return.
if (!has_attribute(AttributeNames::content))
break;
// 2. If the element's content attribute contains a U+002C COMMA character (,), then return.
auto content = get_attribute_value(AttributeNames::content);
if (content.contains(","sv))
break;
// 3. Let input be the value of the element's content attribute.
// 4. Let position point at the first character of input.
GenericLexer lexer { content };
// 5. Skip ASCII whitespace within input given position.
lexer.ignore_while(Web::Infra::is_ascii_whitespace);
// 6. Collect a sequence of code points that are not ASCII whitespace from input given position.
// 7. Let candidate be the string that resulted from the previous step.
auto candidate = lexer.consume_until(Web::Infra::is_ascii_whitespace);
// 8. If candidate is the empty string, return.
if (candidate.is_empty())
break;
// 9. Set the pragma-set default language to candidate.
auto language = String::from_utf8_without_validation(candidate.bytes());
document().set_pragma_set_default_language(language);
document().document_element()->invalidate_lang_value();
break;
}
case HttpEquivAttributeState::ContentSecurityPolicy: {
// https://html.spec.whatwg.org/multipage/semantics.html#attr-meta-http-equiv-content-security-policy
// This pragma enforces a Content Security Policy on a Document. [CSP]
// 1. If the meta element is not a child of a head element, return.
if (!is<HTMLHeadElement>(parent()))
break;
// 2. If the meta element has no content attribute, or if that attribute's value is the empty string, then return.
auto input = get_attribute_value(AttributeNames::content);
if (input.is_empty())
break;
// 3. Let policy be the result of executing Content Security Policy's parse a serialized Content Security
// Policy algorithm on the meta element's content attribute's value, with a source of "meta", and a
// disposition of "enforce".
auto& realm = this->realm();
auto policy = ContentSecurityPolicy::Policy::parse_a_serialized_csp(realm.heap(), input, ContentSecurityPolicy::Policy::Source::Meta, ContentSecurityPolicy::Policy::Disposition::Enforce);
// 4. Remove all occurrences of the report-uri, frame-ancestors, and sandbox directives from policy.
policy->remove_directive({}, ContentSecurityPolicy::Directives::Names::ReportUri);
policy->remove_directive({}, ContentSecurityPolicy::Directives::Names::FrameAncestors);
policy->remove_directive({}, ContentSecurityPolicy::Directives::Names::Sandbox);
// FIXME: File spec issue stating the policy's self origin isn't set here.
policy->set_self_origin({}, document().origin());
// 5. Enforce the policy policy.
auto policy_list = ContentSecurityPolicy::PolicyList::from_object(realm.global_object());
VERIFY(policy_list);
policy_list->enforce_policy(policy);
break;
}
default:
dbgln("FIXME: Implement '{}' http-equiv state", get_attribute_value(AttributeNames::http_equiv));
break;
}
}
}
void HTMLMetaElement::removed_from(Node* old_parent, Node& old_root)
{
Base::removed_from(old_parent, old_root);
update_metadata();
}
void HTMLMetaElement::attribute_changed(FlyString const& local_name, Optional<String> const& old_value, Optional<String> const& value, Optional<FlyString> const& namespace_)
{
Base::attribute_changed(local_name, old_value, value, namespace_);
if (local_name == HTML::AttributeNames::name) {
update_metadata(old_value);
} else {
update_metadata();
}
}
}