GenerateCSSPropertyID.cpp 39 KB

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