Serenity Operating System
at portability 296 lines 12 kB view raw
1/* 2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright notice, this 9 * list of conditions and the following disclaimer. 10 * 11 * 2. Redistributions in binary form must reproduce the above copyright notice, 12 * this list of conditions and the following disclaimer in the documentation 13 * and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27#include <LibHTML/CSS/SelectorEngine.h> 28#include <LibHTML/CSS/StyleResolver.h> 29#include <LibHTML/CSS/StyleSheet.h> 30#include <LibHTML/DOM/Document.h> 31#include <LibHTML/DOM/Element.h> 32#include <LibHTML/Dump.h> 33#include <LibHTML/Parser/CSSParser.h> 34#include <ctype.h> 35#include <stdio.h> 36 37StyleResolver::StyleResolver(Document& document) 38 : m_document(document) 39{ 40} 41 42StyleResolver::~StyleResolver() 43{ 44} 45 46static StyleSheet& default_stylesheet() 47{ 48 static StyleSheet* sheet; 49 if (!sheet) { 50 extern const char default_stylesheet_source[]; 51 String css = default_stylesheet_source; 52 sheet = parse_css(css).leak_ref(); 53 } 54 return *sheet; 55} 56 57template<typename Callback> 58void StyleResolver::for_each_stylesheet(Callback callback) const 59{ 60 callback(default_stylesheet()); 61 for (auto& sheet : document().stylesheets()) { 62 callback(sheet); 63 } 64} 65 66NonnullRefPtrVector<StyleRule> StyleResolver::collect_matching_rules(const Element& element) const 67{ 68 NonnullRefPtrVector<StyleRule> matching_rules; 69 70 for_each_stylesheet([&](auto& sheet) { 71 for (auto& rule : sheet.rules()) { 72 for (auto& selector : rule.selectors()) { 73 if (SelectorEngine::matches(selector, element)) { 74 matching_rules.append(rule); 75 break; 76 } 77 } 78 } 79 }); 80 81#ifdef HTML_DEBUG 82 dbgprintf("Rules matching Element{%p}\n", &element); 83 for (auto& rule : matching_rules) { 84 dump_rule(rule); 85 } 86#endif 87 88 return matching_rules; 89} 90 91bool StyleResolver::is_inherited_property(CSS::PropertyID property_id) 92{ 93 static HashTable<CSS::PropertyID> inherited_properties; 94 if (inherited_properties.is_empty()) { 95 inherited_properties.set(CSS::PropertyID::BorderCollapse); 96 inherited_properties.set(CSS::PropertyID::BorderSpacing); 97 inherited_properties.set(CSS::PropertyID::Color); 98 inherited_properties.set(CSS::PropertyID::FontFamily); 99 inherited_properties.set(CSS::PropertyID::FontSize); 100 inherited_properties.set(CSS::PropertyID::FontStyle); 101 inherited_properties.set(CSS::PropertyID::FontVariant); 102 inherited_properties.set(CSS::PropertyID::FontWeight); 103 inherited_properties.set(CSS::PropertyID::LetterSpacing); 104 inherited_properties.set(CSS::PropertyID::LineHeight); 105 inherited_properties.set(CSS::PropertyID::ListStyle); 106 inherited_properties.set(CSS::PropertyID::ListStyleImage); 107 inherited_properties.set(CSS::PropertyID::ListStylePosition); 108 inherited_properties.set(CSS::PropertyID::ListStyleType); 109 inherited_properties.set(CSS::PropertyID::TextAlign); 110 inherited_properties.set(CSS::PropertyID::TextIndent); 111 inherited_properties.set(CSS::PropertyID::TextTransform); 112 inherited_properties.set(CSS::PropertyID::Visibility); 113 inherited_properties.set(CSS::PropertyID::WhiteSpace); 114 inherited_properties.set(CSS::PropertyID::WordSpacing); 115 116 // FIXME: This property is not supposed to be inherited, but we currently 117 // rely on inheritance to propagate decorations into line boxes. 118 inherited_properties.set(CSS::PropertyID::TextDecoration); 119 } 120 return inherited_properties.contains(property_id); 121} 122 123static Vector<String> split_on_whitespace(const StringView& string) 124{ 125 if (string.is_empty()) 126 return {}; 127 128 Vector<String> v; 129 size_t substart = 0; 130 for (size_t i = 0; i < string.length(); ++i) { 131 char ch = string.characters_without_null_termination()[i]; 132 if (isspace(ch)) { 133 size_t sublen = i - substart; 134 if (sublen != 0) 135 v.append(string.substring_view(substart, sublen)); 136 substart = i + 1; 137 } 138 } 139 size_t taillen = string.length() - substart; 140 if (taillen != 0) 141 v.append(string.substring_view(substart, taillen)); 142 return v; 143} 144 145static void set_property_expanding_shorthands(StyleProperties& style, CSS::PropertyID property_id, const StyleValue& value) 146{ 147 if (property_id == CSS::PropertyID::BorderStyle) { 148 style.set_property(CSS::PropertyID::BorderTopStyle, value); 149 style.set_property(CSS::PropertyID::BorderRightStyle, value); 150 style.set_property(CSS::PropertyID::BorderBottomStyle, value); 151 style.set_property(CSS::PropertyID::BorderLeftStyle, value); 152 return; 153 } 154 155 if (property_id == CSS::PropertyID::BorderWidth) { 156 style.set_property(CSS::PropertyID::BorderTopWidth, value); 157 style.set_property(CSS::PropertyID::BorderRightWidth, value); 158 style.set_property(CSS::PropertyID::BorderBottomWidth, value); 159 style.set_property(CSS::PropertyID::BorderLeftWidth, value); 160 return; 161 } 162 163 if (property_id == CSS::PropertyID::BorderColor) { 164 style.set_property(CSS::PropertyID::BorderTopColor, value); 165 style.set_property(CSS::PropertyID::BorderRightColor, value); 166 style.set_property(CSS::PropertyID::BorderBottomColor, value); 167 style.set_property(CSS::PropertyID::BorderLeftColor, value); 168 return; 169 } 170 171 if (property_id == CSS::PropertyID::Margin) { 172 if (value.is_length()) { 173 style.set_property(CSS::PropertyID::MarginTop, value); 174 style.set_property(CSS::PropertyID::MarginRight, value); 175 style.set_property(CSS::PropertyID::MarginBottom, value); 176 style.set_property(CSS::PropertyID::MarginLeft, value); 177 return; 178 } 179 if (value.is_string()) { 180 auto parts = split_on_whitespace(value.to_string()); 181 if (parts.size() == 2) { 182 auto vertical = parse_css_value(parts[0]); 183 auto horizontal = parse_css_value(parts[1]); 184 style.set_property(CSS::PropertyID::MarginTop, vertical); 185 style.set_property(CSS::PropertyID::MarginBottom, vertical); 186 style.set_property(CSS::PropertyID::MarginLeft, horizontal); 187 style.set_property(CSS::PropertyID::MarginRight, horizontal); 188 return; 189 } 190 if (parts.size() == 3) { 191 auto top = parse_css_value(parts[0]); 192 auto horizontal = parse_css_value(parts[1]); 193 auto bottom = parse_css_value(parts[2]); 194 style.set_property(CSS::PropertyID::MarginTop, top); 195 style.set_property(CSS::PropertyID::MarginBottom, bottom); 196 style.set_property(CSS::PropertyID::MarginLeft, horizontal); 197 style.set_property(CSS::PropertyID::MarginRight, horizontal); 198 return; 199 } 200 if (parts.size() == 4) { 201 auto top = parse_css_value(parts[0]); 202 auto right = parse_css_value(parts[1]); 203 auto bottom = parse_css_value(parts[2]); 204 auto left = parse_css_value(parts[3]); 205 style.set_property(CSS::PropertyID::MarginTop, top); 206 style.set_property(CSS::PropertyID::MarginBottom, bottom); 207 style.set_property(CSS::PropertyID::MarginLeft, left); 208 style.set_property(CSS::PropertyID::MarginRight, right); 209 return; 210 } 211 dbg() << "Unsure what to do with CSS margin value '" << value.to_string() << "'"; 212 return; 213 } 214 return; 215 } 216 217 if (property_id == CSS::PropertyID::Padding) { 218 if (value.is_length()) { 219 style.set_property(CSS::PropertyID::PaddingTop, value); 220 style.set_property(CSS::PropertyID::PaddingRight, value); 221 style.set_property(CSS::PropertyID::PaddingBottom, value); 222 style.set_property(CSS::PropertyID::PaddingLeft, value); 223 return; 224 } 225 if (value.is_string()) { 226 auto parts = split_on_whitespace(value.to_string()); 227 if (parts.size() == 2) { 228 auto vertical = parse_css_value(parts[0]); 229 auto horizontal = parse_css_value(parts[1]); 230 style.set_property(CSS::PropertyID::PaddingTop, vertical); 231 style.set_property(CSS::PropertyID::PaddingBottom, vertical); 232 style.set_property(CSS::PropertyID::PaddingLeft, horizontal); 233 style.set_property(CSS::PropertyID::PaddingRight, horizontal); 234 return; 235 } 236 if (parts.size() == 3) { 237 auto top = parse_css_value(parts[0]); 238 auto horizontal = parse_css_value(parts[1]); 239 auto bottom = parse_css_value(parts[2]); 240 style.set_property(CSS::PropertyID::PaddingTop, top); 241 style.set_property(CSS::PropertyID::PaddingBottom, bottom); 242 style.set_property(CSS::PropertyID::PaddingLeft, horizontal); 243 style.set_property(CSS::PropertyID::PaddingRight, horizontal); 244 return; 245 } 246 if (parts.size() == 4) { 247 auto top = parse_css_value(parts[0]); 248 auto right = parse_css_value(parts[1]); 249 auto bottom = parse_css_value(parts[2]); 250 auto left = parse_css_value(parts[3]); 251 style.set_property(CSS::PropertyID::PaddingTop, top); 252 style.set_property(CSS::PropertyID::PaddingBottom, bottom); 253 style.set_property(CSS::PropertyID::PaddingLeft, left); 254 style.set_property(CSS::PropertyID::PaddingRight, right); 255 return; 256 } 257 dbg() << "Unsure what to do with CSS padding value '" << value.to_string() << "'"; 258 return; 259 } 260 return; 261 } 262 263 style.set_property(property_id, value); 264} 265 266NonnullRefPtr<StyleProperties> StyleResolver::resolve_style(const Element& element, const StyleProperties* parent_style) const 267{ 268 auto style = StyleProperties::create(); 269 270 if (parent_style) { 271 parent_style->for_each_property([&](auto property_id, auto& value) { 272 if (is_inherited_property(property_id)) 273 set_property_expanding_shorthands(style, property_id, value); 274 }); 275 } 276 277 element.apply_presentational_hints(*style); 278 279 auto matching_rules = collect_matching_rules(element); 280 for (auto& rule : matching_rules) { 281 for (auto& property : rule.declaration().properties()) { 282 set_property_expanding_shorthands(style, property.property_id, property.value); 283 } 284 } 285 286 auto style_attribute = element.attribute("style"); 287 if (!style_attribute.is_null()) { 288 if (auto declaration = parse_css_declaration(style_attribute)) { 289 for (auto& property : declaration->properties()) { 290 set_property_expanding_shorthands(style, property.property_id, property.value); 291 } 292 } 293 } 294 295 return style; 296}