GenerateCSSPropertyID.cpp 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020
  1. /*
  2. * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
  3. * Copyright (c) 2021-2023, Sam Atkins <atkinssj@serenityos.org>
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include "GeneratorUtil.h"
  8. #include <AK/CharacterTypes.h>
  9. #include <AK/GenericShorthands.h>
  10. #include <AK/SourceGenerator.h>
  11. #include <AK/StringBuilder.h>
  12. #include <LibCore/ArgsParser.h>
  13. #include <LibMain/Main.h>
  14. void replace_logical_aliases(JsonObject& properties);
  15. ErrorOr<void> generate_header_file(JsonObject& properties, Core::File& file);
  16. ErrorOr<void> generate_implementation_file(JsonObject& properties, Core::File& file);
  17. void generate_bounds_checking_function(JsonObject& properties, SourceGenerator& parent_generator, StringView css_type_name, StringView type_name, Optional<StringView> default_unit_name = {}, Optional<StringView> value_getter = {});
  18. bool is_animatable_property(JsonObject& properties, StringView property_name);
  19. static bool type_name_is_enum(StringView type_name)
  20. {
  21. return !AK::first_is_one_of(type_name,
  22. "angle"sv,
  23. "background-position"sv,
  24. "basic-shape"sv,
  25. "color"sv,
  26. "counter"sv,
  27. "custom-ident"sv,
  28. "easing-function"sv,
  29. "flex"sv,
  30. "frequency"sv,
  31. "image"sv,
  32. "integer"sv,
  33. "length"sv,
  34. "number"sv,
  35. "paint"sv,
  36. "percentage"sv,
  37. "position"sv,
  38. "ratio"sv,
  39. "rect"sv,
  40. "resolution"sv,
  41. "string"sv,
  42. "time"sv,
  43. "url"sv);
  44. }
  45. ErrorOr<int> serenity_main(Main::Arguments arguments)
  46. {
  47. StringView generated_header_path;
  48. StringView generated_implementation_path;
  49. StringView properties_json_path;
  50. Core::ArgsParser args_parser;
  51. args_parser.add_option(generated_header_path, "Path to the PropertyID header file to generate", "generated-header-path", 'h', "generated-header-path");
  52. args_parser.add_option(generated_implementation_path, "Path to the PropertyID implementation file to generate", "generated-implementation-path", 'c', "generated-implementation-path");
  53. args_parser.add_option(properties_json_path, "Path to the JSON file to read from", "json-path", 'j', "json-path");
  54. args_parser.parse(arguments);
  55. auto json = TRY(read_entire_file_as_json(properties_json_path));
  56. VERIFY(json.is_object());
  57. auto properties = json.as_object();
  58. // Check we're in alphabetical order
  59. ByteString most_recent_name = "";
  60. properties.for_each_member([&](auto& name, auto&) {
  61. if (name < most_recent_name) {
  62. warnln("`{}` is in the wrong position in `{}`. Please keep this list alphabetical!", name, properties_json_path);
  63. VERIFY_NOT_REACHED();
  64. }
  65. most_recent_name = name;
  66. });
  67. replace_logical_aliases(properties);
  68. auto generated_header_file = TRY(Core::File::open(generated_header_path, Core::File::OpenMode::Write));
  69. auto generated_implementation_file = TRY(Core::File::open(generated_implementation_path, Core::File::OpenMode::Write));
  70. TRY(generate_header_file(properties, *generated_header_file));
  71. TRY(generate_implementation_file(properties, *generated_implementation_file));
  72. return 0;
  73. }
  74. void replace_logical_aliases(JsonObject& properties)
  75. {
  76. AK::HashMap<ByteString, ByteString> logical_aliases;
  77. properties.for_each_member([&](auto& name, auto& value) {
  78. VERIFY(value.is_object());
  79. auto const& value_as_object = value.as_object();
  80. auto const logical_alias_for = value_as_object.get_array("logical-alias-for"sv);
  81. if (logical_alias_for.has_value()) {
  82. auto const& aliased_properties = logical_alias_for.value();
  83. for (auto const& aliased_property : aliased_properties.values()) {
  84. logical_aliases.set(name, aliased_property.as_string());
  85. }
  86. }
  87. });
  88. for (auto& [name, alias] : logical_aliases) {
  89. auto const maybe_alias_object = properties.get_object(alias);
  90. if (!maybe_alias_object.has_value()) {
  91. dbgln("No property '{}' found for logical alias '{}'", alias, name);
  92. VERIFY_NOT_REACHED();
  93. }
  94. JsonObject alias_object = maybe_alias_object.value();
  95. // Copy over anything the logical property overrides
  96. properties.get_object(name).value().for_each_member([&](auto& key, auto& value) {
  97. alias_object.set(key, value);
  98. });
  99. properties.set(name, alias_object);
  100. }
  101. }
  102. ErrorOr<void> generate_header_file(JsonObject& properties, Core::File& file)
  103. {
  104. StringBuilder builder;
  105. SourceGenerator generator { builder };
  106. generator.append(R"~~~(
  107. #pragma once
  108. #include <AK/NonnullRefPtr.h>
  109. #include <AK/StringView.h>
  110. #include <AK/Traits.h>
  111. #include <LibJS/Forward.h>
  112. #include <LibWeb/Forward.h>
  113. namespace Web::CSS {
  114. enum class PropertyID {
  115. Invalid,
  116. Custom,
  117. All,
  118. )~~~");
  119. Vector<ByteString> shorthand_property_ids;
  120. Vector<ByteString> longhand_property_ids;
  121. properties.for_each_member([&](auto& name, auto& value) {
  122. VERIFY(value.is_object());
  123. if (value.as_object().has("longhands"sv))
  124. shorthand_property_ids.append(name);
  125. else
  126. longhand_property_ids.append(name);
  127. });
  128. auto first_property_id = shorthand_property_ids.first();
  129. auto last_property_id = longhand_property_ids.last();
  130. for (auto& name : shorthand_property_ids) {
  131. auto member_generator = generator.fork();
  132. member_generator.set("name:titlecase", title_casify(name));
  133. member_generator.append(R"~~~(
  134. @name:titlecase@,
  135. )~~~");
  136. }
  137. for (auto& name : longhand_property_ids) {
  138. auto member_generator = generator.fork();
  139. member_generator.set("name:titlecase", title_casify(name));
  140. member_generator.append(R"~~~(
  141. @name:titlecase@,
  142. )~~~");
  143. }
  144. generator.set("first_property_id", title_casify(first_property_id));
  145. generator.set("last_property_id", title_casify(last_property_id));
  146. generator.set("first_shorthand_property_id", title_casify(shorthand_property_ids.first()));
  147. generator.set("last_shorthand_property_id", title_casify(shorthand_property_ids.last()));
  148. generator.set("first_longhand_property_id", title_casify(longhand_property_ids.first()));
  149. generator.set("last_longhand_property_id", title_casify(longhand_property_ids.last()));
  150. generator.append(R"~~~(
  151. };
  152. enum class AnimationType {
  153. Discrete,
  154. ByComputedValue,
  155. RepeatableList,
  156. Custom,
  157. None,
  158. };
  159. AnimationType animation_type_from_longhand_property(PropertyID);
  160. bool is_animatable_property(PropertyID);
  161. Optional<PropertyID> property_id_from_camel_case_string(StringView);
  162. Optional<PropertyID> property_id_from_string(StringView);
  163. [[nodiscard]] FlyString const& string_from_property_id(PropertyID);
  164. [[nodiscard]] FlyString const& camel_case_string_from_property_id(PropertyID);
  165. bool is_inherited_property(PropertyID);
  166. NonnullRefPtr<StyleValue> property_initial_value(JS::Realm&, PropertyID);
  167. enum class ValueType {
  168. Angle,
  169. BackgroundPosition,
  170. BasicShape,
  171. Color,
  172. Counter,
  173. CustomIdent,
  174. EasingFunction,
  175. FilterValueList,
  176. Flex,
  177. Frequency,
  178. Image,
  179. Integer,
  180. Length,
  181. Number,
  182. Paint,
  183. Percentage,
  184. Position,
  185. Ratio,
  186. Rect,
  187. Resolution,
  188. String,
  189. Time,
  190. Url,
  191. };
  192. bool property_accepts_type(PropertyID, ValueType);
  193. bool property_accepts_identifier(PropertyID, ValueID);
  194. Optional<ValueType> property_resolves_percentages_relative_to(PropertyID);
  195. // These perform range-checking, but are also safe to call with properties that don't accept that type. (They'll just return false.)
  196. bool property_accepts_angle(PropertyID, Angle const&);
  197. bool property_accepts_flex(PropertyID, Flex const&);
  198. bool property_accepts_frequency(PropertyID, Frequency const&);
  199. bool property_accepts_integer(PropertyID, i64 const&);
  200. bool property_accepts_length(PropertyID, Length const&);
  201. bool property_accepts_number(PropertyID, double const&);
  202. bool property_accepts_percentage(PropertyID, Percentage const&);
  203. bool property_accepts_resolution(PropertyID, Resolution const&);
  204. bool property_accepts_time(PropertyID, Time const&);
  205. bool property_is_shorthand(PropertyID);
  206. Vector<PropertyID> longhands_for_shorthand(PropertyID);
  207. size_t property_maximum_value_count(PropertyID);
  208. bool property_affects_layout(PropertyID);
  209. bool property_affects_stacking_context(PropertyID);
  210. constexpr PropertyID first_property_id = PropertyID::@first_property_id@;
  211. constexpr PropertyID last_property_id = PropertyID::@last_property_id@;
  212. constexpr PropertyID first_shorthand_property_id = PropertyID::@first_shorthand_property_id@;
  213. constexpr PropertyID last_shorthand_property_id = PropertyID::@last_shorthand_property_id@;
  214. constexpr PropertyID first_longhand_property_id = PropertyID::@first_longhand_property_id@;
  215. constexpr PropertyID last_longhand_property_id = PropertyID::@last_longhand_property_id@;
  216. enum class Quirk {
  217. // https://quirks.spec.whatwg.org/#the-hashless-hex-color-quirk
  218. HashlessHexColor,
  219. // https://quirks.spec.whatwg.org/#the-unitless-length-quirk
  220. UnitlessLength,
  221. };
  222. bool property_has_quirk(PropertyID, Quirk);
  223. } // namespace Web::CSS
  224. namespace AK {
  225. template<>
  226. struct Traits<Web::CSS::PropertyID> : public DefaultTraits<Web::CSS::PropertyID> {
  227. static unsigned hash(Web::CSS::PropertyID property_id) { return int_hash((unsigned)property_id); }
  228. };
  229. } // namespace AK
  230. )~~~");
  231. TRY(file.write_until_depleted(generator.as_string_view().bytes()));
  232. return {};
  233. }
  234. void generate_bounds_checking_function(JsonObject& properties, SourceGenerator& parent_generator, StringView css_type_name, StringView type_name, Optional<StringView> default_unit_name, Optional<StringView> value_getter)
  235. {
  236. auto generator = parent_generator.fork();
  237. generator.set("css_type_name", css_type_name);
  238. generator.set("type_name", type_name);
  239. generator.append(R"~~~(
  240. bool property_accepts_@css_type_name@(PropertyID property_id, [[maybe_unused]] @type_name@ const& value)
  241. {
  242. switch (property_id) {
  243. )~~~");
  244. properties.for_each_member([&](auto& name, JsonValue const& value) -> void {
  245. VERIFY(value.is_object());
  246. if (auto maybe_valid_types = value.as_object().get_array("valid-types"sv); maybe_valid_types.has_value() && !maybe_valid_types->is_empty()) {
  247. for (auto valid_type : maybe_valid_types->values()) {
  248. auto type_and_range = valid_type.as_string().split_view(' ');
  249. if (type_and_range.first() != css_type_name)
  250. continue;
  251. auto property_generator = generator.fork();
  252. property_generator.set("property_name:titlecase", title_casify(name));
  253. property_generator.append(R"~~~(
  254. case PropertyID::@property_name:titlecase@:
  255. return )~~~");
  256. if (type_and_range.size() > 1) {
  257. auto range = type_and_range[1];
  258. VERIFY(range.starts_with('[') && range.ends_with(']') && range.contains(','));
  259. auto comma_index = range.find(',').value();
  260. StringView min_value_string = range.substring_view(1, comma_index - 1);
  261. StringView max_value_string = range.substring_view(comma_index + 1, range.length() - comma_index - 2);
  262. // If the min/max value is infinite, we can just skip that side of the check.
  263. if (min_value_string == "-∞")
  264. min_value_string = {};
  265. if (max_value_string == "∞")
  266. max_value_string = {};
  267. if (min_value_string.is_empty() && max_value_string.is_empty()) {
  268. property_generator.appendln("true;");
  269. break;
  270. }
  271. auto output_check = [&](auto& value_string, StringView comparator) {
  272. if (value_getter.has_value()) {
  273. property_generator.set("value_number", value_string);
  274. property_generator.set("value_getter", value_getter.value());
  275. property_generator.set("comparator", comparator);
  276. property_generator.append("@value_getter@ @comparator@ @value_number@");
  277. return;
  278. }
  279. GenericLexer lexer { value_string };
  280. auto value_number = lexer.consume_until(is_ascii_alpha);
  281. auto value_unit = lexer.consume_while(is_ascii_alpha);
  282. if (value_unit.is_empty())
  283. value_unit = default_unit_name.value();
  284. VERIFY(lexer.is_eof());
  285. property_generator.set("value_number", value_number);
  286. property_generator.set("value_unit", title_casify(value_unit));
  287. property_generator.set("comparator", comparator);
  288. property_generator.append("value @comparator@ @type_name@(@value_number@, @type_name@::Type::@value_unit@)");
  289. };
  290. if (!min_value_string.is_empty())
  291. output_check(min_value_string, ">="sv);
  292. if (!min_value_string.is_empty() && !max_value_string.is_empty())
  293. property_generator.append(" && ");
  294. if (!max_value_string.is_empty())
  295. output_check(max_value_string, "<="sv);
  296. property_generator.appendln(";");
  297. } else {
  298. property_generator.appendln("true;");
  299. }
  300. break;
  301. }
  302. }
  303. });
  304. generator.append(R"~~~(
  305. default:
  306. return false;
  307. }
  308. }
  309. )~~~");
  310. }
  311. ErrorOr<void> generate_implementation_file(JsonObject& properties, Core::File& file)
  312. {
  313. StringBuilder builder;
  314. SourceGenerator generator { builder };
  315. generator.append(R"~~~(
  316. #include <AK/Assertions.h>
  317. #include <LibWeb/CSS/Enums.h>
  318. #include <LibWeb/CSS/Parser/Parser.h>
  319. #include <LibWeb/CSS/PropertyID.h>
  320. #include <LibWeb/CSS/StyleValue.h>
  321. #include <LibWeb/CSS/StyleValues/PercentageStyleValue.h>
  322. #include <LibWeb/CSS/StyleValues/TimeStyleValue.h>
  323. #include <LibWeb/Infra/Strings.h>
  324. namespace Web::CSS {
  325. Optional<PropertyID> property_id_from_camel_case_string(StringView string)
  326. {
  327. )~~~");
  328. properties.for_each_member([&](auto& name, auto& value) {
  329. VERIFY(value.is_object());
  330. auto member_generator = generator.fork();
  331. member_generator.set("name", name);
  332. member_generator.set("name:titlecase", title_casify(name));
  333. member_generator.set("name:camelcase", camel_casify(name));
  334. member_generator.append(R"~~~(
  335. if (string.equals_ignoring_ascii_case("@name:camelcase@"sv))
  336. return PropertyID::@name:titlecase@;
  337. )~~~");
  338. });
  339. generator.append(R"~~~(
  340. return {};
  341. }
  342. Optional<PropertyID> property_id_from_string(StringView string)
  343. {
  344. if (Infra::is_ascii_case_insensitive_match(string, "all"sv))
  345. return PropertyID::All;
  346. )~~~");
  347. properties.for_each_member([&](auto& name, auto& value) {
  348. VERIFY(value.is_object());
  349. auto member_generator = generator.fork();
  350. member_generator.set("name", name);
  351. member_generator.set("name:titlecase", title_casify(name));
  352. member_generator.append(R"~~~(
  353. if (Infra::is_ascii_case_insensitive_match(string, "@name@"sv))
  354. return PropertyID::@name:titlecase@;
  355. )~~~");
  356. });
  357. generator.append(R"~~~(
  358. return {};
  359. }
  360. FlyString const& string_from_property_id(PropertyID property_id) {
  361. switch (property_id) {
  362. )~~~");
  363. properties.for_each_member([&](auto& name, auto& value) {
  364. VERIFY(value.is_object());
  365. auto member_generator = generator.fork();
  366. member_generator.set("name", name);
  367. member_generator.set("name:titlecase", title_casify(name));
  368. member_generator.append(R"~~~(
  369. case PropertyID::@name:titlecase@: {
  370. static FlyString name = "@name@"_fly_string;
  371. return name;
  372. }
  373. )~~~");
  374. });
  375. generator.append(R"~~~(
  376. default: {
  377. static FlyString invalid_property_id_string = "(invalid CSS::PropertyID)"_fly_string;
  378. return invalid_property_id_string;
  379. }
  380. }
  381. }
  382. FlyString const& camel_case_string_from_property_id(PropertyID property_id) {
  383. switch (property_id) {
  384. )~~~");
  385. properties.for_each_member([&](auto& name, auto& value) {
  386. VERIFY(value.is_object());
  387. auto member_generator = generator.fork();
  388. member_generator.set("name", name);
  389. member_generator.set("name:titlecase", title_casify(name));
  390. member_generator.set("name:camelcase", camel_casify(name));
  391. member_generator.append(R"~~~(
  392. case PropertyID::@name:titlecase@: {
  393. static FlyString name = "@name:camelcase@"_fly_string;
  394. return name;
  395. }
  396. )~~~");
  397. });
  398. generator.append(R"~~~(
  399. default: {
  400. static FlyString invalid_property_id_string = "(invalid CSS::PropertyID)"_fly_string;
  401. return invalid_property_id_string;
  402. }
  403. }
  404. }
  405. AnimationType animation_type_from_longhand_property(PropertyID property_id)
  406. {
  407. switch (property_id) {
  408. )~~~");
  409. properties.for_each_member([&](auto& name, auto& value) {
  410. VERIFY(value.is_object());
  411. auto member_generator = generator.fork();
  412. member_generator.set("name:titlecase", title_casify(name));
  413. // Shorthand properties should have already been expanded before calling into this function
  414. if (value.as_object().has("longhands"sv)) {
  415. if (value.as_object().has("animation-type"sv)) {
  416. dbgln("Property '{}' with longhands cannot specify 'animation-type'", name);
  417. VERIFY_NOT_REACHED();
  418. }
  419. member_generator.append(R"~~~(
  420. case PropertyID::@name:titlecase@:
  421. VERIFY_NOT_REACHED();
  422. )~~~");
  423. return;
  424. }
  425. if (!value.as_object().has("animation-type"sv)) {
  426. dbgln("No animation-type specified for property '{}'", name);
  427. VERIFY_NOT_REACHED();
  428. }
  429. auto animation_type = value.as_object().get_byte_string("animation-type"sv).value();
  430. member_generator.set("value", title_casify(animation_type));
  431. member_generator.append(R"~~~(
  432. case PropertyID::@name:titlecase@:
  433. return AnimationType::@value@;
  434. )~~~");
  435. });
  436. generator.append(R"~~~(
  437. default:
  438. return AnimationType::None;
  439. }
  440. }
  441. bool is_animatable_property(PropertyID property_id)
  442. {
  443. switch (property_id) {
  444. )~~~");
  445. properties.for_each_member([&](auto& name, auto& value) {
  446. VERIFY(value.is_object());
  447. if (is_animatable_property(properties, name)) {
  448. auto member_generator = generator.fork();
  449. member_generator.set("name:titlecase", title_casify(name));
  450. member_generator.append(R"~~~(
  451. case PropertyID::@name:titlecase@:
  452. )~~~");
  453. }
  454. });
  455. generator.append(R"~~~(
  456. return true;
  457. default:
  458. return false;
  459. }
  460. }
  461. bool is_inherited_property(PropertyID property_id)
  462. {
  463. switch (property_id) {
  464. )~~~");
  465. properties.for_each_member([&](auto& name, auto& value) {
  466. VERIFY(value.is_object());
  467. bool inherited = false;
  468. if (value.as_object().has("inherited"sv)) {
  469. auto inherited_value = value.as_object().get_bool("inherited"sv);
  470. VERIFY(inherited_value.has_value());
  471. inherited = inherited_value.value();
  472. }
  473. if (inherited) {
  474. auto member_generator = generator.fork();
  475. member_generator.set("name:titlecase", title_casify(name));
  476. member_generator.append(R"~~~(
  477. case PropertyID::@name:titlecase@:
  478. return true;
  479. )~~~");
  480. }
  481. });
  482. generator.append(R"~~~(
  483. default:
  484. return false;
  485. }
  486. }
  487. bool property_affects_layout(PropertyID property_id)
  488. {
  489. switch (property_id) {
  490. )~~~");
  491. properties.for_each_member([&](auto& name, auto& value) {
  492. VERIFY(value.is_object());
  493. bool affects_layout = true;
  494. if (value.as_object().has("affects-layout"sv))
  495. affects_layout = value.as_object().get_bool("affects-layout"sv).value_or(false);
  496. if (affects_layout) {
  497. auto member_generator = generator.fork();
  498. member_generator.set("name:titlecase", title_casify(name));
  499. member_generator.append(R"~~~(
  500. case PropertyID::@name:titlecase@:
  501. )~~~");
  502. }
  503. });
  504. generator.append(R"~~~(
  505. return true;
  506. default:
  507. return false;
  508. }
  509. }
  510. bool property_affects_stacking_context(PropertyID property_id)
  511. {
  512. switch (property_id) {
  513. )~~~");
  514. properties.for_each_member([&](auto& name, auto& value) {
  515. VERIFY(value.is_object());
  516. bool affects_stacking_context = false;
  517. if (value.as_object().has("affects-stacking-context"sv))
  518. affects_stacking_context = value.as_object().get_bool("affects-stacking-context"sv).value_or(false);
  519. if (affects_stacking_context) {
  520. auto member_generator = generator.fork();
  521. member_generator.set("name:titlecase", title_casify(name));
  522. member_generator.append(R"~~~(
  523. case PropertyID::@name:titlecase@:
  524. )~~~");
  525. }
  526. });
  527. generator.append(R"~~~(
  528. return true;
  529. default:
  530. return false;
  531. }
  532. }
  533. NonnullRefPtr<StyleValue> property_initial_value(JS::Realm& context_realm, PropertyID property_id)
  534. {
  535. static Array<RefPtr<StyleValue>, to_underlying(last_property_id) + 1> initial_values;
  536. if (auto initial_value = initial_values[to_underlying(property_id)])
  537. return initial_value.release_nonnull();
  538. // Lazily parse initial values as needed.
  539. // This ensures the shorthands will always be able to get the initial values of their longhands.
  540. // This also now allows a longhand have its own longhand (like background-position-x).
  541. Parser::ParsingContext parsing_context(context_realm);
  542. switch (property_id) {
  543. )~~~");
  544. auto output_initial_value_code = [&](auto& name, auto& object) {
  545. if (!object.has("initial"sv)) {
  546. dbgln("No initial value specified for property '{}'", name);
  547. VERIFY_NOT_REACHED();
  548. }
  549. auto initial_value = object.get_byte_string("initial"sv);
  550. VERIFY(initial_value.has_value());
  551. auto& initial_value_string = initial_value.value();
  552. auto member_generator = generator.fork();
  553. member_generator.set("name:titlecase", title_casify(name));
  554. member_generator.set("initial_value_string", initial_value_string);
  555. member_generator.append(
  556. R"~~~( case PropertyID::@name:titlecase@:
  557. {
  558. auto parsed_value = parse_css_value(parsing_context, "@initial_value_string@"sv, PropertyID::@name:titlecase@);
  559. VERIFY(!parsed_value.is_null());
  560. auto initial_value = parsed_value.release_nonnull();
  561. initial_values[to_underlying(PropertyID::@name:titlecase@)] = initial_value;
  562. return initial_value;
  563. }
  564. )~~~");
  565. };
  566. properties.for_each_member([&](auto& name, auto& value) {
  567. VERIFY(value.is_object());
  568. output_initial_value_code(name, value.as_object());
  569. });
  570. generator.append(
  571. R"~~~( default: VERIFY_NOT_REACHED();
  572. }
  573. VERIFY_NOT_REACHED();
  574. }
  575. bool property_has_quirk(PropertyID property_id, Quirk quirk)
  576. {
  577. switch (property_id) {
  578. )~~~");
  579. properties.for_each_member([&](auto& name, auto& value) {
  580. VERIFY(value.is_object());
  581. if (value.as_object().has("quirks"sv)) {
  582. auto quirks_value = value.as_object().get_array("quirks"sv);
  583. VERIFY(quirks_value.has_value());
  584. auto& quirks = quirks_value.value();
  585. if (!quirks.is_empty()) {
  586. auto property_generator = generator.fork();
  587. property_generator.set("name:titlecase", title_casify(name));
  588. property_generator.append(R"~~~(
  589. case PropertyID::@name:titlecase@: {
  590. switch (quirk) {
  591. )~~~");
  592. for (auto& quirk : quirks.values()) {
  593. VERIFY(quirk.is_string());
  594. auto quirk_generator = property_generator.fork();
  595. quirk_generator.set("quirk:titlecase", title_casify(quirk.as_string()));
  596. quirk_generator.append(R"~~~(
  597. case Quirk::@quirk:titlecase@:
  598. return true;
  599. )~~~");
  600. }
  601. property_generator.append(R"~~~(
  602. default:
  603. return false;
  604. }
  605. }
  606. )~~~");
  607. }
  608. }
  609. });
  610. generator.append(R"~~~(
  611. default:
  612. return false;
  613. }
  614. }
  615. bool property_accepts_type(PropertyID property_id, ValueType value_type)
  616. {
  617. switch (property_id) {
  618. )~~~");
  619. properties.for_each_member([&](auto& name, auto& value) {
  620. VERIFY(value.is_object());
  621. auto& object = value.as_object();
  622. if (auto maybe_valid_types = object.get_array("valid-types"sv); maybe_valid_types.has_value() && !maybe_valid_types->is_empty()) {
  623. auto& valid_types = maybe_valid_types.value();
  624. auto property_generator = generator.fork();
  625. property_generator.set("name:titlecase", title_casify(name));
  626. property_generator.append(R"~~~(
  627. case PropertyID::@name:titlecase@: {
  628. switch (value_type) {
  629. )~~~");
  630. bool did_output_accepted_type = false;
  631. for (auto& type : valid_types.values()) {
  632. VERIFY(type.is_string());
  633. auto type_name = type.as_string().split_view(' ').first();
  634. if (type_name_is_enum(type_name))
  635. continue;
  636. if (type_name == "angle") {
  637. property_generator.appendln(" case ValueType::Angle:");
  638. } else if (type_name == "background-position") {
  639. property_generator.appendln(" case ValueType::BackgroundPosition:");
  640. } else if (type_name == "basic-shape") {
  641. property_generator.appendln(" case ValueType::BasicShape:");
  642. } else if (type_name == "color") {
  643. property_generator.appendln(" case ValueType::Color:");
  644. } else if (type_name == "counter") {
  645. property_generator.appendln(" case ValueType::Counter:");
  646. } else if (type_name == "custom-ident") {
  647. property_generator.appendln(" case ValueType::CustomIdent:");
  648. } else if (type_name == "easing-function") {
  649. property_generator.appendln(" case ValueType::EasingFunction:");
  650. } else if (type_name == "flex") {
  651. property_generator.appendln(" case ValueType::Flex:");
  652. } else if (type_name == "frequency") {
  653. property_generator.appendln(" case ValueType::Frequency:");
  654. } else if (type_name == "image") {
  655. property_generator.appendln(" case ValueType::Image:");
  656. } else if (type_name == "integer") {
  657. property_generator.appendln(" case ValueType::Integer:");
  658. } else if (type_name == "length") {
  659. property_generator.appendln(" case ValueType::Length:");
  660. } else if (type_name == "number") {
  661. property_generator.appendln(" case ValueType::Number:");
  662. } else if (type_name == "paint") {
  663. property_generator.appendln(" case ValueType::Paint:");
  664. } else if (type_name == "percentage") {
  665. property_generator.appendln(" case ValueType::Percentage:");
  666. } else if (type_name == "position") {
  667. property_generator.appendln(" case ValueType::Position:");
  668. } else if (type_name == "ratio") {
  669. property_generator.appendln(" case ValueType::Ratio:");
  670. } else if (type_name == "rect") {
  671. property_generator.appendln(" case ValueType::Rect:");
  672. } else if (type_name == "resolution") {
  673. property_generator.appendln(" case ValueType::Resolution:");
  674. } else if (type_name == "string") {
  675. property_generator.appendln(" case ValueType::String:");
  676. } else if (type_name == "time") {
  677. property_generator.appendln(" case ValueType::Time:");
  678. } else if (type_name == "url") {
  679. property_generator.appendln(" case ValueType::Url:");
  680. } else {
  681. VERIFY_NOT_REACHED();
  682. }
  683. did_output_accepted_type = true;
  684. }
  685. if (did_output_accepted_type)
  686. property_generator.appendln(" return true;");
  687. property_generator.append(R"~~~(
  688. default:
  689. return false;
  690. }
  691. }
  692. )~~~");
  693. }
  694. });
  695. generator.append(R"~~~(
  696. default:
  697. return false;
  698. }
  699. }
  700. bool property_accepts_identifier(PropertyID property_id, ValueID identifier)
  701. {
  702. switch (property_id) {
  703. )~~~");
  704. properties.for_each_member([&](auto& name, auto& value) {
  705. VERIFY(value.is_object());
  706. auto& object = value.as_object();
  707. auto property_generator = generator.fork();
  708. property_generator.set("name:titlecase", title_casify(name));
  709. property_generator.appendln(" case PropertyID::@name:titlecase@: {");
  710. if (auto maybe_valid_identifiers = object.get_array("valid-identifiers"sv); maybe_valid_identifiers.has_value() && !maybe_valid_identifiers->is_empty()) {
  711. property_generator.appendln(" switch (identifier) {");
  712. auto& valid_identifiers = maybe_valid_identifiers.value();
  713. for (auto& identifier : valid_identifiers.values()) {
  714. auto identifier_generator = generator.fork();
  715. identifier_generator.set("identifier:titlecase", title_casify(identifier.as_string()));
  716. identifier_generator.appendln(" case ValueID::@identifier:titlecase@:");
  717. }
  718. property_generator.append(R"~~~(
  719. return true;
  720. default:
  721. break;
  722. }
  723. )~~~");
  724. }
  725. if (auto maybe_valid_types = object.get_array("valid-types"sv); maybe_valid_types.has_value() && !maybe_valid_types->is_empty()) {
  726. auto& valid_types = maybe_valid_types.value();
  727. for (auto& valid_type : valid_types.values()) {
  728. auto type_name = valid_type.as_string().split_view(' ').first();
  729. if (!type_name_is_enum(type_name))
  730. continue;
  731. auto type_generator = generator.fork();
  732. type_generator.set("type_name:snakecase", snake_casify(type_name));
  733. type_generator.append(R"~~~(
  734. if (value_id_to_@type_name:snakecase@(identifier).has_value())
  735. return true;
  736. )~~~");
  737. }
  738. }
  739. property_generator.append(R"~~~(
  740. return false;
  741. }
  742. )~~~");
  743. });
  744. generator.append(R"~~~(
  745. default:
  746. return false;
  747. }
  748. }
  749. Optional<ValueType> property_resolves_percentages_relative_to(PropertyID property_id)
  750. {
  751. switch (property_id) {
  752. )~~~");
  753. properties.for_each_member([&](auto& name, auto& value) {
  754. VERIFY(value.is_object());
  755. if (auto resolved_type = value.as_object().get_byte_string("percentages-resolve-to"sv); resolved_type.has_value()) {
  756. auto property_generator = generator.fork();
  757. property_generator.set("name:titlecase", title_casify(name));
  758. property_generator.set("resolved_type:titlecase", title_casify(resolved_type.value()));
  759. property_generator.append(R"~~~(
  760. case PropertyID::@name:titlecase@:
  761. return ValueType::@resolved_type:titlecase@;
  762. )~~~");
  763. }
  764. });
  765. generator.append(R"~~~(
  766. default:
  767. return {};
  768. }
  769. }
  770. size_t property_maximum_value_count(PropertyID property_id)
  771. {
  772. switch (property_id) {
  773. )~~~");
  774. properties.for_each_member([&](auto& name, auto& value) {
  775. VERIFY(value.is_object());
  776. if (value.as_object().has("max-values"sv)) {
  777. JsonValue max_values = value.as_object().get("max-values"sv).release_value();
  778. VERIFY(max_values.is_integer<size_t>());
  779. auto property_generator = generator.fork();
  780. property_generator.set("name:titlecase", title_casify(name));
  781. property_generator.set("max_values", MUST(String::formatted("{}", max_values.as_integer<size_t>())));
  782. property_generator.append(R"~~~(
  783. case PropertyID::@name:titlecase@:
  784. return @max_values@;
  785. )~~~");
  786. }
  787. });
  788. generator.append(R"~~~(
  789. default:
  790. return 1;
  791. }
  792. })~~~");
  793. generate_bounds_checking_function(properties, generator, "angle"sv, "Angle"sv, "Deg"sv);
  794. generate_bounds_checking_function(properties, generator, "flex"sv, "Flex"sv, "Fr"sv);
  795. generate_bounds_checking_function(properties, generator, "frequency"sv, "Frequency"sv, "Hertz"sv);
  796. generate_bounds_checking_function(properties, generator, "integer"sv, "i64"sv, {}, "value"sv);
  797. generate_bounds_checking_function(properties, generator, "length"sv, "Length"sv, {}, "value.raw_value()"sv);
  798. generate_bounds_checking_function(properties, generator, "number"sv, "double"sv, {}, "value"sv);
  799. generate_bounds_checking_function(properties, generator, "percentage"sv, "Percentage"sv, {}, "value.value()"sv);
  800. generate_bounds_checking_function(properties, generator, "resolution"sv, "Resolution"sv, "Dpi"sv);
  801. generate_bounds_checking_function(properties, generator, "time"sv, "Time"sv, "S"sv);
  802. generator.append(R"~~~(
  803. bool property_is_shorthand(PropertyID property_id)
  804. {
  805. switch (property_id) {
  806. )~~~");
  807. properties.for_each_member([&](auto& name, auto& value) {
  808. if (value.as_object().has("longhands"sv)) {
  809. auto property_generator = generator.fork();
  810. property_generator.set("name:titlecase", title_casify(name));
  811. property_generator.append(R"~~~(
  812. case PropertyID::@name:titlecase@:
  813. )~~~");
  814. }
  815. });
  816. generator.append(R"~~~(
  817. return true;
  818. default:
  819. return false;
  820. }
  821. }
  822. )~~~");
  823. generator.append(R"~~~(
  824. Vector<PropertyID> longhands_for_shorthand(PropertyID property_id)
  825. {
  826. switch (property_id) {
  827. )~~~");
  828. properties.for_each_member([&](auto& name, auto& value) {
  829. if (value.as_object().has("longhands"sv)) {
  830. auto longhands = value.as_object().get("longhands"sv);
  831. VERIFY(longhands.has_value() && longhands->is_array());
  832. auto longhand_values = longhands->as_array();
  833. auto property_generator = generator.fork();
  834. property_generator.set("name:titlecase", title_casify(name));
  835. StringBuilder builder;
  836. bool first = true;
  837. longhand_values.for_each([&](auto& longhand) {
  838. if (first)
  839. first = false;
  840. else
  841. builder.append(", "sv);
  842. builder.appendff("PropertyID::{}", title_casify(longhand.as_string()));
  843. return IterationDecision::Continue;
  844. });
  845. property_generator.set("longhands", builder.to_byte_string());
  846. property_generator.append(R"~~~(
  847. case PropertyID::@name:titlecase@:
  848. return { @longhands@ };
  849. )~~~");
  850. }
  851. });
  852. generator.append(R"~~~(
  853. default:
  854. return { };
  855. }
  856. }
  857. )~~~");
  858. generator.append(R"~~~(
  859. } // namespace Web::CSS
  860. )~~~");
  861. TRY(file.write_until_depleted(generator.as_string_view().bytes()));
  862. return {};
  863. }
  864. bool is_animatable_property(JsonObject& properties, StringView property_name)
  865. {
  866. auto property = properties.get_object(property_name);
  867. VERIFY(property.has_value());
  868. if (auto animation_type = property.value().get_byte_string("animation-type"sv); animation_type.has_value()) {
  869. return animation_type != "none";
  870. }
  871. if (!property.value().has("longhands"sv)) {
  872. dbgln("Property '{}' must specify either 'animation-type' or 'longhands'"sv, property_name);
  873. VERIFY_NOT_REACHED();
  874. }
  875. auto longhands = property.value().get_array("longhands"sv);
  876. VERIFY(longhands.has_value());
  877. for (auto const& subproperty_name : longhands->values()) {
  878. VERIFY(subproperty_name.is_string());
  879. if (is_animatable_property(properties, subproperty_name.as_string()))
  880. return true;
  881. }
  882. return false;
  883. }