DeprecatedCSSParser.cpp 53 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553
  1. /*
  2. * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
  3. * Copyright (c) 2021, Tobias Christiansen <tobi@tobyase.de>
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include <AK/GenericLexer.h>
  8. #include <AK/HashMap.h>
  9. #include <AK/NonnullOwnPtr.h>
  10. #include <AK/SourceLocation.h>
  11. #include <LibWeb/CSS/CSSImportRule.h>
  12. #include <LibWeb/CSS/CSSRule.h>
  13. #include <LibWeb/CSS/CSSStyleRule.h>
  14. #include <LibWeb/CSS/Parser/DeprecatedCSSParser.h>
  15. #include <LibWeb/CSS/PropertyID.h>
  16. #include <LibWeb/CSS/Selector.h>
  17. #include <LibWeb/DOM/Document.h>
  18. #include <ctype.h>
  19. #include <stdlib.h>
  20. #include <string.h>
  21. #define PARSE_VERIFY(x) \
  22. if (!(x)) { \
  23. dbgln("CSS PARSER ASSERTION FAILED: {}", #x); \
  24. dbgln("At character# {} in CSS: _{}_", index, css); \
  25. VERIFY_NOT_REACHED(); \
  26. }
  27. static inline void log_parse_error(const SourceLocation& location = SourceLocation::current())
  28. {
  29. dbgln("CSS Parse error! {}", location);
  30. }
  31. namespace Web {
  32. namespace CSS {
  33. DeprecatedParsingContext::DeprecatedParsingContext()
  34. {
  35. }
  36. DeprecatedParsingContext::DeprecatedParsingContext(const DOM::Document& document)
  37. : m_document(&document)
  38. {
  39. }
  40. DeprecatedParsingContext::DeprecatedParsingContext(const DOM::ParentNode& parent_node)
  41. : m_document(&parent_node.document())
  42. {
  43. }
  44. bool DeprecatedParsingContext::in_quirks_mode() const
  45. {
  46. return m_document ? m_document->in_quirks_mode() : false;
  47. }
  48. URL DeprecatedParsingContext::complete_url(const String& addr) const
  49. {
  50. return m_document ? m_document->url().complete_url(addr) : URL::create_with_url_or_path(addr);
  51. }
  52. }
  53. static Optional<Color> parse_css_color(const CSS::DeprecatedParsingContext&, const StringView& view)
  54. {
  55. if (view.equals_ignoring_case("transparent"))
  56. return Color::from_rgba(0x00000000);
  57. auto color = Color::from_string(view.to_string().to_lowercase());
  58. if (color.has_value())
  59. return color;
  60. return {};
  61. }
  62. static Optional<float> try_parse_float(const StringView& string)
  63. {
  64. const char* str = string.characters_without_null_termination();
  65. size_t len = string.length();
  66. size_t weight = 1;
  67. int exp_val = 0;
  68. float value = 0.0f;
  69. float fraction = 0.0f;
  70. bool has_sign = false;
  71. bool is_negative = false;
  72. bool is_fractional = false;
  73. bool is_scientific = false;
  74. if (str[0] == '-') {
  75. is_negative = true;
  76. has_sign = true;
  77. }
  78. if (str[0] == '+') {
  79. has_sign = true;
  80. }
  81. for (size_t i = has_sign; i < len; i++) {
  82. // Looks like we're about to start working on the fractional part
  83. if (str[i] == '.') {
  84. is_fractional = true;
  85. continue;
  86. }
  87. if (str[i] == 'e' || str[i] == 'E') {
  88. if (str[i + 1] == '-' || str[i + 1] == '+')
  89. exp_val = atoi(str + i + 2);
  90. else
  91. exp_val = atoi(str + i + 1);
  92. is_scientific = true;
  93. continue;
  94. }
  95. if (str[i] < '0' || str[i] > '9' || exp_val != 0) {
  96. return {};
  97. continue;
  98. }
  99. if (is_fractional) {
  100. fraction *= 10;
  101. fraction += str[i] - '0';
  102. weight *= 10;
  103. } else {
  104. value = value * 10;
  105. value += str[i] - '0';
  106. }
  107. }
  108. fraction /= weight;
  109. value += fraction;
  110. if (is_scientific) {
  111. bool divide = exp_val < 0;
  112. if (divide)
  113. exp_val *= -1;
  114. for (int i = 0; i < exp_val; i++) {
  115. if (divide)
  116. value /= 10;
  117. else
  118. value *= 10;
  119. }
  120. }
  121. return is_negative ? -value : value;
  122. }
  123. static CSS::Length::Type length_type_from_unit(const StringView& view)
  124. {
  125. if (view.ends_with('%'))
  126. return CSS::Length::Type::Percentage;
  127. if (view.ends_with("px", CaseSensitivity::CaseInsensitive))
  128. return CSS::Length::Type::Px;
  129. if (view.ends_with("pt", CaseSensitivity::CaseInsensitive))
  130. return CSS::Length::Type::Pt;
  131. if (view.ends_with("pc", CaseSensitivity::CaseInsensitive))
  132. return CSS::Length::Type::Pc;
  133. if (view.ends_with("mm", CaseSensitivity::CaseInsensitive))
  134. return CSS::Length::Type::Mm;
  135. if (view.ends_with("rem", CaseSensitivity::CaseInsensitive))
  136. return CSS::Length::Type::Rem;
  137. if (view.ends_with("em", CaseSensitivity::CaseInsensitive))
  138. return CSS::Length::Type::Em;
  139. if (view.ends_with("ex", CaseSensitivity::CaseInsensitive))
  140. return CSS::Length::Type::Ex;
  141. if (view.ends_with("vw", CaseSensitivity::CaseInsensitive))
  142. return CSS::Length::Type::Vw;
  143. if (view.ends_with("vh", CaseSensitivity::CaseInsensitive))
  144. return CSS::Length::Type::Vh;
  145. if (view.ends_with("vmax", CaseSensitivity::CaseInsensitive))
  146. return CSS::Length::Type::Vmax;
  147. if (view.ends_with("vmin", CaseSensitivity::CaseInsensitive))
  148. return CSS::Length::Type::Vmin;
  149. if (view.ends_with("cm", CaseSensitivity::CaseInsensitive))
  150. return CSS::Length::Type::Cm;
  151. if (view.ends_with("in", CaseSensitivity::CaseInsensitive))
  152. return CSS::Length::Type::In;
  153. if (view.ends_with("Q", CaseSensitivity::CaseInsensitive))
  154. return CSS::Length::Type::Q;
  155. if (view == "0")
  156. return CSS::Length::Type::Px;
  157. return CSS::Length::Type::Undefined;
  158. }
  159. static CSS::Length parse_length(const CSS::DeprecatedParsingContext& context, const StringView& view, bool& is_bad_length)
  160. {
  161. CSS::Length::Type type = length_type_from_unit(view);
  162. Optional<float> value;
  163. switch (type) {
  164. case CSS::Length::Type::Percentage:
  165. value = try_parse_float(view.substring_view(0, view.length() - 1));
  166. break;
  167. case CSS::Length::Type::Px:
  168. if (view == "0")
  169. value = 0;
  170. else
  171. value = try_parse_float(view.substring_view(0, view.length() - 2));
  172. break;
  173. case CSS::Length::Type::Pt:
  174. value = try_parse_float(view.substring_view(0, view.length() - 2));
  175. break;
  176. case CSS::Length::Type::Pc:
  177. value = try_parse_float(view.substring_view(0, view.length() - 2));
  178. break;
  179. case CSS::Length::Type::Mm:
  180. value = try_parse_float(view.substring_view(0, view.length() - 2));
  181. break;
  182. case CSS::Length::Type::Rem:
  183. value = try_parse_float(view.substring_view(0, view.length() - 3));
  184. break;
  185. case CSS::Length::Type::Em:
  186. value = try_parse_float(view.substring_view(0, view.length() - 2));
  187. break;
  188. case CSS::Length::Type::Ex:
  189. value = try_parse_float(view.substring_view(0, view.length() - 2));
  190. break;
  191. case CSS::Length::Type::Vw:
  192. value = try_parse_float(view.substring_view(0, view.length() - 2));
  193. break;
  194. case CSS::Length::Type::Vh:
  195. value = try_parse_float(view.substring_view(0, view.length() - 2));
  196. break;
  197. case CSS::Length::Type::Vmax:
  198. value = try_parse_float(view.substring_view(0, view.length() - 4));
  199. break;
  200. case CSS::Length::Type::Vmin:
  201. value = try_parse_float(view.substring_view(0, view.length() - 4));
  202. break;
  203. case CSS::Length::Type::Cm:
  204. value = try_parse_float(view.substring_view(0, view.length() - 2));
  205. break;
  206. case CSS::Length::Type::In:
  207. value = try_parse_float(view.substring_view(0, view.length() - 2));
  208. break;
  209. case CSS::Length::Type::Q:
  210. value = try_parse_float(view.substring_view(0, view.length() - 1));
  211. break;
  212. default:
  213. if (context.in_quirks_mode()) {
  214. type = CSS::Length::Type::Px;
  215. value = try_parse_float(view);
  216. } else {
  217. value = try_parse_float(view);
  218. if (value.has_value())
  219. is_bad_length = true;
  220. }
  221. }
  222. if (!value.has_value())
  223. return {};
  224. return CSS::Length(value.value(), type);
  225. }
  226. static bool takes_integer_value(CSS::PropertyID property_id)
  227. {
  228. return property_id == CSS::PropertyID::ZIndex || property_id == CSS::PropertyID::FontWeight || property_id == CSS::PropertyID::Custom;
  229. }
  230. static StringView parse_custom_property_name(const StringView& value)
  231. {
  232. if (!value.starts_with("var(") || !value.ends_with(")"))
  233. return {};
  234. // FIXME: Allow for fallback
  235. auto first_comma_index = value.find(',');
  236. auto length = value.length();
  237. auto substring_length = first_comma_index.has_value() ? first_comma_index.value() - 4 - 1 : length - 4 - 1;
  238. return value.substring_view(4, substring_length);
  239. }
  240. static StringView isolate_calc_expression(const StringView& value)
  241. {
  242. if (!value.starts_with("calc(") || !value.ends_with(")"))
  243. return {};
  244. auto substring_length = value.length() - 5 - 1;
  245. return value.substring_view(5, substring_length);
  246. }
  247. struct CalcToken {
  248. enum class Type {
  249. Undefined,
  250. Number,
  251. Unit,
  252. Whitespace,
  253. Plus,
  254. Minus,
  255. Asterisk,
  256. Slash,
  257. OpenBracket,
  258. CloseBracket,
  259. } type { Type::Undefined };
  260. String value {};
  261. };
  262. static void eat_white_space(Vector<CalcToken>&);
  263. static Optional<CSS::CalculatedStyleValue::CalcValue> parse_calc_value(Vector<CalcToken>&);
  264. static OwnPtr<CSS::CalculatedStyleValue::CalcProductPartWithOperator> parse_calc_product_part_with_operator(Vector<CalcToken>&);
  265. static Optional<CSS::CalculatedStyleValue::CalcNumberValue> parse_calc_number_value(Vector<CalcToken>&);
  266. static OwnPtr<CSS::CalculatedStyleValue::CalcProduct> parse_calc_product(Vector<CalcToken>&);
  267. static OwnPtr<CSS::CalculatedStyleValue::CalcSumPartWithOperator> parse_calc_sum_part_with_operator(Vector<CalcToken>&);
  268. static OwnPtr<CSS::CalculatedStyleValue::CalcSum> parse_calc_sum(Vector<CalcToken>&);
  269. static OwnPtr<CSS::CalculatedStyleValue::CalcNumberSum> parse_calc_number_sum(Vector<CalcToken>& tokens);
  270. static OwnPtr<CSS::CalculatedStyleValue::CalcNumberProductPartWithOperator> parse_calc_number_product_part_with_operator(Vector<CalcToken>& tokens);
  271. static OwnPtr<CSS::CalculatedStyleValue::CalcNumberProduct> parse_calc_number_product(Vector<CalcToken>& tokens);
  272. static OwnPtr<CSS::CalculatedStyleValue::CalcNumberSumPartWithOperator> parse_calc_number_sum_part_with_operator(Vector<CalcToken>& tokens);
  273. static OwnPtr<CSS::CalculatedStyleValue::CalcSum> parse_calc_expression(const StringView& expression_string)
  274. {
  275. // First, tokenize
  276. Vector<CalcToken> tokens;
  277. GenericLexer lexer(expression_string);
  278. while (!lexer.is_eof()) {
  279. // Number
  280. if (((lexer.next_is('+') || lexer.next_is('-')) && !isspace(lexer.peek(1)))
  281. || lexer.next_is('.')
  282. || lexer.next_is(isdigit)) {
  283. auto number = lexer.consume_while(is_any_of("+-.0123456789"));
  284. tokens.append(CalcToken { CalcToken::Type ::Number, number.to_string() });
  285. continue;
  286. }
  287. auto ch = lexer.consume();
  288. if (isspace(ch)) {
  289. tokens.append(CalcToken { CalcToken::Type::Whitespace });
  290. continue;
  291. }
  292. if (ch == '%') {
  293. tokens.append(CalcToken { CalcToken::Type::Unit, "%" });
  294. continue;
  295. }
  296. if (ch == '+') {
  297. tokens.append(CalcToken { CalcToken::Type::Plus });
  298. continue;
  299. }
  300. if (ch == '-') {
  301. tokens.append(CalcToken { CalcToken::Type::Minus });
  302. continue;
  303. }
  304. if (ch == '*') {
  305. tokens.append(CalcToken { CalcToken::Type::Asterisk });
  306. continue;
  307. }
  308. if (ch == '/') {
  309. tokens.append(CalcToken { CalcToken::Type::Slash });
  310. continue;
  311. }
  312. if (ch == '(') {
  313. tokens.append(CalcToken { CalcToken::Type::OpenBracket });
  314. continue;
  315. }
  316. if (ch == ')') {
  317. tokens.append(CalcToken { CalcToken::Type::CloseBracket });
  318. continue;
  319. }
  320. // Unit
  321. if (isalpha(ch)) {
  322. lexer.retreat();
  323. auto unit = lexer.consume_while(isalpha);
  324. tokens.append(CalcToken { CalcToken::Type::Unit, unit.to_string() });
  325. continue;
  326. }
  327. VERIFY_NOT_REACHED();
  328. }
  329. // Then, parse
  330. return parse_calc_sum(tokens);
  331. }
  332. static void eat_white_space(Vector<CalcToken>& tokens)
  333. {
  334. while (tokens.size() > 0 && tokens.first().type == CalcToken::Type::Whitespace)
  335. tokens.take_first();
  336. }
  337. static Optional<CSS::CalculatedStyleValue::CalcValue> parse_calc_value(Vector<CalcToken>& tokens)
  338. {
  339. auto current_token = tokens.take_first();
  340. if (current_token.type == CalcToken::Type::OpenBracket) {
  341. auto parsed_calc_sum = parse_calc_sum(tokens);
  342. if (!parsed_calc_sum)
  343. return {};
  344. return (CSS::CalculatedStyleValue::CalcValue) { parsed_calc_sum.release_nonnull() };
  345. }
  346. if (current_token.type != CalcToken::Type::Number)
  347. return {};
  348. auto try_the_number = try_parse_float(current_token.value);
  349. if (!try_the_number.has_value())
  350. return {};
  351. float the_number = try_the_number.value();
  352. if (tokens.first().type != CalcToken::Type::Unit)
  353. return (CSS::CalculatedStyleValue::CalcValue) { the_number };
  354. auto type = length_type_from_unit(tokens.take_first().value);
  355. if (type == CSS::Length::Type::Undefined)
  356. return {};
  357. return (CSS::CalculatedStyleValue::CalcValue) { CSS::Length(the_number, type) };
  358. }
  359. static OwnPtr<CSS::CalculatedStyleValue::CalcProductPartWithOperator> parse_calc_product_part_with_operator(Vector<CalcToken>& tokens)
  360. {
  361. auto product_with_operator = make<CSS::CalculatedStyleValue::CalcProductPartWithOperator>();
  362. eat_white_space(tokens);
  363. auto op = tokens.first();
  364. if (op.type == CalcToken::Type::Asterisk) {
  365. tokens.take_first();
  366. eat_white_space(tokens);
  367. product_with_operator->op = CSS::CalculatedStyleValue::CalcProductPartWithOperator::Multiply;
  368. auto parsed_calc_value = parse_calc_value(tokens);
  369. if (!parsed_calc_value.has_value())
  370. return nullptr;
  371. product_with_operator->value = { parsed_calc_value.release_value() };
  372. } else if (op.type == CalcToken::Type::Slash) {
  373. tokens.take_first();
  374. eat_white_space(tokens);
  375. product_with_operator->op = CSS::CalculatedStyleValue::CalcProductPartWithOperator::Divide;
  376. auto parsed_calc_number_value = parse_calc_number_value(tokens);
  377. if (!parsed_calc_number_value.has_value())
  378. return nullptr;
  379. product_with_operator->value = { parsed_calc_number_value.release_value() };
  380. } else {
  381. return nullptr;
  382. }
  383. return product_with_operator;
  384. }
  385. static OwnPtr<CSS::CalculatedStyleValue::CalcNumberProductPartWithOperator> parse_calc_number_product_part_with_operator(Vector<CalcToken>& tokens)
  386. {
  387. auto number_product_with_operator = make<CSS::CalculatedStyleValue::CalcNumberProductPartWithOperator>();
  388. eat_white_space(tokens);
  389. auto op = tokens.first();
  390. if (op.type == CalcToken::Type::Asterisk) {
  391. tokens.take_first();
  392. eat_white_space(tokens);
  393. number_product_with_operator->op = CSS::CalculatedStyleValue::CalcNumberProductPartWithOperator::Multiply;
  394. } else if (op.type == CalcToken::Type::Slash) {
  395. tokens.take_first();
  396. eat_white_space(tokens);
  397. number_product_with_operator->op = CSS::CalculatedStyleValue::CalcNumberProductPartWithOperator::Divide;
  398. } else {
  399. return nullptr;
  400. }
  401. auto parsed_calc_value = parse_calc_number_value(tokens);
  402. if (!parsed_calc_value.has_value())
  403. return nullptr;
  404. number_product_with_operator->value = parsed_calc_value.release_value();
  405. return number_product_with_operator;
  406. }
  407. static OwnPtr<CSS::CalculatedStyleValue::CalcNumberProduct> parse_calc_number_product(Vector<CalcToken>& tokens)
  408. {
  409. auto calc_number_product = make<CSS::CalculatedStyleValue::CalcNumberProduct>();
  410. auto first_calc_number_value_or_error = parse_calc_number_value(tokens);
  411. if (!first_calc_number_value_or_error.has_value())
  412. return nullptr;
  413. calc_number_product->first_calc_number_value = first_calc_number_value_or_error.release_value();
  414. while (tokens.size() > 0) {
  415. auto number_product_with_operator = parse_calc_number_product_part_with_operator(tokens);
  416. if (!number_product_with_operator)
  417. break;
  418. calc_number_product->zero_or_more_additional_calc_number_values.append(number_product_with_operator.release_nonnull());
  419. }
  420. return calc_number_product;
  421. }
  422. static OwnPtr<CSS::CalculatedStyleValue::CalcNumberSumPartWithOperator> parse_calc_number_sum_part_with_operator(Vector<CalcToken>& tokens)
  423. {
  424. if (tokens.size() < 3)
  425. return nullptr;
  426. if (!((tokens[0].type == CalcToken::Type::Plus
  427. || tokens[0].type == CalcToken::Type::Minus)
  428. && tokens[1].type == CalcToken::Type::Whitespace))
  429. return nullptr;
  430. auto op_token = tokens.take_first().type;
  431. tokens.take_first(); // Whitespace;
  432. CSS::CalculatedStyleValue::CalcNumberSumPartWithOperator::Operation op;
  433. if (op_token == CalcToken::Type::Plus)
  434. op = CSS::CalculatedStyleValue::CalcNumberSumPartWithOperator::Operation::Add;
  435. else if (op_token == CalcToken::Type::Minus)
  436. op = CSS::CalculatedStyleValue::CalcNumberSumPartWithOperator::Operation::Subtract;
  437. else
  438. return nullptr;
  439. auto calc_number_product = parse_calc_number_product(tokens);
  440. if (!calc_number_product)
  441. return nullptr;
  442. return make<CSS::CalculatedStyleValue::CalcNumberSumPartWithOperator>(op, calc_number_product.release_nonnull());
  443. }
  444. static OwnPtr<CSS::CalculatedStyleValue::CalcNumberSum> parse_calc_number_sum(Vector<CalcToken>& tokens)
  445. {
  446. if (tokens.take_first().type != CalcToken::Type::OpenBracket)
  447. return nullptr;
  448. auto first_calc_number_product_or_error = parse_calc_number_product(tokens);
  449. if (!first_calc_number_product_or_error)
  450. return nullptr;
  451. NonnullOwnPtrVector<CSS::CalculatedStyleValue::CalcNumberSumPartWithOperator> additional {};
  452. while (tokens.size() > 0 && tokens.first().type != CalcToken::Type::CloseBracket) {
  453. auto calc_sum_part = parse_calc_number_sum_part_with_operator(tokens);
  454. if (!calc_sum_part)
  455. return nullptr;
  456. additional.append(calc_sum_part.release_nonnull());
  457. }
  458. eat_white_space(tokens);
  459. auto calc_number_sum = make<CSS::CalculatedStyleValue::CalcNumberSum>(first_calc_number_product_or_error.release_nonnull(), move(additional));
  460. return calc_number_sum;
  461. }
  462. static Optional<CSS::CalculatedStyleValue::CalcNumberValue> parse_calc_number_value(Vector<CalcToken>& tokens)
  463. {
  464. if (tokens.first().type == CalcToken::Type::OpenBracket) {
  465. auto calc_number_sum = parse_calc_number_sum(tokens);
  466. if (calc_number_sum)
  467. return { calc_number_sum.release_nonnull() };
  468. }
  469. if (tokens.first().type != CalcToken::Type::Number)
  470. return {};
  471. auto the_number_string = tokens.take_first().value;
  472. auto try_the_number = try_parse_float(the_number_string);
  473. if (!try_the_number.has_value())
  474. return {};
  475. return try_the_number.value();
  476. }
  477. static OwnPtr<CSS::CalculatedStyleValue::CalcProduct> parse_calc_product(Vector<CalcToken>& tokens)
  478. {
  479. auto calc_product = make<CSS::CalculatedStyleValue::CalcProduct>();
  480. auto first_calc_value_or_error = parse_calc_value(tokens);
  481. if (!first_calc_value_or_error.has_value())
  482. return nullptr;
  483. calc_product->first_calc_value = first_calc_value_or_error.release_value();
  484. while (tokens.size() > 0) {
  485. auto product_with_operator = parse_calc_product_part_with_operator(tokens);
  486. if (!product_with_operator)
  487. break;
  488. calc_product->zero_or_more_additional_calc_values.append(product_with_operator.release_nonnull());
  489. }
  490. return calc_product;
  491. }
  492. static OwnPtr<CSS::CalculatedStyleValue::CalcSumPartWithOperator> parse_calc_sum_part_with_operator(Vector<CalcToken>& tokens)
  493. {
  494. // The following has to have the shape of <Whitespace><+ or -><Whitespace>
  495. // But the first whitespace gets eaten in parse_calc_product_part_with_operator().
  496. if (tokens.size() < 3)
  497. return {};
  498. if (!((tokens[0].type == CalcToken::Type::Plus
  499. || tokens[0].type == CalcToken::Type::Minus)
  500. && tokens[1].type == CalcToken::Type::Whitespace))
  501. return nullptr;
  502. auto op_token = tokens.take_first().type;
  503. tokens.take_first(); // Whitespace;
  504. CSS::CalculatedStyleValue::CalcSumPartWithOperator::Operation op;
  505. if (op_token == CalcToken::Type::Plus)
  506. op = CSS::CalculatedStyleValue::CalcSumPartWithOperator::Operation::Add;
  507. else if (op_token == CalcToken::Type::Minus)
  508. op = CSS::CalculatedStyleValue::CalcSumPartWithOperator::Operation::Subtract;
  509. else
  510. return nullptr;
  511. auto calc_product = parse_calc_product(tokens);
  512. if (!calc_product)
  513. return nullptr;
  514. return make<CSS::CalculatedStyleValue::CalcSumPartWithOperator>(op, calc_product.release_nonnull());
  515. };
  516. static OwnPtr<CSS::CalculatedStyleValue::CalcSum> parse_calc_sum(Vector<CalcToken>& tokens)
  517. {
  518. auto parsed_calc_product = parse_calc_product(tokens);
  519. if (!parsed_calc_product)
  520. return nullptr;
  521. NonnullOwnPtrVector<CSS::CalculatedStyleValue::CalcSumPartWithOperator> additional {};
  522. while (tokens.size() > 0 && tokens.first().type != CalcToken::Type::CloseBracket) {
  523. auto calc_sum_part = parse_calc_sum_part_with_operator(tokens);
  524. if (!calc_sum_part)
  525. return nullptr;
  526. additional.append(calc_sum_part.release_nonnull());
  527. }
  528. eat_white_space(tokens);
  529. return make<CSS::CalculatedStyleValue::CalcSum>(parsed_calc_product.release_nonnull(), move(additional));
  530. }
  531. static RefPtr<CSS::BoxShadowStyleValue> parse_box_shadow(CSS::DeprecatedParsingContext const& context, StringView const& string)
  532. {
  533. // FIXME: Also support inset, spread-radius and multiple comma-seperated box-shadows
  534. CSS::Length offset_x {};
  535. CSS::Length offset_y {};
  536. CSS::Length blur_radius {};
  537. Color color {};
  538. auto parts = string.split_view(' ');
  539. if (parts.size() < 3 || parts.size() > 4)
  540. return nullptr;
  541. bool bad_length = false;
  542. offset_x = parse_length(context, parts[0], bad_length);
  543. if (bad_length)
  544. return nullptr;
  545. bad_length = false;
  546. offset_y = parse_length(context, parts[1], bad_length);
  547. if (bad_length)
  548. return nullptr;
  549. if (parts.size() == 3) {
  550. auto parsed_color = parse_color(context, parts[2]);
  551. if (!parsed_color)
  552. return nullptr;
  553. color = parsed_color->color();
  554. } else if (parts.size() == 4) {
  555. bad_length = false;
  556. blur_radius = parse_length(context, parts[2], bad_length);
  557. if (bad_length)
  558. return nullptr;
  559. auto parsed_color = parse_color(context, parts[3]);
  560. if (!parsed_color)
  561. return nullptr;
  562. color = parsed_color->color();
  563. }
  564. return CSS::BoxShadowStyleValue::create(offset_x, offset_y, blur_radius, color);
  565. }
  566. RefPtr<CSS::StyleValue> parse_css_value(const CSS::DeprecatedParsingContext& context, const StringView& string, CSS::PropertyID property_id)
  567. {
  568. bool is_bad_length = false;
  569. if (property_id == CSS::PropertyID::BoxShadow) {
  570. auto parsed_box_shadow = parse_box_shadow(context, string);
  571. if (parsed_box_shadow)
  572. return parsed_box_shadow;
  573. }
  574. if (takes_integer_value(property_id)) {
  575. auto integer = string.to_int();
  576. if (integer.has_value())
  577. return CSS::LengthStyleValue::create(CSS::Length::make_px(integer.value()));
  578. }
  579. auto length = parse_length(context, string, is_bad_length);
  580. if (is_bad_length) {
  581. auto float_number = try_parse_float(string);
  582. if (float_number.has_value())
  583. return CSS::NumericStyleValue::create(float_number.value());
  584. return nullptr;
  585. }
  586. if (!length.is_undefined())
  587. return CSS::LengthStyleValue::create(length);
  588. if (string.equals_ignoring_case("inherit"))
  589. return CSS::InheritStyleValue::create();
  590. if (string.equals_ignoring_case("initial"))
  591. return CSS::InitialStyleValue::create();
  592. if (string.equals_ignoring_case("auto"))
  593. return CSS::LengthStyleValue::create(CSS::Length::make_auto());
  594. if (string.starts_with("var("))
  595. return CSS::CustomStyleValue::create(parse_custom_property_name(string));
  596. if (string.starts_with("calc(")) {
  597. auto calc_expression_string = isolate_calc_expression(string);
  598. auto calc_expression = parse_calc_expression(calc_expression_string);
  599. if (calc_expression)
  600. return CSS::CalculatedStyleValue::create(calc_expression_string, calc_expression.release_nonnull());
  601. }
  602. auto value_id = CSS::value_id_from_string(string);
  603. if (value_id != CSS::ValueID::Invalid)
  604. return CSS::IdentifierStyleValue::create(value_id);
  605. auto color = parse_css_color(context, string);
  606. if (color.has_value())
  607. return CSS::ColorStyleValue::create(color.value());
  608. return CSS::StringStyleValue::create(string);
  609. }
  610. RefPtr<CSS::LengthStyleValue> parse_line_width(const CSS::DeprecatedParsingContext& context, const StringView& part)
  611. {
  612. auto value = parse_css_value(context, part);
  613. if (value && value->is_length())
  614. return static_ptr_cast<CSS::LengthStyleValue>(value);
  615. return nullptr;
  616. }
  617. RefPtr<CSS::ColorStyleValue> parse_color(const CSS::DeprecatedParsingContext& context, const StringView& part)
  618. {
  619. auto value = parse_css_value(context, part);
  620. if (value && value->is_color())
  621. return static_ptr_cast<CSS::ColorStyleValue>(value);
  622. return nullptr;
  623. }
  624. RefPtr<CSS::IdentifierStyleValue> parse_line_style(const CSS::DeprecatedParsingContext& context, const StringView& part)
  625. {
  626. auto parsed_value = parse_css_value(context, part);
  627. if (!parsed_value || parsed_value->type() != CSS::StyleValue::Type::Identifier)
  628. return nullptr;
  629. auto value = static_ptr_cast<CSS::IdentifierStyleValue>(parsed_value);
  630. if (value->id() == CSS::ValueID::Dotted)
  631. return value;
  632. if (value->id() == CSS::ValueID::Dashed)
  633. return value;
  634. if (value->id() == CSS::ValueID::Solid)
  635. return value;
  636. if (value->id() == CSS::ValueID::Double)
  637. return value;
  638. if (value->id() == CSS::ValueID::Groove)
  639. return value;
  640. if (value->id() == CSS::ValueID::Ridge)
  641. return value;
  642. if (value->id() == CSS::ValueID::None)
  643. return value;
  644. if (value->id() == CSS::ValueID::Hidden)
  645. return value;
  646. if (value->id() == CSS::ValueID::Inset)
  647. return value;
  648. if (value->id() == CSS::ValueID::Outset)
  649. return value;
  650. return nullptr;
  651. }
  652. class CSSParser {
  653. public:
  654. CSSParser(const CSS::DeprecatedParsingContext& context, const StringView& input)
  655. : m_context(context)
  656. , css(input)
  657. {
  658. }
  659. bool next_is(const char* str) const
  660. {
  661. size_t len = strlen(str);
  662. for (size_t i = 0; i < len; ++i) {
  663. if (peek(i) != str[i])
  664. return false;
  665. }
  666. return true;
  667. }
  668. char peek(size_t offset = 0) const
  669. {
  670. if ((index + offset) < css.length())
  671. return css[index + offset];
  672. return 0;
  673. }
  674. bool consume_specific(char ch)
  675. {
  676. if (peek() != ch) {
  677. dbgln("CSSParser: Peeked '{:c}' wanted specific '{:c}'", peek(), ch);
  678. }
  679. if (!peek()) {
  680. log_parse_error();
  681. return false;
  682. }
  683. if (peek() != ch) {
  684. log_parse_error();
  685. ++index;
  686. return false;
  687. }
  688. ++index;
  689. return true;
  690. }
  691. char consume_one()
  692. {
  693. PARSE_VERIFY(index < css.length());
  694. return css[index++];
  695. };
  696. bool consume_whitespace_or_comments()
  697. {
  698. size_t original_index = index;
  699. bool in_comment = false;
  700. for (; index < css.length(); ++index) {
  701. char ch = peek();
  702. if (isspace(ch))
  703. continue;
  704. if (!in_comment && ch == '/' && peek(1) == '*') {
  705. in_comment = true;
  706. ++index;
  707. continue;
  708. }
  709. if (in_comment && ch == '*' && peek(1) == '/') {
  710. in_comment = false;
  711. ++index;
  712. continue;
  713. }
  714. if (in_comment)
  715. continue;
  716. break;
  717. }
  718. return original_index != index;
  719. }
  720. static bool is_valid_selector_char(char ch)
  721. {
  722. return isalnum(ch) || ch == '-' || ch == '+' || ch == '_' || ch == '(' || ch == ')' || ch == '@';
  723. }
  724. static bool is_valid_selector_args_char(char ch)
  725. {
  726. return is_valid_selector_char(ch) || ch == ' ' || ch == '\t';
  727. }
  728. bool is_combinator(char ch) const
  729. {
  730. return ch == '~' || ch == '>' || ch == '+';
  731. }
  732. static StringView capture_selector_args(const String& pseudo_name)
  733. {
  734. if (const auto start_pos = pseudo_name.find('('); start_pos.has_value()) {
  735. const auto start = start_pos.value() + 1;
  736. if (const auto end_pos = pseudo_name.find(')', start); end_pos.has_value()) {
  737. return pseudo_name.substring_view(start, end_pos.value() - start).trim_whitespace();
  738. }
  739. }
  740. return {};
  741. }
  742. Optional<CSS::Selector::SimpleSelector> parse_simple_selector()
  743. {
  744. auto index_at_start = index;
  745. if (consume_whitespace_or_comments())
  746. return {};
  747. if (!peek() || peek() == '{' || peek() == ',' || is_combinator(peek()))
  748. return {};
  749. CSS::Selector::SimpleSelector simple_selector;
  750. if (peek() == '*') {
  751. simple_selector.type = CSS::Selector::SimpleSelector::Type::Universal;
  752. consume_one();
  753. return simple_selector;
  754. }
  755. if (peek() == '.') {
  756. simple_selector.type = CSS::Selector::SimpleSelector::Type::Class;
  757. consume_one();
  758. } else if (peek() == '#') {
  759. simple_selector.type = CSS::Selector::SimpleSelector::Type::Id;
  760. consume_one();
  761. } else if (isalpha(peek())) {
  762. simple_selector.type = CSS::Selector::SimpleSelector::Type::TagName;
  763. } else if (peek() == '[') {
  764. simple_selector.type = CSS::Selector::SimpleSelector::Type::Attribute;
  765. } else if (peek() == ':') {
  766. simple_selector.type = CSS::Selector::SimpleSelector::Type::PseudoClass;
  767. } else {
  768. simple_selector.type = CSS::Selector::SimpleSelector::Type::Universal;
  769. }
  770. if ((simple_selector.type != CSS::Selector::SimpleSelector::Type::Universal)
  771. && (simple_selector.type != CSS::Selector::SimpleSelector::Type::Attribute)
  772. && (simple_selector.type != CSS::Selector::SimpleSelector::Type::PseudoClass)) {
  773. while (is_valid_selector_char(peek()))
  774. buffer.append(consume_one());
  775. PARSE_VERIFY(!buffer.is_empty());
  776. }
  777. auto value = String::copy(buffer);
  778. if (simple_selector.type == CSS::Selector::SimpleSelector::Type::TagName) {
  779. // Some stylesheets use uppercase tag names, so here's a hack to just lowercase them internally.
  780. value = value.to_lowercase();
  781. }
  782. simple_selector.value = value;
  783. buffer.clear();
  784. if (simple_selector.type == CSS::Selector::SimpleSelector::Type::Attribute) {
  785. CSS::Selector::SimpleSelector::Attribute::MatchType attribute_match_type = CSS::Selector::SimpleSelector::Attribute::MatchType::HasAttribute;
  786. String attribute_name;
  787. String attribute_value;
  788. bool in_value = false;
  789. consume_specific('[');
  790. char expected_end_of_attribute_selector = ']';
  791. while (peek() != expected_end_of_attribute_selector) {
  792. char ch = consume_one();
  793. if (ch == '=' || (ch == '~' && peek() == '=')) {
  794. if (ch == '=') {
  795. attribute_match_type = CSS::Selector::SimpleSelector::Attribute::MatchType::ExactValueMatch;
  796. } else if (ch == '~') {
  797. consume_one();
  798. attribute_match_type = CSS::Selector::SimpleSelector::Attribute::MatchType::ContainsWord;
  799. }
  800. attribute_name = String::copy(buffer);
  801. buffer.clear();
  802. in_value = true;
  803. consume_whitespace_or_comments();
  804. if (peek() == '\'') {
  805. expected_end_of_attribute_selector = '\'';
  806. consume_one();
  807. } else if (peek() == '"') {
  808. expected_end_of_attribute_selector = '"';
  809. consume_one();
  810. }
  811. continue;
  812. }
  813. // FIXME: This is a hack that will go away when we replace this with a big boy CSS parser.
  814. if (ch == '\\')
  815. ch = consume_one();
  816. buffer.append(ch);
  817. }
  818. if (in_value)
  819. attribute_value = String::copy(buffer);
  820. else
  821. attribute_name = String::copy(buffer);
  822. buffer.clear();
  823. simple_selector.attribute.match_type = attribute_match_type;
  824. simple_selector.attribute.name = attribute_name;
  825. simple_selector.attribute.value = attribute_value;
  826. if (expected_end_of_attribute_selector != ']') {
  827. if (!consume_specific(expected_end_of_attribute_selector))
  828. return {};
  829. }
  830. consume_whitespace_or_comments();
  831. if (!consume_specific(']'))
  832. return {};
  833. }
  834. if (simple_selector.type == CSS::Selector::SimpleSelector::Type::PseudoClass) {
  835. // FIXME: Implement pseudo elements.
  836. [[maybe_unused]] bool is_pseudo_element = false;
  837. consume_one();
  838. if (peek() == ':') {
  839. is_pseudo_element = true;
  840. consume_one();
  841. }
  842. if (next_is("not")) {
  843. buffer.append(consume_one());
  844. buffer.append(consume_one());
  845. buffer.append(consume_one());
  846. if (!consume_specific('('))
  847. return {};
  848. buffer.append('(');
  849. while (peek() != ')')
  850. buffer.append(consume_one());
  851. if (!consume_specific(')'))
  852. return {};
  853. buffer.append(')');
  854. } else {
  855. int nesting_level = 0;
  856. while (true) {
  857. const auto ch = peek();
  858. if (ch == '(')
  859. ++nesting_level;
  860. else if (ch == ')' && nesting_level > 0)
  861. --nesting_level;
  862. if (nesting_level > 0 ? is_valid_selector_args_char(ch) : is_valid_selector_char(ch))
  863. buffer.append(consume_one());
  864. else
  865. break;
  866. };
  867. }
  868. auto pseudo_name = String::copy(buffer);
  869. buffer.clear();
  870. // Ignore for now, otherwise we produce a "false positive" selector
  871. // and apply styles to the element itself, not its pseudo element
  872. if (is_pseudo_element)
  873. return {};
  874. auto& pseudo_class = simple_selector.pseudo_class;
  875. if (pseudo_name.equals_ignoring_case("link")) {
  876. pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::Link;
  877. } else if (pseudo_name.equals_ignoring_case("visited")) {
  878. pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::Visited;
  879. } else if (pseudo_name.equals_ignoring_case("active")) {
  880. pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::Active;
  881. } else if (pseudo_name.equals_ignoring_case("hover")) {
  882. pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::Hover;
  883. } else if (pseudo_name.equals_ignoring_case("focus")) {
  884. pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::Focus;
  885. } else if (pseudo_name.equals_ignoring_case("first-child")) {
  886. pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::FirstChild;
  887. } else if (pseudo_name.equals_ignoring_case("last-child")) {
  888. pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::LastChild;
  889. } else if (pseudo_name.equals_ignoring_case("only-child")) {
  890. pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::OnlyChild;
  891. } else if (pseudo_name.equals_ignoring_case("empty")) {
  892. pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::Empty;
  893. } else if (pseudo_name.equals_ignoring_case("root")) {
  894. pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::Root;
  895. } else if (pseudo_name.equals_ignoring_case("first-of-type")) {
  896. pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::FirstOfType;
  897. } else if (pseudo_name.equals_ignoring_case("last-of-type")) {
  898. pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::LastOfType;
  899. } else if (pseudo_name.starts_with("nth-child", CaseSensitivity::CaseInsensitive)) {
  900. pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::NthChild;
  901. pseudo_class.nth_child_pattern = CSS::Selector::SimpleSelector::NthChildPattern::parse(capture_selector_args(pseudo_name));
  902. } else if (pseudo_name.starts_with("nth-last-child", CaseSensitivity::CaseInsensitive)) {
  903. pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::NthLastChild;
  904. pseudo_class.nth_child_pattern = CSS::Selector::SimpleSelector::NthChildPattern::parse(capture_selector_args(pseudo_name));
  905. } else if (pseudo_name.equals_ignoring_case("before")) {
  906. simple_selector.pseudo_element = CSS::Selector::SimpleSelector::PseudoElement::Before;
  907. } else if (pseudo_name.equals_ignoring_case("after")) {
  908. simple_selector.pseudo_element = CSS::Selector::SimpleSelector::PseudoElement::After;
  909. } else if (pseudo_name.equals_ignoring_case("disabled")) {
  910. pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::Disabled;
  911. } else if (pseudo_name.equals_ignoring_case("enabled")) {
  912. pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::Enabled;
  913. } else if (pseudo_name.equals_ignoring_case("checked")) {
  914. pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::Checked;
  915. } else if (pseudo_name.starts_with("not", CaseSensitivity::CaseInsensitive)) {
  916. pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::Not;
  917. auto not_selector = Web::parse_selector(m_context, capture_selector_args(pseudo_name));
  918. if (not_selector) {
  919. pseudo_class.not_selector.clear();
  920. pseudo_class.not_selector.append(not_selector.release_nonnull());
  921. }
  922. } else {
  923. dbgln("Unknown pseudo class: '{}'", pseudo_name);
  924. return {};
  925. }
  926. }
  927. if (index == index_at_start) {
  928. // We consumed nothing.
  929. return {};
  930. }
  931. return simple_selector;
  932. }
  933. Optional<CSS::Selector::CompoundSelector> parse_complex_selector()
  934. {
  935. auto relation = CSS::Selector::Combinator::Descendant;
  936. if (peek() == '{' || peek() == ',')
  937. return {};
  938. if (is_combinator(peek())) {
  939. switch (peek()) {
  940. case '>':
  941. relation = CSS::Selector::Combinator::ImmediateChild;
  942. break;
  943. case '+':
  944. relation = CSS::Selector::Combinator::NextSibling;
  945. break;
  946. case '~':
  947. relation = CSS::Selector::Combinator::SubsequentSibling;
  948. break;
  949. }
  950. consume_one();
  951. consume_whitespace_or_comments();
  952. }
  953. consume_whitespace_or_comments();
  954. Vector<CSS::Selector::SimpleSelector> simple_selectors;
  955. for (;;) {
  956. auto component = parse_simple_selector();
  957. if (!component.has_value())
  958. break;
  959. simple_selectors.append(component.value());
  960. // If this assert triggers, we're most likely up to no good.
  961. PARSE_VERIFY(simple_selectors.size() < 100);
  962. }
  963. if (simple_selectors.is_empty())
  964. return {};
  965. return CSS::Selector::CompoundSelector { relation, move(simple_selectors) };
  966. }
  967. void parse_selector()
  968. {
  969. Vector<CSS::Selector::CompoundSelector> complex_selectors;
  970. for (;;) {
  971. auto index_before = index;
  972. auto complex_selector = parse_complex_selector();
  973. if (complex_selector.has_value())
  974. complex_selectors.append(complex_selector.value());
  975. consume_whitespace_or_comments();
  976. if (!peek() || peek() == ',' || peek() == '{')
  977. break;
  978. // HACK: If we didn't move forward, just let go.
  979. if (index == index_before)
  980. break;
  981. }
  982. if (complex_selectors.is_empty())
  983. return;
  984. complex_selectors.first().combinator = CSS::Selector::Combinator::None;
  985. current_rule.selectors.append(CSS::Selector::create(move(complex_selectors)));
  986. }
  987. RefPtr<CSS::Selector> parse_individual_selector()
  988. {
  989. parse_selector();
  990. if (current_rule.selectors.is_empty())
  991. return {};
  992. return current_rule.selectors.last();
  993. }
  994. void parse_selector_list()
  995. {
  996. for (;;) {
  997. auto index_before = index;
  998. parse_selector();
  999. consume_whitespace_or_comments();
  1000. if (peek() == ',') {
  1001. consume_one();
  1002. continue;
  1003. }
  1004. if (peek() == '{')
  1005. break;
  1006. // HACK: If we didn't move forward, just let go.
  1007. if (index_before == index)
  1008. break;
  1009. }
  1010. }
  1011. bool is_valid_property_name_char(char ch) const
  1012. {
  1013. return ch && !isspace(ch) && ch != ':';
  1014. }
  1015. bool is_valid_property_value_char(char ch) const
  1016. {
  1017. return ch && ch != '!' && ch != ';' && ch != '}';
  1018. }
  1019. bool is_valid_string_quotes_char(char ch) const
  1020. {
  1021. return ch == '\'' || ch == '\"';
  1022. }
  1023. struct ValueAndImportant {
  1024. String value;
  1025. bool important { false };
  1026. };
  1027. ValueAndImportant consume_css_value()
  1028. {
  1029. buffer.clear();
  1030. int paren_nesting_level = 0;
  1031. bool important = false;
  1032. for (;;) {
  1033. char ch = peek();
  1034. if (ch == '(') {
  1035. ++paren_nesting_level;
  1036. buffer.append(consume_one());
  1037. continue;
  1038. }
  1039. if (ch == ')') {
  1040. PARSE_VERIFY(paren_nesting_level > 0);
  1041. --paren_nesting_level;
  1042. buffer.append(consume_one());
  1043. continue;
  1044. }
  1045. if (paren_nesting_level > 0) {
  1046. buffer.append(consume_one());
  1047. continue;
  1048. }
  1049. if (next_is("!important")) {
  1050. consume_specific('!');
  1051. consume_specific('i');
  1052. consume_specific('m');
  1053. consume_specific('p');
  1054. consume_specific('o');
  1055. consume_specific('r');
  1056. consume_specific('t');
  1057. consume_specific('a');
  1058. consume_specific('n');
  1059. consume_specific('t');
  1060. important = true;
  1061. continue;
  1062. }
  1063. if (next_is("/*")) {
  1064. consume_whitespace_or_comments();
  1065. continue;
  1066. }
  1067. if (!ch)
  1068. break;
  1069. if (ch == '\\') {
  1070. consume_one();
  1071. buffer.append(consume_one());
  1072. continue;
  1073. }
  1074. if (ch == '}')
  1075. break;
  1076. if (ch == ';')
  1077. break;
  1078. buffer.append(consume_one());
  1079. }
  1080. // Remove trailing whitespace.
  1081. while (!buffer.is_empty() && isspace(buffer.last()))
  1082. buffer.take_last();
  1083. auto string = String::copy(buffer);
  1084. buffer.clear();
  1085. return { string, important };
  1086. }
  1087. Optional<CSS::StyleProperty> parse_property()
  1088. {
  1089. consume_whitespace_or_comments();
  1090. if (peek() == ';') {
  1091. consume_one();
  1092. return {};
  1093. }
  1094. if (peek() == '}')
  1095. return {};
  1096. buffer.clear();
  1097. while (is_valid_property_name_char(peek()))
  1098. buffer.append(consume_one());
  1099. auto property_name = String::copy(buffer);
  1100. buffer.clear();
  1101. consume_whitespace_or_comments();
  1102. if (!consume_specific(':'))
  1103. return {};
  1104. consume_whitespace_or_comments();
  1105. auto [property_value, important] = consume_css_value();
  1106. consume_whitespace_or_comments();
  1107. if (peek() && peek() != '}') {
  1108. if (!consume_specific(';'))
  1109. return {};
  1110. }
  1111. auto property_id = CSS::property_id_from_string(property_name);
  1112. if (property_id == CSS::PropertyID::Invalid && property_name.starts_with("--"))
  1113. property_id = CSS::PropertyID::Custom;
  1114. if (property_id == CSS::PropertyID::Invalid && !property_name.starts_with("-")) {
  1115. dbgln("CSSParser: Unrecognized property '{}'", property_name);
  1116. }
  1117. auto value = parse_css_value(m_context, property_value, property_id);
  1118. if (!value)
  1119. return {};
  1120. if (property_id == CSS::PropertyID::Custom) {
  1121. return CSS::StyleProperty { property_id, value.release_nonnull(), property_name, important };
  1122. }
  1123. return CSS::StyleProperty { property_id, value.release_nonnull(), {}, important };
  1124. }
  1125. void parse_declaration()
  1126. {
  1127. for (;;) {
  1128. auto property = parse_property();
  1129. if (property.has_value()) {
  1130. auto property_value = property.value();
  1131. if (property_value.property_id == CSS::PropertyID::Custom)
  1132. current_rule.custom_properties.set(property_value.custom_name, property_value);
  1133. else
  1134. current_rule.properties.append(property_value);
  1135. }
  1136. consume_whitespace_or_comments();
  1137. if (!peek() || peek() == '}')
  1138. break;
  1139. }
  1140. }
  1141. void parse_style_rule()
  1142. {
  1143. parse_selector_list();
  1144. if (!consume_specific('{')) {
  1145. log_parse_error();
  1146. return;
  1147. }
  1148. parse_declaration();
  1149. if (!consume_specific('}')) {
  1150. log_parse_error();
  1151. return;
  1152. }
  1153. rules.append(CSS::CSSStyleRule::create(move(current_rule.selectors), CSS::CSSStyleDeclaration::create(move(current_rule.properties), move(current_rule.custom_properties))));
  1154. }
  1155. Optional<String> parse_string()
  1156. {
  1157. if (!is_valid_string_quotes_char(peek())) {
  1158. log_parse_error();
  1159. return {};
  1160. }
  1161. char end_char = consume_one();
  1162. buffer.clear();
  1163. while (peek() && peek() != end_char) {
  1164. if (peek() == '\\') {
  1165. consume_specific('\\');
  1166. if (peek() == 0)
  1167. break;
  1168. }
  1169. buffer.append(consume_one());
  1170. }
  1171. String string_value(String::copy(buffer));
  1172. buffer.clear();
  1173. if (consume_specific(end_char)) {
  1174. return { string_value };
  1175. }
  1176. return {};
  1177. }
  1178. Optional<String> parse_url()
  1179. {
  1180. if (is_valid_string_quotes_char(peek()))
  1181. return parse_string();
  1182. buffer.clear();
  1183. while (peek() && peek() != ')')
  1184. buffer.append(consume_one());
  1185. String url_value(String::copy(buffer));
  1186. buffer.clear();
  1187. if (peek() == ')')
  1188. return { url_value };
  1189. return {};
  1190. }
  1191. void parse_at_import_rule()
  1192. {
  1193. consume_whitespace_or_comments();
  1194. Optional<String> imported_address;
  1195. if (is_valid_string_quotes_char(peek())) {
  1196. imported_address = parse_string();
  1197. } else if (next_is("url")) {
  1198. consume_specific('u');
  1199. consume_specific('r');
  1200. consume_specific('l');
  1201. consume_whitespace_or_comments();
  1202. if (!consume_specific('('))
  1203. return;
  1204. imported_address = parse_url();
  1205. if (!consume_specific(')'))
  1206. return;
  1207. } else {
  1208. log_parse_error();
  1209. return;
  1210. }
  1211. if (imported_address.has_value())
  1212. rules.append(CSS::CSSImportRule::create(m_context.complete_url(imported_address.value())));
  1213. // FIXME: We ignore possible media query list
  1214. while (peek() && peek() != ';')
  1215. consume_one();
  1216. consume_specific(';');
  1217. }
  1218. void parse_at_rule()
  1219. {
  1220. HashMap<String, void (CSSParser::*)()> at_rules_parsers({ { "@import", &CSSParser::parse_at_import_rule } });
  1221. for (const auto& rule_parser_pair : at_rules_parsers) {
  1222. if (next_is(rule_parser_pair.key.characters())) {
  1223. for (char c : rule_parser_pair.key) {
  1224. consume_specific(c);
  1225. }
  1226. (this->*(rule_parser_pair.value))();
  1227. return;
  1228. }
  1229. }
  1230. // FIXME: We ignore other @-rules completely for now.
  1231. int level = 0;
  1232. bool in_comment = false;
  1233. while (peek() != 0) {
  1234. auto ch = consume_one();
  1235. if (!in_comment) {
  1236. if (ch == '/' && peek() == '*') {
  1237. consume_one();
  1238. in_comment = true;
  1239. } else if (ch == '{') {
  1240. ++level;
  1241. } else if (ch == '}') {
  1242. --level;
  1243. if (level == 0)
  1244. break;
  1245. }
  1246. } else {
  1247. if (ch == '*' && peek() == '/') {
  1248. consume_one();
  1249. in_comment = false;
  1250. }
  1251. }
  1252. }
  1253. }
  1254. void parse_rule()
  1255. {
  1256. consume_whitespace_or_comments();
  1257. if (!peek())
  1258. return;
  1259. if (peek() == '@') {
  1260. parse_at_rule();
  1261. } else {
  1262. parse_style_rule();
  1263. }
  1264. consume_whitespace_or_comments();
  1265. }
  1266. RefPtr<CSS::CSSStyleSheet> parse_sheet()
  1267. {
  1268. if (peek(0) == (char)0xef && peek(1) == (char)0xbb && peek(2) == (char)0xbf) {
  1269. // HACK: Skip UTF-8 BOM.
  1270. index += 3;
  1271. }
  1272. while (peek()) {
  1273. parse_rule();
  1274. }
  1275. return CSS::CSSStyleSheet::create(move(rules));
  1276. }
  1277. RefPtr<CSS::CSSStyleDeclaration> parse_standalone_declaration()
  1278. {
  1279. consume_whitespace_or_comments();
  1280. for (;;) {
  1281. auto property = parse_property();
  1282. if (property.has_value()) {
  1283. auto property_value = property.value();
  1284. if (property_value.property_id == CSS::PropertyID::Custom)
  1285. current_rule.custom_properties.set(property_value.custom_name, property_value);
  1286. else
  1287. current_rule.properties.append(property_value);
  1288. }
  1289. consume_whitespace_or_comments();
  1290. if (!peek())
  1291. break;
  1292. }
  1293. return CSS::CSSStyleDeclaration::create(move(current_rule.properties), move(current_rule.custom_properties));
  1294. }
  1295. private:
  1296. CSS::DeprecatedParsingContext m_context;
  1297. NonnullRefPtrVector<CSS::CSSRule> rules;
  1298. struct CurrentRule {
  1299. NonnullRefPtrVector<CSS::Selector> selectors;
  1300. Vector<CSS::StyleProperty> properties;
  1301. HashMap<String, CSS::StyleProperty> custom_properties;
  1302. };
  1303. CurrentRule current_rule;
  1304. Vector<char> buffer;
  1305. size_t index = 0;
  1306. StringView css;
  1307. };
  1308. RefPtr<CSS::Selector> parse_selector(const CSS::DeprecatedParsingContext& context, const StringView& selector_text)
  1309. {
  1310. CSSParser parser(context, selector_text);
  1311. return parser.parse_individual_selector();
  1312. }
  1313. RefPtr<CSS::CSSStyleSheet> parse_css(const CSS::DeprecatedParsingContext& context, const StringView& css)
  1314. {
  1315. if (css.is_empty())
  1316. return CSS::CSSStyleSheet::create({});
  1317. CSSParser parser(context, css);
  1318. return parser.parse_sheet();
  1319. }
  1320. RefPtr<CSS::CSSStyleDeclaration> parse_css_declaration(const CSS::DeprecatedParsingContext& context, const StringView& css)
  1321. {
  1322. if (css.is_empty())
  1323. return CSS::CSSStyleDeclaration::create({}, {});
  1324. CSSParser parser(context, css);
  1325. return parser.parse_standalone_declaration();
  1326. }
  1327. RefPtr<CSS::StyleValue> parse_html_length(const DOM::Document& document, const StringView& string)
  1328. {
  1329. auto integer = string.to_int();
  1330. if (integer.has_value())
  1331. return CSS::LengthStyleValue::create(CSS::Length::make_px(integer.value()));
  1332. return parse_css_value(CSS::DeprecatedParsingContext(document), string);
  1333. }
  1334. }