Serenity Operating System
at hosted 386 lines 16 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 <LibWeb/CSS/SelectorEngine.h> 28#include <LibWeb/CSS/StyleResolver.h> 29#include <LibWeb/CSS/StyleSheet.h> 30#include <LibWeb/DOM/Document.h> 31#include <LibWeb/DOM/Element.h> 32#include <LibWeb/Dump.h> 33#include <LibWeb/Parser/CSSParser.h> 34#include <ctype.h> 35#include <stdio.h> 36 37namespace Web { 38 39StyleResolver::StyleResolver(Document& document) 40 : m_document(document) 41{ 42} 43 44StyleResolver::~StyleResolver() 45{ 46} 47 48static StyleSheet& default_stylesheet() 49{ 50 static StyleSheet* sheet; 51 if (!sheet) { 52 extern const char default_stylesheet_source[]; 53 String css = default_stylesheet_source; 54 sheet = parse_css(css).leak_ref(); 55 } 56 return *sheet; 57} 58 59template<typename Callback> 60void StyleResolver::for_each_stylesheet(Callback callback) const 61{ 62 callback(default_stylesheet()); 63 for (auto& sheet : document().stylesheets()) { 64 callback(sheet); 65 } 66} 67 68NonnullRefPtrVector<StyleRule> StyleResolver::collect_matching_rules(const Element& element) const 69{ 70 NonnullRefPtrVector<StyleRule> matching_rules; 71 72 for_each_stylesheet([&](auto& sheet) { 73 for (auto& rule : sheet.rules()) { 74 for (auto& selector : rule.selectors()) { 75 if (SelectorEngine::matches(selector, element)) { 76 matching_rules.append(rule); 77 break; 78 } 79 } 80 } 81 }); 82 83#ifdef HTML_DEBUG 84 dbgprintf("Rules matching Element{%p}\n", &element); 85 for (auto& rule : matching_rules) { 86 dump_rule(rule); 87 } 88#endif 89 90 return matching_rules; 91} 92 93bool StyleResolver::is_inherited_property(CSS::PropertyID property_id) 94{ 95 static HashTable<CSS::PropertyID> inherited_properties; 96 if (inherited_properties.is_empty()) { 97 inherited_properties.set(CSS::PropertyID::BorderCollapse); 98 inherited_properties.set(CSS::PropertyID::BorderSpacing); 99 inherited_properties.set(CSS::PropertyID::Color); 100 inherited_properties.set(CSS::PropertyID::FontFamily); 101 inherited_properties.set(CSS::PropertyID::FontSize); 102 inherited_properties.set(CSS::PropertyID::FontStyle); 103 inherited_properties.set(CSS::PropertyID::FontVariant); 104 inherited_properties.set(CSS::PropertyID::FontWeight); 105 inherited_properties.set(CSS::PropertyID::LetterSpacing); 106 inherited_properties.set(CSS::PropertyID::LineHeight); 107 inherited_properties.set(CSS::PropertyID::ListStyle); 108 inherited_properties.set(CSS::PropertyID::ListStyleImage); 109 inherited_properties.set(CSS::PropertyID::ListStylePosition); 110 inherited_properties.set(CSS::PropertyID::ListStyleType); 111 inherited_properties.set(CSS::PropertyID::TextAlign); 112 inherited_properties.set(CSS::PropertyID::TextIndent); 113 inherited_properties.set(CSS::PropertyID::TextTransform); 114 inherited_properties.set(CSS::PropertyID::Visibility); 115 inherited_properties.set(CSS::PropertyID::WhiteSpace); 116 inherited_properties.set(CSS::PropertyID::WordSpacing); 117 118 // FIXME: This property is not supposed to be inherited, but we currently 119 // rely on inheritance to propagate decorations into line boxes. 120 inherited_properties.set(CSS::PropertyID::TextDecoration); 121 } 122 return inherited_properties.contains(property_id); 123} 124 125static Vector<String> split_on_whitespace(const StringView& string) 126{ 127 if (string.is_empty()) 128 return {}; 129 130 Vector<String> v; 131 size_t substart = 0; 132 for (size_t i = 0; i < string.length(); ++i) { 133 char ch = string.characters_without_null_termination()[i]; 134 if (isspace(ch)) { 135 size_t sublen = i - substart; 136 if (sublen != 0) 137 v.append(string.substring_view(substart, sublen)); 138 substart = i + 1; 139 } 140 } 141 size_t taillen = string.length() - substart; 142 if (taillen != 0) 143 v.append(string.substring_view(substart, taillen)); 144 return v; 145} 146 147static inline void set_property_border_width(StyleProperties& style, const StyleValue& value) 148{ 149 ASSERT(value.is_length()); 150 style.set_property(CSS::PropertyID::BorderTopWidth, value); 151 style.set_property(CSS::PropertyID::BorderRightWidth, value); 152 style.set_property(CSS::PropertyID::BorderBottomWidth, value); 153 style.set_property(CSS::PropertyID::BorderLeftWidth, value); 154} 155 156static inline void set_property_border_color(StyleProperties& style, const StyleValue& value) 157{ 158 ASSERT(value.is_color()); 159 style.set_property(CSS::PropertyID::BorderTopColor, value); 160 style.set_property(CSS::PropertyID::BorderRightColor, value); 161 style.set_property(CSS::PropertyID::BorderBottomColor, value); 162 style.set_property(CSS::PropertyID::BorderLeftColor, value); 163} 164 165static inline void set_property_border_style(StyleProperties& style, const StyleValue& value) 166{ 167 ASSERT(value.is_string()); 168 style.set_property(CSS::PropertyID::BorderTopStyle, value); 169 style.set_property(CSS::PropertyID::BorderRightStyle, value); 170 style.set_property(CSS::PropertyID::BorderBottomStyle, value); 171 style.set_property(CSS::PropertyID::BorderLeftStyle, value); 172} 173 174static void set_property_expanding_shorthands(StyleProperties& style, CSS::PropertyID property_id, const StyleValue& value) 175{ 176 if (property_id == CSS::PropertyID::Border) { 177 auto parts = split_on_whitespace(value.to_string()); 178 if (value.is_length()) { 179 set_property_border_width(style, value); 180 return; 181 } 182 if (value.is_color()) { 183 set_property_border_color(style, value); 184 return; 185 } 186 if (value.is_string()) { 187 auto parts = split_on_whitespace(value.to_string()); 188 189 if (parts.size() == 1) { 190 if (auto value = parse_line_style(parts[0])) { 191 set_property_border_style(style, value.release_nonnull()); 192 set_property_border_color(style, ColorStyleValue::create(Gfx::Color::Black)); 193 set_property_border_width(style, LengthStyleValue::create(Length(3, Length::Type::Absolute))); 194 return; 195 } 196 } 197 198 RefPtr<LengthStyleValue> line_width_value; 199 RefPtr<ColorStyleValue> color_value; 200 RefPtr<StringStyleValue> line_style_value; 201 202 for (auto& part : parts) { 203 if (auto value = parse_line_width(part)) { 204 if (line_width_value) 205 return; 206 line_width_value = move(value); 207 continue; 208 } 209 if (auto value = parse_color(part)) { 210 if (color_value) 211 return; 212 color_value = move(value); 213 continue; 214 } 215 if (auto value = parse_line_style(part)) { 216 if (line_style_value) 217 return; 218 line_style_value = move(value); 219 continue; 220 } 221 } 222 223 if (line_width_value) 224 set_property_border_width(style, line_width_value.release_nonnull()); 225 if (color_value) 226 set_property_border_color(style, color_value.release_nonnull()); 227 if (line_style_value) 228 set_property_border_style(style, line_style_value.release_nonnull()); 229 230 return; 231 } 232 return; 233 } 234 235 if (property_id == CSS::PropertyID::BorderStyle) { 236 style.set_property(CSS::PropertyID::BorderTopStyle, value); 237 style.set_property(CSS::PropertyID::BorderRightStyle, value); 238 style.set_property(CSS::PropertyID::BorderBottomStyle, value); 239 style.set_property(CSS::PropertyID::BorderLeftStyle, value); 240 return; 241 } 242 243 if (property_id == CSS::PropertyID::BorderWidth) { 244 style.set_property(CSS::PropertyID::BorderTopWidth, value); 245 style.set_property(CSS::PropertyID::BorderRightWidth, value); 246 style.set_property(CSS::PropertyID::BorderBottomWidth, value); 247 style.set_property(CSS::PropertyID::BorderLeftWidth, value); 248 return; 249 } 250 251 if (property_id == CSS::PropertyID::BorderColor) { 252 style.set_property(CSS::PropertyID::BorderTopColor, value); 253 style.set_property(CSS::PropertyID::BorderRightColor, value); 254 style.set_property(CSS::PropertyID::BorderBottomColor, value); 255 style.set_property(CSS::PropertyID::BorderLeftColor, value); 256 return; 257 } 258 259 if (property_id == CSS::PropertyID::Margin) { 260 if (value.is_length()) { 261 style.set_property(CSS::PropertyID::MarginTop, value); 262 style.set_property(CSS::PropertyID::MarginRight, value); 263 style.set_property(CSS::PropertyID::MarginBottom, value); 264 style.set_property(CSS::PropertyID::MarginLeft, value); 265 return; 266 } 267 if (value.is_string()) { 268 auto parts = split_on_whitespace(value.to_string()); 269 if (parts.size() == 2) { 270 auto vertical = parse_css_value(parts[0]); 271 auto horizontal = parse_css_value(parts[1]); 272 style.set_property(CSS::PropertyID::MarginTop, vertical); 273 style.set_property(CSS::PropertyID::MarginBottom, vertical); 274 style.set_property(CSS::PropertyID::MarginLeft, horizontal); 275 style.set_property(CSS::PropertyID::MarginRight, horizontal); 276 return; 277 } 278 if (parts.size() == 3) { 279 auto top = parse_css_value(parts[0]); 280 auto horizontal = parse_css_value(parts[1]); 281 auto bottom = parse_css_value(parts[2]); 282 style.set_property(CSS::PropertyID::MarginTop, top); 283 style.set_property(CSS::PropertyID::MarginBottom, bottom); 284 style.set_property(CSS::PropertyID::MarginLeft, horizontal); 285 style.set_property(CSS::PropertyID::MarginRight, horizontal); 286 return; 287 } 288 if (parts.size() == 4) { 289 auto top = parse_css_value(parts[0]); 290 auto right = parse_css_value(parts[1]); 291 auto bottom = parse_css_value(parts[2]); 292 auto left = parse_css_value(parts[3]); 293 style.set_property(CSS::PropertyID::MarginTop, top); 294 style.set_property(CSS::PropertyID::MarginBottom, bottom); 295 style.set_property(CSS::PropertyID::MarginLeft, left); 296 style.set_property(CSS::PropertyID::MarginRight, right); 297 return; 298 } 299 dbg() << "Unsure what to do with CSS margin value '" << value.to_string() << "'"; 300 return; 301 } 302 return; 303 } 304 305 if (property_id == CSS::PropertyID::Padding) { 306 if (value.is_length()) { 307 style.set_property(CSS::PropertyID::PaddingTop, value); 308 style.set_property(CSS::PropertyID::PaddingRight, value); 309 style.set_property(CSS::PropertyID::PaddingBottom, value); 310 style.set_property(CSS::PropertyID::PaddingLeft, value); 311 return; 312 } 313 if (value.is_string()) { 314 auto parts = split_on_whitespace(value.to_string()); 315 if (parts.size() == 2) { 316 auto vertical = parse_css_value(parts[0]); 317 auto horizontal = parse_css_value(parts[1]); 318 style.set_property(CSS::PropertyID::PaddingTop, vertical); 319 style.set_property(CSS::PropertyID::PaddingBottom, vertical); 320 style.set_property(CSS::PropertyID::PaddingLeft, horizontal); 321 style.set_property(CSS::PropertyID::PaddingRight, horizontal); 322 return; 323 } 324 if (parts.size() == 3) { 325 auto top = parse_css_value(parts[0]); 326 auto horizontal = parse_css_value(parts[1]); 327 auto bottom = parse_css_value(parts[2]); 328 style.set_property(CSS::PropertyID::PaddingTop, top); 329 style.set_property(CSS::PropertyID::PaddingBottom, bottom); 330 style.set_property(CSS::PropertyID::PaddingLeft, horizontal); 331 style.set_property(CSS::PropertyID::PaddingRight, horizontal); 332 return; 333 } 334 if (parts.size() == 4) { 335 auto top = parse_css_value(parts[0]); 336 auto right = parse_css_value(parts[1]); 337 auto bottom = parse_css_value(parts[2]); 338 auto left = parse_css_value(parts[3]); 339 style.set_property(CSS::PropertyID::PaddingTop, top); 340 style.set_property(CSS::PropertyID::PaddingBottom, bottom); 341 style.set_property(CSS::PropertyID::PaddingLeft, left); 342 style.set_property(CSS::PropertyID::PaddingRight, right); 343 return; 344 } 345 dbg() << "Unsure what to do with CSS padding value '" << value.to_string() << "'"; 346 return; 347 } 348 return; 349 } 350 351 style.set_property(property_id, value); 352} 353 354NonnullRefPtr<StyleProperties> StyleResolver::resolve_style(const Element& element, const StyleProperties* parent_style) const 355{ 356 auto style = StyleProperties::create(); 357 358 if (parent_style) { 359 parent_style->for_each_property([&](auto property_id, auto& value) { 360 if (is_inherited_property(property_id)) 361 set_property_expanding_shorthands(style, property_id, value); 362 }); 363 } 364 365 element.apply_presentational_hints(*style); 366 367 auto matching_rules = collect_matching_rules(element); 368 for (auto& rule : matching_rules) { 369 for (auto& property : rule.declaration().properties()) { 370 set_property_expanding_shorthands(style, property.property_id, property.value); 371 } 372 } 373 374 auto style_attribute = element.attribute("style"); 375 if (!style_attribute.is_null()) { 376 if (auto declaration = parse_css_declaration(style_attribute)) { 377 for (auto& property : declaration->properties()) { 378 set_property_expanding_shorthands(style, property.property_id, property.value); 379 } 380 } 381 } 382 383 return style; 384} 385 386}