summaryrefslogtreecommitdiff
path: root/Libraries/LibHTML/CSS/StyleResolver.cpp
blob: 081675a94598c84407d9288caaaec03554e9a227 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
#include <LibHTML/CSS/StyleResolver.h>
#include <LibHTML/CSS/StyleSheet.h>
#include <LibHTML/DOM/Document.h>
#include <LibHTML/DOM/Element.h>
#include <LibHTML/Dump.h>
#include <LibHTML/Parser/CSSParser.h>
#include <stdio.h>

StyleResolver::StyleResolver(Document& document)
    : m_document(document)
{
}

StyleResolver::~StyleResolver()
{
}

static bool matches(const Selector& selector, const Element& element)
{
    // FIXME: Support compound selectors.
    ASSERT(selector.components().size() == 1);

    auto& component = selector.components().first();
    switch (component.type) {
    case Selector::Component::Type::Id:
        return component.value == element.attribute("id");
    case Selector::Component::Type::Class:
        return element.has_class(component.value);
    case Selector::Component::Type::TagName:
        return component.value == element.tag_name();
    default:
        ASSERT_NOT_REACHED();
    }
}

static StyleSheet& default_stylesheet()
{
    static StyleSheet* sheet;
    if (!sheet) {
        extern const char default_stylesheet_source[];
        String css = default_stylesheet_source;
        sheet = &parse_css(css).leak_ref();
    }
    return *sheet;
}

template<typename Callback>
void StyleResolver::for_each_stylesheet(Callback callback) const
{
    callback(default_stylesheet());
    for (auto& sheet : document().stylesheets()) {
        callback(sheet);
    }
}

NonnullRefPtrVector<StyleRule> StyleResolver::collect_matching_rules(const Element& element) const
{
    NonnullRefPtrVector<StyleRule> matching_rules;

    for_each_stylesheet([&](auto& sheet) {
        for (auto& rule : sheet.rules()) {
            for (auto& selector : rule.selectors()) {
                if (matches(selector, element)) {
                    matching_rules.append(rule);
                    break;
                }
            }
        }
    });

#ifdef HTML_DEBUG
    dbgprintf("Rules matching Element{%p}\n", &element);
    for (auto& rule : matching_rules) {
        dump_rule(rule);
    }
#endif

    return matching_rules;
}

static bool is_inherited_property(const StringView& name)
{
    static HashTable<String> inherited_properties;
    if (inherited_properties.is_empty()) {
        inherited_properties.set("border-collapse");
        inherited_properties.set("border-spacing");
        inherited_properties.set("color");
        inherited_properties.set("font-family");
        inherited_properties.set("font-size");
        inherited_properties.set("font-style");
        inherited_properties.set("font-variant");
        inherited_properties.set("font-weight");
        inherited_properties.set("font");
        inherited_properties.set("letter-spacing");
        inherited_properties.set("line-height");
        inherited_properties.set("list-style-image");
        inherited_properties.set("list-style-position");
        inherited_properties.set("list-style-type");
        inherited_properties.set("list-style");
        inherited_properties.set("text-align");
        inherited_properties.set("text-indent");
        inherited_properties.set("text-transform");
        inherited_properties.set("visibility");
        inherited_properties.set("white-space");
        inherited_properties.set("word-spacing");

        // FIXME: This property is not supposed to be inherited, but we currently
        //        rely on inheritance to propagate decorations into line boxes.
        inherited_properties.set("text-decoraton");
    }
    return inherited_properties.contains(name);
}

NonnullRefPtr<StyleProperties> StyleResolver::resolve_style(const Element& element, const StyleProperties* parent_properties) const
{
    auto style_properties = StyleProperties::create();

    if (parent_properties) {
        parent_properties->for_each_property([&](const StringView& name, auto& value) {
            if (is_inherited_property(name))
                style_properties->set_property(name, value);
        });
    }

    element.apply_presentational_hints(*style_properties);

    auto matching_rules = collect_matching_rules(element);
    for (auto& rule : matching_rules) {
        for (auto& property : rule.declaration().properties()) {
            style_properties->set_property(property.name, property.value);
        }
    }

    auto style_attribute = element.attribute("style");
    if (!style_attribute.is_null()) {
        if (auto declaration = parse_css_declaration(style_attribute)) {
            for (auto& property : declaration->properties()) {
                style_properties->set_property(property.name, property.value);
            }
        }
    }

    return style_properties;
}