Serenity Operating System
1/*
2 * Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
3 * Copyright (c) 2021, the SerenityOS developers.
4 *
5 * SPDX-License-Identifier: BSD-2-Clause
6 */
7
8#include <LibWeb/CSS/Parser/Parser.h>
9#include <LibWeb/DOM/Document.h>
10#include <LibWeb/HTML/HTMLStyleElement.h>
11#include <LibWeb/Infra/Strings.h>
12
13namespace Web::HTML {
14
15HTMLStyleElement::HTMLStyleElement(DOM::Document& document, DOM::QualifiedName qualified_name)
16 : HTMLElement(document, move(qualified_name))
17{
18}
19
20HTMLStyleElement::~HTMLStyleElement() = default;
21
22JS::ThrowCompletionOr<void> HTMLStyleElement::initialize(JS::Realm& realm)
23{
24 MUST_OR_THROW_OOM(Base::initialize(realm));
25 set_prototype(&Bindings::ensure_web_prototype<Bindings::HTMLStyleElementPrototype>(realm, "HTMLStyleElement"));
26
27 return {};
28}
29
30void HTMLStyleElement::visit_edges(Cell::Visitor& visitor)
31{
32 Base::visit_edges(visitor);
33 visitor.visit(m_associated_css_style_sheet.ptr());
34}
35
36void HTMLStyleElement::children_changed()
37{
38
39 update_a_style_block();
40 HTMLElement::children_changed();
41}
42
43void HTMLStyleElement::inserted()
44{
45 update_a_style_block();
46 return HTMLElement::inserted();
47}
48
49void HTMLStyleElement::removed_from(Node* old_parent)
50{
51 update_a_style_block();
52 return HTMLElement::removed_from(old_parent);
53}
54
55// https://www.w3.org/TR/cssom/#remove-a-css-style-sheet
56static void remove_a_css_style_sheet(DOM::Document& document, CSS::CSSStyleSheet& sheet)
57{
58 // 1. Remove the CSS style sheet from the list of document or shadow root CSS style sheets.
59 document.style_sheets().remove_sheet(sheet);
60
61 // 2. Set the CSS style sheet’s parent CSS style sheet, owner node and owner CSS rule to null.
62 sheet.set_parent_css_style_sheet(nullptr);
63 sheet.set_owner_node(nullptr);
64 sheet.set_owner_css_rule(nullptr);
65}
66
67// https://www.w3.org/TR/cssom/#add-a-css-style-sheet
68static void add_a_css_style_sheet(DOM::Document& document, CSS::CSSStyleSheet& sheet)
69{
70 // 1. Add the CSS style sheet to the list of document or shadow root CSS style sheets at the appropriate location. The remainder of these steps deal with the disabled flag.
71 document.style_sheets().add_sheet(sheet);
72
73 // 2. If the disabled flag is set, then return.
74 if (sheet.disabled())
75 return;
76
77 // FIXME: 3. If the title is not the empty string, the alternate flag is unset, and preferred CSS style sheet set name is the empty string change the preferred CSS style sheet set name to the title.
78
79 // FIXME: 4. If any of the following is true, then unset the disabled flag and return:
80 // The title is the empty string.
81 // The last CSS style sheet set name is null and the title is a case-sensitive match for the preferred CSS style sheet set name.
82 // The title is a case-sensitive match for the last CSS style sheet set name.
83
84 // FIXME: 5. Set the disabled flag.
85}
86
87// https://www.w3.org/TR/cssom/#create-a-css-style-sheet
88static void create_a_css_style_sheet(DOM::Document& document, DeprecatedString type, DOM::Element* owner_node, DeprecatedString media, DeprecatedString title, bool alternate, bool origin_clean, DeprecatedString location, CSS::CSSStyleSheet* parent_style_sheet, CSS::CSSRule* owner_rule, CSS::CSSStyleSheet& sheet)
89{
90 // 1. Create a new CSS style sheet object and set its properties as specified.
91 // FIXME: We receive `sheet` from the caller already. This is weird.
92
93 sheet.set_parent_css_style_sheet(parent_style_sheet);
94 sheet.set_owner_css_rule(owner_rule);
95 sheet.set_owner_node(owner_node);
96 sheet.set_type(move(type));
97 sheet.set_media(move(media));
98 sheet.set_title(move(title));
99 sheet.set_alternate(alternate);
100 sheet.set_origin_clean(origin_clean);
101 sheet.set_location(move(location));
102
103 // 2. Then run the add a CSS style sheet steps for the newly created CSS style sheet.
104 add_a_css_style_sheet(document, sheet);
105}
106
107// The user agent must run the "update a style block" algorithm whenever one of the following conditions occur:
108// FIXME: The element is popped off the stack of open elements of an HTML parser or XML parser.
109//
110// NOTE: This is basically done by children_changed() today:
111// The element's children changed steps run.
112//
113// NOTE: This is basically done by inserted() and removed_from() today:
114// The element is not on the stack of open elements of an HTML parser or XML parser, and it becomes connected or disconnected.
115//
116// https://html.spec.whatwg.org/multipage/semantics.html#update-a-style-block
117void HTMLStyleElement::update_a_style_block()
118{
119 // 1. Let element be the style element.
120 // 2. If element has an associated CSS style sheet, remove the CSS style sheet in question.
121
122 if (m_associated_css_style_sheet) {
123 remove_a_css_style_sheet(document(), *m_associated_css_style_sheet);
124
125 // FIXME: This should probably be handled by StyleSheet::set_owner_node().
126 m_associated_css_style_sheet = nullptr;
127 }
128
129 // 3. If element is not connected, then return.
130 if (!is_connected())
131 return;
132
133 // 4. If element's type attribute is present and its value is neither the empty string nor an ASCII case-insensitive match for "text/css", then return.
134 auto type_attribute = attribute(HTML::AttributeNames::type);
135 if (!type_attribute.is_null() && !type_attribute.is_empty() && !Infra::is_ascii_case_insensitive_match(type_attribute, "text/css"sv))
136 return;
137
138 // FIXME: 5. If the Should element's inline behavior be blocked by Content Security Policy? algorithm returns "Blocked" when executed upon the style element, "style", and the style element's child text content, then return. [CSP]
139
140 // FIXME: This is a bit awkward, as the spec doesn't actually tell us when to parse the CSS text,
141 // so we just do it here and pass the parsed sheet to create_a_css_style_sheet().
142 auto sheet = parse_css_stylesheet(CSS::Parser::ParsingContext(document()), text_content());
143 if (!sheet)
144 return;
145
146 // FIXME: This should probably be handled by StyleSheet::set_owner_node().
147 m_associated_css_style_sheet = sheet;
148
149 // 6. Create a CSS style sheet with the following properties...
150 create_a_css_style_sheet(
151 document(),
152 "text/css"sv,
153 this,
154 attribute(HTML::AttributeNames::media),
155 in_a_document_tree() ? attribute(HTML::AttributeNames::title) : DeprecatedString::empty(),
156 false,
157 true,
158 {},
159 nullptr,
160 nullptr,
161 *sheet);
162}
163
164// https://www.w3.org/TR/cssom/#dom-linkstyle-sheet
165CSS::CSSStyleSheet* HTMLStyleElement::sheet()
166{
167 // The sheet attribute must return the associated CSS style sheet for the node or null if there is no associated CSS style sheet.
168 return m_associated_css_style_sheet;
169}
170
171// https://www.w3.org/TR/cssom/#dom-linkstyle-sheet
172CSS::CSSStyleSheet const* HTMLStyleElement::sheet() const
173{
174 // The sheet attribute must return the associated CSS style sheet for the node or null if there is no associated CSS style sheet.
175 return m_associated_css_style_sheet;
176}
177
178}