| # This Source Code Form is subject to the terms of the Mozilla Public |
| # License, v. 2.0. If a copy of the MPL was not distributed with this |
| # file, You can obtain one at https://mozilla.org/MPL/2.0/. |
| |
| # fmt: off |
| |
| from __future__ import annotations |
| |
| import os |
| import sys |
| import json |
| import re |
| from typing import TYPE_CHECKING |
| from collections.abc import Iterator |
| |
| SCRIPT_PATH = os.path.abspath(os.path.dirname(__file__)) |
| SERVO_ROOT = os.path.abspath(os.path.join(SCRIPT_PATH, "..", "..", "..")) |
| |
| FILTER_PATTERN = re.compile("// skip-unless ([A-Z_]+)\n") |
| |
| if TYPE_CHECKING: |
| from configuration import Configuration |
| from WebIDL import Parser |
| |
| def main() -> None: |
| os.chdir(os.path.join(os.path.dirname(__file__))) |
| sys.path.insert(0, os.path.join(SERVO_ROOT, "third_party", "WebIDL")) |
| sys.path.insert(0, os.path.join(SERVO_ROOT, "third_party", "ply")) |
| |
| css_properties_json, out_dir = sys.argv[1:] |
| # Four dotdots: /path/to/target(4)/debug(3)/build(2)/style-*(1)/out |
| # Do not ascend above the target dir, because it may not be called target |
| # or even have a parent (see CARGO_TARGET_DIR). |
| doc_servo = os.path.join(out_dir, "..", "..", "..", "..", "doc") |
| webidls_dir = os.path.join(SCRIPT_PATH, "..", "webidls") |
| config_file = "Bindings.conf" |
| |
| import WebIDL |
| from configuration import Configuration |
| from codegen import CGBindingRoot, CGConcreteBindingRoot |
| |
| parser = WebIDL.Parser(make_dir(os.path.join(out_dir, "cache"))) |
| webidls = [name for name in os.listdir(webidls_dir) if name.endswith(".webidl")] |
| for webidl in webidls: |
| filename = os.path.join(webidls_dir, webidl) |
| with open(filename, "r", encoding="utf-8") as f: |
| contents = f.read() |
| filter_match = FILTER_PATTERN.search(contents) |
| if filter_match: |
| env_var = filter_match.group(1) |
| if not os.environ.get(env_var): |
| continue |
| |
| parser.parse(contents, filename) |
| |
| add_css_properties_attributes(css_properties_json, parser) |
| parser_results = parser.finish() |
| config = Configuration(config_file, parser_results) |
| make_dir(os.path.join(out_dir, "Bindings")) |
| make_dir(os.path.join(out_dir, "ConcreteBindings")) |
| |
| for name, filename in [ |
| ("PrototypeList", "PrototypeList.rs"), |
| ("RegisterBindings", "RegisterBindings.rs"), |
| ("Globals", "Globals.rs"), |
| ("InterfaceObjectMap", "InterfaceObjectMap.rs"), |
| ("InterfaceObjectMapData", "InterfaceObjectMapData.json"), |
| ("InterfaceTypes", "InterfaceTypes.rs"), |
| ("InheritTypes", "InheritTypes.rs"), |
| ("ConcreteInheritTypes", "ConcreteInheritTypes.rs"), |
| ("Bindings", "Bindings/mod.rs"), |
| ("Bindings", "ConcreteBindings/mod.rs"), |
| ("UnionTypes", "GenericUnionTypes.rs"), |
| ("ConcreteUnionTypes", "UnionTypes.rs"), |
| ("DomTypes", "DomTypes.rs"), |
| ("DomTypeHolder", "DomTypeHolder.rs"), |
| ]: |
| generate(config, name, os.path.join(out_dir, filename)) |
| make_dir(doc_servo) |
| generate(config, "SupportedDomApis", os.path.join(doc_servo, "apis.html")) |
| |
| for webidl in webidls: |
| filename = os.path.join(webidls_dir, webidl) |
| prefix = "Bindings/%sBinding" % webidl[:-len(".webidl")] |
| module = CGBindingRoot(config, prefix, filename).define() |
| if module: |
| with open(os.path.join(out_dir, prefix + ".rs"), "wb") as f: |
| f.write(module.encode("utf-8")) |
| prefix = "ConcreteBindings/%sBinding" % webidl[:-len(".webidl")] |
| module = CGConcreteBindingRoot(config, prefix, filename).define() |
| if module: |
| with open(os.path.join(out_dir, prefix + ".rs"), "wb") as f: |
| f.write(module.encode("utf-8")) |
| |
| |
| def make_dir(path: str)-> str: |
| if not os.path.exists(path): |
| os.makedirs(path) |
| return path |
| |
| |
| def generate(config: Configuration, name: str, filename: str) -> None: |
| from codegen import GlobalGenRoots |
| root = getattr(GlobalGenRoots, name)(config) |
| code = root.define() |
| with open(filename, "wb") as f: |
| f.write(code.encode("utf-8")) |
| |
| |
| def add_css_properties_attributes(css_properties_json: str, parser: Parser) -> None: |
| def map_preference_name(preference_name: str) -> str: |
| """Map between Stylo preference names and Servo preference names as the |
| `css-properties.json` file is generated by Stylo. This should be kept in sync with the |
| preference mapping done in `components/servo_config/prefs.rs`, which handles the runtime version of |
| these preferences.""" |
| MAPPING = [ |
| ["layout.unimplemented", "layout_unimplemented"], |
| ["layout.threads", "layout_threads"], |
| ["layout.flexbox.enabled", "layout_flexbox_enabled"], |
| ["layout.columns.enabled", "layout_columns_enabled"], |
| ["layout.grid.enabled", "layout_grid_enabled"], |
| ["layout.css.transition-behavior.enabled", "layout_css_transition_behavior_enabled"], |
| ["layout.writing-mode.enabled", "layout_writing_mode_enabled"], |
| ["layout.container-queries.enabled", "layout_container_queries_enabled"], |
| ["layout.variable_fonts.enabled", "layout_variable_fonts_enabled"] |
| ] |
| for mapping in MAPPING: |
| if mapping[0] == preference_name: |
| return mapping[1] |
| return preference_name |
| |
| css_properties = json.load(open(css_properties_json, "rb")) |
| idl = "partial interface CSSStyleDeclaration {\n%s\n};\n" % "\n".join( |
| " [%sCEReactions, SetterThrows] attribute [LegacyNullToEmptyString] DOMString %s;" % ( |
| (f'Pref="{map_preference_name(data["pref"])}", ' if data["pref"] else ""), |
| attribute_name |
| ) |
| for (kind, properties_list) in sorted(css_properties.items()) |
| for (property_name, data) in sorted(properties_list.items()) |
| for attribute_name in attribute_names(property_name) |
| ) |
| parser.parse(idl, "CSSStyleDeclaration_generated.webidl") |
| |
| |
| def attribute_names(property_name: str) -> Iterator[str]: |
| # https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-dashed-attribute |
| if property_name != "float": |
| yield property_name |
| else: |
| yield "_float" |
| |
| # https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-camel-cased-attribute |
| if "-" in property_name: |
| yield "".join(camel_case(property_name)) |
| |
| # https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-webkit-cased-attribute |
| if property_name.startswith("-webkit-"): |
| yield "".join(camel_case(property_name, True)) |
| |
| |
| # https://drafts.csswg.org/cssom/#css-property-to-idl-attribute |
| def camel_case(chars: str, webkit_prefixed: bool = False) -> Iterator[str]: |
| if webkit_prefixed: |
| chars = chars[1:] |
| next_is_uppercase = False |
| for c in chars: |
| if c == '-': |
| next_is_uppercase = True |
| elif next_is_uppercase: |
| next_is_uppercase = False |
| # Should be ASCII-uppercase, but all non-custom CSS property names are within ASCII |
| yield c.upper() |
| else: |
| yield c |
| |
| |
| if __name__ == "__main__": |
| main() |