CSSParser.cpp 32 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013
  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. #include <AK/HashMap.h>
  27. #include <LibWeb/CSS/CSSImportRule.h>
  28. #include <LibWeb/CSS/CSSRule.h>
  29. #include <LibWeb/CSS/CSSStyleRule.h>
  30. #include <LibWeb/CSS/Parser/CSSParser.h>
  31. #include <LibWeb/CSS/PropertyID.h>
  32. #include <LibWeb/CSS/StyleSheet.h>
  33. #include <LibWeb/DOM/Document.h>
  34. #include <ctype.h>
  35. #include <stdlib.h>
  36. #include <string.h>
  37. #define PARSE_VERIFY(x) \
  38. if (!(x)) { \
  39. dbgln("CSS PARSER ASSERTION FAILED: {}", #x); \
  40. dbgln("At character# {} in CSS: _{}_", index, css); \
  41. VERIFY_NOT_REACHED(); \
  42. }
  43. #define PARSE_ERROR() \
  44. do { \
  45. dbgln("CSS parse error"); \
  46. } while (0)
  47. namespace Web {
  48. namespace CSS {
  49. ParsingContext::ParsingContext()
  50. {
  51. }
  52. ParsingContext::ParsingContext(const DOM::Document& document)
  53. : m_document(&document)
  54. {
  55. }
  56. ParsingContext::ParsingContext(const DOM::ParentNode& parent_node)
  57. : m_document(&parent_node.document())
  58. {
  59. }
  60. bool ParsingContext::in_quirks_mode() const
  61. {
  62. return m_document ? m_document->in_quirks_mode() : false;
  63. }
  64. URL ParsingContext::complete_url(const String& addr) const
  65. {
  66. return m_document ? m_document->url().complete_url(addr) : URL::create_with_url_or_path(addr);
  67. }
  68. }
  69. static Optional<Color> parse_css_color(const CSS::ParsingContext&, const StringView& view)
  70. {
  71. if (view.equals_ignoring_case("transparent"))
  72. return Color::from_rgba(0x00000000);
  73. auto color = Color::from_string(view.to_string().to_lowercase());
  74. if (color.has_value())
  75. return color;
  76. return {};
  77. }
  78. static Optional<float> try_parse_float(const StringView& string)
  79. {
  80. const char* str = string.characters_without_null_termination();
  81. size_t len = string.length();
  82. size_t weight = 1;
  83. int exp_val = 0;
  84. float value = 0.0f;
  85. float fraction = 0.0f;
  86. bool has_sign = false;
  87. bool is_negative = false;
  88. bool is_fractional = false;
  89. bool is_scientific = false;
  90. if (str[0] == '-') {
  91. is_negative = true;
  92. has_sign = true;
  93. }
  94. if (str[0] == '+') {
  95. has_sign = true;
  96. }
  97. for (size_t i = has_sign; i < len; i++) {
  98. // Looks like we're about to start working on the fractional part
  99. if (str[i] == '.') {
  100. is_fractional = true;
  101. continue;
  102. }
  103. if (str[i] == 'e' || str[i] == 'E') {
  104. if (str[i + 1] == '-' || str[i + 1] == '+')
  105. exp_val = atoi(str + i + 2);
  106. else
  107. exp_val = atoi(str + i + 1);
  108. is_scientific = true;
  109. continue;
  110. }
  111. if (str[i] < '0' || str[i] > '9' || exp_val != 0) {
  112. return {};
  113. continue;
  114. }
  115. if (is_fractional) {
  116. fraction *= 10;
  117. fraction += str[i] - '0';
  118. weight *= 10;
  119. } else {
  120. value = value * 10;
  121. value += str[i] - '0';
  122. }
  123. }
  124. fraction /= weight;
  125. value += fraction;
  126. if (is_scientific) {
  127. bool divide = exp_val < 0;
  128. if (divide)
  129. exp_val *= -1;
  130. for (int i = 0; i < exp_val; i++) {
  131. if (divide)
  132. value /= 10;
  133. else
  134. value *= 10;
  135. }
  136. }
  137. return is_negative ? -value : value;
  138. }
  139. static CSS::Length parse_length(const CSS::ParsingContext& context, const StringView& view, bool& is_bad_length)
  140. {
  141. CSS::Length::Type type = CSS::Length::Type::Undefined;
  142. Optional<float> value;
  143. if (view.ends_with('%')) {
  144. type = CSS::Length::Type::Percentage;
  145. value = try_parse_float(view.substring_view(0, view.length() - 1));
  146. } else if (view.ends_with("px", CaseSensitivity::CaseInsensitive)) {
  147. type = CSS::Length::Type::Px;
  148. value = try_parse_float(view.substring_view(0, view.length() - 2));
  149. } else if (view.ends_with("pt", CaseSensitivity::CaseInsensitive)) {
  150. type = CSS::Length::Type::Pt;
  151. value = try_parse_float(view.substring_view(0, view.length() - 2));
  152. } else if (view.ends_with("pc", CaseSensitivity::CaseInsensitive)) {
  153. type = CSS::Length::Type::Pc;
  154. value = try_parse_float(view.substring_view(0, view.length() - 2));
  155. } else if (view.ends_with("mm", CaseSensitivity::CaseInsensitive)) {
  156. type = CSS::Length::Type::Mm;
  157. value = try_parse_float(view.substring_view(0, view.length() - 2));
  158. } else if (view.ends_with("rem", CaseSensitivity::CaseInsensitive)) {
  159. type = CSS::Length::Type::Rem;
  160. value = try_parse_float(view.substring_view(0, view.length() - 3));
  161. } else if (view.ends_with("em", CaseSensitivity::CaseInsensitive)) {
  162. type = CSS::Length::Type::Em;
  163. value = try_parse_float(view.substring_view(0, view.length() - 2));
  164. } else if (view.ends_with("ex", CaseSensitivity::CaseInsensitive)) {
  165. type = CSS::Length::Type::Ex;
  166. value = try_parse_float(view.substring_view(0, view.length() - 2));
  167. } else if (view.ends_with("vw", CaseSensitivity::CaseInsensitive)) {
  168. type = CSS::Length::Type::Vw;
  169. value = try_parse_float(view.substring_view(0, view.length() - 2));
  170. } else if (view.ends_with("vh", CaseSensitivity::CaseInsensitive)) {
  171. type = CSS::Length::Type::Vh;
  172. value = try_parse_float(view.substring_view(0, view.length() - 2));
  173. } else if (view.ends_with("vmax", CaseSensitivity::CaseInsensitive)) {
  174. type = CSS::Length::Type::Vmax;
  175. value = try_parse_float(view.substring_view(0, view.length() - 4));
  176. } else if (view.ends_with("vmin", CaseSensitivity::CaseInsensitive)) {
  177. type = CSS::Length::Type::Vmin;
  178. value = try_parse_float(view.substring_view(0, view.length() - 4));
  179. } else if (view.ends_with("cm", CaseSensitivity::CaseInsensitive)) {
  180. type = CSS::Length::Type::Cm;
  181. value = try_parse_float(view.substring_view(0, view.length() - 2));
  182. } else if (view.ends_with("in", CaseSensitivity::CaseInsensitive)) {
  183. type = CSS::Length::Type::In;
  184. value = try_parse_float(view.substring_view(0, view.length() - 2));
  185. } else if (view.ends_with("Q", CaseSensitivity::CaseInsensitive)) {
  186. type = CSS::Length::Type::Q;
  187. value = try_parse_float(view.substring_view(0, view.length() - 1));
  188. } else if (view == "0") {
  189. type = CSS::Length::Type::Px;
  190. value = 0;
  191. } else if (context.in_quirks_mode()) {
  192. type = CSS::Length::Type::Px;
  193. value = try_parse_float(view);
  194. } else {
  195. value = try_parse_float(view);
  196. if (value.has_value())
  197. is_bad_length = true;
  198. }
  199. if (!value.has_value())
  200. return {};
  201. return CSS::Length(value.value(), type);
  202. }
  203. static bool takes_integer_value(CSS::PropertyID property_id)
  204. {
  205. return property_id == CSS::PropertyID::ZIndex || property_id == CSS::PropertyID::FontWeight;
  206. }
  207. RefPtr<CSS::StyleValue> parse_css_value(const CSS::ParsingContext& context, const StringView& string, CSS::PropertyID property_id)
  208. {
  209. bool is_bad_length = false;
  210. if (takes_integer_value(property_id)) {
  211. auto integer = string.to_int();
  212. if (integer.has_value())
  213. return CSS::LengthStyleValue::create(CSS::Length::make_px(integer.value()));
  214. }
  215. auto length = parse_length(context, string, is_bad_length);
  216. if (is_bad_length)
  217. return nullptr;
  218. if (!length.is_undefined())
  219. return CSS::LengthStyleValue::create(length);
  220. if (string.equals_ignoring_case("inherit"))
  221. return CSS::InheritStyleValue::create();
  222. if (string.equals_ignoring_case("initial"))
  223. return CSS::InitialStyleValue::create();
  224. if (string.equals_ignoring_case("auto"))
  225. return CSS::LengthStyleValue::create(CSS::Length::make_auto());
  226. auto value_id = CSS::value_id_from_string(string);
  227. if (value_id != CSS::ValueID::Invalid)
  228. return CSS::IdentifierStyleValue::create(value_id);
  229. auto color = parse_css_color(context, string);
  230. if (color.has_value())
  231. return CSS::ColorStyleValue::create(color.value());
  232. return CSS::StringStyleValue::create(string);
  233. }
  234. RefPtr<CSS::LengthStyleValue> parse_line_width(const CSS::ParsingContext& context, const StringView& part)
  235. {
  236. auto value = parse_css_value(context, part);
  237. if (value && value->is_length())
  238. return static_ptr_cast<CSS::LengthStyleValue>(value);
  239. return nullptr;
  240. }
  241. RefPtr<CSS::ColorStyleValue> parse_color(const CSS::ParsingContext& context, const StringView& part)
  242. {
  243. auto value = parse_css_value(context, part);
  244. if (value && value->is_color())
  245. return static_ptr_cast<CSS::ColorStyleValue>(value);
  246. return nullptr;
  247. }
  248. RefPtr<CSS::StringStyleValue> parse_line_style(const CSS::ParsingContext& context, const StringView& part)
  249. {
  250. auto parsed_value = parse_css_value(context, part);
  251. if (!parsed_value || !parsed_value->is_string())
  252. return nullptr;
  253. auto value = static_ptr_cast<CSS::StringStyleValue>(parsed_value);
  254. if (value->to_string() == "dotted")
  255. return value;
  256. if (value->to_string() == "dashed")
  257. return value;
  258. if (value->to_string() == "solid")
  259. return value;
  260. if (value->to_string() == "double")
  261. return value;
  262. if (value->to_string() == "groove")
  263. return value;
  264. if (value->to_string() == "ridge")
  265. return value;
  266. return nullptr;
  267. }
  268. class CSSParser {
  269. public:
  270. CSSParser(const CSS::ParsingContext& context, const StringView& input)
  271. : m_context(context)
  272. , css(input)
  273. {
  274. }
  275. bool next_is(const char* str) const
  276. {
  277. size_t len = strlen(str);
  278. for (size_t i = 0; i < len; ++i) {
  279. if (peek(i) != str[i])
  280. return false;
  281. }
  282. return true;
  283. }
  284. char peek(size_t offset = 0) const
  285. {
  286. if ((index + offset) < css.length())
  287. return css[index + offset];
  288. return 0;
  289. }
  290. bool consume_specific(char ch)
  291. {
  292. if (peek() != ch) {
  293. dbgln("CSSParser: Peeked '{:c}' wanted specific '{:c}'", peek(), ch);
  294. }
  295. if (!peek()) {
  296. PARSE_ERROR();
  297. return false;
  298. }
  299. if (peek() != ch) {
  300. PARSE_ERROR();
  301. ++index;
  302. return false;
  303. }
  304. ++index;
  305. return true;
  306. }
  307. char consume_one()
  308. {
  309. PARSE_VERIFY(index < css.length());
  310. return css[index++];
  311. };
  312. bool consume_whitespace_or_comments()
  313. {
  314. size_t original_index = index;
  315. bool in_comment = false;
  316. for (; index < css.length(); ++index) {
  317. char ch = peek();
  318. if (isspace(ch))
  319. continue;
  320. if (!in_comment && ch == '/' && peek(1) == '*') {
  321. in_comment = true;
  322. ++index;
  323. continue;
  324. }
  325. if (in_comment && ch == '*' && peek(1) == '/') {
  326. in_comment = false;
  327. ++index;
  328. continue;
  329. }
  330. if (in_comment)
  331. continue;
  332. break;
  333. }
  334. return original_index != index;
  335. }
  336. bool is_valid_selector_char(char ch) const
  337. {
  338. return isalnum(ch) || ch == '-' || ch == '_' || ch == '(' || ch == ')' || ch == '@';
  339. }
  340. bool is_combinator(char ch) const
  341. {
  342. return ch == '~' || ch == '>' || ch == '+';
  343. }
  344. Optional<CSS::Selector::SimpleSelector> parse_simple_selector()
  345. {
  346. auto index_at_start = index;
  347. if (consume_whitespace_or_comments())
  348. return {};
  349. if (!peek() || peek() == '{' || peek() == ',' || is_combinator(peek()))
  350. return {};
  351. CSS::Selector::SimpleSelector::Type type;
  352. if (peek() == '*') {
  353. type = CSS::Selector::SimpleSelector::Type::Universal;
  354. consume_one();
  355. return CSS::Selector::SimpleSelector {
  356. type,
  357. CSS::Selector::SimpleSelector::PseudoClass::None,
  358. CSS::Selector::SimpleSelector::PseudoElement::None,
  359. String(),
  360. CSS::Selector::SimpleSelector::AttributeMatchType::None,
  361. String(),
  362. String()
  363. };
  364. }
  365. if (peek() == '.') {
  366. type = CSS::Selector::SimpleSelector::Type::Class;
  367. consume_one();
  368. } else if (peek() == '#') {
  369. type = CSS::Selector::SimpleSelector::Type::Id;
  370. consume_one();
  371. } else if (isalpha(peek())) {
  372. type = CSS::Selector::SimpleSelector::Type::TagName;
  373. } else {
  374. type = CSS::Selector::SimpleSelector::Type::Universal;
  375. }
  376. if (type != CSS::Selector::SimpleSelector::Type::Universal) {
  377. while (is_valid_selector_char(peek()))
  378. buffer.append(consume_one());
  379. PARSE_VERIFY(!buffer.is_null());
  380. }
  381. auto value = String::copy(buffer);
  382. if (type == CSS::Selector::SimpleSelector::Type::TagName) {
  383. // Some stylesheets use uppercase tag names, so here's a hack to just lowercase them internally.
  384. value = value.to_lowercase();
  385. }
  386. CSS::Selector::SimpleSelector simple_selector {
  387. type,
  388. CSS::Selector::SimpleSelector::PseudoClass::None,
  389. CSS::Selector::SimpleSelector::PseudoElement::None,
  390. value,
  391. CSS::Selector::SimpleSelector::AttributeMatchType::None,
  392. String(),
  393. String()
  394. };
  395. buffer.clear();
  396. if (peek() == '[') {
  397. CSS::Selector::SimpleSelector::AttributeMatchType attribute_match_type = CSS::Selector::SimpleSelector::AttributeMatchType::HasAttribute;
  398. String attribute_name;
  399. String attribute_value;
  400. bool in_value = false;
  401. consume_specific('[');
  402. char expected_end_of_attribute_selector = ']';
  403. while (peek() != expected_end_of_attribute_selector) {
  404. char ch = consume_one();
  405. if (ch == '=' || (ch == '~' && peek() == '=')) {
  406. if (ch == '=') {
  407. attribute_match_type = CSS::Selector::SimpleSelector::AttributeMatchType::ExactValueMatch;
  408. } else if (ch == '~') {
  409. consume_one();
  410. attribute_match_type = CSS::Selector::SimpleSelector::AttributeMatchType::Contains;
  411. }
  412. attribute_name = String::copy(buffer);
  413. buffer.clear();
  414. in_value = true;
  415. consume_whitespace_or_comments();
  416. if (peek() == '\'') {
  417. expected_end_of_attribute_selector = '\'';
  418. consume_one();
  419. } else if (peek() == '"') {
  420. expected_end_of_attribute_selector = '"';
  421. consume_one();
  422. }
  423. continue;
  424. }
  425. // FIXME: This is a hack that will go away when we replace this with a big boy CSS parser.
  426. if (ch == '\\')
  427. ch = consume_one();
  428. buffer.append(ch);
  429. }
  430. if (in_value)
  431. attribute_value = String::copy(buffer);
  432. else
  433. attribute_name = String::copy(buffer);
  434. buffer.clear();
  435. simple_selector.attribute_match_type = attribute_match_type;
  436. simple_selector.attribute_name = attribute_name;
  437. simple_selector.attribute_value = attribute_value;
  438. if (expected_end_of_attribute_selector != ']') {
  439. if (!consume_specific(expected_end_of_attribute_selector))
  440. return {};
  441. }
  442. consume_whitespace_or_comments();
  443. if (!consume_specific(']'))
  444. return {};
  445. }
  446. if (peek() == ':') {
  447. // FIXME: Implement pseudo elements.
  448. [[maybe_unused]] bool is_pseudo_element = false;
  449. consume_one();
  450. if (peek() == ':') {
  451. is_pseudo_element = true;
  452. consume_one();
  453. }
  454. if (next_is("not")) {
  455. buffer.append(consume_one());
  456. buffer.append(consume_one());
  457. buffer.append(consume_one());
  458. if (!consume_specific('('))
  459. return {};
  460. buffer.append('(');
  461. while (peek() != ')')
  462. buffer.append(consume_one());
  463. if (!consume_specific(')'))
  464. return {};
  465. buffer.append(')');
  466. } else {
  467. while (is_valid_selector_char(peek()))
  468. buffer.append(consume_one());
  469. }
  470. auto pseudo_name = String::copy(buffer);
  471. buffer.clear();
  472. // Ignore for now, otherwise we produce a "false positive" selector
  473. // and apply styles to the element itself, not its pseudo element
  474. if (is_pseudo_element)
  475. return {};
  476. if (pseudo_name.equals_ignoring_case("link"))
  477. simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::Link;
  478. else if (pseudo_name.equals_ignoring_case("visited"))
  479. simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::Visited;
  480. else if (pseudo_name.equals_ignoring_case("hover"))
  481. simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::Hover;
  482. else if (pseudo_name.equals_ignoring_case("focus"))
  483. simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::Focus;
  484. else if (pseudo_name.equals_ignoring_case("first-child"))
  485. simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::FirstChild;
  486. else if (pseudo_name.equals_ignoring_case("last-child"))
  487. simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::LastChild;
  488. else if (pseudo_name.equals_ignoring_case("only-child"))
  489. simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::OnlyChild;
  490. else if (pseudo_name.equals_ignoring_case("empty"))
  491. simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::Empty;
  492. else if (pseudo_name.equals_ignoring_case("root"))
  493. simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::Root;
  494. else if (pseudo_name.equals_ignoring_case("before"))
  495. simple_selector.pseudo_element = CSS::Selector::SimpleSelector::PseudoElement::Before;
  496. else if (pseudo_name.equals_ignoring_case("after"))
  497. simple_selector.pseudo_element = CSS::Selector::SimpleSelector::PseudoElement::After;
  498. }
  499. if (index == index_at_start) {
  500. // We consumed nothing.
  501. return {};
  502. }
  503. return simple_selector;
  504. }
  505. Optional<CSS::Selector::ComplexSelector> parse_complex_selector()
  506. {
  507. auto relation = CSS::Selector::ComplexSelector::Relation::Descendant;
  508. if (peek() == '{' || peek() == ',')
  509. return {};
  510. if (is_combinator(peek())) {
  511. switch (peek()) {
  512. case '>':
  513. relation = CSS::Selector::ComplexSelector::Relation::ImmediateChild;
  514. break;
  515. case '+':
  516. relation = CSS::Selector::ComplexSelector::Relation::AdjacentSibling;
  517. break;
  518. case '~':
  519. relation = CSS::Selector::ComplexSelector::Relation::GeneralSibling;
  520. break;
  521. }
  522. consume_one();
  523. consume_whitespace_or_comments();
  524. }
  525. consume_whitespace_or_comments();
  526. Vector<CSS::Selector::SimpleSelector> simple_selectors;
  527. for (;;) {
  528. auto component = parse_simple_selector();
  529. if (!component.has_value())
  530. break;
  531. simple_selectors.append(component.value());
  532. // If this assert triggers, we're most likely up to no good.
  533. PARSE_VERIFY(simple_selectors.size() < 100);
  534. }
  535. if (simple_selectors.is_empty())
  536. return {};
  537. return CSS::Selector::ComplexSelector { relation, move(simple_selectors) };
  538. }
  539. void parse_selector()
  540. {
  541. Vector<CSS::Selector::ComplexSelector> complex_selectors;
  542. for (;;) {
  543. auto index_before = index;
  544. auto complex_selector = parse_complex_selector();
  545. if (complex_selector.has_value())
  546. complex_selectors.append(complex_selector.value());
  547. consume_whitespace_or_comments();
  548. if (!peek() || peek() == ',' || peek() == '{')
  549. break;
  550. // HACK: If we didn't move forward, just let go.
  551. if (index == index_before)
  552. break;
  553. }
  554. if (complex_selectors.is_empty())
  555. return;
  556. complex_selectors.first().relation = CSS::Selector::ComplexSelector::Relation::None;
  557. current_rule.selectors.append(CSS::Selector(move(complex_selectors)));
  558. }
  559. Optional<CSS::Selector> parse_individual_selector()
  560. {
  561. parse_selector();
  562. if (current_rule.selectors.is_empty())
  563. return {};
  564. return current_rule.selectors.last();
  565. }
  566. void parse_selector_list()
  567. {
  568. for (;;) {
  569. auto index_before = index;
  570. parse_selector();
  571. consume_whitespace_or_comments();
  572. if (peek() == ',') {
  573. consume_one();
  574. continue;
  575. }
  576. if (peek() == '{')
  577. break;
  578. // HACK: If we didn't move forward, just let go.
  579. if (index_before == index)
  580. break;
  581. }
  582. }
  583. bool is_valid_property_name_char(char ch) const
  584. {
  585. return ch && !isspace(ch) && ch != ':';
  586. }
  587. bool is_valid_property_value_char(char ch) const
  588. {
  589. return ch && ch != '!' && ch != ';' && ch != '}';
  590. }
  591. bool is_valid_string_quotes_char(char ch) const
  592. {
  593. return ch == '\'' || ch == '\"';
  594. }
  595. struct ValueAndImportant {
  596. String value;
  597. bool important { false };
  598. };
  599. ValueAndImportant consume_css_value()
  600. {
  601. buffer.clear();
  602. int paren_nesting_level = 0;
  603. bool important = false;
  604. for (;;) {
  605. char ch = peek();
  606. if (ch == '(') {
  607. ++paren_nesting_level;
  608. buffer.append(consume_one());
  609. continue;
  610. }
  611. if (ch == ')') {
  612. PARSE_VERIFY(paren_nesting_level > 0);
  613. --paren_nesting_level;
  614. buffer.append(consume_one());
  615. continue;
  616. }
  617. if (paren_nesting_level > 0) {
  618. buffer.append(consume_one());
  619. continue;
  620. }
  621. if (next_is("!important")) {
  622. consume_specific('!');
  623. consume_specific('i');
  624. consume_specific('m');
  625. consume_specific('p');
  626. consume_specific('o');
  627. consume_specific('r');
  628. consume_specific('t');
  629. consume_specific('a');
  630. consume_specific('n');
  631. consume_specific('t');
  632. important = true;
  633. continue;
  634. }
  635. if (next_is("/*")) {
  636. consume_whitespace_or_comments();
  637. continue;
  638. }
  639. if (!ch)
  640. break;
  641. if (ch == '\\') {
  642. consume_one();
  643. buffer.append(consume_one());
  644. continue;
  645. }
  646. if (ch == '}')
  647. break;
  648. if (ch == ';')
  649. break;
  650. buffer.append(consume_one());
  651. }
  652. // Remove trailing whitespace.
  653. while (!buffer.is_empty() && isspace(buffer.last()))
  654. buffer.take_last();
  655. auto string = String::copy(buffer);
  656. buffer.clear();
  657. return { string, important };
  658. }
  659. Optional<CSS::StyleProperty> parse_property()
  660. {
  661. consume_whitespace_or_comments();
  662. if (peek() == ';') {
  663. consume_one();
  664. return {};
  665. }
  666. if (peek() == '}')
  667. return {};
  668. buffer.clear();
  669. while (is_valid_property_name_char(peek()))
  670. buffer.append(consume_one());
  671. auto property_name = String::copy(buffer);
  672. buffer.clear();
  673. consume_whitespace_or_comments();
  674. if (!consume_specific(':'))
  675. return {};
  676. consume_whitespace_or_comments();
  677. auto [property_value, important] = consume_css_value();
  678. consume_whitespace_or_comments();
  679. if (peek() && peek() != '}') {
  680. if (!consume_specific(';'))
  681. return {};
  682. }
  683. auto property_id = CSS::property_id_from_string(property_name);
  684. if (property_id == CSS::PropertyID::Invalid) {
  685. dbgln("CSSParser: Unrecognized property '{}'", property_name);
  686. }
  687. auto value = parse_css_value(m_context, property_value, property_id);
  688. if (!value)
  689. return {};
  690. return CSS::StyleProperty { property_id, value.release_nonnull(), important };
  691. }
  692. void parse_declaration()
  693. {
  694. for (;;) {
  695. auto property = parse_property();
  696. if (property.has_value())
  697. current_rule.properties.append(property.value());
  698. consume_whitespace_or_comments();
  699. if (!peek() || peek() == '}')
  700. break;
  701. }
  702. }
  703. void parse_style_rule()
  704. {
  705. parse_selector_list();
  706. if (!consume_specific('{')) {
  707. PARSE_ERROR();
  708. return;
  709. }
  710. parse_declaration();
  711. if (!consume_specific('}')) {
  712. PARSE_ERROR();
  713. return;
  714. }
  715. rules.append(CSS::CSSStyleRule::create(move(current_rule.selectors), CSS::StyleDeclaration::create(move(current_rule.properties))));
  716. }
  717. Optional<String> parse_string()
  718. {
  719. if (!is_valid_string_quotes_char(peek())) {
  720. PARSE_ERROR();
  721. return {};
  722. }
  723. char end_char = consume_one();
  724. buffer.clear();
  725. while (peek() && peek() != end_char) {
  726. if (peek() == '\\') {
  727. consume_specific('\\');
  728. if (peek() == 0)
  729. break;
  730. }
  731. buffer.append(consume_one());
  732. }
  733. String string_value(String::copy(buffer));
  734. buffer.clear();
  735. if (consume_specific(end_char)) {
  736. return { string_value };
  737. }
  738. return {};
  739. }
  740. Optional<String> parse_url()
  741. {
  742. if (is_valid_string_quotes_char(peek()))
  743. return parse_string();
  744. buffer.clear();
  745. while (peek() && peek() != ')')
  746. buffer.append(consume_one());
  747. String url_value(String::copy(buffer));
  748. buffer.clear();
  749. if (peek() == ')')
  750. return { url_value };
  751. return {};
  752. }
  753. void parse_at_import_rule()
  754. {
  755. consume_whitespace_or_comments();
  756. Optional<String> imported_address;
  757. if (is_valid_string_quotes_char(peek())) {
  758. imported_address = parse_string();
  759. } else if (next_is("url")) {
  760. consume_specific('u');
  761. consume_specific('r');
  762. consume_specific('l');
  763. consume_whitespace_or_comments();
  764. if (!consume_specific('('))
  765. return;
  766. imported_address = parse_url();
  767. if (!consume_specific(')'))
  768. return;
  769. } else {
  770. PARSE_ERROR();
  771. return;
  772. }
  773. if (imported_address.has_value())
  774. rules.append(CSS::CSSImportRule::create(m_context.complete_url(imported_address.value())));
  775. // FIXME: We ignore possilbe media query list
  776. while (peek() && peek() != ';')
  777. consume_one();
  778. consume_specific(';');
  779. }
  780. void parse_at_rule()
  781. {
  782. HashMap<String, void (CSSParser::*)()> at_rules_parsers({ { "@import", &CSSParser::parse_at_import_rule } });
  783. for (const auto& rule_parser_pair : at_rules_parsers) {
  784. if (next_is(rule_parser_pair.key.characters())) {
  785. for (char c : rule_parser_pair.key) {
  786. consume_specific(c);
  787. }
  788. (this->*(rule_parser_pair.value))();
  789. return;
  790. }
  791. }
  792. // FIXME: We ignore other @-rules completely for now.
  793. while (peek() != 0 && peek() != '{')
  794. consume_one();
  795. int level = 0;
  796. for (;;) {
  797. auto ch = consume_one();
  798. if (ch == '{') {
  799. ++level;
  800. } else if (ch == '}') {
  801. --level;
  802. if (level == 0)
  803. break;
  804. }
  805. }
  806. }
  807. void parse_rule()
  808. {
  809. consume_whitespace_or_comments();
  810. if (!peek())
  811. return;
  812. if (peek() == '@') {
  813. parse_at_rule();
  814. } else {
  815. parse_style_rule();
  816. }
  817. consume_whitespace_or_comments();
  818. }
  819. RefPtr<CSS::CSSStyleSheet> parse_sheet()
  820. {
  821. if (peek(0) == (char)0xef && peek(1) == (char)0xbb && peek(2) == (char)0xbf) {
  822. // HACK: Skip UTF-8 BOM.
  823. index += 3;
  824. }
  825. while (peek()) {
  826. parse_rule();
  827. }
  828. return CSS::CSSStyleSheet::create(move(rules));
  829. }
  830. RefPtr<CSS::StyleDeclaration> parse_standalone_declaration()
  831. {
  832. consume_whitespace_or_comments();
  833. for (;;) {
  834. auto property = parse_property();
  835. if (property.has_value())
  836. current_rule.properties.append(property.value());
  837. consume_whitespace_or_comments();
  838. if (!peek())
  839. break;
  840. }
  841. return CSS::StyleDeclaration::create(move(current_rule.properties));
  842. }
  843. private:
  844. CSS::ParsingContext m_context;
  845. NonnullRefPtrVector<CSS::CSSRule> rules;
  846. struct CurrentRule {
  847. Vector<CSS::Selector> selectors;
  848. Vector<CSS::StyleProperty> properties;
  849. };
  850. CurrentRule current_rule;
  851. Vector<char> buffer;
  852. size_t index = 0;
  853. StringView css;
  854. };
  855. Optional<CSS::Selector> parse_selector(const CSS::ParsingContext& context, const StringView& selector_text)
  856. {
  857. CSSParser parser(context, selector_text);
  858. return parser.parse_individual_selector();
  859. }
  860. RefPtr<CSS::CSSStyleSheet> parse_css(const CSS::ParsingContext& context, const StringView& css)
  861. {
  862. if (css.is_empty())
  863. return CSS::CSSStyleSheet::create({});
  864. CSSParser parser(context, css);
  865. return parser.parse_sheet();
  866. }
  867. RefPtr<CSS::StyleDeclaration> parse_css_declaration(const CSS::ParsingContext& context, const StringView& css)
  868. {
  869. if (css.is_empty())
  870. return CSS::StyleDeclaration::create({});
  871. CSSParser parser(context, css);
  872. return parser.parse_standalone_declaration();
  873. }
  874. RefPtr<CSS::StyleValue> parse_html_length(const DOM::Document& document, const StringView& string)
  875. {
  876. auto integer = string.to_int();
  877. if (integer.has_value())
  878. return CSS::LengthStyleValue::create(CSS::Length::make_px(integer.value()));
  879. return parse_css_value(CSS::ParsingContext(document), string);
  880. }
  881. }