| 
									
										
										
										
											2019-10-08 15:33:58 +02:00
										 |  |  | #include <LibHTML/CSS/SelectorEngine.h>
 | 
					
						
							| 
									
										
										
										
											2019-06-27 17:47:59 +02:00
										 |  |  | #include <LibHTML/CSS/StyleResolver.h>
 | 
					
						
							|  |  |  | #include <LibHTML/CSS/StyleSheet.h>
 | 
					
						
							| 
									
										
										
										
											2019-06-28 21:17:34 +02:00
										 |  |  | #include <LibHTML/DOM/Document.h>
 | 
					
						
							| 
									
										
										
										
											2019-06-27 20:40:21 +02:00
										 |  |  | #include <LibHTML/DOM/Element.h>
 | 
					
						
							|  |  |  | #include <LibHTML/Dump.h>
 | 
					
						
							| 
									
										
										
										
											2019-09-30 20:25:33 +02:00
										 |  |  | #include <LibHTML/Parser/CSSParser.h>
 | 
					
						
							| 
									
										
										
										
											2019-11-18 11:49:19 +01:00
										 |  |  | #include <ctype.h>
 | 
					
						
							| 
									
										
										
										
											2019-06-27 20:40:21 +02:00
										 |  |  | #include <stdio.h>
 | 
					
						
							| 
									
										
										
										
											2019-06-27 17:47:59 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | StyleResolver::StyleResolver(Document& document) | 
					
						
							|  |  |  |     : m_document(document) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | StyleResolver::~StyleResolver() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-05 09:01:12 +02:00
										 |  |  | static StyleSheet& default_stylesheet() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     static StyleSheet* sheet; | 
					
						
							|  |  |  |     if (!sheet) { | 
					
						
							|  |  |  |         extern const char default_stylesheet_source[]; | 
					
						
							|  |  |  |         String css = default_stylesheet_source; | 
					
						
							| 
									
										
										
										
											2019-11-07 17:58:54 +01:00
										 |  |  |         sheet = parse_css(css).leak_ref(); | 
					
						
							| 
									
										
										
										
											2019-10-05 09:01:12 +02:00
										 |  |  |     } | 
					
						
							|  |  |  |     return *sheet; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | template<typename Callback> | 
					
						
							|  |  |  | void StyleResolver::for_each_stylesheet(Callback callback) const | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     callback(default_stylesheet()); | 
					
						
							|  |  |  |     for (auto& sheet : document().stylesheets()) { | 
					
						
							|  |  |  |         callback(sheet); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-27 20:40:21 +02:00
										 |  |  | NonnullRefPtrVector<StyleRule> StyleResolver::collect_matching_rules(const Element& element) const | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     NonnullRefPtrVector<StyleRule> matching_rules; | 
					
						
							| 
									
										
										
										
											2019-10-05 09:01:12 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     for_each_stylesheet([&](auto& sheet) { | 
					
						
							| 
									
										
										
										
											2019-06-27 20:40:21 +02:00
										 |  |  |         for (auto& rule : sheet.rules()) { | 
					
						
							|  |  |  |             for (auto& selector : rule.selectors()) { | 
					
						
							| 
									
										
										
										
											2019-10-08 15:33:58 +02:00
										 |  |  |                 if (SelectorEngine::matches(selector, element)) { | 
					
						
							| 
									
										
										
										
											2019-06-27 20:40:21 +02:00
										 |  |  |                     matching_rules.append(rule); | 
					
						
							|  |  |  |                     break; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2019-10-05 09:01:12 +02:00
										 |  |  |     }); | 
					
						
							| 
									
										
										
										
											2019-09-25 12:42:10 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | #ifdef HTML_DEBUG
 | 
					
						
							| 
									
										
										
										
											2019-10-03 10:25:00 +02:00
										 |  |  |     dbgprintf("Rules matching Element{%p}\n", &element); | 
					
						
							| 
									
										
										
										
											2019-06-27 20:40:21 +02:00
										 |  |  |     for (auto& rule : matching_rules) { | 
					
						
							|  |  |  |         dump_rule(rule); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-09-25 12:42:10 +03:00
										 |  |  | #endif
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-27 20:40:21 +02:00
										 |  |  |     return matching_rules; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-08 15:34:19 +02:00
										 |  |  | bool StyleResolver::is_inherited_property(CSS::PropertyID property_id) | 
					
						
							| 
									
										
										
										
											2019-10-04 22:52:49 +02:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2019-10-08 15:34:19 +02:00
										 |  |  |     static HashTable<CSS::PropertyID> inherited_properties; | 
					
						
							| 
									
										
										
										
											2019-10-04 22:52:49 +02:00
										 |  |  |     if (inherited_properties.is_empty()) { | 
					
						
							| 
									
										
										
										
											2019-10-08 15:34:19 +02:00
										 |  |  |         inherited_properties.set(CSS::PropertyID::BorderCollapse); | 
					
						
							|  |  |  |         inherited_properties.set(CSS::PropertyID::BorderSpacing); | 
					
						
							|  |  |  |         inherited_properties.set(CSS::PropertyID::Color); | 
					
						
							|  |  |  |         inherited_properties.set(CSS::PropertyID::FontFamily); | 
					
						
							|  |  |  |         inherited_properties.set(CSS::PropertyID::FontSize); | 
					
						
							|  |  |  |         inherited_properties.set(CSS::PropertyID::FontStyle); | 
					
						
							|  |  |  |         inherited_properties.set(CSS::PropertyID::FontVariant); | 
					
						
							|  |  |  |         inherited_properties.set(CSS::PropertyID::FontWeight); | 
					
						
							|  |  |  |         inherited_properties.set(CSS::PropertyID::LetterSpacing); | 
					
						
							|  |  |  |         inherited_properties.set(CSS::PropertyID::LineHeight); | 
					
						
							|  |  |  |         inherited_properties.set(CSS::PropertyID::ListStyle); | 
					
						
							|  |  |  |         inherited_properties.set(CSS::PropertyID::ListStyleImage); | 
					
						
							|  |  |  |         inherited_properties.set(CSS::PropertyID::ListStylePosition); | 
					
						
							|  |  |  |         inherited_properties.set(CSS::PropertyID::ListStyleType); | 
					
						
							|  |  |  |         inherited_properties.set(CSS::PropertyID::TextAlign); | 
					
						
							|  |  |  |         inherited_properties.set(CSS::PropertyID::TextIndent); | 
					
						
							|  |  |  |         inherited_properties.set(CSS::PropertyID::TextTransform); | 
					
						
							|  |  |  |         inherited_properties.set(CSS::PropertyID::Visibility); | 
					
						
							|  |  |  |         inherited_properties.set(CSS::PropertyID::WhiteSpace); | 
					
						
							|  |  |  |         inherited_properties.set(CSS::PropertyID::WordSpacing); | 
					
						
							| 
									
										
										
										
											2019-10-04 22:52:49 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         // FIXME: This property is not supposed to be inherited, but we currently
 | 
					
						
							|  |  |  |         //        rely on inheritance to propagate decorations into line boxes.
 | 
					
						
							| 
									
										
										
										
											2019-10-08 15:34:19 +02:00
										 |  |  |         inherited_properties.set(CSS::PropertyID::TextDecoration); | 
					
						
							| 
									
										
										
										
											2019-10-04 22:52:49 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-10-08 15:34:19 +02:00
										 |  |  |     return inherited_properties.contains(property_id); | 
					
						
							| 
									
										
										
										
											2019-10-04 22:52:49 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-18 11:49:19 +01:00
										 |  |  | static Vector<String> split_on_whitespace(const StringView& string) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (string.is_empty()) | 
					
						
							|  |  |  |         return {}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Vector<String> v; | 
					
						
							| 
									
										
										
										
											2019-12-09 17:45:40 +01:00
										 |  |  |     size_t substart = 0; | 
					
						
							|  |  |  |     for (size_t i = 0; i < string.length(); ++i) { | 
					
						
							| 
									
										
										
										
											2019-11-18 11:49:19 +01:00
										 |  |  |         char ch = string.characters_without_null_termination()[i]; | 
					
						
							|  |  |  |         if (isspace(ch)) { | 
					
						
							| 
									
										
										
										
											2019-12-09 17:45:40 +01:00
										 |  |  |             size_t sublen = i - substart; | 
					
						
							| 
									
										
										
										
											2019-11-18 11:49:19 +01:00
										 |  |  |             if (sublen != 0) | 
					
						
							|  |  |  |                 v.append(string.substring_view(substart, sublen)); | 
					
						
							|  |  |  |             substart = i + 1; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-12-09 17:45:40 +01:00
										 |  |  |     size_t taillen = string.length() - substart; | 
					
						
							| 
									
										
										
										
											2019-11-18 11:49:19 +01:00
										 |  |  |     if (taillen != 0) | 
					
						
							|  |  |  |         v.append(string.substring_view(substart, taillen)); | 
					
						
							|  |  |  |     return v; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void set_property_expanding_shorthands(StyleProperties& style, CSS::PropertyID property_id, const StyleValue& value) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2019-11-27 20:51:15 +01:00
										 |  |  |     if (property_id == CSS::PropertyID::BorderStyle) { | 
					
						
							|  |  |  |         style.set_property(CSS::PropertyID::BorderTopStyle, value); | 
					
						
							|  |  |  |         style.set_property(CSS::PropertyID::BorderRightStyle, value); | 
					
						
							|  |  |  |         style.set_property(CSS::PropertyID::BorderBottomStyle, value); | 
					
						
							|  |  |  |         style.set_property(CSS::PropertyID::BorderLeftStyle, value); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (property_id == CSS::PropertyID::BorderWidth) { | 
					
						
							|  |  |  |         style.set_property(CSS::PropertyID::BorderTopWidth, value); | 
					
						
							|  |  |  |         style.set_property(CSS::PropertyID::BorderRightWidth, value); | 
					
						
							|  |  |  |         style.set_property(CSS::PropertyID::BorderBottomWidth, value); | 
					
						
							|  |  |  |         style.set_property(CSS::PropertyID::BorderLeftWidth, value); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (property_id == CSS::PropertyID::BorderColor) { | 
					
						
							|  |  |  |         style.set_property(CSS::PropertyID::BorderTopColor, value); | 
					
						
							|  |  |  |         style.set_property(CSS::PropertyID::BorderRightColor, value); | 
					
						
							|  |  |  |         style.set_property(CSS::PropertyID::BorderBottomColor, value); | 
					
						
							|  |  |  |         style.set_property(CSS::PropertyID::BorderLeftColor, value); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-18 11:49:19 +01:00
										 |  |  |     if (property_id == CSS::PropertyID::Margin) { | 
					
						
							|  |  |  |         if (value.is_length()) { | 
					
						
							|  |  |  |             style.set_property(CSS::PropertyID::MarginTop, value); | 
					
						
							|  |  |  |             style.set_property(CSS::PropertyID::MarginRight, value); | 
					
						
							|  |  |  |             style.set_property(CSS::PropertyID::MarginBottom, value); | 
					
						
							|  |  |  |             style.set_property(CSS::PropertyID::MarginLeft, value); | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (value.is_string()) { | 
					
						
							|  |  |  |             auto parts = split_on_whitespace(value.to_string()); | 
					
						
							|  |  |  |             if (parts.size() == 2) { | 
					
						
							|  |  |  |                 auto vertical = parse_css_value(parts[0]); | 
					
						
							|  |  |  |                 auto horizontal = parse_css_value(parts[1]); | 
					
						
							|  |  |  |                 style.set_property(CSS::PropertyID::MarginTop, vertical); | 
					
						
							|  |  |  |                 style.set_property(CSS::PropertyID::MarginBottom, vertical); | 
					
						
							|  |  |  |                 style.set_property(CSS::PropertyID::MarginLeft, horizontal); | 
					
						
							|  |  |  |                 style.set_property(CSS::PropertyID::MarginRight, horizontal); | 
					
						
							|  |  |  |                 return; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (parts.size() == 3) { | 
					
						
							|  |  |  |                 auto top = parse_css_value(parts[0]); | 
					
						
							|  |  |  |                 auto horizontal = parse_css_value(parts[1]); | 
					
						
							|  |  |  |                 auto bottom = parse_css_value(parts[2]); | 
					
						
							|  |  |  |                 style.set_property(CSS::PropertyID::MarginTop, top); | 
					
						
							|  |  |  |                 style.set_property(CSS::PropertyID::MarginBottom, bottom); | 
					
						
							|  |  |  |                 style.set_property(CSS::PropertyID::MarginLeft, horizontal); | 
					
						
							|  |  |  |                 style.set_property(CSS::PropertyID::MarginRight, horizontal); | 
					
						
							|  |  |  |                 return; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (parts.size() == 4) { | 
					
						
							|  |  |  |                 auto top = parse_css_value(parts[0]); | 
					
						
							|  |  |  |                 auto right = parse_css_value(parts[1]); | 
					
						
							|  |  |  |                 auto bottom = parse_css_value(parts[2]); | 
					
						
							|  |  |  |                 auto left = parse_css_value(parts[3]); | 
					
						
							|  |  |  |                 style.set_property(CSS::PropertyID::MarginTop, top); | 
					
						
							|  |  |  |                 style.set_property(CSS::PropertyID::MarginBottom, bottom); | 
					
						
							|  |  |  |                 style.set_property(CSS::PropertyID::MarginLeft, left); | 
					
						
							|  |  |  |                 style.set_property(CSS::PropertyID::MarginRight, right); | 
					
						
							|  |  |  |                 return; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             dbg() << "Unsure what to do with CSS margin value '" << value.to_string() << "'"; | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-18 12:25:22 +01:00
										 |  |  |     if (property_id == CSS::PropertyID::Padding) { | 
					
						
							|  |  |  |         if (value.is_length()) { | 
					
						
							|  |  |  |             style.set_property(CSS::PropertyID::PaddingTop, value); | 
					
						
							|  |  |  |             style.set_property(CSS::PropertyID::PaddingRight, value); | 
					
						
							|  |  |  |             style.set_property(CSS::PropertyID::PaddingBottom, value); | 
					
						
							|  |  |  |             style.set_property(CSS::PropertyID::PaddingLeft, value); | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (value.is_string()) { | 
					
						
							|  |  |  |             auto parts = split_on_whitespace(value.to_string()); | 
					
						
							|  |  |  |             if (parts.size() == 2) { | 
					
						
							|  |  |  |                 auto vertical = parse_css_value(parts[0]); | 
					
						
							|  |  |  |                 auto horizontal = parse_css_value(parts[1]); | 
					
						
							|  |  |  |                 style.set_property(CSS::PropertyID::PaddingTop, vertical); | 
					
						
							|  |  |  |                 style.set_property(CSS::PropertyID::PaddingBottom, vertical); | 
					
						
							|  |  |  |                 style.set_property(CSS::PropertyID::PaddingLeft, horizontal); | 
					
						
							|  |  |  |                 style.set_property(CSS::PropertyID::PaddingRight, horizontal); | 
					
						
							|  |  |  |                 return; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (parts.size() == 3) { | 
					
						
							|  |  |  |                 auto top = parse_css_value(parts[0]); | 
					
						
							|  |  |  |                 auto horizontal = parse_css_value(parts[1]); | 
					
						
							|  |  |  |                 auto bottom = parse_css_value(parts[2]); | 
					
						
							|  |  |  |                 style.set_property(CSS::PropertyID::PaddingTop, top); | 
					
						
							|  |  |  |                 style.set_property(CSS::PropertyID::PaddingBottom, bottom); | 
					
						
							|  |  |  |                 style.set_property(CSS::PropertyID::PaddingLeft, horizontal); | 
					
						
							|  |  |  |                 style.set_property(CSS::PropertyID::PaddingRight, horizontal); | 
					
						
							|  |  |  |                 return; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (parts.size() == 4) { | 
					
						
							|  |  |  |                 auto top = parse_css_value(parts[0]); | 
					
						
							|  |  |  |                 auto right = parse_css_value(parts[1]); | 
					
						
							|  |  |  |                 auto bottom = parse_css_value(parts[2]); | 
					
						
							|  |  |  |                 auto left = parse_css_value(parts[3]); | 
					
						
							|  |  |  |                 style.set_property(CSS::PropertyID::PaddingTop, top); | 
					
						
							|  |  |  |                 style.set_property(CSS::PropertyID::PaddingBottom, bottom); | 
					
						
							|  |  |  |                 style.set_property(CSS::PropertyID::PaddingLeft, left); | 
					
						
							|  |  |  |                 style.set_property(CSS::PropertyID::PaddingRight, right); | 
					
						
							|  |  |  |                 return; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             dbg() << "Unsure what to do with CSS padding value '" << value.to_string() << "'"; | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-18 11:49:19 +01:00
										 |  |  |     style.set_property(property_id, value); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-07 09:23:53 +02:00
										 |  |  | NonnullRefPtr<StyleProperties> StyleResolver::resolve_style(const Element& element, const StyleProperties* parent_style) const | 
					
						
							| 
									
										
										
										
											2019-06-27 17:47:59 +02:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2019-10-07 09:23:53 +02:00
										 |  |  |     auto style = StyleProperties::create(); | 
					
						
							| 
									
										
										
										
											2019-09-25 12:33:28 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-07 09:23:53 +02:00
										 |  |  |     if (parent_style) { | 
					
						
							| 
									
										
										
										
											2019-10-08 15:34:19 +02:00
										 |  |  |         parent_style->for_each_property([&](auto property_id, auto& value) { | 
					
						
							|  |  |  |             if (is_inherited_property(property_id)) | 
					
						
							| 
									
										
										
										
											2019-11-18 11:49:19 +01:00
										 |  |  |                 set_property_expanding_shorthands(style, property_id, value); | 
					
						
							| 
									
										
										
										
											2019-09-25 12:33:28 +03:00
										 |  |  |         }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-07 09:23:53 +02:00
										 |  |  |     element.apply_presentational_hints(*style); | 
					
						
							| 
									
										
										
										
											2019-10-04 21:05:52 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-27 20:40:21 +02:00
										 |  |  |     auto matching_rules = collect_matching_rules(element); | 
					
						
							| 
									
										
										
										
											2019-06-28 21:17:34 +02:00
										 |  |  |     for (auto& rule : matching_rules) { | 
					
						
							| 
									
										
										
										
											2019-09-30 20:06:17 +02:00
										 |  |  |         for (auto& property : rule.declaration().properties()) { | 
					
						
							| 
									
										
										
										
											2019-11-18 11:49:19 +01:00
										 |  |  |             set_property_expanding_shorthands(style, property.property_id, property.value); | 
					
						
							| 
									
										
										
										
											2019-06-28 21:17:34 +02:00
										 |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-09-30 20:25:33 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     auto style_attribute = element.attribute("style"); | 
					
						
							|  |  |  |     if (!style_attribute.is_null()) { | 
					
						
							|  |  |  |         if (auto declaration = parse_css_declaration(style_attribute)) { | 
					
						
							|  |  |  |             for (auto& property : declaration->properties()) { | 
					
						
							| 
									
										
										
										
											2019-11-18 11:49:19 +01:00
										 |  |  |                 set_property_expanding_shorthands(style, property.property_id, property.value); | 
					
						
							| 
									
										
										
										
											2019-09-30 20:25:33 +02:00
										 |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-07 09:23:53 +02:00
										 |  |  |     return style; | 
					
						
							| 
									
										
										
										
											2019-06-27 17:47:59 +02:00
										 |  |  | } |