[
  {
    "path": ".cargo/config.toml",
    "content": "[target.'cfg(target_env = \"gnu\")']\nrustflags = [\"-C\", \"link-args=-Wl,-z,nodelete\"]\n\n[target.aarch64-unknown-linux-gnu]\nlinker = \"aarch64-linux-gnu-gcc\"\n\n[target.armv7-unknown-linux-gnueabihf]\nlinker = \"arm-linux-gnueabihf-gcc\"\n\n[target.aarch64-unknown-linux-musl]\nlinker = \"aarch64-linux-musl-gcc\"\nrustflags = [\"-C\", \"target-feature=-crt-static\"]\n\n[target.wasm32-unknown-unknown]\nrustflags = [\n    \"-C\",\n    \"link-arg=--export-table\",\n    '--cfg',\n    'getrandom_backend=\"custom\"',\n]\n\n\n# Statically link Visual Studio redistributables on Windows builds\n[target.x86_64-pc-windows-msvc]\nrustflags = [\"-C\", \"target-feature=+crt-static\"]\n[target.aarch64-pc-windows-msvc]\nrustflags = [\"-C\", \"target-feature=+crt-static\"]\n"
  },
  {
    "path": ".github/workflows/release-crates.yml",
    "content": "name: release-crates\non:\n  workflow_dispatch:\n\njobs:\n  release-crates:\n    runs-on: ubuntu-latest\n    name: Release Rust crate\n    steps:\n      - uses: actions/checkout@v3\n      - uses: bahmutov/npm-install@v1.8.32\n      - name: Install Rust\n        uses: dtolnay/rust-toolchain@stable\n      - run: cargo login ${CRATES_IO_TOKEN}\n        env:\n          CRATES_IO_TOKEN: ${{ secrets.CRATES_IO_TOKEN }}\n      - run: |\n          cargo install cargo-workspaces\n          cargo workspaces publish --no-remove-dev-deps --from-git -y\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: release\non:\n  workflow_dispatch:\n\njobs:\n  build:\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          # Windows\n          - os: windows-latest\n            target: x86_64-pc-windows-msvc\n            binary: lightningcss.exe\n          - os: windows-latest\n            target: aarch64-pc-windows-msvc\n            binary: lightningcss.exe\n          # Mac OS\n          - os: macos-latest\n            target: x86_64-apple-darwin\n            strip: strip -x # Must use -x on macOS. This produces larger results on linux.\n            binary: lightningcss\n\n    name: build-${{ matrix.target }}\n    runs-on: ${{ matrix.os }}\n    steps:\n      - uses: actions/checkout@v3\n      - name: Install Node.JS\n        uses: actions/setup-node@v3\n        with:\n          node-version: 18\n      - name: Install Rust\n        uses: dtolnay/rust-toolchain@stable\n\n      - name: Setup rust target\n        run: rustup target add ${{ matrix.target }}\n\n      - uses: bahmutov/npm-install@v1.8.32\n      - name: Build release\n        run: yarn build-release\n        env:\n          RUST_TARGET: ${{ matrix.target }}\n      - name: Build CLI\n        run: |\n          cargo build --release --features cli --target ${{ matrix.target }}\n          node -e \"require('fs').renameSync('target/${{ matrix.target }}/release/${{ matrix.binary }}', '${{ matrix.binary }}')\"\n      - name: Strip debug symbols # https://github.com/rust-lang/rust/issues/46034\n        if: ${{ matrix.strip }}\n        run: ${{ matrix.strip }} *.node ${{ matrix.binary }}\n      - name: Upload artifacts\n        uses: actions/upload-artifact@v4\n        with:\n          name: bindings-${{ matrix.target }}\n          path: |\n            *.node\n            ${{ matrix.binary }}\n\n  build-apple-silicon:\n    name: build-apple-silicon\n    runs-on: macos-latest\n    steps:\n      - uses: actions/checkout@v3\n      - name: Install Node.JS\n        uses: actions/setup-node@v3\n        with:\n          node-version: 18\n      - name: Install Rust\n        uses: dtolnay/rust-toolchain@stable\n\n      - name: Setup rust target\n        run: rustup target add aarch64-apple-darwin\n\n      - uses: bahmutov/npm-install@v1.8.32\n      - name: Build release\n        run: yarn build-release\n        env:\n          RUST_TARGET: aarch64-apple-darwin\n          JEMALLOC_SYS_WITH_LG_PAGE: 14\n      - name: Build CLI\n        run: |\n          export CC=$(xcrun -f clang);\n          export CXX=$(xcrun -f clang++);\n          SYSROOT=$(xcrun --sdk macosx --show-sdk-path);\n          export CFLAGS=\"-isysroot $SYSROOT -isystem $SYSROOT\";\n          export MACOSX_DEPLOYMENT_TARGET=\"10.9\";\n          cargo build --release --features cli --target aarch64-apple-darwin\n          mv target/aarch64-apple-darwin/release/lightningcss lightningcss\n        env:\n          JEMALLOC_SYS_WITH_LG_PAGE: 14\n      - name: Strip debug symbols # https://github.com/rust-lang/rust/issues/46034\n        run: strip -x *.node lightningcss\n      - name: Upload artifacts\n        uses: actions/upload-artifact@v4\n        with:\n          name: bindings-aarch64-apple-darwin\n          path: |\n            *.node\n            lightningcss\n\n  build-linux:\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          - target: x86_64-unknown-linux-gnu\n            strip: strip\n            image: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian\n            setup: npm install --global yarn@1\n          - target: aarch64-unknown-linux-gnu\n            strip: llvm-strip\n            image: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian-aarch64\n          - target: aarch64-linux-android\n            strip: llvm-strip\n            image: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian-aarch64\n          - target: armv7-unknown-linux-gnueabihf\n            strip: llvm-strip\n            image: ghcr.io/napi-rs/napi-rs/nodejs-rust@sha256:c22284b2d79092d3e885f64ede00f6afdeb2ccef7e2b6e78be52e7909091cd57\n          - target: aarch64-unknown-linux-musl\n            image: ghcr.io/napi-rs/napi-rs/nodejs-rust@sha256:78c9ab1f117f8c535b93c4b91a2f19063dda6e4dba48a6187df49810625992c1\n            strip: aarch64-linux-musl-strip\n          - target: x86_64-unknown-linux-musl\n            image: ghcr.io/napi-rs/napi-rs/nodejs-rust@sha256:78c9ab1f117f8c535b93c4b91a2f19063dda6e4dba48a6187df49810625992c1\n            strip: strip\n\n    name: build-${{ matrix.target }}\n    runs-on: ubuntu-latest\n    container:\n      image: ${{ matrix.image }}\n\n    steps:\n      - uses: actions/checkout@v3\n      - name: Install Node.JS\n        uses: actions/setup-node@v3\n        with:\n          node-version: 18\n      - name: Install Rust\n        uses: dtolnay/rust-toolchain@stable\n\n      - name: Setup Android NDK\n        if: ${{ matrix.target == 'aarch64-linux-android' }}\n        run: |\n          sudo apt update && sudo apt install unzip -y\n          cd /tmp\n          wget -q https://dl.google.com/android/repository/android-ndk-r28-linux.zip -O /tmp/ndk.zip\n          unzip ndk.zip\n\n      - name: Setup cross compile toolchain\n        if: ${{ matrix.setup }}\n        run: ${{ matrix.setup }}\n\n      - name: Setup rust target\n        run: rustup target add ${{ matrix.target }}\n\n      - uses: bahmutov/npm-install@v1.8.32\n      - name: Build release\n        run: yarn build-release\n        env:\n          ANDROID_NDK_LATEST_HOME: /tmp/android-ndk-r28\n          RUST_TARGET: ${{ matrix.target }}\n      - name: Build CLI\n        env:\n          ANDROID_NDK_LATEST_HOME: /tmp/android-ndk-r28\n        run: |\n          yarn napi build --bin lightningcss --release --features cli --target ${{ matrix.target }}\n          mv target/${{ matrix.target }}/release/lightningcss lightningcss\n      - name: Strip debug symbols # https://github.com/rust-lang/rust/issues/46034\n        if: ${{ matrix.strip }}\n        run: ${{ matrix.strip }} *.node lightningcss\n      - name: Upload artifacts\n        uses: actions/upload-artifact@v4\n        with:\n          name: bindings-${{ matrix.target }}\n          path: |\n            *.node\n            lightningcss\n\n  build-freebsd:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n      - name: Build FreeBSD\n        uses: cross-platform-actions/action@v0.25.0\n        env:\n          DEBUG: napi:*\n          RUSTUP_HOME: /usr/local/rustup\n          CARGO_HOME: /usr/local/cargo\n          RUSTUP_IO_THREADS: 1\n        with:\n          operating_system: freebsd\n          version: '14.0'\n          memory: 13G\n          cpu_count: 3\n          environment_variables: 'DEBUG RUSTUP_IO_THREADS'\n          shell: bash\n          run: |\n            sudo pkg install -y -f curl node libnghttp2 npm yarn\n            curl https://sh.rustup.rs -sSf --output rustup.sh\n            sh rustup.sh -y --profile minimal --default-toolchain beta\n            source \"$HOME/.cargo/env\"\n            echo \"~~~~ rustc --version ~~~~\"\n            rustc --version\n            echo \"~~~~ node -v ~~~~\"\n            node -v\n            echo \"~~~~ yarn --version ~~~~\"\n            yarn --version\n            yarn install || true\n            yarn build-release\n            strip -x *.node\n            cargo build --release --features cli\n            mv target/release/lightningcss lightningcss\n            node -e \"require('.')\"\n            ./lightningcss --help\n            rm -rf node_modules\n            rm -rf target\n            rm -rf .yarn/cache\n\n      - name: Upload artifacts\n        uses: actions/upload-artifact@v4\n        with:\n          name: bindings-x86_64-unknown-freebsd\n          path: |\n            *.node\n            lightningcss\n\n  build-wasm:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n      - name: Install Node.JS\n        uses: actions/setup-node@v3\n        with:\n          node-version: 18\n      - uses: bahmutov/npm-install@v1.8.32\n      - name: Install Rust\n        uses: dtolnay/rust-toolchain@stable\n        with:\n          targets: wasm32-unknown-unknown\n      - name: Setup rust target\n        run: rustup target add wasm32-unknown-unknown\n      - name: Install wasm-opt\n        run: |\n          curl -L -O https://github.com/WebAssembly/binaryen/releases/download/version_111/binaryen-version_111-x86_64-linux.tar.gz\n          tar -xf binaryen-version_111-x86_64-linux.tar.gz\n      - name: Build wasm\n        run: |\n          export PATH=\"$PATH:./binaryen-version_111/bin\"\n          yarn wasm:build-release\n      - name: Upload artifacts\n        uses: actions/upload-artifact@v4\n        with:\n          name: wasm\n          path: wasm/lightningcss_node.wasm\n\n  release:\n    runs-on: ubuntu-latest\n    name: Build and release\n    needs:\n      - build\n      - build-linux\n      - build-apple-silicon\n      - build-freebsd\n      - build-wasm\n    steps:\n      - uses: actions/checkout@v3\n      - uses: bahmutov/npm-install@v1.8.32\n      - name: Download artifacts\n        uses: actions/download-artifact@v4\n        with:\n          path: artifacts\n      - name: Show artifacts\n        run: ls -R artifacts\n      - name: Build npm packages\n        run: |\n          node scripts/build-npm.js\n          cp artifacts/wasm/* wasm/.\n          node scripts/build-wasm.js\n      - run: echo //registry.npmjs.org/:_authToken=${NPM_TOKEN} > ~/.npmrc\n        env:\n          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}\n      - name: Publish to npm\n        run: |\n          for pkg in npm/*; do\n            echo \"Publishing $pkg...\"\n            cd $pkg;\n            npm publish;\n            cd ../..;\n          done\n          cd wasm\n          echo \"Publishing lightningcss-wasm...\";\n          npm publish\n          cd ..\n          cd cli\n          echo \"Publishing lightningcss-cli...\";\n          npm publish\n          cd ..\n          echo \"Publishing lightningcss...\";\n          npm publish\n\n  release-crates:\n    runs-on: ubuntu-latest\n    name: Release Rust crate\n    steps:\n      - uses: actions/checkout@v3\n      - uses: bahmutov/npm-install@v1.8.32\n      - name: Install Rust\n        uses: dtolnay/rust-toolchain@stable\n      - run: cargo login ${CRATES_IO_TOKEN}\n        env:\n          CRATES_IO_TOKEN: ${{ secrets.CRATES_IO_TOKEN }}\n      - run: |\n          cargo install cargo-workspaces\n          cargo workspaces publish --no-remove-dev-deps --from-git -y\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: test\n\non:\n  push:\n    branches: [master]\n  pull_request:\n    branches: [master]\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    env:\n      CARGO_TERM_COLOR: always\n      RUST_BACKTRACE: full\n      RUSTFLAGS: -D warnings\n    steps:\n      - uses: actions/checkout@v3\n      - uses: dtolnay/rust-toolchain@stable\n      - uses: Swatinem/rust-cache@v2\n      - run: cargo fmt\n      - run: cargo test --all-features\n\n  test-js:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n      - uses: actions/setup-node@v3\n        with:\n          node-version: 18\n      - uses: bahmutov/npm-install@v1.8.32\n      - uses: dtolnay/rust-toolchain@stable\n      - uses: Swatinem/rust-cache@v2\n      - run: yarn build\n      - run: yarn test\n      - run: yarn tsc\n\n  test-wasm:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n      - uses: actions/setup-node@v3\n        with:\n          node-version: 18\n      - uses: bahmutov/npm-install@v1.8.32\n      - uses: dtolnay/rust-toolchain@stable\n        with:\n          targets: wasm32-unknown-unknown\n      - name: Setup rust target\n        run: rustup target add wasm32-unknown-unknown\n      - uses: Swatinem/rust-cache@v2\n      - name: Install wasm-opt\n        run: |\n          curl -L -O https://github.com/WebAssembly/binaryen/releases/download/version_111/binaryen-version_111-x86_64-linux.tar.gz\n          tar -xf binaryen-version_111-x86_64-linux.tar.gz\n      - name: Build wasm\n        run: |\n          export PATH=\"$PATH:./binaryen-version_111/bin\"\n          yarn wasm:build-release\n      - run: TEST_WASM=node yarn test\n      - run: TEST_WASM=browser yarn test\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\n*.node\nnode_modules/\ntarget/\npkg/\ndist/\n.parcel-cache\nnode/*.flow\nartifacts\nnpm\nnode/ast.json\n"
  },
  {
    "path": ".prettierrc",
    "content": "{\n  \"bracketSpacing\": false,\n  \"endOfLine\": \"lf\",\n  \"singleQuote\": true,\n  \"trailingComma\": \"all\"\n}\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing\n\nWelcome, we really appreciate if you're considering to contribute, the joint effort of our contributors make projects like this possible!\n\nThe goal of this document is to provide guidance on how you can get involved.\n\n## Getting started with bug fixing\n\nIn order to make it easier to get familiar with the codebase we labeled simpler issues using [Good First Issue](https://github.com/parcel-bundler/lightningcss/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22) and [Help Wanted](https://github.com/parcel-bundler/lightningcss/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22+label%3A%22help+wanted%22).\n\nBefore starting make sure you have the following requirements installed: [git](https://git-scm.com), [Node](https://nodejs.org), [Yarn](https://yarnpkg.com) and [Rust](https://www.rust-lang.org/tools/install).\n\nThe process starts by [forking](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo) the project and setup a new branch to work in. It's important that the changes are made in separated branches in order to ensure a pull request only includes the commits related to a bug or feature.\n\nClone the forked repository locally and install the dependencies:\n\n```sh\ngit clone https://github.com/USERNAME/lightningcss.git\ncd lightningcss\nyarn install\n```\n\n## Testing\n\nIn order to test, you first need to build the core package:\n\n```sh\nyarn build\n```\n\nThen you can run the tests:\n\n```sh\nyarn test # js tests\ncargo test # rust tests\n```\n\n## Building\n\nThere are different build targets available, with \"release\" being a production build:\n\n```sh\nyarn build\nyarn build-release\n\nyarn wasm:build\nyarn wasm:build-release\n```\n\nNote: If you plan to build the WASM target, ensure that you have the required toolchain and binaries installed.\n\n```sh\nrustup target add wasm32-unknown-unknown\ncargo install wasm-opt\n```\n\n## Website\n\nThe website is built using [Parcel](https://parceljs.org). You can start the development server by running:\n\n```sh\nyarn website:start\n```\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[workspace]\nmembers = [\n  \"node\",\n  \"napi\",\n  \"selectors\",\n  \"c\",\n  \"derive\",\n  \"static-self\",\n  \"static-self-derive\",\n]\n\n[package]\nauthors = [\"Devon Govett <devongovett@gmail.com>\"]\nname = \"lightningcss\"\nversion = \"1.0.0-alpha.71\"\ndescription = \"A CSS parser, transformer, and minifier\"\nlicense = \"MPL-2.0\"\nedition = \"2021\"\nkeywords = [\"CSS\", \"minifier\", \"Parcel\"]\nrepository = \"https://github.com/parcel-bundler/lightningcss\"\n\n[package.metadata.docs.rs]\nall-features = true\nrustdoc-args = [\"--cfg\", \"docsrs\"]\n\n[[bin]]\nname = \"lightningcss\"\npath = \"src/main.rs\"\nrequired-features = [\"cli\"]\n\n[lib]\nname = \"lightningcss\"\npath = \"src/lib.rs\"\ncrate-type = [\"rlib\"]\n\n[features]\ndefault = [\"bundler\", \"nodejs\", \"sourcemap\"]\nbrowserslist = [\"browserslist-rs\"]\nbundler = [\"dashmap\", \"sourcemap\", \"rayon\"]\ncli = [\"atty\", \"clap\", \"serde_json\", \"browserslist\", \"jemallocator\"]\njsonschema = [\"schemars\", \"serde\", \"parcel_selectors/jsonschema\"]\nnodejs = [\"dep:serde\", \"dep:serde-content\"]\nserde = [\n  \"dep:serde\",\n  \"dep:serde-content\",\n  \"bitflags/serde\",\n  \"smallvec/serde\",\n  \"cssparser/serde\",\n  \"parcel_selectors/serde\",\n  \"into_owned\",\n]\nsourcemap = [\"parcel_sourcemap\"]\nvisitor = []\ninto_owned = [\n  \"static-self\",\n  \"static-self/smallvec\",\n  \"static-self/indexmap\",\n  \"parcel_selectors/into_owned\",\n]\nsubstitute_variables = [\"visitor\", \"into_owned\"]\n\n[dependencies]\nserde = { version = \"1.0.228\", features = [\"derive\"], optional = true }\nserde-content = { version = \"0.1.2\", features = [\"serde\"], optional = true }\ncssparser = \"0.33.0\"\ncssparser-color = \"0.1.0\"\nparcel_selectors = { version = \"0.28.2\", path = \"./selectors\" }\nitertools = \"0.10.1\"\nsmallvec = { version = \"1.7.0\", features = [\"union\"] }\nbitflags = \"2.2.1\"\nparcel_sourcemap = { version = \"2.1.1\", features = [\"json\"], optional = true }\ndata-encoding = \"2.3.2\"\nlazy_static = \"1.4.0\"\nconst-str = \"0.3.1\"\npathdiff = \"0.2.1\"\nahash = \"0.8.7\"\npastey = \"0.1.0\"\nindexmap = { version = \"2.2.6\", features = [\"serde\"] }\n# CLI deps\natty = { version = \"0.2\", optional = true }\nclap = { version = \"3.0.6\", features = [\"derive\"], optional = true }\nbrowserslist-rs = { version = \"0.19.0\", optional = true }\nrayon = { version = \"1.5.1\", optional = true }\ndashmap = { version = \"5.0.0\", optional = true }\nserde_json = { version = \"1.0.78\", optional = true }\nlightningcss-derive = { version = \"=1.0.0-alpha.43\", path = \"./derive\" }\nschemars = { version = \"0.8.19\", features = [\"smallvec\", \"indexmap2\"], optional = true }\nstatic-self = { version = \"0.1.2\", path = \"static-self\", optional = true }\n\n[target.'cfg(target_os = \"macos\")'.dependencies]\njemallocator = { version = \"0.3.2\", features = [\n  \"disable_initial_exec_tls\",\n], optional = true }\n\n[target.'cfg(target_arch = \"wasm32\")'.dependencies]\ngetrandom = { version = \"0.3\", default-features = false }\n\n[dev-dependencies]\nindoc = \"1.0.3\"\nassert_cmd = \"2.0\"\nassert_fs = \"1.0\"\npredicates = \"2.1\"\nserde_json = \"1\"\npretty_assertions = \"1.4.0\"\n\n[[test]]\nname = \"cli_integration_tests\"\npath = \"tests/cli_integration_tests.rs\"\nrequired-features = [\"cli\"]\n\n[[example]]\nname = \"custom_at_rule\"\nrequired-features = [\"visitor\"]\n\n[[example]]\nname = \"serialize\"\nrequired-features = [\"serde\"]\n\n[profile.release]\nlto = true\ncodegen-units = 1\npanic = 'abort'\n"
  },
  {
    "path": "LICENSE",
    "content": " Mozilla Public License Version 2.0\n==================================\n\n1. Definitions\n--------------\n\n1.1. \"Contributor\"\nmeans each individual or legal entity that creates, contributes to\nthe creation of, or owns Covered Software.\n\n1.2. \"Contributor Version\"\nmeans the combination of the Contributions of others (if any) used\nby a Contributor and that particular Contributor's Contribution.\n\n1.3. \"Contribution\"\nmeans Covered Software of a particular Contributor.\n\n1.4. \"Covered Software\"\nmeans Source Code Form to which the initial Contributor has attached\nthe notice in Exhibit A, the Executable Form of such Source Code\nForm, and Modifications of such Source Code Form, in each case\nincluding portions thereof.\n\n1.5. \"Incompatible With Secondary Licenses\"\nmeans\n\n(a) that the initial Contributor has attached the notice described\nin Exhibit B to the Covered Software; or\n\n(b) that the Covered Software was made available under the terms of\nversion 1.1 or earlier of the License, but not also under the\nterms of a Secondary License.\n\n1.6. \"Executable Form\"\nmeans any form of the work other than Source Code Form.\n\n1.7. \"Larger Work\"\nmeans a work that combines Covered Software with other material, in\na separate file or files, that is not Covered Software.\n\n1.8. \"License\"\nmeans this document.\n\n1.9. \"Licensable\"\nmeans having the right to grant, to the maximum extent possible,\nwhether at the time of the initial grant or subsequently, any and\nall of the rights conveyed by this License.\n\n1.10. \"Modifications\"\nmeans any of the following:\n\n(a) any file in Source Code Form that results from an addition to,\ndeletion from, or modification of the contents of Covered\nSoftware; or\n\n(b) any new file in Source Code Form that contains any Covered\nSoftware.\n\n1.11. \"Patent Claims\" of a Contributor\nmeans any patent claim(s), including without limitation, method,\nprocess, and apparatus claims, in any patent Licensable by such\nContributor that would be infringed, but for the grant of the\nLicense, by the making, using, selling, offering for sale, having\nmade, import, or transfer of either its Contributions or its\nContributor Version.\n\n1.12. \"Secondary License\"\nmeans either the GNU General Public License, Version 2.0, the GNU\nLesser General Public License, Version 2.1, the GNU Affero General\nPublic License, Version 3.0, or any later versions of those\nlicenses.\n\n1.13. \"Source Code Form\"\nmeans the form of the work preferred for making modifications.\n\n1.14. \"You\" (or \"Your\")\nmeans an individual or a legal entity exercising rights under this\nLicense. For legal entities, \"You\" includes any entity that\ncontrols, is controlled by, or is under common control with You. For\npurposes of this definition, \"control\" means (a) the power, direct\nor indirect, to cause the direction or management of such entity,\nwhether by contract or otherwise, or (b) ownership of more than\nfifty percent (50%) of the outstanding shares or beneficial\nownership of such entity.\n\n2. License Grants and Conditions\n--------------------------------\n\n2.1. Grants\n\nEach Contributor hereby grants You a world-wide, royalty-free,\nnon-exclusive license:\n\n(a) under intellectual property rights (other than patent or trademark)\nLicensable by such Contributor to use, reproduce, make available,\nmodify, display, perform, distribute, and otherwise exploit its\nContributions, either on an unmodified basis, with Modifications, or\nas part of a Larger Work; and\n\n(b) under Patent Claims of such Contributor to make, use, sell, offer\nfor sale, have made, import, and otherwise transfer either its\nContributions or its Contributor Version.\n\n2.2. Effective Date\n\nThe licenses granted in Section 2.1 with respect to any Contribution\nbecome effective for each Contribution on the date the Contributor first\ndistributes such Contribution.\n\n2.3. Limitations on Grant Scope\n\nThe licenses granted in this Section 2 are the only rights granted under\nthis License. No additional rights or licenses will be implied from the\ndistribution or licensing of Covered Software under this License.\nNotwithstanding Section 2.1(b) above, no patent license is granted by a\nContributor:\n\n(a) for any code that a Contributor has removed from Covered Software;\nor\n\n(b) for infringements caused by: (i) Your and any other third party's\nmodifications of Covered Software, or (ii) the combination of its\nContributions with other software (except as part of its Contributor\nVersion); or\n\n(c) under Patent Claims infringed by Covered Software in the absence of\nits Contributions.\n\nThis License does not grant any rights in the trademarks, service marks,\nor logos of any Contributor (except as may be necessary to comply with\nthe notice requirements in Section 3.4).\n\n2.4. Subsequent Licenses\n\nNo Contributor makes additional grants as a result of Your choice to\ndistribute the Covered Software under a subsequent version of this\nLicense (see Section 10.2) or under the terms of a Secondary License (if\npermitted under the terms of Section 3.3).\n\n2.5. Representation\n\nEach Contributor represents that the Contributor believes its\nContributions are its original creation(s) or it has sufficient rights\nto grant the rights to its Contributions conveyed by this License.\n\n2.6. Fair Use\n\nThis License is not intended to limit any rights You have under\napplicable copyright doctrines of fair use, fair dealing, or other\nequivalents.\n\n2.7. Conditions\n\nSections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted\nin Section 2.1.\n\n3. Responsibilities\n-------------------\n\n3.1. Distribution of Source Form\n\nAll distribution of Covered Software in Source Code Form, including any\nModifications that You create or to which You contribute, must be under\nthe terms of this License. You must inform recipients that the Source\nCode Form of the Covered Software is governed by the terms of this\nLicense, and how they can obtain a copy of this License. You may not\nattempt to alter or restrict the recipients' rights in the Source Code\nForm.\n\n3.2. Distribution of Executable Form\n\nIf You distribute Covered Software in Executable Form then:\n\n(a) such Covered Software must also be made available in Source Code\nForm, as described in Section 3.1, and You must inform recipients of\nthe Executable Form how they can obtain a copy of such Source Code\nForm by reasonable means in a timely manner, at a charge no more\nthan the cost of distribution to the recipient; and\n\n(b) You may distribute such Executable Form under the terms of this\nLicense, or sublicense it under different terms, provided that the\nlicense for the Executable Form does not attempt to limit or alter\nthe recipients' rights in the Source Code Form under this License.\n\n3.3. Distribution of a Larger Work\n\nYou may create and distribute a Larger Work under terms of Your choice,\nprovided that You also comply with the requirements of this License for\nthe Covered Software. If the Larger Work is a combination of Covered\nSoftware with a work governed by one or more Secondary Licenses, and the\nCovered Software is not Incompatible With Secondary Licenses, this\nLicense permits You to additionally distribute such Covered Software\nunder the terms of such Secondary License(s), so that the recipient of\nthe Larger Work may, at their option, further distribute the Covered\nSoftware under the terms of either this License or such Secondary\nLicense(s).\n\n3.4. Notices\n\nYou may not remove or alter the substance of any license notices\n(including copyright notices, patent notices, disclaimers of warranty,\nor limitations of liability) contained within the Source Code Form of\nthe Covered Software, except that You may alter any license notices to\nthe extent required to remedy known factual inaccuracies.\n\n3.5. Application of Additional Terms\n\nYou may choose to offer, and to charge a fee for, warranty, support,\nindemnity or liability obligations to one or more recipients of Covered\nSoftware. However, You may do so only on Your own behalf, and not on\nbehalf of any Contributor. You must make it absolutely clear that any\nsuch warranty, support, indemnity, or liability obligation is offered by\nYou alone, and You hereby agree to indemnify every Contributor for any\nliability incurred by such Contributor as a result of warranty, support,\nindemnity or liability terms You offer. You may include additional\ndisclaimers of warranty and limitations of liability specific to any\njurisdiction.\n\n4. Inability to Comply Due to Statute or Regulation\n---------------------------------------------------\n\nIf it is impossible for You to comply with any of the terms of this\nLicense with respect to some or all of the Covered Software due to\nstatute, judicial order, or regulation then You must: (a) comply with\nthe terms of this License to the maximum extent possible; and (b)\ndescribe the limitations and the code they affect. Such description must\nbe placed in a text file included with all distributions of the Covered\nSoftware under this License. Except to the extent prohibited by statute\nor regulation, such description must be sufficiently detailed for a\nrecipient of ordinary skill to be able to understand it.\n\n5. Termination\n--------------\n\n5.1. The rights granted under this License will terminate automatically\nif You fail to comply with any of its terms. However, if You become\ncompliant, then the rights granted under this License from a particular\nContributor are reinstated (a) provisionally, unless and until such\nContributor explicitly and finally terminates Your grants, and (b) on an\nongoing basis, if such Contributor fails to notify You of the\nnon-compliance by some reasonable means prior to 60 days after You have\ncome back into compliance. Moreover, Your grants from a particular\nContributor are reinstated on an ongoing basis if such Contributor\nnotifies You of the non-compliance by some reasonable means, this is the\nfirst time You have received notice of non-compliance with this License\nfrom such Contributor, and You become compliant prior to 30 days after\nYour receipt of the notice.\n\n5.2. If You initiate litigation against any entity by asserting a patent\ninfringement claim (excluding declaratory judgment actions,\ncounter-claims, and cross-claims) alleging that a Contributor Version\ndirectly or indirectly infringes any patent, then the rights granted to\nYou by any and all Contributors for the Covered Software under Section\n2.1 of this License shall terminate.\n\n5.3. In the event of termination under Sections 5.1 or 5.2 above, all\nend user license agreements (excluding distributors and resellers) which\nhave been validly granted by You or Your distributors under this License\nprior to termination shall survive termination.\n\n************************************************************************\n* *\n* 6. Disclaimer of Warranty *\n* ------------------------- *\n* *\n* Covered Software is provided under this License on an \"as is\" *\n* basis, without warranty of any kind, either expressed, implied, or *\n* statutory, including, without limitation, warranties that the *\n* Covered Software is free of defects, merchantable, fit for a *\n* particular purpose or non-infringing. The entire risk as to the *\n* quality and performance of the Covered Software is with You. *\n* Should any Covered Software prove defective in any respect, You *\n* (not any Contributor) assume the cost of any necessary servicing, *\n* repair, or correction. This disclaimer of warranty constitutes an *\n* essential part of this License. No use of any Covered Software is *\n* authorized under this License except under this disclaimer. *\n* *\n************************************************************************\n\n************************************************************************\n* *\n* 7. Limitation of Liability *\n* -------------------------- *\n* *\n* Under no circumstances and under no legal theory, whether tort *\n* (including negligence), contract, or otherwise, shall any *\n* Contributor, or anyone who distributes Covered Software as *\n* permitted above, be liable to You for any direct, indirect, *\n* special, incidental, or consequential damages of any character *\n* including, without limitation, damages for lost profits, loss of *\n* goodwill, work stoppage, computer failure or malfunction, or any *\n* and all other commercial damages or losses, even if such party *\n* shall have been informed of the possibility of such damages. This *\n* limitation of liability shall not apply to liability for death or *\n* personal injury resulting from such party's negligence to the *\n* extent applicable law prohibits such limitation. Some *\n* jurisdictions do not allow the exclusion or limitation of *\n* incidental or consequential damages, so this exclusion and *\n* limitation may not apply to You. *\n* *\n************************************************************************\n\n8. Litigation\n-------------\n\nAny litigation relating to this License may be brought only in the\ncourts of a jurisdiction where the defendant maintains its principal\nplace of business and such litigation shall be governed by laws of that\njurisdiction, without reference to its conflict-of-law provisions.\nNothing in this Section shall prevent a party's ability to bring\ncross-claims or counter-claims.\n\n9. Miscellaneous\n----------------\n\nThis License represents the complete agreement concerning the subject\nmatter hereof. If any provision of this License is held to be\nunenforceable, such provision shall be reformed only to the extent\nnecessary to make it enforceable. Any law or regulation which provides\nthat the language of a contract shall be construed against the drafter\nshall not be used to construe this License against a Contributor.\n\n10. Versions of the License\n---------------------------\n\n10.1. New Versions\n\nMozilla Foundation is the license steward. Except as provided in Section\n10.3, no one other than the license steward has the right to modify or\npublish new versions of this License. Each version will be given a\ndistinguishing version number.\n\n10.2. Effect of New Versions\n\nYou may distribute the Covered Software under the terms of the version\nof the License under which You originally received the Covered Software,\nor under the terms of any subsequent version published by the license\nsteward.\n\n10.3. Modified Versions\n\nIf you create software not governed by this License, and you want to\ncreate a new license for such software, you may create and use a\nmodified version of this License if you rename the license and remove\nany references to the name of the license steward (except to note that\nsuch modified license differs from this License).\n\n10.4. Distributing Source Code Form that is Incompatible With Secondary\nLicenses\n\nIf You choose to distribute Source Code Form that is Incompatible With\nSecondary Licenses under the terms of this version of the License, the\nnotice described in Exhibit B of this License must be attached.\n\nExhibit A - Source Code Form License Notice\n-------------------------------------------\n\nThis Source Code Form is subject to the terms of the Mozilla Public\nLicense, v. 2.0. If a copy of the MPL was not distributed with this\nfile, You can obtain one at https://mozilla.org/MPL/2.0/.\n\nIf it is not possible or desirable to put the notice in a particular\nfile, then You may include the notice in a location (such as a LICENSE\nfile in a relevant directory) where a recipient would be likely to look\nfor such a notice.\n\nYou may add additional accurate notices of copyright ownership.\n\nExhibit B - \"Incompatible With Secondary Licenses\" Notice\n---------------------------------------------------------\n\nThis Source Code Form is \"Incompatible With Secondary Licenses\", as\ndefined by the Mozilla Public License, v. 2.0.\n"
  },
  {
    "path": "README.md",
    "content": "# ⚡️ Lightning CSS\n\nAn extremely fast CSS parser, transformer, and minifier written in Rust. Use it with [Parcel](https://parceljs.org), as a standalone library or CLI, or via a plugin with any other tool.\n\n<img width=\"680\" alt=\"performance and build size charts\" src=\"https://user-images.githubusercontent.com/19409/189022599-28246659-f94a-46a4-9de0-b6d17adb0e22.png#gh-light-mode-only\">\n<img width=\"680\" alt=\"performance and build size charts\" src=\"https://user-images.githubusercontent.com/19409/189022693-6956b044-422b-4f56-9628-d59c6f791095.png#gh-dark-mode-only\">\n\n## Features\n\n- **Extremely fast** – Parsing and minifying large files is completed in milliseconds, often with significantly smaller output than other tools. See [benchmarks](#benchmarks) below.\n- **Typed property values** – many other CSS parsers treat property values as an untyped series of tokens. This means that each transformer that wants to do something with these values must interpret them itself, leading to duplicate work and inconsistencies. Lightning CSS parses all values using the grammar from the CSS specification, and exposes a specific value type for each property.\n- **Browser-grade parser** – Lightning CSS is built on the [cssparser](https://github.com/servo/rust-cssparser) and [selectors](https://github.com/servo/stylo/tree/main/selectors) crates created by Mozilla and used by Firefox and Servo. These provide a solid general purpose CSS-parsing foundation on top of which Lightning CSS implements support for all specific CSS rules and properties.\n- **Minification** – One of the main purposes of Lightning CSS is to minify CSS to make it smaller. This includes many optimizations including:\n  - Combining longhand properties into shorthands where possible.\n  - Merging adjacent rules with the same selectors or declarations when it is safe to do so.\n  - Combining CSS transforms into a single matrix or vice versa when smaller.\n  - Removing vendor prefixes that are not needed, based on the provided browser targets.\n  - Reducing `calc()` expressions where possible.\n  - Converting colors to shorter hex notation where possible.\n  - Minifying gradients.\n  - Minifying CSS grid templates.\n  - Normalizing property value order.\n  - Removing default property sub-values which will be inferred by browsers.\n  - Many micro-optimizations, e.g. converting to shorter units, removing unnecessary quotation marks, etc.\n- **Vendor prefixing** – Lightning CSS accepts a list of browser targets, and automatically adds (and removes) vendor prefixes.\n- **Browserslist configuration** – Lightning CSS supports opt-in browserslist configuration discovery to resolve browser targets and integrate with your existing tools and config setup.\n- **Syntax lowering** – Lightning CSS parses modern CSS syntax, and generates more compatible output where needed, based on browser targets.\n  - CSS Nesting\n  - Custom media queries (draft spec)\n  - Logical properties\n  * [Color Level 5](https://drafts.csswg.org/css-color-5/)\n    - `color-mix()` function\n    - Relative color syntax, e.g. `lab(from purple calc(l * .8) a b)`\n  - [Color Level 4](https://drafts.csswg.org/css-color-4/)\n    - `lab()`, `lch()`, `oklab()`, and `oklch()` colors\n    - `color()` function supporting predefined color spaces such as `display-p3` and `xyz`\n    - Space separated components in `rgb` and `hsl` functions\n    - Hex with alpha syntax\n    - `hwb()` color syntax\n    - Percent syntax for opacity\n    - `#rgba` and `#rrggbbaa` hex colors\n  - Selectors\n    - `:not` with multiple arguments\n    - `:lang` with multiple arguments\n    - `:dir`\n    - `:is`\n  - Double position gradient stops (e.g. `red 40% 80%`)\n  - `clamp()`, `round()`, `rem()`, and `mod()` math functions\n  - Alignment shorthands (e.g. `place-items`)\n  - Two-value `overflow` shorthand\n  - Media query range syntax (e.g. `@media (width <= 100px)` or `@media (100px < width < 500px)`)\n  - Multi-value `display` property (e.g. `inline flex`)\n  - `system-ui` font family fallbacks\n- **CSS modules** – Lightning CSS supports compiling a subset of [CSS modules](https://github.com/css-modules/css-modules) features.\n  - Locally scoped class and id selectors\n  - Locally scoped custom identifiers, e.g. `@keyframes` names, grid lines/areas, `@counter-style` names, etc.\n  - Opt-in support for locally scoped CSS variables and other dashed identifiers.\n  - `:local()` and `:global()` selectors\n  - The `composes` property\n- **Custom transforms** – The Lightning CSS visitor API can be used to implement custom transform plugins.\n\n## Documentation\n\nLightning CSS can be used from [Parcel](https://parceljs.org), as a standalone library from JavaScript or Rust, using a standalone CLI, or wrapped as a plugin within any other tool. See the [Lightning CSS website](https://lightningcss.dev/docs.html) for documentation.\n\n## Benchmarks\n\n<img width=\"680\" alt=\"performance and build size charts\" src=\"https://user-images.githubusercontent.com/19409/189022599-28246659-f94a-46a4-9de0-b6d17adb0e22.png#gh-light-mode-only\">\n<img width=\"680\" alt=\"performance and build size charts\" src=\"https://user-images.githubusercontent.com/19409/189022693-6956b044-422b-4f56-9628-d59c6f791095.png#gh-dark-mode-only\">\n\n```\n$ node bench.js bootstrap-4.css\ncssnano: 544.809ms\n159636 bytes\n\nesbuild: 17.199ms\n160332 bytes\n\nlightningcss: 4.16ms\n143091 bytes\n\n\n$ node bench.js animate.css\ncssnano: 283.105ms\n71723 bytes\n\nesbuild: 11.858ms\n72183 bytes\n\nlightningcss: 1.973ms\n23666 bytes\n\n\n$ node bench.js tailwind.css\ncssnano: 2.198s\n1925626 bytes\n\nesbuild: 107.668ms\n1961642 bytes\n\nlightningcss: 43.368ms\n1824130 bytes\n```\n\nFor more benchmarks comparing more tools and input, see [here](http://goalsmashers.github.io/css-minification-benchmark/). Note that some of the tools shown perform unsafe optimizations that may change the behavior of the original CSS in favor of smaller file size. Lightning CSS does not do this – the output CSS should always behave identically to the input. Keep this in mind when comparing file sizes between tools.\n"
  },
  {
    "path": "bench.js",
    "content": "const css = require('./');\nconst cssnano = require('cssnano');\nconst postcss = require('postcss');\nconst esbuild = require('esbuild');\n\nlet opts = {\n  filename: process.argv[process.argv.length - 1],\n  code: require('fs').readFileSync(process.argv[process.argv.length - 1]),\n  minify: true,\n  // source_map: true,\n  targets: {\n    chrome: 95 << 16\n  }\n};\n\nasync function run() {\n  await doCssNano();\n\n  console.time('esbuild');\n  let r = await esbuild.transform(opts.code.toString(), {\n    sourcefile: opts.filename,\n    loader: 'css',\n    minify: true\n  });\n  console.timeEnd('esbuild');\n  console.log(r.code.length + ' bytes');\n  console.log('');\n\n  console.time('lightningcss');\n  let res = css.transform(opts);\n  console.timeEnd('lightningcss');\n  console.log(res.code.length + ' bytes');\n}\n\nasync function doCssNano() {\n  console.time('cssnano');\n  const result = await postcss([\n    cssnano,\n  ]).process(opts.code, {from: opts.filename});\n  console.timeEnd('cssnano');\n  console.log(result.css.length + ' bytes');\n  console.log('');\n}\n\nrun();\n"
  },
  {
    "path": "c/Cargo.toml",
    "content": "[package]\nauthors = [\"Devon Govett <devongovett@gmail.com>\"]\nname = \"lightningcss_c_bindings\"\nversion = \"0.1.0\"\nedition = \"2021\"\npublish = false\n\n[lib]\ncrate-type = [\"cdylib\"]\n\n[dependencies]\nlightningcss = { path = \"../\", features = [\"browserslist\"] }\nparcel_sourcemap = { version = \"2.1.1\", features = [\"json\"] }\nbrowserslist-rs = { version = \"0.19.0\" }\n\n[build-dependencies]\ncbindgen = \"0.24.3\"\n"
  },
  {
    "path": "c/build.rs",
    "content": "use std::env;\n\nfn main() {\n  let crate_dir = env::var(\"CARGO_MANIFEST_DIR\").unwrap();\n\n  cbindgen::generate(crate_dir)\n    .expect(\"Unable to generate bindings\")\n    .write_to_file(\"lightningcss.h\");\n}\n"
  },
  {
    "path": "c/cbindgen.toml",
    "content": "language = \"C\"\n\n[parse]\nparse_deps = false\ninclude = [\"lightningcss\"]\n\n[export.rename]\nStyleSheetWrapper = \"StyleSheet\"\n\n[enum]\nprefix_with_name = true\n"
  },
  {
    "path": "c/lightningcss.h",
    "content": "#include <stdarg.h>\n#include <stdbool.h>\n#include <stdint.h>\n#include <stdlib.h>\n\ntypedef struct CssError CssError;\n\ntypedef struct StyleSheet StyleSheet;\n\ntypedef struct Targets {\n  uint32_t android;\n  uint32_t chrome;\n  uint32_t edge;\n  uint32_t firefox;\n  uint32_t ie;\n  uint32_t ios_saf;\n  uint32_t opera;\n  uint32_t safari;\n  uint32_t samsung;\n} Targets;\n\ntypedef struct ParseOptions {\n  const char *filename;\n  bool nesting;\n  bool custom_media;\n  bool css_modules;\n  const char *css_modules_pattern;\n  bool css_modules_dashed_idents;\n  bool error_recovery;\n} ParseOptions;\n\ntypedef struct TransformOptions {\n  struct Targets targets;\n  char **unused_symbols;\n  uintptr_t unused_symbols_len;\n} TransformOptions;\n\ntypedef struct RawString {\n  char *text;\n  uintptr_t len;\n} RawString;\n\ntypedef enum CssModuleReference_Tag {\n  /**\n   * A local reference.\n   */\n  CssModuleReference_Local,\n  /**\n   * A global reference.\n   */\n  CssModuleReference_Global,\n  /**\n   * A reference to an export in a different file.\n   */\n  CssModuleReference_Dependency,\n} CssModuleReference_Tag;\n\ntypedef struct CssModuleReference_Local_Body {\n  /**\n   * The local (compiled) name for the reference.\n   */\n  struct RawString name;\n} CssModuleReference_Local_Body;\n\ntypedef struct CssModuleReference_Global_Body {\n  /**\n   * The referenced global name.\n   */\n  struct RawString name;\n} CssModuleReference_Global_Body;\n\ntypedef struct CssModuleReference_Dependency_Body {\n  /**\n   * The name to reference within the dependency.\n   */\n  struct RawString name;\n  /**\n   * The dependency specifier for the referenced file.\n   */\n  struct RawString specifier;\n} CssModuleReference_Dependency_Body;\n\ntypedef struct CssModuleReference {\n  CssModuleReference_Tag tag;\n  union {\n    CssModuleReference_Local_Body local;\n    CssModuleReference_Global_Body global;\n    CssModuleReference_Dependency_Body dependency;\n  };\n} CssModuleReference;\n\ntypedef struct CssModuleExport {\n  struct RawString exported;\n  struct RawString local;\n  bool is_referenced;\n  struct CssModuleReference *composes;\n  uintptr_t composes_len;\n} CssModuleExport;\n\ntypedef struct CssModulePlaceholder {\n  struct RawString placeholder;\n  struct CssModuleReference reference;\n} CssModulePlaceholder;\n\ntypedef struct ToCssResult {\n  struct RawString code;\n  struct RawString map;\n  struct CssModuleExport *exports;\n  uintptr_t exports_len;\n  struct CssModulePlaceholder *references;\n  uintptr_t references_len;\n} ToCssResult;\n\ntypedef struct PseudoClasses {\n  const char *hover;\n  const char *active;\n  const char *focus;\n  const char *focus_visible;\n  const char *focus_within;\n} PseudoClasses;\n\ntypedef struct ToCssOptions {\n  bool minify;\n  bool source_map;\n  const char *input_source_map;\n  uintptr_t input_source_map_len;\n  const char *project_root;\n  struct Targets targets;\n  bool analyze_dependencies;\n  struct PseudoClasses pseudo_classes;\n} ToCssOptions;\n\nbool lightningcss_browserslist_to_targets(const char *query,\n                                          struct Targets *targets,\n                                          struct CssError **error);\n\nstruct StyleSheet *lightningcss_stylesheet_parse(const char *source,\n                                                 uintptr_t len,\n                                                 struct ParseOptions options,\n                                                 struct CssError **error);\n\nbool lightningcss_stylesheet_transform(struct StyleSheet *stylesheet,\n                                       struct TransformOptions options,\n                                       struct CssError **error);\n\nstruct ToCssResult lightningcss_stylesheet_to_css(struct StyleSheet *stylesheet,\n                                                  struct ToCssOptions options,\n                                                  struct CssError **error);\n\nvoid lightningcss_stylesheet_free(struct StyleSheet *stylesheet);\n\nvoid lightningcss_to_css_result_free(struct ToCssResult result);\n\nconst char *lightningcss_error_message(struct CssError *error);\n\nvoid lightningcss_error_free(struct CssError *error);\n\nuintptr_t lightningcss_stylesheet_get_warning_count(struct StyleSheet *stylesheet);\n\nconst char *lightningcss_stylesheet_get_warning(struct StyleSheet *stylesheet, uintptr_t index);\n"
  },
  {
    "path": "c/src/lib.rs",
    "content": "#![allow(clippy::not_unsafe_ptr_arg_deref)]\n\nuse std::collections::HashSet;\nuse std::ffi::{CStr, CString};\nuse std::mem::ManuallyDrop;\nuse std::os::raw::c_char;\nuse std::sync::{Arc, RwLock};\n\nuse lightningcss::css_modules::PatternParseError;\nuse lightningcss::error::{Error, MinifyErrorKind, ParserError, PrinterError};\nuse lightningcss::stylesheet::{MinifyOptions, ParserFlags, ParserOptions, PrinterOptions, StyleSheet};\nuse lightningcss::targets::Browsers;\nuse parcel_sourcemap::SourceMap;\n\npub struct StyleSheetWrapper<'i, 'o> {\n  stylesheet: StyleSheet<'i, 'o>,\n  source: &'i str,\n  warnings: Vec<CssError<'i>>,\n}\n\npub struct CssError<'i> {\n  kind: ErrorKind<'i>,\n  message: Option<CString>,\n}\n\nimpl<'i> CssError<'i> {\n  fn message(&mut self) -> *const c_char {\n    if let Some(message) = &self.message {\n      return message.as_ptr();\n    }\n\n    let string: String = match &self.kind {\n      ErrorKind::ParserError(err) => err.to_string().into(),\n      ErrorKind::MinifyError(err) => err.to_string().into(),\n      ErrorKind::PrinterError(err) => err.to_string().into(),\n      ErrorKind::PatternParseError(err) => err.to_string().into(),\n      ErrorKind::BrowserslistError(err) => err.to_string().into(),\n      ErrorKind::SourceMapError(err) => err.to_string().into(),\n    };\n\n    self.message = Some(CString::new(string).unwrap());\n    self.message.as_ref().unwrap().as_ptr()\n  }\n}\n\npub enum ErrorKind<'i> {\n  ParserError(Error<ParserError<'i>>),\n  MinifyError(Error<MinifyErrorKind>),\n  PrinterError(PrinterError),\n  PatternParseError(PatternParseError),\n  BrowserslistError(browserslist::Error),\n  SourceMapError(parcel_sourcemap::SourceMapError),\n}\n\nmacro_rules! impl_from {\n  ($name: ident, $t: ty) => {\n    impl<'i> From<$t> for CssError<'i> {\n      fn from(err: $t) -> Self {\n        CssError {\n          kind: ErrorKind::$name(err),\n          message: None,\n        }\n      }\n    }\n  };\n}\n\nimpl_from!(ParserError, Error<ParserError<'i>>);\nimpl_from!(MinifyError, Error<MinifyErrorKind>);\nimpl_from!(PrinterError, PrinterError);\nimpl_from!(PatternParseError, PatternParseError);\nimpl_from!(BrowserslistError, browserslist::Error);\nimpl_from!(SourceMapError, parcel_sourcemap::SourceMapError);\n\n#[repr(C)]\npub struct ParseOptions {\n  filename: *const c_char,\n  nesting: bool,\n  custom_media: bool,\n  css_modules: bool,\n  css_modules_pattern: *const c_char,\n  css_modules_dashed_idents: bool,\n  error_recovery: bool,\n}\n\n#[repr(C)]\n#[derive(Default, PartialEq)]\npub struct Targets {\n  android: u32,\n  chrome: u32,\n  edge: u32,\n  firefox: u32,\n  ie: u32,\n  ios_saf: u32,\n  opera: u32,\n  safari: u32,\n  samsung: u32,\n}\n\nimpl Into<Browsers> for Targets {\n  fn into(self) -> Browsers {\n    macro_rules! browser {\n      ($val: expr) => {\n        if $val > 0 {\n          Some($val)\n        } else {\n          None\n        }\n      };\n    }\n\n    Browsers {\n      android: browser!(self.android),\n      chrome: browser!(self.chrome),\n      edge: browser!(self.edge),\n      firefox: browser!(self.firefox),\n      ie: browser!(self.ie),\n      ios_saf: browser!(self.ios_saf),\n      opera: browser!(self.opera),\n      safari: browser!(self.safari),\n      samsung: browser!(self.samsung),\n    }\n  }\n}\n\nmacro_rules! unwrap {\n  ($result: expr, $error: ident, $ret: expr) => {\n    match $result {\n      Ok(v) => v,\n      Err(err) => unsafe {\n        *$error = Box::into_raw(Box::new(err.into()));\n        return $ret;\n      },\n    }\n  };\n}\n\n#[no_mangle]\npub extern \"C\" fn lightningcss_browserslist_to_targets(\n  query: *const c_char,\n  targets: *mut Targets,\n  error: *mut *mut CssError,\n) -> bool {\n  let string = unsafe { std::str::from_utf8_unchecked(CStr::from_ptr(query).to_bytes()) };\n  match Browsers::from_browserslist([string]) {\n    Ok(Some(browsers)) => {\n      let targets = unsafe { &mut *targets };\n      targets.android = browsers.android.unwrap_or_default();\n      targets.chrome = browsers.chrome.unwrap_or_default();\n      targets.edge = browsers.edge.unwrap_or_default();\n      targets.firefox = browsers.firefox.unwrap_or_default();\n      targets.ie = browsers.ie.unwrap_or_default();\n      targets.ios_saf = browsers.ios_saf.unwrap_or_default();\n      targets.opera = browsers.opera.unwrap_or_default();\n      targets.safari = browsers.safari.unwrap_or_default();\n      targets.samsung = browsers.samsung.unwrap_or_default();\n      true\n    }\n    Ok(None) => true,\n    Err(err) => unsafe {\n      *error = Box::into_raw(Box::new(err.into()));\n      false\n    },\n  }\n}\n\n#[repr(C)]\npub struct TransformOptions {\n  targets: Targets,\n  unused_symbols: *mut *mut c_char,\n  unused_symbols_len: usize,\n}\n\nimpl Into<MinifyOptions> for TransformOptions {\n  fn into(self) -> MinifyOptions {\n    let mut unused_symbols = HashSet::new();\n    let slice = unsafe { std::slice::from_raw_parts(self.unused_symbols, self.unused_symbols_len) };\n    for symbol in slice {\n      let string = unsafe { std::str::from_utf8_unchecked(CStr::from_ptr(*symbol).to_bytes()).to_owned() };\n      unused_symbols.insert(string);\n    }\n\n    MinifyOptions {\n      targets: if self.targets != Targets::default() {\n        Some(self.targets.into()).into()\n      } else {\n        Default::default()\n      },\n      unused_symbols,\n    }\n  }\n}\n\n#[repr(C)]\npub struct ToCssOptions {\n  minify: bool,\n  source_map: bool,\n  input_source_map: *const c_char,\n  input_source_map_len: usize,\n  project_root: *const c_char,\n  targets: Targets,\n  analyze_dependencies: bool,\n  pseudo_classes: PseudoClasses,\n}\n\n#[derive(PartialEq)]\n#[repr(C)]\npub struct PseudoClasses {\n  hover: *const c_char,\n  active: *const c_char,\n  focus: *const c_char,\n  focus_visible: *const c_char,\n  focus_within: *const c_char,\n}\n\nimpl Default for PseudoClasses {\n  fn default() -> Self {\n    PseudoClasses {\n      hover: std::ptr::null(),\n      active: std::ptr::null(),\n      focus: std::ptr::null(),\n      focus_visible: std::ptr::null(),\n      focus_within: std::ptr::null(),\n    }\n  }\n}\n\nimpl<'a> Into<lightningcss::printer::PseudoClasses<'a>> for PseudoClasses {\n  fn into(self) -> lightningcss::printer::PseudoClasses<'a> {\n    macro_rules! pc {\n      ($ptr: expr) => {\n        if $ptr.is_null() {\n          None\n        } else {\n          Some(unsafe { std::str::from_utf8_unchecked(CStr::from_ptr($ptr).to_bytes()) })\n        }\n      };\n    }\n\n    lightningcss::printer::PseudoClasses {\n      hover: pc!(self.hover),\n      active: pc!(self.active),\n      focus: pc!(self.focus),\n      focus_visible: pc!(self.focus_visible),\n      focus_within: pc!(self.focus_within),\n    }\n  }\n}\n\n#[no_mangle]\npub extern \"C\" fn lightningcss_stylesheet_parse(\n  source: *const c_char,\n  len: usize,\n  options: ParseOptions,\n  error: *mut *mut CssError,\n) -> *mut StyleSheetWrapper {\n  let slice = unsafe { std::slice::from_raw_parts(source as *const u8, len) };\n  let code = unsafe { std::str::from_utf8_unchecked(slice) };\n  let warnings = Arc::new(RwLock::new(Vec::new()));\n  let mut flags = ParserFlags::empty();\n  flags.set(ParserFlags::CUSTOM_MEDIA, options.custom_media);\n  let opts = ParserOptions {\n    filename: if options.filename.is_null() {\n      String::new()\n    } else {\n      unsafe { std::str::from_utf8_unchecked(CStr::from_ptr(options.filename).to_bytes()).to_owned() }\n    },\n    flags,\n    css_modules: if options.css_modules {\n      let pattern = if !options.css_modules_pattern.is_null() {\n        let pattern =\n          unsafe { std::str::from_utf8_unchecked(CStr::from_ptr(options.css_modules_pattern).to_bytes()) };\n        unwrap!(\n          lightningcss::css_modules::Pattern::parse(pattern),\n          error,\n          std::ptr::null_mut()\n        )\n      } else {\n        lightningcss::css_modules::Pattern::default()\n      };\n      Some(lightningcss::css_modules::Config {\n        pattern,\n        dashed_idents: options.css_modules_dashed_idents,\n        ..Default::default()\n      })\n    } else {\n      None\n    },\n    error_recovery: options.error_recovery,\n    source_index: 0,\n    warnings: Some(warnings.clone()),\n  };\n\n  let stylesheet = unwrap!(StyleSheet::parse(code, opts), error, std::ptr::null_mut());\n  Box::into_raw(Box::new(StyleSheetWrapper {\n    stylesheet,\n    source: code,\n    warnings: warnings.clone().read().unwrap().iter().map(|w| w.clone().into()).collect(),\n  }))\n}\n\n#[no_mangle]\npub extern \"C\" fn lightningcss_stylesheet_transform(\n  stylesheet: *mut StyleSheetWrapper,\n  options: TransformOptions,\n  error: *mut *mut CssError,\n) -> bool {\n  let wrapper = unsafe { stylesheet.as_mut() }.unwrap();\n  unwrap!(wrapper.stylesheet.minify(options.into()), error, false);\n  true\n}\n\n#[no_mangle]\npub extern \"C\" fn lightningcss_stylesheet_to_css(\n  stylesheet: *mut StyleSheetWrapper,\n  options: ToCssOptions,\n  error: *mut *mut CssError,\n) -> ToCssResult {\n  let wrapper = unsafe { stylesheet.as_mut() }.unwrap();\n  let mut source_map = if options.source_map {\n    let mut sm = SourceMap::new(\"/\");\n    sm.add_source(&wrapper.stylesheet.sources[0]);\n    unwrap!(sm.set_source_content(0, wrapper.source), error, ToCssResult::default());\n    Some(sm)\n  } else {\n    None\n  };\n\n  let opts = PrinterOptions {\n    minify: options.minify,\n    project_root: if options.project_root.is_null() {\n      None\n    } else {\n      Some(unsafe { std::str::from_utf8_unchecked(CStr::from_ptr(options.project_root).to_bytes()) })\n    },\n    source_map: source_map.as_mut(),\n    targets: if options.targets != Targets::default() {\n      Some(options.targets.into()).into()\n    } else {\n      Default::default()\n    },\n    analyze_dependencies: if options.analyze_dependencies {\n      Some(Default::default())\n    } else {\n      None\n    },\n    pseudo_classes: if options.pseudo_classes != PseudoClasses::default() {\n      Some(options.pseudo_classes.into())\n    } else {\n      None\n    },\n  };\n\n  let res = unwrap!(wrapper.stylesheet.to_css(opts), error, ToCssResult::default());\n\n  let map = if let Some(mut source_map) = source_map {\n    if !options.input_source_map.is_null() {\n      let slice =\n        unsafe { std::slice::from_raw_parts(options.input_source_map as *const u8, options.input_source_map_len) };\n      let input_source_map = unsafe { std::str::from_utf8_unchecked(slice) };\n      let mut sm = unwrap!(\n        SourceMap::from_json(\"/\", input_source_map),\n        error,\n        ToCssResult::default()\n      );\n      unwrap!(source_map.extends(&mut sm), error, ToCssResult::default());\n    }\n\n    unwrap!(source_map.to_json(None), error, ToCssResult::default()).into()\n  } else {\n    RawString::default()\n  };\n\n  let (exports, exports_len) = if let Some(exports) = res.exports {\n    let exports: Vec<CssModuleExport> = exports\n      .into_iter()\n      .map(|(k, v)| {\n        let composes_len = v.composes.len();\n        let composes = if !v.composes.is_empty() {\n          let composes: Vec<CssModuleReference> = v.composes.into_iter().map(|composes| composes.into()).collect();\n          ManuallyDrop::new(composes).as_mut_ptr()\n        } else {\n          std::ptr::null_mut()\n        };\n\n        CssModuleExport {\n          exported: k.into(),\n          local: v.name.into(),\n          is_referenced: v.is_referenced,\n          composes,\n          composes_len,\n        }\n      })\n      .collect();\n    let mut exports = ManuallyDrop::new(exports);\n    (exports.as_mut_ptr(), exports.len())\n  } else {\n    (std::ptr::null_mut(), 0)\n  };\n\n  let (references, references_len) = if let Some(references) = res.references {\n    let references: Vec<CssModulePlaceholder> = references\n      .into_iter()\n      .map(|(k, v)| CssModulePlaceholder {\n        placeholder: k.into(),\n        reference: v.into(),\n      })\n      .collect();\n    let mut references = ManuallyDrop::new(references);\n    (references.as_mut_ptr(), references.len())\n  } else {\n    (std::ptr::null_mut(), 0)\n  };\n\n  ToCssResult {\n    code: res.code.into(),\n    map,\n    exports,\n    exports_len,\n    references,\n    references_len,\n  }\n}\n\n#[no_mangle]\npub extern \"C\" fn lightningcss_stylesheet_free(stylesheet: *mut StyleSheetWrapper) {\n  if !stylesheet.is_null() {\n    drop(unsafe { Box::from_raw(stylesheet) })\n  }\n}\n\n#[repr(C)]\npub struct ToCssResult {\n  code: RawString,\n  map: RawString,\n  exports: *mut CssModuleExport,\n  exports_len: usize,\n  references: *mut CssModulePlaceholder,\n  references_len: usize,\n}\n\nimpl Default for ToCssResult {\n  fn default() -> Self {\n    ToCssResult {\n      code: RawString::default(),\n      map: RawString::default(),\n      exports: std::ptr::null_mut(),\n      exports_len: 0,\n      references: std::ptr::null_mut(),\n      references_len: 0,\n    }\n  }\n}\n\nimpl Drop for ToCssResult {\n  fn drop(&mut self) {\n    if !self.exports.is_null() {\n      let exports = unsafe { Vec::from_raw_parts(self.exports, self.exports_len, self.exports_len) };\n      drop(exports);\n      self.exports = std::ptr::null_mut();\n    }\n\n    if !self.references.is_null() {\n      let references = unsafe { Vec::from_raw_parts(self.references, self.references_len, self.references_len) };\n      drop(references);\n      self.references = std::ptr::null_mut();\n    }\n  }\n}\n\n#[no_mangle]\npub extern \"C\" fn lightningcss_to_css_result_free(result: ToCssResult) {\n  drop(result)\n}\n\n#[repr(C)]\npub struct CssModuleExport {\n  exported: RawString,\n  local: RawString,\n  is_referenced: bool,\n  composes: *mut CssModuleReference,\n  composes_len: usize,\n}\n\nimpl Drop for CssModuleExport {\n  fn drop(&mut self) {\n    if !self.composes.is_null() {\n      let composes = unsafe { Vec::from_raw_parts(self.composes, self.composes_len, self.composes_len) };\n      drop(composes);\n      self.composes = std::ptr::null_mut();\n    }\n  }\n}\n\n#[repr(C)]\npub enum CssModuleReference {\n  /// A local reference.\n  Local {\n    /// The local (compiled) name for the reference.\n    name: RawString,\n  },\n  /// A global reference.\n  Global {\n    /// The referenced global name.\n    name: RawString,\n  },\n  /// A reference to an export in a different file.\n  Dependency {\n    /// The name to reference within the dependency.\n    name: RawString,\n    /// The dependency specifier for the referenced file.\n    specifier: RawString,\n  },\n}\n\nimpl From<lightningcss::css_modules::CssModuleReference> for CssModuleReference {\n  fn from(reference: lightningcss::css_modules::CssModuleReference) -> Self {\n    use lightningcss::css_modules::CssModuleReference::*;\n    match reference {\n      Local { name } => CssModuleReference::Local { name: name.into() },\n      Global { name } => CssModuleReference::Global { name: name.into() },\n      Dependency { name, specifier } => CssModuleReference::Dependency {\n        name: name.into(),\n        specifier: specifier.into(),\n      },\n    }\n  }\n}\n\n#[repr(C)]\npub struct CssModulePlaceholder {\n  placeholder: RawString,\n  reference: CssModuleReference,\n}\n\n#[repr(C)]\npub struct RawString {\n  text: *mut c_char,\n  len: usize,\n}\n\nimpl Default for RawString {\n  fn default() -> Self {\n    RawString {\n      text: std::ptr::null_mut(),\n      len: 0,\n    }\n  }\n}\n\nimpl From<String> for RawString {\n  fn from(string: String) -> RawString {\n    RawString {\n      len: string.len(),\n      text: Box::into_raw(string.into_boxed_str()) as *mut c_char,\n    }\n  }\n}\n\nimpl Drop for RawString {\n  fn drop(&mut self) {\n    if self.text.is_null() {\n      return;\n    }\n    drop(unsafe { Box::from_raw(self.text) });\n    self.text = std::ptr::null_mut();\n  }\n}\n\n#[no_mangle]\npub extern \"C\" fn lightningcss_error_message(error: *mut CssError) -> *const c_char {\n  match unsafe { error.as_mut() } {\n    Some(err) => err.message(),\n    None => std::ptr::null(),\n  }\n}\n\n#[no_mangle]\npub extern \"C\" fn lightningcss_error_free(error: *mut CssError) {\n  if !error.is_null() {\n    drop(unsafe { Box::from_raw(error) })\n  }\n}\n\n#[no_mangle]\npub extern \"C\" fn lightningcss_stylesheet_get_warning_count<'i>(\n  stylesheet: *mut StyleSheetWrapper<'i, '_>,\n) -> usize {\n  match unsafe { stylesheet.as_mut() } {\n    Some(s) => s.warnings.len(),\n    None => 0,\n  }\n}\n\n#[no_mangle]\npub extern \"C\" fn lightningcss_stylesheet_get_warning<'i>(\n  stylesheet: *mut StyleSheetWrapper<'i, '_>,\n  index: usize,\n) -> *const c_char {\n  let stylesheet = match unsafe { stylesheet.as_mut() } {\n    Some(s) => s,\n    None => return std::ptr::null(),\n  };\n\n  match stylesheet.warnings.get_mut(index) {\n    Some(w) => w.message(),\n    None => std::ptr::null(),\n  }\n}\n"
  },
  {
    "path": "c/test.c",
    "content": "#include <stdio.h>\n#include <string.h>\n#include \"lightningcss.h\"\n\nint print_error(CssError *error);\n\nint main()\n{\n  char *source =\n      \".foo {\"\n      \"  color: lch(50.998% 135.363 338);\"\n      \"}\"\n      \".bar {\"\n      \"  color: yellow;\"\n      \"  composes: foo from './bar.css';\"\n      \"}\"\n      \".baz:hover {\"\n      \"  color: var(--foo from './baz.css');\"\n      \"}\";\n\n  ParseOptions parse_opts = {\n      .filename = \"test.css\",\n      .css_modules = true,\n      .css_modules_pattern = \"yo_[name]_[local]\",\n      .css_modules_dashed_idents = true};\n\n  CssError *error = NULL;\n  StyleSheet *stylesheet = lightningcss_stylesheet_parse(source, strlen(source), parse_opts, &error);\n  if (!stylesheet)\n    goto cleanup;\n\n  char *unused_symbols[1] = {\"bar\"};\n  TransformOptions transform_opts = {\n      .unused_symbols = unused_symbols,\n      .unused_symbols_len = 0};\n\n  if (!lightningcss_browserslist_to_targets(\"last 2 versions, not IE <= 11\", &transform_opts.targets, &error))\n    goto cleanup;\n\n  if (!lightningcss_stylesheet_transform(stylesheet, transform_opts, &error))\n    goto cleanup;\n\n  ToCssOptions to_css_opts = {\n      .minify = true,\n      .source_map = true,\n      .pseudo_classes = {\n          .hover = \"is-hovered\"}};\n\n  ToCssResult result = lightningcss_stylesheet_to_css(stylesheet, to_css_opts, &error);\n  if (error)\n    goto cleanup;\n\n  size_t warning_count = lightningcss_stylesheet_get_warning_count(stylesheet);\n  for (size_t i = 0; i < warning_count; i++)\n  {\n    printf(\"warning: %s\\n\", lightningcss_stylesheet_get_warning(stylesheet, i));\n  }\n\n  fwrite(result.code.text, sizeof(char), result.code.len, stdout);\n  printf(\"\\n\");\n  fwrite(result.map.text, sizeof(char), result.map.len, stdout);\n  printf(\"\\n\");\n\n  for (int i = 0; i < result.exports_len; i++)\n  {\n    printf(\"%.*s -> %.*s\\n\", (int)result.exports[i].exported.len, result.exports[i].exported.text, (int)result.exports[i].local.len, result.exports[i].local.text);\n    for (int j = 0; j < result.exports[i].composes_len; j++)\n    {\n      const CssModuleReference *ref = &result.exports[i].composes[j];\n      switch (ref->tag)\n      {\n      case CssModuleReference_Local:\n        printf(\"  composes local: %.*s\\n\", (int)ref->local.name.len, ref->local.name.text);\n        break;\n      case CssModuleReference_Global:\n        printf(\"  composes global: %.*s\\n\", (int)ref->global.name.len, ref->global.name.text);\n        break;\n      case CssModuleReference_Dependency:\n        printf(\"  composes dependency: %.*s from %.*s\\n\", (int)ref->dependency.name.len, ref->dependency.name.text, (int)ref->dependency.specifier.len, ref->dependency.specifier.text);\n        break;\n      }\n    }\n  }\n\n  for (int i = 0; i < result.references_len; i++)\n  {\n    printf(\"placeholder: %.*s\\n\", (int)result.references[i].placeholder.len, result.references[i].placeholder.text);\n  }\n\ncleanup:\n  lightningcss_stylesheet_free(stylesheet);\n  lightningcss_to_css_result_free(result);\n\n  if (error)\n  {\n    printf(\"error: %s\\n\", lightningcss_error_message(error));\n    lightningcss_error_free(error);\n    return 1;\n  }\n}\n"
  },
  {
    "path": "cli/.gitignore",
    "content": "package.json\nREADME.md\n.DS_Store\nlightningcss.exe\n"
  },
  {
    "path": "cli/lightningcss",
    "content": "This file is required so that npm creates the lightningcss binary on Windows.\n"
  },
  {
    "path": "cli/postinstall.js",
    "content": "let fs = require('fs');\nlet path = require('path');\n\nlet parts = [process.platform, process.arch];\nif (process.platform === 'linux') {\n  const {MUSL, familySync} = require('detect-libc');\n  const family = familySync();\n  if (family === MUSL) {\n    parts.push('musl');\n  } else if (process.arch === 'arm') {\n    parts.push('gnueabihf');\n  } else {\n    parts.push('gnu');\n  }\n} else if (process.platform === 'win32') {\n  parts.push('msvc');\n}\n\nlet binary = process.platform === 'win32' ? 'lightningcss.exe' : 'lightningcss';\n\nlet pkgPath;\ntry {\n  pkgPath = path.dirname(require.resolve(`lightningcss-cli-${parts.join('-')}/package.json`));\n} catch (err) {\n  pkgPath = path.join(__dirname, '..', 'target', 'release');\n  if (!fs.existsSync(path.join(pkgPath, binary))) {\n    pkgPath = path.join(__dirname, '..', 'target', 'debug');\n  }\n}\n\ntry {\n  fs.linkSync(path.join(pkgPath, binary), path.join(__dirname, binary));\n} catch (err) {\n  try {\n    fs.copyFileSync(path.join(pkgPath, binary), path.join(__dirname, binary));\n  } catch (err) {\n    console.error('Failed to move lightningcss-cli binary into place.');\n    process.exit(1);\n  }\n}\n\nif (process.platform === 'win32') {\n  try {\n    fs.unlinkSync(path.join(__dirname, 'lightningcss'));\n  } catch (err) { }\n}\n"
  },
  {
    "path": "derive/Cargo.toml",
    "content": "[package]\nauthors = [\"Devon Govett <devongovett@gmail.com>\"]\nname = \"lightningcss-derive\"\ndescription = \"Derive macros for lightningcss\"\nversion = \"1.0.0-alpha.43\"\nlicense = \"MPL-2.0\"\nedition = \"2021\"\nrepository = \"https://github.com/parcel-bundler/lightningcss\"\n\n[lib]\nproc-macro = true\n\n[dependencies]\nsyn = { version = \"1.0\", features = [\"extra-traits\"] }\nquote = \"1.0\"\nproc-macro2 = \"1.0\"\nconvert_case = \"0.6.0\"\n"
  },
  {
    "path": "derive/src/lib.rs",
    "content": "use proc_macro::TokenStream;\n\nmod parse;\nmod to_css;\nmod visit;\n\n#[proc_macro_derive(Visit, attributes(visit, skip_visit, skip_type, visit_types))]\npub fn derive_visit_children(input: TokenStream) -> TokenStream {\n  visit::derive_visit_children(input)\n}\n\n#[proc_macro_derive(Parse, attributes(css))]\npub fn derive_parse(input: TokenStream) -> TokenStream {\n  parse::derive_parse(input)\n}\n\n#[proc_macro_derive(ToCss, attributes(css))]\npub fn derive_to_css(input: TokenStream) -> TokenStream {\n  to_css::derive_to_css(input)\n}\n"
  },
  {
    "path": "derive/src/parse.rs",
    "content": "use convert_case::Casing;\nuse proc_macro::{self, TokenStream};\nuse proc_macro2::{Literal, Span, TokenStream as TokenStream2};\nuse quote::quote;\nuse syn::{\n  parse::Parse, parse_macro_input, parse_quote, Attribute, Data, DataEnum, DeriveInput, Fields, Ident, Token,\n};\n\npub fn derive_parse(input: TokenStream) -> TokenStream {\n  let DeriveInput {\n    ident,\n    data,\n    mut generics,\n    attrs,\n    ..\n  } = parse_macro_input!(input);\n  let opts = CssOptions::parse_attributes(&attrs).unwrap();\n  let cloned_generics = generics.clone();\n  let (_, ty_generics, _) = cloned_generics.split_for_impl();\n\n  if generics.lifetimes().next().is_none() {\n    generics.params.insert(0, parse_quote! { 'i })\n  }\n\n  let lifetime = generics.lifetimes().next().unwrap().clone();\n  let (impl_generics, _, where_clause) = generics.split_for_impl();\n\n  let imp = match &data {\n    Data::Enum(data) => derive_enum(&data, &ident, &opts),\n    _ => todo!(),\n  };\n\n  let output = quote! {\n    impl #impl_generics Parse<#lifetime> for #ident #ty_generics #where_clause {\n      fn parse<'t>(input: &mut Parser<#lifetime, 't>) -> Result<Self, ParseError<#lifetime, ParserError<#lifetime>>> {\n        #imp\n      }\n    }\n  };\n\n  output.into()\n}\n\nfn derive_enum(data: &DataEnum, ident: &Ident, opts: &CssOptions) -> TokenStream2 {\n  let mut idents = Vec::new();\n  let mut non_idents = Vec::new();\n  for (index, variant) in data.variants.iter().enumerate() {\n    let name = &variant.ident;\n    let fields = variant\n      .fields\n      .iter()\n      .enumerate()\n      .map(|(index, field)| {\n        field.ident.as_ref().map_or_else(\n          || Ident::new(&format!(\"_{}\", index), Span::call_site()),\n          |ident| ident.clone(),\n        )\n      })\n      .collect::<Vec<_>>();\n\n    let mut expr = match &variant.fields {\n      Fields::Unit => {\n        idents.push((\n          Literal::string(&variant.ident.to_string().to_case(opts.case)),\n          name.clone(),\n        ));\n        continue;\n      }\n      Fields::Named(_) => {\n        quote! {\n          return Ok(#ident::#name { #(#fields),* })\n        }\n      }\n      Fields::Unnamed(_) => {\n        quote! {\n          return Ok(#ident::#name(#(#fields),*))\n        }\n      }\n    };\n\n    // Group multiple ident branches together.\n    if !idents.is_empty() {\n      if idents.len() == 1 {\n        let (s, name) = idents.remove(0);\n        non_idents.push(quote! {\n          if input.try_parse(|input| input.expect_ident_matching(#s)).is_ok() {\n            return Ok(#ident::#name)\n          }\n        });\n      } else {\n        let matches = idents\n          .iter()\n          .map(|(s, name)| {\n            quote! {\n              #s => return Ok(#ident::#name),\n            }\n          })\n          .collect::<Vec<_>>();\n        non_idents.push(quote! {\n          {\n            let state = input.state();\n            if let Ok(ident) = input.try_parse(|input| input.expect_ident_cloned()) {\n              cssparser::match_ignore_ascii_case! { &*ident,\n                #(#matches)*\n                _ => {}\n              }\n              input.reset(&state);\n            }\n          }\n        });\n        idents.clear();\n      }\n    }\n\n    let is_last = index == data.variants.len() - 1;\n\n    for (index, field) in variant.fields.iter().enumerate().rev() {\n      let ty = &field.ty;\n      let field_name = field.ident.as_ref().map_or_else(\n        || Ident::new(&format!(\"_{}\", index), Span::call_site()),\n        |ident| ident.clone(),\n      );\n      if is_last {\n        expr = quote! {\n          let #field_name = <#ty>::parse(input)?;\n          #expr\n        };\n      } else {\n        expr = quote! {\n          if let Ok(#field_name) = input.try_parse(<#ty>::parse) {\n            #expr\n          }\n        };\n      }\n    }\n\n    non_idents.push(expr);\n  }\n\n  let idents = if idents.is_empty() {\n    quote! {}\n  } else if idents.len() == 1 {\n    let (s, name) = idents.remove(0);\n    quote! {\n      input.expect_ident_matching(#s)?;\n      Ok(#ident::#name)\n    }\n  } else {\n    let idents = idents\n      .into_iter()\n      .map(|(s, name)| {\n        quote! {\n          #s => Ok(#ident::#name),\n        }\n      })\n      .collect::<Vec<_>>();\n    quote! {\n      let location = input.current_source_location();\n      let ident = input.expect_ident()?;\n      cssparser::match_ignore_ascii_case! { &*ident,\n        #(#idents)*\n        _ => Err(location.new_unexpected_token_error(\n          cssparser::Token::Ident(ident.clone())\n        ))\n      }\n    }\n  };\n\n  let output = quote! {\n    #(#non_idents)*\n    #idents\n  };\n\n  output.into()\n}\n\npub struct CssOptions {\n  pub case: convert_case::Case,\n}\n\nimpl CssOptions {\n  pub fn parse_attributes(attrs: &Vec<Attribute>) -> syn::Result<Self> {\n    for attr in attrs {\n      if attr.path.is_ident(\"css\") {\n        let opts: CssOptions = attr.parse_args()?;\n        return Ok(opts);\n      }\n    }\n\n    Ok(CssOptions {\n      case: convert_case::Case::Kebab,\n    })\n  }\n}\n\nimpl Parse for CssOptions {\n  fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {\n    let mut case = convert_case::Case::Kebab;\n    while !input.is_empty() {\n      let k: Ident = input.parse()?;\n      let _: Token![=] = input.parse()?;\n      let v: Ident = input.parse()?;\n\n      if k == \"case\" {\n        if v == \"lower\" {\n          case = convert_case::Case::Flat;\n        }\n      }\n    }\n\n    Ok(Self { case })\n  }\n}\n"
  },
  {
    "path": "derive/src/to_css.rs",
    "content": "use convert_case::Casing;\nuse proc_macro::{self, TokenStream};\nuse proc_macro2::{Literal, Span, TokenStream as TokenStream2};\nuse quote::quote;\nuse syn::{parse_macro_input, Data, DataEnum, DeriveInput, Fields, Ident, Type};\n\nuse crate::parse::CssOptions;\n\npub fn derive_to_css(input: TokenStream) -> TokenStream {\n  let DeriveInput {\n    ident,\n    data,\n    generics,\n    attrs,\n    ..\n  } = parse_macro_input!(input);\n\n  let opts = CssOptions::parse_attributes(&attrs).unwrap();\n  let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();\n\n  let imp = match &data {\n    Data::Enum(data) => derive_enum(&data, &opts),\n    _ => todo!(),\n  };\n\n  let output = quote! {\n    impl #impl_generics ToCss for #ident #ty_generics #where_clause {\n      fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n      where\n        W: std::fmt::Write,\n      {\n        #imp\n      }\n    }\n  };\n\n  output.into()\n}\n\nfn derive_enum(data: &DataEnum, opts: &CssOptions) -> TokenStream2 {\n  let variants = data\n    .variants\n    .iter()\n    .map(|variant| {\n      let name = &variant.ident;\n      let fields = variant\n        .fields\n        .iter()\n        .enumerate()\n        .map(|(index, field)| {\n          field.ident.as_ref().map_or_else(\n            || Ident::new(&format!(\"_{}\", index), Span::call_site()),\n            |ident| ident.clone(),\n          )\n        })\n        .collect::<Vec<_>>();\n\n      #[derive(PartialEq)]\n      enum NeedsSpace {\n        Yes,\n        No,\n        Maybe,\n      }\n\n      let mut needs_space = NeedsSpace::No;\n      let mut fields_iter = variant.fields.iter().zip(fields.iter()).peekable();\n      let mut writes = Vec::new();\n      let mut has_needs_space = false;\n      while let Some((field, name)) = fields_iter.next() {\n        writes.push(if fields.len() > 1 {\n          let space = match needs_space {\n            NeedsSpace::Yes => quote! { dest.write_char(' ')?; },\n            NeedsSpace::No => quote! {},\n            NeedsSpace::Maybe => {\n              has_needs_space = true;\n              quote! {\n                if needs_space {\n                  dest.write_char(' ')?;\n                }\n              }\n            }\n          };\n\n          if is_option(&field.ty) {\n            needs_space = NeedsSpace::Maybe;\n            let after_space = if matches!(fields_iter.peek(), Some((field, _)) if !is_option(&field.ty)) {\n              // If the next field is non-optional, just insert the space here.\n              needs_space = NeedsSpace::No;\n              quote! { dest.write_char(' ')?; }\n            } else {\n              quote! {}\n            };\n            quote! {\n              if let Some(v) = #name {\n                #space\n                v.to_css(dest)?;\n                #after_space\n              }\n            }\n          } else {\n            needs_space = NeedsSpace::Yes;\n            quote! {\n              #space\n              #name.to_css(dest)?;\n            }\n          }\n        } else {\n          quote! { #name.to_css(dest) }\n        });\n      }\n\n      if writes.len() > 1 {\n        writes.push(quote! { Ok(()) });\n      }\n\n      if has_needs_space {\n        writes.insert(0, quote! { let mut needs_space = false });\n      }\n\n      match variant.fields {\n        Fields::Unit => {\n          let s = Literal::string(&variant.ident.to_string().to_case(opts.case));\n          quote! {\n            Self::#name => dest.write_str(#s)\n          }\n        }\n        Fields::Named(_) => {\n          quote! {\n            Self::#name { #(#fields),* } => {\n              #(#writes)*\n            }\n          }\n        }\n        Fields::Unnamed(_) => {\n          quote! {\n            Self::#name(#(#fields),*) => {\n              #(#writes)*\n            }\n          }\n        }\n      }\n    })\n    .collect::<Vec<_>>();\n\n  let output = quote! {\n    match self {\n      #(#variants),*\n    }\n  };\n\n  output.into()\n}\n\nfn is_option(ty: &Type) -> bool {\n  matches!(&ty, Type::Path(p) if p.qself.is_none() && p.path.segments.iter().next().unwrap().ident == \"Option\")\n}\n"
  },
  {
    "path": "derive/src/visit.rs",
    "content": "use std::collections::HashSet;\n\nuse proc_macro::{self, TokenStream};\nuse proc_macro2::{Span, TokenStream as TokenStream2};\nuse quote::quote;\nuse syn::{\n  parse::Parse, parse_macro_input, parse_quote, Attribute, Data, DataEnum, DeriveInput, Field, Fields,\n  GenericParam, Generics, Ident, Member, Token, Type, Visibility,\n};\n\npub fn derive_visit_children(input: TokenStream) -> TokenStream {\n  let DeriveInput {\n    ident,\n    data,\n    generics,\n    attrs,\n    ..\n  } = parse_macro_input!(input);\n\n  let options: Vec<VisitOptions> = attrs\n    .iter()\n    .filter_map(|attr| {\n      if attr.path.is_ident(\"visit\") {\n        let opts: VisitOptions = attr.parse_args().unwrap();\n        Some(opts)\n      } else {\n        None\n      }\n    })\n    .collect();\n\n  let visit_types = if let Some(attr) = attrs.iter().find(|attr| attr.path.is_ident(\"visit_types\")) {\n    let types: VisitTypes = attr.parse_args().unwrap();\n    let types = types.types;\n    Some(quote! { crate::visit_types!(#(#types)|*) })\n  } else {\n    None\n  };\n\n  if options.is_empty() {\n    derive(&ident, &data, &generics, None, visit_types)\n  } else {\n    options\n      .into_iter()\n      .map(|options| derive(&ident, &data, &generics, Some(options), visit_types.clone()))\n      .collect()\n  }\n}\n\nfn derive(\n  ident: &Ident,\n  data: &Data,\n  generics: &Generics,\n  options: Option<VisitOptions>,\n  visit_types: Option<TokenStream2>,\n) -> TokenStream {\n  let mut impl_generics = generics.clone();\n  let mut type_defs = quote! {};\n  let generics = if let Some(VisitOptions {\n    generic: Some(generic), ..\n  }) = &options\n  {\n    let mappings = generics\n      .type_params()\n      .zip(generic.type_params())\n      .map(|(a, b)| quote! { type #a = #b; });\n    type_defs = quote! { #(#mappings)* };\n    impl_generics.params.clear();\n    generic\n  } else {\n    &generics\n  };\n\n  if impl_generics.lifetimes().next().is_none() {\n    impl_generics.params.insert(0, parse_quote! { 'i })\n  }\n\n  let lifetime = impl_generics.lifetimes().next().unwrap().clone();\n  let t = impl_generics.type_params().find(|g| &g.ident.to_string() == \"R\");\n  let v = quote! { __V };\n  let t = if let Some(t) = t {\n    GenericParam::Type(t.ident.clone().into())\n  } else {\n    let t: GenericParam = parse_quote! { __T };\n    impl_generics\n      .params\n      .push(parse_quote! { #t: crate::visitor::Visit<#lifetime, __T, #v> });\n    t\n  };\n\n  impl_generics\n    .params\n    .push(parse_quote! { #v: ?Sized + crate::visitor::Visitor<#lifetime, #t> });\n\n  for ty in generics.type_params() {\n    let name = &ty.ident;\n    impl_generics.make_where_clause().predicates.push(parse_quote! {\n      #name: Visit<#lifetime, #t, #v>\n    })\n  }\n\n  let mut seen_types = HashSet::new();\n  let mut child_types = Vec::new();\n  let mut visit = Vec::new();\n  match data {\n    Data::Struct(s) => {\n      for (\n        index,\n        Field {\n          vis, ty, ident, attrs, ..\n        },\n      ) in s.fields.iter().enumerate()\n      {\n        if attrs.iter().any(|attr| attr.path.is_ident(\"skip_visit\")) {\n          continue;\n        }\n\n        if matches!(ty, Type::Reference(_)) || !matches!(vis, Visibility::Public(..)) {\n          continue;\n        }\n\n        if visit_types.is_none() && !seen_types.contains(ty) && !skip_type(attrs) {\n          seen_types.insert(ty.clone());\n          child_types.push(quote! {\n            <#ty as Visit<#lifetime, #t, #v>>::CHILD_TYPES.bits()\n          });\n        }\n\n        let name = ident\n          .as_ref()\n          .map_or_else(|| Member::Unnamed(index.into()), |ident| Member::Named(ident.clone()));\n        visit.push(quote! { self.#name.visit(visitor)?; })\n      }\n    }\n    Data::Enum(DataEnum { variants, .. }) => {\n      let variants = variants\n        .iter()\n        .map(|variant| {\n          let name = &variant.ident;\n          let mut field_names = Vec::new();\n          let mut visit_fields = Vec::new();\n          for (index, Field { ty, ident, attrs, .. }) in variant.fields.iter().enumerate() {\n            let name = ident.as_ref().map_or_else(\n              || Ident::new(&format!(\"_{}\", index), Span::call_site()),\n              |ident| ident.clone(),\n            );\n            field_names.push(name.clone());\n\n            if matches!(ty, Type::Reference(_)) {\n              continue;\n            }\n\n            if visit_types.is_none() && !seen_types.contains(ty) && !skip_type(attrs) && !skip_type(&variant.attrs)\n            {\n              seen_types.insert(ty.clone());\n              child_types.push(quote! {\n                <#ty as Visit<#lifetime, #t, #v>>::CHILD_TYPES.bits()\n              });\n            }\n\n            visit_fields.push(quote! { #name.visit(visitor)?; })\n          }\n\n          match variant.fields {\n            Fields::Unnamed(_) => {\n              quote! {\n                Self::#name(#(#field_names),*) => {\n                  #(#visit_fields)*\n                }\n              }\n            }\n            Fields::Named(_) => {\n              quote! {\n                Self::#name { #(#field_names),* } => {\n                  #(#visit_fields)*\n                }\n              }\n            }\n            Fields::Unit => quote! {},\n          }\n        })\n        .collect::<proc_macro2::TokenStream>();\n\n      visit.push(quote! {\n        match self {\n          #variants\n          _ => {}\n        }\n      })\n    }\n    _ => {}\n  }\n\n  if visit_types.is_none() && child_types.is_empty() {\n    child_types.push(quote! { crate::visitor::VisitTypes::empty().bits() });\n  }\n\n  let (_, ty_generics, _) = generics.split_for_impl();\n  let (impl_generics, _, where_clause) = impl_generics.split_for_impl();\n\n  let self_visit = if let Some(VisitOptions {\n    visit: Some(visit),\n    kind: Some(kind),\n    ..\n  }) = &options\n  {\n    child_types.push(quote! { crate::visitor::VisitTypes::#kind.bits() });\n\n    quote! {\n      fn visit(&mut self, visitor: &mut #v) -> Result<(), #v::Error> {\n        if visitor.visit_types().contains(crate::visitor::VisitTypes::#kind) {\n          visitor.#visit(self)\n        } else {\n          self.visit_children(visitor)\n        }\n      }\n    }\n  } else {\n    quote! {}\n  };\n\n  let child_types = visit_types.unwrap_or_else(|| {\n    quote! {\n      {\n        #type_defs\n        crate::visitor::VisitTypes::from_bits_retain(#(#child_types)|*)\n      }\n    }\n  });\n\n  let output = quote! {\n    impl #impl_generics Visit<#lifetime, #t, #v> for #ident #ty_generics #where_clause {\n      const CHILD_TYPES: crate::visitor::VisitTypes = #child_types;\n\n      #self_visit\n\n      fn visit_children(&mut self, visitor: &mut #v) -> Result<(), #v::Error> {\n        if !<Self as Visit<#lifetime, #t, #v>>::CHILD_TYPES.intersects(visitor.visit_types()) {\n          return Ok(())\n        }\n\n        #(#visit)*\n\n        Ok(())\n      }\n    }\n  };\n\n  output.into()\n}\n\nfn skip_type(attrs: &Vec<Attribute>) -> bool {\n  attrs.iter().any(|attr| attr.path.is_ident(\"skip_type\"))\n}\n\nstruct VisitOptions {\n  visit: Option<Ident>,\n  kind: Option<Ident>,\n  generic: Option<Generics>,\n}\n\nimpl Parse for VisitOptions {\n  fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {\n    let (visit, kind, comma) = if input.peek(Ident) {\n      let visit: Ident = input.parse()?;\n      let _: Token![,] = input.parse()?;\n      let kind: Ident = input.parse()?;\n      let comma: Result<Token![,], _> = input.parse();\n      (Some(visit), Some(kind), comma.is_ok())\n    } else {\n      (None, None, true)\n    };\n    let generic: Option<Generics> = if comma { Some(input.parse()?) } else { None };\n    Ok(Self { visit, kind, generic })\n  }\n}\n\nstruct VisitTypes {\n  types: Vec<Ident>,\n}\n\nimpl Parse for VisitTypes {\n  fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {\n    let first: Ident = input.parse()?;\n    let mut types = vec![first];\n    while input.parse::<Token![|]>().is_ok() {\n      let id: Ident = input.parse()?;\n      types.push(id);\n    }\n    Ok(Self { types })\n  }\n}\n"
  },
  {
    "path": "examples/custom_at_rule.rs",
    "content": "use std::{collections::HashMap, convert::Infallible};\n\nuse cssparser::*;\nuse lightningcss::{\n  declaration::DeclarationBlock,\n  error::PrinterError,\n  printer::Printer,\n  properties::custom::{Token, TokenOrValue},\n  rules::{style::StyleRule, CssRule, CssRuleList, Location},\n  selector::{Component, Selector},\n  stylesheet::{ParserOptions, PrinterOptions, StyleSheet},\n  targets::Browsers,\n  traits::{AtRuleParser, ToCss},\n  values::{\n    color::{CssColor, RGBA},\n    length::LengthValue,\n  },\n  vendor_prefix::VendorPrefix,\n  visit_types,\n  visitor::{Visit, VisitTypes, Visitor},\n};\n\nfn main() {\n  let args: Vec<String> = std::env::args().collect();\n  let source = std::fs::read_to_string(&args[1]).unwrap();\n  let opts = ParserOptions {\n    filename: args[1].clone(),\n    ..Default::default()\n  };\n\n  let mut stylesheet = StyleSheet::parse_with(&source, opts, &mut TailwindAtRuleParser).unwrap();\n\n  println!(\"{:?}\", stylesheet);\n\n  let mut style_rules = HashMap::new();\n  stylesheet\n    .visit(&mut StyleRuleCollector {\n      rules: &mut style_rules,\n    })\n    .unwrap();\n  println!(\"{:?}\", style_rules);\n  stylesheet.visit(&mut ApplyVisitor { rules: &style_rules }).unwrap();\n\n  let result = stylesheet\n    .to_css(PrinterOptions {\n      targets: Browsers {\n        chrome: Some(100 << 16),\n        ..Browsers::default()\n      }\n      .into(),\n      ..PrinterOptions::default()\n    })\n    .unwrap();\n  println!(\"{}\", result.code);\n}\n\n/// An @tailwind directive.\n#[derive(Debug, Clone)]\nenum TailwindDirective {\n  Base,\n  Components,\n  Utilities,\n  Variants,\n}\n\n/// A custom at rule prelude.\nenum Prelude {\n  Tailwind(TailwindDirective),\n  Apply(Vec<String>),\n}\n\n/// A @tailwind rule.\n#[derive(Debug, Clone)]\nstruct TailwindRule {\n  directive: TailwindDirective,\n  loc: SourceLocation,\n}\n\n/// An @apply rule.\n#[derive(Debug, Clone)]\nstruct ApplyRule {\n  names: Vec<String>,\n  loc: SourceLocation,\n}\n\n/// A custom at rule.\n#[derive(Debug, Clone)]\nenum AtRule {\n  Tailwind(TailwindRule),\n  Apply(ApplyRule),\n}\n\n#[derive(Debug)]\nstruct TailwindAtRuleParser;\nimpl<'i> AtRuleParser<'i> for TailwindAtRuleParser {\n  type Prelude = Prelude;\n  type Error = Infallible;\n  type AtRule = AtRule;\n\n  fn parse_prelude<'t>(\n    &mut self,\n    name: CowRcStr<'i>,\n    input: &mut Parser<'i, 't>,\n    _options: &ParserOptions<'_, 'i>,\n  ) -> Result<Self::Prelude, ParseError<'i, Self::Error>> {\n    match_ignore_ascii_case! {&*name,\n      \"tailwind\" => {\n        let location = input.current_source_location();\n        let ident = input.expect_ident()?;\n        let directive = match_ignore_ascii_case! { &*ident,\n          \"base\" => TailwindDirective::Base,\n          \"components\" => TailwindDirective::Components,\n          \"utilities\" => TailwindDirective::Utilities,\n          \"variants\" => TailwindDirective::Variants,\n          _ => return Err(location.new_unexpected_token_error(\n            cssparser::Token::Ident(ident.clone())\n          ))\n        };\n        Ok(Prelude::Tailwind(directive))\n      },\n      \"apply\" => {\n        let mut names = Vec::new();\n        loop {\n          if let Ok(name) = input.try_parse(|input| input.expect_ident_cloned()) {\n            names.push(name.as_ref().into());\n          } else {\n            break\n          }\n        }\n\n        Ok(Prelude::Apply(names))\n      },\n      _ => Err(input.new_error(BasicParseErrorKind::AtRuleInvalid(name)))\n    }\n  }\n\n  fn rule_without_block(\n    &mut self,\n    prelude: Self::Prelude,\n    start: &ParserState,\n    _options: &ParserOptions<'_, 'i>,\n    _is_nested: bool,\n  ) -> Result<Self::AtRule, ()> {\n    let loc = start.source_location();\n    match prelude {\n      Prelude::Tailwind(directive) => Ok(AtRule::Tailwind(TailwindRule { directive, loc })),\n      Prelude::Apply(names) => Ok(AtRule::Apply(ApplyRule { names, loc })),\n    }\n  }\n}\n\nstruct StyleRuleCollector<'i, 'a> {\n  rules: &'a mut HashMap<String, DeclarationBlock<'i>>,\n}\n\nimpl<'i, 'a> Visitor<'i, AtRule> for StyleRuleCollector<'i, 'a> {\n  type Error = Infallible;\n\n  fn visit_types(&self) -> VisitTypes {\n    VisitTypes::RULES\n  }\n\n  fn visit_rule(&mut self, rule: &mut lightningcss::rules::CssRule<'i, AtRule>) -> Result<(), Self::Error> {\n    match rule {\n      CssRule::Style(rule) => {\n        for selector in rule.selectors.0.iter() {\n          if selector.len() != 1 {\n            continue; // TODO\n          }\n          for component in selector.iter_raw_match_order() {\n            match component {\n              Component::Class(name) => {\n                self.rules.insert(name.0.to_string(), rule.declarations.clone());\n              }\n              _ => {}\n            }\n          }\n        }\n      }\n      _ => {}\n    }\n\n    rule.visit_children(self)\n  }\n}\n\nstruct ApplyVisitor<'a, 'i> {\n  rules: &'a HashMap<String, DeclarationBlock<'i>>,\n}\n\nimpl<'a, 'i> Visitor<'i, AtRule> for ApplyVisitor<'a, 'i> {\n  type Error = Infallible;\n\n  fn visit_types(&self) -> VisitTypes {\n    visit_types!(RULES | COLORS | LENGTHS | DASHED_IDENTS | SELECTORS | TOKENS)\n  }\n\n  fn visit_rule(&mut self, rule: &mut CssRule<'i, AtRule>) -> Result<(), Self::Error> {\n    // Replace @apply rule with nested style rule.\n    if let CssRule::Custom(AtRule::Apply(apply)) = rule {\n      let mut declarations = DeclarationBlock::new();\n      for name in &apply.names {\n        let Some(applied) = self.rules.get(name) else {\n          continue;\n        };\n        declarations\n          .important_declarations\n          .extend(applied.important_declarations.iter().cloned());\n        declarations.declarations.extend(applied.declarations.iter().cloned());\n      }\n      *rule = CssRule::Style(StyleRule {\n        selectors: Component::Nesting.into(),\n        vendor_prefix: VendorPrefix::None,\n        declarations,\n        rules: CssRuleList(vec![]),\n        loc: Location {\n          source_index: 0,\n          line: apply.loc.line,\n          column: apply.loc.column,\n        },\n      })\n    }\n\n    rule.visit_children(self)\n  }\n\n  fn visit_url(&mut self, url: &mut lightningcss::values::url::Url<'i>) -> Result<(), Self::Error> {\n    url.url = format!(\"https://mywebsite.com/{}\", url.url).into();\n    Ok(())\n  }\n\n  fn visit_color(&mut self, color: &mut lightningcss::values::color::CssColor) -> Result<(), Self::Error> {\n    *color = color.to_lab().unwrap();\n    Ok(())\n  }\n\n  fn visit_length(&mut self, length: &mut lightningcss::values::length::LengthValue) -> Result<(), Self::Error> {\n    match length {\n      LengthValue::Px(px) => *length = LengthValue::Rem(*px / 16.0),\n      _ => {}\n    }\n\n    Ok(())\n  }\n\n  fn visit_dashed_ident(\n    &mut self,\n    ident: &mut lightningcss::values::ident::DashedIdent,\n  ) -> Result<(), Self::Error> {\n    ident.0 = format!(\"--tw-{}\", &ident.0[2..]).into();\n    Ok(())\n  }\n\n  fn visit_selector(&mut self, selector: &mut Selector<'i>) -> Result<(), Self::Error> {\n    for c in selector.iter_mut_raw_match_order() {\n      match c {\n        Component::Class(c) => {\n          *c = format!(\"tw-{}\", c).into();\n        }\n        _ => {}\n      }\n    }\n\n    Ok(())\n  }\n\n  fn visit_token(&mut self, token: &mut TokenOrValue<'i>) -> Result<(), Self::Error> {\n    match token {\n      TokenOrValue::Function(f) if f.name == \"theme\" => match f.arguments.0.first() {\n        Some(TokenOrValue::Token(Token::String(s))) => match s.as_ref() {\n          \"blue-500\" => *token = TokenOrValue::Color(CssColor::RGBA(RGBA::new(0, 0, 255, 1.0))),\n          \"red-500\" => *token = TokenOrValue::Color(CssColor::RGBA(RGBA::new(255, 0, 0, 1.0))),\n          _ => {}\n        },\n        _ => {}\n      },\n      _ => {}\n    }\n\n    token.visit_children(self)\n  }\n}\n\n#[cfg(feature = \"visitor\")]\nimpl<'i, V: Visitor<'i, AtRule>> Visit<'i, AtRule, V> for AtRule {\n  const CHILD_TYPES: VisitTypes = VisitTypes::empty();\n\n  fn visit_children(&mut self, _: &mut V) -> Result<(), V::Error> {\n    Ok(())\n  }\n}\n\nimpl ToCss for AtRule {\n  fn to_css<W: std::fmt::Write>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError> {\n    match self {\n      AtRule::Tailwind(rule) => {\n        let _ = rule.loc; // TODO: source maps\n        let directive = match rule.directive {\n          TailwindDirective::Base => \"TAILWIND BASE HERE\",\n          TailwindDirective::Components => \"TAILWIND COMPONENTS HERE\",\n          TailwindDirective::Utilities => \"TAILWIND UTILITIES HERE\",\n          TailwindDirective::Variants => \"TAILWIND VARIANTS HERE\",\n        };\n        dest.write_str(directive)\n      }\n      AtRule::Apply(_) => Ok(()),\n    }\n  }\n}\n"
  },
  {
    "path": "examples/schema.rs",
    "content": "fn main() {\n  #[cfg(feature = \"jsonschema\")]\n  {\n    let schema = schemars::schema_for!(lightningcss::stylesheet::StyleSheet);\n    let output = serde_json::to_string_pretty(&schema).unwrap();\n    let _ = std::fs::write(\"node/ast.json\", output);\n  }\n}\n"
  },
  {
    "path": "examples/serialize.rs",
    "content": "fn main() {\n  parse();\n}\n\n#[cfg(feature = \"serde\")]\nfn parse() {\n  use lightningcss::stylesheet::{ParserOptions, StyleSheet};\n  use std::{env, fs};\n\n  let args: Vec<String> = env::args().collect();\n  let contents = fs::read_to_string(&args[1]).unwrap();\n  let stylesheet = StyleSheet::parse(\n    &contents,\n    ParserOptions {\n      filename: args[1].clone(),\n      ..ParserOptions::default()\n    },\n  )\n  .unwrap();\n  let json = serde_json::to_string(&stylesheet).unwrap();\n  println!(\"{}\", json);\n}\n\n#[cfg(not(feature = \"serde\"))]\nfn parse() {\n  panic!(\"serde feature is not enabled\")\n}\n"
  },
  {
    "path": "napi/Cargo.toml",
    "content": "[package]\nauthors = [\"Devon Govett <devongovett@gmail.com>\"]\nname = \"lightningcss-napi\"\nversion = \"0.4.8\"\ndescription = \"Node-API bindings for Lightning CSS\"\nlicense = \"MPL-2.0\"\nrepository = \"https://github.com/parcel-bundler/lightningcss\"\nedition = \"2021\"\n\n[features]\ndefault = []\nvisitor = [\"lightningcss/visitor\"]\nbundler = [\"dep:crossbeam-channel\", \"dep:rayon\"]\n\n[dependencies]\nserde = { version = \"1.0.201\", features = [\"derive\"] }\nserde-content = { version = \"0.1.2\", features = [\"serde\"] }\nserde_bytes = \"0.11.5\"\ncssparser = \"0.33.0\"\nlightningcss = { version = \"1.0.0-alpha.71\", path = \"../\", features = [\n  \"nodejs\",\n  \"serde\",\n] }\nparcel_sourcemap = { version = \"2.1.1\", features = [\"json\"] }\nserde-detach = \"0.0.1\"\nsmallvec = { version = \"1.7.0\", features = [\"union\"] }\nnapi = { version = \"2\", default-features = false, features = [\n  \"napi4\",\n  \"napi5\",\n  \"serde-json\",\n] }\ncrossbeam-channel = { version = \"0.5.6\", optional = true }\nrayon = { version = \"1.5.1\", optional = true }\n"
  },
  {
    "path": "napi/src/at_rule_parser.rs",
    "content": "use std::collections::HashMap;\n\nuse cssparser::*;\nuse lightningcss::{\n  declaration::DeclarationBlock,\n  error::ParserError,\n  rules::{CssRuleList, Location},\n  stylesheet::ParserOptions,\n  traits::{AtRuleParser, ToCss},\n  values::{\n    string::CowArcStr,\n    syntax::{ParsedComponent, SyntaxString},\n  },\n};\nuse serde::{Deserialize, Deserializer, Serialize};\n\n#[derive(Deserialize, Debug, Clone)]\npub struct CustomAtRuleConfig {\n  #[serde(default, deserialize_with = \"deserialize_prelude\")]\n  prelude: Option<SyntaxString>,\n  body: Option<CustomAtRuleBodyType>,\n}\n\nfn deserialize_prelude<'de, D>(deserializer: D) -> Result<Option<SyntaxString>, D::Error>\nwhere\n  D: Deserializer<'de>,\n{\n  let s = Option::<CowArcStr<'de>>::deserialize(deserializer)?;\n  if let Some(s) = s {\n    Ok(Some(\n      SyntaxString::parse_string(&s).map_err(|_| serde::de::Error::custom(\"invalid syntax string\"))?,\n    ))\n  } else {\n    Ok(None)\n  }\n}\n\n#[derive(Deserialize, Debug, Clone)]\n#[serde(rename_all = \"kebab-case\")]\nenum CustomAtRuleBodyType {\n  DeclarationList,\n  RuleList,\n  StyleBlock,\n}\n\npub struct Prelude<'i> {\n  name: CowArcStr<'i>,\n  prelude: Option<ParsedComponent<'i>>,\n}\n\n#[derive(Serialize, Deserialize, Clone)]\npub struct AtRule<'i> {\n  #[serde(borrow)]\n  pub name: CowArcStr<'i>,\n  pub prelude: Option<ParsedComponent<'i>>,\n  pub body: Option<AtRuleBody<'i>>,\n  pub loc: Location,\n}\n\n#[derive(Serialize, Deserialize, Clone)]\n#[serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")]\npub enum AtRuleBody<'i> {\n  #[serde(borrow)]\n  DeclarationList(DeclarationBlock<'i>),\n  RuleList(CssRuleList<'i, AtRule<'i>>),\n}\n\n#[derive(Clone)]\npub struct CustomAtRuleParser {\n  pub configs: HashMap<String, CustomAtRuleConfig>,\n}\n\nimpl<'i> AtRuleParser<'i> for CustomAtRuleParser {\n  type Prelude = Prelude<'i>;\n  type Error = ParserError<'i>;\n  type AtRule = AtRule<'i>;\n\n  fn parse_prelude<'t>(\n    &mut self,\n    name: CowRcStr<'i>,\n    input: &mut Parser<'i, 't>,\n    _options: &ParserOptions<'_, 'i>,\n  ) -> Result<Self::Prelude, ParseError<'i, Self::Error>> {\n    if let Some(config) = self.configs.get(name.as_ref()) {\n      let prelude = if let Some(prelude) = &config.prelude {\n        Some(prelude.parse_value(input)?)\n      } else {\n        None\n      };\n      Ok(Prelude {\n        name: name.into(),\n        prelude,\n      })\n    } else {\n      Err(input.new_error(BasicParseErrorKind::AtRuleInvalid(name)))\n    }\n  }\n\n  fn parse_block<'t>(\n    &mut self,\n    prelude: Self::Prelude,\n    start: &ParserState,\n    input: &mut Parser<'i, 't>,\n    options: &ParserOptions<'_, 'i>,\n    is_nested: bool,\n  ) -> Result<Self::AtRule, ParseError<'i, Self::Error>> {\n    let config = self.configs.get(prelude.name.as_ref()).unwrap();\n    let body = if let Some(body) = &config.body {\n      match body {\n        CustomAtRuleBodyType::DeclarationList => {\n          Some(AtRuleBody::DeclarationList(DeclarationBlock::parse(input, options)?))\n        }\n        CustomAtRuleBodyType::RuleList => {\n          Some(AtRuleBody::RuleList(CssRuleList::parse_with(input, options, self)?))\n        }\n        CustomAtRuleBodyType::StyleBlock => Some(AtRuleBody::RuleList(CssRuleList::parse_style_block_with(\n          input, options, self, is_nested,\n        )?)),\n      }\n    } else {\n      return Err(input.new_error(BasicParseErrorKind::AtRuleBodyInvalid));\n    };\n\n    let loc = start.source_location();\n    Ok(AtRule {\n      name: prelude.name,\n      prelude: prelude.prelude,\n      body,\n      loc: Location {\n        source_index: options.source_index,\n        line: loc.line,\n        column: loc.column,\n      },\n    })\n  }\n\n  fn rule_without_block(\n    &mut self,\n    prelude: Self::Prelude,\n    start: &ParserState,\n    options: &ParserOptions<'_, 'i>,\n    _is_nested: bool,\n  ) -> Result<Self::AtRule, ()> {\n    let config = self.configs.get(prelude.name.as_ref()).unwrap();\n    if config.body.is_some() {\n      return Err(());\n    }\n\n    let loc = start.source_location();\n    Ok(AtRule {\n      name: prelude.name,\n      prelude: prelude.prelude,\n      body: None,\n      loc: Location {\n        source_index: options.source_index,\n        line: loc.line,\n        column: loc.column,\n      },\n    })\n  }\n}\n\nimpl<'i> ToCss for AtRule<'i> {\n  fn to_css<W>(\n    &self,\n    dest: &mut lightningcss::printer::Printer<W>,\n  ) -> Result<(), lightningcss::error::PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    dest.write_char('@')?;\n    serialize_identifier(&self.name, dest)?;\n    if let Some(prelude) = &self.prelude {\n      dest.write_char(' ')?;\n      prelude.to_css(dest)?;\n    }\n\n    if let Some(body) = &self.body {\n      match body {\n        AtRuleBody::DeclarationList(decls) => {\n          decls.to_css_block(dest)?;\n        }\n        AtRuleBody::RuleList(rules) => {\n          dest.whitespace()?;\n          dest.write_char('{')?;\n          dest.indent();\n          dest.newline()?;\n          rules.to_css(dest)?;\n          dest.dedent();\n          dest.newline()?;\n          dest.write_char('}')?;\n        }\n      }\n    }\n\n    Ok(())\n  }\n}\n\n#[cfg(feature = \"visitor\")]\nuse lightningcss::visitor::{Visit, VisitTypes, Visitor};\n\n#[cfg(feature = \"visitor\")]\nimpl<'i, V: Visitor<'i, AtRule<'i>>> Visit<'i, AtRule<'i>, V> for AtRule<'i> {\n  const CHILD_TYPES: VisitTypes = VisitTypes::empty();\n\n  fn visit_children(&mut self, visitor: &mut V) -> Result<(), V::Error> {\n    self.prelude.visit(visitor)?;\n    match &mut self.body {\n      Some(AtRuleBody::DeclarationList(decls)) => decls.visit(visitor),\n      Some(AtRuleBody::RuleList(rules)) => rules.visit(visitor),\n      None => Ok(()),\n    }\n  }\n}\n"
  },
  {
    "path": "napi/src/lib.rs",
    "content": "#[cfg(feature = \"bundler\")]\nuse at_rule_parser::AtRule;\nuse at_rule_parser::{CustomAtRuleConfig, CustomAtRuleParser};\nuse lightningcss::bundler::BundleErrorKind;\n#[cfg(feature = \"bundler\")]\nuse lightningcss::bundler::{Bundler, SourceProvider};\nuse lightningcss::css_modules::{CssModuleExports, CssModuleReferences, PatternParseError};\nuse lightningcss::dependencies::{Dependency, DependencyOptions};\nuse lightningcss::error::{Error, ErrorLocation, MinifyErrorKind, ParserError, PrinterErrorKind};\nuse lightningcss::stylesheet::{\n  MinifyOptions, ParserFlags, ParserOptions, PrinterOptions, PseudoClasses, StyleAttribute, StyleSheet,\n};\nuse lightningcss::targets::{Browsers, Features, Targets};\nuse napi::bindgen_prelude::{FromNapiValue, ToNapiValue};\nuse napi::{CallContext, Env, JsObject, JsUnknown};\nuse parcel_sourcemap::SourceMap;\nuse serde::{Deserialize, Serialize};\nuse std::collections::{HashMap, HashSet};\nuse std::sync::{Arc, RwLock};\n\nmod at_rule_parser;\n#[cfg(feature = \"bundler\")]\n#[cfg(not(target_arch = \"wasm32\"))]\nmod threadsafe_function;\n#[cfg(feature = \"visitor\")]\nmod transformer;\nmod utils;\n\n#[cfg(feature = \"visitor\")]\nuse transformer::JsVisitor;\n\n#[cfg(not(feature = \"visitor\"))]\nstruct JsVisitor;\n\n#[cfg(feature = \"visitor\")]\nuse lightningcss::visitor::Visit;\n\nuse utils::get_named_property;\n\n#[derive(Serialize)]\n#[serde(rename_all = \"camelCase\")]\nstruct TransformResult<'i> {\n  #[serde(with = \"serde_bytes\")]\n  code: Vec<u8>,\n  #[serde(with = \"serde_bytes\")]\n  map: Option<Vec<u8>>,\n  exports: Option<CssModuleExports>,\n  references: Option<CssModuleReferences>,\n  dependencies: Option<Vec<Dependency>>,\n  warnings: Vec<Warning<'i>>,\n}\n\nimpl<'i> TransformResult<'i> {\n  fn into_js(self, env: Env) -> napi::Result<JsUnknown> {\n    // Manually construct buffers so we avoid a copy and work around\n    // https://github.com/napi-rs/napi-rs/issues/1124.\n    let mut obj = env.create_object()?;\n    let buf = env.create_buffer_with_data(self.code)?;\n    obj.set_named_property(\"code\", buf.into_raw())?;\n    obj.set_named_property(\n      \"map\",\n      if let Some(map) = self.map {\n        let buf = env.create_buffer_with_data(map)?;\n        buf.into_raw().into_unknown()\n      } else {\n        env.get_null()?.into_unknown()\n      },\n    )?;\n    obj.set_named_property(\"exports\", env.to_js_value(&self.exports)?)?;\n    obj.set_named_property(\"references\", env.to_js_value(&self.references)?)?;\n    obj.set_named_property(\"dependencies\", env.to_js_value(&self.dependencies)?)?;\n    obj.set_named_property(\"warnings\", env.to_js_value(&self.warnings)?)?;\n    Ok(obj.into_unknown())\n  }\n}\n\n#[cfg(feature = \"visitor\")]\nfn get_visitor(env: Env, opts: &JsObject) -> Option<JsVisitor> {\n  if let Ok(visitor) = get_named_property::<JsObject>(opts, \"visitor\") {\n    Some(JsVisitor::new(env, visitor))\n  } else {\n    None\n  }\n}\n\n#[cfg(not(feature = \"visitor\"))]\nfn get_visitor(_env: Env, _opts: &JsObject) -> Option<JsVisitor> {\n  None\n}\n\npub fn transform(ctx: CallContext) -> napi::Result<JsUnknown> {\n  let opts = ctx.get::<JsObject>(0)?;\n  let mut visitor = get_visitor(*ctx.env, &opts);\n\n  let config: Config = ctx.env.from_js_value(opts)?;\n  let code = unsafe { std::str::from_utf8_unchecked(&config.code) };\n  let res = compile(code, &config, &mut visitor);\n\n  match res {\n    Ok(res) => res.into_js(*ctx.env),\n    Err(err) => Err(err.into_js_error(*ctx.env, Some(code))?),\n  }\n}\n\npub fn transform_style_attribute(ctx: CallContext) -> napi::Result<JsUnknown> {\n  let opts = ctx.get::<JsObject>(0)?;\n  let mut visitor = get_visitor(*ctx.env, &opts);\n\n  let config: AttrConfig = ctx.env.from_js_value(opts)?;\n  let code = unsafe { std::str::from_utf8_unchecked(&config.code) };\n  let res = compile_attr(code, &config, &mut visitor);\n\n  match res {\n    Ok(res) => res.into_js(ctx),\n    Err(err) => Err(err.into_js_error(*ctx.env, Some(code))?),\n  }\n}\n\n#[cfg(feature = \"bundler\")]\n#[cfg(not(target_arch = \"wasm32\"))]\nmod bundle {\n  use super::*;\n  use crossbeam_channel::{self, Receiver, Sender};\n  use lightningcss::bundler::{FileProvider, ResolveResult};\n  use napi::{Env, JsBoolean, JsFunction, JsString, NapiRaw};\n  use std::path::{Path, PathBuf};\n  use std::str::FromStr;\n  use std::sync::Mutex;\n  use threadsafe_function::{ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode};\n\n  pub fn bundle(ctx: CallContext) -> napi::Result<JsUnknown> {\n    let opts = ctx.get::<JsObject>(0)?;\n    let mut visitor = get_visitor(*ctx.env, &opts);\n\n    let config: BundleConfig = ctx.env.from_js_value(opts)?;\n    let fs = FileProvider::new();\n\n    // This is pretty silly, but works around a rust limitation that you cannot\n    // explicitly annotate lifetime bounds on closures.\n    fn annotate<'i, 'o, F>(f: F) -> F\n    where\n      F: FnOnce(&mut StyleSheet<'i, 'o, AtRule<'i>>) -> napi::Result<()>,\n    {\n      f\n    }\n\n    let res = compile_bundle(\n      &fs,\n      &config,\n      visitor.as_mut().map(|visitor| annotate(|stylesheet| stylesheet.visit(visitor))),\n    );\n\n    match res {\n      Ok(res) => res.into_js(*ctx.env),\n      Err(err) => Err(err.into_js_error(*ctx.env, None)?),\n    }\n  }\n\n  // A SourceProvider which calls JavaScript functions to resolve and read files.\n  struct JsSourceProvider {\n    resolve: Option<ThreadsafeFunction<ResolveMessage>>,\n    read: Option<ThreadsafeFunction<ReadMessage>>,\n    inputs: Mutex<Vec<*mut String>>,\n  }\n\n  unsafe impl Sync for JsSourceProvider {}\n  unsafe impl Send for JsSourceProvider {}\n\n  // Allocate a single channel per thread to communicate with the JS thread.\n  thread_local! {\n    static CHANNEL: (Sender<napi::Result<String>>, Receiver<napi::Result<String>>) = crossbeam_channel::unbounded();\n    static RESOLVER_CHANNEL: (Sender<napi::Result<ResolveResult>>, Receiver<napi::Result<ResolveResult>>) = crossbeam_channel::unbounded();\n  }\n\n  impl SourceProvider for JsSourceProvider {\n    type Error = napi::Error;\n\n    fn read<'a>(&'a self, file: &Path) -> Result<&'a str, Self::Error> {\n      let source = if let Some(read) = &self.read {\n        CHANNEL.with(|channel| {\n          let message = ReadMessage {\n            file: file.to_str().unwrap().to_owned(),\n            tx: channel.0.clone(),\n          };\n\n          read.call(message, ThreadsafeFunctionCallMode::Blocking);\n          channel.1.recv().unwrap()\n        })\n      } else {\n        Ok(std::fs::read_to_string(file)?)\n      };\n\n      match source {\n        Ok(source) => {\n          // cache the result\n          let ptr = Box::into_raw(Box::new(source));\n          self.inputs.lock().unwrap().push(ptr);\n          // SAFETY: this is safe because the pointer is not dropped\n          // until the JsSourceProvider is, and we never remove from the\n          // list of pointers stored in the vector.\n          Ok(unsafe { &*ptr })\n        }\n        Err(e) => Err(e),\n      }\n    }\n\n    fn resolve(&self, specifier: &str, originating_file: &Path) -> Result<ResolveResult, Self::Error> {\n      if let Some(resolve) = &self.resolve {\n        return RESOLVER_CHANNEL.with(|channel| {\n          let message = ResolveMessage {\n            specifier: specifier.to_owned(),\n            originating_file: originating_file.to_str().unwrap().to_owned(),\n            tx: channel.0.clone(),\n          };\n\n          resolve.call(message, ThreadsafeFunctionCallMode::Blocking);\n          channel.1.recv().unwrap()\n        });\n      }\n\n      Ok(originating_file.with_file_name(specifier).into())\n    }\n  }\n\n  struct ResolveMessage {\n    specifier: String,\n    originating_file: String,\n    tx: Sender<napi::Result<ResolveResult>>,\n  }\n\n  struct ReadMessage {\n    file: String,\n    tx: Sender<napi::Result<String>>,\n  }\n\n  struct VisitMessage {\n    stylesheet: &'static mut StyleSheet<'static, 'static, AtRule<'static>>,\n    tx: Sender<napi::Result<String>>,\n  }\n\n  fn await_promise<T, Cb>(env: Env, result: JsUnknown, tx: Sender<napi::Result<T>>, parse: Cb) -> napi::Result<()>\n  where\n    T: 'static,\n    Cb: 'static + Fn(JsUnknown) -> Result<T, napi::Error>,\n  {\n    // If the result is a promise, wait for it to resolve, and send the result to the channel.\n    // Otherwise, send the result immediately.\n    if result.is_promise()? {\n      let result: JsObject = result.try_into()?;\n      let then: JsFunction = get_named_property(&result, \"then\")?;\n      let tx2 = tx.clone();\n      let cb = env.create_function_from_closure(\"callback\", move |ctx| {\n        let res = parse(ctx.get::<JsUnknown>(0)?)?;\n        tx.send(Ok(res)).unwrap();\n        ctx.env.get_undefined()\n      })?;\n      let eb = env.create_function_from_closure(\"error_callback\", move |ctx| {\n        let res = ctx.get::<JsUnknown>(0)?;\n        tx2.send(Err(napi::Error::from(res))).unwrap();\n        ctx.env.get_undefined()\n      })?;\n      then.call(Some(&result), &[cb, eb])?;\n    } else {\n      let result = parse(result)?;\n      tx.send(Ok(result)).unwrap();\n    }\n\n    Ok(())\n  }\n\n  fn resolve_on_js_thread(ctx: ThreadSafeCallContext<ResolveMessage>) -> napi::Result<()> {\n    let specifier = ctx.env.create_string(&ctx.value.specifier)?;\n    let originating_file = ctx.env.create_string(&ctx.value.originating_file)?;\n    let result = ctx.callback.unwrap().call(None, &[specifier, originating_file])?;\n    await_promise(ctx.env, result, ctx.value.tx, move |unknown| {\n      ctx.env.from_js_value(unknown)\n    })\n  }\n\n  fn handle_error<T>(tx: Sender<napi::Result<T>>, res: napi::Result<()>) -> napi::Result<()> {\n    match res {\n      Ok(_) => Ok(()),\n      Err(e) => {\n        tx.send(Err(e)).expect(\"send error\");\n        Ok(())\n      }\n    }\n  }\n\n  fn resolve_on_js_thread_wrapper(ctx: ThreadSafeCallContext<ResolveMessage>) -> napi::Result<()> {\n    let tx = ctx.value.tx.clone();\n    handle_error(tx, resolve_on_js_thread(ctx))\n  }\n\n  fn read_on_js_thread(ctx: ThreadSafeCallContext<ReadMessage>) -> napi::Result<()> {\n    let file = ctx.env.create_string(&ctx.value.file)?;\n    let result = ctx.callback.unwrap().call(None, &[file])?;\n    await_promise(ctx.env, result, ctx.value.tx, |unknown| {\n      JsString::try_from(unknown)?.into_utf8()?.into_owned()\n    })\n  }\n\n  fn read_on_js_thread_wrapper(ctx: ThreadSafeCallContext<ReadMessage>) -> napi::Result<()> {\n    let tx = ctx.value.tx.clone();\n    handle_error(tx, read_on_js_thread(ctx))\n  }\n\n  pub fn bundle_async(ctx: CallContext) -> napi::Result<JsObject> {\n    let opts = ctx.get::<JsObject>(0)?;\n    let visitor = get_visitor(*ctx.env, &opts);\n\n    let config: BundleConfig = ctx.env.from_js_value(&opts)?;\n\n    if let Ok(resolver) = get_named_property::<JsObject>(&opts, \"resolver\") {\n      let read = if resolver.has_named_property(\"read\")? {\n        let read = get_named_property::<JsFunction>(&resolver, \"read\")?;\n        Some(ThreadsafeFunction::create(\n          ctx.env.raw(),\n          unsafe { read.raw() },\n          0,\n          read_on_js_thread_wrapper,\n        )?)\n      } else {\n        None\n      };\n\n      let resolve = if resolver.has_named_property(\"resolve\")? {\n        let resolve = get_named_property::<JsFunction>(&resolver, \"resolve\")?;\n        Some(ThreadsafeFunction::create(\n          ctx.env.raw(),\n          unsafe { resolve.raw() },\n          0,\n          resolve_on_js_thread_wrapper,\n        )?)\n      } else {\n        None\n      };\n\n      let provider = JsSourceProvider {\n        resolve,\n        read,\n        inputs: Mutex::new(Vec::new()),\n      };\n\n      run_bundle_task(provider, config, visitor, *ctx.env)\n    } else {\n      let provider = FileProvider::new();\n      run_bundle_task(provider, config, visitor, *ctx.env)\n    }\n  }\n\n  // Runs bundling on a background thread managed by rayon. This is similar to AsyncTask from napi-rs, however,\n  // because we call back into the JS thread, which might call other tasks in the node threadpool (e.g. fs.readFile),\n  // we may end up deadlocking if the number of rayon threads exceeds node's threadpool size. Therefore, we must\n  // run bundling from a thread not managed by Node.\n  fn run_bundle_task<P: 'static + SourceProvider>(\n    provider: P,\n    config: BundleConfig,\n    visitor: Option<JsVisitor>,\n    env: Env,\n  ) -> napi::Result<JsObject>\n  where\n    P::Error: IntoJsError,\n  {\n    let (deferred, promise) = env.create_deferred()?;\n\n    let tsfn = if let Some(mut visitor) = visitor {\n      Some(ThreadsafeFunction::create(\n        env.raw(),\n        std::ptr::null_mut(),\n        0,\n        move |ctx: ThreadSafeCallContext<VisitMessage>| {\n          if let Err(err) = ctx.value.stylesheet.visit(&mut visitor) {\n            ctx.value.tx.send(Err(err)).expect(\"send error\");\n            return Ok(());\n          }\n          ctx.value.tx.send(Ok(Default::default())).expect(\"send error\");\n          Ok(())\n        },\n      )?)\n    } else {\n      None\n    };\n\n    // Run bundling task in rayon threadpool.\n    rayon::spawn(move || {\n      let res = compile_bundle(\n        unsafe { std::mem::transmute::<&'_ P, &'static P>(&provider) },\n        &config,\n        tsfn.map(move |tsfn| {\n          move |stylesheet: &mut StyleSheet<AtRule>| {\n            CHANNEL.with(|channel| {\n              let message = VisitMessage {\n                // SAFETY: we immediately lock the thread until we get a response,\n                // so stylesheet cannot be dropped in that time.\n                stylesheet: unsafe {\n                  std::mem::transmute::<\n                    &'_ mut StyleSheet<'_, '_, AtRule>,\n                    &'static mut StyleSheet<'static, 'static, AtRule>,\n                  >(stylesheet)\n                },\n                tx: channel.0.clone(),\n              };\n\n              tsfn.call(message, ThreadsafeFunctionCallMode::Blocking);\n              channel.1.recv().expect(\"recv error\").map(|_| ())\n            })\n          }\n        }),\n      );\n\n      deferred.resolve(move |env| match res {\n        Ok(v) => v.into_js(env),\n        Err(err) => Err(err.into_js_error(env, None)?),\n      });\n    });\n\n    Ok(promise)\n  }\n}\n\n#[cfg(feature = \"bundler\")]\n#[cfg(target_arch = \"wasm32\")]\nmod bundle {\n  use super::*;\n  use lightningcss::bundler::ResolveResult;\n  use napi::{Env, JsFunction, JsString, NapiRaw, NapiValue, Ref};\n  use std::cell::UnsafeCell;\n  use std::path::Path;\n\n  pub fn bundle(ctx: CallContext) -> napi::Result<JsUnknown> {\n    let opts = ctx.get::<JsObject>(0)?;\n    let mut visitor = get_visitor(*ctx.env, &opts);\n\n    let resolver = get_named_property::<JsObject>(&opts, \"resolver\")?;\n    let read = get_named_property::<JsFunction>(&resolver, \"read\")?;\n    let resolve = if resolver.has_named_property(\"resolve\")? {\n      let resolve = get_named_property::<JsFunction>(&resolver, \"resolve\")?;\n      Some(ctx.env.create_reference(resolve)?)\n    } else {\n      None\n    };\n    let config: BundleConfig = ctx.env.from_js_value(opts)?;\n\n    let provider = JsSourceProvider {\n      env: ctx.env.clone(),\n      resolve,\n      read: ctx.env.create_reference(read)?,\n      inputs: UnsafeCell::new(Vec::new()),\n    };\n\n    // This is pretty silly, but works around a rust limitation that you cannot\n    // explicitly annotate lifetime bounds on closures.\n    fn annotate<'i, 'o, F>(f: F) -> F\n    where\n      F: FnOnce(&mut StyleSheet<'i, 'o, AtRule<'i>>) -> napi::Result<()>,\n    {\n      f\n    }\n\n    let res = compile_bundle(\n      &provider,\n      &config,\n      visitor.as_mut().map(|visitor| annotate(|stylesheet| stylesheet.visit(visitor))),\n    );\n\n    match res {\n      Ok(res) => res.into_js(*ctx.env),\n      Err(err) => Err(err.into_js_error(*ctx.env, None)?),\n    }\n  }\n\n  struct JsSourceProvider {\n    env: Env,\n    resolve: Option<Ref<()>>,\n    read: Ref<()>,\n    inputs: UnsafeCell<Vec<*mut String>>,\n  }\n\n  impl Drop for JsSourceProvider {\n    fn drop(&mut self) {\n      if let Some(resolve) = &mut self.resolve {\n        drop(resolve.unref(self.env));\n      }\n      drop(self.read.unref(self.env));\n    }\n  }\n\n  unsafe impl Sync for JsSourceProvider {}\n  unsafe impl Send for JsSourceProvider {}\n\n  // This relies on Binaryen's Asyncify transform to allow Rust to call async JS functions from sync code.\n  // See the comments in async.mjs for more details about how this works.\n  extern \"C\" {\n    fn await_promise_sync(\n      promise: napi::sys::napi_value,\n      result: *mut napi::sys::napi_value,\n      error: *mut napi::sys::napi_value,\n    );\n  }\n\n  fn get_result(env: Env, mut value: JsUnknown) -> napi::Result<JsUnknown> {\n    if value.is_promise()? {\n      let mut result = std::ptr::null_mut();\n      let mut error = std::ptr::null_mut();\n      unsafe { await_promise_sync(value.raw(), &mut result, &mut error) };\n      if !error.is_null() {\n        let error = unsafe { JsUnknown::from_raw(env.raw(), error)? };\n        return Err(napi::Error::from(error));\n      }\n      if result.is_null() {\n        return Err(napi::Error::new(napi::Status::GenericFailure, \"No result\".to_string()));\n      }\n\n      value = unsafe { JsUnknown::from_raw(env.raw(), result)? };\n    }\n\n    Ok(value)\n  }\n\n  impl SourceProvider for JsSourceProvider {\n    type Error = napi::Error;\n\n    fn read<'a>(&'a self, file: &Path) -> Result<&'a str, Self::Error> {\n      let read: JsFunction = self.env.get_reference_value_unchecked(&self.read)?;\n      let file = self.env.create_string(file.to_str().unwrap())?;\n      let source: JsUnknown = read.call(None, &[file])?;\n      let source = get_result(self.env, source)?;\n      let source: JsString = source.try_into()?;\n      let source = source.into_utf8()?.into_owned()?;\n\n      // cache the result\n      let ptr = Box::into_raw(Box::new(source));\n      let inputs = unsafe { &mut *self.inputs.get() };\n      inputs.push(ptr);\n      // SAFETY: this is safe because the pointer is not dropped\n      // until the JsSourceProvider is, and we never remove from the\n      // list of pointers stored in the vector.\n      Ok(unsafe { &*ptr })\n    }\n\n    fn resolve(&self, specifier: &str, originating_file: &Path) -> Result<ResolveResult, Self::Error> {\n      if let Some(resolve) = &self.resolve {\n        let resolve: JsFunction = self.env.get_reference_value_unchecked(resolve)?;\n        let specifier = self.env.create_string(specifier)?;\n        let originating_file = self.env.create_string(originating_file.to_str().unwrap())?;\n        let result: JsUnknown = resolve.call(None, &[specifier, originating_file])?;\n        let result = get_result(self.env, result)?;\n        let result = self.env.from_js_value(result)?;\n        Ok(result)\n      } else {\n        Ok(ResolveResult::File(originating_file.with_file_name(specifier)))\n      }\n    }\n  }\n}\n\n#[cfg(feature = \"bundler\")]\npub use bundle::*;\n\n// ---------------------------------------------\n\n#[derive(Debug, Deserialize)]\n#[serde(rename_all = \"camelCase\")]\nstruct Config {\n  pub filename: Option<String>,\n  pub project_root: Option<String>,\n  #[serde(with = \"serde_bytes\")]\n  pub code: Vec<u8>,\n  pub targets: Option<Browsers>,\n  #[serde(default)]\n  pub include: u32,\n  #[serde(default)]\n  pub exclude: u32,\n  pub minify: Option<bool>,\n  pub source_map: Option<bool>,\n  pub input_source_map: Option<String>,\n  pub drafts: Option<Drafts>,\n  pub non_standard: Option<NonStandard>,\n  pub css_modules: Option<CssModulesOption>,\n  pub analyze_dependencies: Option<AnalyzeDependenciesOption>,\n  pub pseudo_classes: Option<OwnedPseudoClasses>,\n  pub unused_symbols: Option<HashSet<String>>,\n  pub error_recovery: Option<bool>,\n  pub custom_at_rules: Option<HashMap<String, CustomAtRuleConfig>>,\n}\n\n#[derive(Debug, Deserialize)]\n#[serde(untagged)]\nenum AnalyzeDependenciesOption {\n  Bool(bool),\n  Config(AnalyzeDependenciesConfig),\n}\n\n#[derive(Debug, Deserialize)]\n#[serde(rename_all = \"camelCase\")]\nstruct AnalyzeDependenciesConfig {\n  preserve_imports: bool,\n}\n\n#[derive(Debug, Deserialize)]\n#[serde(untagged)]\nenum CssModulesOption {\n  Bool(bool),\n  Config(CssModulesConfig),\n}\n\n#[derive(Debug, Deserialize)]\n#[serde(rename_all = \"camelCase\")]\nstruct CssModulesConfig {\n  pattern: Option<String>,\n  dashed_idents: Option<bool>,\n  animation: Option<bool>,\n  container: Option<bool>,\n  grid: Option<bool>,\n  custom_idents: Option<bool>,\n  pure: Option<bool>,\n}\n\n#[cfg(feature = \"bundler\")]\n#[derive(Debug, Deserialize)]\n#[serde(rename_all = \"camelCase\")]\nstruct BundleConfig {\n  pub filename: String,\n  pub project_root: Option<String>,\n  pub targets: Option<Browsers>,\n  #[serde(default)]\n  pub include: u32,\n  #[serde(default)]\n  pub exclude: u32,\n  pub minify: Option<bool>,\n  pub source_map: Option<bool>,\n  pub drafts: Option<Drafts>,\n  pub non_standard: Option<NonStandard>,\n  pub css_modules: Option<CssModulesOption>,\n  pub analyze_dependencies: Option<AnalyzeDependenciesOption>,\n  pub pseudo_classes: Option<OwnedPseudoClasses>,\n  pub unused_symbols: Option<HashSet<String>>,\n  pub error_recovery: Option<bool>,\n  pub custom_at_rules: Option<HashMap<String, CustomAtRuleConfig>>,\n}\n\n#[derive(Debug, Deserialize)]\n#[serde(rename_all = \"camelCase\")]\nstruct OwnedPseudoClasses {\n  pub hover: Option<String>,\n  pub active: Option<String>,\n  pub focus: Option<String>,\n  pub focus_visible: Option<String>,\n  pub focus_within: Option<String>,\n}\n\nimpl<'a> Into<PseudoClasses<'a>> for &'a OwnedPseudoClasses {\n  fn into(self) -> PseudoClasses<'a> {\n    PseudoClasses {\n      hover: self.hover.as_deref(),\n      active: self.active.as_deref(),\n      focus: self.focus.as_deref(),\n      focus_visible: self.focus_visible.as_deref(),\n      focus_within: self.focus_within.as_deref(),\n    }\n  }\n}\n\n#[derive(Serialize, Debug, Deserialize, Default)]\n#[serde(rename_all = \"camelCase\")]\nstruct Drafts {\n  #[serde(default)]\n  custom_media: bool,\n}\n\n#[derive(Serialize, Debug, Deserialize, Default)]\n#[serde(rename_all = \"camelCase\")]\nstruct NonStandard {\n  #[serde(default)]\n  deep_selector_combinator: bool,\n}\n\nfn compile<'i>(\n  code: &'i str,\n  config: &Config,\n  #[allow(unused_variables)] visitor: &mut Option<JsVisitor>,\n) -> Result<TransformResult<'i>, CompileError<'i, napi::Error>> {\n  let drafts = config.drafts.as_ref();\n  let non_standard = config.non_standard.as_ref();\n  let warnings = Some(Arc::new(RwLock::new(Vec::new())));\n\n  let filename = config.filename.clone().unwrap_or_default();\n  let project_root = config.project_root.as_ref().map(|p| p.as_ref());\n  let mut source_map = if config.source_map.unwrap_or_default() {\n    let mut sm = SourceMap::new(project_root.unwrap_or(\"/\"));\n    sm.add_source(&filename);\n    sm.set_source_content(0, code)?;\n    Some(sm)\n  } else {\n    None\n  };\n\n  let res = {\n    let mut flags = ParserFlags::empty();\n    flags.set(ParserFlags::CUSTOM_MEDIA, matches!(drafts, Some(d) if d.custom_media));\n    flags.set(\n      ParserFlags::DEEP_SELECTOR_COMBINATOR,\n      matches!(non_standard, Some(v) if v.deep_selector_combinator),\n    );\n\n    let mut stylesheet = StyleSheet::parse_with(\n      &code,\n      ParserOptions {\n        filename: filename.clone(),\n        flags,\n        css_modules: if let Some(css_modules) = &config.css_modules {\n          match css_modules {\n            CssModulesOption::Bool(true) => Some(lightningcss::css_modules::Config::default()),\n            CssModulesOption::Bool(false) => None,\n            CssModulesOption::Config(c) => Some(lightningcss::css_modules::Config {\n              pattern: if let Some(pattern) = c.pattern.as_ref() {\n                match lightningcss::css_modules::Pattern::parse(pattern) {\n                  Ok(p) => p,\n                  Err(e) => return Err(CompileError::PatternError(e)),\n                }\n              } else {\n                Default::default()\n              },\n              dashed_idents: c.dashed_idents.unwrap_or_default(),\n              animation: c.animation.unwrap_or(true),\n              container: c.container.unwrap_or(true),\n              grid: c.grid.unwrap_or(true),\n              custom_idents: c.custom_idents.unwrap_or(true),\n              pure: c.pure.unwrap_or_default(),\n            }),\n          }\n        } else {\n          None\n        },\n        source_index: 0,\n        error_recovery: config.error_recovery.unwrap_or_default(),\n        warnings: warnings.clone(),\n      },\n      &mut CustomAtRuleParser {\n        configs: config.custom_at_rules.clone().unwrap_or_default(),\n      },\n    )?;\n\n    #[cfg(feature = \"visitor\")]\n    if let Some(visitor) = visitor.as_mut() {\n      stylesheet.visit(visitor).map_err(CompileError::JsError)?;\n    }\n\n    let targets = Targets {\n      browsers: config.targets,\n      include: Features::from_bits_truncate(config.include),\n      exclude: Features::from_bits_truncate(config.exclude),\n    };\n\n    stylesheet.minify(MinifyOptions {\n      targets,\n      unused_symbols: config.unused_symbols.clone().unwrap_or_default(),\n    })?;\n\n    stylesheet.to_css(PrinterOptions {\n      minify: config.minify.unwrap_or_default(),\n      source_map: source_map.as_mut(),\n      project_root,\n      targets,\n      analyze_dependencies: if let Some(d) = &config.analyze_dependencies {\n        match d {\n          AnalyzeDependenciesOption::Bool(b) if *b => Some(DependencyOptions { remove_imports: true }),\n          AnalyzeDependenciesOption::Config(c) => Some(DependencyOptions {\n            remove_imports: !c.preserve_imports,\n          }),\n          _ => None,\n        }\n      } else {\n        None\n      },\n      pseudo_classes: config.pseudo_classes.as_ref().map(|p| p.into()),\n    })?\n  };\n\n  let map = if let Some(mut source_map) = source_map {\n    if let Some(input_source_map) = &config.input_source_map {\n      if let Ok(mut sm) = SourceMap::from_json(\"/\", input_source_map) {\n        let _ = source_map.extends(&mut sm);\n      }\n    }\n\n    source_map.to_json(None).ok()\n  } else {\n    None\n  };\n\n  Ok(TransformResult {\n    code: res.code.into_bytes(),\n    map: map.map(|m| m.into_bytes()),\n    exports: res.exports,\n    references: res.references,\n    dependencies: res.dependencies,\n    warnings: warnings.map_or(Vec::new(), |w| {\n      Arc::try_unwrap(w)\n        .unwrap()\n        .into_inner()\n        .unwrap()\n        .into_iter()\n        .map(|w| w.into())\n        .collect()\n    }),\n  })\n}\n\n#[cfg(feature = \"bundler\")]\nfn compile_bundle<\n  'i,\n  'o,\n  P: SourceProvider,\n  F: FnOnce(&mut StyleSheet<'i, 'o, AtRule<'i>>) -> napi::Result<()>,\n>(\n  fs: &'i P,\n  config: &'o BundleConfig,\n  visit: Option<F>,\n) -> Result<TransformResult<'i>, CompileError<'i, P::Error>> {\n  use std::path::Path;\n\n  let project_root = config.project_root.as_ref().map(|p| p.as_ref());\n  let mut source_map = if config.source_map.unwrap_or_default() {\n    Some(SourceMap::new(project_root.unwrap_or(\"/\")))\n  } else {\n    None\n  };\n  let warnings = Some(Arc::new(RwLock::new(Vec::new())));\n\n  let res = {\n    let drafts = config.drafts.as_ref();\n    let non_standard = config.non_standard.as_ref();\n    let mut flags = ParserFlags::empty();\n    flags.set(ParserFlags::CUSTOM_MEDIA, matches!(drafts, Some(d) if d.custom_media));\n    flags.set(\n      ParserFlags::DEEP_SELECTOR_COMBINATOR,\n      matches!(non_standard, Some(v) if v.deep_selector_combinator),\n    );\n\n    let parser_options = ParserOptions {\n      flags,\n      css_modules: if let Some(css_modules) = &config.css_modules {\n        match css_modules {\n          CssModulesOption::Bool(true) => Some(lightningcss::css_modules::Config::default()),\n          CssModulesOption::Bool(false) => None,\n          CssModulesOption::Config(c) => Some(lightningcss::css_modules::Config {\n            pattern: if let Some(pattern) = c.pattern.as_ref() {\n              match lightningcss::css_modules::Pattern::parse(pattern) {\n                Ok(p) => p,\n                Err(e) => return Err(CompileError::PatternError(e)),\n              }\n            } else {\n              Default::default()\n            },\n            dashed_idents: c.dashed_idents.unwrap_or_default(),\n            animation: c.animation.unwrap_or(true),\n            container: c.container.unwrap_or(true),\n            grid: c.grid.unwrap_or(true),\n            custom_idents: c.custom_idents.unwrap_or(true),\n            pure: c.pure.unwrap_or_default(),\n          }),\n        }\n      } else {\n        None\n      },\n      error_recovery: config.error_recovery.unwrap_or_default(),\n      warnings: warnings.clone(),\n      filename: String::new(),\n      source_index: 0,\n    };\n\n    let mut at_rule_parser = CustomAtRuleParser {\n      configs: config.custom_at_rules.clone().unwrap_or_default(),\n    };\n\n    let mut bundler =\n      Bundler::new_with_at_rule_parser(fs, source_map.as_mut(), parser_options, &mut at_rule_parser);\n    let mut stylesheet = bundler.bundle(Path::new(&config.filename))?;\n\n    if let Some(visit) = visit {\n      visit(&mut stylesheet).map_err(CompileError::JsError)?;\n    }\n\n    let targets = Targets {\n      browsers: config.targets,\n      include: Features::from_bits_truncate(config.include),\n      exclude: Features::from_bits_truncate(config.exclude),\n    };\n\n    stylesheet.minify(MinifyOptions {\n      targets,\n      unused_symbols: config.unused_symbols.clone().unwrap_or_default(),\n    })?;\n\n    stylesheet.to_css(PrinterOptions {\n      minify: config.minify.unwrap_or_default(),\n      source_map: source_map.as_mut(),\n      project_root,\n      targets,\n      analyze_dependencies: if let Some(d) = &config.analyze_dependencies {\n        match d {\n          AnalyzeDependenciesOption::Bool(b) if *b => Some(DependencyOptions { remove_imports: true }),\n          AnalyzeDependenciesOption::Config(c) => Some(DependencyOptions {\n            remove_imports: !c.preserve_imports,\n          }),\n          _ => None,\n        }\n      } else {\n        None\n      },\n      pseudo_classes: config.pseudo_classes.as_ref().map(|p| p.into()),\n    })?\n  };\n\n  let map = if let Some(source_map) = &mut source_map {\n    source_map.to_json(None).ok()\n  } else {\n    None\n  };\n\n  Ok(TransformResult {\n    code: res.code.into_bytes(),\n    map: map.map(|m| m.into_bytes()),\n    exports: res.exports,\n    references: res.references,\n    dependencies: res.dependencies,\n    warnings: warnings.map_or(Vec::new(), |w| {\n      Arc::try_unwrap(w)\n        .unwrap()\n        .into_inner()\n        .unwrap()\n        .into_iter()\n        .map(|w| w.into())\n        .collect()\n    }),\n  })\n}\n\n#[derive(Debug, Deserialize)]\n#[serde(rename_all = \"camelCase\")]\nstruct AttrConfig {\n  pub filename: Option<String>,\n  #[serde(with = \"serde_bytes\")]\n  pub code: Vec<u8>,\n  pub targets: Option<Browsers>,\n  #[serde(default)]\n  pub include: u32,\n  #[serde(default)]\n  pub exclude: u32,\n  #[serde(default)]\n  pub minify: bool,\n  #[serde(default)]\n  pub analyze_dependencies: bool,\n  #[serde(default)]\n  pub error_recovery: bool,\n}\n\n#[derive(Serialize)]\n#[serde(rename_all = \"camelCase\")]\nstruct AttrResult<'i> {\n  #[serde(with = \"serde_bytes\")]\n  code: Vec<u8>,\n  dependencies: Option<Vec<Dependency>>,\n  warnings: Vec<Warning<'i>>,\n}\n\nimpl<'i> AttrResult<'i> {\n  fn into_js(self, ctx: CallContext) -> napi::Result<JsUnknown> {\n    // Manually construct buffers so we avoid a copy and work around\n    // https://github.com/napi-rs/napi-rs/issues/1124.\n    let mut obj = ctx.env.create_object()?;\n    let buf = ctx.env.create_buffer_with_data(self.code)?;\n    obj.set_named_property(\"code\", buf.into_raw())?;\n    obj.set_named_property(\"dependencies\", ctx.env.to_js_value(&self.dependencies)?)?;\n    obj.set_named_property(\"warnings\", ctx.env.to_js_value(&self.warnings)?)?;\n    Ok(obj.into_unknown())\n  }\n}\n\nfn compile_attr<'i>(\n  code: &'i str,\n  config: &AttrConfig,\n  #[allow(unused_variables)] visitor: &mut Option<JsVisitor>,\n) -> Result<AttrResult<'i>, CompileError<'i, napi::Error>> {\n  let warnings = if config.error_recovery {\n    Some(Arc::new(RwLock::new(Vec::new())))\n  } else {\n    None\n  };\n  let res = {\n    let filename = config.filename.clone().unwrap_or_default();\n    let mut attr = StyleAttribute::parse(\n      &code,\n      ParserOptions {\n        filename,\n        error_recovery: config.error_recovery,\n        warnings: warnings.clone(),\n        ..ParserOptions::default()\n      },\n    )?;\n\n    #[cfg(feature = \"visitor\")]\n    if let Some(visitor) = visitor.as_mut() {\n      attr.visit(visitor).unwrap();\n    }\n\n    let targets = Targets {\n      browsers: config.targets,\n      include: Features::from_bits_truncate(config.include),\n      exclude: Features::from_bits_truncate(config.exclude),\n    };\n\n    attr.minify(MinifyOptions {\n      targets,\n      ..MinifyOptions::default()\n    });\n    attr.to_css(PrinterOptions {\n      minify: config.minify,\n      source_map: None,\n      project_root: None,\n      targets,\n      analyze_dependencies: if config.analyze_dependencies {\n        Some(DependencyOptions::default())\n      } else {\n        None\n      },\n      pseudo_classes: None,\n    })?\n  };\n  Ok(AttrResult {\n    code: res.code.into_bytes(),\n    dependencies: res.dependencies,\n    warnings: warnings.map_or(Vec::new(), |w| {\n      Arc::try_unwrap(w)\n        .unwrap()\n        .into_inner()\n        .unwrap()\n        .into_iter()\n        .map(|w| w.into())\n        .collect()\n    }),\n  })\n}\n\nenum CompileError<'i, E: std::error::Error> {\n  ParseError(Error<ParserError<'i>>),\n  MinifyError(Error<MinifyErrorKind>),\n  PrinterError(Error<PrinterErrorKind>),\n  SourceMapError(parcel_sourcemap::SourceMapError),\n  BundleError(Error<BundleErrorKind<'i, E>>),\n  PatternError(PatternParseError),\n  #[cfg(feature = \"visitor\")]\n  JsError(napi::Error),\n}\n\nimpl<'i, E: std::error::Error> std::fmt::Display for CompileError<'i, E> {\n  fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n    match self {\n      CompileError::ParseError(err) => err.kind.fmt(f),\n      CompileError::MinifyError(err) => err.kind.fmt(f),\n      CompileError::PrinterError(err) => err.kind.fmt(f),\n      CompileError::BundleError(err) => err.kind.fmt(f),\n      CompileError::PatternError(err) => err.fmt(f),\n      CompileError::SourceMapError(err) => write!(f, \"{}\", err.to_string()), // TODO: switch to `fmt::Display` once parcel_sourcemap supports this\n      #[cfg(feature = \"visitor\")]\n      CompileError::JsError(err) => std::fmt::Debug::fmt(&err, f),\n    }\n  }\n}\n\nimpl<'i, E: IntoJsError + std::error::Error> CompileError<'i, E> {\n  fn into_js_error(self, env: Env, code: Option<&str>) -> napi::Result<napi::Error> {\n    let reason = self.to_string();\n    let data = match &self {\n      CompileError::ParseError(Error { kind, .. }) => env.to_js_value(kind)?,\n      CompileError::PrinterError(Error { kind, .. }) => env.to_js_value(kind)?,\n      CompileError::MinifyError(Error { kind, .. }) => env.to_js_value(kind)?,\n      CompileError::BundleError(Error { kind, .. }) => env.to_js_value(kind)?,\n      _ => env.get_null()?.into_unknown(),\n    };\n\n    let (js_error, loc) = match self {\n      CompileError::BundleError(Error {\n        loc,\n        kind: BundleErrorKind::ResolverError(e),\n      }) => {\n        // Add location info to existing JS error if available.\n        (e.into_js_error(env)?, loc)\n      }\n      CompileError::ParseError(Error { loc, .. })\n      | CompileError::PrinterError(Error { loc, .. })\n      | CompileError::MinifyError(Error { loc, .. })\n      | CompileError::BundleError(Error { loc, .. }) => {\n        // Generate an error with location information.\n        let syntax_error = env.get_global()?.get_named_property::<napi::JsFunction>(\"SyntaxError\")?;\n        let reason = env.create_string_from_std(reason)?;\n        let obj = syntax_error.new_instance(&[reason])?;\n        (obj.into_unknown(), loc)\n      }\n      _ => return Ok(self.into()),\n    };\n\n    if js_error.get_type()? == napi::ValueType::Object {\n      let mut obj: JsObject = unsafe { js_error.cast() };\n      if let Some(loc) = loc {\n        let line = env.create_int32((loc.line + 1) as i32)?;\n        let col = env.create_int32(loc.column as i32)?;\n        let filename = env.create_string_from_std(loc.filename)?;\n        obj.set_named_property(\"fileName\", filename)?;\n        if let Some(code) = code {\n          let source = env.create_string(code)?;\n          obj.set_named_property(\"source\", source)?;\n        }\n        let mut loc = env.create_object()?;\n        loc.set_named_property(\"line\", line)?;\n        loc.set_named_property(\"column\", col)?;\n        obj.set_named_property(\"loc\", loc)?;\n      }\n      obj.set_named_property(\"data\", data)?;\n      Ok(obj.into_unknown().into())\n    } else {\n      Ok(js_error.into())\n    }\n  }\n}\n\ntrait IntoJsError {\n  fn into_js_error(self, env: Env) -> napi::Result<JsUnknown>;\n}\n\nimpl IntoJsError for std::io::Error {\n  fn into_js_error(self, env: Env) -> napi::Result<JsUnknown> {\n    let reason = self.to_string();\n    let syntax_error = env.get_global()?.get_named_property::<napi::JsFunction>(\"SyntaxError\")?;\n    let reason = env.create_string_from_std(reason)?;\n    let obj = syntax_error.new_instance(&[reason])?;\n    Ok(obj.into_unknown())\n  }\n}\n\nimpl IntoJsError for napi::Error {\n  fn into_js_error(self, env: Env) -> napi::Result<JsUnknown> {\n    unsafe { JsUnknown::from_napi_value(env.raw(), ToNapiValue::to_napi_value(env.raw(), self)?) }\n  }\n}\n\nimpl<'i, E: std::error::Error> From<Error<ParserError<'i>>> for CompileError<'i, E> {\n  fn from(e: Error<ParserError<'i>>) -> CompileError<'i, E> {\n    CompileError::ParseError(e)\n  }\n}\n\nimpl<'i, E: std::error::Error> From<Error<MinifyErrorKind>> for CompileError<'i, E> {\n  fn from(err: Error<MinifyErrorKind>) -> CompileError<'i, E> {\n    CompileError::MinifyError(err)\n  }\n}\n\nimpl<'i, E: std::error::Error> From<Error<PrinterErrorKind>> for CompileError<'i, E> {\n  fn from(err: Error<PrinterErrorKind>) -> CompileError<'i, E> {\n    CompileError::PrinterError(err)\n  }\n}\n\nimpl<'i, E: std::error::Error> From<parcel_sourcemap::SourceMapError> for CompileError<'i, E> {\n  fn from(e: parcel_sourcemap::SourceMapError) -> CompileError<'i, E> {\n    CompileError::SourceMapError(e)\n  }\n}\n\nimpl<'i, E: std::error::Error> From<Error<BundleErrorKind<'i, E>>> for CompileError<'i, E> {\n  fn from(e: Error<BundleErrorKind<'i, E>>) -> CompileError<'i, E> {\n    CompileError::BundleError(e)\n  }\n}\n\nimpl<'i, E: std::error::Error> From<CompileError<'i, E>> for napi::Error {\n  fn from(e: CompileError<'i, E>) -> napi::Error {\n    match e {\n      CompileError::SourceMapError(e) => napi::Error::from_reason(e.to_string()),\n      CompileError::PatternError(e) => napi::Error::from_reason(e.to_string()),\n      #[cfg(feature = \"visitor\")]\n      CompileError::JsError(e) => e,\n      _ => napi::Error::new(napi::Status::GenericFailure, e.to_string()),\n    }\n  }\n}\n\n#[derive(Serialize)]\nstruct Warning<'i> {\n  message: String,\n  #[serde(flatten)]\n  data: ParserError<'i>,\n  loc: Option<ErrorLocation>,\n}\n\nimpl<'i> From<Error<ParserError<'i>>> for Warning<'i> {\n  fn from(mut e: Error<ParserError<'i>>) -> Self {\n    // Convert to 1-based line numbers.\n    if let Some(loc) = &mut e.loc {\n      loc.line += 1;\n    }\n    Warning {\n      message: e.kind.to_string(),\n      data: e.kind,\n      loc: e.loc,\n    }\n  }\n}\n"
  },
  {
    "path": "napi/src/threadsafe_function.rs",
    "content": "// Fork of threadsafe_function from napi-rs that allows calling JS function manually rather than\n// only returning args. This enables us to use the return value of the function.\n\n#![allow(clippy::single_component_path_imports)]\n\nuse std::convert::Into;\nuse std::ffi::CString;\nuse std::marker::PhantomData;\nuse std::os::raw::c_void;\nuse std::ptr;\nuse std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};\nuse std::sync::Arc;\n\nuse napi::{check_status, sys, Env, Result, Status};\nuse napi::{JsError, JsFunction, NapiValue};\n\n/// ThreadSafeFunction Context object\n/// the `value` is the value passed to `call` method\npub struct ThreadSafeCallContext<T: 'static> {\n  pub env: Env,\n  pub value: T,\n  pub callback: Option<JsFunction>,\n}\n\n#[repr(u8)]\npub enum ThreadsafeFunctionCallMode {\n  NonBlocking,\n  Blocking,\n}\n\nimpl From<ThreadsafeFunctionCallMode> for sys::napi_threadsafe_function_call_mode {\n  fn from(value: ThreadsafeFunctionCallMode) -> Self {\n    match value {\n      ThreadsafeFunctionCallMode::Blocking => sys::ThreadsafeFunctionCallMode::blocking,\n      ThreadsafeFunctionCallMode::NonBlocking => sys::ThreadsafeFunctionCallMode::nonblocking,\n    }\n  }\n}\n\n/// Communicate with the addon's main thread by invoking a JavaScript function from other threads.\n///\n/// ## Example\n/// An example of using `ThreadsafeFunction`:\n///\n/// ```rust\n/// #[macro_use]\n/// extern crate napi_derive;\n///\n/// use std::thread;\n///\n/// use napi::{\n///     threadsafe_function::{\n///         ThreadSafeCallContext, ThreadsafeFunctionCallMode, ThreadsafeFunctionReleaseMode,\n///     },\n///     CallContext, Error, JsFunction, JsNumber, JsUndefined, Result, Status,\n/// };\n///\n/// #[js_function(1)]\n/// pub fn test_threadsafe_function(ctx: CallContext) -> Result<JsUndefined> {\n///   let func = ctx.get::<JsFunction>(0)?;\n///\n///   let tsfn =\n///       ctx\n///           .env\n///           .create_threadsafe_function(&func, 0, |ctx: ThreadSafeCallContext<Vec<u32>>| {\n///             ctx.value\n///                 .iter()\n///                 .map(|v| ctx.env.create_uint32(*v))\n///                 .collect::<Result<Vec<JsNumber>>>()\n///           })?;\n///\n///   let tsfn_cloned = tsfn.clone();\n///\n///   thread::spawn(move || {\n///       let output: Vec<u32> = vec![0, 1, 2, 3];\n///       // It's okay to call a threadsafe function multiple times.\n///       tsfn.call(Ok(output.clone()), ThreadsafeFunctionCallMode::Blocking);\n///   });\n///\n///   thread::spawn(move || {\n///       let output: Vec<u32> = vec![3, 2, 1, 0];\n///       // It's okay to call a threadsafe function multiple times.\n///       tsfn_cloned.call(Ok(output.clone()), ThreadsafeFunctionCallMode::NonBlocking);\n///   });\n///\n///   ctx.env.get_undefined()\n/// }\n/// ```\npub struct ThreadsafeFunction<T: 'static> {\n  raw_tsfn: sys::napi_threadsafe_function,\n  aborted: Arc<AtomicBool>,\n  ref_count: Arc<AtomicUsize>,\n  _phantom: PhantomData<T>,\n}\n\nimpl<T: 'static> Clone for ThreadsafeFunction<T> {\n  fn clone(&self) -> Self {\n    if !self.aborted.load(Ordering::Acquire) {\n      let acquire_status = unsafe { sys::napi_acquire_threadsafe_function(self.raw_tsfn) };\n      debug_assert!(\n        acquire_status == sys::Status::napi_ok,\n        \"Acquire threadsafe function failed in clone\"\n      );\n    }\n\n    Self {\n      raw_tsfn: self.raw_tsfn,\n      aborted: Arc::clone(&self.aborted),\n      ref_count: Arc::clone(&self.ref_count),\n      _phantom: PhantomData,\n    }\n  }\n}\n\nunsafe impl<T> Send for ThreadsafeFunction<T> {}\nunsafe impl<T> Sync for ThreadsafeFunction<T> {}\n\nimpl<T: 'static> ThreadsafeFunction<T> {\n  /// See [napi_create_threadsafe_function](https://nodejs.org/api/n-api.html#n_api_napi_create_threadsafe_function)\n  /// for more information.\n  pub(crate) fn create<R: 'static + Send + FnMut(ThreadSafeCallContext<T>) -> Result<()>>(\n    env: sys::napi_env,\n    func: sys::napi_value,\n    max_queue_size: usize,\n    callback: R,\n  ) -> Result<Self> {\n    let mut async_resource_name = ptr::null_mut();\n    let s = \"napi_rs_threadsafe_function\";\n    let len = s.len();\n    let s = CString::new(s)?;\n    check_status!(unsafe { sys::napi_create_string_utf8(env, s.as_ptr(), len, &mut async_resource_name) })?;\n\n    let initial_thread_count = 1usize;\n    let mut raw_tsfn = ptr::null_mut();\n    let ptr = Box::into_raw(Box::new(callback)) as *mut c_void;\n    check_status!(unsafe {\n      sys::napi_create_threadsafe_function(\n        env,\n        func,\n        ptr::null_mut(),\n        async_resource_name,\n        max_queue_size,\n        initial_thread_count,\n        ptr,\n        Some(thread_finalize_cb::<T, R>),\n        ptr,\n        Some(call_js_cb::<T, R>),\n        &mut raw_tsfn,\n      )\n    })?;\n\n    let aborted = Arc::new(AtomicBool::new(false));\n    let aborted_ptr = Arc::into_raw(aborted.clone()) as *mut c_void;\n    check_status!(unsafe { sys::napi_add_env_cleanup_hook(env, Some(cleanup_cb), aborted_ptr) })?;\n\n    Ok(ThreadsafeFunction {\n      raw_tsfn,\n      aborted,\n      ref_count: Arc::new(AtomicUsize::new(initial_thread_count)),\n      _phantom: PhantomData,\n    })\n  }\n}\n\nimpl<T: 'static> ThreadsafeFunction<T> {\n  /// See [napi_call_threadsafe_function](https://nodejs.org/api/n-api.html#n_api_napi_call_threadsafe_function)\n  /// for more information.\n  pub fn call(&self, value: T, mode: ThreadsafeFunctionCallMode) -> Status {\n    if self.aborted.load(Ordering::Acquire) {\n      return Status::Closing;\n    }\n    unsafe {\n      sys::napi_call_threadsafe_function(self.raw_tsfn, Box::into_raw(Box::new(value)) as *mut _, mode.into())\n    }\n    .into()\n  }\n}\n\nimpl<T: 'static> Drop for ThreadsafeFunction<T> {\n  fn drop(&mut self) {\n    if !self.aborted.load(Ordering::Acquire) && self.ref_count.load(Ordering::Acquire) > 0usize {\n      let release_status = unsafe {\n        sys::napi_release_threadsafe_function(self.raw_tsfn, sys::ThreadsafeFunctionReleaseMode::release)\n      };\n      assert!(\n        release_status == sys::Status::napi_ok,\n        \"Threadsafe Function release failed\"\n      );\n    }\n  }\n}\n\nunsafe extern \"C\" fn cleanup_cb(cleanup_data: *mut c_void) {\n  let aborted = Arc::<AtomicBool>::from_raw(cleanup_data.cast());\n  aborted.store(true, Ordering::SeqCst);\n}\n\nunsafe extern \"C\" fn thread_finalize_cb<T: 'static, R>(\n  _raw_env: sys::napi_env,\n  finalize_data: *mut c_void,\n  _finalize_hint: *mut c_void,\n) where\n  R: 'static + Send + FnMut(ThreadSafeCallContext<T>) -> Result<()>,\n{\n  // cleanup\n  drop(Box::<R>::from_raw(finalize_data.cast()));\n}\n\nunsafe extern \"C\" fn call_js_cb<T: 'static, R>(\n  raw_env: sys::napi_env,\n  js_callback: sys::napi_value,\n  context: *mut c_void,\n  data: *mut c_void,\n) where\n  R: 'static + Send + FnMut(ThreadSafeCallContext<T>) -> Result<()>,\n{\n  // env and/or callback can be null when shutting down\n  if raw_env.is_null() {\n    return;\n  }\n\n  let ctx: &mut R = &mut *context.cast::<R>();\n  let val: Result<T> = Ok(*Box::<T>::from_raw(data.cast()));\n\n  let mut recv = ptr::null_mut();\n  sys::napi_get_undefined(raw_env, &mut recv);\n\n  let ret = val.and_then(|v| {\n    (ctx)(ThreadSafeCallContext {\n      env: Env::from_raw(raw_env),\n      value: v,\n      callback: if js_callback.is_null() {\n        None\n      } else {\n        Some(JsFunction::from_raw(raw_env, js_callback).unwrap()) // TODO: unwrap\n      },\n    })\n  });\n\n  let status = match ret {\n    Ok(()) => sys::Status::napi_ok,\n    Err(e) => sys::napi_fatal_exception(raw_env, JsError::from(e).into_value(raw_env)),\n  };\n  if status == sys::Status::napi_ok {\n    return;\n  }\n  if status == sys::Status::napi_pending_exception {\n    let mut error_result = ptr::null_mut();\n    assert_eq!(\n      sys::napi_get_and_clear_last_exception(raw_env, &mut error_result),\n      sys::Status::napi_ok\n    );\n\n    // When shutting down, napi_fatal_exception sometimes returns another exception\n    let stat = sys::napi_fatal_exception(raw_env, error_result);\n    assert!(stat == sys::Status::napi_ok || stat == sys::Status::napi_pending_exception);\n  } else {\n    let error_code: Status = status.into();\n    let error_code_string = format!(\"{:?}\", error_code);\n    let mut error_code_value = ptr::null_mut();\n    assert_eq!(\n      sys::napi_create_string_utf8(\n        raw_env,\n        error_code_string.as_ptr() as *const _,\n        error_code_string.len(),\n        &mut error_code_value,\n      ),\n      sys::Status::napi_ok,\n    );\n    let error_msg = \"Call JavaScript callback failed in thread safe function\";\n    let mut error_msg_value = ptr::null_mut();\n    assert_eq!(\n      sys::napi_create_string_utf8(\n        raw_env,\n        error_msg.as_ptr() as *const _,\n        error_msg.len(),\n        &mut error_msg_value,\n      ),\n      sys::Status::napi_ok,\n    );\n    let mut error_value = ptr::null_mut();\n    assert_eq!(\n      sys::napi_create_error(raw_env, error_code_value, error_msg_value, &mut error_value),\n      sys::Status::napi_ok,\n    );\n    assert_eq!(sys::napi_fatal_exception(raw_env, error_value), sys::Status::napi_ok);\n  }\n}\n"
  },
  {
    "path": "napi/src/transformer.rs",
    "content": "use std::{\n  marker::PhantomData,\n  ops::{Index, IndexMut},\n};\n\nuse lightningcss::{\n  media_query::MediaFeatureValue,\n  properties::{\n    custom::{Token, TokenList, TokenOrValue},\n    Property,\n  },\n  rules::{CssRule, CssRuleList},\n  stylesheet::ParserOptions,\n  traits::ParseWithOptions,\n  values::{\n    ident::Ident,\n    length::{Length, LengthValue},\n    string::CowArcStr,\n  },\n  visitor::{Visit, VisitTypes, Visitor},\n};\nuse lightningcss::{stylesheet::StyleSheet, traits::IntoOwned};\nuse napi::{Env, JsFunction, JsObject, JsUnknown, Ref, ValueType};\nuse serde::{Deserialize, Serialize};\nuse smallvec::SmallVec;\n\nuse crate::{at_rule_parser::AtRule, utils::get_named_property};\n\npub struct JsVisitor {\n  env: Env,\n  visit_stylesheet: VisitorsRef,\n  visit_rule: VisitorsRef,\n  rule_map: VisitorsRef,\n  property_map: VisitorsRef,\n  visit_declaration: VisitorsRef,\n  visit_length: Option<Ref<()>>,\n  visit_angle: Option<Ref<()>>,\n  visit_ratio: Option<Ref<()>>,\n  visit_resolution: Option<Ref<()>>,\n  visit_time: Option<Ref<()>>,\n  visit_color: Option<Ref<()>>,\n  visit_image: VisitorsRef,\n  visit_url: Option<Ref<()>>,\n  visit_media_query: VisitorsRef,\n  visit_supports_condition: VisitorsRef,\n  visit_custom_ident: Option<Ref<()>>,\n  visit_dashed_ident: Option<Ref<()>>,\n  visit_selector: Option<Ref<()>>,\n  visit_token: VisitorsRef,\n  token_map: VisitorsRef,\n  visit_function: VisitorsRef,\n  function_map: VisitorsRef,\n  visit_variable: VisitorsRef,\n  visit_env: VisitorsRef,\n  env_map: VisitorsRef,\n  types: VisitTypes,\n}\n\n// This is so that the visitor can work with bundleAsync.\n// We ensure that we only call JsVisitor from the main JS thread.\nunsafe impl Send for JsVisitor {}\n\n#[derive(PartialEq, Eq, Clone, Copy)]\nenum VisitStage {\n  Enter,\n  Exit,\n}\n\ntype VisitorsRef = Visitors<Ref<()>>;\n\nstruct Visitors<T> {\n  enter: Option<T>,\n  exit: Option<T>,\n}\n\nimpl<T> Visitors<T> {\n  fn new(enter: Option<T>, exit: Option<T>) -> Self {\n    Self { enter, exit }\n  }\n\n  fn for_stage(&self, stage: VisitStage) -> Option<&T> {\n    match stage {\n      VisitStage::Enter => self.enter.as_ref(),\n      VisitStage::Exit => self.exit.as_ref(),\n    }\n  }\n}\n\nimpl Visitors<Ref<()>> {\n  fn get<U: napi::NapiValue>(&self, env: &Env) -> Visitors<U> {\n    Visitors {\n      enter: self.enter.as_ref().and_then(|p| env.get_reference_value_unchecked(p).ok()),\n      exit: self.exit.as_ref().and_then(|p| env.get_reference_value_unchecked(p).ok()),\n    }\n  }\n}\n\nimpl Visitors<JsObject> {\n  fn named(&self, stage: VisitStage, name: &str) -> Option<JsFunction> {\n    self\n      .for_stage(stage)\n      .and_then(|m| get_named_property::<JsFunction>(m, name).ok())\n  }\n\n  fn custom(&self, stage: VisitStage, obj: &str, name: &str) -> Option<JsFunction> {\n    self\n      .for_stage(stage)\n      .and_then(|m| m.get_named_property::<JsUnknown>(obj).ok())\n      .and_then(|v| {\n        match v.get_type() {\n          Ok(ValueType::Function) => return v.try_into().ok(),\n          Ok(ValueType::Object) => {\n            let o: napi::Result<JsObject> = v.try_into();\n            if let Ok(o) = o {\n              return get_named_property::<JsFunction>(&o, name).ok();\n            }\n          }\n          _ => {}\n        }\n\n        None\n      })\n  }\n}\n\nimpl Drop for JsVisitor {\n  fn drop(&mut self) {\n    macro_rules! drop {\n      ($id: ident) => {\n        if let Some(v) = &mut self.$id {\n          drop(v.unref(self.env));\n        }\n      };\n    }\n\n    macro_rules! drop_tuple {\n      ($id: ident) => {\n        if let Some(v) = &mut self.$id.enter {\n          drop(v.unref(self.env));\n        }\n        if let Some(v) = &mut self.$id.exit {\n          drop(v.unref(self.env));\n        }\n      };\n    }\n\n    drop_tuple!(visit_stylesheet);\n    drop_tuple!(visit_rule);\n    drop_tuple!(rule_map);\n    drop_tuple!(visit_declaration);\n    drop_tuple!(property_map);\n    drop!(visit_length);\n    drop!(visit_angle);\n    drop!(visit_ratio);\n    drop!(visit_resolution);\n    drop!(visit_time);\n    drop!(visit_color);\n    drop_tuple!(visit_image);\n    drop!(visit_url);\n    drop_tuple!(visit_media_query);\n    drop_tuple!(visit_supports_condition);\n    drop_tuple!(visit_variable);\n    drop_tuple!(visit_env);\n    drop_tuple!(env_map);\n    drop!(visit_custom_ident);\n    drop!(visit_dashed_ident);\n    drop_tuple!(visit_function);\n    drop_tuple!(function_map);\n    drop!(visit_selector);\n    drop_tuple!(visit_token);\n    drop_tuple!(token_map);\n  }\n}\n\nimpl JsVisitor {\n  pub fn new(env: Env, visitor: JsObject) -> Self {\n    let mut types = VisitTypes::empty();\n    macro_rules! get {\n      ($name: literal, $( $t: ident )|+) => {{\n        let res: Option<JsFunction> = get_named_property(&visitor, $name).ok();\n\n        if res.is_some() {\n          types |= $( VisitTypes::$t )|+;\n        }\n\n        // We must create a reference so that the garbage collector doesn't destroy\n        // the function before we try to call it (in the async bundle case).\n        res.and_then(|res| env.create_reference(res).ok())\n      }};\n    }\n\n    macro_rules! map {\n      ($name: literal, $( $t: ident )|+) => {{\n        let obj: Option<JsObject> = get_named_property(&visitor, $name).ok();\n\n        if obj.is_some() {\n          types |= $( VisitTypes::$t )|+;\n        }\n\n        obj.and_then(|obj| env.create_reference(obj).ok())\n      }};\n    }\n\n    Self {\n      env,\n      visit_stylesheet: VisitorsRef::new(get!(\"StyleSheet\", RULES), get!(\"StyleSheetExit\", RULES)),\n      visit_rule: VisitorsRef::new(get!(\"Rule\", RULES), get!(\"RuleExit\", RULES)),\n      rule_map: VisitorsRef::new(map!(\"Rule\", RULES), get!(\"RuleExit\", RULES)),\n      visit_declaration: VisitorsRef::new(get!(\"Declaration\", PROPERTIES), get!(\"DeclarationExit\", PROPERTIES)),\n      property_map: VisitorsRef::new(map!(\"Declaration\", PROPERTIES), map!(\"DeclarationExit\", PROPERTIES)),\n      visit_length: get!(\"Length\", LENGTHS),\n      visit_angle: get!(\"Angle\", ANGLES),\n      visit_ratio: get!(\"Ratio\", RATIOS),\n      visit_resolution: get!(\"Resolution\", RESOLUTIONS),\n      visit_time: get!(\"Time\", TIMES),\n      visit_color: get!(\"Color\", COLORS),\n      visit_image: VisitorsRef::new(get!(\"Image\", IMAGES), get!(\"ImageExit\", IMAGES)),\n      visit_url: get!(\"Url\", URLS),\n      visit_media_query: VisitorsRef::new(\n        get!(\"MediaQuery\", MEDIA_QUERIES),\n        get!(\"MediaQueryExit\", MEDIA_QUERIES),\n      ),\n      visit_supports_condition: VisitorsRef::new(\n        get!(\"SupportsCondition\", SUPPORTS_CONDITIONS),\n        get!(\"SupportsConditionExit\", SUPPORTS_CONDITIONS),\n      ),\n      visit_variable: VisitorsRef::new(get!(\"Variable\", TOKENS), get!(\"VariableExit\", TOKENS)),\n      visit_env: VisitorsRef::new(\n        get!(\"EnvironmentVariable\", TOKENS | MEDIA_QUERIES | ENVIRONMENT_VARIABLES),\n        get!(\n          \"EnvironmentVariableExit\",\n          TOKENS | MEDIA_QUERIES | ENVIRONMENT_VARIABLES\n        ),\n      ),\n      env_map: VisitorsRef::new(\n        map!(\"EnvironmentVariable\", TOKENS | MEDIA_QUERIES | ENVIRONMENT_VARIABLES),\n        map!(\n          \"EnvironmentVariableExit\",\n          TOKENS | MEDIA_QUERIES | ENVIRONMENT_VARIABLES\n        ),\n      ),\n      visit_custom_ident: get!(\"CustomIdent\", CUSTOM_IDENTS),\n      visit_dashed_ident: get!(\"DashedIdent\", DASHED_IDENTS),\n      visit_function: VisitorsRef::new(get!(\"Function\", TOKENS), get!(\"FunctionExit\", TOKENS)),\n      function_map: VisitorsRef::new(map!(\"Function\", TOKENS), map!(\"FunctionExit\", TOKENS)),\n      visit_selector: get!(\"Selector\", SELECTORS),\n      visit_token: VisitorsRef::new(get!(\"Token\", TOKENS), None),\n      token_map: VisitorsRef::new(map!(\"Token\", TOKENS), None),\n      types,\n    }\n  }\n}\n\nimpl<'i> Visitor<'i, AtRule<'i>> for JsVisitor {\n  type Error = napi::Error;\n\n  fn visit_types(&self) -> VisitTypes {\n    self.types\n  }\n\n  fn visit_stylesheet<'o>(&mut self, stylesheet: &mut StyleSheet<'i, 'o, AtRule<'i>>) -> Result<(), Self::Error> {\n    if self.types.contains(VisitTypes::RULES) {\n      let env = self.env;\n      let visit_stylesheet = self.visit_stylesheet.get::<JsFunction>(&env);\n      if let Some(visit) = visit_stylesheet.for_stage(VisitStage::Enter) {\n        call_visitor(&env, stylesheet, visit)?\n      }\n\n      stylesheet.visit_children(self)?;\n\n      if let Some(visit) = visit_stylesheet.for_stage(VisitStage::Exit) {\n        call_visitor(&env, stylesheet, visit)?\n      }\n\n      Ok(())\n    } else {\n      stylesheet.visit_children(self)\n    }\n  }\n\n  fn visit_rule_list(\n    &mut self,\n    rules: &mut lightningcss::rules::CssRuleList<'i, AtRule<'i>>,\n  ) -> Result<(), Self::Error> {\n    if self.types.contains(VisitTypes::RULES) {\n      let env = self.env;\n      let rule_map = self.rule_map.get::<JsObject>(&env);\n      let visit_rule = self.visit_rule.get::<JsFunction>(&env);\n\n      visit_list(\n        rules,\n        |value, stage| {\n          // Use a more specific visitor function if available, but fall back to visit_rule.\n          let name = match value {\n            CssRule::Media(..) => \"media\",\n            CssRule::Import(..) => \"import\",\n            CssRule::Style(..) => \"style\",\n            CssRule::Keyframes(..) => \"keyframes\",\n            CssRule::FontFace(..) => \"font-face\",\n            CssRule::FontPaletteValues(..) => \"font-palette-values\",\n            CssRule::FontFeatureValues(..) => \"font-feature-values\",\n            CssRule::Page(..) => \"page\",\n            CssRule::Supports(..) => \"supports\",\n            CssRule::CounterStyle(..) => \"counter-style\",\n            CssRule::Namespace(..) => \"namespace\",\n            CssRule::CustomMedia(..) => \"custom-media\",\n            CssRule::LayerBlock(..) => \"layer-block\",\n            CssRule::LayerStatement(..) => \"layer-statement\",\n            CssRule::Property(..) => \"property\",\n            CssRule::Container(..) => \"container\",\n            CssRule::Scope(..) => \"scope\",\n            CssRule::MozDocument(..) => \"moz-document\",\n            CssRule::Nesting(..) => \"nesting\",\n            CssRule::NestedDeclarations(..) => \"nested-declarations\",\n            CssRule::Viewport(..) => \"viewport\",\n            CssRule::StartingStyle(..) => \"starting-style\",\n            CssRule::ViewTransition(..) => \"view-transition\",\n            CssRule::Unknown(v) => {\n              let name = v.name.as_ref();\n              if let Some(visit) = rule_map.custom(stage, \"unknown\", name) {\n                let js_value = env.to_js_value(v)?;\n                let res = visit.call(None, &[js_value])?;\n                return env.from_js_value(res).map(serde_detach::detach);\n              } else {\n                \"unknown\"\n              }\n            }\n            CssRule::Custom(c) => {\n              let name = c.name.as_ref();\n              if let Some(visit) = rule_map.custom(stage, \"custom\", name) {\n                let js_value = env.to_js_value(c)?;\n                let res = visit.call(None, &[js_value])?;\n                return env.from_js_value(res).map(serde_detach::detach);\n              } else {\n                \"custom\"\n              }\n            }\n            CssRule::Ignored => return Ok(None),\n          };\n\n          if let Some(visit) = rule_map.named(stage, name).as_ref().or(visit_rule.for_stage(stage)) {\n            let js_value = env.to_js_value(value)?;\n            let res = visit.call(None, &[js_value])?;\n            env.from_js_value(res).map(serde_detach::detach)\n          } else {\n            Ok(None)\n          }\n        },\n        |rule| rule.visit_children(self),\n      )?;\n\n      Ok(())\n    } else {\n      rules.visit_children(self)\n    }\n  }\n\n  fn visit_declaration_block(\n    &mut self,\n    decls: &mut lightningcss::declaration::DeclarationBlock<'i>,\n  ) -> Result<(), Self::Error> {\n    if self.types.contains(VisitTypes::PROPERTIES) {\n      let env = self.env;\n      let property_map = self.property_map.get::<JsObject>(&env);\n      let visit_declaration = self.visit_declaration.get::<JsFunction>(&env);\n      visit_declaration_list(\n        &env,\n        &mut decls.important_declarations,\n        &visit_declaration,\n        &property_map,\n        |property| property.visit_children(self),\n      )?;\n      visit_declaration_list(\n        &env,\n        &mut decls.declarations,\n        &visit_declaration,\n        &property_map,\n        |property| property.visit_children(self),\n      )?;\n      Ok(())\n    } else {\n      decls.visit_children(self)\n    }\n  }\n\n  fn visit_length(&mut self, length: &mut LengthValue) -> Result<(), Self::Error> {\n    visit(&self.env, length, &self.visit_length)\n  }\n\n  fn visit_angle(&mut self, angle: &mut lightningcss::values::angle::Angle) -> Result<(), Self::Error> {\n    visit(&self.env, angle, &self.visit_angle)\n  }\n\n  fn visit_ratio(&mut self, ratio: &mut lightningcss::values::ratio::Ratio) -> Result<(), Self::Error> {\n    visit(&self.env, ratio, &self.visit_ratio)\n  }\n\n  fn visit_resolution(\n    &mut self,\n    resolution: &mut lightningcss::values::resolution::Resolution,\n  ) -> Result<(), Self::Error> {\n    visit(&self.env, resolution, &self.visit_resolution)\n  }\n\n  fn visit_time(&mut self, time: &mut lightningcss::values::time::Time) -> Result<(), Self::Error> {\n    visit(&self.env, time, &self.visit_time)\n  }\n\n  fn visit_color(&mut self, color: &mut lightningcss::values::color::CssColor) -> Result<(), Self::Error> {\n    visit(&self.env, color, &self.visit_color)\n  }\n\n  fn visit_image(&mut self, image: &mut lightningcss::values::image::Image<'i>) -> Result<(), Self::Error> {\n    visit(&self.env, image, &self.visit_image.enter)?;\n    image.visit_children(self)?;\n    visit(&self.env, image, &self.visit_image.exit)\n  }\n\n  fn visit_url(&mut self, url: &mut lightningcss::values::url::Url<'i>) -> Result<(), Self::Error> {\n    visit(&self.env, url, &self.visit_url)\n  }\n\n  fn visit_media_list(&mut self, media: &mut lightningcss::media_query::MediaList<'i>) -> Result<(), Self::Error> {\n    if self.types.contains(VisitTypes::MEDIA_QUERIES) {\n      let env = self.env;\n      let visit_media_query = self.visit_media_query.get::<JsFunction>(&env);\n      visit_list(\n        &mut media.media_queries,\n        |value, stage| {\n          if let Some(visit) = visit_media_query.for_stage(stage) {\n            let js_value = env.to_js_value(value)?;\n            let res = visit.call(None, &[js_value])?;\n            env.from_js_value(res).map(serde_detach::detach)\n          } else {\n            Ok(None)\n          }\n        },\n        |q| q.visit_children(self),\n      )?;\n      Ok(())\n    } else {\n      media.visit_children(self)\n    }\n  }\n\n  fn visit_media_feature_value(&mut self, value: &mut MediaFeatureValue<'i>) -> Result<(), Self::Error> {\n    if self.types.contains(VisitTypes::ENVIRONMENT_VARIABLES) && matches!(value, MediaFeatureValue::Env(_)) {\n      let env_map = self.env_map.get::<JsObject>(&self.env);\n      let visit_env = self.visit_env.get::<JsFunction>(&self.env);\n      let call = |stage: VisitStage, value: &mut MediaFeatureValue, env: &Env| -> napi::Result<()> {\n        let env_var = if let MediaFeatureValue::Env(env) = value {\n          env\n        } else {\n          return Ok(());\n        };\n        let visit_type = env_map.named(stage, env_var.name.name());\n        let visit = visit_env.for_stage(stage);\n        let new_value: Option<TokenOrValue> = if let Some(visit) = visit_type.as_ref().or(visit) {\n          let js_value = env.to_js_value(env_var)?;\n          let res = visit.call(None, &[js_value])?;\n          env.from_js_value(res).map(serde_detach::detach)?\n        } else {\n          None\n        };\n\n        match new_value {\n          None => return Ok(()),\n          Some(TokenOrValue::Length(l)) => *value = MediaFeatureValue::Length(Length::Value(l)),\n          Some(TokenOrValue::Resolution(r)) => *value = MediaFeatureValue::Resolution(r),\n          Some(TokenOrValue::Token(Token::Number { value: n, .. })) => *value = MediaFeatureValue::Number(n),\n          Some(TokenOrValue::Token(Token::Ident(ident))) => *value = MediaFeatureValue::Ident(Ident(ident)),\n          // TODO: ratio\n          _ => {\n            return Err(napi::Error::new(\n              napi::Status::InvalidArg,\n              format!(\"invalid environment value in media query: {:?}\", new_value),\n            ))\n          }\n        }\n\n        Ok(())\n      };\n\n      call(VisitStage::Enter, value, &self.env)?;\n      value.visit_children(self)?;\n      call(VisitStage::Exit, value, &self.env)?;\n      return Ok(());\n    }\n\n    value.visit_children(self)\n  }\n\n  fn visit_supports_condition(\n    &mut self,\n    condition: &mut lightningcss::rules::supports::SupportsCondition<'i>,\n  ) -> Result<(), Self::Error> {\n    visit(&self.env, condition, &self.visit_supports_condition.enter)?;\n    condition.visit_children(self)?;\n    visit(&self.env, condition, &self.visit_supports_condition.exit)\n  }\n\n  fn visit_custom_ident(\n    &mut self,\n    ident: &mut lightningcss::values::ident::CustomIdent,\n  ) -> Result<(), Self::Error> {\n    visit(&self.env, ident, &self.visit_custom_ident)\n  }\n\n  fn visit_dashed_ident(\n    &mut self,\n    ident: &mut lightningcss::values::ident::DashedIdent,\n  ) -> Result<(), Self::Error> {\n    visit(&self.env, ident, &self.visit_dashed_ident)\n  }\n\n  fn visit_selector_list(\n    &mut self,\n    selectors: &mut lightningcss::selector::SelectorList<'i>,\n  ) -> Result<(), Self::Error> {\n    if let Some(visit) = self\n      .visit_selector\n      .as_ref()\n      .and_then(|v| self.env.get_reference_value_unchecked::<JsFunction>(v).ok())\n    {\n      map::<_, _, _, true>(&mut selectors.0, |value| {\n        let js_value = self.env.to_js_value(value)?;\n        let res = visit.call(None, &[js_value])?;\n        self.env.from_js_value(res).map(serde_detach::detach)\n      })?;\n    }\n\n    Ok(())\n  }\n\n  fn visit_token_list(\n    &mut self,\n    tokens: &mut lightningcss::properties::custom::TokenList<'i>,\n  ) -> Result<(), Self::Error> {\n    if self.types.contains(VisitTypes::TOKENS) {\n      let env = self.env;\n      let visit_token = self.visit_token.get::<JsFunction>(&env);\n      let token_map = self.token_map.get::<JsObject>(&env);\n      let visit_function = self.visit_function.get::<JsFunction>(&env);\n      let function_map = self.function_map.get::<JsObject>(&env);\n      let visit_variable = self.visit_variable.get::<JsFunction>(&env);\n      let visit_env = self.visit_env.get::<JsFunction>(&env);\n      let env_map = self.env_map.get::<JsObject>(&env);\n\n      visit_list(\n        &mut tokens.0,\n        |value, stage| {\n          let (visit_type, visit) = match value {\n            TokenOrValue::Function(f) => (\n              function_map.named(stage, f.name.0.as_ref()),\n              visit_function.for_stage(stage),\n            ),\n            TokenOrValue::Var(_) => (None, visit_variable.for_stage(stage)),\n            TokenOrValue::Env(e) => (env_map.named(stage, e.name.name()), visit_env.for_stage(stage)),\n            TokenOrValue::Token(t) => {\n              let name = match t {\n                Token::Ident(_) => Some(\"ident\"),\n                Token::AtKeyword(_) => Some(\"at-keyword\"),\n                Token::Hash(_) => Some(\"hash\"),\n                Token::IDHash(_) => Some(\"id-hash\"),\n                Token::String(_) => Some(\"string\"),\n                Token::Number { .. } => Some(\"number\"),\n                Token::Percentage { .. } => Some(\"percentage\"),\n                Token::Dimension { .. } => Some(\"dimension\"),\n                _ => None,\n              };\n              let visit = if let Some(name) = name {\n                token_map.named(stage, name)\n              } else {\n                None\n              };\n              (visit, visit_token.for_stage(stage))\n            }\n            _ => return Ok(None),\n          };\n\n          if let Some(visit) = visit_type.as_ref().or(visit) {\n            let js_value = match value {\n              TokenOrValue::Function(f) => env.to_js_value(f)?,\n              TokenOrValue::Var(v) => env.to_js_value(v)?,\n              TokenOrValue::Env(v) => env.to_js_value(v)?,\n              TokenOrValue::Token(t) => env.to_js_value(t)?,\n              _ => unreachable!(),\n            };\n\n            let res = visit.call(None, &[js_value])?;\n            let res: Option<TokensOrRaw> = env.from_js_value(res).map(serde_detach::detach)?;\n            Ok(res.map(|r| r.0))\n          } else {\n            Ok(None)\n          }\n        },\n        |value| value.visit_children(self),\n      )?;\n\n      Ok(())\n    } else {\n      tokens.visit_children(self)\n    }\n  }\n}\n\nfn visit<V: Serialize + Deserialize<'static>>(\n  env: &Env,\n  value: &mut V,\n  visit: &Option<Ref<()>>,\n) -> napi::Result<()> {\n  if let Some(visit) = visit\n    .as_ref()\n    .and_then(|v| env.get_reference_value_unchecked::<JsFunction>(v).ok())\n  {\n    call_visitor(env, value, &visit)?;\n  }\n\n  Ok(())\n}\n\nfn call_visitor<V: Serialize + Deserialize<'static>>(\n  env: &Env,\n  value: &mut V,\n  visit: &JsFunction,\n) -> napi::Result<()> {\n  let js_value = env.to_js_value(value)?;\n  let res = visit.call(None, &[js_value])?;\n  let new_value: Option<V> = env.from_js_value(res).map(serde_detach::detach)?;\n  match new_value {\n    Some(new_value) => *value = new_value,\n    None => {}\n  }\n\n  Ok(())\n}\n\nfn visit_declaration_list<'i, C: FnMut(&mut Property<'i>) -> napi::Result<()>>(\n  env: &Env,\n  list: &mut Vec<Property<'i>>,\n  visit_declaration: &Visitors<JsFunction>,\n  property_map: &Visitors<JsObject>,\n  visit_children: C,\n) -> napi::Result<()> {\n  visit_list(\n    list,\n    |value, stage| {\n      // Use a specific property visitor if available, or fall back to Property visitor.\n      let visit = match value {\n        Property::Custom(v) => {\n          if let Some(visit) = property_map.custom(stage, \"custom\", v.name.as_ref()) {\n            let js_value = env.to_js_value(v)?;\n            let res = visit.call(None, &[js_value])?;\n            return env.from_js_value(res).map(serde_detach::detach);\n          } else {\n            None\n          }\n        }\n        _ => property_map.named(stage, value.property_id().name()),\n      };\n\n      if let Some(visit) = visit.as_ref().or(visit_declaration.for_stage(stage)) {\n        let js_value = env.to_js_value(value)?;\n        let res = visit.call(None, &[js_value])?;\n        env.from_js_value(res).map(serde_detach::detach)\n      } else {\n        Ok(None)\n      }\n    },\n    visit_children,\n  )\n}\n\nfn visit_list<\n  V,\n  L: List<V>,\n  F: Fn(&mut V, VisitStage) -> napi::Result<Option<ValueOrVec<V>>>,\n  C: FnMut(&mut V) -> napi::Result<()>,\n>(\n  list: &mut L,\n  visit: F,\n  mut visit_children: C,\n) -> napi::Result<()> {\n  map(list, |value| {\n    let mut new_value: Option<ValueOrVec<V>> = visit(value, VisitStage::Enter)?;\n\n    match &mut new_value {\n      Some(ValueOrVec::Value(v)) => {\n        visit_children(v)?;\n\n        if let Some(val) = visit(v, VisitStage::Exit)? {\n          new_value = Some(val);\n        }\n      }\n      Some(ValueOrVec::Vec(v)) => {\n        map(v, |value| {\n          visit_children(value)?;\n          visit(value, VisitStage::Exit)\n        })?;\n      }\n      None => {\n        visit_children(value)?;\n        if let Some(val) = visit(value, VisitStage::Exit)? {\n          new_value = Some(val);\n        }\n      }\n    }\n\n    Ok(new_value)\n  })\n}\n\nfn map<V, L: List<V>, F: FnMut(&mut V) -> napi::Result<Option<ValueOrVec<V, IS_VEC>>>, const IS_VEC: bool>(\n  list: &mut L,\n  mut f: F,\n) -> napi::Result<()> {\n  let mut i = 0;\n  while i < list.len() {\n    let value = &mut list[i];\n    let new_value = f(value)?;\n    match new_value {\n      Some(ValueOrVec::Value(v)) => {\n        list[i] = v;\n        i += 1;\n      }\n      Some(ValueOrVec::Vec(vec)) => {\n        if vec.is_empty() {\n          list.remove(i);\n        } else {\n          let len = vec.len();\n          list.replace(i, vec);\n          i += len;\n        }\n      }\n      None => {\n        i += 1;\n      }\n    }\n  }\n  Ok(())\n}\n\n#[derive(serde::Serialize)]\n#[serde(untagged)]\nenum ValueOrVec<V, const IS_VEC: bool = false> {\n  Value(V),\n  Vec(Vec<V>),\n}\n\n// Manually implemented deserialize for better error messages.\n// https://github.com/serde-rs/serde/issues/773\nimpl<'de, V: serde::Deserialize<'de>, const IS_VEC: bool> serde::Deserialize<'de> for ValueOrVec<V, IS_VEC> {\n  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n  where\n    D: serde::Deserializer<'de>,\n  {\n    use serde::Deserializer;\n    let content = serde_content::Value::deserialize(deserializer)?;\n    let de = serde_content::Deserializer::new(content.clone()).coerce_numbers();\n\n    // Try to deserialize as a sequence first.\n    let mut was_seq = false;\n    let res = de.deserialize_seq(SeqVisitor {\n      was_seq: &mut was_seq,\n      phantom: PhantomData,\n    });\n\n    if was_seq {\n      // Allow fallback if we know the value is also a list (e.g. selector).\n      if res.is_ok() || !IS_VEC {\n        return res.map_err(|e| serde::de::Error::custom(e.to_string())).map(ValueOrVec::Vec);\n      }\n    }\n\n    // If it wasn't a sequence, try a value.\n    let de = serde_content::Deserializer::new(content).coerce_numbers();\n    return V::deserialize(de)\n      .map_err(|e| serde::de::Error::custom(e.to_string()))\n      .map(ValueOrVec::Value);\n\n    struct SeqVisitor<'a, V> {\n      was_seq: &'a mut bool,\n      phantom: PhantomData<V>,\n    }\n\n    impl<'a, 'de, V: serde::Deserialize<'de>> serde::de::Visitor<'de> for SeqVisitor<'a, V> {\n      type Value = Vec<V>;\n\n      fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {\n        formatter.write_str(\"a sequence\")\n      }\n\n      fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>\n      where\n        A: serde::de::SeqAccess<'de>,\n      {\n        *self.was_seq = true;\n        let mut vec = Vec::with_capacity(seq.size_hint().unwrap_or(1));\n        while let Some(v) = seq.next_element()? {\n          vec.push(v);\n        }\n        Ok(vec)\n      }\n    }\n  }\n}\n\nstruct TokensOrRaw<'i>(ValueOrVec<TokenOrValue<'i>>);\n\nimpl<'i, 'de: 'i> serde::Deserialize<'de> for TokensOrRaw<'i> {\n  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n  where\n    D: serde::Deserializer<'de>,\n  {\n    #[derive(serde::Deserialize)]\n    struct Raw<'i> {\n      #[serde(borrow)]\n      raw: CowArcStr<'i>,\n    }\n\n    let content = serde_content::Value::deserialize(deserializer)?;\n    let de = serde_content::Deserializer::new(content.clone()).coerce_numbers();\n\n    if let Ok(res) = Raw::deserialize(de) {\n      let res = TokenList::parse_string_with_options(res.raw.as_ref(), ParserOptions::default())\n        .map_err(|_| serde::de::Error::custom(\"Could not parse value\"))?;\n      return Ok(TokensOrRaw(ValueOrVec::Vec(res.into_owned().0)));\n    }\n\n    let de = serde_content::Deserializer::new(content).coerce_numbers();\n    Ok(TokensOrRaw(\n      ValueOrVec::deserialize(de).map_err(|e| serde::de::Error::custom(e.to_string()))?,\n    ))\n  }\n}\n\ntrait List<V>: Index<usize, Output = V> + IndexMut<usize, Output = V> {\n  fn len(&self) -> usize;\n  fn remove(&mut self, i: usize);\n  fn replace(&mut self, i: usize, items: Vec<V>);\n}\n\nimpl<V> List<V> for Vec<V> {\n  fn len(&self) -> usize {\n    Vec::len(self)\n  }\n\n  fn remove(&mut self, i: usize) {\n    Vec::remove(self, i);\n  }\n\n  fn replace(&mut self, i: usize, items: Vec<V>) {\n    self.splice(i..i + 1, items);\n  }\n}\n\nimpl<V, T: smallvec::Array<Item = V>> List<V> for SmallVec<T> {\n  fn len(&self) -> usize {\n    SmallVec::len(self)\n  }\n\n  fn remove(&mut self, i: usize) {\n    SmallVec::remove(self, i);\n  }\n\n  fn replace(&mut self, i: usize, items: Vec<V>) {\n    let len = items.len();\n    let mut iter = items.into_iter();\n    self[i] = iter.next().unwrap();\n    if len > 1 {\n      self.insert_many(i + 1, iter);\n    }\n  }\n}\n\nimpl<'i, R> List<CssRule<'i, R>> for CssRuleList<'i, R> {\n  fn len(&self) -> usize {\n    self.0.len()\n  }\n\n  fn remove(&mut self, i: usize) {\n    self[i] = CssRule::Ignored;\n  }\n\n  fn replace(&mut self, i: usize, items: Vec<CssRule<'i, R>>) {\n    self.0.replace(i, items)\n  }\n}\n"
  },
  {
    "path": "napi/src/utils.rs",
    "content": "use napi::{Error, JsObject, JsUnknown, Result};\n\n// Workaround for https://github.com/napi-rs/napi-rs/issues/1641\npub fn get_named_property<T: TryFrom<JsUnknown, Error = Error>>(obj: &JsObject, property: &str) -> Result<T> {\n  let unknown = obj.get_named_property::<JsUnknown>(property)?;\n  T::try_from(unknown)\n}\n"
  },
  {
    "path": "node/Cargo.toml",
    "content": "[package]\nauthors = [\"Devon Govett <devongovett@gmail.com>\"]\nname = \"lightningcss_node\"\nversion = \"0.1.0\"\nedition = \"2021\"\npublish = false\n\n[lib]\ncrate-type = [\"cdylib\"]\n\n[dependencies]\nlightningcss-napi = { version = \"0.4.8\", path = \"../napi\", features = [\n  \"bundler\",\n  \"visitor\",\n] }\nnapi = { version = \"2.15.4\", default-features = false, features = [\n  \"compat-mode\",\n] }\nnapi-derive = \"2\"\n\n[target.'cfg(target_os = \"macos\")'.dependencies]\njemallocator = { version = \"0.3.2\", features = [\"disable_initial_exec_tls\"] }\n\n[target.'cfg(not(target_arch = \"wasm32\"))'.build-dependencies]\nnapi-build = \"1\"\n"
  },
  {
    "path": "node/ast.d.ts",
    "content": "/* eslint-disable */\n/**\n * This file was automatically generated by json-schema-to-typescript.\n * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,\n * and run json-schema-to-typescript to regenerate this file.\n */\n\nexport type String = string;\n/**\n * A CSS rule.\n */\nexport type Rule<D = Declaration, M = MediaQuery> = | {\n    type: \"media\";\n    value: MediaRule<D, M>;\n  }\n| {\n    type: \"import\";\n    value: ImportRule<M>;\n  }\n| {\n    type: \"style\";\n    value: StyleRule<D, M>;\n  }\n| {\n    type: \"keyframes\";\n    value: KeyframesRule<D>;\n  }\n| {\n    type: \"font-face\";\n    value: FontFaceRule;\n  }\n| {\n    type: \"font-palette-values\";\n    value: FontPaletteValuesRule;\n  }\n| {\n    type: \"font-feature-values\";\n    value: FontFeatureValuesRule;\n  }\n| {\n    type: \"page\";\n    value: PageRule<D>;\n  }\n| {\n    type: \"supports\";\n    value: SupportsRule<D, M>;\n  }\n| {\n    type: \"counter-style\";\n    value: CounterStyleRule<D>;\n  }\n| {\n    type: \"namespace\";\n    value: NamespaceRule;\n  }\n| {\n    type: \"moz-document\";\n    value: MozDocumentRule<D, M>;\n  }\n| {\n    type: \"nesting\";\n    value: NestingRule<D, M>;\n  }\n| {\n    type: \"nested-declarations\";\n    value: NestedDeclarationsRule<D>;\n  }\n| {\n    type: \"viewport\";\n    value: ViewportRule<D>;\n  }\n| {\n    type: \"custom-media\";\n    value: CustomMediaRule<M>;\n  }\n| {\n    type: \"layer-statement\";\n    value: LayerStatementRule;\n  }\n| {\n    type: \"layer-block\";\n    value: LayerBlockRule<D, M>;\n  }\n| {\n    type: \"property\";\n    value: PropertyRule;\n  }\n| {\n    type: \"container\";\n    value: ContainerRule<D, M>;\n  }\n| {\n    type: \"scope\";\n    value: ScopeRule<D, M>;\n  }\n| {\n    type: \"starting-style\";\n    value: StartingStyleRule<D, M>;\n  }\n| {\n    type: \"view-transition\";\n    value: ViewTransitionRule;\n  }\n| {\n    type: \"ignored\";\n  }\n| {\n    type: \"unknown\";\n    value: UnknownAtRule;\n  }\n| {\n    type: \"custom\";\n    value: DefaultAtRule;\n  };\n/**\n * Represents a media condition.\n */\nexport type MediaCondition =\n  | {\n      type: \"feature\";\n      value: QueryFeatureFor_MediaFeatureId;\n    }\n  | {\n      type: \"not\";\n      value: MediaCondition;\n    }\n  | {\n      /**\n       * The conditions for the operator.\n       */\n      conditions: MediaCondition[];\n      /**\n       * The operator for the conditions.\n       */\n      operator: Operator;\n      type: \"operation\";\n    }\n  | {\n      type: \"unknown\";\n      value: TokenOrValue[];\n    };\n/**\n * A generic media feature or container feature.\n */\nexport type QueryFeatureFor_MediaFeatureId =\n  | {\n      /**\n       * The name of the feature.\n       */\n      name: MediaFeatureNameFor_MediaFeatureId;\n      type: \"plain\";\n      /**\n       * The feature value.\n       */\n      value: MediaFeatureValue;\n    }\n  | {\n      /**\n       * The name of the feature.\n       */\n      name: MediaFeatureNameFor_MediaFeatureId;\n      type: \"boolean\";\n    }\n  | {\n      /**\n       * The name of the feature.\n       */\n      name: MediaFeatureNameFor_MediaFeatureId;\n      /**\n       * A comparator.\n       */\n      operator: MediaFeatureComparison;\n      type: \"range\";\n      /**\n       * The feature value.\n       */\n      value: MediaFeatureValue;\n    }\n  | {\n      /**\n       * The end value.\n       */\n      end: MediaFeatureValue;\n      /**\n       * A comparator for the end value.\n       */\n      endOperator: MediaFeatureComparison;\n      /**\n       * The name of the feature.\n       */\n      name: MediaFeatureNameFor_MediaFeatureId;\n      /**\n       * A start value.\n       */\n      start: MediaFeatureValue;\n      /**\n       * A comparator for the start value.\n       */\n      startOperator: MediaFeatureComparison;\n      type: \"interval\";\n    };\n/**\n * A media feature name.\n */\nexport type MediaFeatureNameFor_MediaFeatureId = MediaFeatureId | String | String;\n/**\n * A media query feature identifier.\n */\nexport type MediaFeatureId =\n  | \"width\"\n  | \"height\"\n  | \"aspect-ratio\"\n  | \"orientation\"\n  | \"overflow-block\"\n  | \"overflow-inline\"\n  | \"horizontal-viewport-segments\"\n  | \"vertical-viewport-segments\"\n  | \"display-mode\"\n  | \"resolution\"\n  | \"scan\"\n  | \"grid\"\n  | \"update\"\n  | \"environment-blending\"\n  | \"color\"\n  | \"color-index\"\n  | \"monochrome\"\n  | \"color-gamut\"\n  | \"dynamic-range\"\n  | \"inverted-colors\"\n  | \"pointer\"\n  | \"hover\"\n  | \"any-pointer\"\n  | \"any-hover\"\n  | \"nav-controls\"\n  | \"video-color-gamut\"\n  | \"video-dynamic-range\"\n  | \"scripting\"\n  | \"prefers-reduced-motion\"\n  | \"prefers-reduced-transparency\"\n  | \"prefers-contrast\"\n  | \"forced-colors\"\n  | \"prefers-color-scheme\"\n  | \"prefers-reduced-data\"\n  | \"device-width\"\n  | \"device-height\"\n  | \"device-aspect-ratio\"\n  | \"-webkit-device-pixel-ratio\"\n  | \"-moz-device-pixel-ratio\";\n/**\n * [media feature value](https://drafts.csswg.org/mediaqueries/#typedef-mf-value) within a media query.\n *\n * See [MediaFeature](MediaFeature).\n */\nexport type MediaFeatureValue =\n  | {\n      type: \"length\";\n      value: Length;\n    }\n  | {\n      type: \"number\";\n      value: number;\n    }\n  | {\n      type: \"integer\";\n      value: number;\n    }\n  | {\n      type: \"boolean\";\n      value: boolean;\n    }\n  | {\n      type: \"resolution\";\n      value: Resolution;\n    }\n  | {\n      type: \"ratio\";\n      value: Ratio;\n    }\n  | {\n      type: \"ident\";\n      value: String;\n    }\n  | {\n      type: \"env\";\n      value: EnvironmentVariable;\n    };\n/**\n * A CSS [`<length>`](https://www.w3.org/TR/css-values-4/#lengths) value, with support for `calc()`.\n */\nexport type Length =\n  | {\n      type: \"value\";\n      value: LengthValue;\n    }\n  | {\n      type: \"calc\";\n      value: CalcFor_Length;\n    };\nexport type LengthUnit =\n  | \"px\"\n  | \"in\"\n  | \"cm\"\n  | \"mm\"\n  | \"q\"\n  | \"pt\"\n  | \"pc\"\n  | \"em\"\n  | \"rem\"\n  | \"ex\"\n  | \"rex\"\n  | \"ch\"\n  | \"rch\"\n  | \"cap\"\n  | \"rcap\"\n  | \"ic\"\n  | \"ric\"\n  | \"lh\"\n  | \"rlh\"\n  | \"vw\"\n  | \"lvw\"\n  | \"svw\"\n  | \"dvw\"\n  | \"cqw\"\n  | \"vh\"\n  | \"lvh\"\n  | \"svh\"\n  | \"dvh\"\n  | \"cqh\"\n  | \"vi\"\n  | \"svi\"\n  | \"lvi\"\n  | \"dvi\"\n  | \"cqi\"\n  | \"vb\"\n  | \"svb\"\n  | \"lvb\"\n  | \"dvb\"\n  | \"cqb\"\n  | \"vmin\"\n  | \"svmin\"\n  | \"lvmin\"\n  | \"dvmin\"\n  | \"cqmin\"\n  | \"vmax\"\n  | \"svmax\"\n  | \"lvmax\"\n  | \"dvmax\"\n  | \"cqmax\";\n/**\n * A mathematical expression used within the [`calc()`](https://www.w3.org/TR/css-values-4/#calc-func) function.\n *\n * This type supports generic value types. Values such as [Length](super::length::Length), [Percentage](super::percentage::Percentage), [Time](super::time::Time), and [Angle](super::angle::Angle) support `calc()` expressions.\n */\nexport type CalcFor_Length =\n  | {\n      type: \"value\";\n      value: Length;\n    }\n  | {\n      type: \"number\";\n      value: number;\n    }\n  | {\n      type: \"sum\";\n      /**\n       * @minItems 2\n       * @maxItems 2\n       */\n      value: [CalcFor_Length, CalcFor_Length];\n    }\n  | {\n      type: \"product\";\n      /**\n       * @minItems 2\n       * @maxItems 2\n       */\n      value: [number, CalcFor_Length];\n    }\n  | {\n      type: \"function\";\n      value: MathFunctionFor_Length;\n    };\n/**\n * A CSS [math function](https://www.w3.org/TR/css-values-4/#math-function).\n *\n * Math functions may be used in most properties and values that accept numeric values, including lengths, percentages, angles, times, etc.\n */\nexport type MathFunctionFor_Length =\n  | {\n      type: \"calc\";\n      value: CalcFor_Length;\n    }\n  | {\n      type: \"min\";\n      value: CalcFor_Length[];\n    }\n  | {\n      type: \"max\";\n      value: CalcFor_Length[];\n    }\n  | {\n      type: \"clamp\";\n      /**\n       * @minItems 3\n       * @maxItems 3\n       */\n      value: [CalcFor_Length, CalcFor_Length, CalcFor_Length];\n    }\n  | {\n      type: \"round\";\n      /**\n       * @minItems 3\n       * @maxItems 3\n       */\n      value: [RoundingStrategy, CalcFor_Length, CalcFor_Length];\n    }\n  | {\n      type: \"rem\";\n      /**\n       * @minItems 2\n       * @maxItems 2\n       */\n      value: [CalcFor_Length, CalcFor_Length];\n    }\n  | {\n      type: \"mod\";\n      /**\n       * @minItems 2\n       * @maxItems 2\n       */\n      value: [CalcFor_Length, CalcFor_Length];\n    }\n  | {\n      type: \"abs\";\n      value: CalcFor_Length;\n    }\n  | {\n      type: \"sign\";\n      value: CalcFor_Length;\n    }\n  | {\n      type: \"hypot\";\n      value: CalcFor_Length[];\n    };\n/**\n * A [rounding strategy](https://www.w3.org/TR/css-values-4/#typedef-rounding-strategy), as used in the `round()` function.\n */\nexport type RoundingStrategy = \"nearest\" | \"up\" | \"down\" | \"to-zero\";\n/**\n * A CSS [`<resolution>`](https://www.w3.org/TR/css-values-4/#resolution) value.\n */\nexport type Resolution =\n  | {\n      type: \"dpi\";\n      value: number;\n    }\n  | {\n      type: \"dpcm\";\n      value: number;\n    }\n  | {\n      type: \"dppx\";\n      value: number;\n    };\n/**\n * A CSS [`<ratio>`](https://www.w3.org/TR/css-values-4/#ratios) value, representing the ratio of two numeric values.\n *\n * @minItems 2\n * @maxItems 2\n */\nexport type Ratio = [number, number];\n/**\n * A raw CSS token, or a parsed value.\n */\nexport type TokenOrValue =\n  | {\n      type: \"token\";\n      value: Token;\n    }\n  | {\n      type: \"color\";\n      value: CssColor;\n    }\n  | {\n      type: \"unresolved-color\";\n      value: UnresolvedColor;\n    }\n  | {\n      type: \"url\";\n      value: Url;\n    }\n  | {\n      type: \"var\";\n      value: Variable;\n    }\n  | {\n      type: \"env\";\n      value: EnvironmentVariable;\n    }\n  | {\n      type: \"function\";\n      value: Function;\n    }\n  | {\n      type: \"length\";\n      value: LengthValue;\n    }\n  | {\n      type: \"angle\";\n      value: Angle;\n    }\n  | {\n      type: \"time\";\n      value: Time;\n    }\n  | {\n      type: \"resolution\";\n      value: Resolution;\n    }\n  | {\n      type: \"dashed-ident\";\n      value: String;\n    }\n  | {\n      type: \"animation-name\";\n      value: AnimationName;\n    };\n/**\n * A raw CSS token.\n */\nexport type Token =\n  | {\n      type: \"ident\";\n      value: String;\n    }\n  | {\n      type: \"at-keyword\";\n      value: String;\n    }\n  | {\n      type: \"hash\";\n      value: String;\n    }\n  | {\n      type: \"id-hash\";\n      value: String;\n    }\n  | {\n      type: \"string\";\n      value: String;\n    }\n  | {\n      type: \"unquoted-url\";\n      value: String;\n    }\n  | {\n      type: \"delim\";\n      value: string;\n    }\n  | {\n      type: \"number\";\n      /**\n       * The value as a float\n       */\n      value: number;\n    }\n  | {\n      type: \"percentage\";\n      /**\n       * The value as a float, divided by 100 so that the nominal range is 0.0 to 1.0.\n       */\n      value: number;\n    }\n  | {\n      type: \"dimension\";\n      /**\n       * The unit, e.g. \"px\" in `12px`\n       */\n      unit: String;\n      /**\n       * The value as a float\n       */\n      value: number;\n    }\n  | {\n      type: \"white-space\";\n      value: String;\n    }\n  | {\n      type: \"comment\";\n      value: String;\n    }\n  | {\n      type: \"colon\";\n    }\n  | {\n      type: \"semicolon\";\n    }\n  | {\n      type: \"comma\";\n    }\n  | {\n      type: \"include-match\";\n    }\n  | {\n      type: \"dash-match\";\n    }\n  | {\n      type: \"prefix-match\";\n    }\n  | {\n      type: \"suffix-match\";\n    }\n  | {\n      type: \"substring-match\";\n    }\n  | {\n      type: \"cdo\";\n    }\n  | {\n      type: \"cdc\";\n    }\n  | {\n      type: \"function\";\n      value: String;\n    }\n  | {\n      type: \"parenthesis-block\";\n    }\n  | {\n      type: \"square-bracket-block\";\n    }\n  | {\n      type: \"curly-bracket-block\";\n    }\n  | {\n      type: \"bad-url\";\n      value: String;\n    }\n  | {\n      type: \"bad-string\";\n      value: String;\n    }\n  | {\n      type: \"close-parenthesis\";\n    }\n  | {\n      type: \"close-square-bracket\";\n    }\n  | {\n      type: \"close-curly-bracket\";\n    };\n/**\n * A CSS [`<color>`](https://www.w3.org/TR/css-color-4/#color-type) value.\n *\n * CSS supports many different color spaces to represent colors. The most common values are stored as RGBA using a single byte per component. Less common values are stored using a `Box` to reduce the amount of memory used per color.\n *\n * Each color space is represented as a struct that implements the `From` and `Into` traits for all other color spaces, so it is possible to convert between color spaces easily. In addition, colors support [interpolation](#method.interpolate) as in the `color-mix()` function.\n */\nexport type CssColor = CurrentColor | RGBColor | LABColor | PredefinedColor | FloatColor | LightDark | SystemColor;\nexport type CurrentColor = {\n  type: \"currentcolor\";\n};\nexport type RGBColor = {\n  /**\n   * The alpha component.\n   */\n  alpha: number;\n  /**\n   * The blue component.\n   */\n  b: number;\n  /**\n   * The green component.\n   */\n  g: number;\n  /**\n   * The red component.\n   */\n  r: number;\n  type: \"rgb\";\n};\n/**\n * A color in a LAB color space, including the `lab()`, `lch()`, `oklab()`, and `oklch()` functions.\n */\nexport type LABColor =\n  | {\n      /**\n       * The a component.\n       */\n      a: number;\n      /**\n       * The alpha component.\n       */\n      alpha: number;\n      /**\n       * The b component.\n       */\n      b: number;\n      /**\n       * The lightness component.\n       */\n      l: number;\n      type: \"lab\";\n    }\n  | {\n      /**\n       * The alpha component.\n       */\n      alpha: number;\n      /**\n       * The chroma component.\n       */\n      c: number;\n      /**\n       * The hue component.\n       */\n      h: number;\n      /**\n       * The lightness component.\n       */\n      l: number;\n      type: \"lch\";\n    }\n  | {\n      /**\n       * The a component.\n       */\n      a: number;\n      /**\n       * The alpha component.\n       */\n      alpha: number;\n      /**\n       * The b component.\n       */\n      b: number;\n      /**\n       * The lightness component.\n       */\n      l: number;\n      type: \"oklab\";\n    }\n  | {\n      /**\n       * The alpha component.\n       */\n      alpha: number;\n      /**\n       * The chroma component.\n       */\n      c: number;\n      /**\n       * The hue component.\n       */\n      h: number;\n      /**\n       * The lightness component.\n       */\n      l: number;\n      type: \"oklch\";\n    };\n/**\n * A color in a predefined color space, e.g. `display-p3`.\n */\nexport type PredefinedColor =\n  | {\n      /**\n       * The alpha component.\n       */\n      alpha: number;\n      /**\n       * The blue component.\n       */\n      b: number;\n      /**\n       * The green component.\n       */\n      g: number;\n      /**\n       * The red component.\n       */\n      r: number;\n      type: \"srgb\";\n    }\n  | {\n      /**\n       * The alpha component.\n       */\n      alpha: number;\n      /**\n       * The blue component.\n       */\n      b: number;\n      /**\n       * The green component.\n       */\n      g: number;\n      /**\n       * The red component.\n       */\n      r: number;\n      type: \"srgb-linear\";\n    }\n  | {\n      /**\n       * The alpha component.\n       */\n      alpha: number;\n      /**\n       * The blue component.\n       */\n      b: number;\n      /**\n       * The green component.\n       */\n      g: number;\n      /**\n       * The red component.\n       */\n      r: number;\n      type: \"display-p3\";\n    }\n  | {\n      /**\n       * The alpha component.\n       */\n      alpha: number;\n      /**\n       * The blue component.\n       */\n      b: number;\n      /**\n       * The green component.\n       */\n      g: number;\n      /**\n       * The red component.\n       */\n      r: number;\n      type: \"a98-rgb\";\n    }\n  | {\n      /**\n       * The alpha component.\n       */\n      alpha: number;\n      /**\n       * The blue component.\n       */\n      b: number;\n      /**\n       * The green component.\n       */\n      g: number;\n      /**\n       * The red component.\n       */\n      r: number;\n      type: \"prophoto-rgb\";\n    }\n  | {\n      /**\n       * The alpha component.\n       */\n      alpha: number;\n      /**\n       * The blue component.\n       */\n      b: number;\n      /**\n       * The green component.\n       */\n      g: number;\n      /**\n       * The red component.\n       */\n      r: number;\n      type: \"rec2020\";\n    }\n  | {\n      /**\n       * The alpha component.\n       */\n      alpha: number;\n      type: \"xyz-d50\";\n      /**\n       * The x component.\n       */\n      x: number;\n      /**\n       * The y component.\n       */\n      y: number;\n      /**\n       * The z component.\n       */\n      z: number;\n    }\n  | {\n      /**\n       * The alpha component.\n       */\n      alpha: number;\n      type: \"xyz-d65\";\n      /**\n       * The x component.\n       */\n      x: number;\n      /**\n       * The y component.\n       */\n      y: number;\n      /**\n       * The z component.\n       */\n      z: number;\n    };\n/**\n * A floating point representation of color types that are usually stored as RGBA. These are used when there are any `none` components, which are represented as NaN.\n */\nexport type FloatColor =\n  | {\n      /**\n       * The alpha component.\n       */\n      alpha: number;\n      /**\n       * The blue component.\n       */\n      b: number;\n      /**\n       * The green component.\n       */\n      g: number;\n      /**\n       * The red component.\n       */\n      r: number;\n      type: \"rgb\";\n    }\n  | {\n      /**\n       * The alpha component.\n       */\n      alpha: number;\n      /**\n       * The hue component.\n       */\n      h: number;\n      /**\n       * The lightness component.\n       */\n      l: number;\n      /**\n       * The saturation component.\n       */\n      s: number;\n      type: \"hsl\";\n    }\n  | {\n      /**\n       * The alpha component.\n       */\n      alpha: number;\n      /**\n       * The blackness component.\n       */\n      b: number;\n      /**\n       * The hue component.\n       */\n      h: number;\n      type: \"hwb\";\n      /**\n       * The whiteness component.\n       */\n      w: number;\n    };\nexport type LightDark = {\n  dark: CssColor;\n  light: CssColor;\n  type: \"light-dark\";\n};\n/**\n * A CSS [system color](https://drafts.csswg.org/css-color/#css-system-colors) keyword.\n */\nexport type SystemColor =\n  | \"accentcolor\"\n  | \"accentcolortext\"\n  | \"activetext\"\n  | \"buttonborder\"\n  | \"buttonface\"\n  | \"buttontext\"\n  | \"canvas\"\n  | \"canvastext\"\n  | \"field\"\n  | \"fieldtext\"\n  | \"graytext\"\n  | \"highlight\"\n  | \"highlighttext\"\n  | \"linktext\"\n  | \"mark\"\n  | \"marktext\"\n  | \"selecteditem\"\n  | \"selecteditemtext\"\n  | \"visitedtext\"\n  | \"activeborder\"\n  | \"activecaption\"\n  | \"appworkspace\"\n  | \"background\"\n  | \"buttonhighlight\"\n  | \"buttonshadow\"\n  | \"captiontext\"\n  | \"inactiveborder\"\n  | \"inactivecaption\"\n  | \"inactivecaptiontext\"\n  | \"infobackground\"\n  | \"infotext\"\n  | \"menu\"\n  | \"menutext\"\n  | \"scrollbar\"\n  | \"threeddarkshadow\"\n  | \"threedface\"\n  | \"threedhighlight\"\n  | \"threedlightshadow\"\n  | \"threedshadow\"\n  | \"window\"\n  | \"windowframe\"\n  | \"windowtext\";\n/**\n * A color value with an unresolved alpha value (e.g. a variable). These can be converted from the modern slash syntax to older comma syntax. This can only be done when the only unresolved component is the alpha since variables can resolve to multiple tokens.\n */\nexport type UnresolvedColor =\n  | {\n      /**\n       * The unresolved alpha component.\n       */\n      alpha: TokenOrValue[];\n      /**\n       * The blue component.\n       */\n      b: number;\n      /**\n       * The green component.\n       */\n      g: number;\n      /**\n       * The red component.\n       */\n      r: number;\n      type: \"rgb\";\n    }\n  | {\n      /**\n       * The unresolved alpha component.\n       */\n      alpha: TokenOrValue[];\n      /**\n       * The hue component.\n       */\n      h: number;\n      /**\n       * The lightness component.\n       */\n      l: number;\n      /**\n       * The saturation component.\n       */\n      s: number;\n      type: \"hsl\";\n    }\n  | {\n      /**\n       * The dark value.\n       */\n      dark: TokenOrValue[];\n      /**\n       * The light value.\n       */\n      light: TokenOrValue[];\n      type: \"light-dark\";\n    };\n/**\n * Defines where the class names referenced in the `composes` property are located.\n *\n * See [Composes](Composes).\n */\nexport type Specifier =\n  | {\n      type: \"global\";\n    }\n  | {\n      type: \"file\";\n      value: String;\n    }\n  | {\n      type: \"source-index\";\n      value: number;\n    };\n/**\n * A CSS [`<angle>`](https://www.w3.org/TR/css-values-4/#angles) value.\n *\n * Angles may be explicit or computed by `calc()`, but are always stored and serialized as their computed value.\n */\nexport type Angle =\n  | {\n      type: \"deg\";\n      value: number;\n    }\n  | {\n      type: \"rad\";\n      value: number;\n    }\n  | {\n      type: \"grad\";\n      value: number;\n    }\n  | {\n      type: \"turn\";\n      value: number;\n    };\n/**\n * A CSS [`<time>`](https://www.w3.org/TR/css-values-4/#time) value, in either seconds or milliseconds.\n *\n * Time values may be explicit or computed by `calc()`, but are always stored and serialized as their computed value.\n */\nexport type Time =\n  | {\n      type: \"seconds\";\n      value: number;\n    }\n  | {\n      type: \"milliseconds\";\n      value: number;\n    };\n/**\n * A value for the [animation-name](https://drafts.csswg.org/css-animations/#animation-name) property.\n */\nexport type AnimationName =\n  | {\n      type: \"none\";\n    }\n  | {\n      type: \"ident\";\n      value: String;\n    }\n  | {\n      type: \"string\";\n      value: String;\n    };\n/**\n * A CSS environment variable name.\n */\nexport type EnvironmentVariableName =\n  | {\n      type: \"ua\";\n      value: UAEnvironmentVariable;\n    }\n  | {\n      /**\n       * CSS modules extension: the filename where the variable is defined. Only enabled when the CSS modules `dashed_idents` option is turned on.\n       */\n      from?: Specifier | null;\n      /**\n       * The referenced identifier.\n       */\n      ident: String;\n      type: \"custom\";\n    }\n  | {\n      type: \"unknown\";\n      value: String;\n    };\n/**\n * A UA-defined environment variable name.\n */\nexport type UAEnvironmentVariable =\n  | \"safe-area-inset-top\"\n  | \"safe-area-inset-right\"\n  | \"safe-area-inset-bottom\"\n  | \"safe-area-inset-left\"\n  | \"viewport-segment-width\"\n  | \"viewport-segment-height\"\n  | \"viewport-segment-top\"\n  | \"viewport-segment-left\"\n  | \"viewport-segment-bottom\"\n  | \"viewport-segment-right\";\n/**\n * A [comparator](https://drafts.csswg.org/mediaqueries/#typedef-mf-comparison) within a media query.\n */\nexport type MediaFeatureComparison = \"equal\" | \"greater-than\" | \"greater-than-equal\" | \"less-than\" | \"less-than-equal\";\n/**\n * A binary `and` or `or` operator.\n */\nexport type Operator = \"and\" | \"or\";\nexport type MediaType = string;\n/**\n * A [media query qualifier](https://drafts.csswg.org/mediaqueries/#mq-prefix).\n */\nexport type Qualifier = \"only\" | \"not\";\n/**\n * A [`<supports-condition>`](https://drafts.csswg.org/css-conditional-3/#typedef-supports-condition), as used in the `@supports` and `@import` rules.\n */\nexport type SupportsCondition =\n  | {\n      type: \"not\";\n      value: SupportsCondition;\n    }\n  | {\n      type: \"and\";\n      value: SupportsCondition[];\n    }\n  | {\n      type: \"or\";\n      value: SupportsCondition[];\n    }\n  | {\n      /**\n       * The property id for the declaration.\n       */\n      propertyId: PropertyId;\n      type: \"declaration\";\n      /**\n       * The raw value of the declaration.\n       */\n      value: String;\n    }\n  | {\n      type: \"selector\";\n      value: String;\n    }\n  | {\n      type: \"unknown\";\n      value: String;\n    };\nexport type PropertyId =\n  | {\n      property: \"background-color\";\n    }\n  | {\n      property: \"background-image\";\n    }\n  | {\n      property: \"background-position-x\";\n    }\n  | {\n      property: \"background-position-y\";\n    }\n  | {\n      property: \"background-position\";\n    }\n  | {\n      property: \"background-size\";\n    }\n  | {\n      property: \"background-repeat\";\n    }\n  | {\n      property: \"background-attachment\";\n    }\n  | {\n      property: \"background-clip\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"background-origin\";\n    }\n  | {\n      property: \"background\";\n    }\n  | {\n      property: \"box-shadow\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"opacity\";\n    }\n  | {\n      property: \"color\";\n    }\n  | {\n      property: \"display\";\n    }\n  | {\n      property: \"visibility\";\n    }\n  | {\n      property: \"width\";\n    }\n  | {\n      property: \"height\";\n    }\n  | {\n      property: \"min-width\";\n    }\n  | {\n      property: \"min-height\";\n    }\n  | {\n      property: \"max-width\";\n    }\n  | {\n      property: \"max-height\";\n    }\n  | {\n      property: \"block-size\";\n    }\n  | {\n      property: \"inline-size\";\n    }\n  | {\n      property: \"min-block-size\";\n    }\n  | {\n      property: \"min-inline-size\";\n    }\n  | {\n      property: \"max-block-size\";\n    }\n  | {\n      property: \"max-inline-size\";\n    }\n  | {\n      property: \"box-sizing\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"aspect-ratio\";\n    }\n  | {\n      property: \"overflow\";\n    }\n  | {\n      property: \"overflow-x\";\n    }\n  | {\n      property: \"overflow-y\";\n    }\n  | {\n      property: \"text-overflow\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"position\";\n    }\n  | {\n      property: \"top\";\n    }\n  | {\n      property: \"bottom\";\n    }\n  | {\n      property: \"left\";\n    }\n  | {\n      property: \"right\";\n    }\n  | {\n      property: \"inset-block-start\";\n    }\n  | {\n      property: \"inset-block-end\";\n    }\n  | {\n      property: \"inset-inline-start\";\n    }\n  | {\n      property: \"inset-inline-end\";\n    }\n  | {\n      property: \"inset-block\";\n    }\n  | {\n      property: \"inset-inline\";\n    }\n  | {\n      property: \"inset\";\n    }\n  | {\n      property: \"border-spacing\";\n    }\n  | {\n      property: \"border-top-color\";\n    }\n  | {\n      property: \"border-bottom-color\";\n    }\n  | {\n      property: \"border-left-color\";\n    }\n  | {\n      property: \"border-right-color\";\n    }\n  | {\n      property: \"border-block-start-color\";\n    }\n  | {\n      property: \"border-block-end-color\";\n    }\n  | {\n      property: \"border-inline-start-color\";\n    }\n  | {\n      property: \"border-inline-end-color\";\n    }\n  | {\n      property: \"border-top-style\";\n    }\n  | {\n      property: \"border-bottom-style\";\n    }\n  | {\n      property: \"border-left-style\";\n    }\n  | {\n      property: \"border-right-style\";\n    }\n  | {\n      property: \"border-block-start-style\";\n    }\n  | {\n      property: \"border-block-end-style\";\n    }\n  | {\n      property: \"border-inline-start-style\";\n    }\n  | {\n      property: \"border-inline-end-style\";\n    }\n  | {\n      property: \"border-top-width\";\n    }\n  | {\n      property: \"border-bottom-width\";\n    }\n  | {\n      property: \"border-left-width\";\n    }\n  | {\n      property: \"border-right-width\";\n    }\n  | {\n      property: \"border-block-start-width\";\n    }\n  | {\n      property: \"border-block-end-width\";\n    }\n  | {\n      property: \"border-inline-start-width\";\n    }\n  | {\n      property: \"border-inline-end-width\";\n    }\n  | {\n      property: \"border-top-left-radius\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"border-top-right-radius\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"border-bottom-left-radius\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"border-bottom-right-radius\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"border-start-start-radius\";\n    }\n  | {\n      property: \"border-start-end-radius\";\n    }\n  | {\n      property: \"border-end-start-radius\";\n    }\n  | {\n      property: \"border-end-end-radius\";\n    }\n  | {\n      property: \"border-radius\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"border-image-source\";\n    }\n  | {\n      property: \"border-image-outset\";\n    }\n  | {\n      property: \"border-image-repeat\";\n    }\n  | {\n      property: \"border-image-width\";\n    }\n  | {\n      property: \"border-image-slice\";\n    }\n  | {\n      property: \"border-image\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"border-color\";\n    }\n  | {\n      property: \"border-style\";\n    }\n  | {\n      property: \"border-width\";\n    }\n  | {\n      property: \"border-block-color\";\n    }\n  | {\n      property: \"border-block-style\";\n    }\n  | {\n      property: \"border-block-width\";\n    }\n  | {\n      property: \"border-inline-color\";\n    }\n  | {\n      property: \"border-inline-style\";\n    }\n  | {\n      property: \"border-inline-width\";\n    }\n  | {\n      property: \"border\";\n    }\n  | {\n      property: \"border-top\";\n    }\n  | {\n      property: \"border-bottom\";\n    }\n  | {\n      property: \"border-left\";\n    }\n  | {\n      property: \"border-right\";\n    }\n  | {\n      property: \"border-block\";\n    }\n  | {\n      property: \"border-block-start\";\n    }\n  | {\n      property: \"border-block-end\";\n    }\n  | {\n      property: \"border-inline\";\n    }\n  | {\n      property: \"border-inline-start\";\n    }\n  | {\n      property: \"border-inline-end\";\n    }\n  | {\n      property: \"outline\";\n    }\n  | {\n      property: \"outline-color\";\n    }\n  | {\n      property: \"outline-style\";\n    }\n  | {\n      property: \"outline-width\";\n    }\n  | {\n      property: \"flex-direction\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"flex-wrap\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"flex-flow\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"flex-grow\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"flex-shrink\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"flex-basis\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"flex\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"order\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"align-content\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"justify-content\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"place-content\";\n    }\n  | {\n      property: \"align-self\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"justify-self\";\n    }\n  | {\n      property: \"place-self\";\n    }\n  | {\n      property: \"align-items\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"justify-items\";\n    }\n  | {\n      property: \"place-items\";\n    }\n  | {\n      property: \"row-gap\";\n    }\n  | {\n      property: \"column-gap\";\n    }\n  | {\n      property: \"gap\";\n    }\n  | {\n      property: \"box-orient\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"box-direction\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"box-ordinal-group\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"box-align\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"box-flex\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"box-flex-group\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"box-pack\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"box-lines\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"flex-pack\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"flex-order\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"flex-align\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"flex-item-align\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"flex-line-pack\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"flex-positive\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"flex-negative\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"flex-preferred-size\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"grid-template-columns\";\n    }\n  | {\n      property: \"grid-template-rows\";\n    }\n  | {\n      property: \"grid-auto-columns\";\n    }\n  | {\n      property: \"grid-auto-rows\";\n    }\n  | {\n      property: \"grid-auto-flow\";\n    }\n  | {\n      property: \"grid-template-areas\";\n    }\n  | {\n      property: \"grid-template\";\n    }\n  | {\n      property: \"grid\";\n    }\n  | {\n      property: \"grid-row-start\";\n    }\n  | {\n      property: \"grid-row-end\";\n    }\n  | {\n      property: \"grid-column-start\";\n    }\n  | {\n      property: \"grid-column-end\";\n    }\n  | {\n      property: \"grid-row\";\n    }\n  | {\n      property: \"grid-column\";\n    }\n  | {\n      property: \"grid-area\";\n    }\n  | {\n      property: \"margin-top\";\n    }\n  | {\n      property: \"margin-bottom\";\n    }\n  | {\n      property: \"margin-left\";\n    }\n  | {\n      property: \"margin-right\";\n    }\n  | {\n      property: \"margin-block-start\";\n    }\n  | {\n      property: \"margin-block-end\";\n    }\n  | {\n      property: \"margin-inline-start\";\n    }\n  | {\n      property: \"margin-inline-end\";\n    }\n  | {\n      property: \"margin-block\";\n    }\n  | {\n      property: \"margin-inline\";\n    }\n  | {\n      property: \"margin\";\n    }\n  | {\n      property: \"padding-top\";\n    }\n  | {\n      property: \"padding-bottom\";\n    }\n  | {\n      property: \"padding-left\";\n    }\n  | {\n      property: \"padding-right\";\n    }\n  | {\n      property: \"padding-block-start\";\n    }\n  | {\n      property: \"padding-block-end\";\n    }\n  | {\n      property: \"padding-inline-start\";\n    }\n  | {\n      property: \"padding-inline-end\";\n    }\n  | {\n      property: \"padding-block\";\n    }\n  | {\n      property: \"padding-inline\";\n    }\n  | {\n      property: \"padding\";\n    }\n  | {\n      property: \"scroll-margin-top\";\n    }\n  | {\n      property: \"scroll-margin-bottom\";\n    }\n  | {\n      property: \"scroll-margin-left\";\n    }\n  | {\n      property: \"scroll-margin-right\";\n    }\n  | {\n      property: \"scroll-margin-block-start\";\n    }\n  | {\n      property: \"scroll-margin-block-end\";\n    }\n  | {\n      property: \"scroll-margin-inline-start\";\n    }\n  | {\n      property: \"scroll-margin-inline-end\";\n    }\n  | {\n      property: \"scroll-margin-block\";\n    }\n  | {\n      property: \"scroll-margin-inline\";\n    }\n  | {\n      property: \"scroll-margin\";\n    }\n  | {\n      property: \"scroll-padding-top\";\n    }\n  | {\n      property: \"scroll-padding-bottom\";\n    }\n  | {\n      property: \"scroll-padding-left\";\n    }\n  | {\n      property: \"scroll-padding-right\";\n    }\n  | {\n      property: \"scroll-padding-block-start\";\n    }\n  | {\n      property: \"scroll-padding-block-end\";\n    }\n  | {\n      property: \"scroll-padding-inline-start\";\n    }\n  | {\n      property: \"scroll-padding-inline-end\";\n    }\n  | {\n      property: \"scroll-padding-block\";\n    }\n  | {\n      property: \"scroll-padding-inline\";\n    }\n  | {\n      property: \"scroll-padding\";\n    }\n  | {\n      property: \"font-weight\";\n    }\n  | {\n      property: \"font-size\";\n    }\n  | {\n      property: \"font-stretch\";\n    }\n  | {\n      property: \"font-family\";\n    }\n  | {\n      property: \"font-style\";\n    }\n  | {\n      property: \"font-variant-caps\";\n    }\n  | {\n      property: \"line-height\";\n    }\n  | {\n      property: \"font\";\n    }\n  | {\n      property: \"vertical-align\";\n    }\n  | {\n      property: \"font-palette\";\n    }\n  | {\n      property: \"transition-property\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"transition-duration\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"transition-delay\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"transition-timing-function\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"transition\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"animation-name\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"animation-duration\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"animation-timing-function\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"animation-iteration-count\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"animation-direction\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"animation-play-state\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"animation-delay\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"animation-fill-mode\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"animation-composition\";\n    }\n  | {\n      property: \"animation-timeline\";\n    }\n  | {\n      property: \"animation-range-start\";\n    }\n  | {\n      property: \"animation-range-end\";\n    }\n  | {\n      property: \"animation-range\";\n    }\n  | {\n      property: \"animation\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"transform\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"transform-origin\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"transform-style\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"transform-box\";\n    }\n  | {\n      property: \"backface-visibility\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"perspective\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"perspective-origin\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"translate\";\n    }\n  | {\n      property: \"rotate\";\n    }\n  | {\n      property: \"scale\";\n    }\n  | {\n      property: \"text-transform\";\n    }\n  | {\n      property: \"white-space\";\n    }\n  | {\n      property: \"tab-size\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"word-break\";\n    }\n  | {\n      property: \"line-break\";\n    }\n  | {\n      property: \"hyphens\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"overflow-wrap\";\n    }\n  | {\n      property: \"word-wrap\";\n    }\n  | {\n      property: \"text-align\";\n    }\n  | {\n      property: \"text-align-last\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"text-justify\";\n    }\n  | {\n      property: \"word-spacing\";\n    }\n  | {\n      property: \"letter-spacing\";\n    }\n  | {\n      property: \"text-indent\";\n    }\n  | {\n      property: \"text-decoration-line\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"text-decoration-style\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"text-decoration-color\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"text-decoration-thickness\";\n    }\n  | {\n      property: \"text-decoration\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"text-decoration-skip-ink\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"text-emphasis-style\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"text-emphasis-color\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"text-emphasis\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"text-emphasis-position\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"text-shadow\";\n    }\n  | {\n      property: \"text-size-adjust\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"direction\";\n    }\n  | {\n      property: \"unicode-bidi\";\n    }\n  | {\n      property: \"box-decoration-break\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"resize\";\n    }\n  | {\n      property: \"cursor\";\n    }\n  | {\n      property: \"caret-color\";\n    }\n  | {\n      property: \"caret-shape\";\n    }\n  | {\n      property: \"caret\";\n    }\n  | {\n      property: \"user-select\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"accent-color\";\n    }\n  | {\n      property: \"appearance\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"list-style-type\";\n    }\n  | {\n      property: \"list-style-image\";\n    }\n  | {\n      property: \"list-style-position\";\n    }\n  | {\n      property: \"list-style\";\n    }\n  | {\n      property: \"marker-side\";\n    }\n  | {\n      property: \"composes\";\n    }\n  | {\n      property: \"fill\";\n    }\n  | {\n      property: \"fill-rule\";\n    }\n  | {\n      property: \"fill-opacity\";\n    }\n  | {\n      property: \"stroke\";\n    }\n  | {\n      property: \"stroke-opacity\";\n    }\n  | {\n      property: \"stroke-width\";\n    }\n  | {\n      property: \"stroke-linecap\";\n    }\n  | {\n      property: \"stroke-linejoin\";\n    }\n  | {\n      property: \"stroke-miterlimit\";\n    }\n  | {\n      property: \"stroke-dasharray\";\n    }\n  | {\n      property: \"stroke-dashoffset\";\n    }\n  | {\n      property: \"marker-start\";\n    }\n  | {\n      property: \"marker-mid\";\n    }\n  | {\n      property: \"marker-end\";\n    }\n  | {\n      property: \"marker\";\n    }\n  | {\n      property: \"color-interpolation\";\n    }\n  | {\n      property: \"color-interpolation-filters\";\n    }\n  | {\n      property: \"color-rendering\";\n    }\n  | {\n      property: \"shape-rendering\";\n    }\n  | {\n      property: \"text-rendering\";\n    }\n  | {\n      property: \"image-rendering\";\n    }\n  | {\n      property: \"clip-path\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"clip-rule\";\n    }\n  | {\n      property: \"mask-image\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"mask-mode\";\n    }\n  | {\n      property: \"mask-repeat\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"mask-position-x\";\n    }\n  | {\n      property: \"mask-position-y\";\n    }\n  | {\n      property: \"mask-position\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"mask-clip\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"mask-origin\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"mask-size\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"mask-composite\";\n    }\n  | {\n      property: \"mask-type\";\n    }\n  | {\n      property: \"mask\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"mask-border-source\";\n    }\n  | {\n      property: \"mask-border-mode\";\n    }\n  | {\n      property: \"mask-border-slice\";\n    }\n  | {\n      property: \"mask-border-width\";\n    }\n  | {\n      property: \"mask-border-outset\";\n    }\n  | {\n      property: \"mask-border-repeat\";\n    }\n  | {\n      property: \"mask-border\";\n    }\n  | {\n      property: \"-webkit-mask-composite\";\n    }\n  | {\n      property: \"mask-source-type\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"mask-box-image\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"mask-box-image-source\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"mask-box-image-slice\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"mask-box-image-width\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"mask-box-image-outset\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"mask-box-image-repeat\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"filter\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"backdrop-filter\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"z-index\";\n    }\n  | {\n      property: \"container-type\";\n    }\n  | {\n      property: \"container-name\";\n    }\n  | {\n      property: \"container\";\n    }\n  | {\n      property: \"view-transition-name\";\n    }\n  | {\n      property: \"view-transition-class\";\n    }\n  | {\n      property: \"view-transition-group\";\n    }\n  | {\n      property: \"color-scheme\";\n    }\n  | {\n      property: \"print-color-adjust\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"all\";\n    }\n  | {\n      property: string;\n    };\nexport type Prefix = \"none\" | \"webkit\" | \"moz\" | \"ms\" | \"o\";\nexport type VendorPrefix = Prefix[];\nexport type Declaration =\n  | {\n      property: \"background-color\";\n      value: CssColor;\n    }\n  | {\n      property: \"background-image\";\n      value: Image[];\n    }\n  | {\n      property: \"background-position-x\";\n      value: PositionComponentFor_HorizontalPositionKeyword[];\n    }\n  | {\n      property: \"background-position-y\";\n      value: PositionComponentFor_VerticalPositionKeyword[];\n    }\n  | {\n      property: \"background-position\";\n      value: BackgroundPosition[];\n    }\n  | {\n      property: \"background-size\";\n      value: BackgroundSize[];\n    }\n  | {\n      property: \"background-repeat\";\n      value: BackgroundRepeat[];\n    }\n  | {\n      property: \"background-attachment\";\n      value: BackgroundAttachment[];\n    }\n  | {\n      property: \"background-clip\";\n      value: BackgroundClip[];\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"background-origin\";\n      value: BackgroundOrigin[];\n    }\n  | {\n      property: \"background\";\n      value: Background[];\n    }\n  | {\n      property: \"box-shadow\";\n      value: BoxShadow[];\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"opacity\";\n      value: number;\n    }\n  | {\n      property: \"color\";\n      value: CssColor;\n    }\n  | {\n      property: \"display\";\n      value: Display;\n    }\n  | {\n      property: \"visibility\";\n      value: Visibility;\n    }\n  | {\n      property: \"width\";\n      value: Size;\n    }\n  | {\n      property: \"height\";\n      value: Size;\n    }\n  | {\n      property: \"min-width\";\n      value: Size;\n    }\n  | {\n      property: \"min-height\";\n      value: Size;\n    }\n  | {\n      property: \"max-width\";\n      value: MaxSize;\n    }\n  | {\n      property: \"max-height\";\n      value: MaxSize;\n    }\n  | {\n      property: \"block-size\";\n      value: Size;\n    }\n  | {\n      property: \"inline-size\";\n      value: Size;\n    }\n  | {\n      property: \"min-block-size\";\n      value: Size;\n    }\n  | {\n      property: \"min-inline-size\";\n      value: Size;\n    }\n  | {\n      property: \"max-block-size\";\n      value: MaxSize;\n    }\n  | {\n      property: \"max-inline-size\";\n      value: MaxSize;\n    }\n  | {\n      property: \"box-sizing\";\n      value: BoxSizing;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"aspect-ratio\";\n      value: AspectRatio;\n    }\n  | {\n      property: \"overflow\";\n      value: Overflow;\n    }\n  | {\n      property: \"overflow-x\";\n      value: OverflowKeyword;\n    }\n  | {\n      property: \"overflow-y\";\n      value: OverflowKeyword;\n    }\n  | {\n      property: \"text-overflow\";\n      value: TextOverflow;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"position\";\n      value: Position2;\n    }\n  | {\n      property: \"top\";\n      value: LengthPercentageOrAuto;\n    }\n  | {\n      property: \"bottom\";\n      value: LengthPercentageOrAuto;\n    }\n  | {\n      property: \"left\";\n      value: LengthPercentageOrAuto;\n    }\n  | {\n      property: \"right\";\n      value: LengthPercentageOrAuto;\n    }\n  | {\n      property: \"inset-block-start\";\n      value: LengthPercentageOrAuto;\n    }\n  | {\n      property: \"inset-block-end\";\n      value: LengthPercentageOrAuto;\n    }\n  | {\n      property: \"inset-inline-start\";\n      value: LengthPercentageOrAuto;\n    }\n  | {\n      property: \"inset-inline-end\";\n      value: LengthPercentageOrAuto;\n    }\n  | {\n      property: \"inset-block\";\n      value: InsetBlock;\n    }\n  | {\n      property: \"inset-inline\";\n      value: InsetInline;\n    }\n  | {\n      property: \"inset\";\n      value: Inset;\n    }\n  | {\n      property: \"border-spacing\";\n      value: Size2DFor_Length;\n    }\n  | {\n      property: \"border-top-color\";\n      value: CssColor;\n    }\n  | {\n      property: \"border-bottom-color\";\n      value: CssColor;\n    }\n  | {\n      property: \"border-left-color\";\n      value: CssColor;\n    }\n  | {\n      property: \"border-right-color\";\n      value: CssColor;\n    }\n  | {\n      property: \"border-block-start-color\";\n      value: CssColor;\n    }\n  | {\n      property: \"border-block-end-color\";\n      value: CssColor;\n    }\n  | {\n      property: \"border-inline-start-color\";\n      value: CssColor;\n    }\n  | {\n      property: \"border-inline-end-color\";\n      value: CssColor;\n    }\n  | {\n      property: \"border-top-style\";\n      value: LineStyle;\n    }\n  | {\n      property: \"border-bottom-style\";\n      value: LineStyle;\n    }\n  | {\n      property: \"border-left-style\";\n      value: LineStyle;\n    }\n  | {\n      property: \"border-right-style\";\n      value: LineStyle;\n    }\n  | {\n      property: \"border-block-start-style\";\n      value: LineStyle;\n    }\n  | {\n      property: \"border-block-end-style\";\n      value: LineStyle;\n    }\n  | {\n      property: \"border-inline-start-style\";\n      value: LineStyle;\n    }\n  | {\n      property: \"border-inline-end-style\";\n      value: LineStyle;\n    }\n  | {\n      property: \"border-top-width\";\n      value: BorderSideWidth;\n    }\n  | {\n      property: \"border-bottom-width\";\n      value: BorderSideWidth;\n    }\n  | {\n      property: \"border-left-width\";\n      value: BorderSideWidth;\n    }\n  | {\n      property: \"border-right-width\";\n      value: BorderSideWidth;\n    }\n  | {\n      property: \"border-block-start-width\";\n      value: BorderSideWidth;\n    }\n  | {\n      property: \"border-block-end-width\";\n      value: BorderSideWidth;\n    }\n  | {\n      property: \"border-inline-start-width\";\n      value: BorderSideWidth;\n    }\n  | {\n      property: \"border-inline-end-width\";\n      value: BorderSideWidth;\n    }\n  | {\n      property: \"border-top-left-radius\";\n      value: Size2DFor_DimensionPercentageFor_LengthValue;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"border-top-right-radius\";\n      value: Size2DFor_DimensionPercentageFor_LengthValue;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"border-bottom-left-radius\";\n      value: Size2DFor_DimensionPercentageFor_LengthValue;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"border-bottom-right-radius\";\n      value: Size2DFor_DimensionPercentageFor_LengthValue;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"border-start-start-radius\";\n      value: Size2DFor_DimensionPercentageFor_LengthValue;\n    }\n  | {\n      property: \"border-start-end-radius\";\n      value: Size2DFor_DimensionPercentageFor_LengthValue;\n    }\n  | {\n      property: \"border-end-start-radius\";\n      value: Size2DFor_DimensionPercentageFor_LengthValue;\n    }\n  | {\n      property: \"border-end-end-radius\";\n      value: Size2DFor_DimensionPercentageFor_LengthValue;\n    }\n  | {\n      property: \"border-radius\";\n      value: BorderRadius;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"border-image-source\";\n      value: Image;\n    }\n  | {\n      property: \"border-image-outset\";\n      value: RectFor_LengthOrNumber;\n    }\n  | {\n      property: \"border-image-repeat\";\n      value: BorderImageRepeat;\n    }\n  | {\n      property: \"border-image-width\";\n      value: RectFor_BorderImageSideWidth;\n    }\n  | {\n      property: \"border-image-slice\";\n      value: BorderImageSlice;\n    }\n  | {\n      property: \"border-image\";\n      value: BorderImage;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"border-color\";\n      value: BorderColor;\n    }\n  | {\n      property: \"border-style\";\n      value: BorderStyle;\n    }\n  | {\n      property: \"border-width\";\n      value: BorderWidth;\n    }\n  | {\n      property: \"border-block-color\";\n      value: BorderBlockColor;\n    }\n  | {\n      property: \"border-block-style\";\n      value: BorderBlockStyle;\n    }\n  | {\n      property: \"border-block-width\";\n      value: BorderBlockWidth;\n    }\n  | {\n      property: \"border-inline-color\";\n      value: BorderInlineColor;\n    }\n  | {\n      property: \"border-inline-style\";\n      value: BorderInlineStyle;\n    }\n  | {\n      property: \"border-inline-width\";\n      value: BorderInlineWidth;\n    }\n  | {\n      property: \"border\";\n      value: GenericBorderFor_LineStyle;\n    }\n  | {\n      property: \"border-top\";\n      value: GenericBorderFor_LineStyle;\n    }\n  | {\n      property: \"border-bottom\";\n      value: GenericBorderFor_LineStyle;\n    }\n  | {\n      property: \"border-left\";\n      value: GenericBorderFor_LineStyle;\n    }\n  | {\n      property: \"border-right\";\n      value: GenericBorderFor_LineStyle;\n    }\n  | {\n      property: \"border-block\";\n      value: GenericBorderFor_LineStyle;\n    }\n  | {\n      property: \"border-block-start\";\n      value: GenericBorderFor_LineStyle;\n    }\n  | {\n      property: \"border-block-end\";\n      value: GenericBorderFor_LineStyle;\n    }\n  | {\n      property: \"border-inline\";\n      value: GenericBorderFor_LineStyle;\n    }\n  | {\n      property: \"border-inline-start\";\n      value: GenericBorderFor_LineStyle;\n    }\n  | {\n      property: \"border-inline-end\";\n      value: GenericBorderFor_LineStyle;\n    }\n  | {\n      property: \"outline\";\n      value: GenericBorderFor_OutlineStyleAnd_11;\n    }\n  | {\n      property: \"outline-color\";\n      value: CssColor;\n    }\n  | {\n      property: \"outline-style\";\n      value: OutlineStyle;\n    }\n  | {\n      property: \"outline-width\";\n      value: BorderSideWidth;\n    }\n  | {\n      property: \"flex-direction\";\n      value: FlexDirection;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"flex-wrap\";\n      value: FlexWrap;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"flex-flow\";\n      value: FlexFlow;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"flex-grow\";\n      value: number;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"flex-shrink\";\n      value: number;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"flex-basis\";\n      value: LengthPercentageOrAuto;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"flex\";\n      value: Flex;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"order\";\n      value: number;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"align-content\";\n      value: AlignContent;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"justify-content\";\n      value: JustifyContent;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"place-content\";\n      value: PlaceContent;\n    }\n  | {\n      property: \"align-self\";\n      value: AlignSelf;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"justify-self\";\n      value: JustifySelf;\n    }\n  | {\n      property: \"place-self\";\n      value: PlaceSelf;\n    }\n  | {\n      property: \"align-items\";\n      value: AlignItems;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"justify-items\";\n      value: JustifyItems;\n    }\n  | {\n      property: \"place-items\";\n      value: PlaceItems;\n    }\n  | {\n      property: \"row-gap\";\n      value: GapValue;\n    }\n  | {\n      property: \"column-gap\";\n      value: GapValue;\n    }\n  | {\n      property: \"gap\";\n      value: Gap;\n    }\n  | {\n      property: \"box-orient\";\n      value: BoxOrient;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"box-direction\";\n      value: BoxDirection;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"box-ordinal-group\";\n      value: number;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"box-align\";\n      value: BoxAlign;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"box-flex\";\n      value: number;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"box-flex-group\";\n      value: number;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"box-pack\";\n      value: BoxPack;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"box-lines\";\n      value: BoxLines;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"flex-pack\";\n      value: FlexPack;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"flex-order\";\n      value: number;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"flex-align\";\n      value: BoxAlign;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"flex-item-align\";\n      value: FlexItemAlign;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"flex-line-pack\";\n      value: FlexLinePack;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"flex-positive\";\n      value: number;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"flex-negative\";\n      value: number;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"flex-preferred-size\";\n      value: LengthPercentageOrAuto;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"grid-template-columns\";\n      value: TrackSizing;\n    }\n  | {\n      property: \"grid-template-rows\";\n      value: TrackSizing;\n    }\n  | {\n      property: \"grid-auto-columns\";\n      value: TrackSize[];\n    }\n  | {\n      property: \"grid-auto-rows\";\n      value: TrackSize[];\n    }\n  | {\n      property: \"grid-auto-flow\";\n      value: GridAutoFlow;\n    }\n  | {\n      property: \"grid-template-areas\";\n      value: GridTemplateAreas;\n    }\n  | {\n      property: \"grid-template\";\n      value: GridTemplate;\n    }\n  | {\n      property: \"grid\";\n      value: Grid;\n    }\n  | {\n      property: \"grid-row-start\";\n      value: GridLine;\n    }\n  | {\n      property: \"grid-row-end\";\n      value: GridLine;\n    }\n  | {\n      property: \"grid-column-start\";\n      value: GridLine;\n    }\n  | {\n      property: \"grid-column-end\";\n      value: GridLine;\n    }\n  | {\n      property: \"grid-row\";\n      value: GridRow;\n    }\n  | {\n      property: \"grid-column\";\n      value: GridColumn;\n    }\n  | {\n      property: \"grid-area\";\n      value: GridArea;\n    }\n  | {\n      property: \"margin-top\";\n      value: LengthPercentageOrAuto;\n    }\n  | {\n      property: \"margin-bottom\";\n      value: LengthPercentageOrAuto;\n    }\n  | {\n      property: \"margin-left\";\n      value: LengthPercentageOrAuto;\n    }\n  | {\n      property: \"margin-right\";\n      value: LengthPercentageOrAuto;\n    }\n  | {\n      property: \"margin-block-start\";\n      value: LengthPercentageOrAuto;\n    }\n  | {\n      property: \"margin-block-end\";\n      value: LengthPercentageOrAuto;\n    }\n  | {\n      property: \"margin-inline-start\";\n      value: LengthPercentageOrAuto;\n    }\n  | {\n      property: \"margin-inline-end\";\n      value: LengthPercentageOrAuto;\n    }\n  | {\n      property: \"margin-block\";\n      value: MarginBlock;\n    }\n  | {\n      property: \"margin-inline\";\n      value: MarginInline;\n    }\n  | {\n      property: \"margin\";\n      value: Margin;\n    }\n  | {\n      property: \"padding-top\";\n      value: LengthPercentageOrAuto;\n    }\n  | {\n      property: \"padding-bottom\";\n      value: LengthPercentageOrAuto;\n    }\n  | {\n      property: \"padding-left\";\n      value: LengthPercentageOrAuto;\n    }\n  | {\n      property: \"padding-right\";\n      value: LengthPercentageOrAuto;\n    }\n  | {\n      property: \"padding-block-start\";\n      value: LengthPercentageOrAuto;\n    }\n  | {\n      property: \"padding-block-end\";\n      value: LengthPercentageOrAuto;\n    }\n  | {\n      property: \"padding-inline-start\";\n      value: LengthPercentageOrAuto;\n    }\n  | {\n      property: \"padding-inline-end\";\n      value: LengthPercentageOrAuto;\n    }\n  | {\n      property: \"padding-block\";\n      value: PaddingBlock;\n    }\n  | {\n      property: \"padding-inline\";\n      value: PaddingInline;\n    }\n  | {\n      property: \"padding\";\n      value: Padding;\n    }\n  | {\n      property: \"scroll-margin-top\";\n      value: LengthPercentageOrAuto;\n    }\n  | {\n      property: \"scroll-margin-bottom\";\n      value: LengthPercentageOrAuto;\n    }\n  | {\n      property: \"scroll-margin-left\";\n      value: LengthPercentageOrAuto;\n    }\n  | {\n      property: \"scroll-margin-right\";\n      value: LengthPercentageOrAuto;\n    }\n  | {\n      property: \"scroll-margin-block-start\";\n      value: LengthPercentageOrAuto;\n    }\n  | {\n      property: \"scroll-margin-block-end\";\n      value: LengthPercentageOrAuto;\n    }\n  | {\n      property: \"scroll-margin-inline-start\";\n      value: LengthPercentageOrAuto;\n    }\n  | {\n      property: \"scroll-margin-inline-end\";\n      value: LengthPercentageOrAuto;\n    }\n  | {\n      property: \"scroll-margin-block\";\n      value: ScrollMarginBlock;\n    }\n  | {\n      property: \"scroll-margin-inline\";\n      value: ScrollMarginInline;\n    }\n  | {\n      property: \"scroll-margin\";\n      value: ScrollMargin;\n    }\n  | {\n      property: \"scroll-padding-top\";\n      value: LengthPercentageOrAuto;\n    }\n  | {\n      property: \"scroll-padding-bottom\";\n      value: LengthPercentageOrAuto;\n    }\n  | {\n      property: \"scroll-padding-left\";\n      value: LengthPercentageOrAuto;\n    }\n  | {\n      property: \"scroll-padding-right\";\n      value: LengthPercentageOrAuto;\n    }\n  | {\n      property: \"scroll-padding-block-start\";\n      value: LengthPercentageOrAuto;\n    }\n  | {\n      property: \"scroll-padding-block-end\";\n      value: LengthPercentageOrAuto;\n    }\n  | {\n      property: \"scroll-padding-inline-start\";\n      value: LengthPercentageOrAuto;\n    }\n  | {\n      property: \"scroll-padding-inline-end\";\n      value: LengthPercentageOrAuto;\n    }\n  | {\n      property: \"scroll-padding-block\";\n      value: ScrollPaddingBlock;\n    }\n  | {\n      property: \"scroll-padding-inline\";\n      value: ScrollPaddingInline;\n    }\n  | {\n      property: \"scroll-padding\";\n      value: ScrollPadding;\n    }\n  | {\n      property: \"font-weight\";\n      value: FontWeight;\n    }\n  | {\n      property: \"font-size\";\n      value: FontSize;\n    }\n  | {\n      property: \"font-stretch\";\n      value: FontStretch;\n    }\n  | {\n      property: \"font-family\";\n      value: FontFamily[];\n    }\n  | {\n      property: \"font-style\";\n      value: FontStyle;\n    }\n  | {\n      property: \"font-variant-caps\";\n      value: FontVariantCaps;\n    }\n  | {\n      property: \"line-height\";\n      value: LineHeight;\n    }\n  | {\n      property: \"font\";\n      value: Font;\n    }\n  | {\n      property: \"vertical-align\";\n      value: VerticalAlign;\n    }\n  | {\n      property: \"font-palette\";\n      value: DashedIdentReference;\n    }\n  | {\n      property: \"transition-property\";\n      value: PropertyId[];\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"transition-duration\";\n      value: Time[];\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"transition-delay\";\n      value: Time[];\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"transition-timing-function\";\n      value: EasingFunction[];\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"transition\";\n      value: Transition[];\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"animation-name\";\n      value: AnimationName[];\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"animation-duration\";\n      value: Time[];\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"animation-timing-function\";\n      value: EasingFunction[];\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"animation-iteration-count\";\n      value: AnimationIterationCount[];\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"animation-direction\";\n      value: AnimationDirection[];\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"animation-play-state\";\n      value: AnimationPlayState[];\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"animation-delay\";\n      value: Time[];\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"animation-fill-mode\";\n      value: AnimationFillMode[];\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"animation-composition\";\n      value: AnimationComposition[];\n    }\n  | {\n      property: \"animation-timeline\";\n      value: AnimationTimeline[];\n    }\n  | {\n      property: \"animation-range-start\";\n      value: AnimationRangeStart[];\n    }\n  | {\n      property: \"animation-range-end\";\n      value: AnimationRangeEnd[];\n    }\n  | {\n      property: \"animation-range\";\n      value: AnimationRange[];\n    }\n  | {\n      property: \"animation\";\n      value: Animation[];\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"transform\";\n      value: Transform[];\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"transform-origin\";\n      value: Position;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"transform-style\";\n      value: TransformStyle;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"transform-box\";\n      value: TransformBox;\n    }\n  | {\n      property: \"backface-visibility\";\n      value: BackfaceVisibility;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"perspective\";\n      value: Perspective;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"perspective-origin\";\n      value: Position;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"translate\";\n      value: Translate;\n    }\n  | {\n      property: \"rotate\";\n      value: Rotate;\n    }\n  | {\n      property: \"scale\";\n      value: Scale;\n    }\n  | {\n      property: \"text-transform\";\n      value: TextTransform;\n    }\n  | {\n      property: \"white-space\";\n      value: WhiteSpace;\n    }\n  | {\n      property: \"tab-size\";\n      value: LengthOrNumber;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"word-break\";\n      value: WordBreak;\n    }\n  | {\n      property: \"line-break\";\n      value: LineBreak;\n    }\n  | {\n      property: \"hyphens\";\n      value: Hyphens;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"overflow-wrap\";\n      value: OverflowWrap;\n    }\n  | {\n      property: \"word-wrap\";\n      value: OverflowWrap;\n    }\n  | {\n      property: \"text-align\";\n      value: TextAlign;\n    }\n  | {\n      property: \"text-align-last\";\n      value: TextAlignLast;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"text-justify\";\n      value: TextJustify;\n    }\n  | {\n      property: \"word-spacing\";\n      value: Spacing;\n    }\n  | {\n      property: \"letter-spacing\";\n      value: Spacing;\n    }\n  | {\n      property: \"text-indent\";\n      value: TextIndent;\n    }\n  | {\n      property: \"text-decoration-line\";\n      value: TextDecorationLine;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"text-decoration-style\";\n      value: TextDecorationStyle;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"text-decoration-color\";\n      value: CssColor;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"text-decoration-thickness\";\n      value: TextDecorationThickness;\n    }\n  | {\n      property: \"text-decoration\";\n      value: TextDecoration;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"text-decoration-skip-ink\";\n      value: TextDecorationSkipInk;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"text-emphasis-style\";\n      value: TextEmphasisStyle;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"text-emphasis-color\";\n      value: CssColor;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"text-emphasis\";\n      value: TextEmphasis;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"text-emphasis-position\";\n      value: TextEmphasisPosition;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"text-shadow\";\n      value: TextShadow[];\n    }\n  | {\n      property: \"text-size-adjust\";\n      value: TextSizeAdjust;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"direction\";\n      value: Direction2;\n    }\n  | {\n      property: \"unicode-bidi\";\n      value: UnicodeBidi;\n    }\n  | {\n      property: \"box-decoration-break\";\n      value: BoxDecorationBreak;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"resize\";\n      value: Resize;\n    }\n  | {\n      property: \"cursor\";\n      value: Cursor;\n    }\n  | {\n      property: \"caret-color\";\n      value: ColorOrAuto;\n    }\n  | {\n      property: \"caret-shape\";\n      value: CaretShape;\n    }\n  | {\n      property: \"caret\";\n      value: Caret;\n    }\n  | {\n      property: \"user-select\";\n      value: UserSelect;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"accent-color\";\n      value: ColorOrAuto;\n    }\n  | {\n      property: \"appearance\";\n      value: Appearance;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"list-style-type\";\n      value: ListStyleType;\n    }\n  | {\n      property: \"list-style-image\";\n      value: Image;\n    }\n  | {\n      property: \"list-style-position\";\n      value: ListStylePosition;\n    }\n  | {\n      property: \"list-style\";\n      value: ListStyle;\n    }\n  | {\n      property: \"marker-side\";\n      value: MarkerSide;\n    }\n  | {\n      property: \"composes\";\n      value: Composes;\n    }\n  | {\n      property: \"fill\";\n      value: SVGPaint;\n    }\n  | {\n      property: \"fill-rule\";\n      value: FillRule;\n    }\n  | {\n      property: \"fill-opacity\";\n      value: number;\n    }\n  | {\n      property: \"stroke\";\n      value: SVGPaint;\n    }\n  | {\n      property: \"stroke-opacity\";\n      value: number;\n    }\n  | {\n      property: \"stroke-width\";\n      value: DimensionPercentageFor_LengthValue;\n    }\n  | {\n      property: \"stroke-linecap\";\n      value: StrokeLinecap;\n    }\n  | {\n      property: \"stroke-linejoin\";\n      value: StrokeLinejoin;\n    }\n  | {\n      property: \"stroke-miterlimit\";\n      value: number;\n    }\n  | {\n      property: \"stroke-dasharray\";\n      value: StrokeDasharray;\n    }\n  | {\n      property: \"stroke-dashoffset\";\n      value: DimensionPercentageFor_LengthValue;\n    }\n  | {\n      property: \"marker-start\";\n      value: Marker;\n    }\n  | {\n      property: \"marker-mid\";\n      value: Marker;\n    }\n  | {\n      property: \"marker-end\";\n      value: Marker;\n    }\n  | {\n      property: \"marker\";\n      value: Marker;\n    }\n  | {\n      property: \"color-interpolation\";\n      value: ColorInterpolation;\n    }\n  | {\n      property: \"color-interpolation-filters\";\n      value: ColorInterpolation;\n    }\n  | {\n      property: \"color-rendering\";\n      value: ColorRendering;\n    }\n  | {\n      property: \"shape-rendering\";\n      value: ShapeRendering;\n    }\n  | {\n      property: \"text-rendering\";\n      value: TextRendering;\n    }\n  | {\n      property: \"image-rendering\";\n      value: ImageRendering;\n    }\n  | {\n      property: \"clip-path\";\n      value: ClipPath;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"clip-rule\";\n      value: FillRule;\n    }\n  | {\n      property: \"mask-image\";\n      value: Image[];\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"mask-mode\";\n      value: MaskMode[];\n    }\n  | {\n      property: \"mask-repeat\";\n      value: BackgroundRepeat[];\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"mask-position-x\";\n      value: PositionComponentFor_HorizontalPositionKeyword[];\n    }\n  | {\n      property: \"mask-position-y\";\n      value: PositionComponentFor_VerticalPositionKeyword[];\n    }\n  | {\n      property: \"mask-position\";\n      value: Position[];\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"mask-clip\";\n      value: MaskClip[];\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"mask-origin\";\n      value: GeometryBox[];\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"mask-size\";\n      value: BackgroundSize[];\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"mask-composite\";\n      value: MaskComposite[];\n    }\n  | {\n      property: \"mask-type\";\n      value: MaskType;\n    }\n  | {\n      property: \"mask\";\n      value: Mask[];\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"mask-border-source\";\n      value: Image;\n    }\n  | {\n      property: \"mask-border-mode\";\n      value: MaskBorderMode;\n    }\n  | {\n      property: \"mask-border-slice\";\n      value: BorderImageSlice;\n    }\n  | {\n      property: \"mask-border-width\";\n      value: RectFor_BorderImageSideWidth;\n    }\n  | {\n      property: \"mask-border-outset\";\n      value: RectFor_LengthOrNumber;\n    }\n  | {\n      property: \"mask-border-repeat\";\n      value: BorderImageRepeat;\n    }\n  | {\n      property: \"mask-border\";\n      value: MaskBorder;\n    }\n  | {\n      property: \"-webkit-mask-composite\";\n      value: WebKitMaskComposite[];\n    }\n  | {\n      property: \"mask-source-type\";\n      value: WebKitMaskSourceType[];\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"mask-box-image\";\n      value: BorderImage;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"mask-box-image-source\";\n      value: Image;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"mask-box-image-slice\";\n      value: BorderImageSlice;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"mask-box-image-width\";\n      value: RectFor_BorderImageSideWidth;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"mask-box-image-outset\";\n      value: RectFor_LengthOrNumber;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"mask-box-image-repeat\";\n      value: BorderImageRepeat;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"filter\";\n      value: FilterList;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"backdrop-filter\";\n      value: FilterList;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"z-index\";\n      value: ZIndex;\n    }\n  | {\n      property: \"container-type\";\n      value: ContainerType;\n    }\n  | {\n      property: \"container-name\";\n      value: ContainerNameList;\n    }\n  | {\n      property: \"container\";\n      value: Container;\n    }\n  | {\n      property: \"view-transition-name\";\n      value: ViewTransitionName;\n    }\n  | {\n      property: \"view-transition-class\";\n      value: NoneOrCustomIdentList;\n    }\n  | {\n      property: \"view-transition-group\";\n      value: ViewTransitionGroup;\n    }\n  | {\n      property: \"color-scheme\";\n      value: ColorScheme;\n    }\n  | {\n      property: \"print-color-adjust\";\n      value: PrintColorAdjust;\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      property: \"all\";\n      value: CSSWideKeyword;\n    }\n  | {\n      property: \"unparsed\";\n      value: UnparsedProperty;\n    }\n  | {\n      property: \"custom\";\n      value: CustomProperty;\n    };\n/**\n * A CSS [`<image>`](https://www.w3.org/TR/css-images-3/#image-values) value.\n */\nexport type Image =\n  | {\n      type: \"none\";\n    }\n  | {\n      type: \"url\";\n      value: Url;\n    }\n  | {\n      type: \"gradient\";\n      value: Gradient;\n    }\n  | {\n      type: \"image-set\";\n      value: ImageSet;\n    };\n/**\n * A CSS [`<gradient>`](https://www.w3.org/TR/css-images-3/#gradients) value.\n */\nexport type Gradient =\n  | {\n      /**\n       * The direction of the gradient.\n       */\n      direction: LineDirection;\n      /**\n       * The color stops and transition hints for the gradient.\n       */\n      items: GradientItemFor_DimensionPercentageFor_LengthValue[];\n      type: \"linear\";\n      /**\n       * The vendor prefixes for the gradient.\n       */\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      /**\n       * The direction of the gradient.\n       */\n      direction: LineDirection;\n      /**\n       * The color stops and transition hints for the gradient.\n       */\n      items: GradientItemFor_DimensionPercentageFor_LengthValue[];\n      type: \"repeating-linear\";\n      /**\n       * The vendor prefixes for the gradient.\n       */\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      /**\n       * The color stops and transition hints for the gradient.\n       */\n      items: GradientItemFor_DimensionPercentageFor_LengthValue[];\n      /**\n       * The position of the gradient.\n       */\n      position: Position;\n      /**\n       * The shape of the gradient.\n       */\n      shape: EndingShape;\n      type: \"radial\";\n      /**\n       * The vendor prefixes for the gradient.\n       */\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      /**\n       * The color stops and transition hints for the gradient.\n       */\n      items: GradientItemFor_DimensionPercentageFor_LengthValue[];\n      /**\n       * The position of the gradient.\n       */\n      position: Position;\n      /**\n       * The shape of the gradient.\n       */\n      shape: EndingShape;\n      type: \"repeating-radial\";\n      /**\n       * The vendor prefixes for the gradient.\n       */\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      /**\n       * The angle of the gradient.\n       */\n      angle: Angle;\n      /**\n       * The color stops and transition hints for the gradient.\n       */\n      items: GradientItemFor_DimensionPercentageFor_Angle[];\n      /**\n       * The position of the gradient.\n       */\n      position: Position;\n      type: \"conic\";\n    }\n  | {\n      /**\n       * The angle of the gradient.\n       */\n      angle: Angle;\n      /**\n       * The color stops and transition hints for the gradient.\n       */\n      items: GradientItemFor_DimensionPercentageFor_Angle[];\n      /**\n       * The position of the gradient.\n       */\n      position: Position;\n      type: \"repeating-conic\";\n    }\n  | (\n      | {\n          type: \"webkit-gradient\";\n          /**\n           * The starting point of the gradient.\n           */\n          from: WebKitGradientPoint;\n          kind: \"linear\";\n          /**\n           * The color stops in the gradient.\n           */\n          stops: WebKitColorStop[];\n          /**\n           * The ending point of the gradient.\n           */\n          to: WebKitGradientPoint;\n        }\n      | {\n          type: \"webkit-gradient\";\n          /**\n           * The starting point of the gradient.\n           */\n          from: WebKitGradientPoint;\n          kind: \"radial\";\n          /**\n           * The starting radius of the gradient.\n           */\n          r0: number;\n          /**\n           * The ending radius of the gradient.\n           */\n          r1: number;\n          /**\n           * The color stops in the gradient.\n           */\n          stops: WebKitColorStop[];\n          /**\n           * The ending point of the gradient.\n           */\n          to: WebKitGradientPoint;\n        }\n    );\n/**\n * The direction of a CSS `linear-gradient()`.\n *\n * See [LinearGradient](LinearGradient).\n */\nexport type LineDirection =\n  | {\n      type: \"angle\";\n      value: Angle;\n    }\n  | {\n      type: \"horizontal\";\n      value: HorizontalPositionKeyword;\n    }\n  | {\n      type: \"vertical\";\n      value: VerticalPositionKeyword;\n    }\n  | {\n      /**\n       * A horizontal position keyword, e.g. `left` or `right.\n       */\n      horizontal: HorizontalPositionKeyword;\n      type: \"corner\";\n      /**\n       * A vertical posision keyword, e.g. `top` or `bottom`.\n       */\n      vertical: VerticalPositionKeyword;\n    };\n/**\n * A horizontal position keyword.\n */\nexport type HorizontalPositionKeyword = \"left\" | \"right\";\n/**\n * A vertical position keyword.\n */\nexport type VerticalPositionKeyword = \"top\" | \"bottom\";\n/**\n * Either a color stop or interpolation hint within a gradient.\n *\n * This type is generic, and items may be either a [LengthPercentage](super::length::LengthPercentage) or [Angle](super::angle::Angle) depending on what type of gradient it is within.\n */\nexport type GradientItemFor_DimensionPercentageFor_LengthValue =\n  | {\n      /**\n       * The color of the color stop.\n       */\n      color: CssColor;\n      /**\n       * The position of the color stop.\n       */\n      position?: DimensionPercentageFor_LengthValue | null;\n      type: \"color-stop\";\n    }\n  | {\n      type: \"hint\";\n      value: DimensionPercentageFor_LengthValue;\n    };\n/**\n * A generic type that allows any kind of dimension and percentage to be used standalone or mixed within a `calc()` expression.\n *\n * <https://drafts.csswg.org/css-values-4/#mixed-percentages>\n */\nexport type DimensionPercentageFor_LengthValue =\n  | {\n      type: \"dimension\";\n      value: LengthValue;\n    }\n  | {\n      type: \"percentage\";\n      value: number;\n    }\n  | {\n      type: \"calc\";\n      value: CalcFor_DimensionPercentageFor_LengthValue;\n    };\n/**\n * A mathematical expression used within the [`calc()`](https://www.w3.org/TR/css-values-4/#calc-func) function.\n *\n * This type supports generic value types. Values such as [Length](super::length::Length), [Percentage](super::percentage::Percentage), [Time](super::time::Time), and [Angle](super::angle::Angle) support `calc()` expressions.\n */\nexport type CalcFor_DimensionPercentageFor_LengthValue =\n  | {\n      type: \"value\";\n      value: DimensionPercentageFor_LengthValue;\n    }\n  | {\n      type: \"number\";\n      value: number;\n    }\n  | {\n      type: \"sum\";\n      /**\n       * @minItems 2\n       * @maxItems 2\n       */\n      value: [CalcFor_DimensionPercentageFor_LengthValue, CalcFor_DimensionPercentageFor_LengthValue];\n    }\n  | {\n      type: \"product\";\n      /**\n       * @minItems 2\n       * @maxItems 2\n       */\n      value: [number, CalcFor_DimensionPercentageFor_LengthValue];\n    }\n  | {\n      type: \"function\";\n      value: MathFunctionFor_DimensionPercentageFor_LengthValue;\n    };\n/**\n * A CSS [math function](https://www.w3.org/TR/css-values-4/#math-function).\n *\n * Math functions may be used in most properties and values that accept numeric values, including lengths, percentages, angles, times, etc.\n */\nexport type MathFunctionFor_DimensionPercentageFor_LengthValue =\n  | {\n      type: \"calc\";\n      value: CalcFor_DimensionPercentageFor_LengthValue;\n    }\n  | {\n      type: \"min\";\n      value: CalcFor_DimensionPercentageFor_LengthValue[];\n    }\n  | {\n      type: \"max\";\n      value: CalcFor_DimensionPercentageFor_LengthValue[];\n    }\n  | {\n      type: \"clamp\";\n      /**\n       * @minItems 3\n       * @maxItems 3\n       */\n      value: [\n        CalcFor_DimensionPercentageFor_LengthValue,\n        CalcFor_DimensionPercentageFor_LengthValue,\n        CalcFor_DimensionPercentageFor_LengthValue\n      ];\n    }\n  | {\n      type: \"round\";\n      /**\n       * @minItems 3\n       * @maxItems 3\n       */\n      value: [RoundingStrategy, CalcFor_DimensionPercentageFor_LengthValue, CalcFor_DimensionPercentageFor_LengthValue];\n    }\n  | {\n      type: \"rem\";\n      /**\n       * @minItems 2\n       * @maxItems 2\n       */\n      value: [CalcFor_DimensionPercentageFor_LengthValue, CalcFor_DimensionPercentageFor_LengthValue];\n    }\n  | {\n      type: \"mod\";\n      /**\n       * @minItems 2\n       * @maxItems 2\n       */\n      value: [CalcFor_DimensionPercentageFor_LengthValue, CalcFor_DimensionPercentageFor_LengthValue];\n    }\n  | {\n      type: \"abs\";\n      value: CalcFor_DimensionPercentageFor_LengthValue;\n    }\n  | {\n      type: \"sign\";\n      value: CalcFor_DimensionPercentageFor_LengthValue;\n    }\n  | {\n      type: \"hypot\";\n      value: CalcFor_DimensionPercentageFor_LengthValue[];\n    };\n/**\n * A component within a [Position](Position) value, representing a position along either the horizontal or vertical axis of a box.\n *\n * This type is generic over side keywords.\n */\nexport type PositionComponentFor_HorizontalPositionKeyword =\n  | {\n      type: \"center\";\n    }\n  | {\n      type: \"length\";\n      value: DimensionPercentageFor_LengthValue;\n    }\n  | {\n      /**\n       * Offset from the side.\n       */\n      offset?: DimensionPercentageFor_LengthValue | null;\n      /**\n       * A side keyword.\n       */\n      side: HorizontalPositionKeyword;\n      type: \"side\";\n    };\n/**\n * A component within a [Position](Position) value, representing a position along either the horizontal or vertical axis of a box.\n *\n * This type is generic over side keywords.\n */\nexport type PositionComponentFor_VerticalPositionKeyword =\n  | {\n      type: \"center\";\n    }\n  | {\n      type: \"length\";\n      value: DimensionPercentageFor_LengthValue;\n    }\n  | {\n      /**\n       * Offset from the side.\n       */\n      offset?: DimensionPercentageFor_LengthValue | null;\n      /**\n       * A side keyword.\n       */\n      side: VerticalPositionKeyword;\n      type: \"side\";\n    };\n/**\n * A `radial-gradient()` [ending shape](https://www.w3.org/TR/css-images-3/#valdef-radial-gradient-ending-shape).\n *\n * See [RadialGradient](RadialGradient).\n */\nexport type EndingShape =\n  | {\n      type: \"ellipse\";\n      value: Ellipse;\n    }\n  | {\n      type: \"circle\";\n      value: Circle;\n    };\n/**\n * An ellipse ending shape for a `radial-gradient()`.\n *\n * See [RadialGradient](RadialGradient).\n */\nexport type Ellipse =\n  | {\n      type: \"size\";\n      /**\n       * The x-radius of the ellipse.\n       */\n      x: DimensionPercentageFor_LengthValue;\n      /**\n       * The y-radius of the ellipse.\n       */\n      y: DimensionPercentageFor_LengthValue;\n    }\n  | {\n      type: \"extent\";\n      value: ShapeExtent;\n    };\n/**\n * A shape extent for a `radial-gradient()`.\n *\n * See [RadialGradient](RadialGradient).\n */\nexport type ShapeExtent = \"closest-side\" | \"farthest-side\" | \"closest-corner\" | \"farthest-corner\";\n/**\n * A circle ending shape for a `radial-gradient()`.\n *\n * See [RadialGradient](RadialGradient).\n */\nexport type Circle =\n  | {\n      type: \"radius\";\n      value: Length;\n    }\n  | {\n      type: \"extent\";\n      value: ShapeExtent;\n    };\n/**\n * Either a color stop or interpolation hint within a gradient.\n *\n * This type is generic, and items may be either a [LengthPercentage](super::length::LengthPercentage) or [Angle](super::angle::Angle) depending on what type of gradient it is within.\n */\nexport type GradientItemFor_DimensionPercentageFor_Angle =\n  | {\n      /**\n       * The color of the color stop.\n       */\n      color: CssColor;\n      /**\n       * The position of the color stop.\n       */\n      position?: DimensionPercentageFor_Angle | null;\n      type: \"color-stop\";\n    }\n  | {\n      type: \"hint\";\n      value: DimensionPercentageFor_Angle;\n    };\n/**\n * A generic type that allows any kind of dimension and percentage to be used standalone or mixed within a `calc()` expression.\n *\n * <https://drafts.csswg.org/css-values-4/#mixed-percentages>\n */\nexport type DimensionPercentageFor_Angle =\n  | {\n      type: \"dimension\";\n      value: Angle;\n    }\n  | {\n      type: \"percentage\";\n      value: number;\n    }\n  | {\n      type: \"calc\";\n      value: CalcFor_DimensionPercentageFor_Angle;\n    };\n/**\n * A mathematical expression used within the [`calc()`](https://www.w3.org/TR/css-values-4/#calc-func) function.\n *\n * This type supports generic value types. Values such as [Length](super::length::Length), [Percentage](super::percentage::Percentage), [Time](super::time::Time), and [Angle](super::angle::Angle) support `calc()` expressions.\n */\nexport type CalcFor_DimensionPercentageFor_Angle =\n  | {\n      type: \"value\";\n      value: DimensionPercentageFor_Angle;\n    }\n  | {\n      type: \"number\";\n      value: number;\n    }\n  | {\n      type: \"sum\";\n      /**\n       * @minItems 2\n       * @maxItems 2\n       */\n      value: [CalcFor_DimensionPercentageFor_Angle, CalcFor_DimensionPercentageFor_Angle];\n    }\n  | {\n      type: \"product\";\n      /**\n       * @minItems 2\n       * @maxItems 2\n       */\n      value: [number, CalcFor_DimensionPercentageFor_Angle];\n    }\n  | {\n      type: \"function\";\n      value: MathFunctionFor_DimensionPercentageFor_Angle;\n    };\n/**\n * A CSS [math function](https://www.w3.org/TR/css-values-4/#math-function).\n *\n * Math functions may be used in most properties and values that accept numeric values, including lengths, percentages, angles, times, etc.\n */\nexport type MathFunctionFor_DimensionPercentageFor_Angle =\n  | {\n      type: \"calc\";\n      value: CalcFor_DimensionPercentageFor_Angle;\n    }\n  | {\n      type: \"min\";\n      value: CalcFor_DimensionPercentageFor_Angle[];\n    }\n  | {\n      type: \"max\";\n      value: CalcFor_DimensionPercentageFor_Angle[];\n    }\n  | {\n      type: \"clamp\";\n      /**\n       * @minItems 3\n       * @maxItems 3\n       */\n      value: [\n        CalcFor_DimensionPercentageFor_Angle,\n        CalcFor_DimensionPercentageFor_Angle,\n        CalcFor_DimensionPercentageFor_Angle\n      ];\n    }\n  | {\n      type: \"round\";\n      /**\n       * @minItems 3\n       * @maxItems 3\n       */\n      value: [RoundingStrategy, CalcFor_DimensionPercentageFor_Angle, CalcFor_DimensionPercentageFor_Angle];\n    }\n  | {\n      type: \"rem\";\n      /**\n       * @minItems 2\n       * @maxItems 2\n       */\n      value: [CalcFor_DimensionPercentageFor_Angle, CalcFor_DimensionPercentageFor_Angle];\n    }\n  | {\n      type: \"mod\";\n      /**\n       * @minItems 2\n       * @maxItems 2\n       */\n      value: [CalcFor_DimensionPercentageFor_Angle, CalcFor_DimensionPercentageFor_Angle];\n    }\n  | {\n      type: \"abs\";\n      value: CalcFor_DimensionPercentageFor_Angle;\n    }\n  | {\n      type: \"sign\";\n      value: CalcFor_DimensionPercentageFor_Angle;\n    }\n  | {\n      type: \"hypot\";\n      value: CalcFor_DimensionPercentageFor_Angle[];\n    };\n/**\n * A keyword or number within a [WebKitGradientPoint](WebKitGradientPoint).\n */\nexport type WebKitGradientPointComponentFor_HorizontalPositionKeyword =\n  | {\n      type: \"center\";\n    }\n  | {\n      type: \"number\";\n      value: NumberOrPercentage;\n    }\n  | {\n      type: \"side\";\n      value: HorizontalPositionKeyword;\n    };\n/**\n * Either a `<number>` or `<percentage>`.\n */\nexport type NumberOrPercentage =\n  | {\n      type: \"number\";\n      value: number;\n    }\n  | {\n      type: \"percentage\";\n      value: number;\n    };\n/**\n * A keyword or number within a [WebKitGradientPoint](WebKitGradientPoint).\n */\nexport type WebKitGradientPointComponentFor_VerticalPositionKeyword =\n  | {\n      type: \"center\";\n    }\n  | {\n      type: \"number\";\n      value: NumberOrPercentage;\n    }\n  | {\n      type: \"side\";\n      value: VerticalPositionKeyword;\n    };\n/**\n * A value for the [background-size](https://www.w3.org/TR/css-backgrounds-3/#background-size) property.\n */\nexport type BackgroundSize =\n  | {\n      /**\n       * The height of the background.\n       */\n      height: LengthPercentageOrAuto;\n      type: \"explicit\";\n      /**\n       * The width of the background.\n       */\n      width: LengthPercentageOrAuto;\n    }\n  | {\n      type: \"cover\";\n    }\n  | {\n      type: \"contain\";\n    };\n/**\n * Either a [`<length-percentage>`](https://www.w3.org/TR/css-values-4/#typedef-length-percentage), or the `auto` keyword.\n */\nexport type LengthPercentageOrAuto =\n  | {\n      type: \"auto\";\n    }\n  | {\n      type: \"length-percentage\";\n      value: DimensionPercentageFor_LengthValue;\n    };\n/**\n * A [`<repeat-style>`](https://www.w3.org/TR/css-backgrounds-3/#typedef-repeat-style) value, used within the `background-repeat` property to represent how a background image is repeated in a single direction.\n *\n * See [BackgroundRepeat](BackgroundRepeat).\n */\nexport type BackgroundRepeatKeyword = \"repeat\" | \"space\" | \"round\" | \"no-repeat\";\n/**\n * A value for the [background-attachment](https://www.w3.org/TR/css-backgrounds-3/#background-attachment) property.\n */\nexport type BackgroundAttachment = \"scroll\" | \"fixed\" | \"local\";\n/**\n * A value for the [background-clip](https://drafts.csswg.org/css-backgrounds-4/#background-clip) property.\n */\nexport type BackgroundClip = \"border-box\" | \"padding-box\" | \"content-box\" | \"border\" | \"text\";\n/**\n * A value for the [background-origin](https://www.w3.org/TR/css-backgrounds-3/#background-origin) property.\n */\nexport type BackgroundOrigin = \"border-box\" | \"padding-box\" | \"content-box\";\n/**\n * A value for the [display](https://drafts.csswg.org/css-display-3/#the-display-properties) property.\n */\nexport type Display =\n  | {\n      type: \"keyword\";\n      value: DisplayKeyword;\n    }\n  | {\n      /**\n       * The inside display value.\n       */\n      inside: DisplayInside;\n      /**\n       * Whether this is a list item.\n       */\n      isListItem: boolean;\n      /**\n       * The outside display value.\n       */\n      outside: DisplayOutside;\n      type: \"pair\";\n    };\n/**\n * A `display` keyword.\n *\n * See [Display](Display).\n */\nexport type DisplayKeyword =\n  | \"none\"\n  | \"contents\"\n  | \"table-row-group\"\n  | \"table-header-group\"\n  | \"table-footer-group\"\n  | \"table-row\"\n  | \"table-cell\"\n  | \"table-column-group\"\n  | \"table-column\"\n  | \"table-caption\"\n  | \"ruby-base\"\n  | \"ruby-text\"\n  | \"ruby-base-container\"\n  | \"ruby-text-container\";\n/**\n * A [`<display-inside>`](https://drafts.csswg.org/css-display-3/#typedef-display-inside) value.\n */\nexport type DisplayInside =\n  | {\n      type: \"flow\";\n    }\n  | {\n      type: \"flow-root\";\n    }\n  | {\n      type: \"table\";\n    }\n  | {\n      type: \"flex\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      type: \"box\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      type: \"grid\";\n    }\n  | {\n      type: \"ruby\";\n    };\n/**\n * A [`<display-outside>`](https://drafts.csswg.org/css-display-3/#typedef-display-outside) value.\n */\nexport type DisplayOutside = \"block\" | \"inline\" | \"run-in\";\n/**\n * A value for the [visibility](https://drafts.csswg.org/css-display-3/#visibility) property.\n */\nexport type Visibility = \"visible\" | \"hidden\" | \"collapse\";\n/**\n * A value for the [preferred size properties](https://drafts.csswg.org/css-sizing-3/#preferred-size-properties), i.e. `width` and `height.\n */\nexport type Size =\n  | {\n      type: \"auto\";\n    }\n  | {\n      type: \"length-percentage\";\n      value: DimensionPercentageFor_LengthValue;\n    }\n  | {\n      type: \"min-content\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      type: \"max-content\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      type: \"fit-content\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      type: \"fit-content-function\";\n      value: DimensionPercentageFor_LengthValue;\n    }\n  | {\n      type: \"stretch\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      type: \"contain\";\n    };\n/**\n * A value for the [minimum](https://drafts.csswg.org/css-sizing-3/#min-size-properties) and [maximum](https://drafts.csswg.org/css-sizing-3/#max-size-properties) size properties, e.g. `min-width` and `max-height`.\n */\nexport type MaxSize =\n  | {\n      type: \"none\";\n    }\n  | {\n      type: \"length-percentage\";\n      value: DimensionPercentageFor_LengthValue;\n    }\n  | {\n      type: \"min-content\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      type: \"max-content\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      type: \"fit-content\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      type: \"fit-content-function\";\n      value: DimensionPercentageFor_LengthValue;\n    }\n  | {\n      type: \"stretch\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      type: \"contain\";\n    };\n/**\n * A value for the [box-sizing](https://drafts.csswg.org/css-sizing-3/#box-sizing) property.\n */\nexport type BoxSizing = \"content-box\" | \"border-box\";\n/**\n * An [overflow](https://www.w3.org/TR/css-overflow-3/#overflow-properties) keyword as used in the `overflow-x`, `overflow-y`, and `overflow` properties.\n */\nexport type OverflowKeyword = \"visible\" | \"hidden\" | \"clip\" | \"scroll\" | \"auto\";\n/**\n * A value for the [text-overflow](https://www.w3.org/TR/css-overflow-3/#text-overflow) property.\n */\nexport type TextOverflow = \"clip\" | \"ellipsis\";\n/**\n * A value for the [position](https://www.w3.org/TR/css-position-3/#position-property) property.\n */\nexport type Position2 =\n  | {\n      type: \"static\";\n    }\n  | {\n      type: \"relative\";\n    }\n  | {\n      type: \"absolute\";\n    }\n  | {\n      type: \"sticky\";\n      value: VendorPrefix;\n    }\n  | {\n      type: \"fixed\";\n    };\n/**\n * A generic value that represents a value with two components, e.g. a border radius.\n *\n * When serialized, only a single component will be written if both are equal.\n *\n * @minItems 2\n * @maxItems 2\n */\nexport type Size2DFor_Length = [Length, Length];\n/**\n * A [`<line-style>`](https://drafts.csswg.org/css-backgrounds/#typedef-line-style) value, used in the `border-style` property.\n */\nexport type LineStyle =\n  | \"none\"\n  | \"hidden\"\n  | \"inset\"\n  | \"groove\"\n  | \"outset\"\n  | \"ridge\"\n  | \"dotted\"\n  | \"dashed\"\n  | \"solid\"\n  | \"double\";\n/**\n * A value for the [border-width](https://www.w3.org/TR/css-backgrounds-3/#border-width) property.\n */\nexport type BorderSideWidth =\n  | {\n      type: \"thin\";\n    }\n  | {\n      type: \"medium\";\n    }\n  | {\n      type: \"thick\";\n    }\n  | {\n      type: \"length\";\n      value: Length;\n    };\n/**\n * A generic value that represents a value with two components, e.g. a border radius.\n *\n * When serialized, only a single component will be written if both are equal.\n *\n * @minItems 2\n * @maxItems 2\n */\nexport type Size2DFor_DimensionPercentageFor_LengthValue = [\n  DimensionPercentageFor_LengthValue,\n  DimensionPercentageFor_LengthValue\n];\n/**\n * A generic value that represents a value for four sides of a box, e.g. border-width, margin, padding, etc.\n *\n * When serialized, as few components as possible are written when there are duplicate values.\n *\n * @minItems 4\n * @maxItems 4\n */\nexport type RectFor_LengthOrNumber = [LengthOrNumber, LengthOrNumber, LengthOrNumber, LengthOrNumber];\n/**\n * Either a [`<length>`](https://www.w3.org/TR/css-values-4/#lengths) or a [`<number>`](https://www.w3.org/TR/css-values-4/#numbers).\n */\nexport type LengthOrNumber =\n  | {\n      type: \"number\";\n      value: number;\n    }\n  | {\n      type: \"length\";\n      value: Length;\n    };\n/**\n * A single [border-image-repeat](https://www.w3.org/TR/css-backgrounds-3/#border-image-repeat) keyword.\n */\nexport type BorderImageRepeatKeyword = \"stretch\" | \"repeat\" | \"round\" | \"space\";\n/**\n * A generic value that represents a value for four sides of a box, e.g. border-width, margin, padding, etc.\n *\n * When serialized, as few components as possible are written when there are duplicate values.\n *\n * @minItems 4\n * @maxItems 4\n */\nexport type RectFor_BorderImageSideWidth = [\n  BorderImageSideWidth,\n  BorderImageSideWidth,\n  BorderImageSideWidth,\n  BorderImageSideWidth\n];\n/**\n * A value for the [border-image-width](https://www.w3.org/TR/css-backgrounds-3/#border-image-width) property.\n */\nexport type BorderImageSideWidth =\n  | {\n      type: \"number\";\n      value: number;\n    }\n  | {\n      type: \"length-percentage\";\n      value: DimensionPercentageFor_LengthValue;\n    }\n  | {\n      type: \"auto\";\n    };\n/**\n * A generic value that represents a value for four sides of a box, e.g. border-width, margin, padding, etc.\n *\n * When serialized, as few components as possible are written when there are duplicate values.\n *\n * @minItems 4\n * @maxItems 4\n */\nexport type RectFor_NumberOrPercentage = [\n  NumberOrPercentage,\n  NumberOrPercentage,\n  NumberOrPercentage,\n  NumberOrPercentage\n];\n/**\n * A value for the [outline-style](https://drafts.csswg.org/css-ui/#outline-style) property.\n */\nexport type OutlineStyle =\n  | {\n      type: \"auto\";\n    }\n  | {\n      type: \"line-style\";\n      value: LineStyle;\n    };\n/**\n * A value for the [flex-direction](https://www.w3.org/TR/2018/CR-css-flexbox-1-20181119/#propdef-flex-direction) property.\n */\nexport type FlexDirection = \"row\" | \"row-reverse\" | \"column\" | \"column-reverse\";\n/**\n * A value for the [flex-wrap](https://www.w3.org/TR/2018/CR-css-flexbox-1-20181119/#flex-wrap-property) property.\n */\nexport type FlexWrap = \"nowrap\" | \"wrap\" | \"wrap-reverse\";\n/**\n * A value for the [align-content](https://www.w3.org/TR/css-align-3/#propdef-align-content) property.\n */\nexport type AlignContent =\n  | {\n      type: \"normal\";\n    }\n  | {\n      type: \"baseline-position\";\n      value: BaselinePosition;\n    }\n  | {\n      type: \"content-distribution\";\n      value: ContentDistribution;\n    }\n  | {\n      /**\n       * An overflow alignment mode.\n       */\n      overflow?: OverflowPosition | null;\n      type: \"content-position\";\n      /**\n       * A content position keyword.\n       */\n      value: ContentPosition;\n    };\n/**\n * A [`<baseline-position>`](https://www.w3.org/TR/css-align-3/#typedef-baseline-position) value, as used in the alignment properties.\n */\nexport type BaselinePosition = \"first\" | \"last\";\n/**\n * A [`<content-distribution>`](https://www.w3.org/TR/css-align-3/#typedef-content-distribution) value.\n */\nexport type ContentDistribution = \"space-between\" | \"space-around\" | \"space-evenly\" | \"stretch\";\n/**\n * An [`<overflow-position>`](https://www.w3.org/TR/css-align-3/#typedef-overflow-position) value.\n */\nexport type OverflowPosition = \"safe\" | \"unsafe\";\n/**\n * A [`<content-position>`](https://www.w3.org/TR/css-align-3/#typedef-content-position) value.\n */\nexport type ContentPosition = \"center\" | \"start\" | \"end\" | \"flex-start\" | \"flex-end\";\n/**\n * A value for the [justify-content](https://www.w3.org/TR/css-align-3/#propdef-justify-content) property.\n */\nexport type JustifyContent =\n  | {\n      type: \"normal\";\n    }\n  | {\n      type: \"content-distribution\";\n      value: ContentDistribution;\n    }\n  | {\n      /**\n       * An overflow alignment mode.\n       */\n      overflow?: OverflowPosition | null;\n      type: \"content-position\";\n      /**\n       * A content position keyword.\n       */\n      value: ContentPosition;\n    }\n  | {\n      /**\n       * An overflow alignment mode.\n       */\n      overflow?: OverflowPosition | null;\n      type: \"left\";\n    }\n  | {\n      /**\n       * An overflow alignment mode.\n       */\n      overflow?: OverflowPosition | null;\n      type: \"right\";\n    };\n/**\n * A value for the [align-self](https://www.w3.org/TR/css-align-3/#align-self-property) property.\n */\nexport type AlignSelf =\n  | {\n      type: \"auto\";\n    }\n  | {\n      type: \"normal\";\n    }\n  | {\n      type: \"stretch\";\n    }\n  | {\n      type: \"baseline-position\";\n      value: BaselinePosition;\n    }\n  | {\n      /**\n       * An overflow alignment mode.\n       */\n      overflow?: OverflowPosition | null;\n      type: \"self-position\";\n      /**\n       * A self position keyword.\n       */\n      value: SelfPosition;\n    };\n/**\n * A [`<self-position>`](https://www.w3.org/TR/css-align-3/#typedef-self-position) value.\n */\nexport type SelfPosition = \"center\" | \"start\" | \"end\" | \"self-start\" | \"self-end\" | \"flex-start\" | \"flex-end\";\n/**\n * A value for the [justify-self](https://www.w3.org/TR/css-align-3/#justify-self-property) property.\n */\nexport type JustifySelf =\n  | {\n      type: \"auto\";\n    }\n  | {\n      type: \"normal\";\n    }\n  | {\n      type: \"stretch\";\n    }\n  | {\n      type: \"baseline-position\";\n      value: BaselinePosition;\n    }\n  | {\n      /**\n       * An overflow alignment mode.\n       */\n      overflow?: OverflowPosition | null;\n      type: \"self-position\";\n      /**\n       * A self position keyword.\n       */\n      value: SelfPosition;\n    }\n  | {\n      /**\n       * An overflow alignment mode.\n       */\n      overflow?: OverflowPosition | null;\n      type: \"left\";\n    }\n  | {\n      /**\n       * An overflow alignment mode.\n       */\n      overflow?: OverflowPosition | null;\n      type: \"right\";\n    };\n/**\n * A value for the [align-items](https://www.w3.org/TR/css-align-3/#align-items-property) property.\n */\nexport type AlignItems =\n  | {\n      type: \"normal\";\n    }\n  | {\n      type: \"stretch\";\n    }\n  | {\n      type: \"baseline-position\";\n      value: BaselinePosition;\n    }\n  | {\n      /**\n       * An overflow alignment mode.\n       */\n      overflow?: OverflowPosition | null;\n      type: \"self-position\";\n      /**\n       * A self position keyword.\n       */\n      value: SelfPosition;\n    };\n/**\n * A value for the [justify-items](https://www.w3.org/TR/css-align-3/#justify-items-property) property.\n */\nexport type JustifyItems =\n  | {\n      type: \"normal\";\n    }\n  | {\n      type: \"stretch\";\n    }\n  | {\n      type: \"baseline-position\";\n      value: BaselinePosition;\n    }\n  | {\n      /**\n       * An overflow alignment mode.\n       */\n      overflow?: OverflowPosition | null;\n      type: \"self-position\";\n      /**\n       * A self position keyword.\n       */\n      value: SelfPosition;\n    }\n  | {\n      /**\n       * An overflow alignment mode.\n       */\n      overflow?: OverflowPosition | null;\n      type: \"left\";\n    }\n  | {\n      /**\n       * An overflow alignment mode.\n       */\n      overflow?: OverflowPosition | null;\n      type: \"right\";\n    }\n  | {\n      type: \"legacy\";\n      value: LegacyJustify;\n    };\n/**\n * A legacy justification keyword, as used in the `justify-items` property.\n */\nexport type LegacyJustify = \"left\" | \"right\" | \"center\";\n/**\n * A [gap](https://www.w3.org/TR/css-align-3/#column-row-gap) value, as used in the `column-gap` and `row-gap` properties.\n */\nexport type GapValue =\n  | {\n      type: \"normal\";\n    }\n  | {\n      type: \"length-percentage\";\n      value: DimensionPercentageFor_LengthValue;\n    };\n/**\n * A value for the legacy (prefixed) [box-orient](https://www.w3.org/TR/2009/WD-css3-flexbox-20090723/#orientation) property. Partially equivalent to `flex-direction` in the standard syntax.\n */\nexport type BoxOrient = \"horizontal\" | \"vertical\" | \"inline-axis\" | \"block-axis\";\n/**\n * A value for the legacy (prefixed) [box-direction](https://www.w3.org/TR/2009/WD-css3-flexbox-20090723/#displayorder) property. Partially equivalent to the `flex-direction` property in the standard syntax.\n */\nexport type BoxDirection = \"normal\" | \"reverse\";\n/**\n * A value for the legacy (prefixed) [box-align](https://www.w3.org/TR/2009/WD-css3-flexbox-20090723/#alignment) property. Equivalent to the `align-items` property in the standard syntax.\n */\nexport type BoxAlign = \"start\" | \"end\" | \"center\" | \"baseline\" | \"stretch\";\n/**\n * A value for the legacy (prefixed) [box-pack](https://www.w3.org/TR/2009/WD-css3-flexbox-20090723/#packing) property. Equivalent to the `justify-content` property in the standard syntax.\n */\nexport type BoxPack = \"start\" | \"end\" | \"center\" | \"justify\";\n/**\n * A value for the legacy (prefixed) [box-lines](https://www.w3.org/TR/2009/WD-css3-flexbox-20090723/#multiple) property. Equivalent to the `flex-wrap` property in the standard syntax.\n */\nexport type BoxLines = \"single\" | \"multiple\";\n/**\n * A value for the legacy (prefixed) [flex-pack](https://www.w3.org/TR/2012/WD-css3-flexbox-20120322/#flex-pack) property. Equivalent to the `justify-content` property in the standard syntax.\n */\nexport type FlexPack = \"start\" | \"end\" | \"center\" | \"justify\" | \"distribute\";\n/**\n * A value for the legacy (prefixed) [flex-item-align](https://www.w3.org/TR/2012/WD-css3-flexbox-20120322/#flex-align) property. Equivalent to the `align-self` property in the standard syntax.\n */\nexport type FlexItemAlign = \"auto\" | \"start\" | \"end\" | \"center\" | \"baseline\" | \"stretch\";\n/**\n * A value for the legacy (prefixed) [flex-line-pack](https://www.w3.org/TR/2012/WD-css3-flexbox-20120322/#flex-line-pack) property. Equivalent to the `align-content` property in the standard syntax.\n */\nexport type FlexLinePack = \"start\" | \"end\" | \"center\" | \"justify\" | \"distribute\" | \"stretch\";\n/**\n * A [track sizing](https://drafts.csswg.org/css-grid-2/#track-sizing) value for the `grid-template-rows` and `grid-template-columns` properties.\n */\nexport type TrackSizing =\n  | {\n      type: \"none\";\n    }\n  | {\n      /**\n       * A list of grid track items.\n       */\n      items: TrackListItem[];\n      /**\n       * A list of line names.\n       */\n      lineNames: String[][];\n      type: \"track-list\";\n    };\n/**\n * Either a track size or `repeat()` function.\n *\n * See [TrackList](TrackList).\n */\nexport type TrackListItem =\n  | {\n      type: \"track-size\";\n      value: TrackSize;\n    }\n  | {\n      type: \"track-repeat\";\n      value: TrackRepeat;\n    };\n/**\n * A [`<track-size>`](https://drafts.csswg.org/css-grid-2/#typedef-track-size) value, as used in the `grid-template-rows` and `grid-template-columns` properties.\n *\n * See [TrackListItem](TrackListItem).\n */\nexport type TrackSize =\n  | {\n      type: \"track-breadth\";\n      value: TrackBreadth;\n    }\n  | {\n      /**\n       * The maximum value.\n       */\n      max: TrackBreadth;\n      /**\n       * The minimum value.\n       */\n      min: TrackBreadth;\n      type: \"min-max\";\n    }\n  | {\n      type: \"fit-content\";\n      value: DimensionPercentageFor_LengthValue;\n    };\n/**\n * A [`<track-breadth>`](https://drafts.csswg.org/css-grid-2/#typedef-track-breadth) value.\n *\n * See [TrackSize](TrackSize).\n */\nexport type TrackBreadth =\n  | {\n      type: \"length\";\n      value: DimensionPercentageFor_LengthValue;\n    }\n  | {\n      type: \"flex\";\n      value: number;\n    }\n  | {\n      type: \"min-content\";\n    }\n  | {\n      type: \"max-content\";\n    }\n  | {\n      type: \"auto\";\n    };\n/**\n * A [`<repeat-count>`](https://drafts.csswg.org/css-grid-2/#typedef-track-repeat) value, used in the `repeat()` function.\n *\n * See [TrackRepeat](TrackRepeat).\n */\nexport type RepeatCount =\n  | {\n      type: \"number\";\n      value: number;\n    }\n  | {\n      type: \"auto-fill\";\n    }\n  | {\n      type: \"auto-fit\";\n    };\nexport type AutoFlowDirection = \"row\" | \"column\";\n/**\n * A value for the [grid-template-areas](https://drafts.csswg.org/css-grid-2/#grid-template-areas-property) property. none | <string>+\n */\nexport type GridTemplateAreas =\n  | {\n      type: \"none\";\n    }\n  | {\n      /**\n       * A flattened list of grid area names. Unnamed areas specified by the `.` token are represented as `None`.\n       */\n      areas: (string | null)[];\n      /**\n       * The number of columns in the grid.\n       */\n      columns: number;\n      type: \"areas\";\n    };\n/**\n * A [`<grid-line>`](https://drafts.csswg.org/css-grid-2/#typedef-grid-row-start-grid-line) value, used in the `grid-row-start`, `grid-row-end`, `grid-column-start`, and `grid-column-end` properties.\n */\nexport type GridLine =\n  | {\n      type: \"auto\";\n    }\n  | {\n      /**\n       * A grid area name.\n       */\n      name: String;\n      type: \"area\";\n    }\n  | {\n      /**\n       * A line number.\n       */\n      index: number;\n      /**\n       * A line name to filter by.\n       */\n      name?: String | null;\n      type: \"line\";\n    }\n  | {\n      /**\n       * A line number.\n       */\n      index: number;\n      /**\n       * A line name to filter by.\n       */\n      name?: String | null;\n      type: \"span\";\n    };\n/**\n * A value for the [font-weight](https://www.w3.org/TR/css-fonts-4/#font-weight-prop) property.\n */\nexport type FontWeight =\n  | {\n      type: \"absolute\";\n      value: AbsoluteFontWeight;\n    }\n  | {\n      type: \"bolder\";\n    }\n  | {\n      type: \"lighter\";\n    };\n/**\n * An [absolute font weight](https://www.w3.org/TR/css-fonts-4/#font-weight-absolute-values), as used in the `font-weight` property.\n *\n * See [FontWeight](FontWeight).\n */\nexport type AbsoluteFontWeight =\n  | {\n      type: \"weight\";\n      value: number;\n    }\n  | {\n      type: \"normal\";\n    }\n  | {\n      type: \"bold\";\n    };\n/**\n * A value for the [font-size](https://www.w3.org/TR/css-fonts-4/#font-size-prop) property.\n */\nexport type FontSize =\n  | {\n      type: \"length\";\n      value: DimensionPercentageFor_LengthValue;\n    }\n  | {\n      type: \"absolute\";\n      value: AbsoluteFontSize;\n    }\n  | {\n      type: \"relative\";\n      value: RelativeFontSize;\n    };\n/**\n * An [absolute font size](https://www.w3.org/TR/css-fonts-3/#absolute-size-value), as used in the `font-size` property.\n *\n * See [FontSize](FontSize).\n */\nexport type AbsoluteFontSize =\n  | \"xx-small\"\n  | \"x-small\"\n  | \"small\"\n  | \"medium\"\n  | \"large\"\n  | \"x-large\"\n  | \"xx-large\"\n  | \"xxx-large\";\n/**\n * A [relative font size](https://www.w3.org/TR/css-fonts-3/#relative-size-value), as used in the `font-size` property.\n *\n * See [FontSize](FontSize).\n */\nexport type RelativeFontSize = \"smaller\" | \"larger\";\n/**\n * A value for the [font-stretch](https://www.w3.org/TR/css-fonts-4/#font-stretch-prop) property.\n */\nexport type FontStretch =\n  | {\n      type: \"keyword\";\n      value: FontStretchKeyword;\n    }\n  | {\n      type: \"percentage\";\n      value: number;\n    };\n/**\n * A [font stretch keyword](https://www.w3.org/TR/css-fonts-4/#font-stretch-prop), as used in the `font-stretch` property.\n *\n * See [FontStretch](FontStretch).\n */\nexport type FontStretchKeyword =\n  | \"normal\"\n  | \"ultra-condensed\"\n  | \"extra-condensed\"\n  | \"condensed\"\n  | \"semi-condensed\"\n  | \"semi-expanded\"\n  | \"expanded\"\n  | \"extra-expanded\"\n  | \"ultra-expanded\";\n/**\n * A value for the [font-family](https://www.w3.org/TR/css-fonts-4/#font-family-prop) property.\n */\nexport type FontFamily = GenericFontFamily | String;\n/**\n * A [generic font family](https://www.w3.org/TR/css-fonts-4/#generic-font-families) name, as used in the `font-family` property.\n *\n * See [FontFamily](FontFamily).\n */\nexport type GenericFontFamily =\n  | \"serif\"\n  | \"sans-serif\"\n  | \"cursive\"\n  | \"fantasy\"\n  | \"monospace\"\n  | \"system-ui\"\n  | \"emoji\"\n  | \"math\"\n  | \"fangsong\"\n  | \"ui-serif\"\n  | \"ui-sans-serif\"\n  | \"ui-monospace\"\n  | \"ui-rounded\"\n  | \"initial\"\n  | \"inherit\"\n  | \"unset\"\n  | \"default\"\n  | \"revert\"\n  | \"revert-layer\";\n/**\n * A value for the [font-style](https://www.w3.org/TR/css-fonts-4/#font-style-prop) property.\n */\nexport type FontStyle =\n  | {\n      type: \"normal\";\n    }\n  | {\n      type: \"italic\";\n    }\n  | {\n      type: \"oblique\";\n      value: Angle;\n    };\n/**\n * A value for the [font-variant-caps](https://www.w3.org/TR/css-fonts-4/#font-variant-caps-prop) property.\n */\nexport type FontVariantCaps =\n  | \"normal\"\n  | \"small-caps\"\n  | \"all-small-caps\"\n  | \"petite-caps\"\n  | \"all-petite-caps\"\n  | \"unicase\"\n  | \"titling-caps\";\n/**\n * A value for the [line-height](https://www.w3.org/TR/2020/WD-css-inline-3-20200827/#propdef-line-height) property.\n */\nexport type LineHeight =\n  | {\n      type: \"normal\";\n    }\n  | {\n      type: \"number\";\n      value: number;\n    }\n  | {\n      type: \"length\";\n      value: DimensionPercentageFor_LengthValue;\n    };\n/**\n * A value for the [vertical align](https://drafts.csswg.org/css2/#propdef-vertical-align) property.\n */\nexport type VerticalAlign =\n  | {\n      type: \"keyword\";\n      value: VerticalAlignKeyword;\n    }\n  | {\n      type: \"length\";\n      value: DimensionPercentageFor_LengthValue;\n    };\n/**\n * A keyword for the [vertical align](https://drafts.csswg.org/css2/#propdef-vertical-align) property.\n */\nexport type VerticalAlignKeyword =\n  | \"baseline\"\n  | \"sub\"\n  | \"super\"\n  | \"top\"\n  | \"text-top\"\n  | \"middle\"\n  | \"bottom\"\n  | \"text-bottom\";\n/**\n * A CSS [easing function](https://www.w3.org/TR/css-easing-1/#easing-functions).\n */\nexport type EasingFunction =\n  | {\n      type: \"linear\";\n    }\n  | {\n      type: \"ease\";\n    }\n  | {\n      type: \"ease-in\";\n    }\n  | {\n      type: \"ease-out\";\n    }\n  | {\n      type: \"ease-in-out\";\n    }\n  | {\n      type: \"cubic-bezier\";\n      /**\n       * The x-position of the first point in the curve.\n       */\n      x1: number;\n      /**\n       * The x-position of the second point in the curve.\n       */\n      x2: number;\n      /**\n       * The y-position of the first point in the curve.\n       */\n      y1: number;\n      /**\n       * The y-position of the second point in the curve.\n       */\n      y2: number;\n    }\n  | {\n      /**\n       * The number of intervals in the function.\n       */\n      count: number;\n      /**\n       * The step position.\n       */\n      position?: StepPosition;\n      type: \"steps\";\n    };\n/**\n * A [step position](https://www.w3.org/TR/css-easing-1/#step-position), used within the `steps()` function.\n */\nexport type StepPosition =\n  | {\n      type: \"start\";\n    }\n  | {\n      type: \"end\";\n    }\n  | {\n      type: \"jump-none\";\n    }\n  | {\n      type: \"jump-both\";\n    };\n/**\n * A value for the [animation-iteration-count](https://drafts.csswg.org/css-animations/#animation-iteration-count) property.\n */\nexport type AnimationIterationCount =\n  | {\n      type: \"number\";\n      value: number;\n    }\n  | {\n      type: \"infinite\";\n    };\n/**\n * A value for the [animation-direction](https://drafts.csswg.org/css-animations/#animation-direction) property.\n */\nexport type AnimationDirection = \"normal\" | \"reverse\" | \"alternate\" | \"alternate-reverse\";\n/**\n * A value for the [animation-play-state](https://drafts.csswg.org/css-animations/#animation-play-state) property.\n */\nexport type AnimationPlayState = \"running\" | \"paused\";\n/**\n * A value for the [animation-fill-mode](https://drafts.csswg.org/css-animations/#animation-fill-mode) property.\n */\nexport type AnimationFillMode = \"none\" | \"forwards\" | \"backwards\" | \"both\";\n/**\n * A value for the [animation-composition](https://drafts.csswg.org/css-animations-2/#animation-composition) property.\n */\nexport type AnimationComposition = \"replace\" | \"add\" | \"accumulate\";\n/**\n * A value for the [animation-timeline](https://drafts.csswg.org/css-animations-2/#animation-timeline) property.\n */\nexport type AnimationTimeline =\n  | {\n      type: \"auto\";\n    }\n  | {\n      type: \"none\";\n    }\n  | {\n      type: \"dashed-ident\";\n      value: String;\n    }\n  | {\n      type: \"scroll\";\n      value: ScrollTimeline;\n    }\n  | {\n      type: \"view\";\n      value: ViewTimeline;\n    };\n/**\n * A scroll axis, used in the `scroll()` function.\n */\nexport type ScrollAxis = \"block\" | \"inline\" | \"x\" | \"y\";\n/**\n * A scroller, used in the `scroll()` function.\n */\nexport type Scroller = \"root\" | \"nearest\" | \"self\";\n/**\n * A generic value that represents a value with two components, e.g. a border radius.\n *\n * When serialized, only a single component will be written if both are equal.\n *\n * @minItems 2\n * @maxItems 2\n */\nexport type Size2DFor_LengthPercentageOrAuto = [LengthPercentageOrAuto, LengthPercentageOrAuto];\n/**\n * A value for the [animation-range-start](https://drafts.csswg.org/scroll-animations/#animation-range-start) property.\n */\nexport type AnimationRangeStart = AnimationAttachmentRange;\n/**\n * A value for the [animation-range-start](https://drafts.csswg.org/scroll-animations/#animation-range-start) or [animation-range-end](https://drafts.csswg.org/scroll-animations/#animation-range-end) property.\n */\nexport type AnimationAttachmentRange =\n  \"normal\" | DimensionPercentageFor_LengthValue | {\n    /**\n     * The name of the timeline range.\n     */\n    name: TimelineRangeName;\n    /**\n     * The offset from the start of the named timeline range.\n     */\n    offset: DimensionPercentageFor_LengthValue;\n  };\n/**\n * A [view progress timeline range](https://drafts.csswg.org/scroll-animations/#view-timelines-ranges)\n */\nexport type TimelineRangeName = \"cover\" | \"contain\" | \"entry\" | \"exit\" | \"entry-crossing\" | \"exit-crossing\";\n/**\n * A value for the [animation-range-end](https://drafts.csswg.org/scroll-animations/#animation-range-end) property.\n */\nexport type AnimationRangeEnd = AnimationAttachmentRange;\n/**\n * An individual [transform function](https://www.w3.org/TR/2019/CR-css-transforms-1-20190214/#two-d-transform-functions).\n */\nexport type Transform =\n  | {\n      type: \"translate\";\n      /**\n       * @minItems 2\n       * @maxItems 2\n       */\n      value: [DimensionPercentageFor_LengthValue, DimensionPercentageFor_LengthValue];\n    }\n  | {\n      type: \"translateX\";\n      value: DimensionPercentageFor_LengthValue;\n    }\n  | {\n      type: \"translateY\";\n      value: DimensionPercentageFor_LengthValue;\n    }\n  | {\n      type: \"translateZ\";\n      value: Length;\n    }\n  | {\n      type: \"translate3d\";\n      /**\n       * @minItems 3\n       * @maxItems 3\n       */\n      value: [DimensionPercentageFor_LengthValue, DimensionPercentageFor_LengthValue, Length];\n    }\n  | {\n      type: \"scale\";\n      /**\n       * @minItems 2\n       * @maxItems 2\n       */\n      value: [NumberOrPercentage, NumberOrPercentage];\n    }\n  | {\n      type: \"scaleX\";\n      value: NumberOrPercentage;\n    }\n  | {\n      type: \"scaleY\";\n      value: NumberOrPercentage;\n    }\n  | {\n      type: \"scaleZ\";\n      value: NumberOrPercentage;\n    }\n  | {\n      type: \"scale3d\";\n      /**\n       * @minItems 3\n       * @maxItems 3\n       */\n      value: [NumberOrPercentage, NumberOrPercentage, NumberOrPercentage];\n    }\n  | {\n      type: \"rotate\";\n      value: Angle;\n    }\n  | {\n      type: \"rotateX\";\n      value: Angle;\n    }\n  | {\n      type: \"rotateY\";\n      value: Angle;\n    }\n  | {\n      type: \"rotateZ\";\n      value: Angle;\n    }\n  | {\n      type: \"rotate3d\";\n      /**\n       * @minItems 4\n       * @maxItems 4\n       */\n      value: [number, number, number, Angle];\n    }\n  | {\n      type: \"skew\";\n      /**\n       * @minItems 2\n       * @maxItems 2\n       */\n      value: [Angle, Angle];\n    }\n  | {\n      type: \"skewX\";\n      value: Angle;\n    }\n  | {\n      type: \"skewY\";\n      value: Angle;\n    }\n  | {\n      type: \"perspective\";\n      value: Length;\n    }\n  | {\n      type: \"matrix\";\n      value: MatrixForFloat;\n    }\n  | {\n      type: \"matrix3d\";\n      value: Matrix3DForFloat;\n    };\n/**\n * A value for the [transform-style](https://drafts.csswg.org/css-transforms-2/#transform-style-property) property.\n */\nexport type TransformStyle = \"flat\" | \"preserve3d\";\n/**\n * A value for the [transform-box](https://drafts.csswg.org/css-transforms-1/#transform-box) property.\n */\nexport type TransformBox = \"content-box\" | \"border-box\" | \"fill-box\" | \"stroke-box\" | \"view-box\";\n/**\n * A value for the [backface-visibility](https://drafts.csswg.org/css-transforms-2/#backface-visibility-property) property.\n */\nexport type BackfaceVisibility = \"visible\" | \"hidden\";\n/**\n * A value for the [perspective](https://drafts.csswg.org/css-transforms-2/#perspective-property) property.\n */\nexport type Perspective =\n  | {\n      type: \"none\";\n    }\n  | {\n      type: \"length\";\n      value: Length;\n    };\n/**\n * A value for the [translate](https://drafts.csswg.org/css-transforms-2/#propdef-translate) property.\n */\nexport type Translate =\n  | \"none\"\n  | {\n    /**\n     * The x translation.\n     */\n    x: DimensionPercentageFor_LengthValue;\n    /**\n     * The y translation.\n     */\n    y: DimensionPercentageFor_LengthValue;\n    /**\n     * The z translation.\n     */\n    z: Length;\n  };\n/**\n * A value for the [scale](https://drafts.csswg.org/css-transforms-2/#propdef-scale) property.\n */\nexport type Scale =\n  | \"none\"\n  | {\n    /**\n     * Scale on the x axis.\n     */\n    x: NumberOrPercentage;\n    /**\n     * Scale on the y axis.\n     */\n    y: NumberOrPercentage;\n    /**\n     * Scale on the z axis.\n     */\n    z: NumberOrPercentage;\n  };\n/**\n * Defines how text case should be transformed in the [text-transform](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#text-transform-property) property.\n */\nexport type TextTransformCase = \"none\" | \"uppercase\" | \"lowercase\" | \"capitalize\";\n/**\n * A value for the [white-space](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#white-space-property) property.\n */\nexport type WhiteSpace = \"normal\" | \"pre\" | \"nowrap\" | \"pre-wrap\" | \"break-spaces\" | \"pre-line\";\n/**\n * A value for the [word-break](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#word-break-property) property.\n */\nexport type WordBreak = \"normal\" | \"keep-all\" | \"break-all\" | \"break-word\";\n/**\n * A value for the [line-break](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#line-break-property) property.\n */\nexport type LineBreak = \"auto\" | \"loose\" | \"normal\" | \"strict\" | \"anywhere\";\n/**\n * A value for the [hyphens](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#hyphenation) property.\n */\nexport type Hyphens = \"none\" | \"manual\" | \"auto\";\n/**\n * A value for the [overflow-wrap](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#overflow-wrap-property) property.\n */\nexport type OverflowWrap = \"normal\" | \"anywhere\" | \"break-word\";\n/**\n * A value for the [text-align](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#text-align-property) property.\n */\nexport type TextAlign = \"start\" | \"end\" | \"left\" | \"right\" | \"center\" | \"justify\" | \"match-parent\" | \"justify-all\";\n/**\n * A value for the [text-align-last](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#text-align-last-property) property.\n */\nexport type TextAlignLast = \"auto\" | \"start\" | \"end\" | \"left\" | \"right\" | \"center\" | \"justify\" | \"match-parent\";\n/**\n * A value for the [text-justify](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#text-justify-property) property.\n */\nexport type TextJustify = \"auto\" | \"none\" | \"inter-word\" | \"inter-character\";\n/**\n * A value for the [word-spacing](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#word-spacing-property) and [letter-spacing](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#letter-spacing-property) properties.\n */\nexport type Spacing =\n  | {\n      type: \"normal\";\n    }\n  | {\n      type: \"length\";\n      value: Length;\n    };\nexport type TextDecorationLine = ExclusiveTextDecorationLine | OtherTextDecorationLine[];\nexport type ExclusiveTextDecorationLine = \"none\" | \"spelling-error\" | \"grammar-error\";\nexport type OtherTextDecorationLine = \"underline\" | \"overline\" | \"line-through\" | \"blink\";\n/**\n * A value for the [text-decoration-style](https://www.w3.org/TR/2020/WD-css-text-decor-4-20200506/#text-decoration-style-property) property.\n */\nexport type TextDecorationStyle = \"solid\" | \"double\" | \"dotted\" | \"dashed\" | \"wavy\";\n/**\n * A value for the [text-decoration-thickness](https://www.w3.org/TR/2020/WD-css-text-decor-4-20200506/#text-decoration-width-property) property.\n */\nexport type TextDecorationThickness =\n  | {\n      type: \"auto\";\n    }\n  | {\n      type: \"from-font\";\n    }\n  | {\n      type: \"length-percentage\";\n      value: DimensionPercentageFor_LengthValue;\n    };\n/**\n * A value for the [text-decoration-skip-ink](https://www.w3.org/TR/2020/WD-css-text-decor-4-20200506/#text-decoration-skip-ink-property) property.\n */\nexport type TextDecorationSkipInk = \"auto\" | \"none\" | \"all\";\n/**\n * A value for the [text-emphasis-style](https://www.w3.org/TR/2020/WD-css-text-decor-4-20200506/#text-emphasis-style-property) property.\n */\nexport type TextEmphasisStyle =\n  | {\n      type: \"none\";\n    }\n  | {\n      /**\n       * The fill mode for the marks.\n       */\n      fill: TextEmphasisFillMode;\n      /**\n       * The shape of the marks.\n       */\n      shape?: TextEmphasisShape | null;\n      type: \"keyword\";\n    }\n  | {\n      type: \"string\";\n      value: String;\n    };\n/**\n * A keyword for the [text-emphasis-style](https://www.w3.org/TR/2020/WD-css-text-decor-4-20200506/#text-emphasis-style-property) property.\n *\n * See [TextEmphasisStyle](TextEmphasisStyle).\n */\nexport type TextEmphasisFillMode = \"filled\" | \"open\";\n/**\n * A text emphasis shape for the [text-emphasis-style](https://www.w3.org/TR/2020/WD-css-text-decor-4-20200506/#text-emphasis-style-property) property.\n *\n * See [TextEmphasisStyle](TextEmphasisStyle).\n */\nexport type TextEmphasisShape = \"dot\" | \"circle\" | \"double-circle\" | \"triangle\" | \"sesame\";\n/**\n * A horizontal position keyword for the [text-emphasis-position](https://www.w3.org/TR/2020/WD-css-text-decor-4-20200506/#text-emphasis-position-property) property.\n *\n * See [TextEmphasisPosition](TextEmphasisPosition).\n */\nexport type TextEmphasisPositionHorizontal = \"left\" | \"right\";\n/**\n * A vertical position keyword for the [text-emphasis-position](https://www.w3.org/TR/2020/WD-css-text-decor-4-20200506/#text-emphasis-position-property) property.\n *\n * See [TextEmphasisPosition](TextEmphasisPosition).\n */\nexport type TextEmphasisPositionVertical = \"over\" | \"under\";\n/**\n * A value for the [text-size-adjust](https://w3c.github.io/csswg-drafts/css-size-adjust/#adjustment-control) property.\n */\nexport type TextSizeAdjust =\n  | {\n      type: \"auto\";\n    }\n  | {\n      type: \"none\";\n    }\n  | {\n      type: \"percentage\";\n      value: number;\n    };\n/**\n * A value for the [direction](https://drafts.csswg.org/css-writing-modes-3/#direction) property.\n */\nexport type Direction2 = \"ltr\" | \"rtl\";\n/**\n * A value for the [unicode-bidi](https://drafts.csswg.org/css-writing-modes-3/#unicode-bidi) property.\n */\nexport type UnicodeBidi = \"normal\" | \"embed\" | \"isolate\" | \"bidi-override\" | \"isolate-override\" | \"plaintext\";\n/**\n * A value for the [box-decoration-break](https://www.w3.org/TR/css-break-3/#break-decoration) property.\n */\nexport type BoxDecorationBreak = \"slice\" | \"clone\";\n/**\n * A value for the [resize](https://www.w3.org/TR/2021/WD-css-ui-4-20210316/#resize) property.\n */\nexport type Resize = \"none\" | \"both\" | \"horizontal\" | \"vertical\" | \"block\" | \"inline\";\n/**\n * A pre-defined [cursor](https://www.w3.org/TR/2021/WD-css-ui-4-20210316/#cursor) value, used in the `cursor` property.\n *\n * See [Cursor](Cursor).\n */\nexport type CursorKeyword =\n  | \"auto\"\n  | \"default\"\n  | \"none\"\n  | \"context-menu\"\n  | \"help\"\n  | \"pointer\"\n  | \"progress\"\n  | \"wait\"\n  | \"cell\"\n  | \"crosshair\"\n  | \"text\"\n  | \"vertical-text\"\n  | \"alias\"\n  | \"copy\"\n  | \"move\"\n  | \"no-drop\"\n  | \"not-allowed\"\n  | \"grab\"\n  | \"grabbing\"\n  | \"e-resize\"\n  | \"n-resize\"\n  | \"ne-resize\"\n  | \"nw-resize\"\n  | \"s-resize\"\n  | \"se-resize\"\n  | \"sw-resize\"\n  | \"w-resize\"\n  | \"ew-resize\"\n  | \"ns-resize\"\n  | \"nesw-resize\"\n  | \"nwse-resize\"\n  | \"col-resize\"\n  | \"row-resize\"\n  | \"all-scroll\"\n  | \"zoom-in\"\n  | \"zoom-out\";\n/**\n * A value for the [caret-color](https://www.w3.org/TR/2021/WD-css-ui-4-20210316/#caret-color) property.\n */\nexport type ColorOrAuto =\n  | {\n      type: \"auto\";\n    }\n  | {\n      type: \"color\";\n      value: CssColor;\n    };\n/**\n * A value for the [caret-shape](https://www.w3.org/TR/2021/WD-css-ui-4-20210316/#caret-shape) property.\n */\nexport type CaretShape = \"auto\" | \"bar\" | \"block\" | \"underscore\";\n/**\n * A value for the [user-select](https://www.w3.org/TR/2021/WD-css-ui-4-20210316/#content-selection) property.\n */\nexport type UserSelect = \"auto\" | \"text\" | \"none\" | \"contain\" | \"all\";\nexport type Appearance = string;\n/**\n * A value for the [list-style-type](https://www.w3.org/TR/2020/WD-css-lists-3-20201117/#text-markers) property.\n */\nexport type ListStyleType =\n  | {\n      type: \"none\";\n    }\n  | {\n      type: \"string\";\n      value: String;\n    }\n  | {\n      type: \"counter-style\";\n      value: CounterStyle;\n    };\n/**\n * A [counter-style](https://www.w3.org/TR/css-counter-styles-3/#typedef-counter-style) name.\n */\nexport type CounterStyle =\n  | {\n      type: \"predefined\";\n      value: PredefinedCounterStyle;\n    }\n  | {\n      type: \"name\";\n      value: String;\n    }\n  | {\n      /**\n       * The symbols.\n       */\n      symbols: Symbol[];\n      /**\n       * The counter system.\n       */\n      system?: SymbolsType & string;\n      type: \"symbols\";\n    };\n/**\n * A [predefined counter](https://www.w3.org/TR/css-counter-styles-3/#predefined-counters) style.\n */\nexport type PredefinedCounterStyle =\n  | \"decimal\"\n  | \"decimal-leading-zero\"\n  | \"arabic-indic\"\n  | \"armenian\"\n  | \"upper-armenian\"\n  | \"lower-armenian\"\n  | \"bengali\"\n  | \"cambodian\"\n  | \"khmer\"\n  | \"cjk-decimal\"\n  | \"devanagari\"\n  | \"georgian\"\n  | \"gujarati\"\n  | \"gurmukhi\"\n  | \"hebrew\"\n  | \"kannada\"\n  | \"lao\"\n  | \"malayalam\"\n  | \"mongolian\"\n  | \"myanmar\"\n  | \"oriya\"\n  | \"persian\"\n  | \"lower-roman\"\n  | \"upper-roman\"\n  | \"tamil\"\n  | \"telugu\"\n  | \"thai\"\n  | \"tibetan\"\n  | \"lower-alpha\"\n  | \"lower-latin\"\n  | \"upper-alpha\"\n  | \"upper-latin\"\n  | \"lower-greek\"\n  | \"hiragana\"\n  | \"hiragana-iroha\"\n  | \"katakana\"\n  | \"katakana-iroha\"\n  | \"disc\"\n  | \"circle\"\n  | \"square\"\n  | \"disclosure-open\"\n  | \"disclosure-closed\"\n  | \"cjk-earthly-branch\"\n  | \"cjk-heavenly-stem\"\n  | \"japanese-informal\"\n  | \"japanese-formal\"\n  | \"korean-hangul-formal\"\n  | \"korean-hanja-informal\"\n  | \"korean-hanja-formal\"\n  | \"simp-chinese-informal\"\n  | \"simp-chinese-formal\"\n  | \"trad-chinese-informal\"\n  | \"trad-chinese-formal\"\n  | \"ethiopic-numeric\";\n/**\n * A single [symbol](https://www.w3.org/TR/css-counter-styles-3/#funcdef-symbols) as used in the `symbols()` function.\n *\n * See [CounterStyle](CounterStyle).\n */\nexport type Symbol =\n  | {\n      type: \"string\";\n      value: String;\n    }\n  | {\n      type: \"image\";\n      value: Image;\n    };\n/**\n * A [`<symbols-type>`](https://www.w3.org/TR/css-counter-styles-3/#typedef-symbols-type) value, as used in the `symbols()` function.\n *\n * See [CounterStyle](CounterStyle).\n */\nexport type SymbolsType = \"cyclic\" | \"numeric\" | \"alphabetic\" | \"symbolic\" | \"fixed\";\n/**\n * A value for the [list-style-position](https://www.w3.org/TR/2020/WD-css-lists-3-20201117/#list-style-position-property) property.\n */\nexport type ListStylePosition = \"inside\" | \"outside\";\n/**\n * A value for the [marker-side](https://www.w3.org/TR/2020/WD-css-lists-3-20201117/#marker-side) property.\n */\nexport type MarkerSide = \"match-self\" | \"match-parent\";\n/**\n * An SVG [`<paint>`](https://www.w3.org/TR/SVG2/painting.html#SpecifyingPaint) value used in the `fill` and `stroke` properties.\n */\nexport type SVGPaint =\n  | {\n      /**\n       * A fallback to be used used in case the paint server cannot be resolved.\n       */\n      fallback?: SVGPaintFallback | null;\n      type: \"url\";\n      /**\n       * The url of the paint server.\n       */\n      url: Url;\n    }\n  | {\n      type: \"color\";\n      value: CssColor;\n    }\n  | {\n      type: \"context-fill\";\n    }\n  | {\n      type: \"context-stroke\";\n    }\n  | {\n      type: \"none\";\n    };\n/**\n * A fallback for an SVG paint in case a paint server `url()` cannot be resolved.\n *\n * See [SVGPaint](SVGPaint).\n */\nexport type SVGPaintFallback =\n  | {\n      type: \"none\";\n    }\n  | {\n      type: \"color\";\n      value: CssColor;\n    };\n/**\n * A [`<fill-rule>`](https://www.w3.org/TR/css-shapes-1/#typedef-fill-rule) used to determine the interior of a `polygon()` shape.\n *\n * See [Polygon](Polygon).\n */\nexport type FillRule = \"nonzero\" | \"evenodd\";\n/**\n * A value for the [stroke-linecap](https://www.w3.org/TR/SVG2/painting.html#LineCaps) property.\n */\nexport type StrokeLinecap = \"butt\" | \"round\" | \"square\";\n/**\n * A value for the [stroke-linejoin](https://www.w3.org/TR/SVG2/painting.html#LineJoin) property.\n */\nexport type StrokeLinejoin = \"miter\" | \"miter-clip\" | \"round\" | \"bevel\" | \"arcs\";\n/**\n * A value for the [stroke-dasharray](https://www.w3.org/TR/SVG2/painting.html#StrokeDashing) property.\n */\nexport type StrokeDasharray =\n  | {\n      type: \"none\";\n    }\n  | {\n      type: \"values\";\n      value: DimensionPercentageFor_LengthValue[];\n    };\n/**\n * A value for the [marker](https://www.w3.org/TR/SVG2/painting.html#VertexMarkerProperties) properties.\n */\nexport type Marker =\n  | {\n      type: \"none\";\n    }\n  | {\n      type: \"url\";\n      value: Url;\n    };\n/**\n * A value for the [color-interpolation](https://www.w3.org/TR/SVG2/painting.html#ColorInterpolation) property.\n */\nexport type ColorInterpolation = \"auto\" | \"srgb\" | \"linearrgb\";\n/**\n * A value for the [color-rendering](https://www.w3.org/TR/SVG2/painting.html#ColorRendering) property.\n */\nexport type ColorRendering = \"auto\" | \"optimizespeed\" | \"optimizequality\";\n/**\n * A value for the [shape-rendering](https://www.w3.org/TR/SVG2/painting.html#ShapeRendering) property.\n */\nexport type ShapeRendering = \"auto\" | \"optimizespeed\" | \"crispedges\" | \"geometricprecision\";\n/**\n * A value for the [text-rendering](https://www.w3.org/TR/SVG2/painting.html#TextRendering) property.\n */\nexport type TextRendering = \"auto\" | \"optimizespeed\" | \"optimizelegibility\" | \"geometricprecision\";\n/**\n * A value for the [image-rendering](https://www.w3.org/TR/SVG2/painting.html#ImageRendering) property.\n */\nexport type ImageRendering = \"auto\" | \"optimizespeed\" | \"optimizequality\";\n/**\n * A value for the [clip-path](https://www.w3.org/TR/css-masking-1/#the-clip-path) property.\n */\nexport type ClipPath =\n  | {\n      type: \"none\";\n    }\n  | {\n      type: \"url\";\n      value: Url;\n    }\n  | {\n      /**\n       * A reference box that the shape is positioned according to.\n       */\n      referenceBox: GeometryBox;\n      /**\n       * A basic shape.\n       */\n      shape: BasicShape;\n      type: \"shape\";\n    }\n  | {\n      type: \"box\";\n      value: GeometryBox;\n    };\n/**\n * A [`<geometry-box>`](https://www.w3.org/TR/css-masking-1/#typedef-geometry-box) value as used in the `mask-clip` and `clip-path` properties.\n */\nexport type GeometryBox =\n  | \"border-box\"\n  | \"padding-box\"\n  | \"content-box\"\n  | \"margin-box\"\n  | \"fill-box\"\n  | \"stroke-box\"\n  | \"view-box\";\n/**\n * A CSS [`<basic-shape>`](https://www.w3.org/TR/css-shapes-1/#basic-shape-functions) value.\n */\nexport type BasicShape =\n  | {\n      type: \"inset\";\n      value: InsetRect;\n    }\n  | {\n      type: \"circle\";\n      value: Circle2;\n    }\n  | {\n      type: \"ellipse\";\n      value: Ellipse2;\n    }\n  | {\n      type: \"polygon\";\n      value: Polygon;\n    };\n/**\n * A generic value that represents a value for four sides of a box, e.g. border-width, margin, padding, etc.\n *\n * When serialized, as few components as possible are written when there are duplicate values.\n *\n * @minItems 4\n * @maxItems 4\n */\nexport type RectFor_DimensionPercentageFor_LengthValue = [\n  DimensionPercentageFor_LengthValue,\n  DimensionPercentageFor_LengthValue,\n  DimensionPercentageFor_LengthValue,\n  DimensionPercentageFor_LengthValue\n];\n/**\n * A [`<shape-radius>`](https://www.w3.org/TR/css-shapes-1/#typedef-shape-radius) value that defines the radius of a `circle()` or `ellipse()` shape.\n */\nexport type ShapeRadius =\n  | {\n      type: \"length-percentage\";\n      value: DimensionPercentageFor_LengthValue;\n    }\n  | {\n      type: \"closest-side\";\n    }\n  | {\n      type: \"farthest-side\";\n    };\n/**\n * A value for the [mask-mode](https://www.w3.org/TR/css-masking-1/#the-mask-mode) property.\n */\nexport type MaskMode = \"luminance\" | \"alpha\" | \"match-source\";\n/**\n * A value for the [mask-clip](https://www.w3.org/TR/css-masking-1/#the-mask-clip) property.\n */\nexport type MaskClip =\n  | {\n      type: \"geometry-box\";\n      value: GeometryBox;\n    }\n  | {\n      type: \"no-clip\";\n    };\n/**\n * A value for the [mask-composite](https://www.w3.org/TR/css-masking-1/#the-mask-composite) property.\n */\nexport type MaskComposite = \"add\" | \"subtract\" | \"intersect\" | \"exclude\";\n/**\n * A value for the [mask-type](https://www.w3.org/TR/css-masking-1/#the-mask-type) property.\n */\nexport type MaskType = \"luminance\" | \"alpha\";\n/**\n * A value for the [mask-border-mode](https://www.w3.org/TR/css-masking-1/#the-mask-border-mode) property.\n */\nexport type MaskBorderMode = \"luminance\" | \"alpha\";\n/**\n * A value for the [-webkit-mask-composite](https://developer.mozilla.org/en-US/docs/Web/CSS/-webkit-mask-composite) property.\n *\n * See also [MaskComposite](MaskComposite).\n */\nexport type WebKitMaskComposite =\n  | (\"clear\" | \"copy\" | \"source-atop\" | \"destination-over\" | \"destination-in\" | \"destination-out\" | \"destination-atop\")\n  | \"source-over\"\n  | \"source-in\"\n  | \"source-out\"\n  | \"xor\";\n/**\n * A value for the [-webkit-mask-source-type](https://github.com/WebKit/WebKit/blob/6eece09a1c31e47489811edd003d1e36910e9fd3/Source/WebCore/css/CSSProperties.json#L6578-L6587) property.\n *\n * See also [MaskMode](MaskMode).\n */\nexport type WebKitMaskSourceType = \"auto\" | \"luminance\" | \"alpha\";\n/**\n * A value for the [filter](https://drafts.fxtf.org/filter-effects-1/#FilterProperty) and [backdrop-filter](https://drafts.fxtf.org/filter-effects-2/#BackdropFilterProperty) properties.\n */\nexport type FilterList =\n  | {\n      type: \"none\";\n    }\n  | {\n      type: \"filters\";\n      value: Filter[];\n    };\n/**\n * A [filter](https://drafts.fxtf.org/filter-effects-1/#filter-functions) function.\n */\nexport type Filter =\n  | {\n      type: \"blur\";\n      value: Length;\n    }\n  | {\n      type: \"brightness\";\n      value: NumberOrPercentage;\n    }\n  | {\n      type: \"contrast\";\n      value: NumberOrPercentage;\n    }\n  | {\n      type: \"grayscale\";\n      value: NumberOrPercentage;\n    }\n  | {\n      type: \"hue-rotate\";\n      value: Angle;\n    }\n  | {\n      type: \"invert\";\n      value: NumberOrPercentage;\n    }\n  | {\n      type: \"opacity\";\n      value: NumberOrPercentage;\n    }\n  | {\n      type: \"saturate\";\n      value: NumberOrPercentage;\n    }\n  | {\n      type: \"sepia\";\n      value: NumberOrPercentage;\n    }\n  | {\n      type: \"drop-shadow\";\n      value: DropShadow;\n    }\n  | {\n      type: \"url\";\n      value: Url;\n    };\n/**\n * A value for the [z-index](https://drafts.csswg.org/css2/#z-index) property.\n */\nexport type ZIndex =\n  | {\n      type: \"auto\";\n    }\n  | {\n      type: \"integer\";\n      value: number;\n    };\n/**\n * A value for the [container-type](https://drafts.csswg.org/css-contain-3/#container-type) property. Establishes the element as a query container for the purpose of container queries.\n */\nexport type ContainerType = \"normal\" | \"inline-size\" | \"size\" | \"scroll-state\";\n/**\n * A value for the [container-name](https://drafts.csswg.org/css-contain-3/#container-name) property.\n */\nexport type ContainerNameList =\n  | {\n      type: \"none\";\n    }\n  | {\n      type: \"names\";\n      value: String[];\n    };\n/**\n * A value for the [view-transition-name](https://drafts.csswg.org/css-view-transitions-1/#view-transition-name-prop) property.\n */\nexport type ViewTransitionName =\n  \"none\" | \"auto\" | String;\n/**\n * The `none` keyword, or a space-separated list of custom idents.\n */\nexport type NoneOrCustomIdentList =\n  \"none\" | String[];\n/**\n * A value for the [view-transition-group](https://drafts.csswg.org/css-view-transitions-2/#view-transition-group-prop) property.\n */\nexport type ViewTransitionGroup =\n  \"normal\" | \"contain\" | \"nearest\" | String;\n/**\n * A value for the [print-color-adjust](https://drafts.csswg.org/css-color-adjust/#propdef-print-color-adjust) property.\n */\nexport type PrintColorAdjust = \"economy\" | \"exact\";\n/**\n * A [CSS-wide keyword](https://drafts.csswg.org/css-cascade-5/#defaulting-keywords).\n */\nexport type CSSWideKeyword = \"initial\" | \"inherit\" | \"unset\" | \"revert\" | \"revert-layer\";\n/**\n * A CSS custom property name.\n */\nexport type CustomPropertyName = String | String;\nexport type SelectorComponent =\n  | {\n      type: \"combinator\";\n      value: Combinator;\n    }\n  | {\n      type: \"universal\";\n    }\n  | (\n      | {\n          type: \"namespace\";\n          kind: \"none\";\n        }\n      | {\n          type: \"namespace\";\n          kind: \"any\";\n        }\n      | {\n          type: \"namespace\";\n          kind: \"named\";\n          prefix: string;\n        }\n    )\n  | {\n      name: string;\n      type: \"type\";\n    }\n  | {\n      name: string;\n      type: \"id\";\n    }\n  | {\n      name: string;\n      type: \"class\";\n    }\n  | {\n      name: string;\n      namespace?: NamespaceConstraint | null;\n      operation?: AttrOperation | null;\n      type: \"attribute\";\n    }\n  | ({\n      type: \"pseudo-class\";\n    } & (TSPseudoClass | PseudoClass))\n  | ({\n      type: \"pseudo-element\";\n    } & (BuiltinPseudoElement | PseudoElement))\n  | {\n      type: \"nesting\";\n    };\nexport type Combinator =\n  | (\"child\" | \"descendant\" | \"next-sibling\" | \"later-sibling\")\n  | \"pseudo-element\"\n  | \"slot-assignment\"\n  | \"part\"\n  | \"deep-descendant\"\n  | \"deep\";\nexport type NamespaceConstraint =\n  | {\n      type: \"any\";\n    }\n  | {\n      prefix: string;\n      type: \"specific\";\n      url: string;\n    };\nexport type ParsedCaseSensitivity =\n  | \"explicit-case-sensitive\"\n  | \"ascii-case-insensitive\"\n  | \"case-sensitive\"\n  | \"ascii-case-insensitive-if-in-html-element-in-html-document\";\nexport type AttrSelectorOperator = \"equal\" | \"includes\" | \"dash-match\" | \"prefix\" | \"substring\" | \"suffix\";\nexport type TSPseudoClass =\n  | {\n      kind: \"not\";\n      selectors: Selector[];\n    }\n  | {\n      kind: \"first-child\";\n    }\n  | {\n      kind: \"last-child\";\n    }\n  | {\n      kind: \"only-child\";\n    }\n  | {\n      kind: \"root\";\n    }\n  | {\n      kind: \"empty\";\n    }\n  | {\n      kind: \"scope\";\n    }\n  | {\n      a: number;\n      b: number;\n      kind: \"nth-child\";\n      of?: Selector[] | null;\n    }\n  | {\n      a: number;\n      b: number;\n      kind: \"nth-last-child\";\n      of?: Selector[] | null;\n    }\n  | {\n      a: number;\n      b: number;\n      kind: \"nth-col\";\n    }\n  | {\n      a: number;\n      b: number;\n      kind: \"nth-last-col\";\n    }\n  | {\n      a: number;\n      b: number;\n      kind: \"nth-of-type\";\n    }\n  | {\n      a: number;\n      b: number;\n      kind: \"nth-last-of-type\";\n    }\n  | {\n      kind: \"first-of-type\";\n    }\n  | {\n      kind: \"last-of-type\";\n    }\n  | {\n      kind: \"only-of-type\";\n    }\n  | {\n      kind: \"host\";\n      selectors?: Selector | null;\n    }\n  | {\n      kind: \"where\";\n      selectors: Selector[];\n    }\n  | {\n      kind: \"is\";\n      selectors: Selector[];\n    }\n  | {\n      kind: \"any\";\n      selectors: Selector[];\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      kind: \"has\";\n      selectors: Selector[];\n    };\n/**\n * A pseudo class.\n */\nexport type PseudoClass =\n  | {\n      kind: \"lang\";\n      /**\n       * A list of language codes.\n       */\n      languages: String[];\n    }\n  | {\n      /**\n       * A direction.\n       */\n      direction: Direction;\n      kind: \"dir\";\n    }\n  | {\n      kind: \"hover\";\n    }\n  | {\n      kind: \"active\";\n    }\n  | {\n      kind: \"focus\";\n    }\n  | {\n      kind: \"focus-visible\";\n    }\n  | {\n      kind: \"focus-within\";\n    }\n  | {\n      kind: \"current\";\n    }\n  | {\n      kind: \"past\";\n    }\n  | {\n      kind: \"future\";\n    }\n  | {\n      kind: \"playing\";\n    }\n  | {\n      kind: \"paused\";\n    }\n  | {\n      kind: \"seeking\";\n    }\n  | {\n      kind: \"buffering\";\n    }\n  | {\n      kind: \"stalled\";\n    }\n  | {\n      kind: \"muted\";\n    }\n  | {\n      kind: \"volume-locked\";\n    }\n  | {\n      kind: \"fullscreen\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      kind: \"open\";\n    }\n  | {\n      kind: \"closed\";\n    }\n  | {\n      kind: \"modal\";\n    }\n  | {\n      kind: \"picture-in-picture\";\n    }\n  | {\n      kind: \"popover-open\";\n    }\n  | {\n      kind: \"defined\";\n    }\n  | {\n      kind: \"any-link\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      kind: \"link\";\n    }\n  | {\n      kind: \"local-link\";\n    }\n  | {\n      kind: \"target\";\n    }\n  | {\n      kind: \"target-within\";\n    }\n  | {\n      kind: \"visited\";\n    }\n  | {\n      kind: \"enabled\";\n    }\n  | {\n      kind: \"disabled\";\n    }\n  | {\n      kind: \"read-only\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      kind: \"read-write\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      kind: \"placeholder-shown\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      kind: \"default\";\n    }\n  | {\n      kind: \"checked\";\n    }\n  | {\n      kind: \"indeterminate\";\n    }\n  | {\n      kind: \"blank\";\n    }\n  | {\n      kind: \"valid\";\n    }\n  | {\n      kind: \"invalid\";\n    }\n  | {\n      kind: \"in-range\";\n    }\n  | {\n      kind: \"out-of-range\";\n    }\n  | {\n      kind: \"required\";\n    }\n  | {\n      kind: \"optional\";\n    }\n  | {\n      kind: \"user-valid\";\n    }\n  | {\n      kind: \"user-invalid\";\n    }\n  | {\n      kind: \"autofill\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      kind: \"active-view-transition\";\n    }\n  | {\n      kind: \"active-view-transition-type\";\n      /**\n       * A view transition type.\n       */\n      type: String[];\n    }\n  | {\n      kind: \"state\";\n      /**\n       * The custom state identifier.\n       */\n      state: String;\n    }\n  | {\n      kind: \"local\";\n      /**\n       * A local selector.\n       */\n      selector: Selector;\n    }\n  | {\n      kind: \"global\";\n      /**\n       * A global selector.\n       */\n      selector: Selector;\n    }\n  | {\n      kind: \"webkit-scrollbar\";\n      value: WebKitScrollbarPseudoClass;\n    }\n  | {\n      kind: \"custom\";\n      /**\n       * The pseudo class name.\n       */\n      name: String;\n    }\n  | {\n      /**\n       * The arguments of the pseudo class function.\n       */\n      arguments: TokenOrValue[];\n      kind: \"custom-function\";\n      /**\n       * The pseudo class name.\n       */\n      name: String;\n    };\n/**\n * The [:dir()](https://drafts.csswg.org/selectors-4/#the-dir-pseudo) pseudo class.\n */\nexport type Direction = \"ltr\" | \"rtl\";\n/**\n * A [webkit scrollbar](https://webkit.org/blog/363/styling-scrollbars/) pseudo class.\n */\nexport type WebKitScrollbarPseudoClass =\n  | \"horizontal\"\n  | \"vertical\"\n  | \"decrement\"\n  | \"increment\"\n  | \"start\"\n  | \"end\"\n  | \"double-button\"\n  | \"single-button\"\n  | \"no-button\"\n  | \"corner-present\"\n  | \"window-inactive\";\nexport type BuiltinPseudoElement =\n  | {\n      kind: \"slotted\";\n      selector: Selector;\n    }\n  | {\n      kind: \"part\";\n      names: string[];\n    };\n/**\n * A pseudo element.\n */\nexport type PseudoElement =\n  | {\n      kind: \"after\";\n    }\n  | {\n      kind: \"before\";\n    }\n  | {\n      kind: \"first-line\";\n    }\n  | {\n      kind: \"first-letter\";\n    }\n  | {\n      kind: \"details-content\";\n    }\n  | {\n      kind: \"target-text\";\n    }\n  | {\n      kind: \"selection\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      kind: \"placeholder\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      kind: \"marker\";\n    }\n  | {\n      kind: \"backdrop\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      kind: \"file-selector-button\";\n      vendorPrefix: VendorPrefix;\n    }\n  | {\n      kind: \"webkit-scrollbar\";\n      value: WebKitScrollbarPseudoElement;\n    }\n  | {\n      kind: \"cue\";\n    }\n  | {\n      kind: \"cue-region\";\n    }\n  | {\n      kind: \"cue-function\";\n      /**\n       * The selector argument.\n       */\n      selector: Selector;\n    }\n  | {\n      kind: \"cue-region-function\";\n      /**\n       * The selector argument.\n       */\n      selector: Selector;\n    }\n  | {\n      kind: \"view-transition\";\n    }\n  | {\n      kind: \"view-transition-group\";\n      /**\n       * A part name selector.\n       */\n      part: ViewTransitionPartSelector;\n    }\n  | {\n      kind: \"view-transition-image-pair\";\n      /**\n       * A part name selector.\n       */\n      part: ViewTransitionPartSelector;\n    }\n  | {\n      kind: \"view-transition-old\";\n      /**\n       * A part name selector.\n       */\n      part: ViewTransitionPartSelector;\n    }\n  | {\n      kind: \"view-transition-new\";\n      /**\n       * A part name selector.\n       */\n      part: ViewTransitionPartSelector;\n    }\n  | {\n      /**\n       * A form control identifier.\n       */\n      identifier: String;\n      kind: \"picker-function\";\n    }\n  | {\n      kind: \"picker-icon\";\n    }\n  | {\n      kind: \"checkmark\";\n    }\n  | {\n      kind: \"grammar-error\";\n    }\n  | {\n      kind: \"spelling-error\";\n    }\n  | {\n      kind: \"custom\";\n      /**\n       * The name of the pseudo element.\n       */\n      name: String;\n    }\n  | {\n      /**\n       * The arguments of the pseudo element function.\n       */\n      arguments: TokenOrValue[];\n      kind: \"custom-function\";\n      /**\n       * The name of the pseudo element.\n       */\n      name: String;\n    };\n/**\n * A [webkit scrollbar](https://webkit.org/blog/363/styling-scrollbars/) pseudo element.\n */\nexport type WebKitScrollbarPseudoElement =\n  | \"scrollbar\"\n  | \"button\"\n  | \"track\"\n  | \"track-piece\"\n  | \"thumb\"\n  | \"corner\"\n  | \"resizer\";\nexport type ViewTransitionPartName = string;\nexport type Selector = SelectorComponent[];\nexport type SelectorList = Selector[];\n/**\n * A [keyframe selector](https://drafts.csswg.org/css-animations/#typedef-keyframe-selector) within an `@keyframes` rule.\n */\nexport type KeyframeSelector =\n  | {\n      type: \"percentage\";\n      value: number;\n    }\n  | {\n      type: \"from\";\n    }\n  | {\n      type: \"to\";\n    }\n  | {\n      type: \"timeline-range-percentage\";\n      value: TimelineRangePercentage;\n    };\n/**\n * KeyframesName\n */\nexport type KeyframesName =\n  | {\n      type: \"ident\";\n      value: String;\n    }\n  | {\n      type: \"custom\";\n      value: String;\n    };\n/**\n * A property within an `@font-face` rule.\n *\n * See [FontFaceRule](FontFaceRule).\n */\nexport type FontFaceProperty =\n  | {\n      type: \"source\";\n      value: Source[];\n    }\n  | {\n      type: \"font-family\";\n      value: FontFamily;\n    }\n  | {\n      type: \"font-style\";\n      value: FontStyle2;\n    }\n  | {\n      type: \"font-weight\";\n      value: Size2DFor_FontWeight;\n    }\n  | {\n      type: \"font-stretch\";\n      value: Size2DFor_FontStretch;\n    }\n  | {\n      type: \"unicode-range\";\n      value: UnicodeRange[];\n    }\n  | {\n      type: \"custom\";\n      value: CustomProperty;\n    };\n/**\n * A value for the [src](https://drafts.csswg.org/css-fonts/#src-desc) property in an `@font-face` rule.\n */\nexport type Source =\n  | {\n      type: \"url\";\n      value: UrlSource;\n    }\n  | {\n      type: \"local\";\n      value: FontFamily;\n    };\n/**\n * A font format keyword in the `format()` function of the the [src](https://drafts.csswg.org/css-fonts/#src-desc) property of an `@font-face` rule.\n */\nexport type FontFormat =\n  | {\n      type: \"woff\";\n    }\n  | {\n      type: \"woff2\";\n    }\n  | {\n      type: \"truetype\";\n    }\n  | {\n      type: \"opentype\";\n    }\n  | {\n      type: \"embedded-opentype\";\n    }\n  | {\n      type: \"collection\";\n    }\n  | {\n      type: \"svg\";\n    }\n  | {\n      type: \"string\";\n      value: String;\n    };\n/**\n * A font format keyword in the `format()` function of the the [src](https://drafts.csswg.org/css-fonts/#src-desc) property of an `@font-face` rule.\n */\nexport type FontTechnology =\n  | \"features-opentype\"\n  | \"features-aat\"\n  | \"features-graphite\"\n  | \"color-colrv0\"\n  | \"color-colrv1\"\n  | \"color-svg\"\n  | \"color-sbix\"\n  | \"color-cbdt\"\n  | \"variations\"\n  | \"palettes\"\n  | \"incremental\";\n/**\n * A value for the [font-style](https://w3c.github.io/csswg-drafts/css-fonts/#descdef-font-face-font-style) descriptor in an `@font-face` rule.\n */\nexport type FontStyle2 =\n  | {\n      type: \"normal\";\n    }\n  | {\n      type: \"italic\";\n    }\n  | {\n      type: \"oblique\";\n      value: Size2DFor_Angle;\n    };\n/**\n * A generic value that represents a value with two components, e.g. a border radius.\n *\n * When serialized, only a single component will be written if both are equal.\n *\n * @minItems 2\n * @maxItems 2\n */\nexport type Size2DFor_Angle = [Angle, Angle];\n/**\n * A generic value that represents a value with two components, e.g. a border radius.\n *\n * When serialized, only a single component will be written if both are equal.\n *\n * @minItems 2\n * @maxItems 2\n */\nexport type Size2DFor_FontWeight = [FontWeight, FontWeight];\n/**\n * A generic value that represents a value with two components, e.g. a border radius.\n *\n * When serialized, only a single component will be written if both are equal.\n *\n * @minItems 2\n * @maxItems 2\n */\nexport type Size2DFor_FontStretch = [FontStretch, FontStretch];\n/**\n * A property within an `@font-palette-values` rule.\n *\n * See [FontPaletteValuesRule](FontPaletteValuesRule).\n */\nexport type FontPaletteValuesProperty =\n  | {\n      type: \"font-family\";\n      value: FontFamily;\n    }\n  | {\n      type: \"base-palette\";\n      value: BasePalette;\n    }\n  | {\n      type: \"override-colors\";\n      value: OverrideColors[];\n    }\n  | {\n      type: \"custom\";\n      value: CustomProperty;\n    };\n/**\n * A value for the [base-palette](https://drafts.csswg.org/css-fonts-4/#base-palette-desc) property in an `@font-palette-values` rule.\n */\nexport type BasePalette =\n  | {\n      type: \"light\";\n    }\n  | {\n      type: \"dark\";\n    }\n  | {\n      type: \"integer\";\n      value: number;\n    };\n/**\n * The name of the `@font-feature-values` sub-rule. font-feature-value-type = <@stylistic> | <@historical-forms> | <@styleset> | <@character-variant> | <@swash> | <@ornaments> | <@annotation>\n */\nexport type FontFeatureSubruleType =\n  | \"stylistic\"\n  | \"historical-forms\"\n  | \"styleset\"\n  | \"character-variant\"\n  | \"swash\"\n  | \"ornaments\"\n  | \"annotation\";\n/**\n * A [page margin box](https://www.w3.org/TR/css-page-3/#margin-boxes).\n */\nexport type PageMarginBox =\n  | \"top-left-corner\"\n  | \"top-left\"\n  | \"top-center\"\n  | \"top-right\"\n  | \"top-right-corner\"\n  | \"left-top\"\n  | \"left-middle\"\n  | \"left-bottom\"\n  | \"right-top\"\n  | \"right-middle\"\n  | \"right-bottom\"\n  | \"bottom-left-corner\"\n  | \"bottom-left\"\n  | \"bottom-center\"\n  | \"bottom-right\"\n  | \"bottom-right-corner\";\n/**\n * A page pseudo class within an `@page` selector.\n *\n * See [PageSelector](PageSelector).\n */\nexport type PagePseudoClass = \"left\" | \"right\" | \"first\" | \"last\" | \"blank\";\n/**\n * A parsed value for a [SyntaxComponent](SyntaxComponent).\n */\nexport type ParsedComponent =\n  | {\n      type: \"length\";\n      value: Length;\n    }\n  | {\n      type: \"number\";\n      value: number;\n    }\n  | {\n      type: \"percentage\";\n      value: number;\n    }\n  | {\n      type: \"length-percentage\";\n      value: DimensionPercentageFor_LengthValue;\n    }\n  | {\n      type: \"string\";\n      value: String;\n    }\n  | {\n      type: \"color\";\n      value: CssColor;\n    }\n  | {\n      type: \"image\";\n      value: Image;\n    }\n  | {\n      type: \"url\";\n      value: Url;\n    }\n  | {\n      type: \"integer\";\n      value: number;\n    }\n  | {\n      type: \"angle\";\n      value: Angle;\n    }\n  | {\n      type: \"time\";\n      value: Time;\n    }\n  | {\n      type: \"resolution\";\n      value: Resolution;\n    }\n  | {\n      type: \"transform-function\";\n      value: Transform;\n    }\n  | {\n      type: \"transform-list\";\n      value: Transform[];\n    }\n  | {\n      type: \"custom-ident\";\n      value: String;\n    }\n  | {\n      type: \"literal\";\n      value: String;\n    }\n  | {\n      type: \"repeated\";\n      value: {\n        /**\n         * The components to repeat.\n         */\n        components: ParsedComponent[];\n        /**\n         * A multiplier describing how the components repeat.\n         */\n        multiplier: Multiplier;\n      };\n    }\n  | {\n      type: \"token-list\";\n      value: TokenOrValue[];\n    };\n/**\n * A [multiplier](https://drafts.css-houdini.org/css-properties-values-api/#multipliers) for a [SyntaxComponent](SyntaxComponent). Indicates whether and how the component may be repeated.\n */\nexport type Multiplier =\n  | {\n      type: \"none\";\n    }\n  | {\n      type: \"space\";\n    }\n  | {\n      type: \"comma\";\n    };\n/**\n * A CSS [syntax string](https://drafts.css-houdini.org/css-properties-values-api/#syntax-strings) used to define the grammar for a registered custom property.\n */\nexport type SyntaxString =\n  | {\n      type: \"components\";\n      value: SyntaxComponent[];\n    }\n  | {\n      type: \"universal\";\n    };\n/**\n * A [syntax component component name](https://drafts.css-houdini.org/css-properties-values-api/#supported-names).\n */\nexport type SyntaxComponentKind =\n  | {\n      type: \"length\";\n    }\n  | {\n      type: \"number\";\n    }\n  | {\n      type: \"percentage\";\n    }\n  | {\n      type: \"length-percentage\";\n    }\n  | {\n      type: \"string\";\n    }\n  | {\n      type: \"color\";\n    }\n  | {\n      type: \"image\";\n    }\n  | {\n      type: \"url\";\n    }\n  | {\n      type: \"integer\";\n    }\n  | {\n      type: \"angle\";\n    }\n  | {\n      type: \"time\";\n    }\n  | {\n      type: \"resolution\";\n    }\n  | {\n      type: \"transform-function\";\n    }\n  | {\n      type: \"transform-list\";\n    }\n  | {\n      type: \"custom-ident\";\n    }\n  | {\n      type: \"literal\";\n      value: string;\n    };\n/**\n * Represents a container condition.\n */\nexport type ContainerCondition<D = Declaration> = | {\n    type: \"feature\";\n    value: QueryFeatureFor_ContainerSizeFeatureId;\n  }\n| {\n    type: \"not\";\n    value: ContainerCondition<D>;\n  }\n| {\n    /**\n     * The conditions for the operator.\n     */\n    conditions: ContainerCondition<D>[];\n    /**\n     * The operator for the conditions.\n     */\n    operator: Operator;\n    type: \"operation\";\n  }\n| {\n    type: \"style\";\n    value: StyleQuery<D>;\n  }\n| {\n    type: \"scroll-state\";\n    value: ScrollStateQuery;\n  }\n| {\n    type: \"unknown\";\n    value: TokenOrValue[];\n  };\n/**\n * A generic media feature or container feature.\n */\nexport type QueryFeatureFor_ContainerSizeFeatureId =\n  | {\n      /**\n       * The name of the feature.\n       */\n      name: MediaFeatureNameFor_ContainerSizeFeatureId;\n      type: \"plain\";\n      /**\n       * The feature value.\n       */\n      value: MediaFeatureValue;\n    }\n  | {\n      /**\n       * The name of the feature.\n       */\n      name: MediaFeatureNameFor_ContainerSizeFeatureId;\n      type: \"boolean\";\n    }\n  | {\n      /**\n       * The name of the feature.\n       */\n      name: MediaFeatureNameFor_ContainerSizeFeatureId;\n      /**\n       * A comparator.\n       */\n      operator: MediaFeatureComparison;\n      type: \"range\";\n      /**\n       * The feature value.\n       */\n      value: MediaFeatureValue;\n    }\n  | {\n      /**\n       * The end value.\n       */\n      end: MediaFeatureValue;\n      /**\n       * A comparator for the end value.\n       */\n      endOperator: MediaFeatureComparison;\n      /**\n       * The name of the feature.\n       */\n      name: MediaFeatureNameFor_ContainerSizeFeatureId;\n      /**\n       * A start value.\n       */\n      start: MediaFeatureValue;\n      /**\n       * A comparator for the start value.\n       */\n      startOperator: MediaFeatureComparison;\n      type: \"interval\";\n    };\n/**\n * A media feature name.\n */\nexport type MediaFeatureNameFor_ContainerSizeFeatureId = ContainerSizeFeatureId | String | String;\n/**\n * A container query size feature identifier.\n */\nexport type ContainerSizeFeatureId = \"width\" | \"height\" | \"inline-size\" | \"block-size\" | \"aspect-ratio\" | \"orientation\";\n/**\n * Represents a style query within a container condition.\n */\nexport type StyleQuery<D = Declaration> = | {\n    type: \"declaration\";\n    value: D;\n  }\n| {\n    type: \"property\";\n    value: PropertyId;\n  }\n| {\n    type: \"not\";\n    value: StyleQuery<D>;\n  }\n| {\n    /**\n     * The conditions for the operator.\n     */\n    conditions: StyleQuery<D>[];\n    /**\n     * The operator for the conditions.\n     */\n    operator: Operator;\n    type: \"operation\";\n  };\n/**\n * Represents a scroll state query within a container condition.\n */\nexport type ScrollStateQuery =\n  | {\n      type: \"feature\";\n      value: QueryFeatureFor_ScrollStateFeatureId;\n    }\n  | {\n      type: \"not\";\n      value: ScrollStateQuery;\n    }\n  | {\n      /**\n       * The conditions for the operator.\n       */\n      conditions: ScrollStateQuery[];\n      /**\n       * The operator for the conditions.\n       */\n      operator: Operator;\n      type: \"operation\";\n    };\n/**\n * A generic media feature or container feature.\n */\nexport type QueryFeatureFor_ScrollStateFeatureId =\n  | {\n      /**\n       * The name of the feature.\n       */\n      name: MediaFeatureNameFor_ScrollStateFeatureId;\n      type: \"plain\";\n      /**\n       * The feature value.\n       */\n      value: MediaFeatureValue;\n    }\n  | {\n      /**\n       * The name of the feature.\n       */\n      name: MediaFeatureNameFor_ScrollStateFeatureId;\n      type: \"boolean\";\n    }\n  | {\n      /**\n       * The name of the feature.\n       */\n      name: MediaFeatureNameFor_ScrollStateFeatureId;\n      /**\n       * A comparator.\n       */\n      operator: MediaFeatureComparison;\n      type: \"range\";\n      /**\n       * The feature value.\n       */\n      value: MediaFeatureValue;\n    }\n  | {\n      /**\n       * The end value.\n       */\n      end: MediaFeatureValue;\n      /**\n       * A comparator for the end value.\n       */\n      endOperator: MediaFeatureComparison;\n      /**\n       * The name of the feature.\n       */\n      name: MediaFeatureNameFor_ScrollStateFeatureId;\n      /**\n       * A start value.\n       */\n      start: MediaFeatureValue;\n      /**\n       * A comparator for the start value.\n       */\n      startOperator: MediaFeatureComparison;\n      type: \"interval\";\n    };\n/**\n * A media feature name.\n */\nexport type MediaFeatureNameFor_ScrollStateFeatureId = ScrollStateFeatureId | String | String;\n/**\n * A container query scroll state feature identifier.\n */\nexport type ScrollStateFeatureId = \"stuck\" | \"snapped\" | \"scrollable\" | \"scrolled\";\n/**\n * A property within a `@view-transition` rule.\n *\n * See [ViewTransitionRule](ViewTransitionRule).\n */\nexport type ViewTransitionProperty =\n  | {\n      property: \"navigation\";\n      value: Navigation;\n    }\n  | {\n      property: \"types\";\n      value: NoneOrCustomIdentList;\n    }\n  | {\n      property: \"custom\";\n      value: CustomProperty;\n    };\n/**\n * A value for the [navigation](https://drafts.csswg.org/css-view-transitions-2/#view-transition-navigation-descriptor) property in a `@view-transition` rule.\n */\nexport type Navigation = \"none\" | \"auto\";\nexport type DefaultAtRule = null;\n\n/**\n * A CSS style sheet, representing a `.css` file or inline `<style>` element.\n *\n * Style sheets can be parsed from a string, constructed from scratch, or created using a [Bundler](super::bundler::Bundler). Then, they can be minified and transformed for a set of target browsers, and serialied to a string.\n *\n * # Example\n *\n * ``` use lightningcss::stylesheet::{ StyleSheet, ParserOptions, MinifyOptions, PrinterOptions };\n *\n * // Parse a style sheet from a string. let mut stylesheet = StyleSheet::parse( r#\" .foo { color: red; }\n *\n * .bar { color: red; } \"#, ParserOptions::default() ).unwrap();\n *\n * // Minify the stylesheet. stylesheet.minify(MinifyOptions::default()).unwrap();\n *\n * // Serialize it to a string. let res = stylesheet.to_css(PrinterOptions::default()).unwrap(); assert_eq!(res.code, \".foo, .bar {\\n  color: red;\\n}\\n\"); ```\n */\nexport interface StyleSheet<D = Declaration, M = MediaQuery> {\n  /**\n   * The license comments that appeared at the start of the file.\n   */\n  licenseComments: String[];\n  /**\n   * A list of top-level rules within the style sheet.\n   */\n  rules: Rule<D, M>[];\n  /**\n   * The source map URL extracted from the original style sheet.\n   */\n  sourceMapUrls: (string | null)[];\n  /**\n   * A list of file names for all source files included within the style sheet. Sources are referenced by index in the `loc` property of each rule.\n   */\n  sources: string[];\n}\n/**\n * A [@media](https://drafts.csswg.org/css-conditional-3/#at-media) rule.\n */\nexport interface MediaRule<D = Declaration, M = MediaQuery> {\n  /**\n   * The location of the rule in the source file.\n   */\n  loc: Location2;\n  /**\n   * The media query list.\n   */\n  query: MediaList<M>;\n  /**\n   * The rules within the `@media` rule.\n   */\n  rules: Rule<D, M>[];\n}\n/**\n * A source location.\n */\nexport interface Location2 {\n  /**\n   * The column number within a line, starting at 1 for first the character of the line. Column numbers are counted in UTF-16 code units.\n   */\n  column: number;\n  /**\n   * The line number, starting at 0.\n   */\n  line: number;\n  /**\n   * The index of the source file within the source map.\n   */\n  source_index: number;\n}\n/**\n * A [media query list](https://drafts.csswg.org/mediaqueries/#mq-list).\n */\nexport interface MediaList<M = MediaQuery> {\n  /**\n   * The list of media queries.\n   */\n  mediaQueries: M[];\n}\n/**\n * A [media query](https://drafts.csswg.org/mediaqueries/#media).\n */\nexport interface MediaQuery {\n  /**\n   * The condition that this media query contains. This cannot have `or` in the first level.\n   */\n  condition?: MediaCondition | null;\n  /**\n   * The media type for this query, that can be known, unknown, or \"all\".\n   */\n  mediaType: MediaType;\n  /**\n   * The qualifier for this query.\n   */\n  qualifier?: Qualifier | null;\n}\nexport interface LengthValue {\n  /**\n   * The length unit.\n   */\n  unit: LengthUnit;\n  /**\n   * The length value.\n   */\n  value: number;\n}\n/**\n * A CSS environment variable reference.\n */\nexport interface EnvironmentVariable {\n  /**\n   * A fallback value in case the variable is not defined.\n   */\n  fallback?: TokenOrValue[] | null;\n  /**\n   * Optional indices into the dimensions of the environment variable.\n   */\n  indices?: number[];\n  /**\n   * The environment variable name.\n   */\n  name: EnvironmentVariableName;\n}\n/**\n * A CSS [url()](https://www.w3.org/TR/css-values-4/#urls) value and its source location.\n */\nexport interface Url {\n  /**\n   * The location where the `url()` was seen in the CSS source file.\n   */\n  loc: Location;\n  /**\n   * The url string.\n   */\n  url: String;\n}\n/**\n * A line and column position within a source file.\n */\nexport interface Location {\n  /**\n   * The column number, starting from 1.\n   */\n  column: number;\n  /**\n   * The line number, starting from 1.\n   */\n  line: number;\n}\n/**\n * A CSS variable reference.\n */\nexport interface Variable {\n  /**\n   * A fallback value in case the variable is not defined.\n   */\n  fallback?: TokenOrValue[] | null;\n  /**\n   * The variable name.\n   */\n  name: DashedIdentReference;\n}\n/**\n * A CSS [`<dashed-ident>`](https://www.w3.org/TR/css-values-4/#dashed-idents) reference.\n *\n * Dashed idents are used in cases where an identifier can be either author defined _or_ CSS-defined. Author defined idents must start with two dash characters (\"--\") or parsing will fail.\n *\n * In CSS modules, when the `dashed_idents` option is enabled, the identifier may be followed by the `from` keyword and an argument indicating where the referenced identifier is declared (e.g. a filename).\n */\nexport interface DashedIdentReference {\n  /**\n   * CSS modules extension: the filename where the variable is defined. Only enabled when the CSS modules `dashed_idents` option is turned on.\n   */\n  from?: Specifier | null;\n  /**\n   * The referenced identifier.\n   */\n  ident: String;\n}\n/**\n * A custom CSS function.\n */\nexport interface Function {\n  /**\n   * The function arguments.\n   */\n  arguments: TokenOrValue[];\n  /**\n   * The function name.\n   */\n  name: String;\n}\n/**\n * A [@import](https://drafts.csswg.org/css-cascade/#at-import) rule.\n */\nexport interface ImportRule<M = MediaQuery> {\n  /**\n   * An optional cascade layer name, or `None` for an anonymous layer.\n   */\n  layer?: String[] | null;\n  /**\n   * The location of the rule in the source file.\n   */\n  loc: Location2;\n  /**\n   * A media query.\n   */\n  media?: MediaList<M>;\n  /**\n   * An optional `supports()` condition.\n   */\n  supports?: SupportsCondition | null;\n  /**\n   * The url to import.\n   */\n  url: String;\n}\n/**\n * A CSS [style rule](https://drafts.csswg.org/css-syntax/#style-rules).\n */\nexport interface StyleRule<D = Declaration, M = MediaQuery> {\n  /**\n   * The declarations within the style rule.\n   */\n  declarations?: DeclarationBlock<D>;\n  /**\n   * The location of the rule in the source file.\n   */\n  loc: Location2;\n  /**\n   * Nested rules within the style rule.\n   */\n  rules?: Rule<D, M>[];\n  /**\n   * The selectors for the style rule.\n   */\n  selectors: SelectorList;\n}\n/**\n * A CSS declaration block.\n *\n * Properties are separated into a list of `!important` declararations, and a list of normal declarations. This reduces memory usage compared with storing a boolean along with each property.\n */\nexport interface DeclarationBlock<D = Declaration> {\n  /**\n   * A list of normal declarations in the block.\n   */\n  declarations?: D[];\n  /**\n   * A list of `!important` declarations in the block.\n   */\n  importantDeclarations?: D[];\n}\n/**\n * A CSS [`<position>`](https://www.w3.org/TR/css3-values/#position) value, as used in the `background-position` property, gradients, masks, etc.\n */\nexport interface Position {\n  /**\n   * The x-position.\n   */\n  x: PositionComponentFor_HorizontalPositionKeyword;\n  /**\n   * The y-position.\n   */\n  y: PositionComponentFor_VerticalPositionKeyword;\n}\n/**\n * An x/y position within a legacy `-webkit-gradient()`.\n */\nexport interface WebKitGradientPoint {\n  /**\n   * The x-position.\n   */\n  x: WebKitGradientPointComponentFor_HorizontalPositionKeyword;\n  /**\n   * The y-position.\n   */\n  y: WebKitGradientPointComponentFor_VerticalPositionKeyword;\n}\n/**\n * A color stop within a legacy `-webkit-gradient()`.\n */\nexport interface WebKitColorStop {\n  /**\n   * The color of the color stop.\n   */\n  color: CssColor;\n  /**\n   * The position of the color stop.\n   */\n  position: number;\n}\n/**\n * A CSS [`image-set()`](https://drafts.csswg.org/css-images-4/#image-set-notation) value.\n *\n * `image-set()` allows the user agent to choose between multiple versions of an image to display the most appropriate resolution or file type that it supports.\n */\nexport interface ImageSet {\n  /**\n   * The image options to choose from.\n   */\n  options: ImageSetOption[];\n  /**\n   * The vendor prefix for the `image-set()` function.\n   */\n  vendorPrefix: VendorPrefix;\n}\n/**\n * An image option within the `image-set()` function. See [ImageSet](ImageSet).\n */\nexport interface ImageSetOption {\n  /**\n   * The mime type of the image.\n   */\n  fileType?: String | null;\n  /**\n   * The image for this option.\n   */\n  image: Image;\n  /**\n   * The resolution of the image.\n   */\n  resolution: Resolution;\n}\n/**\n * A value for the [background-position](https://drafts.csswg.org/css-backgrounds/#background-position) shorthand property.\n */\nexport interface BackgroundPosition {\n  /**\n   * The x-position.\n   */\n  x: PositionComponentFor_HorizontalPositionKeyword;\n  /**\n   * The y-position.\n   */\n  y: PositionComponentFor_VerticalPositionKeyword;\n}\n/**\n * A value for the [background-repeat](https://www.w3.org/TR/css-backgrounds-3/#background-repeat) property.\n */\nexport interface BackgroundRepeat {\n  /**\n   * A repeat style for the x direction.\n   */\n  x: BackgroundRepeatKeyword;\n  /**\n   * A repeat style for the y direction.\n   */\n  y: BackgroundRepeatKeyword;\n}\n/**\n * A value for the [background](https://www.w3.org/TR/css-backgrounds-3/#background) shorthand property.\n */\nexport interface Background {\n  /**\n   * The background attachment.\n   */\n  attachment: BackgroundAttachment;\n  /**\n   * How the background should be clipped.\n   */\n  clip: BackgroundClip;\n  /**\n   * The background color.\n   */\n  color: CssColor;\n  /**\n   * The background image.\n   */\n  image: Image;\n  /**\n   * The background origin.\n   */\n  origin: BackgroundOrigin;\n  /**\n   * The background position.\n   */\n  position: BackgroundPosition;\n  /**\n   * How the background image should repeat.\n   */\n  repeat: BackgroundRepeat;\n  /**\n   * The size of the background image.\n   */\n  size: BackgroundSize;\n}\n/**\n * A value for the [box-shadow](https://drafts.csswg.org/css-backgrounds/#box-shadow) property.\n */\nexport interface BoxShadow {\n  /**\n   * The blur radius of the shadow.\n   */\n  blur: Length;\n  /**\n   * The color of the box shadow.\n   */\n  color: CssColor;\n  /**\n   * Whether the shadow is inset within the box.\n   */\n  inset: boolean;\n  /**\n   * The spread distance of the shadow.\n   */\n  spread: Length;\n  /**\n   * The x offset of the shadow.\n   */\n  xOffset: Length;\n  /**\n   * The y offset of the shadow.\n   */\n  yOffset: Length;\n}\n/**\n * A value for the [aspect-ratio](https://drafts.csswg.org/css-sizing-4/#aspect-ratio) property.\n */\nexport interface AspectRatio {\n  /**\n   * The `auto` keyword.\n   */\n  auto: boolean;\n  /**\n   * A preferred aspect ratio for the box, specified as width / height.\n   */\n  ratio?: Ratio | null;\n}\n/**\n * A value for the [overflow](https://www.w3.org/TR/css-overflow-3/#overflow-properties) shorthand property.\n */\nexport interface Overflow {\n  /**\n   * The overflow mode for the x direction.\n   */\n  x: OverflowKeyword;\n  /**\n   * The overflow mode for the y direction.\n   */\n  y: OverflowKeyword;\n}\n/**\n * A value for the [inset-block](https://drafts.csswg.org/css-logical/#propdef-inset-block) shorthand property.\n */\nexport interface InsetBlock {\n  /**\n   * The block end value.\n   */\n  blockEnd: LengthPercentageOrAuto;\n  /**\n   * The block start value.\n   */\n  blockStart: LengthPercentageOrAuto;\n}\n/**\n * A value for the [inset-inline](https://drafts.csswg.org/css-logical/#propdef-inset-inline) shorthand property.\n */\nexport interface InsetInline {\n  /**\n   * The inline end value.\n   */\n  inlineEnd: LengthPercentageOrAuto;\n  /**\n   * The inline start value.\n   */\n  inlineStart: LengthPercentageOrAuto;\n}\n/**\n * A value for the [inset](https://drafts.csswg.org/css-logical/#propdef-inset) shorthand property.\n */\nexport interface Inset {\n  /**\n   * The bottom value.\n   */\n  bottom: LengthPercentageOrAuto;\n  /**\n   * The left value.\n   */\n  left: LengthPercentageOrAuto;\n  /**\n   * The right value.\n   */\n  right: LengthPercentageOrAuto;\n  /**\n   * The top value.\n   */\n  top: LengthPercentageOrAuto;\n}\n/**\n * A value for the [border-radius](https://www.w3.org/TR/css-backgrounds-3/#border-radius) property.\n */\nexport interface BorderRadius {\n  /**\n   * The x and y radius values for the bottom left corner.\n   */\n  bottomLeft: Size2DFor_DimensionPercentageFor_LengthValue;\n  /**\n   * The x and y radius values for the bottom right corner.\n   */\n  bottomRight: Size2DFor_DimensionPercentageFor_LengthValue;\n  /**\n   * The x and y radius values for the top left corner.\n   */\n  topLeft: Size2DFor_DimensionPercentageFor_LengthValue;\n  /**\n   * The x and y radius values for the top right corner.\n   */\n  topRight: Size2DFor_DimensionPercentageFor_LengthValue;\n}\n/**\n * A value for the [border-image-repeat](https://www.w3.org/TR/css-backgrounds-3/#border-image-repeat) property.\n */\nexport interface BorderImageRepeat {\n  /**\n   * The horizontal repeat value.\n   */\n  horizontal: BorderImageRepeatKeyword;\n  /**\n   * The vertical repeat value.\n   */\n  vertical: BorderImageRepeatKeyword;\n}\n/**\n * A value for the [border-image-slice](https://www.w3.org/TR/css-backgrounds-3/#border-image-slice) property.\n */\nexport interface BorderImageSlice {\n  /**\n   * Whether the middle of the border image should be preserved.\n   */\n  fill: boolean;\n  /**\n   * The offsets from the edges of the image.\n   */\n  offsets: RectFor_NumberOrPercentage;\n}\n/**\n * A value for the [border-image](https://www.w3.org/TR/css-backgrounds-3/#border-image) shorthand property.\n */\nexport interface BorderImage {\n  /**\n   * The amount that the image extends beyond the border box.\n   */\n  outset: RectFor_LengthOrNumber;\n  /**\n   * How the border image is scaled and tiled.\n   */\n  repeat: BorderImageRepeat;\n  /**\n   * The offsets that define where the image is sliced.\n   */\n  slice: BorderImageSlice;\n  /**\n   * The border image.\n   */\n  source: Image;\n  /**\n   * The width of the border image.\n   */\n  width: RectFor_BorderImageSideWidth;\n}\n/**\n * A value for the [border-color](https://drafts.csswg.org/css-backgrounds/#propdef-border-color) shorthand property.\n */\nexport interface BorderColor {\n  /**\n   * The bottom value.\n   */\n  bottom: CssColor;\n  /**\n   * The left value.\n   */\n  left: CssColor;\n  /**\n   * The right value.\n   */\n  right: CssColor;\n  /**\n   * The top value.\n   */\n  top: CssColor;\n}\n/**\n * A value for the [border-style](https://drafts.csswg.org/css-backgrounds/#propdef-border-style) shorthand property.\n */\nexport interface BorderStyle {\n  /**\n   * The bottom value.\n   */\n  bottom: LineStyle;\n  /**\n   * The left value.\n   */\n  left: LineStyle;\n  /**\n   * The right value.\n   */\n  right: LineStyle;\n  /**\n   * The top value.\n   */\n  top: LineStyle;\n}\n/**\n * A value for the [border-width](https://drafts.csswg.org/css-backgrounds/#propdef-border-width) shorthand property.\n */\nexport interface BorderWidth {\n  /**\n   * The bottom value.\n   */\n  bottom: BorderSideWidth;\n  /**\n   * The left value.\n   */\n  left: BorderSideWidth;\n  /**\n   * The right value.\n   */\n  right: BorderSideWidth;\n  /**\n   * The top value.\n   */\n  top: BorderSideWidth;\n}\n/**\n * A value for the [border-block-color](https://drafts.csswg.org/css-logical/#propdef-border-block-color) shorthand property.\n */\nexport interface BorderBlockColor {\n  /**\n   * The block end value.\n   */\n  end: CssColor;\n  /**\n   * The block start value.\n   */\n  start: CssColor;\n}\n/**\n * A value for the [border-block-style](https://drafts.csswg.org/css-logical/#propdef-border-block-style) shorthand property.\n */\nexport interface BorderBlockStyle {\n  /**\n   * The block end value.\n   */\n  end: LineStyle;\n  /**\n   * The block start value.\n   */\n  start: LineStyle;\n}\n/**\n * A value for the [border-block-width](https://drafts.csswg.org/css-logical/#propdef-border-block-width) shorthand property.\n */\nexport interface BorderBlockWidth {\n  /**\n   * The block end value.\n   */\n  end: BorderSideWidth;\n  /**\n   * The block start value.\n   */\n  start: BorderSideWidth;\n}\n/**\n * A value for the [border-inline-color](https://drafts.csswg.org/css-logical/#propdef-border-inline-color) shorthand property.\n */\nexport interface BorderInlineColor {\n  /**\n   * The inline end value.\n   */\n  end: CssColor;\n  /**\n   * The inline start value.\n   */\n  start: CssColor;\n}\n/**\n * A value for the [border-inline-style](https://drafts.csswg.org/css-logical/#propdef-border-inline-style) shorthand property.\n */\nexport interface BorderInlineStyle {\n  /**\n   * The inline end value.\n   */\n  end: LineStyle;\n  /**\n   * The inline start value.\n   */\n  start: LineStyle;\n}\n/**\n * A value for the [border-inline-width](https://drafts.csswg.org/css-logical/#propdef-border-inline-width) shorthand property.\n */\nexport interface BorderInlineWidth {\n  /**\n   * The inline end value.\n   */\n  end: BorderSideWidth;\n  /**\n   * The inline start value.\n   */\n  start: BorderSideWidth;\n}\n/**\n * A generic type that represents the `border` and `outline` shorthand properties.\n */\nexport interface GenericBorderFor_LineStyle {\n  /**\n   * The border color.\n   */\n  color: CssColor;\n  /**\n   * The border style.\n   */\n  style: LineStyle;\n  /**\n   * The width of the border.\n   */\n  width: BorderSideWidth;\n}\n/**\n * A generic type that represents the `border` and `outline` shorthand properties.\n */\nexport interface GenericBorderFor_OutlineStyleAnd_11 {\n  /**\n   * The border color.\n   */\n  color: CssColor;\n  /**\n   * The border style.\n   */\n  style: OutlineStyle;\n  /**\n   * The width of the border.\n   */\n  width: BorderSideWidth;\n}\n/**\n * A value for the [flex-flow](https://www.w3.org/TR/2018/CR-css-flexbox-1-20181119/#flex-flow-property) shorthand property.\n */\nexport interface FlexFlow {\n  /**\n   * The direction that flex items flow.\n   */\n  direction: FlexDirection;\n  /**\n   * How the flex items wrap.\n   */\n  wrap: FlexWrap;\n}\n/**\n * A value for the [flex](https://www.w3.org/TR/2018/CR-css-flexbox-1-20181119/#flex-property) shorthand property.\n */\nexport interface Flex {\n  /**\n   * The flex basis.\n   */\n  basis: LengthPercentageOrAuto;\n  /**\n   * The flex grow factor.\n   */\n  grow: number;\n  /**\n   * The flex shrink factor.\n   */\n  shrink: number;\n}\n/**\n * A value for the [place-content](https://www.w3.org/TR/css-align-3/#place-content) shorthand property.\n */\nexport interface PlaceContent {\n  /**\n   * The content alignment.\n   */\n  align: AlignContent;\n  /**\n   * The content justification.\n   */\n  justify: JustifyContent;\n}\n/**\n * A value for the [place-self](https://www.w3.org/TR/css-align-3/#place-self-property) shorthand property.\n */\nexport interface PlaceSelf {\n  /**\n   * The item alignment.\n   */\n  align: AlignSelf;\n  /**\n   * The item justification.\n   */\n  justify: JustifySelf;\n}\n/**\n * A value for the [place-items](https://www.w3.org/TR/css-align-3/#place-items-property) shorthand property.\n */\nexport interface PlaceItems {\n  /**\n   * The item alignment.\n   */\n  align: AlignItems;\n  /**\n   * The item justification.\n   */\n  justify: JustifyItems;\n}\n/**\n * A value for the [gap](https://www.w3.org/TR/css-align-3/#gap-shorthand) shorthand property.\n */\nexport interface Gap {\n  /**\n   * The column gap.\n   */\n  column: GapValue;\n  /**\n   * The row gap.\n   */\n  row: GapValue;\n}\n/**\n * A [`<track-repeat>`](https://drafts.csswg.org/css-grid-2/#typedef-track-repeat) value, representing the `repeat()` function in a track list.\n *\n * See [TrackListItem](TrackListItem).\n */\nexport interface TrackRepeat {\n  /**\n   * The repeat count.\n   */\n  count: RepeatCount;\n  /**\n   * The line names to repeat.\n   */\n  lineNames: String[][];\n  /**\n   * The track sizes to repeat.\n   */\n  trackSizes: TrackSize[];\n}\nexport interface GridAutoFlow {\n  /**\n   * If specified, a dense packing algorithm is used, which fills in holes in the grid.\n   */\n  dense: boolean;\n  /**\n   * The direction of the auto flow.\n   */\n  direction: AutoFlowDirection;\n}\n/**\n * A value for the [grid-template](https://drafts.csswg.org/css-grid-2/#explicit-grid-shorthand) shorthand property.\n *\n * none | [ <'grid-template-rows'> / <'grid-template-columns'> ] | [ <line-names>? <string> <track-size>? <line-names>? ]+ [ / <explicit-track-list> ]?\n *\n * If `areas` is not `None`, then `rows` must also not be `None`.\n */\nexport interface GridTemplate {\n  /**\n   * The named grid areas.\n   */\n  areas: GridTemplateAreas;\n  /**\n   * The grid template columns.\n   */\n  columns: TrackSizing;\n  /**\n   * The grid template rows.\n   */\n  rows: TrackSizing;\n}\n/**\n * A value for the [grid](https://drafts.csswg.org/css-grid-2/#grid-shorthand) shorthand property.\n *\n * <'grid-template'> | <'grid-template-rows'> / [ auto-flow && dense? ] <'grid-auto-columns'>? | [ auto-flow && dense? ] <'grid-auto-rows'>? / <'grid-template-columns'>\n *\n * Explicit and implicit values may not be combined.\n */\nexport interface Grid {\n  /**\n   * Explicit grid template areas.\n   */\n  areas: GridTemplateAreas;\n  /**\n   * The grid auto columns.\n   */\n  autoColumns: TrackSize[];\n  /**\n   * The grid auto flow.\n   */\n  autoFlow: GridAutoFlow;\n  /**\n   * The grid auto rows.\n   */\n  autoRows: TrackSize[];\n  /**\n   * Explicit grid template columns.\n   */\n  columns: TrackSizing;\n  /**\n   * Explicit grid template rows.\n   */\n  rows: TrackSizing;\n}\n/**\n * A value for the [grid-row](https://drafts.csswg.org/css-grid-2/#propdef-grid-row) shorthand property. <grid-line> [ / <grid-line> ]?\n */\nexport interface GridRow {\n  /**\n   * The ending line.\n   */\n  end: GridLine;\n  /**\n   * The starting line.\n   */\n  start: GridLine;\n}\n/**\n * A value for the [grid-column](https://drafts.csswg.org/css-grid-2/#propdef-grid-column) shorthand property. <grid-line> [ / <grid-line> ]?\n */\nexport interface GridColumn {\n  /**\n   * The ending line.\n   */\n  end: GridLine;\n  /**\n   * The starting line.\n   */\n  start: GridLine;\n}\n/**\n * A value for the [grid-area](https://drafts.csswg.org/css-grid-2/#propdef-grid-area) shorthand property. <grid-line> [ / <grid-line> ]{0,3}\n */\nexport interface GridArea {\n  /**\n   * The grid column end placement.\n   */\n  columnEnd: GridLine;\n  /**\n   * The grid column start placement.\n   */\n  columnStart: GridLine;\n  /**\n   * The grid row end placement.\n   */\n  rowEnd: GridLine;\n  /**\n   * The grid row start placement.\n   */\n  rowStart: GridLine;\n}\n/**\n * A value for the [margin-block](https://drafts.csswg.org/css-logical/#propdef-margin-block) shorthand property.\n */\nexport interface MarginBlock {\n  /**\n   * The block end value.\n   */\n  blockEnd: LengthPercentageOrAuto;\n  /**\n   * The block start value.\n   */\n  blockStart: LengthPercentageOrAuto;\n}\n/**\n * A value for the [margin-inline](https://drafts.csswg.org/css-logical/#propdef-margin-inline) shorthand property.\n */\nexport interface MarginInline {\n  /**\n   * The inline end value.\n   */\n  inlineEnd: LengthPercentageOrAuto;\n  /**\n   * The inline start value.\n   */\n  inlineStart: LengthPercentageOrAuto;\n}\n/**\n * A value for the [margin](https://drafts.csswg.org/css-box-4/#propdef-margin) shorthand property.\n */\nexport interface Margin {\n  /**\n   * The bottom value.\n   */\n  bottom: LengthPercentageOrAuto;\n  /**\n   * The left value.\n   */\n  left: LengthPercentageOrAuto;\n  /**\n   * The right value.\n   */\n  right: LengthPercentageOrAuto;\n  /**\n   * The top value.\n   */\n  top: LengthPercentageOrAuto;\n}\n/**\n * A value for the [padding-block](https://drafts.csswg.org/css-logical/#propdef-padding-block) shorthand property.\n */\nexport interface PaddingBlock {\n  /**\n   * The block end value.\n   */\n  blockEnd: LengthPercentageOrAuto;\n  /**\n   * The block start value.\n   */\n  blockStart: LengthPercentageOrAuto;\n}\n/**\n * A value for the [padding-inline](https://drafts.csswg.org/css-logical/#propdef-padding-inline) shorthand property.\n */\nexport interface PaddingInline {\n  /**\n   * The inline end value.\n   */\n  inlineEnd: LengthPercentageOrAuto;\n  /**\n   * The inline start value.\n   */\n  inlineStart: LengthPercentageOrAuto;\n}\n/**\n * A value for the [padding](https://drafts.csswg.org/css-box-4/#propdef-padding) shorthand property.\n */\nexport interface Padding {\n  /**\n   * The bottom value.\n   */\n  bottom: LengthPercentageOrAuto;\n  /**\n   * The left value.\n   */\n  left: LengthPercentageOrAuto;\n  /**\n   * The right value.\n   */\n  right: LengthPercentageOrAuto;\n  /**\n   * The top value.\n   */\n  top: LengthPercentageOrAuto;\n}\n/**\n * A value for the [scroll-margin-block](https://drafts.csswg.org/css-scroll-snap/#propdef-scroll-margin-block) shorthand property.\n */\nexport interface ScrollMarginBlock {\n  /**\n   * The block end value.\n   */\n  blockEnd: LengthPercentageOrAuto;\n  /**\n   * The block start value.\n   */\n  blockStart: LengthPercentageOrAuto;\n}\n/**\n * A value for the [scroll-margin-inline](https://drafts.csswg.org/css-scroll-snap/#propdef-scroll-margin-inline) shorthand property.\n */\nexport interface ScrollMarginInline {\n  /**\n   * The inline end value.\n   */\n  inlineEnd: LengthPercentageOrAuto;\n  /**\n   * The inline start value.\n   */\n  inlineStart: LengthPercentageOrAuto;\n}\n/**\n * A value for the [scroll-margin](https://drafts.csswg.org/css-scroll-snap/#scroll-margin) shorthand property.\n */\nexport interface ScrollMargin {\n  /**\n   * The bottom value.\n   */\n  bottom: LengthPercentageOrAuto;\n  /**\n   * The left value.\n   */\n  left: LengthPercentageOrAuto;\n  /**\n   * The right value.\n   */\n  right: LengthPercentageOrAuto;\n  /**\n   * The top value.\n   */\n  top: LengthPercentageOrAuto;\n}\n/**\n * A value for the [scroll-padding-block](https://drafts.csswg.org/css-scroll-snap/#propdef-scroll-padding-block) shorthand property.\n */\nexport interface ScrollPaddingBlock {\n  /**\n   * The block end value.\n   */\n  blockEnd: LengthPercentageOrAuto;\n  /**\n   * The block start value.\n   */\n  blockStart: LengthPercentageOrAuto;\n}\n/**\n * A value for the [scroll-padding-inline](https://drafts.csswg.org/css-scroll-snap/#propdef-scroll-padding-inline) shorthand property.\n */\nexport interface ScrollPaddingInline {\n  /**\n   * The inline end value.\n   */\n  inlineEnd: LengthPercentageOrAuto;\n  /**\n   * The inline start value.\n   */\n  inlineStart: LengthPercentageOrAuto;\n}\n/**\n * A value for the [scroll-padding](https://drafts.csswg.org/css-scroll-snap/#scroll-padding) shorthand property.\n */\nexport interface ScrollPadding {\n  /**\n   * The bottom value.\n   */\n  bottom: LengthPercentageOrAuto;\n  /**\n   * The left value.\n   */\n  left: LengthPercentageOrAuto;\n  /**\n   * The right value.\n   */\n  right: LengthPercentageOrAuto;\n  /**\n   * The top value.\n   */\n  top: LengthPercentageOrAuto;\n}\n/**\n * A value for the [font](https://www.w3.org/TR/css-fonts-4/#font-prop) shorthand property.\n */\nexport interface Font {\n  /**\n   * The font family.\n   */\n  family: FontFamily[];\n  /**\n   * The line height.\n   */\n  lineHeight: LineHeight;\n  /**\n   * The font size.\n   */\n  size: FontSize;\n  /**\n   * The font stretch.\n   */\n  stretch: FontStretch;\n  /**\n   * The font style.\n   */\n  style: FontStyle;\n  /**\n   * How the text should be capitalized. Only CSS 2.1 values are supported.\n   */\n  variantCaps: FontVariantCaps;\n  /**\n   * The font weight.\n   */\n  weight: FontWeight;\n}\n/**\n * A value for the [transition](https://www.w3.org/TR/2018/WD-css-transitions-1-20181011/#transition-shorthand-property) property.\n */\nexport interface Transition {\n  /**\n   * The delay before the transition starts.\n   */\n  delay: Time;\n  /**\n   * The duration of the transition.\n   */\n  duration: Time;\n  /**\n   * The property to transition.\n   */\n  property: PropertyId;\n  /**\n   * The easing function for the transition.\n   */\n  timingFunction: EasingFunction;\n}\n/**\n * The [scroll()](https://drafts.csswg.org/scroll-animations-1/#scroll-notation) function.\n */\nexport interface ScrollTimeline {\n  /**\n   * Specifies which axis of the scroll container to use as the progress for the timeline.\n   */\n  axis: ScrollAxis;\n  /**\n   * Specifies which element to use as the scroll container.\n   */\n  scroller: Scroller;\n}\n/**\n * The [view()](https://drafts.csswg.org/scroll-animations-1/#view-notation) function.\n */\nexport interface ViewTimeline {\n  /**\n   * Specifies which axis of the scroll container to use as the progress for the timeline.\n   */\n  axis: ScrollAxis;\n  /**\n   * Provides an adjustment of the view progress visibility range.\n   */\n  inset: Size2DFor_LengthPercentageOrAuto;\n}\n/**\n * A value for the [animation-range](https://drafts.csswg.org/scroll-animations/#animation-range) shorthand property.\n */\nexport interface AnimationRange {\n  /**\n   * The end of the animation's attachment range.\n   */\n  end: AnimationRangeEnd;\n  /**\n   * The start of the animation's attachment range.\n   */\n  start: AnimationRangeStart;\n}\n/**\n * A value for the [animation](https://drafts.csswg.org/css-animations/#animation) shorthand property.\n */\nexport interface Animation {\n  /**\n   * The animation delay.\n   */\n  delay: Time;\n  /**\n   * The direction of the animation.\n   */\n  direction: AnimationDirection;\n  /**\n   * The animation duration.\n   */\n  duration: Time;\n  /**\n   * The animation fill mode.\n   */\n  fillMode: AnimationFillMode;\n  /**\n   * The number of times the animation will run.\n   */\n  iterationCount: AnimationIterationCount;\n  /**\n   * The animation name.\n   */\n  name: AnimationName;\n  /**\n   * The current play state of the animation.\n   */\n  playState: AnimationPlayState;\n  /**\n   * The animation timeline.\n   */\n  timeline: AnimationTimeline;\n  /**\n   * The easing function for the animation.\n   */\n  timingFunction: EasingFunction;\n}\n/**\n * A 2D matrix.\n */\nexport interface MatrixForFloat {\n  a: number;\n  b: number;\n  c: number;\n  d: number;\n  e: number;\n  f: number;\n}\n/**\n * A 3D matrix.\n */\nexport interface Matrix3DForFloat {\n  m11: number;\n  m12: number;\n  m13: number;\n  m14: number;\n  m21: number;\n  m22: number;\n  m23: number;\n  m24: number;\n  m31: number;\n  m32: number;\n  m33: number;\n  m34: number;\n  m41: number;\n  m42: number;\n  m43: number;\n  m44: number;\n}\n/**\n * A value for the [rotate](https://drafts.csswg.org/css-transforms-2/#propdef-rotate) property.\n */\nexport interface Rotate {\n  /**\n   * The angle of rotation.\n   */\n  angle: Angle;\n  /**\n   * Rotation around the x axis.\n   */\n  x: number;\n  /**\n   * Rotation around the y axis.\n   */\n  y: number;\n  /**\n   * Rotation around the z axis.\n   */\n  z: number;\n}\n/**\n * A value for the [text-transform](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#text-transform-property) property.\n */\nexport interface TextTransform {\n  /**\n   * How case should be transformed.\n   */\n  case: TextTransformCase;\n  /**\n   * Converts all small Kana characters to the equivalent full-size Kana.\n   */\n  fullSizeKana: boolean;\n  /**\n   * Puts all typographic character units in full-width form.\n   */\n  fullWidth: boolean;\n}\n/**\n * A value for the [text-indent](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#text-indent-property) property.\n */\nexport interface TextIndent {\n  /**\n   * Affects the first line after each hard break.\n   */\n  eachLine: boolean;\n  /**\n   * Inverts which lines are affected.\n   */\n  hanging: boolean;\n  /**\n   * The amount to indent.\n   */\n  value: DimensionPercentageFor_LengthValue;\n}\n/**\n * A value for the [text-decoration](https://www.w3.org/TR/2020/WD-css-text-decor-4-20200506/#text-decoration-property) shorthand property.\n */\nexport interface TextDecoration {\n  /**\n   * The color of the lines.\n   */\n  color: CssColor;\n  /**\n   * The lines to display.\n   */\n  line: TextDecorationLine;\n  /**\n   * The style of the lines.\n   */\n  style: TextDecorationStyle;\n  /**\n   * The thickness of the lines.\n   */\n  thickness: TextDecorationThickness;\n}\n/**\n * A value for the [text-emphasis](https://www.w3.org/TR/2020/WD-css-text-decor-4-20200506/#text-emphasis-property) shorthand property.\n */\nexport interface TextEmphasis {\n  /**\n   * The text emphasis color.\n   */\n  color: CssColor;\n  /**\n   * The text emphasis style.\n   */\n  style: TextEmphasisStyle;\n}\n/**\n * A value for the [text-emphasis-position](https://www.w3.org/TR/2020/WD-css-text-decor-4-20200506/#text-emphasis-position-property) property.\n */\nexport interface TextEmphasisPosition {\n  /**\n   * The horizontal position.\n   */\n  horizontal: TextEmphasisPositionHorizontal;\n  /**\n   * The vertical position.\n   */\n  vertical: TextEmphasisPositionVertical;\n}\n/**\n * A value for the [text-shadow](https://www.w3.org/TR/2020/WD-css-text-decor-4-20200506/#text-shadow-property) property.\n */\nexport interface TextShadow {\n  /**\n   * The blur radius of the text shadow.\n   */\n  blur: Length;\n  /**\n   * The color of the text shadow.\n   */\n  color: CssColor;\n  /**\n   * The spread distance of the text shadow.\n   */\n  spread: Length;\n  /**\n   * The x offset of the text shadow.\n   */\n  xOffset: Length;\n  /**\n   * The y offset of the text shadow.\n   */\n  yOffset: Length;\n}\n/**\n * A value for the [cursor](https://www.w3.org/TR/2021/WD-css-ui-4-20210316/#cursor) property.\n */\nexport interface Cursor {\n  /**\n   * A list of cursor images.\n   */\n  images: CursorImage[];\n  /**\n   * A pre-defined cursor.\n   */\n  keyword: CursorKeyword;\n}\n/**\n * A [cursor image](https://www.w3.org/TR/2021/WD-css-ui-4-20210316/#cursor) value, used in the `cursor` property.\n *\n * See [Cursor](Cursor).\n */\nexport interface CursorImage {\n  /**\n   * The location in the image where the mouse pointer appears.\n   *\n   * @minItems 2\n   * @maxItems 2\n   */\n  hotspot?: [number, number] | null;\n  /**\n   * A url to the cursor image.\n   */\n  url: Url;\n}\n/**\n * A value for the [caret](https://www.w3.org/TR/2021/WD-css-ui-4-20210316/#caret) shorthand property.\n */\nexport interface Caret {\n  /**\n   * The caret color.\n   */\n  color: ColorOrAuto;\n  /**\n   * The caret shape.\n   */\n  shape: CaretShape;\n}\n/**\n * A value for the [list-style](https://www.w3.org/TR/2020/WD-css-lists-3-20201117/#list-style-property) shorthand property.\n */\nexport interface ListStyle {\n  /**\n   * The list marker image.\n   */\n  image: Image;\n  /**\n   * The list style type.\n   */\n  listStyleType: ListStyleType;\n  /**\n   * The position of the list marker.\n   */\n  position: ListStylePosition;\n}\n/**\n * A value for the [composes](https://github.com/css-modules/css-modules/#dependencies) property from CSS modules.\n */\nexport interface Composes {\n  /**\n   * Where the class names are composed from.\n   */\n  from?: Specifier | null;\n  /**\n   * The source location of the `composes` property.\n   */\n  loc: Location;\n  /**\n   * A list of class names to compose.\n   */\n  names: String[];\n}\n/**\n * An [`inset()`](https://www.w3.org/TR/css-shapes-1/#funcdef-inset) rectangle shape.\n */\nexport interface InsetRect {\n  /**\n   * A corner radius for the rectangle.\n   */\n  radius: BorderRadius;\n  /**\n   * The rectangle.\n   */\n  rect: RectFor_DimensionPercentageFor_LengthValue;\n}\n/**\n * A [`circle()`](https://www.w3.org/TR/css-shapes-1/#funcdef-circle) shape.\n */\nexport interface Circle2 {\n  /**\n   * The position of the center of the circle.\n   */\n  position: Position;\n  /**\n   * The radius of the circle.\n   */\n  radius: ShapeRadius;\n}\n/**\n * An [`ellipse()`](https://www.w3.org/TR/css-shapes-1/#funcdef-ellipse) shape.\n */\nexport interface Ellipse2 {\n  /**\n   * The position of the center of the ellipse.\n   */\n  position: Position;\n  /**\n   * The x-radius of the ellipse.\n   */\n  radiusX: ShapeRadius;\n  /**\n   * The y-radius of the ellipse.\n   */\n  radiusY: ShapeRadius;\n}\n/**\n * A [`polygon()`](https://www.w3.org/TR/css-shapes-1/#funcdef-polygon) shape.\n */\nexport interface Polygon {\n  /**\n   * The fill rule used to determine the interior of the polygon.\n   */\n  fillRule: FillRule;\n  /**\n   * The points of each vertex of the polygon.\n   */\n  points: Point[];\n}\n/**\n * A point within a `polygon()` shape.\n *\n * See [Polygon](Polygon).\n */\nexport interface Point {\n  /**\n   * The x position of the point.\n   */\n  x: DimensionPercentageFor_LengthValue;\n  /**\n   * the y position of the point.\n   */\n  y: DimensionPercentageFor_LengthValue;\n}\n/**\n * A value for the [mask](https://www.w3.org/TR/css-masking-1/#the-mask) shorthand property.\n */\nexport interface Mask {\n  /**\n   * The box in which the mask is clipped.\n   */\n  clip: MaskClip;\n  /**\n   * How the mask is composited with the element.\n   */\n  composite: MaskComposite;\n  /**\n   * The mask image.\n   */\n  image: Image;\n  /**\n   * How the mask image is interpreted.\n   */\n  mode: MaskMode;\n  /**\n   * The origin of the mask.\n   */\n  origin: GeometryBox;\n  /**\n   * The position of the mask.\n   */\n  position: Position;\n  /**\n   * How the mask repeats.\n   */\n  repeat: BackgroundRepeat;\n  /**\n   * The size of the mask image.\n   */\n  size: BackgroundSize;\n}\n/**\n * A value for the [mask-border](https://www.w3.org/TR/css-masking-1/#the-mask-border) shorthand property.\n */\nexport interface MaskBorder {\n  /**\n   * How the mask image is interpreted.\n   */\n  mode: MaskBorderMode;\n  /**\n   * The amount that the image extends beyond the border box.\n   */\n  outset: RectFor_LengthOrNumber;\n  /**\n   * How the mask image is scaled and tiled.\n   */\n  repeat: BorderImageRepeat;\n  /**\n   * The offsets that define where the image is sliced.\n   */\n  slice: BorderImageSlice;\n  /**\n   * The mask image.\n   */\n  source: Image;\n  /**\n   * The width of the mask image.\n   */\n  width: RectFor_BorderImageSideWidth;\n}\n/**\n * A [`drop-shadow()`](https://drafts.fxtf.org/filter-effects-1/#funcdef-filter-drop-shadow) filter function.\n */\nexport interface DropShadow {\n  /**\n   * The blur radius of the drop shadow.\n   */\n  blur: Length;\n  /**\n   * The color of the drop shadow.\n   */\n  color: CssColor;\n  /**\n   * The x offset of the drop shadow.\n   */\n  xOffset: Length;\n  /**\n   * The y offset of the drop shadow.\n   */\n  yOffset: Length;\n}\n/**\n * A value for the [container](https://drafts.csswg.org/css-contain-3/#container-shorthand) shorthand property.\n */\nexport interface Container {\n  /**\n   * The container type.\n   */\n  containerType: ContainerType;\n  /**\n   * The container name.\n   */\n  name: ContainerNameList;\n}\nexport interface ColorScheme {\n  dark: boolean;\n  light: boolean;\n  only: boolean;\n}\n/**\n * A known property with an unparsed value.\n *\n * This type is used when the value of a known property could not be parsed, e.g. in the case css `var()` references are encountered. In this case, the raw tokens are stored instead.\n */\nexport interface UnparsedProperty {\n  /**\n   * The id of the property.\n   */\n  propertyId: PropertyId;\n  /**\n   * The property value, stored as a raw token list.\n   */\n  value: TokenOrValue[];\n}\n/**\n * A CSS custom property, representing any unknown property.\n */\nexport interface CustomProperty {\n  /**\n   * The name of the property.\n   */\n  name: CustomPropertyName;\n  /**\n   * The property value, stored as a raw token list.\n   */\n  value: TokenOrValue[];\n}\nexport interface AttrOperation {\n  caseSensitivity?: ParsedCaseSensitivity & string;\n  operator: AttrSelectorOperator;\n  value: string;\n}\n/**\n * A [view transition part selector](https://w3c.github.io/csswg-drafts/css-view-transitions-1/#typedef-pt-name-selector).\n */\nexport interface ViewTransitionPartSelector {\n  /**\n   * A list of view transition classes.\n   */\n  classes: String[];\n  /**\n   * The view transition part name.\n   */\n  name?: ViewTransitionPartName | null;\n}\n/**\n * A [@keyframes](https://drafts.csswg.org/css-animations/#keyframes) rule.\n */\nexport interface KeyframesRule<D = Declaration> {\n  /**\n   * A list of keyframes in the animation.\n   */\n  keyframes: Keyframe<D>[];\n  /**\n   * The location of the rule in the source file.\n   */\n  loc: Location2;\n  /**\n   * The animation name. <keyframes-name> = <custom-ident> | <string>\n   */\n  name: KeyframesName;\n  /**\n   * A vendor prefix for the rule, e.g. `@-webkit-keyframes`.\n   */\n  vendorPrefix: VendorPrefix;\n}\n/**\n * An individual keyframe within an `@keyframes` rule.\n *\n * See [KeyframesRule](KeyframesRule).\n */\nexport interface Keyframe<D = Declaration> {\n  /**\n   * The declarations for this keyframe.\n   */\n  declarations: DeclarationBlock<D>;\n  /**\n   * A list of keyframe selectors to associate with the declarations in this keyframe.\n   */\n  selectors: KeyframeSelector[];\n}\n/**\n * A percentage of a given timeline range\n */\nexport interface TimelineRangePercentage {\n  /**\n   * The name of the timeline range.\n   */\n  name: TimelineRangeName;\n  /**\n   * The percentage progress between the start and end of the range.\n   */\n  percentage: number;\n}\n/**\n * A [@font-face](https://drafts.csswg.org/css-fonts/#font-face-rule) rule.\n */\nexport interface FontFaceRule {\n  /**\n   * The location of the rule in the source file.\n   */\n  loc: Location2;\n  /**\n   * Declarations in the `@font-face` rule.\n   */\n  properties: FontFaceProperty[];\n}\n/**\n * A `url()` value for the [src](https://drafts.csswg.org/css-fonts/#src-desc) property in an `@font-face` rule.\n */\nexport interface UrlSource {\n  /**\n   * Optional `format()` function.\n   */\n  format?: FontFormat | null;\n  /**\n   * Optional `tech()` function.\n   */\n  tech: FontTechnology[];\n  /**\n   * The URL.\n   */\n  url: Url;\n}\n/**\n * A contiguous range of Unicode code points.\n *\n * Cannot be empty. Can represent a single code point when start == end.\n */\nexport interface UnicodeRange {\n  /**\n   * Inclusive end of the range. In [0, 0x10FFFF].\n   */\n  end: number;\n  /**\n   * Inclusive start of the range. In [0, end].\n   */\n  start: number;\n}\n/**\n * A [@font-palette-values](https://drafts.csswg.org/css-fonts-4/#font-palette-values) rule.\n */\nexport interface FontPaletteValuesRule {\n  /**\n   * The location of the rule in the source file.\n   */\n  loc: Location2;\n  /**\n   * The name of the font palette.\n   */\n  name: String;\n  /**\n   * Declarations in the `@font-palette-values` rule.\n   */\n  properties: FontPaletteValuesProperty[];\n}\n/**\n * A value for the [override-colors](https://drafts.csswg.org/css-fonts-4/#override-color) property in an `@font-palette-values` rule.\n */\nexport interface OverrideColors {\n  /**\n   * The replacement color.\n   */\n  color: CssColor;\n  /**\n   * The index of the color within the palette to override.\n   */\n  index: number;\n}\n/**\n * A [@font-feature-values](https://drafts.csswg.org/css-fonts/#font-feature-values) rule.\n */\nexport interface FontFeatureValuesRule {\n  /**\n   * The location of the rule in the source file.\n   */\n  loc: Location2;\n  /**\n   * The name of the font feature values.\n   */\n  name: String[];\n  /**\n   * The rules within the `@font-feature-values` rule.\n   */\n  rules: {\n    [k: string]: FontFeatureSubrule;\n  };\n}\n/**\n * A sub-rule of `@font-feature-values` https://drafts.csswg.org/css-fonts/#font-feature-values-syntax\n */\nexport interface FontFeatureSubrule {\n  /**\n   * The declarations within the `@font-feature-values` sub-rules.\n   */\n  declarations: {\n    [k: string]: number[];\n  };\n  /**\n   * The location of the rule in the source file.\n   */\n  loc: Location2;\n  /**\n   * The name of the `@font-feature-values` sub-rule.\n   */\n  name: FontFeatureSubruleType;\n}\n/**\n * A [@page](https://www.w3.org/TR/css-page-3/#at-page-rule) rule.\n */\nexport interface PageRule<D = Declaration> {\n  /**\n   * The declarations within the `@page` rule.\n   */\n  declarations: DeclarationBlock<D>;\n  /**\n   * The location of the rule in the source file.\n   */\n  loc: Location2;\n  /**\n   * The nested margin rules.\n   */\n  rules: PageMarginRule<D>[];\n  /**\n   * A list of page selectors.\n   */\n  selectors: PageSelector[];\n}\n/**\n * A [page margin rule](https://www.w3.org/TR/css-page-3/#margin-at-rules) rule.\n */\nexport interface PageMarginRule<D = Declaration> {\n  /**\n   * The declarations within the rule.\n   */\n  declarations: DeclarationBlock<D>;\n  /**\n   * The location of the rule in the source file.\n   */\n  loc: Location2;\n  /**\n   * The margin box identifier for this rule.\n   */\n  marginBox: PageMarginBox;\n}\n/**\n * A [page selector](https://www.w3.org/TR/css-page-3/#typedef-page-selector) within a `@page` rule.\n *\n * Either a name or at least one pseudo class is required.\n */\nexport interface PageSelector {\n  /**\n   * An optional named page type.\n   */\n  name?: String | null;\n  /**\n   * A list of page pseudo classes.\n   */\n  pseudoClasses: PagePseudoClass[];\n}\n/**\n * A [@supports](https://drafts.csswg.org/css-conditional-3/#at-supports) rule.\n */\nexport interface SupportsRule<D = Declaration, M = MediaQuery> {\n  /**\n   * The supports condition.\n   */\n  condition: SupportsCondition;\n  /**\n   * The location of the rule in the source file.\n   */\n  loc: Location2;\n  /**\n   * The rules within the `@supports` rule.\n   */\n  rules: Rule<D, M>[];\n}\n/**\n * A [@counter-style](https://drafts.csswg.org/css-counter-styles/#the-counter-style-rule) rule.\n */\nexport interface CounterStyleRule<D = Declaration> {\n  /**\n   * Declarations in the `@counter-style` rule.\n   */\n  declarations: DeclarationBlock<D>;\n  /**\n   * The location of the rule in the source file.\n   */\n  loc: Location2;\n  /**\n   * The name of the counter style to declare.\n   */\n  name: String;\n}\n/**\n * A [@namespace](https://drafts.csswg.org/css-namespaces/#declaration) rule.\n */\nexport interface NamespaceRule {\n  /**\n   * The location of the rule in the source file.\n   */\n  loc: Location2;\n  /**\n   * An optional namespace prefix to declare, or `None` to declare the default namespace.\n   */\n  prefix?: String | null;\n  /**\n   * The url of the namespace.\n   */\n  url: String;\n}\n/**\n * A [@-moz-document](https://www.w3.org/TR/2012/WD-css3-conditional-20120911/#at-document) rule.\n *\n * Note that only the `url-prefix()` function with no arguments is supported, and only the `-moz` prefix is allowed since Firefox was the only browser that ever implemented this rule.\n */\nexport interface MozDocumentRule<D = Declaration, M = MediaQuery> {\n  /**\n   * The location of the rule in the source file.\n   */\n  loc: Location2;\n  /**\n   * Nested rules within the `@-moz-document` rule.\n   */\n  rules: Rule<D, M>[];\n}\n/**\n * A [@nest](https://www.w3.org/TR/css-nesting-1/#at-nest) rule.\n */\nexport interface NestingRule<D = Declaration, M = MediaQuery> {\n  /**\n   * The location of the rule in the source file.\n   */\n  loc: Location2;\n  /**\n   * The style rule that defines the selector and declarations for the `@nest` rule.\n   */\n  style: StyleRule<D, M>;\n}\n/**\n * A [nested declarations](https://drafts.csswg.org/css-nesting/#nested-declarations-rule) rule.\n */\nexport interface NestedDeclarationsRule<D = Declaration> {\n  /**\n   * The style rule that defines the selector and declarations for the `@nest` rule.\n   */\n  declarations: DeclarationBlock<D>;\n  /**\n   * The location of the rule in the source file.\n   */\n  loc: Location2;\n}\n/**\n * A [@viewport](https://drafts.csswg.org/css-device-adapt/#atviewport-rule) rule.\n */\nexport interface ViewportRule<D = Declaration> {\n  /**\n   * The declarations within the `@viewport` rule.\n   */\n  declarations: DeclarationBlock<D>;\n  /**\n   * The location of the rule in the source file.\n   */\n  loc: Location2;\n  /**\n   * The vendor prefix for this rule, e.g. `@-ms-viewport`.\n   */\n  vendorPrefix: VendorPrefix;\n}\n/**\n * A [@custom-media](https://drafts.csswg.org/mediaqueries-5/#custom-mq) rule.\n */\nexport interface CustomMediaRule<M = MediaQuery> {\n  /**\n   * The location of the rule in the source file.\n   */\n  loc: Location2;\n  /**\n   * The name of the declared media query.\n   */\n  name: String;\n  /**\n   * The media query to declare.\n   */\n  query: MediaList<M>;\n}\n/**\n * A [@layer statement](https://drafts.csswg.org/css-cascade-5/#layer-empty) rule.\n *\n * See also [LayerBlockRule](LayerBlockRule).\n */\nexport interface LayerStatementRule {\n  /**\n   * The location of the rule in the source file.\n   */\n  loc: Location2;\n  /**\n   * The layer names to declare.\n   */\n  names: String[][];\n}\n/**\n * A [@layer block](https://drafts.csswg.org/css-cascade-5/#layer-block) rule.\n */\nexport interface LayerBlockRule<D = Declaration, M = MediaQuery> {\n  /**\n   * The location of the rule in the source file.\n   */\n  loc: Location2;\n  /**\n   * The name of the layer to declare, or `None` to declare an anonymous layer.\n   */\n  name?: String[] | null;\n  /**\n   * The rules within the `@layer` rule.\n   */\n  rules: Rule<D, M>[];\n}\n/**\n * A [@property](https://drafts.css-houdini.org/css-properties-values-api/#at-property-rule) rule.\n */\nexport interface PropertyRule {\n  /**\n   * Whether the custom property is inherited.\n   */\n  inherits: boolean;\n  /**\n   * An optional initial value for the custom property.\n   */\n  initialValue?: ParsedComponent | null;\n  /**\n   * The location of the rule in the source file.\n   */\n  loc: Location2;\n  /**\n   * The name of the custom property to declare.\n   */\n  name: String;\n  /**\n   * A syntax string to specify the grammar for the custom property.\n   */\n  syntax: SyntaxString;\n}\n/**\n * A [syntax component](https://drafts.css-houdini.org/css-properties-values-api/#syntax-component) within a [SyntaxString](SyntaxString).\n *\n * A syntax component consists of a component kind an a multiplier, which indicates how the component may repeat during parsing.\n */\nexport interface SyntaxComponent {\n  /**\n   * The kind of component.\n   */\n  kind: SyntaxComponentKind;\n  /**\n   * A multiplier for the component.\n   */\n  multiplier: Multiplier;\n}\n/**\n * A [@container](https://drafts.csswg.org/css-contain-3/#container-rule) rule.\n */\nexport interface ContainerRule<D = Declaration, M = MediaQuery> {\n  /**\n   * The container condition.\n   */\n  condition?: ContainerCondition<D> | null;\n  /**\n   * The location of the rule in the source file.\n   */\n  loc: Location2;\n  /**\n   * The name of the container.\n   */\n  name?: String | null;\n  /**\n   * The rules within the `@container` rule.\n   */\n  rules: Rule<D, M>[];\n}\n/**\n * A [@scope](https://drafts.csswg.org/css-cascade-6/#scope-atrule) rule.\n *\n * @scope (<scope-start>) [to (<scope-end>)]? { <stylesheet> }\n */\nexport interface ScopeRule<D = Declaration, M = MediaQuery> {\n  /**\n   * The location of the rule in the source file.\n   */\n  loc: Location2;\n  /**\n   * Nested rules within the `@scope` rule.\n   */\n  rules: Rule<D, M>[];\n  /**\n   * A selector list used to identify any scoping limits.\n   */\n  scopeEnd?: SelectorList | null;\n  /**\n   * A selector list used to identify the scoping root(s).\n   */\n  scopeStart?: SelectorList | null;\n}\n/**\n * A [@starting-style](https://drafts.csswg.org/css-transitions-2/#defining-before-change-style-the-starting-style-rule) rule.\n */\nexport interface StartingStyleRule<D = Declaration, M = MediaQuery> {\n  /**\n   * The location of the rule in the source file.\n   */\n  loc: Location2;\n  /**\n   * Nested rules within the `@starting-style` rule.\n   */\n  rules: Rule<D, M>[];\n}\n/**\n * A [@view-transition](https://drafts.csswg.org/css-view-transitions-2/#view-transition-rule) rule.\n */\nexport interface ViewTransitionRule {\n  /**\n   * The location of the rule in the source file.\n   */\n  loc: Location2;\n  /**\n   * Declarations in the `@view-transition` rule.\n   */\n  properties: ViewTransitionProperty[];\n}\n/**\n * An unknown at-rule, stored as raw tokens.\n */\nexport interface UnknownAtRule {\n  /**\n   * The contents of the block, if any.\n   */\n  block?: TokenOrValue[] | null;\n  /**\n   * The location of the rule in the source file.\n   */\n  loc: Location2;\n  /**\n   * The name of the at-rule (without the @).\n   */\n  name: String;\n  /**\n   * The prelude of the rule.\n   */\n  prelude: TokenOrValue[];\n}\n"
  },
  {
    "path": "node/browserslistToTargets.js",
    "content": "const BROWSER_MAPPING = {\n  and_chr: 'chrome',\n  and_ff: 'firefox',\n  ie_mob: 'ie',\n  op_mob: 'opera',\n  and_qq: null,\n  and_uc: null,\n  baidu: null,\n  bb: null,\n  kaios: null,\n  op_mini: null,\n};\n\nfunction browserslistToTargets(browserslist) {\n  let targets = {};\n  for (let browser of browserslist) {\n    let [name, v] = browser.split(' ');\n    if (BROWSER_MAPPING[name] === null) {\n      continue;\n    }\n\n    let version = parseVersion(v);\n    if (version == null) {\n      continue;\n    }\n\n    if (targets[name] == null || version < targets[name]) {\n      targets[name] = version;\n    }\n  }\n\n  return targets;\n}\n\nfunction parseVersion(version) {\n  let [major, minor = 0, patch = 0] = version\n    .split('-')[0]\n    .split('.')\n    .map(v => parseInt(v, 10));\n\n  if (isNaN(major) || isNaN(minor) || isNaN(patch)) {\n    return null;\n  }\n\n  return (major << 16) | (minor << 8) | patch;\n}\n\nmodule.exports = browserslistToTargets;\n"
  },
  {
    "path": "node/build.rs",
    "content": "#[cfg(not(target_arch = \"wasm32\"))]\nextern crate napi_build;\n\nfn main() {\n  #[cfg(not(target_arch = \"wasm32\"))]\n  napi_build::setup();\n}\n"
  },
  {
    "path": "node/composeVisitors.js",
    "content": "// @ts-check\n/** @typedef {import('./index').Visitor} Visitor */\n/** @typedef {import('./index').VisitorFunction} VisitorFunction */\n\n/**\n * Composes multiple visitor objects into a single one.\n * @param {(Visitor | VisitorFunction)[]} visitors \n * @return {Visitor | VisitorFunction}\n */\nfunction composeVisitors(visitors) {\n  if (visitors.length === 1) {\n    return visitors[0];\n  }\n  \n  if (visitors.some(v => typeof v === 'function')) {\n    return (opts) => {\n      let v = visitors.map(v => typeof v === 'function' ? v(opts) : v);\n      return composeVisitors(v);\n    };\n  }\n\n  /** @type Visitor */\n  let res = {};\n  composeSimpleVisitors(res, visitors, 'StyleSheet');\n  composeSimpleVisitors(res, visitors, 'StyleSheetExit');\n  composeObjectVisitors(res, visitors, 'Rule', ruleVisitor, wrapCustomAndUnknownAtRule);\n  composeObjectVisitors(res, visitors, 'RuleExit', ruleVisitor, wrapCustomAndUnknownAtRule);\n  composeObjectVisitors(res, visitors, 'Declaration', declarationVisitor, wrapCustomProperty);\n  composeObjectVisitors(res, visitors, 'DeclarationExit', declarationVisitor, wrapCustomProperty);\n  composeSimpleVisitors(res, visitors, 'Url');\n  composeSimpleVisitors(res, visitors, 'Color');\n  composeSimpleVisitors(res, visitors, 'Image');\n  composeSimpleVisitors(res, visitors, 'ImageExit');\n  composeSimpleVisitors(res, visitors, 'Length');\n  composeSimpleVisitors(res, visitors, 'Angle');\n  composeSimpleVisitors(res, visitors, 'Ratio');\n  composeSimpleVisitors(res, visitors, 'Resolution');\n  composeSimpleVisitors(res, visitors, 'Time');\n  composeSimpleVisitors(res, visitors, 'CustomIdent');\n  composeSimpleVisitors(res, visitors, 'DashedIdent');\n  composeArrayFunctions(res, visitors, 'MediaQuery');\n  composeArrayFunctions(res, visitors, 'MediaQueryExit');\n  composeSimpleVisitors(res, visitors, 'SupportsCondition');\n  composeSimpleVisitors(res, visitors, 'SupportsConditionExit');\n  composeArrayFunctions(res, visitors, 'Selector');\n  composeTokenVisitors(res, visitors, 'Token', 'token', false);\n  composeTokenVisitors(res, visitors, 'Function', 'function', false);\n  composeTokenVisitors(res, visitors, 'FunctionExit', 'function', true);\n  composeTokenVisitors(res, visitors, 'Variable', 'var', false);\n  composeTokenVisitors(res, visitors, 'VariableExit', 'var', true);\n  composeTokenVisitors(res, visitors, 'EnvironmentVariable', 'env', false);\n  composeTokenVisitors(res, visitors, 'EnvironmentVariableExit', 'env', true);\n  return res;\n}\n\nmodule.exports = composeVisitors;\n\nfunction wrapCustomAndUnknownAtRule(k, f) {\n  if (k === 'unknown') {\n    return (value => f({ type: 'unknown', value }));\n  }\n  if (k === 'custom') {\n    return (value => f({ type: 'custom', value }));\n  }\n  return f;\n}\n\nfunction wrapCustomProperty(k, f) {\n  return k === 'custom' ? (value => f({ property: 'custom', value })) : f;\n}\n\n/**\n * @param {import('./index').Visitor['Rule']} f \n * @param {import('./ast').Rule} item \n */\nfunction ruleVisitor(f, item) {\n  if (typeof f === 'object') {\n    if (item.type === 'unknown') {\n      let v = f.unknown;\n      if (typeof v === 'object') {\n        v = v[item.value.name];\n      }\n      return v?.(item.value);\n    }\n    if (item.type === 'custom') {\n      let v = f.custom;\n      if (typeof v === 'object') {\n        v = v[item.value.name];\n      }\n      return v?.(item.value);\n    }\n    return f[item.type]?.(item);\n  }\n  return f?.(item);\n}\n\n/**\n * @param {import('./index').Visitor['Declaration']} f \n * @param {import('./ast').Declaration} item \n */\nfunction declarationVisitor(f, item) {\n  if (typeof f === 'object') {\n    /** @type {string} */\n    let name = item.property;\n    if (item.property === 'unparsed') {\n      name = item.value.propertyId.property;\n    } else if (item.property === 'custom') {\n      let v = f.custom;\n      if (typeof v === 'object') {\n        v = v[item.value.name];\n      }\n      return v?.(item.value);\n    }\n    return f[name]?.(item);\n  }\n  return f?.(item);\n}\n\n/**\n * \n * @param {Visitor[]} visitors \n * @param {string} key \n * @returns {[any[], boolean, Set<string>]}\n */\nfunction extractObjectsOrFunctions(visitors, key) {\n  let values = [];\n  let hasFunction = false;\n  let allKeys = new Set();\n  for (let visitor of visitors) {\n    let v = visitor[key];\n    if (v) {\n      if (typeof v === 'function') {\n        hasFunction = true;\n      } else {\n        for (let key in v) {\n          allKeys.add(key);\n        }\n      }\n      values.push(v);\n    }\n  }\n  return [values, hasFunction, allKeys];\n}\n\n/**\n * @template {keyof Visitor} K\n * @param {Visitor} res\n * @param {Visitor[]} visitors\n * @param {K} key\n * @param {(visitor: Visitor[K], item: any) => any | any[] | void} apply \n * @param {(k: string, f: any) => any} wrapKey \n */\nfunction composeObjectVisitors(res, visitors, key, apply, wrapKey) {\n  let [values, hasFunction, allKeys] = extractObjectsOrFunctions(visitors, key);\n  if (values.length === 0) {\n    return;\n  }\n\n  if (values.length === 1) {\n    res[key] = values[0];\n    return;\n  }\n\n  let f = createArrayVisitor(visitors, (visitor, item) => apply(visitor[key], item));\n  if (hasFunction) {\n    res[key] = f;\n  } else {\n    /** @type {any} */\n    let v = {};\n    for (let k of allKeys) {\n      v[k] = wrapKey(k, f);\n    }\n    res[key] = v;\n  }\n}\n\n/**\n * @param {Visitor} res \n * @param {Visitor[]} visitors \n * @param {string} key \n * @param {import('./ast').TokenOrValue['type']} type \n * @param {boolean} isExit \n */\nfunction composeTokenVisitors(res, visitors, key, type, isExit) {\n  let [values, hasFunction, allKeys] = extractObjectsOrFunctions(visitors, key);\n  if (values.length === 0) {\n    return;\n  }\n\n  if (values.length === 1) {\n    res[key] = values[0];\n    return;\n  }\n\n  let f = createTokenVisitor(visitors, type, isExit);\n  if (hasFunction) {\n    res[key] = f;\n  } else {\n    let v = {};\n    for (let key of allKeys) {\n      v[key] = f;\n    }\n    res[key] = v;\n  }\n}\n\n/**\n * @param {Visitor[]} visitors \n * @param {import('./ast').TokenOrValue['type']} type \n */\nfunction createTokenVisitor(visitors, type, isExit) {\n  let v = createArrayVisitor(visitors, (visitor, /** @type {import('./ast').TokenOrValue} */ item) => {\n    let f;\n    switch (item.type) {\n      case 'token':\n        f = visitor.Token;\n        if (typeof f === 'object') {\n          f = f[item.value.type];\n        }\n        break;\n      case 'function':\n        f = isExit ? visitor.FunctionExit : visitor.Function;\n        if (typeof f === 'object') {\n          f = f[item.value.name];\n        }\n        break;\n      case 'var':\n        f = isExit ? visitor.VariableExit : visitor.Variable;\n        break;\n      case 'env':\n        f = isExit ? visitor.EnvironmentVariableExit : visitor.EnvironmentVariable;\n        if (typeof f === 'object') {\n          let name;\n          switch (item.value.name.type) {\n            case 'ua':\n            case 'unknown':\n              name = item.value.name.value;\n              break;\n            case 'custom':\n              name = item.value.name.ident;\n              break;\n          }\n          f = f[name];\n        }\n        break;\n      case 'color':\n        f = visitor.Color;\n        break;\n      case 'url':\n        f = visitor.Url;\n        break;\n      case 'length':\n        f = visitor.Length;\n        break;\n      case 'angle':\n        f = visitor.Angle;\n        break;\n      case 'time':\n        f = visitor.Time;\n        break;\n      case 'resolution':\n        f = visitor.Resolution;\n        break;\n      case 'dashed-ident':\n        f = visitor.DashedIdent;\n        break;\n    }\n\n    if (!f) {\n      return;\n    }\n\n    let res = f(item.value);\n    switch (item.type) {\n      case 'color':\n      case 'url':\n      case 'length':\n      case 'angle':\n      case 'time':\n      case 'resolution':\n      case 'dashed-ident':\n        if (Array.isArray(res)) {\n          res = res.map(value => ({ type: item.type, value }))\n        } else if (res) {\n          res = { type: item.type, value: res };\n        }\n        break;\n    }\n\n    return res;\n  });\n\n  return value => v({ type, value });\n}\n\n/**\n * @param {Visitor[]} visitors \n * @param {string} key \n */\nfunction extractFunctions(visitors, key) {\n  let functions = [];\n  for (let visitor of visitors) {\n    let f = visitor[key];\n    if (f) {\n      functions.push(f);\n    }\n  }\n  return functions;\n}\n\n/**\n * @param {Visitor} res \n * @param {Visitor[]} visitors \n * @param {string} key \n */\nfunction composeSimpleVisitors(res, visitors, key) {\n  let functions = extractFunctions(visitors, key);\n  if (functions.length === 0) {\n    return;\n  }\n\n  if (functions.length === 1) {\n    res[key] = functions[0];\n    return;\n  }\n\n  res[key] = arg => {\n    let mutated = false;\n    for (let f of functions) {\n      let res = f(arg);\n      if (res) {\n        arg = res;\n        mutated = true;\n      }\n    }\n\n    return mutated ? arg : undefined;\n  };\n}\n\n/**\n * @param {Visitor} res \n * @param {Visitor[]} visitors \n * @param {string} key \n */\nfunction composeArrayFunctions(res, visitors, key) {\n  let functions = extractFunctions(visitors, key);\n  if (functions.length === 0) {\n    return;\n  }\n\n  if (functions.length === 1) {\n    res[key] = functions[0];\n    return;\n  }\n\n  res[key] = createArrayVisitor(functions, (f, item) => f(item));\n}\n\n/**\n * @template T\n * @template V\n * @param {T[]} visitors \n * @param {(visitor: T, item: V) => V | V[] | void} apply \n * @returns {(item: V) => V | V[] | void}\n */\nfunction createArrayVisitor(visitors, apply) {\n  let seen = new Bitset(visitors.length);\n  return arg => {\n    let arr = [arg];\n    let mutated = false;\n    seen.clear();\n    for (let i = 0; i < arr.length; i++) {\n      // For each value, call all visitors. If a visitor returns a new value,\n      // we start over, but skip the visitor that generated the value or saw\n      // it before (to avoid cycles). This way, visitors can be composed in any order. \n      for (let v = 0; v < visitors.length && i < arr.length;) {\n        if (seen.get(v)) {\n          v++;\n          continue;\n        }\n\n        let item = arr[i];\n        let visitor = visitors[v];\n        let res = apply(visitor, item);\n        if (Array.isArray(res)) {\n          if (res.length === 0) {\n            arr.splice(i, 1);\n          } else if (res.length === 1) {\n            arr[i] = res[0];\n          } else {\n            arr.splice(i, 1, ...res);\n          }\n          mutated = true;\n          seen.set(v);\n          v = 0;\n        } else if (res) {\n          arr[i] = res;\n          mutated = true;\n          seen.set(v);\n          v = 0;\n        } else {\n          v++;\n        }\n      }\n    }\n\n    if (!mutated) {\n      return;\n    }\n\n    return arr.length === 1 ? arr[0] : arr;\n  };\n}\n\nclass Bitset {\n  constructor(maxBits = 32) {\n    this.bits = 0;\n    this.more = maxBits > 32 ? new Uint32Array(Math.ceil((maxBits - 32) / 32)) : null;\n  }\n\n  /** @param {number} bit */\n  get(bit) {\n    if (bit >= 32 && this.more) {\n      let i = Math.floor((bit - 32) / 32);\n      let b = bit % 32;\n      return Boolean(this.more[i] & (1 << b));\n    } else {\n      return Boolean(this.bits & (1 << bit));\n    }\n  }\n\n  /** @param {number} bit */\n  set(bit) {\n    if (bit >= 32 && this.more) {\n      let i = Math.floor((bit - 32) / 32);\n      let b = bit % 32;\n      this.more[i] |= 1 << b;\n    } else {\n      this.bits |= 1 << bit;\n    }\n  }\n\n  clear() {\n    this.bits = 0;\n    if (this.more) {\n      this.more.fill(0);\n    }\n  }\n}\n"
  },
  {
    "path": "node/flags.js",
    "content": "// This file is autogenerated by build-prefixes.js. DO NOT EDIT!\n\nexports.Features = {\n  Nesting: 1,\n  NotSelectorList: 2,\n  DirSelector: 4,\n  LangSelectorList: 8,\n  IsSelector: 16,\n  TextDecorationThicknessPercent: 32,\n  MediaIntervalSyntax: 64,\n  MediaRangeSyntax: 128,\n  CustomMediaQueries: 256,\n  ClampFunction: 512,\n  ColorFunction: 1024,\n  OklabColors: 2048,\n  LabColors: 4096,\n  P3Colors: 8192,\n  HexAlphaColors: 16384,\n  SpaceSeparatedColorNotation: 32768,\n  FontFamilySystemUi: 65536,\n  DoublePositionGradients: 131072,\n  VendorPrefixes: 262144,\n  LogicalProperties: 524288,\n  LightDark: 1048576,\n  Selectors: 31,\n  MediaQueries: 448,\n  Colors: 1113088,\n};\n"
  },
  {
    "path": "node/index.d.ts",
    "content": "import type { Angle, CssColor, Rule, CustomProperty, EnvironmentVariable, Function, Image, LengthValue, MediaQuery, Declaration, Ratio, Resolution, Selector, SupportsCondition, Time, Token, TokenOrValue, UnknownAtRule, Url, Variable, StyleRule, DeclarationBlock, ParsedComponent, Multiplier, StyleSheet, Location2 } from './ast';\nimport { Targets, Features } from './targets';\n\nexport * from './ast';\n\nexport { Targets, Features };\n\nexport interface TransformOptions<C extends CustomAtRules> {\n  /** The filename being transformed. Used for error messages and source maps. */\n  filename: string,\n  /** The source code to transform. */\n  code: Uint8Array,\n  /** Whether to enable minification. */\n  minify?: boolean,\n  /** Whether to output a source map. */\n  sourceMap?: boolean,\n  /** An input source map to extend. */\n  inputSourceMap?: string,\n  /**\n   * An optional project root path, used as the source root in the output source map.\n   * Also used to generate relative paths for sources used in CSS module hashes.\n   */\n  projectRoot?: string,\n  /** The browser targets for the generated code. */\n  targets?: Targets,\n  /** Features that should always be compiled, even when supported by targets. */\n  include?: number,\n  /** Features that should never be compiled, even when unsupported by targets. */\n  exclude?: number,\n  /** Whether to enable parsing various draft syntax. */\n  drafts?: Drafts,\n  /** Whether to enable various non-standard syntax. */\n  nonStandard?: NonStandard,\n  /** Whether to compile this file as a CSS module. */\n  cssModules?: boolean | CSSModulesConfig,\n  /**\n   * Whether to analyze dependencies (e.g. `@import` and `url()`).\n   * When enabled, `@import` rules are removed, and `url()` dependencies\n   * are replaced with hashed placeholders that can be replaced with the final\n   * urls later (after bundling). Dependencies are returned as part of the result.\n   */\n  analyzeDependencies?: boolean | DependencyOptions,\n  /**\n   * Replaces user action pseudo classes with class names that can be applied from JavaScript.\n   * This is useful for polyfills, for example.\n   */\n  pseudoClasses?: PseudoClasses,\n  /**\n   * A list of class names, ids, and custom identifiers (e.g. @keyframes) that are known\n   * to be unused. These will be removed during minification. Note that these are not\n   * selectors but individual names (without any . or # prefixes).\n   */\n  unusedSymbols?: string[],\n  /**\n   * Whether to ignore invalid rules and declarations rather than erroring.\n   * When enabled, warnings are returned, and the invalid rule or declaration is\n   * omitted from the output code.\n   */\n  errorRecovery?: boolean,\n  /**\n   * An AST visitor object. This allows custom transforms or analysis to be implemented in JavaScript.\n   * Multiple visitors can be composed into one using the `composeVisitors` function.\n   * For optimal performance, visitors should be as specific as possible about what types of values\n   * they care about so that JavaScript has to be called as little as possible.\n   */\n  visitor?: Visitor<C> | VisitorFunction<C>,\n  /**\n   * Defines how to parse custom CSS at-rules. Each at-rule can have a prelude, defined using a CSS\n   * [syntax string](https://drafts.css-houdini.org/css-properties-values-api/#syntax-strings), and\n   * a block body. The body can be a declaration list, rule list, or style block as defined in the\n   * [css spec](https://drafts.csswg.org/css-syntax/#declaration-rule-list).\n   */\n  customAtRules?: C\n}\n\n// This is a hack to make TS still provide autocomplete for `property` vs. just making it `string`.\ntype PropertyStart = '-' | '_' | 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z';\nexport type ReturnedDeclaration = Declaration | {\n  /** The property name. */\n  property: `${PropertyStart}${string}`,\n  /** The raw string value for the declaration. */\n  raw: string\n};\n\nexport type ReturnedMediaQuery = MediaQuery | {\n  /** The raw string value for the media query. */\n  raw: string\n};\n\ntype FindByType<Union, Name> = Union extends { type: Name } ? Union : never;\nexport type ReturnedRule = Rule<ReturnedDeclaration, ReturnedMediaQuery>;\ntype RequiredValue<Rule> = Rule extends { value: object }\n  ? Rule['value'] extends StyleRule\n  ? Rule & { value: Required<StyleRule> & { declarations: Required<DeclarationBlock> } }\n  : Rule & { value: Required<Rule['value']> }\n  : Rule;\ntype RuleVisitor<R = RequiredValue<Rule>> = ((rule: R) => ReturnedRule | ReturnedRule[] | void);\ntype MappedRuleVisitors = {\n  [Name in Exclude<Rule['type'], 'unknown' | 'custom'>]?: RuleVisitor<RequiredValue<FindByType<Rule, Name>>>;\n}\n\ntype UnknownVisitors<T> = {\n  [name: string]: RuleVisitor<T>\n}\n\ntype CustomVisitors<T extends CustomAtRules> = {\n  [Name in keyof T]?: RuleVisitor<CustomAtRule<Name, T[Name]>>\n};\n\ntype AnyCustomAtRule<C extends CustomAtRules> = {\n  [Key in keyof C]: CustomAtRule<Key, C[Key]>\n}[keyof C];\n\ntype RuleVisitors<C extends CustomAtRules> = MappedRuleVisitors & {\n  unknown?: UnknownVisitors<UnknownAtRule> | Omit<RuleVisitor<UnknownAtRule>, keyof CallableFunction>,\n  custom?: CustomVisitors<C> | Omit<RuleVisitor<AnyCustomAtRule<C>>, keyof CallableFunction>\n};\n\ntype PreludeTypes = Exclude<ParsedComponent['type'], 'literal' | 'repeated' | 'token'>;\ntype SyntaxString = `<${PreludeTypes}>` | `<${PreludeTypes}>+` | `<${PreludeTypes}>#` | (string & {});\ntype ComponentTypes = {\n  [Key in PreludeTypes as `<${Key}>`]: FindByType<ParsedComponent, Key>\n};\n\ntype Repetitions = {\n  [Key in PreludeTypes as `<${Key}>+` | `<${Key}>#`]: {\n    type: \"repeated\",\n    value: {\n      components: FindByType<ParsedComponent, Key>[],\n      multiplier: Multiplier\n    }\n  }\n};\n\ntype MappedPrelude = ComponentTypes & Repetitions;\ntype MappedBody<P extends CustomAtRuleDefinition['body']> = P extends 'style-block' ? 'rule-list' : P;\ninterface CustomAtRule<N, R extends CustomAtRuleDefinition> {\n  name: N,\n  prelude: R['prelude'] extends keyof MappedPrelude ? MappedPrelude[R['prelude']] : ParsedComponent,\n  body: FindByType<CustomAtRuleBody, MappedBody<R['body']>>,\n  loc: Location2\n}\n\ntype CustomAtRuleBody = {\n  type: 'declaration-list',\n  value: Required<DeclarationBlock>\n} | {\n  type: 'rule-list',\n  value: RequiredValue<Rule>[]\n};\n\ntype FindProperty<Union, Name> = Union extends { property: Name } ? Union : never;\ntype DeclarationVisitor<P = Declaration> = ((property: P) => ReturnedDeclaration | ReturnedDeclaration[] | void);\ntype MappedDeclarationVisitors = {\n  [Name in Exclude<Declaration['property'], 'unparsed' | 'custom'>]?: DeclarationVisitor<FindProperty<Declaration, Name> | FindProperty<Declaration, 'unparsed'>>;\n}\n\ntype CustomPropertyVisitors = {\n  [name: string]: DeclarationVisitor<CustomProperty>\n}\n\ntype DeclarationVisitors = MappedDeclarationVisitors & {\n  custom?: CustomPropertyVisitors | DeclarationVisitor<CustomProperty>\n}\n\ninterface RawValue {\n  /** A raw string value which will be parsed like CSS. */\n  raw: string\n}\n\ntype TokenReturnValue = TokenOrValue | TokenOrValue[] | RawValue | void;\ntype TokenVisitor = (token: Token) => TokenReturnValue;\ntype VisitableTokenTypes = 'ident' | 'at-keyword' | 'hash' | 'id-hash' | 'string' | 'number' | 'percentage' | 'dimension';\ntype TokenVisitors = {\n  [Name in VisitableTokenTypes]?: (token: FindByType<Token, Name>) => TokenReturnValue;\n}\n\ntype FunctionVisitor = (fn: Function) => TokenReturnValue;\ntype EnvironmentVariableVisitor = (env: EnvironmentVariable) => TokenReturnValue;\ntype EnvironmentVariableVisitors = {\n  [name: string]: EnvironmentVariableVisitor\n};\n\nexport interface Visitor<C extends CustomAtRules> {\n  StyleSheet?(stylesheet: StyleSheet): StyleSheet<ReturnedDeclaration, ReturnedMediaQuery> | void;\n  StyleSheetExit?(stylesheet: StyleSheet): StyleSheet<ReturnedDeclaration, ReturnedMediaQuery> | void;\n  Rule?: RuleVisitor | RuleVisitors<C>;\n  RuleExit?: RuleVisitor | RuleVisitors<C>;\n  Declaration?: DeclarationVisitor | DeclarationVisitors;\n  DeclarationExit?: DeclarationVisitor | DeclarationVisitors;\n  Url?(url: Url): Url | void;\n  Color?(color: CssColor): CssColor | void;\n  Image?(image: Image): Image | void;\n  ImageExit?(image: Image): Image | void;\n  Length?(length: LengthValue): LengthValue | void;\n  Angle?(angle: Angle): Angle | void;\n  Ratio?(ratio: Ratio): Ratio | void;\n  Resolution?(resolution: Resolution): Resolution | void;\n  Time?(time: Time): Time | void;\n  CustomIdent?(ident: string): string | void;\n  DashedIdent?(ident: string): string | void;\n  MediaQuery?(query: MediaQuery): ReturnedMediaQuery | ReturnedMediaQuery[] | void;\n  MediaQueryExit?(query: MediaQuery): ReturnedMediaQuery | ReturnedMediaQuery[] | void;\n  SupportsCondition?(condition: SupportsCondition): SupportsCondition;\n  SupportsConditionExit?(condition: SupportsCondition): SupportsCondition;\n  Selector?(selector: Selector): Selector | Selector[] | void;\n  Token?: TokenVisitor | TokenVisitors;\n  Function?: FunctionVisitor | { [name: string]: FunctionVisitor };\n  FunctionExit?: FunctionVisitor | { [name: string]: FunctionVisitor };\n  Variable?(variable: Variable): TokenReturnValue;\n  VariableExit?(variable: Variable): TokenReturnValue;\n  EnvironmentVariable?: EnvironmentVariableVisitor | EnvironmentVariableVisitors;\n  EnvironmentVariableExit?: EnvironmentVariableVisitor | EnvironmentVariableVisitors;\n}\n\nexport type VisitorDependency = FileDependency | GlobDependency;\nexport interface VisitorOptions {\n  addDependency: (dep: VisitorDependency) => void\n}\n\nexport type VisitorFunction<C extends CustomAtRules> = (options: VisitorOptions) => Visitor<C>;\n\nexport interface CustomAtRules {\n  [name: string]: CustomAtRuleDefinition\n}\n\nexport interface CustomAtRuleDefinition {\n  /**\n   * Defines the syntax for a custom at-rule prelude. The value should be a\n   * CSS [syntax string](https://drafts.css-houdini.org/css-properties-values-api/#syntax-strings)\n   * representing the types of values that are accepted. This property may be omitted or\n   * set to null to indicate that no prelude is accepted.\n   */\n  prelude?: SyntaxString | null,\n  /**\n   * Defines the type of body contained within the at-rule block.\n   *   - declaration-list: A CSS declaration list, as in a style rule.\n   *   - rule-list: A list of CSS rules, as supported within a non-nested\n   *       at-rule such as `@media` or `@supports`.\n   *   - style-block: Both a declaration list and rule list, as accepted within\n   *       a nested at-rule within a style rule (e.g. `@media` inside a style rule\n   *       with directly nested declarations).\n   */\n  body?: 'declaration-list' | 'rule-list' | 'style-block' | null\n}\n\nexport interface DependencyOptions {\n  /** Whether to preserve `@import` rules rather than removing them. */\n  preserveImports?: boolean\n}\n\nexport type BundleOptions<C extends CustomAtRules> = Omit<TransformOptions<C>, 'code'>;\n\nexport interface BundleAsyncOptions<C extends CustomAtRules> extends BundleOptions<C> {\n  resolver?: Resolver;\n}\n\n/** Custom resolver to use when loading CSS files. */\nexport interface Resolver {\n  /** Read the given file and return its contents as a string. */\n  read?: (file: string) => string | Promise<string>;\n\n  /**\n   * Resolve the given CSS import specifier from the provided originating file to a\n   * path which gets passed to `read()`.\n   */\n  resolve?: (specifier: string, originatingFile: string) => string | Promise<string>;\n}\n\nexport interface Drafts {\n  /** Whether to enable @custom-media rules. */\n  customMedia?: boolean\n}\n\nexport interface NonStandard {\n  /** Whether to enable the non-standard >>> and /deep/ selector combinators used by Angular and Vue. */\n  deepSelectorCombinator?: boolean\n}\n\nexport interface PseudoClasses {\n  hover?: string,\n  active?: string,\n  focus?: string,\n  focusVisible?: string,\n  focusWithin?: string\n}\n\nexport interface TransformResult {\n  /** The transformed code. */\n  code: Uint8Array,\n  /** The generated source map, if enabled. */\n  map: Uint8Array | void,\n  /** CSS module exports, if enabled. */\n  exports: CSSModuleExports | void,\n  /** CSS module references, if `dashedIdents` is enabled. */\n  references: CSSModuleReferences,\n  /** `@import` and `url()` dependencies, if enabled. */\n  dependencies: Dependency[] | void,\n  /** Warnings that occurred during compilation. */\n  warnings: Warning[]\n}\n\nexport interface Warning {\n  message: string,\n  type: string,\n  value?: any,\n  loc: ErrorLocation\n}\n\nexport interface CSSModulesConfig {\n  /** The pattern to use when renaming class names and other identifiers. Default is `[hash]_[local]`. */\n  pattern?: string,\n  /** Whether to rename dashed identifiers, e.g. custom properties. */\n  dashedIdents?: boolean,\n  /** Whether to enable hashing for `@keyframes`. */\n  animation?: boolean,\n  /** Whether to enable hashing for CSS grid identifiers. */\n  grid?: boolean,\n  /** Whether to enable hashing for `@container` names. */\n  container?: boolean,\n  /** Whether to enable hashing for custom identifiers. */\n  customIdents?: boolean,\n  /** Whether to require at least one class or id selector in each rule. */\n  pure?: boolean\n}\n\nexport type CSSModuleExports = {\n  /** Maps exported (i.e. original) names to local names. */\n  [name: string]: CSSModuleExport\n};\n\nexport interface CSSModuleExport {\n  /** The local (compiled) name for this export. */\n  name: string,\n  /** Whether the export is referenced in this file. */\n  isReferenced: boolean,\n  /** Other names that are composed by this export. */\n  composes: CSSModuleReference[]\n}\n\nexport type CSSModuleReferences = {\n  /** Maps placeholder names to references. */\n  [name: string]: DependencyCSSModuleReference,\n};\n\nexport type CSSModuleReference = LocalCSSModuleReference | GlobalCSSModuleReference | DependencyCSSModuleReference;\n\nexport interface LocalCSSModuleReference {\n  type: 'local',\n  /** The local (compiled) name for the reference. */\n  name: string,\n}\n\nexport interface GlobalCSSModuleReference {\n  type: 'global',\n  /** The referenced global name. */\n  name: string,\n}\n\nexport interface DependencyCSSModuleReference {\n  type: 'dependency',\n  /** The name to reference within the dependency. */\n  name: string,\n  /** The dependency specifier for the referenced file. */\n  specifier: string\n}\n\nexport type Dependency = ImportDependency | UrlDependency | FileDependency | GlobDependency;\n\nexport interface ImportDependency {\n  type: 'import',\n  /** The url of the `@import` dependency. */\n  url: string,\n  /** The media query for the `@import` rule. */\n  media: string | null,\n  /** The `supports()` query for the `@import` rule. */\n  supports: string | null,\n  /** The source location where the `@import` rule was found. */\n  loc: SourceLocation,\n  /** The placeholder that the import was replaced with. */\n  placeholder: string\n}\n\nexport interface UrlDependency {\n  type: 'url',\n  /** The url of the dependency. */\n  url: string,\n  /** The source location where the `url()` was found. */\n  loc: SourceLocation,\n  /** The placeholder that the url was replaced with. */\n  placeholder: string\n}\n\nexport interface FileDependency {\n  type: 'file',\n  filePath: string\n}\n\nexport interface GlobDependency {\n  type: 'glob',\n  glob: string\n}\n\nexport interface SourceLocation {\n  /** The file path in which the dependency exists. */\n  filePath: string,\n  /** The start location of the dependency. */\n  start: Location,\n  /** The end location (inclusive) of the dependency. */\n  end: Location\n}\n\nexport interface Location {\n  /** The line number (1-based). */\n  line: number,\n  /** The column number (0-based). */\n  column: number\n}\n\nexport interface ErrorLocation extends Location {\n  filename: string\n}\n\n/**\n * Compiles a CSS file, including optionally minifying and lowering syntax to the given\n * targets. A source map may also be generated, but this is not enabled by default.\n */\nexport declare function transform<C extends CustomAtRules>(options: TransformOptions<C>): TransformResult;\n\nexport interface TransformAttributeOptions {\n  /** The filename in which the style attribute appeared. Used for error messages and dependencies. */\n  filename?: string,\n  /** The source code to transform. */\n  code: Uint8Array,\n  /** Whether to enable minification. */\n  minify?: boolean,\n  /** The browser targets for the generated code. */\n  targets?: Targets,\n  /**\n   * Whether to analyze `url()` dependencies.\n   * When enabled, `url()` dependencies are replaced with hashed placeholders\n   * that can be replaced with the final urls later (after bundling).\n   * Dependencies are returned as part of the result.\n   */\n  analyzeDependencies?: boolean,\n  /**\n   * Whether to ignore invalid rules and declarations rather than erroring.\n   * When enabled, warnings are returned, and the invalid rule or declaration is\n   * omitted from the output code.\n   */\n  errorRecovery?: boolean,\n  /**\n   * An AST visitor object. This allows custom transforms or analysis to be implemented in JavaScript.\n   * Multiple visitors can be composed into one using the `composeVisitors` function.\n   * For optimal performance, visitors should be as specific as possible about what types of values\n   * they care about so that JavaScript has to be called as little as possible.\n   */\n  visitor?: Visitor<never> | VisitorFunction<never>\n}\n\nexport interface TransformAttributeResult {\n  /** The transformed code. */\n  code: Uint8Array,\n  /** `@import` and `url()` dependencies, if enabled. */\n  dependencies: Dependency[] | void,\n  /** Warnings that occurred during compilation. */\n  warnings: Warning[]\n}\n\n/**\n * Compiles a single CSS declaration list, such as an inline style attribute in HTML.\n */\nexport declare function transformStyleAttribute(options: TransformAttributeOptions): TransformAttributeResult;\n\n/**\n * Converts a browserslist result into targets that can be passed to lightningcss.\n * @param browserslist the result of calling `browserslist`\n */\nexport declare function browserslistToTargets(browserslist: string[]): Targets;\n\n/**\n * Bundles a CSS file and its dependencies, inlining @import rules.\n */\nexport declare function bundle<C extends CustomAtRules>(options: BundleOptions<C>): TransformResult;\n\n/**\n * Bundles a CSS file and its dependencies asynchronously, inlining @import rules.\n */\nexport declare function bundleAsync<C extends CustomAtRules>(options: BundleAsyncOptions<C>): Promise<TransformResult>;\n\n/**\n * Composes multiple visitor objects into a single one.\n */\nexport declare function composeVisitors<C extends CustomAtRules>(visitors: (Visitor<C> | VisitorFunction<C>)[]): Visitor<C> | VisitorFunction<C>;\n"
  },
  {
    "path": "node/index.js",
    "content": "let parts = [process.platform, process.arch];\nif (process.platform === 'linux') {\n  const { MUSL, familySync } = require('detect-libc');\n  const family = familySync();\n  if (family === MUSL) {\n    parts.push('musl');\n  } else if (process.arch === 'arm') {\n    parts.push('gnueabihf');\n  } else {\n    parts.push('gnu');\n  }\n} else if (process.platform === 'win32') {\n  parts.push('msvc');\n}\n\nlet native;\ntry {\n  native = require(`lightningcss-${parts.join('-')}`);\n} catch (err) {\n  native = require(`../lightningcss.${parts.join('-')}.node`);\n}\n\nmodule.exports.transform = wrap(native.transform);\nmodule.exports.transformStyleAttribute = wrap(native.transformStyleAttribute);\nmodule.exports.bundle = wrap(native.bundle);\nmodule.exports.bundleAsync = wrap(native.bundleAsync);\nmodule.exports.browserslistToTargets = require('./browserslistToTargets');\nmodule.exports.composeVisitors = require('./composeVisitors');\nmodule.exports.Features = require('./flags').Features;\n\nfunction wrap(call) {\n  return (options) => {\n    if (typeof options.visitor === 'function') {\n      let deps = [];\n      options.visitor = options.visitor({\n        addDependency(dep) {\n          deps.push(dep);\n        }\n      });\n\n      let result = call(options);\n      if (result instanceof Promise) {\n        result = result.then(res => {\n          if (deps.length) {\n            res.dependencies ??= [];\n            res.dependencies.push(...deps);\n          }\n          return res;\n        });\n      } else if (deps.length) {\n        result.dependencies ??= [];\n        result.dependencies.push(...deps);\n      }\n      return result;\n    } else {\n      return call(options);\n    }\n  };\n}\n"
  },
  {
    "path": "node/index.mjs",
    "content": "import index from './index.js';\n\nconst { transform, transformStyleAttribute, bundle, bundleAsync, browserslistToTargets, composeVisitors, Features } = index;\nexport { transform, transformStyleAttribute, bundle, bundleAsync, browserslistToTargets, composeVisitors, Features };\n"
  },
  {
    "path": "node/src/lib.rs",
    "content": "#[cfg(target_os = \"macos\")]\n#[global_allocator]\nstatic GLOBAL: jemallocator::Jemalloc = jemallocator::Jemalloc;\n\nuse napi::{CallContext, JsObject, JsUnknown};\nuse napi_derive::js_function;\n\n#[js_function(1)]\nfn transform(ctx: CallContext) -> napi::Result<JsUnknown> {\n  lightningcss_napi::transform(ctx)\n}\n\n#[js_function(1)]\nfn transform_style_attribute(ctx: CallContext) -> napi::Result<JsUnknown> {\n  lightningcss_napi::transform_style_attribute(ctx)\n}\n\n#[js_function(1)]\npub fn bundle(ctx: CallContext) -> napi::Result<JsUnknown> {\n  lightningcss_napi::bundle(ctx)\n}\n\n#[cfg(not(target_arch = \"wasm32\"))]\n#[js_function(1)]\npub fn bundle_async(ctx: CallContext) -> napi::Result<JsObject> {\n  lightningcss_napi::bundle_async(ctx)\n}\n\n#[cfg_attr(not(target_arch = \"wasm32\"), napi_derive::module_exports)]\nfn init(mut exports: JsObject) -> napi::Result<()> {\n  exports.create_named_method(\"transform\", transform)?;\n  exports.create_named_method(\"transformStyleAttribute\", transform_style_attribute)?;\n  exports.create_named_method(\"bundle\", bundle)?;\n  #[cfg(not(target_arch = \"wasm32\"))]\n  {\n    exports.create_named_method(\"bundleAsync\", bundle_async)?;\n  }\n\n  Ok(())\n}\n\n#[cfg(target_arch = \"wasm32\")]\n#[no_mangle]\npub fn register_module() {\n  unsafe fn register(raw_env: napi::sys::napi_env, raw_exports: napi::sys::napi_value) -> napi::Result<()> {\n    use napi::{Env, JsObject, NapiValue};\n\n    let exports = JsObject::from_raw_unchecked(raw_env, raw_exports);\n    init(exports)\n  }\n\n  napi::bindgen_prelude::register_module_exports(register)\n}\n\n#[cfg(target_arch = \"wasm32\")]\n#[no_mangle]\npub extern \"C\" fn napi_wasm_malloc(size: usize) -> *mut u8 {\n  use std::alloc::{alloc, Layout};\n  use std::mem;\n\n  let align = mem::align_of::<usize>();\n  if let Ok(layout) = Layout::from_size_align(size, align) {\n    unsafe {\n      if layout.size() > 0 {\n        let ptr = alloc(layout);\n        if !ptr.is_null() {\n          return ptr;\n        }\n      } else {\n        return align as *mut u8;\n      }\n    }\n  }\n\n  std::process::abort();\n}\n"
  },
  {
    "path": "node/targets.d.ts",
    "content": "// This file is autogenerated by build-prefixes.js. DO NOT EDIT!\n\nexport interface Targets {\n  android?: number,\n  chrome?: number,\n  edge?: number,\n  firefox?: number,\n  ie?: number,\n  ios_saf?: number,\n  opera?: number,\n  safari?: number,\n  samsung?: number\n}\n\nexport const Features: {\n  Nesting: 1,\n  NotSelectorList: 2,\n  DirSelector: 4,\n  LangSelectorList: 8,\n  IsSelector: 16,\n  TextDecorationThicknessPercent: 32,\n  MediaIntervalSyntax: 64,\n  MediaRangeSyntax: 128,\n  CustomMediaQueries: 256,\n  ClampFunction: 512,\n  ColorFunction: 1024,\n  OklabColors: 2048,\n  LabColors: 4096,\n  P3Colors: 8192,\n  HexAlphaColors: 16384,\n  SpaceSeparatedColorNotation: 32768,\n  FontFamilySystemUi: 65536,\n  DoublePositionGradients: 131072,\n  VendorPrefixes: 262144,\n  LogicalProperties: 524288,\n  LightDark: 1048576,\n  Selectors: 31,\n  MediaQueries: 448,\n  Colors: 1113088,\n};\n"
  },
  {
    "path": "node/test/bundle.test.mjs",
    "content": "import path from 'path';\nimport fs from 'fs';\nimport { test } from 'uvu';\nimport * as assert from 'uvu/assert';\nimport {webcrypto as crypto} from 'node:crypto';\n\nlet bundleAsync;\nif (process.env.TEST_WASM === 'node') {\n  bundleAsync = (await import('../../wasm/wasm-node.mjs')).bundleAsync;\n} else if (process.env.TEST_WASM === 'browser') {\n  // Define crypto globally for old node.\n  // @ts-ignore\n  globalThis.crypto ??= crypto;\n  let wasm = await import('../../wasm/index.mjs');\n  await wasm.default();\n  bundleAsync = function (options) {\n    if (!options.resolver?.read) {\n      options.resolver = {\n        ...options.resolver,\n        read: (filePath) => fs.readFileSync(filePath, 'utf8')\n      };\n    }\n\n    return wasm.bundleAsync(options);\n  }\n} else {\n  bundleAsync = (await import('../index.mjs')).bundleAsync;\n}\n\ntest('resolver', async () => {\n  const inMemoryFs = new Map(Object.entries({\n    'foo.css': `\n @import 'root:bar.css';\n\n .foo { color: red; }\n         `.trim(),\n\n    'bar.css': `\n @import 'root:hello/world.css';\n\n .bar { color: green; }\n         `.trim(),\n\n    'hello/world.css': `\n .baz { color: blue; }\n         `.trim(),\n  }));\n\n  const { code: buffer } = await bundleAsync({\n    filename: 'foo.css',\n    resolver: {\n      read(file) {\n        const result = inMemoryFs.get(path.normalize(file));\n        if (!result) throw new Error(`Could not find ${file} in ${Array.from(inMemoryFs.keys()).join(', ')}.`);\n        return result;\n      },\n\n      resolve(specifier) {\n        return specifier.slice('root:'.length);\n      },\n    },\n  });\n  const code = buffer.toString('utf-8').trim();\n\n  const expected = `\n.baz {\n  color: #00f;\n}\n\n.bar {\n  color: green;\n}\n\n.foo {\n  color: red;\n}\n     `.trim();\n  if (code !== expected) throw new Error(`\\`testResolver()\\` failed. Expected:\\n${expected}\\n\\nGot:\\n${code}`);\n});\n\ntest('only custom read', async () => {\n  const inMemoryFs = new Map(Object.entries({\n    'foo.css': `\n @import 'hello/world.css';\n\n .foo { color: red; }\n         `.trim(),\n\n    'hello/world.css': `\n @import '../bar.css';\n\n .bar { color: green; }\n         `.trim(),\n\n    'bar.css': `\n .baz { color: blue; }\n         `.trim(),\n  }));\n\n  const { code: buffer } = await bundleAsync({\n    filename: 'foo.css',\n    resolver: {\n      read(file) {\n        const result = inMemoryFs.get(path.normalize(file));\n        if (!result) throw new Error(`Could not find ${file} in ${Array.from(inMemoryFs.keys()).join(', ')}.`);\n        return result;\n      },\n    },\n  });\n  const code = buffer.toString('utf-8').trim();\n\n  const expected = `\n.baz {\n  color: #00f;\n}\n\n.bar {\n  color: green;\n}\n\n.foo {\n  color: red;\n}\n     `.trim();\n  if (code !== expected) throw new Error(`\\`testOnlyCustomRead()\\` failed. Expected:\\n${expected}\\n\\nGot:\\n${code}`);\n});\n\ntest('only custom resolve', async () => {\n  const root = path.join('tests', 'testdata');\n  const { code: buffer } = await bundleAsync({\n    filename: path.join(root, 'foo.css'),\n    resolver: {\n      resolve(specifier) {\n        // Strip `root:` prefix off specifier and resolve it as an absolute path\n        // in the test data root.\n        return path.join(root, specifier.slice('root:'.length));\n      },\n    },\n  });\n  const code = buffer.toString('utf-8').trim();\n\n  const expected = `\n.baz {\n  color: #00f;\n}\n\n.bar {\n  color: green;\n}\n\n.foo {\n  color: red;\n}\n     `.trim();\n  if (code !== expected) throw new Error(`\\`testOnlyCustomResolve()\\` failed. Expected:\\n${expected}\\n\\nGot:\\n${code}`);\n});\n\ntest('async read', async () => {\n  const root = path.join('tests', 'testdata');\n  const { code: buffer } = await bundleAsync({\n    filename: path.join(root, 'foo.css'),\n    resolver: {\n      async read(file) {\n        return await fs.promises.readFile(file, 'utf8');\n      },\n      resolve(specifier) {\n        // Strip `root:` prefix off specifier and resolve it as an absolute path\n        // in the test data root.\n        return path.join(root, specifier.slice('root:'.length));\n      },\n    },\n  });\n  const code = buffer.toString('utf-8').trim();\n\n  const expected = `\n.baz {\n  color: #00f;\n}\n\n.bar {\n  color: green;\n}\n\n.foo {\n  color: red;\n}\n     `.trim();\n  if (code !== expected) throw new Error(`\\`testAsyncRead()\\` failed. Expected:\\n${expected}\\n\\nGot:\\n${code}`);\n});\n\ntest('read throw', async () => {\n  let error = undefined;\n  try {\n    await bundleAsync({\n      filename: 'foo.css',\n      resolver: {\n        read(file) {\n          throw new Error(`Oh noes! Failed to read \\`${file}\\`.`);\n        }\n      },\n    });\n  } catch (err) {\n    error = err;\n  }\n\n  if (!error) throw new Error(`\\`testReadThrow()\\` failed. Expected \\`bundleAsync()\\` to throw, but it did not.`);\n  assert.equal(error.message, `Oh noes! Failed to read \\`foo.css\\`.`);\n  assert.equal(error.loc, undefined); // error occurred when reading initial file, no location info available.\n});\n\ntest('async read throw', async () => {\n  let error = undefined;\n  try {\n    await bundleAsync({\n      filename: 'foo.css',\n      resolver: {\n        async read(file) {\n          throw new Error(`Oh noes! Failed to read \\`${file}\\`.`);\n        }\n      },\n    });\n  } catch (err) {\n    error = err;\n  }\n\n  if (!error) throw new Error(`\\`testReadThrow()\\` failed. Expected \\`bundleAsync()\\` to throw, but it did not.`);\n  assert.equal(error.message, `Oh noes! Failed to read \\`foo.css\\`.`);\n  assert.equal(error.loc, undefined); // error occurred when reading initial file, no location info available.\n});\n\ntest('read throw with location info', async () => {\n  let error = undefined;\n  try {\n    await bundleAsync({\n      filename: 'foo.css',\n      resolver: {\n        read(file) {\n          if (file === 'foo.css') {\n            return '@import \"bar.css\"';\n          }\n          throw new Error(`Oh noes! Failed to read \\`${file}\\`.`);\n        }\n      },\n    });\n  } catch (err) {\n    error = err;\n  }\n\n  if (!error) throw new Error(`\\`testReadThrow()\\` failed. Expected \\`bundleAsync()\\` to throw, but it did not.`);\n  assert.equal(error.message, `Oh noes! Failed to read \\`bar.css\\`.`);\n  assert.equal(error.fileName, 'foo.css');\n  assert.equal(error.loc, {\n    line: 1,\n    column: 1\n  });\n});\n\ntest('async read throw with location info', async () => {\n  let error = undefined;\n  try {\n    await bundleAsync({\n      filename: 'foo.css',\n      resolver: {\n        async read(file) {\n          if (file === 'foo.css') {\n            return '@import \"bar.css\"';\n          }\n          throw new Error(`Oh noes! Failed to read \\`${file}\\`.`);\n        }\n      },\n    });\n  } catch (err) {\n    error = err;\n  }\n\n  if (!error) throw new Error(`\\`testReadThrow()\\` failed. Expected \\`bundleAsync()\\` to throw, but it did not.`);\n  assert.equal(error.message, `Oh noes! Failed to read \\`bar.css\\`.`);\n  assert.equal(error.fileName, 'foo.css');\n  assert.equal(error.loc, {\n    line: 1,\n    column: 1\n  });\n});\n\ntest('resolve throw', async () => {\n  let error = undefined;\n  try {\n    await bundleAsync({\n      filename: 'tests/testdata/foo.css',\n      resolver: {\n        resolve(specifier, originatingFile) {\n          throw new Error(`Oh noes! Failed to resolve \\`${specifier}\\` from \\`${originatingFile}\\`.`);\n        }\n      },\n    });\n  } catch (err) {\n    error = err;\n  }\n\n  if (!error) throw new Error(`\\`testResolveThrow()\\` failed. Expected \\`bundleAsync()\\` to throw, but it did not.`);\n  assert.equal(error.message, `Oh noes! Failed to resolve \\`root:hello/world.css\\` from \\`tests/testdata/foo.css\\`.`);\n  assert.equal(error.fileName, 'tests/testdata/foo.css');\n  assert.equal(error.loc, {\n    line: 1,\n    column: 1\n  });\n});\n\ntest('async resolve throw', async () => {\n  let error = undefined;\n  try {\n    await bundleAsync({\n      filename: 'tests/testdata/foo.css',\n      resolver: {\n        async resolve(specifier, originatingFile) {\n          throw new Error(`Oh noes! Failed to resolve \\`${specifier}\\` from \\`${originatingFile}\\`.`);\n        }\n      },\n    });\n  } catch (err) {\n    error = err;\n  }\n\n  if (!error) throw new Error(`\\`testResolveThrow()\\` failed. Expected \\`bundleAsync()\\` to throw, but it did not.`);\n  assert.equal(error.message, `Oh noes! Failed to resolve \\`root:hello/world.css\\` from \\`tests/testdata/foo.css\\`.`);\n  assert.equal(error.fileName, 'tests/testdata/foo.css');\n  assert.equal(error.loc, {\n    line: 1,\n    column: 1\n  });\n});\n\ntest('read return non-string', async () => {\n  let error = undefined;\n  try {\n    await bundleAsync({\n      filename: 'foo.css',\n      resolver: {\n        read() {\n          return 1234; // Returns a non-string value.\n        }\n      },\n    });\n  } catch (err) {\n    error = err;\n  }\n\n  if (!error) throw new Error(`\\`testReadReturnNonString()\\` failed. Expected \\`bundleAsync()\\` to throw, but it did not.`);\n  assert.equal(error.message, 'expect String, got: Number');\n});\n\ntest('resolve return non-string', async () => {\n  let error = undefined;\n  try {\n    await bundleAsync({\n      filename: 'tests/testdata/foo.css',\n      resolver: {\n        resolve() {\n          return 1234; // Returns a non-string value.\n        }\n      },\n    });\n  } catch (err) {\n    error = err;\n  }\n\n  if (!error) throw new Error(`\\`testResolveReturnNonString()\\` failed. Expected \\`bundleAsync()\\` to throw, but it did not.`);\n  assert.equal(error.message, 'data did not match any variant of untagged enum ResolveResult');\n  assert.equal(error.fileName, 'tests/testdata/foo.css');\n  assert.equal(error.loc, {\n    line: 1,\n    column: 1\n  });\n});\n\ntest('should throw with location info on syntax errors', async () => {\n  let error = undefined;\n  try {\n    await bundleAsync({\n      filename: 'tests/testdata/foo.css',\n      resolver: {\n        read() {\n          return '.foo'\n        }\n      },\n    });\n  } catch (err) {\n    error = err;\n  }\n\n  assert.equal(error.message, `Unexpected end of input`);\n  assert.equal(error.fileName, 'tests/testdata/foo.css');\n  assert.equal(error.loc, {\n    line: 1,\n    column: 5\n  });\n});\n\ntest('should support throwing in visitors', async () => {\n  let error = undefined;\n  try {\n    await bundleAsync({\n      filename: 'tests/testdata/a.css',\n      visitor: {\n        Rule() {\n          throw new Error('Some error')\n        }\n      }\n    });\n  } catch (err) {\n    error = err;\n  }\n\n  assert.equal(error.message, 'Some error');\n});\n\ntest('external import', async () => {\n  const { code: buffer } = await bundleAsync(/** @type {import('../index').BundleAsyncOptions} */ ({\n    filename: 'tests/testdata/has_external.css',\n    resolver: {\n      resolve(specifier, originatingFile) {\n        if (specifier === './does_not_exist.css' || specifier.startsWith('https:')) {\n          return {external: specifier};\n        }\n        return path.resolve(path.dirname(originatingFile), specifier);\n      }\n    }\n  }));\n  const code = buffer.toString('utf-8').trim();\n\n  const expected = `\n@import \"https://fonts.googleapis.com/css2?family=Roboto&display=swap\";\n@import \"./does_not_exist.css\";\n\n.b {\n  height: calc(100vh - 64px);\n}\n     `.trim();\n  if (code !== expected) throw new Error(`\\`testResolver()\\` failed. Expected:\\n${expected}\\n\\nGot:\\n${code}`);\n});\n\ntest.run();\n"
  },
  {
    "path": "node/test/composeVisitors.test.mjs",
    "content": "// @ts-check\n\nimport { test } from 'uvu';\nimport * as assert from 'uvu/assert';\nimport {webcrypto as crypto} from 'node:crypto';\n\nlet transform, composeVisitors;\nif (process.env.TEST_WASM === 'node') {\n  ({transform, composeVisitors} = await import('../../wasm/wasm-node.mjs'));\n} else if (process.env.TEST_WASM === 'browser') {\n  // Define crypto globally for old node.\n  // @ts-ignore\n  globalThis.crypto ??= crypto;\n  let wasm = await import('../../wasm/index.mjs');\n  await wasm.default();\n  ({transform, composeVisitors} = wasm);\n} else {\n  ({transform, composeVisitors} = await import('../index.mjs'));\n}\n\ntest('different types', () => {\n  let res = transform({\n    filename: 'test.css',\n    minify: true,\n    code: Buffer.from(`\n      .foo {\n        width: 16px;\n        color: red;\n      }\n    `),\n    visitor: composeVisitors([\n      {\n        Length(l) {\n          if (l.unit === 'px') {\n            return {\n              unit: 'rem',\n              value: l.value / 16\n            }\n          }\n        }\n      },\n      {\n        Color(c) {\n          if (c.type === 'rgb') {\n            return {\n              type: 'rgb',\n              r: c.g,\n              g: c.r,\n              b: c.b,\n              alpha: c.alpha\n            };\n          }\n        }\n      }\n    ])\n  });\n\n  assert.equal(res.code.toString(), '.foo{color:#0f0;width:1rem}');\n});\n\ntest('simple matching types', () => {\n  let res = transform({\n    filename: 'test.css',\n    minify: true,\n    code: Buffer.from(`\n      .foo {\n        width: 16px;\n      }\n    `),\n    visitor: composeVisitors([\n      {\n        Length(l) {\n          return {\n            unit: l.unit,\n            value: l.value * 2\n          };\n        }\n      },\n      {\n        Length(l) {\n          if (l.unit === 'px') {\n            return {\n              unit: 'rem',\n              value: l.value / 16\n            }\n          }\n        }\n      }\n    ])\n  });\n\n  assert.equal(res.code.toString(), '.foo{width:2rem}');\n});\n\ntest('different properties', () => {\n  let res = transform({\n    filename: 'test.css',\n    minify: true,\n    code: Buffer.from(`\n      .foo {\n        size: 16px;\n        bg: #ff0;\n      }\n    `),\n    visitor: composeVisitors([\n      {\n        Declaration: {\n          custom: {\n            size(v) {\n              return [\n                { property: 'unparsed', value: { propertyId: { property: 'width' }, value: v.value } },\n                { property: 'unparsed', value: { propertyId: { property: 'height' }, value: v.value } }\n              ];\n            }\n          }\n        }\n      },\n      {\n        Declaration: {\n          custom: {\n            bg(v) {\n              if (v.value[0].type === 'color') {\n                return { property: 'background-color', value: v.value[0].value };\n              }\n            }\n          }\n        }\n      }\n    ])\n  });\n\n  assert.equal(res.code.toString(), '.foo{width:16px;height:16px;background-color:#ff0}');\n});\n\ntest('composed properties', () => {\n  /** @type {import('../index').Visitor[]} */\n  let visitors = [\n    {\n      Declaration: {\n        custom: {\n          size(v) {\n            if (v.value[0].type === 'length') {\n              return [\n                { property: 'width', value: { type: 'length-percentage', value: { type: 'dimension', value: v.value[0].value } } },\n                { property: 'height', value: { type: 'length-percentage', value: { type: 'dimension', value: v.value[0].value } } },\n              ];\n            }\n          }\n        }\n      }\n    },\n    {\n      Declaration: {\n        width() {\n          return [];\n        }\n      }\n    }\n  ];\n\n  // Check that it works in any order.\n  for (let i = 0; i < 2; i++) {\n    let res = transform({\n      filename: 'test.css',\n      minify: true,\n      code: Buffer.from(`\n        .foo {\n          size: 16px;\n        }\n      `),\n      visitor: composeVisitors(visitors)\n    });\n\n    assert.equal(res.code.toString(), '.foo{height:16px}');\n    visitors.reverse();\n  }\n});\n\ntest('same properties', () => {\n  let res = transform({\n    filename: 'test.css',\n    minify: true,\n    code: Buffer.from(`\n      .foo {\n        color: red;\n      }\n    `),\n    visitor: composeVisitors([\n      {\n        Declaration: {\n          color(v) {\n            if (v.property === 'color' && v.value.type === 'rgb') {\n              return {\n                property: 'color',\n                value: {\n                  type: 'rgb',\n                  r: v.value.g,\n                  g: v.value.r,\n                  b: v.value.b,\n                  alpha: v.value.alpha\n                }\n              };\n            }\n          }\n        }\n      },\n      {\n        Declaration: {\n          color(v) {\n            if (v.property === 'color' && v.value.type === 'rgb' && v.value.g > 0) {\n              v.value.alpha /= 2;\n            }\n            return v;\n          }\n        }\n      }\n    ])\n  });\n\n  assert.equal(res.code.toString(), '.foo{color:#00ff0080}');\n});\n\ntest('properties plus values', () => {\n  let res = transform({\n    filename: 'test.css',\n    minify: true,\n    code: Buffer.from(`\n      .foo {\n        size: test;\n      }\n    `),\n    visitor: composeVisitors([\n      {\n        Declaration: {\n          custom: {\n            size() {\n              return [\n                { property: 'width', value: { type: 'length-percentage', value: { type: 'dimension', value: { unit: 'px', value: 32 } } } },\n                { property: 'height', value: { type: 'length-percentage', value: { type: 'dimension', value: { unit: 'px', value: 32 } } } },\n              ];\n            }\n          }\n        }\n      },\n      {\n        Length(l) {\n          if (l.unit === 'px') {\n            return {\n              unit: 'rem',\n              value: l.value / 16\n            }\n          }\n        }\n      }\n    ])\n  });\n\n  assert.equal(res.code.toString(), '.foo{width:2rem;height:2rem}');\n});\n\ntest('unparsed properties', () => {\n  let res = transform({\n    filename: 'test.css',\n    minify: true,\n    code: Buffer.from(`\n      .foo {\n        width: test;\n      }\n      .bar {\n        width: 16px;\n      }\n    `),\n    visitor: composeVisitors([\n      {\n        Declaration: {\n          width(v) {\n            if (v.property === 'unparsed') {\n              return [\n                { property: 'width', value: { type: 'length-percentage', value: { type: 'dimension', value: { unit: 'px', value: 32 } } } },\n                { property: 'height', value: { type: 'length-percentage', value: { type: 'dimension', value: { unit: 'px', value: 32 } } } },\n              ];\n            }\n          }\n        }\n      },\n      {\n        Length(l) {\n          if (l.unit === 'px') {\n            return {\n              unit: 'rem',\n              value: l.value / 16\n            }\n          }\n        }\n      }\n    ])\n  });\n\n  assert.equal(res.code.toString(), '.foo{width:2rem;height:2rem}.bar{width:1rem}');\n});\n\ntest('returning unparsed properties', () => {\n  let res = transform({\n    filename: 'test.css',\n    minify: true,\n    code: Buffer.from(`\n      .foo {\n        width: test;\n      }\n    `),\n    visitor: composeVisitors([\n      {\n        Declaration: {\n          width(v) {\n            if (v.property === 'unparsed' && v.value.value[0].type === 'token' && v.value.value[0].value.type === 'ident') {\n              return {\n                property: 'unparsed',\n                value: {\n                  propertyId: { property: 'width' },\n                  value: [{\n                    type: 'var',\n                    value: {\n                      name: {\n                        ident: '--' + v.value.value[0].value.value\n                      }\n                    }\n                  }]\n                }\n              }\n            }\n          }\n        }\n      },\n      {\n        Declaration: {\n          width(v) {\n            if (v.property === 'unparsed') {\n              return {\n                property: 'unparsed',\n                value: {\n                  propertyId: { property: 'width' },\n                  value: [{\n                    type: 'function',\n                    value: {\n                      name: 'calc',\n                      arguments: v.value.value\n                    }\n                  }]\n                }\n              }\n            }\n          }\n        }\n      }\n    ])\n  });\n\n  assert.equal(res.code.toString(), '.foo{width:calc(var(--test))}');\n});\n\ntest('all property handlers', () => {\n  let res = transform({\n    filename: 'test.css',\n    minify: true,\n    code: Buffer.from(`\n      .foo {\n        width: test;\n        height: test;\n      }\n    `),\n    visitor: composeVisitors([\n      {\n        Declaration(decl) {\n          if (decl.property === 'unparsed' && decl.value.propertyId.property === 'width') {\n            return { property: 'width', value: { type: 'length-percentage', value: { type: 'dimension', value: { unit: 'px', value: 32 } } } };\n          }\n        }\n      },\n      {\n        Declaration(decl) {\n          if (decl.property === 'unparsed' && decl.value.propertyId.property === 'height') {\n            return { property: 'height', value: { type: 'length-percentage', value: { type: 'dimension', value: { unit: 'px', value: 32 } } } };\n          }\n        }\n      }\n    ])\n  });\n\n  assert.equal(res.code.toString(), '.foo{width:32px;height:32px}');\n});\n\ntest('all property handlers (exit)', () => {\n  let res = transform({\n    filename: 'test.css',\n    minify: true,\n    code: Buffer.from(`\n      .foo {\n        width: test;\n        height: test;\n      }\n    `),\n    visitor: composeVisitors([\n      {\n        DeclarationExit(decl) {\n          if (decl.property === 'unparsed' && decl.value.propertyId.property === 'width') {\n            return { property: 'width', value: { type: 'length-percentage', value: { type: 'dimension', value: { unit: 'px', value: 32 } } } };\n          }\n        }\n      },\n      {\n        DeclarationExit(decl) {\n          if (decl.property === 'unparsed' && decl.value.propertyId.property === 'height') {\n            return { property: 'height', value: { type: 'length-percentage', value: { type: 'dimension', value: { unit: 'px', value: 32 } } } };\n          }\n        }\n      }\n    ])\n  });\n\n  assert.equal(res.code.toString(), '.foo{width:32px;height:32px}');\n});\n\ntest('tokens and functions', () => {\n  let res = transform({\n    filename: 'test.css',\n    minify: true,\n    code: Buffer.from(`\n      .foo {\n        width: f3(f2(f1(test)));\n      }\n    `),\n    visitor: composeVisitors([\n      {\n        FunctionExit: {\n          f1(f) {\n            if (f.arguments.length === 1 && f.arguments[0].type === 'token' && f.arguments[0].value.type === 'ident') {\n              return {\n                type: 'length',\n                value: {\n                  unit: 'px',\n                  value: 32\n                }\n              }\n            }\n          }\n        }\n      },\n      {\n        FunctionExit(f) {\n          return f.arguments[0];\n        }\n      },\n      {\n        Length(l) {\n          if (l.unit === 'px') {\n            return {\n              unit: 'rem',\n              value: l.value / 16\n            }\n          }\n        }\n      }\n    ])\n  });\n\n  assert.equal(res.code.toString(), '.foo{width:2rem}');\n});\n\ntest('unknown rules', () => {\n  let declared = new Map();\n  let res = transform({\n    filename: 'test.css',\n    minify: true,\n    code: Buffer.from(`\n      @test #056ef0;\n\n      .menu_link {\n        background: @blue;\n      }\n    `),\n    visitor: composeVisitors([\n      {\n        Rule: {\n          unknown: {\n            test(rule) {\n              rule.name = 'blue';\n              return {\n                type: 'unknown',\n                value: rule\n              };\n            }\n          }\n        }\n      },\n      {\n        Rule: {\n          unknown(rule) {\n            declared.set(rule.name, rule.prelude);\n            return [];\n          }\n        },\n        Token: {\n          'at-keyword'(token) {\n            if (declared.has(token.value)) {\n              return declared.get(token.value);\n            }\n          }\n        }\n      }\n    ])\n  });\n\n  assert.equal(res.code.toString(), '.menu_link{background:#056ef0}');\n});\n\ntest('custom at rules', () => {\n  let res = transform({\n    filename: 'test.css',\n    minify: true,\n    code: Buffer.from(`\n      @testA;\n      @testB;\n    `),\n    customAtRules: {\n      testA: {},\n      testB: {}\n    },\n    visitor: composeVisitors([\n      {\n        Rule: {\n          custom: {\n            testA(rule) {\n              return {\n                type: 'style',\n                value: {\n                  loc: rule.loc,\n                  selectors: [\n                    [{ type: 'class', name: 'testA' }]\n                  ],\n                  declarations: {\n                    declarations: [\n                      {\n                        property: 'color',\n                        value: {\n                          type: 'rgb',\n                          r: 0xff,\n                          g: 0x00,\n                          b: 0x00,\n                          alpha: 1,\n                        }\n                      }\n                    ]\n                  }\n                }\n              };\n            }\n          }\n        }\n      },\n      {\n        Rule: {\n          custom: {\n            testB(rule) {\n              return {\n                type: 'style',\n                value: {\n                  loc: rule.loc,\n                  selectors: [\n                    [{ type: 'class', name: 'testB' }]\n                  ],\n                  declarations: {\n                    declarations: [\n                      {\n                        property: 'color',\n                        value: {\n                          type: 'rgb',\n                          r: 0x00,\n                          g: 0xff,\n                          b: 0x00,\n                          alpha: 1,\n                        }\n                      }\n                    ]\n                  }\n                }\n              };\n            }\n          }\n        }\n      }\n    ])\n  });\n\n  assert.equal(res.code.toString(), '.testA{color:red}.testB{color:#0f0}');\n});\n\ntest('known rules', () => {\n  let declared = new Map();\n  let res = transform({\n    filename: 'test.css',\n    minify: true,\n    code: Buffer.from(`\n      .test:focus-visible {\n        margin-left: 20px;\n        margin-right: @margin-left;\n      }\n    `),\n    targets: {\n      safari: 14 << 16\n    },\n    visitor: composeVisitors([\n      {\n        Rule: {\n          style(rule) {\n            let valuesByProperty = new Map();\n            for (let decl of rule.value.declarations.declarations) {\n              /** @type string */\n              let name = decl.property;\n              if (decl.property === 'unparsed') {\n                name = decl.value.propertyId.property;\n              }\n              valuesByProperty.set(name, decl);\n            }\n\n            rule.value.declarations.declarations = rule.value.declarations.declarations.map(decl => {\n              // Only single value supported. Would need a way to convert parsed values to unparsed tokens otherwise.\n              if (decl.property === 'unparsed' && decl.value.value.length === 1) {\n                let token = decl.value.value[0];\n                if (token.type === 'token' && token.value.type === 'at-keyword' && valuesByProperty.has(token.value.value)) {\n                  let v = valuesByProperty.get(token.value.value);\n                  return {\n                    /** @type any */\n                    property: decl.value.propertyId.property,\n                    value: v.value\n                  };\n                }\n              }\n              return decl;\n            });\n\n            return rule;\n          }\n        }\n      },\n      {\n        Rule: {\n          style(rule) {\n            let clone = null;\n            for (let selector of rule.value.selectors) {\n              for (let [i, component] of selector.entries()) {\n                if (component.type === 'pseudo-class' && component.kind === 'focus-visible') {\n                  if (clone == null) {\n                    clone = [...rule.value.selectors.map(s => [...s])];\n                  }\n\n                  selector[i] = { type: 'class', name: 'focus-visible' };\n                }\n              }\n            }\n\n            if (clone) {\n              return [rule, { type: 'style', value: { ...rule.value, selectors: clone } }];\n            }\n          }\n        }\n      }\n    ])\n  });\n\n  assert.equal(res.code.toString(), '.test.focus-visible{margin-left:20px;margin-right:20px}.test:focus-visible{margin-left:20px;margin-right:20px}');\n});\n\ntest('environment variables', () => {\n  /** @type {Record<string, import('../ast').TokenOrValue>} */\n  let tokens = {\n    '--branding-small': {\n      type: 'length',\n      value: {\n        unit: 'px',\n        value: 600\n      }\n    },\n    '--branding-padding': {\n      type: 'length',\n      value: {\n        unit: 'px',\n        value: 20\n      }\n    }\n  };\n\n  let res = transform({\n    filename: 'test.css',\n    minify: true,\n    errorRecovery: true,\n    code: Buffer.from(`\n      @media (max-width: env(--branding-small)) {\n        body {\n          padding: env(--branding-padding);\n        }\n      }\n    `),\n    visitor: composeVisitors([\n      {\n        EnvironmentVariable: {\n          '--branding-small': () => tokens['--branding-small']\n        }\n      },\n      {\n        EnvironmentVariable: {\n          '--branding-padding': () => tokens['--branding-padding']\n        }\n      }\n    ])\n  });\n\n  assert.equal(res.code.toString(), '@media (width<=600px){body{padding:20px}}');\n});\n\ntest('variables', () => {\n  /** @type {Record<string, import('../ast').TokenOrValue>} */\n  let tokens = {\n    '--branding-small': {\n      type: 'length',\n      value: {\n        unit: 'px',\n        value: 600\n      }\n    },\n    '--branding-padding': {\n      type: 'length',\n      value: {\n        unit: 'px',\n        value: 20\n      }\n    }\n  };\n\n  let res = transform({\n    filename: 'test.css',\n    minify: true,\n    errorRecovery: true,\n    code: Buffer.from(`\n      body {\n        padding: var(--branding-padding);\n        width: var(--branding-small);\n      }\n    `),\n    visitor: composeVisitors([\n      {\n        Variable(v) {\n          if (v.name.ident === '--branding-small') {\n            return tokens['--branding-small'];\n          }\n        }\n      },\n      {\n        Variable(v) {\n          if (v.name.ident === '--branding-padding') {\n            return tokens['--branding-padding'];\n          }\n        }\n      }\n    ])\n  });\n\n  assert.equal(res.code.toString(), 'body{padding:20px;width:600px}');\n});\n\ntest('StyleSheet', () => {\n  let styleSheetCalledCount = 0;\n  let styleSheetExitCalledCount = 0;\n  transform({\n    filename: 'test.css',\n    code: Buffer.from(`\n      body {\n        color: blue;\n      }\n    `),\n    visitor: composeVisitors([\n      {\n        StyleSheet() {\n          styleSheetCalledCount++\n        },\n        StyleSheetExit() {\n          styleSheetExitCalledCount++\n        }\n      },\n      {\n        StyleSheet() {\n          styleSheetCalledCount++\n        },\n        StyleSheetExit() {\n          styleSheetExitCalledCount++\n        }\n      }\n    ])\n  });\n  assert.equal(styleSheetCalledCount, 2);\n  assert.equal(styleSheetExitCalledCount, 2);\n});\n\ntest('visitor function', () => {\n  let res = transform({\n    filename: 'test.css',\n    minify: true,\n    code: Buffer.from(`\n      @dep \"foo.js\";\n      @dep2 \"bar.js\";\n\n      .foo {\n        width: 32px;\n      }\n    `),\n    visitor: composeVisitors([\n      ({addDependency}) => ({\n        Rule: {\n          unknown: {\n            dep(rule) {\n              let file = rule.prelude[0].value.value;\n              addDependency({\n                type: 'file',\n                filePath: file\n              });\n              return [];\n            }\n          }\n        }\n      }),\n      ({addDependency}) => ({\n        Rule: {\n          unknown: {\n            dep2(rule) {\n              let file = rule.prelude[0].value.value;\n              addDependency({\n                type: 'file',\n                filePath: file\n              });\n              return [];\n            }\n          }\n        }\n      })\n    ])\n  });\n\n  assert.equal(res.code.toString(), '.foo{width:32px}');\n  assert.equal(res.dependencies, [\n    {\n      type: 'file',\n      filePath: 'foo.js'\n    },\n    {\n      type: 'file',\n      filePath: 'bar.js'\n    }\n  ]);\n});\n\ntest.run();\n"
  },
  {
    "path": "node/test/customAtRules.mjs",
    "content": "// @ts-check\n\nimport { test } from 'uvu';\nimport * as assert from 'uvu/assert';\nimport fs from 'fs';\nimport {webcrypto as crypto} from 'node:crypto';\n\nlet bundle, transform;\nif (process.env.TEST_WASM === 'node') {\n  ({ bundle, transform } = await import('../../wasm/wasm-node.mjs'));\n} else if (process.env.TEST_WASM === 'browser') {\n  // Define crypto globally for old node.\n  // @ts-ignore\n  globalThis.crypto ??= crypto;\n  let wasm = await import('../../wasm/index.mjs');\n  await wasm.default();\n  transform = wasm.transform;\n  bundle = function(options) {\n    return wasm.bundle({\n      ...options,\n      resolver: {\n        read: (filePath) => fs.readFileSync(filePath, 'utf8')\n      }\n    });\n  }\n} else {\n  ({bundle, transform} = await import('../index.mjs'));\n}\n\ntest('declaration list', () => {\n  let definitions = new Map();\n  let res = transform({\n    filename: 'test.css',\n    minify: true,\n    code: Buffer.from(`\n      @theme spacing {\n        foo: 16px;\n        bar: 32px;\n      }\n\n      .foo {\n        width: theme('spacing.foo');\n      }\n    `),\n    customAtRules: {\n      theme: {\n        prelude: '<custom-ident>',\n        body: 'declaration-list'\n      }\n    },\n    visitor: {\n      Rule: {\n        custom: {\n          theme(rule) {\n            for (let decl of rule.body.value.declarations) {\n              if (decl.property === 'custom') {\n                definitions.set(rule.prelude.value + '.' + decl.value.name, decl.value.value);\n              }\n            }\n            return [];\n          }\n        }\n      },\n      Function: {\n        theme(f) {\n          if (f.arguments[0].type === 'token' && f.arguments[0].value.type === 'string') {\n            return definitions.get(f.arguments[0].value.value);\n          }\n        }\n      }\n    }\n  });\n\n  assert.equal(res.code.toString(), '.foo{width:16px}');\n});\n\ntest('mixin', () => {\n  let mixins = new Map();\n  let res = transform({\n    filename: 'test.css',\n    minify: true,\n    code: Buffer.from(`\n      @mixin color {\n        color: red;\n\n        &.bar {\n          color: yellow;\n        }\n      }\n\n      .foo {\n        @apply color;\n      }\n    `),\n    targets: { chrome: 100 << 16 },\n    customAtRules: {\n      mixin: {\n        prelude: '<custom-ident>',\n        body: 'style-block'\n      },\n      apply: {\n        prelude: '<custom-ident>'\n      }\n    },\n    visitor: {\n      Rule: {\n        custom: {\n          mixin(rule) {\n            mixins.set(rule.prelude.value, rule.body.value);\n            return [];\n          },\n          apply(rule) {\n            return mixins.get(rule.prelude.value);\n          }\n        }\n      }\n    }\n  });\n\n  assert.equal(res.code.toString(), '.foo{color:red}.foo.bar{color:#ff0}');\n});\n\ntest('rule list', () => {\n  let res = transform({\n    filename: 'test.css',\n    minify: true,\n    code: Buffer.from(`\n      @breakpoint 1024px {\n        .foo { color: yellow; }\n      }\n    `),\n    customAtRules: {\n      breakpoint: {\n        prelude: '<length>',\n        body: 'rule-list'\n      }\n    },\n    visitor: {\n      Rule: {\n        custom: {\n          breakpoint(rule) {\n            return {\n              type: 'media',\n              value: {\n                query: {\n                  mediaQueries: [{ mediaType: 'all', condition: { type: 'feature', value: { type: 'range', name: 'width', operator: 'less-than-equal', value: rule.prelude } } }]\n                },\n                rules: rule.body.value,\n                loc: rule.loc\n              }\n            }\n          }\n        }\n      }\n    }\n  });\n\n  assert.equal(res.code.toString(), '@media (width<=1024px){.foo{color:#ff0}}');\n});\n\n\ntest('style block', () => {\n  let res = transform({\n    filename: 'test.css',\n    minify: true,\n    code: Buffer.from(`\n      .foo {\n        @breakpoint 1024px {\n          color: yellow;\n\n          &.bar {\n            color: red;\n          }\n        }\n      }\n    `),\n    targets: {\n      chrome: 105 << 16\n    },\n    customAtRules: {\n      breakpoint: {\n        prelude: '<length>',\n        body: 'style-block'\n      }\n    },\n    visitor: {\n      Rule: {\n        custom: {\n          breakpoint(rule) {\n            return {\n              type: 'media',\n              value: {\n                query: {\n                  mediaQueries: [{ mediaType: 'all', condition: { type: 'feature', value: { type: 'range', name: 'width', operator: 'less-than-equal', value: rule.prelude } } }]\n                },\n                rules: rule.body.value,\n                loc: rule.loc\n              }\n            }\n          }\n        }\n      }\n    }\n  });\n\n  assert.equal(res.code.toString(), '@media (width<=1024px){.foo{color:#ff0}.foo.bar{color:red}}');\n});\n\ntest('style block top level', () => {\n  let res = transform({\n    filename: 'test.css',\n    minify: true,\n    code: Buffer.from(`\n      @test {\n        .foo {\n          background: black;\n        }\n      }\n    `),\n    customAtRules: {\n      test: {\n        body: 'style-block'\n      }\n    }\n  });\n\n  assert.equal(res.code.toString(), '@test{.foo{background:#000}}');\n});\n\ntest('multiple', () => {\n  let res = transform({\n    filename: 'test.css',\n    minify: true,\n    code: Buffer.from(`\n      @breakpoint 1024px {\n        @theme spacing {\n          foo: 16px;\n          bar: 32px;\n        }\n      }\n    `),\n    customAtRules: {\n      breakpoint: {\n        prelude: '<length>',\n        body: 'rule-list'\n      },\n      theme: {\n        prelude: '<custom-ident>',\n        body: 'declaration-list'\n      }\n    },\n    visitor: {\n      Rule: {\n        custom(rule) {\n          if (rule.name === 'breakpoint') {\n            return {\n              type: 'media',\n              value: {\n                query: {\n                  mediaQueries: [{ mediaType: 'all', condition: { type: 'feature', value: { type: 'range', name: 'width', operator: 'less-than-equal', value: rule.prelude } } }]\n                },\n                rules: rule.body.value,\n                loc: rule.loc\n              }\n            }\n          } else {\n            return {\n              type: 'style',\n              value: {\n                selectors: [[{ type: 'pseudo-class', kind: 'root' }]],\n                declarations: rule.body.value,\n                loc: rule.loc\n              }\n            }\n          }\n        }\n      }\n    }\n  });\n\n  assert.equal(res.code.toString(), '@media (width<=1024px){:root{foo:16px;bar:32px}}');\n});\n\ntest('bundler', () => {\n  let mixins = new Map();\n  let res = bundle({\n    filename: 'tests/testdata/apply.css',\n    minify: true,\n    targets: { chrome: 100 << 16 },\n    customAtRules: {\n      mixin: {\n        prelude: '<custom-ident>',\n        body: 'style-block'\n      },\n      apply: {\n        prelude: '<custom-ident>'\n      }\n    },\n    visitor: {\n      Rule: {\n        custom: {\n          mixin(rule) {\n            mixins.set(rule.prelude.value, rule.body.value);\n            return [];\n          },\n          apply(rule) {\n            return mixins.get(rule.prelude.value);\n          }\n        }\n      }\n    }\n  });\n\n  assert.equal(res.code.toString(), '.foo{color:red}.foo.bar{color:#ff0}');\n});\n\ntest.run();\n"
  },
  {
    "path": "node/test/transform.test.mjs",
    "content": "import { test } from 'uvu';\nimport * as assert from 'uvu/assert';\nimport {webcrypto as crypto} from 'node:crypto';\n\nlet transform, Features;\nif (process.env.TEST_WASM === 'node') {\n  ({transform, Features} = await import('../../wasm/wasm-node.mjs'));\n} else if (process.env.TEST_WASM === 'browser') {\n  // Define crypto globally for old node.\n  // @ts-ignore\n  globalThis.crypto ??= crypto;\n  let wasm = await import('../../wasm/index.mjs');\n  await wasm.default();\n  ({transform, Features} = wasm);\n} else {\n  ({transform, Features} = await import('../index.mjs'));\n}\n\ntest('can enable non-standard syntax', () => {\n  let res = transform({\n    filename: 'test.css',\n    code: Buffer.from('.foo >>> .bar { color: red }'),\n    nonStandard: {\n      deepSelectorCombinator: true\n    },\n    minify: true\n  });\n\n  assert.equal(res.code.toString(), '.foo>>>.bar{color:red}');\n});\n\ntest('can enable features without targets', () => {\n  let res = transform({\n    filename: 'test.css',\n    code: Buffer.from('.foo { .bar { color: red }}'),\n    minify: true,\n    include: Features.Nesting\n  });\n\n  assert.equal(res.code.toString(), '.foo .bar{color:red}');\n});\n\ntest('can disable features', () => {\n  let res = transform({\n    filename: 'test.css',\n    code: Buffer.from('.foo { color: lch(50.998% 135.363 338) }'),\n    minify: true,\n    targets: {\n      chrome: 80 << 16\n    },\n    exclude: Features.Colors\n  });\n\n  assert.equal(res.code.toString(), '.foo{color:lch(50.998% 135.363 338)}');\n});\n\ntest('can disable prefixing', () => {\n  let res = transform({\n    filename: 'test.css',\n    code: Buffer.from('.foo { user-select: none }'),\n    minify: true,\n    targets: {\n      safari: 15 << 16\n    },\n    exclude: Features.VendorPrefixes\n  });\n\n  assert.equal(res.code.toString(), '.foo{user-select:none}');\n});\n\ntest.run();\n"
  },
  {
    "path": "node/test/visitor.test.mjs",
    "content": "// @ts-check\n\nimport { test } from 'uvu';\nimport * as assert from 'uvu/assert';\nimport fs from 'fs';\nimport {webcrypto as crypto} from 'node:crypto';\n\nlet bundle, bundleAsync, transform, transformStyleAttribute;\nif (process.env.TEST_WASM === 'node') {\n  ({ bundle, bundleAsync, transform, transformStyleAttribute } = await import('../../wasm/wasm-node.mjs'));\n} else if (process.env.TEST_WASM === 'browser') {\n  // Define crypto globally for old node.\n  // @ts-ignore\n  globalThis.crypto ??= crypto;\n  let wasm = await import('../../wasm/index.mjs');\n  await wasm.default();\n  ({ transform, transformStyleAttribute } = wasm);\n  bundle = function(options) {\n    return wasm.bundle({\n      ...options,\n      resolver: {\n        read: (filePath) => fs.readFileSync(filePath, 'utf8')\n      }\n    });\n  }\n\n  bundleAsync = function (options) {\n    if (!options.resolver?.read) {\n      options.resolver = {\n        ...options.resolver,\n        read: (filePath) => fs.readFileSync(filePath, 'utf8')\n      };\n    }\n\n    return wasm.bundleAsync(options);\n  }\n} else {\n  ({ bundle, bundleAsync, transform, transformStyleAttribute } = await import('../index.mjs'));\n}\n\ntest('px to rem', () => {\n  // Similar to https://github.com/cuth/postcss-pxtorem\n  let res = transform({\n    filename: 'test.css',\n    minify: true,\n    code: Buffer.from(`\n      .foo {\n        width: 32px;\n        height: calc(100vh - 64px);\n        --custom: calc(var(--foo) + 32px);\n      }\n    `),\n    visitor: {\n      Length(length) {\n        if (length.unit === 'px') {\n          return {\n            unit: 'rem',\n            value: length.value / 16\n          };\n        }\n      }\n    }\n  });\n\n  assert.equal(res.code.toString(), '.foo{--custom:calc(var(--foo) + 2rem);width:2rem;height:calc(100vh - 4rem)}');\n});\n\ntest('custom units', () => {\n  // https://github.com/csstools/custom-units\n  let res = transform({\n    filename: 'test.css',\n    minify: true,\n    code: Buffer.from(`\n      .foo {\n        --step: .25rem;\n        font-size: 3--step;\n      }\n    `),\n    visitor: {\n      Token: {\n        dimension(token) {\n          if (token.unit.startsWith('--')) {\n            return {\n              type: 'function',\n              value: {\n                name: 'calc',\n                arguments: [\n                  {\n                    type: 'token',\n                    value: {\n                      type: 'number',\n                      value: token.value\n                    }\n                  },\n                  {\n                    type: 'token',\n                    value: {\n                      type: 'delim',\n                      value: '*'\n                    }\n                  },\n                  {\n                    type: 'var',\n                    value: {\n                      name: {\n                        ident: token.unit\n                      }\n                    }\n                  }\n                ]\n              }\n            }\n          }\n        }\n      }\n    }\n  });\n\n  assert.equal(res.code.toString(), '.foo{--step:.25rem;font-size:calc(3*var(--step))}');\n});\n\ntest('design tokens', () => {\n  // Similar to https://www.npmjs.com/package/@csstools/postcss-design-tokens\n  let tokens = {\n    'color.background.primary': {\n      type: 'color',\n      value: {\n        type: 'rgb',\n        r: 255,\n        g: 0,\n        b: 0,\n        alpha: 1\n      }\n    },\n    'size.spacing.small': {\n      type: 'length',\n      value: {\n        unit: 'px',\n        value: 16\n      }\n    }\n  };\n\n  let res = transform({\n    filename: 'test.css',\n    minify: true,\n    code: Buffer.from(`\n      .foo {\n        color: design-token('color.background.primary');\n        padding: design-token('size.spacing.small');\n      }\n    `),\n    visitor: {\n      Function: {\n        'design-token'(fn) {\n          if (fn.arguments.length === 1 && fn.arguments[0].type === 'token' && fn.arguments[0].value.type === 'string') {\n            return tokens[fn.arguments[0].value.value];\n          }\n        }\n      }\n    }\n  });\n\n  assert.equal(res.code.toString(), '.foo{color:red;padding:16px}');\n});\n\ntest('env function', () => {\n  // https://www.npmjs.com/package/postcss-env-function\n  /** @type {Record<string, import('../ast').TokenOrValue>} */\n  let tokens = {\n    '--branding-small': {\n      type: 'length',\n      value: {\n        unit: 'px',\n        value: 600\n      }\n    },\n    '--branding-padding': {\n      type: 'length',\n      value: {\n        unit: 'px',\n        value: 20\n      }\n    }\n  };\n\n  let res = transform({\n    filename: 'test.css',\n    minify: true,\n    errorRecovery: true,\n    code: Buffer.from(`\n      @media (max-width: env(--branding-small)) {\n        body {\n          padding: env(--branding-padding);\n        }\n      }\n    `),\n    visitor: {\n      EnvironmentVariable(env) {\n        if (env.name.type === 'custom') {\n          return tokens[env.name.ident];\n        }\n      }\n    }\n  });\n\n  assert.equal(res.code.toString(), '@media (width<=600px){body{padding:20px}}');\n});\n\ntest('specific environment variables', () => {\n  // https://www.npmjs.com/package/postcss-env-function\n  /** @type {Record<string, import('../ast').TokenOrValue>} */\n  let tokens = {\n    '--branding-small': {\n      type: 'length',\n      value: {\n        unit: 'px',\n        value: 600\n      }\n    },\n    '--branding-padding': {\n      type: 'length',\n      value: {\n        unit: 'px',\n        value: 20\n      }\n    }\n  };\n\n  let res = transform({\n    filename: 'test.css',\n    minify: true,\n    errorRecovery: true,\n    code: Buffer.from(`\n      @media (max-width: env(--branding-small)) {\n        body {\n          padding: env(--branding-padding);\n        }\n      }\n    `),\n    visitor: {\n      EnvironmentVariable: {\n        '--branding-small': () => tokens['--branding-small'],\n        '--branding-padding': () => tokens['--branding-padding']\n      }\n    }\n  });\n\n  assert.equal(res.code.toString(), '@media (width<=600px){body{padding:20px}}');\n});\n\ntest('spacing with env substitution', () => {\n  // Test spacing for different cases when `env()` functions are replaced with actual values.\n  /** @type {Record<string, string>} */\n  let tokens = {\n    '--var1': 'var(--foo)',\n    '--var2': 'var(--bar)',\n    '--function': 'scale(1.5)',\n    '--length1': '10px',\n    '--length2': '20px',\n    '--x': '4',\n    '--y': '12',\n    '--num1': '5',\n    '--num2': '10',\n    '--num3': '15',\n    '--counter': '2',\n    '--ident1': 'solid',\n    '--ident2': 'auto',\n    '--rotate': '45deg',\n    '--percentage1': '25%',\n    '--percentage2': '75%',\n    '--color': 'red',\n    '--color1': '#ff1234',\n    '--string1': '\"hello\"',\n    '--string2': '\" world\"'\n  };\n\n  let res = transform({\n    filename: 'test.css',\n    minify: true,\n    code: Buffer.from(`\n      .test {\n        /* Asymmetric spacing - no space after var(). */\n        background: env(--var1) env(--var2);\n        border: env(--var1)env(--ident1);\n        transform: env(--function) env(--function);\n        /* Normal spacing between values. */\n        padding: env(--length1) env(--length2);\n        margin: env(--length1) env(--ident2);\n        outline: env(--color) env(--ident1);\n        /* Raw numbers that need spacing. */\n        cursor: url(cursor.png) env(--x) env(--y), auto;\n        stroke-dasharray: env(--num1) env(--num2) env(--num3);\n        counter-increment: myCounter env(--counter);\n        /* Mixed token types. */\n        background: linear-gradient(red env(--percentage1), blue env(--percentage2));\n        content: env(--string1) env(--string2);\n        /* Inside calc expressions. */\n        width: calc(env(--length1) - env(--length2));\n      }\n    `),\n    visitor: {\n      EnvironmentVariable(env) {\n        if (env.name.type === 'custom' && tokens[env.name.ident]) {\n          return { raw: tokens[env.name.ident] };\n        }\n      }\n    }\n  });\n\n  assert.equal(res.code.toString(), '.test{background:var(--foo) var(--bar);border:var(--foo)solid;transform:scale(1.5) scale(1.5);padding:10px 20px;margin:10px auto;outline:red solid;cursor:url(cursor.png) 4 12, auto;stroke-dasharray:5 10 15;counter-increment:myCounter 2;background:linear-gradient(red 25%, blue 75%);content:\"hello\" \" world\";width:calc(10px - 20px)}');\n});\n\ntest('url', () => {\n  // https://www.npmjs.com/package/postcss-url\n  let res = transform({\n    filename: 'test.css',\n    minify: true,\n    code: Buffer.from(`\n      .foo {\n        background: url(foo.png);\n      }\n    `),\n    visitor: {\n      Url(url) {\n        url.url = 'https://mywebsite.com/' + url.url;\n        return url;\n      }\n    }\n  });\n\n  assert.equal(res.code.toString(), '.foo{background:url(https://mywebsite.com/foo.png)}');\n});\n\ntest('static vars', () => {\n  // Similar to https://www.npmjs.com/package/postcss-simple-vars\n  let declared = new Map();\n  let res = transform({\n    filename: 'test.css',\n    minify: true,\n    code: Buffer.from(`\n      @blue #056ef0;\n\n      .menu_link {\n        background: @blue;\n      }\n    `),\n    visitor: {\n      Rule: {\n        unknown(rule) {\n          declared.set(rule.name, rule.prelude);\n          return [];\n        }\n      },\n      Token: {\n        'at-keyword'(token) {\n          if (declared.has(token.value)) {\n            return declared.get(token.value);\n          }\n        }\n      }\n    }\n  });\n\n  assert.equal(res.code.toString(), '.menu_link{background:#056ef0}');\n});\n\ntest('selector prefix', () => {\n  // Similar to https://www.npmjs.com/package/postcss-prefix-selector\n  let res = transform({\n    filename: 'test.css',\n    minify: true,\n    code: Buffer.from(`\n      .a, .b {\n        color: red;\n      }\n    `),\n    visitor: {\n      Selector(selector) {\n        return [{ type: 'class', name: 'prefix' }, { type: 'combinator', value: 'descendant' }, ...selector];\n      }\n    }\n  });\n\n  assert.equal(res.code.toString(), '.prefix .a,.prefix .b{color:red}');\n});\n\ntest('apply', () => {\n  // Similar to https://www.npmjs.com/package/postcss-apply\n  let defined = new Map();\n  let res = transform({\n    filename: 'test.css',\n    minify: true,\n    code: Buffer.from(`\n      --toolbar-theme {\n        color: white;\n        border: 1px solid green;\n      }\n\n      .toolbar {\n        @apply --toolbar-theme;\n      }\n    `),\n    visitor: {\n      Rule: {\n        style(rule) {\n          for (let selector of rule.value.selectors) {\n            if (selector.length === 1 && selector[0].type === 'type' && selector[0].name.startsWith('--')) {\n              defined.set(selector[0].name, rule.value.declarations);\n              return { type: 'ignored', value: null };\n            }\n          }\n\n          rule.value.rules = rule.value.rules.filter(child => {\n            if (child.type === 'unknown' && child.value.name === 'apply') {\n              for (let token of child.value.prelude) {\n                if (token.type === 'dashed-ident' && defined.has(token.value)) {\n                  let r = defined.get(token.value);\n                  let decls = rule.value.declarations;\n                  decls.declarations.push(...r.declarations);\n                  decls.importantDeclarations.push(...r.importantDeclarations);\n                }\n              }\n              return false;\n            }\n            return true;\n          });\n\n          return rule;\n        }\n      }\n    }\n  });\n\n  assert.equal(res.code.toString(), '.toolbar{color:#fff;border:1px solid green}');\n});\n\ntest('property lookup', () => {\n  // Similar to https://www.npmjs.com/package/postcss-property-lookup\n  let res = transform({\n    filename: 'test.css',\n    minify: true,\n    code: Buffer.from(`\n     .test {\n        margin-left: 20px;\n        margin-right: @margin-left;\n     }\n    `),\n    visitor: {\n      Rule: {\n        style(rule) {\n          let valuesByProperty = new Map();\n          for (let decl of rule.value.declarations.declarations) {\n            /** @type string */\n            let name = decl.property;\n            if (decl.property === 'unparsed') {\n              name = decl.value.propertyId.property;\n            }\n            valuesByProperty.set(name, decl);\n          }\n\n          rule.value.declarations.declarations = rule.value.declarations.declarations.map(decl => {\n            // Only single value supported. Would need a way to convert parsed values to unparsed tokens otherwise.\n            if (decl.property === 'unparsed' && decl.value.value.length === 1) {\n              let token = decl.value.value[0];\n              if (token.type === 'token' && token.value.type === 'at-keyword' && valuesByProperty.has(token.value.value)) {\n                let v = valuesByProperty.get(token.value.value);\n                return {\n                  /** @type any */\n                  property: decl.value.propertyId.property,\n                  value: v.value\n                };\n              }\n            }\n            return decl;\n          });\n\n          return rule;\n        }\n      }\n    }\n  });\n\n  assert.equal(res.code.toString(), '.test{margin-left:20px;margin-right:20px}');\n});\n\ntest('focus visible', () => {\n  // Similar to https://www.npmjs.com/package/postcss-focus-visible\n  let res = transform({\n    filename: 'test.css',\n    minify: true,\n    code: Buffer.from(`\n      .test:focus-visible {\n        color: red;\n      }\n    `),\n    targets: {\n      safari: 14 << 16\n    },\n    visitor: {\n      Rule: {\n        style(rule) {\n          let clone = null;\n          for (let selector of rule.value.selectors) {\n            for (let [i, component] of selector.entries()) {\n              if (component.type === 'pseudo-class' && component.kind === 'focus-visible') {\n                if (clone == null) {\n                  clone = [...rule.value.selectors.map(s => [...s])];\n                }\n\n                selector[i] = { type: 'class', name: 'focus-visible' };\n              }\n            }\n          }\n\n          if (clone) {\n            return [rule, { type: 'style', value: { ...rule.value, selectors: clone } }];\n          }\n        }\n      }\n    }\n  });\n\n  assert.equal(res.code.toString(), '.test.focus-visible{color:red}.test:focus-visible{color:red}');\n});\n\ntest('dark theme class', () => {\n  // Similar to https://github.com/postcss/postcss-dark-theme-class\n  let res = transform({\n    filename: 'test.css',\n    minify: true,\n    code: Buffer.from(`\n      @media (prefers-color-scheme: dark) {\n        body {\n          background: black\n        }\n      }\n    `),\n    visitor: {\n      Rule: {\n        media(rule) {\n          let q = rule.value.query.mediaQueries[0];\n          if (q.condition?.type === 'feature' && q.condition.value.type === 'plain' && q.condition.value.name === 'prefers-color-scheme' && q.condition.value.value.value === 'dark') {\n            /** @type {import('../ast').Rule[]} */\n            let clonedRules = [rule];\n            for (let r of rule.value.rules) {\n              if (r.type === 'style') {\n                /** @type {import('../ast').Selector[]} */\n                let clonedSelectors = [];\n                for (let selector of r.value.selectors) {\n                  clonedSelectors.push([\n                    { type: 'type', name: 'html' },\n                    { type: 'attribute', name: 'theme', operation: { operator: 'equal', value: 'dark' } },\n                    { type: 'combinator', value: 'descendant' },\n                    ...selector\n                  ]);\n                  selector.unshift(\n                    { type: 'type', name: 'html' },\n                    {\n                      type: 'pseudo-class',\n                      kind: 'not',\n                      selectors: [\n                        [{ type: 'attribute', name: 'theme', operation: { operator: 'equal', value: 'light' } }]\n                      ]\n                    },\n                    { type: 'combinator', value: 'descendant' }\n                  );\n                }\n\n                clonedRules.push({ type: 'style', value: { ...r.value, selectors: clonedSelectors } });\n              }\n            }\n\n            return clonedRules;\n          }\n        }\n      }\n    }\n  });\n\n  assert.equal(res.code.toString(), '@media (prefers-color-scheme:dark){html:not([theme=light]) body{background:#000}}html[theme=dark] body{background:#000}');\n});\n\ntest('100vh fix', () => {\n  // Similar to https://github.com/postcss/postcss-100vh-fix\n  let res = transform({\n    filename: 'test.css',\n    minify: true,\n    code: Buffer.from(`\n      .foo {\n        color: red;\n        height: 100vh;\n      }\n    `),\n    visitor: {\n      Rule: {\n        style(style) {\n          let cloned;\n          for (let property of style.value.declarations.declarations) {\n            if (property.property === 'height' && property.value.type === 'length-percentage' && property.value.value.type === 'dimension' && property.value.value.value.unit === 'vh' && property.value.value.value.value === 100) {\n              if (!cloned) {\n                cloned = structuredClone(style);\n                cloned.value.declarations.declarations = [];\n              }\n              cloned.value.declarations.declarations.push({\n                ...property,\n                value: {\n                  type: 'stretch',\n                  vendorPrefix: ['webkit']\n                }\n              });\n            }\n          }\n\n          if (cloned) {\n            return [style, {\n              type: 'supports',\n              value: {\n                condition: {\n                  type: 'declaration',\n                  propertyId: {\n                    property: '-webkit-touch-callout'\n                  },\n                  value: 'none'\n                },\n                loc: style.value.loc,\n                rules: [cloned]\n              }\n            }];\n          }\n        }\n      }\n    }\n  });\n\n  assert.equal(res.code.toString(), '.foo{color:red;height:100vh}@supports (-webkit-touch-callout:none){.foo{height:-webkit-fill-available}}')\n});\n\ntest('logical transforms', () => {\n  // Similar to https://github.com/MohammadYounes/rtlcss\n  let res = transform({\n    filename: 'test.css',\n    minify: true,\n    code: Buffer.from(`\n      .foo {\n        transform: translateX(50px);\n      }\n\n      .bar {\n        transform: translateX(20%);\n      }\n\n      .baz {\n        transform: translateX(calc(100vw - 20px));\n      }\n    `),\n    visitor: {\n      Rule: {\n        style(style) {\n          /** @type any */\n          let cloned;\n          for (let property of style.value.declarations.declarations) {\n            if (property.property === 'transform') {\n              let clonedTransforms = property.value.map(transform => {\n                if (transform.type !== 'translateX') {\n                  return transform;\n                }\n\n                if (!cloned) {\n                  cloned = structuredClone(style);\n                  cloned.value.declarations.declarations = [];\n                }\n\n                let value;\n                switch (transform.value.type) {\n                  case 'dimension':\n                    value = { type: 'dimension', value: { unit: transform.value.value.unit, value: -transform.value.value.value } };\n                    break;\n                  case 'percentage':\n                    value = { type: 'percentage', value: -transform.value.value };\n                    break;\n                  case 'calc':\n                    value = { type: 'calc', value: { type: 'product', value: [-1, transform.value.value] } };\n                    break;\n                }\n\n                return {\n                  type: 'translateX',\n                  value\n                }\n              });\n\n              if (cloned) {\n                cloned.value.selectors.at(-1).push({ type: 'pseudo-class', kind: 'dir', direction: 'rtl' });\n                cloned.value.declarations.declarations.push({\n                  ...property,\n                  value: clonedTransforms\n                });\n              }\n            }\n          }\n\n          if (cloned) {\n            return [style, cloned];\n          }\n        }\n      }\n    }\n  });\n\n  assert.equal(res.code.toString(), '.foo{transform:translate(50px)}.foo:dir(rtl){transform:translate(-50px)}.bar{transform:translate(20%)}.bar:dir(rtl){transform:translate(-20%)}.baz{transform:translate(calc(100vw - 20px))}.baz:dir(rtl){transform:translate(-1*calc(100vw - 20px))}');\n});\n\ntest('hover media query', () => {\n  // Similar to https://github.com/twbs/mq4-hover-shim\n  let res = transform({\n    filename: 'test.css',\n    minify: true,\n    code: Buffer.from(`\n      @media (hover) {\n        .foo {\n          color: red;\n        }\n      }\n    `),\n    visitor: {\n      Rule: {\n        media(media) {\n          let mediaQueries = media.value.query.mediaQueries;\n          if (\n            mediaQueries.length === 1 &&\n            mediaQueries[0].condition &&\n            mediaQueries[0].condition.type === 'feature' &&\n            mediaQueries[0].condition.value.type === 'boolean' &&\n            mediaQueries[0].condition.value.name === 'hover'\n          ) {\n            for (let rule of media.value.rules) {\n              if (rule.type === 'style') {\n                for (let selector of rule.value.selectors) {\n                  selector.unshift({ type: 'class', name: 'hoverable' }, { type: 'combinator', value: 'descendant' });\n                }\n              }\n            }\n            return media.value.rules\n          }\n        }\n      }\n    }\n  });\n\n  assert.equal(res.code.toString(), '.hoverable .foo{color:red}');\n});\n\ntest('momentum scrolling', () => {\n  // Similar to https://github.com/yunusga/postcss-momentum-scrolling\n  let visitOverflow = decl => [decl, {\n    property: '-webkit-overflow-scrolling',\n    raw: 'touch'\n  }];\n\n  let res = transform({\n    filename: 'test.css',\n    minify: true,\n    code: Buffer.from(`\n      .foo {\n        overflow: auto;\n      }\n    `),\n    visitor: {\n      Declaration: {\n        overflow: visitOverflow,\n        'overflow-x': visitOverflow,\n        'overflow-y': visitOverflow\n      }\n    }\n  });\n\n  assert.equal(res.code.toString(), '.foo{-webkit-overflow-scrolling:touch;overflow:auto}');\n});\n\ntest('size', () => {\n  // Similar to https://github.com/postcss/postcss-size\n  let res = transform({\n    filename: 'test.css',\n    minify: true,\n    code: Buffer.from(`\n      .foo {\n        size: 12px;\n      }\n    `),\n    visitor: {\n      Declaration: {\n        custom: {\n          size(property) {\n            if (property.value[0].type === 'length') {\n              /** @type {import('../ast').Size} */\n              let value = { type: 'length-percentage', value: { type: 'dimension', value: property.value[0].value } };\n              return [\n                { property: 'width', value },\n                { property: 'height', value }\n              ];\n            }\n          }\n        }\n      }\n    }\n  });\n\n  assert.equal(res.code.toString(), '.foo{width:12px;height:12px}');\n});\n\ntest('works with style attributes', () => {\n  let res = transformStyleAttribute({\n    filename: 'test.css',\n    minify: true,\n    code: Buffer.from('height: calc(100vh - 64px)'),\n    visitor: {\n      Length(length) {\n        if (length.unit === 'px') {\n          return {\n            unit: 'rem',\n            value: length.value / 16\n          };\n        }\n      }\n    }\n  });\n\n  assert.equal(res.code.toString(), 'height:calc(100vh - 4rem)');\n});\n\ntest('works with bundler', () => {\n  let res = bundle({\n    filename: 'tests/testdata/a.css',\n    minify: true,\n    visitor: {\n      Length(length) {\n        if (length.unit === 'px') {\n          return {\n            unit: 'rem',\n            value: length.value / 16\n          };\n        }\n      }\n    }\n  });\n\n  assert.equal(res.code.toString(), '.b{height:calc(100vh - 4rem)}.a{width:2rem}');\n});\n\ntest('works with async bundler', async () => {\n  let res = await bundleAsync({\n    filename: 'tests/testdata/a.css',\n    minify: true,\n    visitor: {\n      Length(length) {\n        if (length.unit === 'px') {\n          return {\n            unit: 'rem',\n            value: length.value / 16\n          };\n        }\n      }\n    }\n  });\n\n  assert.equal(res.code.toString(), '.b{height:calc(100vh - 4rem)}.a{width:2rem}');\n});\n\ntest('dashed idents', () => {\n  let res = transform({\n    filename: 'test.css',\n    minify: true,\n    code: Buffer.from(`\n      .foo {\n        --foo: #ff0;\n        color: var(--foo);\n      }\n    `),\n    visitor: {\n      DashedIdent(ident) {\n        return `--prefix-${ident.slice(2)}`;\n      }\n    }\n  });\n\n  assert.equal(res.code.toString(), '.foo{--prefix-foo:#ff0;color:var(--prefix-foo)}');\n});\n\ntest('custom idents', () => {\n  let res = transform({\n    filename: 'test.css',\n    minify: true,\n    code: Buffer.from(`\n      @keyframes test {\n        from { color: red }\n        to { color: green }\n      }\n      .foo {\n        animation: test;\n      }\n    `),\n    visitor: {\n      CustomIdent(ident) {\n        return `prefix-${ident}`;\n      }\n    }\n  });\n\n  assert.equal(res.code.toString(), '@keyframes prefix-test{0%{color:red}to{color:green}}.foo{animation:prefix-test}');\n});\n\ntest('returning string values', () => {\n  let res = transform({\n    filename: 'test.css',\n    minify: true,\n    code: Buffer.from(`\n      @tailwind base;\n    `),\n    visitor: {\n      Rule: {\n        unknown(rule) {\n          return {\n            type: 'style',\n            value: {\n              loc: rule.loc,\n              selectors: [\n                [{ type: 'universal' }]\n              ],\n              declarations: {\n                declarations: [\n                  {\n                    property: 'visibility',\n                    raw: 'hi\\\\64 den' // escapes work for raw but not value\n                  },\n                  {\n                    property: 'background',\n                    raw: 'yellow'\n                  },\n                  {\n                    property: '--custom',\n                    raw: 'hi'\n                  },\n                  {\n                    property: 'transition',\n                    vendorPrefix: ['moz'],\n                    raw: '200ms test'\n                  },\n                  {\n                    property: '-webkit-animation',\n                    raw: '3s cubic-bezier(0.25, 0.1, 0.25, 1) foo'\n                  }\n                ]\n              }\n            }\n          }\n        }\n      }\n    }\n  });\n\n  assert.equal(res.code.toString(), '*{visibility:hidden;--custom:hi;background:#ff0;-moz-transition:test .2s;-webkit-animation:3s foo}');\n});\n\ntest('errors on invalid dashed idents', () => {\n  assert.throws(() => {\n    transform({\n      filename: 'test.css',\n      minify: true,\n      code: Buffer.from(`\n        .foo {\n          background: opacity(abcdef);\n        }\n      `),\n      visitor: {\n        Function(fn) {\n          if (fn.arguments[0].type === 'token' && fn.arguments[0].value.type === 'ident') {\n            fn.arguments = [\n              {\n                type: 'var',\n                value: {\n                  name: { ident: fn.arguments[0].value.value }\n                }\n              }\n            ];\n          }\n\n          return {\n            type: 'function',\n            value: fn\n          }\n        }\n      }\n    })\n  }, 'Dashed idents must start with --');\n});\n\ntest('supports returning raw values for tokens', () => {\n  let res = transform({\n    filename: 'test.css',\n    minify: true,\n    code: Buffer.from(`\n      .foo {\n        color: theme('red');\n      }\n    `),\n    visitor: {\n      Function: {\n        theme() {\n          return { raw: 'rgba(255, 0, 0)' };\n        }\n      }\n    }\n  });\n\n  assert.equal(res.code.toString(), '.foo{color:red}');\n});\n\ntest('supports returning raw values as variables', () => {\n  let res = transform({\n    filename: 'test.css',\n    minify: true,\n    cssModules: {\n      dashedIdents: true\n    },\n    code: Buffer.from(`\n      .foo {\n        color: theme('foo');\n      }\n    `),\n    visitor: {\n      Function: {\n        theme() {\n          return { raw: 'var(--foo)' };\n        }\n      }\n    }\n  });\n\n  assert.equal(res.code.toString(), '.EgL3uq_foo{color:var(--EgL3uq_foo)}');\n});\n\ntest('works with currentColor', () => {\n  let res = transform({\n    filename: 'test.css',\n    minify: true,\n    code: Buffer.from(`\n      .foo {\n        color: currentColor;\n      }\n    `),\n    visitor: {\n      Rule(rule) {\n        return rule;\n      }\n    }\n  });\n\n  assert.equal(res.code.toString(), '.foo{color:currentColor}');\n});\n\ntest('nth of S to nth-of-type', () => {\n  let res = transform({\n    filename: 'test.css',\n    minify: true,\n    code: Buffer.from(`\n      a:nth-child(even of a) {\n        color: red;\n      }\n    `),\n    visitor: {\n      Selector(selector) {\n        for (let component of selector) {\n          if (component.type === 'pseudo-class' && component.kind === 'nth-child' && component.of) {\n            delete component.of;\n            component.kind = 'nth-of-type';\n          }\n        }\n        return selector;\n      }\n    }\n  });\n\n  assert.equal(res.code.toString(), 'a:nth-of-type(2n){color:red}');\n});\n\ntest('media query raw', () => {\n  let res = transform({\n    filename: 'test.css',\n    minify: true,\n    code: Buffer.from(`\n      @breakpoints {\n        .m-1 {\n          margin: 10px;\n        }\n      }\n    `),\n    customAtRules: {\n      breakpoints: {\n        prelude: null,\n        body: \"rule-list\",\n      },\n    },\n    visitor: {\n      Rule: {\n        custom: {\n          breakpoints({ body, loc }) {\n            /** @type {import('lightningcss').ReturnedRule[]} */\n            const value = [];\n\n            for (let rule of body.value) {\n              if (rule.type !== 'style') {\n                continue;\n              }\n              const clone = structuredClone(rule);\n              for (let selector of clone.value.selectors) {\n                for (let component of selector) {\n                  if (component.type === 'class') {\n                    component.name = `sm:${component.name}`;\n                  }\n                }\n              }\n\n              value.push(rule);\n              value.push({\n                type: \"media\",\n                value: {\n                  rules: [clone],\n                  loc,\n                  query: {\n                    mediaQueries: [\n                      { raw: '(min-width: 500px)' }\n                    ]\n                  }\n                }\n              });\n            }\n\n            return value;\n          }\n        }\n      }\n    }\n  });\n\n  assert.equal(res.code.toString(), '.m-1{margin:10px}@media (width>=500px){.sm\\\\:m-1{margin:10px}}');\n});\n\ntest('visit stylesheet', () => {\n  let res = transform({\n    filename: 'test.css',\n    minify: true,\n    code: Buffer.from(`\n      .foo {\n        width: 32px;\n      }\n\n      .bar {\n        width: 80px;\n      }\n    `),\n    visitor: {\n      StyleSheetExit(stylesheet) {\n        stylesheet.rules.sort((a, b) => a.value.selectors[0][0].name.localeCompare(b.value.selectors[0][0].name));\n        return stylesheet;\n      }\n    }\n  });\n\n  assert.equal(res.code.toString(), '.bar{width:80px}.foo{width:32px}');\n});\n\ntest('visitor function', () => {\n  let res = transform({\n    filename: 'test.css',\n    minify: true,\n    code: Buffer.from(`\n      @dep \"foo.js\";\n\n      .foo {\n        width: 32px;\n      }\n    `),\n    visitor: ({addDependency}) => ({\n      Rule: {\n        unknown: {\n          dep(rule) {\n            let file = rule.prelude[0].value.value;\n            addDependency({\n              type: 'file',\n              filePath: file\n            });\n            return [];\n          }\n        }\n      }\n    })\n  });\n\n  assert.equal(res.code.toString(), '.foo{width:32px}');\n  assert.equal(res.dependencies, [{\n    type: 'file',\n    filePath: 'foo.js'\n  }]);\n});\n\ntest('visitor function works with style attributes', () => {\n  let res = transformStyleAttribute({\n    filename: 'test.css',\n    minify: true,\n    code: Buffer.from('height: 12px'),\n    visitor: ({addDependency}) => ({\n      Length() {\n        addDependency({\n          type: 'file',\n          filePath: 'test.json'\n        });\n      }\n    })\n  });\n\n  assert.equal(res.dependencies, [{\n    type: 'file',\n    filePath: 'test.json'\n  }]);\n});\n\ntest('visitor function works with bundler', () => {\n  let res = bundle({\n    filename: 'tests/testdata/a.css',\n    minify: true,\n    visitor: ({addDependency}) => ({\n      Length() {\n        addDependency({\n          type: 'file',\n          filePath: 'test.json'\n        });\n      }\n    })\n  });\n\n  assert.equal(res.dependencies, [\n    {\n      type: 'file',\n      filePath: 'test.json'\n    },\n    {\n      type: 'file',\n      filePath: 'test.json'\n    },\n    {\n      type: 'file',\n      filePath: 'test.json'\n    }\n  ]);\n});\n\ntest('works with async bundler', async () => {\n  let res = await bundleAsync({\n    filename: 'tests/testdata/a.css',\n    minify: true,\n    visitor: ({addDependency}) => ({\n      Length() {\n        addDependency({\n          type: 'file',\n          filePath: 'test.json'\n        });\n      }\n    })\n  });\n\n  assert.equal(res.dependencies, [\n    {\n      type: 'file',\n      filePath: 'test.json'\n    },\n    {\n      type: 'file',\n      filePath: 'test.json'\n    },\n    {\n      type: 'file',\n      filePath: 'test.json'\n    }\n  ]);\n});\n\ntest.run();\n"
  },
  {
    "path": "node/tsconfig.json",
    "content": "{\n  \"include\": [\"*.d.ts\"],\n  \"compilerOptions\": {\n    \"lib\": [\"ES2020\"],\n    \"moduleResolution\": \"node\",\n    \"isolatedModules\": true,\n    \"noEmit\": true,\n    \"strict\": true\n  }\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"lightningcss\",\n  \"version\": \"1.32.0\",\n  \"license\": \"MPL-2.0\",\n  \"description\": \"A CSS parser, transformer, and minifier written in Rust\",\n  \"main\": \"node/index.js\",\n  \"types\": \"node/index.d.ts\",\n  \"exports\": {\n    \"types\": \"./node/index.d.ts\",\n    \"import\": \"./node/index.mjs\",\n    \"require\": \"./node/index.js\"\n  },\n  \"browserslist\": \"last 2 versions, not dead\",\n  \"targets\": {\n    \"main\": false,\n    \"types\": false\n  },\n  \"publishConfig\": {\n    \"access\": \"public\"\n  },\n  \"funding\": {\n    \"type\": \"opencollective\",\n    \"url\": \"https://opencollective.com/parcel\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/parcel-bundler/lightningcss.git\"\n  },\n  \"engines\": {\n    \"node\": \">= 12.0.0\"\n  },\n  \"napi\": {\n    \"name\": \"lightningcss\"\n  },\n  \"files\": [\n    \"node/*.js\",\n    \"node/*.mjs\",\n    \"node/*.d.ts\",\n    \"node/*.flow\"\n  ],\n  \"dependencies\": {\n    \"detect-libc\": \"^2.0.3\"\n  },\n  \"devDependencies\": {\n    \"@babel/parser\": \"7.21.4\",\n    \"@babel/traverse\": \"7.21.4\",\n    \"@codemirror/lang-css\": \"^6.0.1\",\n    \"@codemirror/lang-javascript\": \"^6.1.2\",\n    \"@codemirror/lint\": \"^6.1.0\",\n    \"@codemirror/theme-one-dark\": \"^6.1.0\",\n    \"@mdn/browser-compat-data\": \"~7.3.6\",\n    \"@napi-rs/cli\": \"^2.14.0\",\n    \"autoprefixer\": \"^10.4.27\",\n    \"caniuse-lite\": \"^1.0.30001777\",\n    \"codemirror\": \"^6.0.1\",\n    \"cssnano\": \"^7.0.6\",\n    \"esbuild\": \"^0.19.8\",\n    \"flowgen\": \"^1.21.0\",\n    \"jest-diff\": \"^27.4.2\",\n    \"json-schema-to-typescript\": \"^11.0.2\",\n    \"markdown-it-anchor\": \"^8.6.6\",\n    \"markdown-it-prism\": \"^2.3.0\",\n    \"markdown-it-table-of-contents\": \"^0.6.0\",\n    \"napi-wasm\": \"^1.0.1\",\n    \"node-fetch\": \"^3.1.0\",\n    \"parcel\": \"^2.8.2\",\n    \"patch-package\": \"^6.5.0\",\n    \"path-browserify\": \"^1.0.1\",\n    \"postcss\": \"^8.3.11\",\n    \"posthtml-include\": \"^1.7.4\",\n    \"posthtml-markdownit\": \"^1.3.1\",\n    \"posthtml-prism\": \"^1.0.4\",\n    \"process\": \"^0.11.10\",\n    \"puppeteer\": \"^12.0.1\",\n    \"recast\": \"^0.22.0\",\n    \"sharp\": \"^0.33.5\",\n    \"typescript\": \"^5.7.2\",\n    \"util\": \"^0.12.4\",\n    \"uvu\": \"^0.5.6\"\n  },\n  \"resolutions\": {\n    \"lightningcss\": \"link:.\"\n  },\n  \"scripts\": {\n    \"prepare\": \"patch-package\",\n    \"build\": \"node scripts/build.js && node scripts/build-flow.js\",\n    \"build-release\": \"node scripts/build.js --release && node scripts/build-flow.js\",\n    \"prepublishOnly\": \"node scripts/build-flow.js\",\n    \"wasm:build\": \"cargo build --target wasm32-unknown-unknown -p lightningcss_node && wasm-opt target/wasm32-unknown-unknown/debug/lightningcss_node.wasm --asyncify --pass-arg=asyncify-imports@env.await_promise_sync -Oz -o wasm/lightningcss_node.wasm && node scripts/build-wasm.js\",\n    \"wasm:build-release\": \"cargo build --target wasm32-unknown-unknown -p lightningcss_node --release && wasm-opt target/wasm32-unknown-unknown/release/lightningcss_node.wasm --asyncify --pass-arg=asyncify-imports@env.await_promise_sync -Oz -o wasm/lightningcss_node.wasm && node scripts/build-wasm.js\",\n    \"website:start\": \"parcel 'website/*.html' website/playground/index.html\",\n    \"website:build\": \"yarn wasm:build-release && parcel build 'website/*.html' website/playground/index.html\",\n    \"build-ast\": \"cargo run --example schema --features jsonschema && node scripts/build-ast.js\",\n    \"tsc\": \"tsc -p node/tsconfig.json\",\n    \"test\": \"uvu node/test\"\n  }\n}\n"
  },
  {
    "path": "patches/@babel+types+7.26.3.patch",
    "content": "diff --git a/node_modules/@babel/types/lib/retrievers/getBindingIdentifiers.js b/node_modules/@babel/types/lib/retrievers/getBindingIdentifiers.js\nindex 31feb1e..a64b83d 100644\n--- a/node_modules/@babel/types/lib/retrievers/getBindingIdentifiers.js\n+++ b/node_modules/@babel/types/lib/retrievers/getBindingIdentifiers.js\n@@ -66,6 +66,13 @@ const keys = {\n   InterfaceDeclaration: [\"id\"],\n   TypeAlias: [\"id\"],\n   OpaqueType: [\"id\"],\n+  TSDeclareFunction: [\"id\"],\n+  TSEnumDeclaration: [\"id\"],\n+  TSImportEqualsDeclaration: [\"id\"],\n+  TSInterfaceDeclaration: [\"id\"],\n+  TSModuleDeclaration: [\"id\"],\n+  TSNamespaceExportDeclaration: [\"id\"],\n+  TSTypeAliasDeclaration: [\"id\"],\n   CatchClause: [\"param\"],\n   LabeledStatement: [\"label\"],\n   UnaryExpression: [\"argument\"],\n"
  },
  {
    "path": "patches/json-schema-to-typescript+11.0.5.patch",
    "content": "diff --git a/node_modules/json-schema-to-typescript/dist/src/parser.js b/node_modules/json-schema-to-typescript/dist/src/parser.js\nindex fa9d2e4..3f65449 100644\n--- a/node_modules/json-schema-to-typescript/dist/src/parser.js\n+++ b/node_modules/json-schema-to-typescript/dist/src/parser.js\n@@ -1,6 +1,6 @@\n \"use strict\";\n var __assign = (this && this.__assign) || function () {\n-    __assign = Object.assign || function(t) {\n+    __assign = Object.assign || function (t) {\n         for (var s, i = 1, n = arguments.length; i < n; i++) {\n             s = arguments[i];\n             for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))\n@@ -90,14 +90,27 @@ function parseNonLiteral(schema, type, options, keyName, processed, usedNames) {\n             };\n         case 'ANY':\n             return __assign(__assign({}, (options.unknownAny ? AST_1.T_UNKNOWN : AST_1.T_ANY)), { comment: schema.description, keyName: keyName, standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames) });\n-        case 'ANY_OF':\n-            return {\n+        case 'ANY_OF': {\n+            let union = {\n                 comment: schema.description,\n                 keyName: keyName,\n                 standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames),\n                 params: schema.anyOf.map(function (_) { return parse(_, options, undefined, processed, usedNames); }),\n                 type: 'UNION'\n             };\n+\n+            if (schema.properties) {\n+                let common = newInterface(schema, options, processed, usedNames, keyName, keyNameFromDefinition);\n+                return {\n+                    comment: schema.description,\n+                    keyName,\n+                    standaloneName: union.standaloneName,\n+                    params: [common, union],\n+                    type: 'INTERSECTION'\n+                };\n+            }\n+            return union;\n+        }\n         case 'BOOLEAN':\n             return {\n                 comment: schema.description,\n@@ -118,10 +131,12 @@ function parseNonLiteral(schema, type, options, keyName, processed, usedNames) {\n                 comment: schema.description,\n                 keyName: keyName,\n                 standaloneName: standaloneName(schema, keyNameFromDefinition !== null && keyNameFromDefinition !== void 0 ? keyNameFromDefinition : keyName, usedNames),\n-                params: schema.enum.map(function (_, n) { return ({\n-                    ast: parse(_, options, undefined, processed, usedNames),\n-                    keyName: schema.tsEnumNames[n]\n-                }); }),\n+                params: schema.enum.map(function (_, n) {\n+                    return ({\n+                        ast: parse(_, options, undefined, processed, usedNames),\n+                        keyName: schema.tsEnumNames[n]\n+                    });\n+                }),\n                 type: 'ENUM'\n             };\n         case 'NAMED_SCHEMA':\n@@ -147,14 +162,24 @@ function parseNonLiteral(schema, type, options, keyName, processed, usedNames) {\n                 standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames),\n                 type: 'OBJECT'\n             };\n-        case 'ONE_OF':\n+        case 'ONE_OF': {\n+            let common = schema.properties ? parseSchema(schema, options, processed, usedNames, keyName) : null;\n+            let commonKeys = common ? new Set(common.map(p => p.keyName)) : null;\n+\n             return {\n                 comment: schema.description,\n                 keyName: keyName,\n                 standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames),\n-                params: schema.oneOf.map(function (_) { return parse(_, options, undefined, processed, usedNames); }),\n+                params: schema.oneOf.map(function (_) {\n+                    let item = parse(_, options, undefined, processed, usedNames);\n+                    if (common && item.type === 'INTERFACE') {\n+                        item.params = common.concat(item.params.filter(p => !commonKeys.has(p.keyName)));\n+                    }\n+                    return item;\n+                }),\n                 type: 'UNION'\n             };\n+        }\n         case 'REFERENCE':\n             throw Error((0, util_1.format)('Refs should have been resolved by the resolver!', schema));\n         case 'STRING':\n@@ -277,13 +302,15 @@ function parseSuperTypes(schema, options, processed, usedNames) {\n  * Helper to parse schema properties into params on the parent schema's type\n  */\n function parseSchema(schema, options, processed, usedNames, parentSchemaName) {\n-    var asts = (0, lodash_1.map)(schema.properties, function (value, key) { return ({\n-        ast: parse(value, options, key, processed, usedNames),\n-        isPatternProperty: false,\n-        isRequired: (0, lodash_1.includes)(schema.required || [], key),\n-        isUnreachableDefinition: false,\n-        keyName: key\n-    }); });\n+    var asts = (0, lodash_1.map)(schema.properties, function (value, key) {\n+        return ({\n+            ast: parse(value, options, key, processed, usedNames),\n+            isPatternProperty: false,\n+            isRequired: (0, lodash_1.includes)(schema.required || [], key),\n+            isUnreachableDefinition: false,\n+            keyName: key\n+        });\n+    });\n     var singlePatternProperty = false;\n     if (schema.patternProperties) {\n         // partially support patternProperties. in the case that\n"
  },
  {
    "path": "rust-toolchain.toml",
    "content": "[toolchain]\nchannel = \"1.92.0\"\ncomponents = [\"rustfmt\", \"clippy\"]\n"
  },
  {
    "path": "rustfmt.toml",
    "content": "tab_spaces = 2\nchain_width = 80\nmax_width = 115"
  },
  {
    "path": "scripts/build-ast.js",
    "content": "const { compileFromFile } = require('json-schema-to-typescript');\nconst fs = require('fs');\nconst recast = require('recast');\nconst traverse = require('@babel/traverse').default;\nconst {parse} = require('@babel/parser');\nconst t = require('@babel/types');\n\nconst skip = {\n  FillRule: true,\n  ImportRule: true,\n  FontFaceRule: true,\n  FontPaletteValuesRule: true,\n  NamespaceRule: true,\n  CustomMediaRule: true,\n  LayerStatementRule: true,\n  PropertyRule: true,\n  UnknownAtRule: true,\n  DefaultAtRule: true\n}\n\ncompileFromFile('node/ast.json', {\n  additionalProperties: false\n}).then(ts => {\n  ts = ts.replaceAll('For_DefaultAtRule', '');\n\n  // Use recast/babel to make some types generic so we can replace them in index.d.ts.\n  let ast = recast.parse(ts, {\n    parser: {\n      parse() {\n        return parse(ts, {\n          sourceType: 'module',\n          plugins: ['typescript'],\n          tokens: true\n        });\n      }\n    }\n  });\n\n  traverse(ast, {\n    Program(path) {\n      process(path.scope.getBinding('Declaration'));\n      process(path.scope.getBinding('MediaQuery'));\n    },\n    TSInterfaceDeclaration(path) {\n      // Dedupe.\n      if (path.node.id.name.startsWith('GenericBorderFor_LineStyleAnd_')) {\n        if (path.node.id.name.endsWith('_0')) {\n          path.node.id.name = 'GenericBorderFor_LineStyle';\n        } else {\n          path.remove();\n        }\n      }\n    },\n    ReferencedIdentifier(path) {\n      if (path.node.name.startsWith('GenericBorderFor_LineStyleAnd_')) {\n        path.node.name = 'GenericBorderFor_LineStyle';\n      }\n    },\n    TSTypeAliasDeclaration(path) {\n      // Workaround for schemars not supporting untagged variants.\n      // https://github.com/GREsau/schemars/issues/222\n      if (\n        (path.node.id.name === 'Translate' || path.node.id.name === 'Scale') &&\n        path.node.typeAnnotation.type === 'TSUnionType' &&\n        path.node.typeAnnotation.types[1].type === 'TSTypeLiteral' &&\n        path.node.typeAnnotation.types[1].members[0].key.name === 'xyz'\n      ) {\n        path.get('typeAnnotation.types.1').replaceWith(path.node.typeAnnotation.types[1].members[0].typeAnnotation.typeAnnotation);\n      } else if (path.node.id.name === 'AnimationAttachmentRange' && path.node.typeAnnotation.type === 'TSUnionType') {\n        let types = path.node.typeAnnotation.types;\n        if (types[1].type === 'TSTypeLiteral' && types[1].members[0].key.name === 'lengthpercentage') {\n          path.get('typeAnnotation.types.1').replaceWith(path.node.typeAnnotation.types[1].members[0].typeAnnotation.typeAnnotation);\n        }\n\n        if (types[2].type === 'TSTypeLiteral' && types[2].members[0].key.name === 'timelinerange') {\n          path.get('typeAnnotation.types.2').replaceWith(path.node.typeAnnotation.types[2].members[0].typeAnnotation.typeAnnotation);\n        }\n      } else if (\n        path.node.id.name === 'NoneOrCustomIdentList' &&\n        path.node.typeAnnotation.type === 'TSUnionType' &&\n        path.node.typeAnnotation.types[1].type === 'TSTypeLiteral' &&\n        path.node.typeAnnotation.types[1].members[0].key.name === 'idents'\n      ) {\n        path.get('typeAnnotation.types.1').replaceWith(path.node.typeAnnotation.types[1].members[0].typeAnnotation.typeAnnotation);\n      } else if (\n        path.node.id.name === 'ViewTransitionGroup' &&\n        path.node.typeAnnotation.type === 'TSUnionType' &&\n        path.node.typeAnnotation.types[3].type === 'TSTypeLiteral' &&\n        path.node.typeAnnotation.types[3].members[0].key.name === 'custom'\n      ) {\n        path.get('typeAnnotation.types.3').replaceWith(path.node.typeAnnotation.types[3].members[0].typeAnnotation.typeAnnotation);\n      } else if (\n        path.node.id.name === 'ViewTransitionName' &&\n        path.node.typeAnnotation.type === 'TSUnionType' &&\n        path.node.typeAnnotation.types[2].type === 'TSTypeLiteral' &&\n        path.node.typeAnnotation.types[2].members[0].key.name === 'custom'\n      ) {\n        path.get('typeAnnotation.types.2').replaceWith(path.node.typeAnnotation.types[2].members[0].typeAnnotation.typeAnnotation);\n      }\n    }\n  });\n\n  ts = recast.print(ast, {objectCurlySpacing: false}).code;\n  fs.writeFileSync('node/ast.d.ts', ts)\n});\n\nfunction process(binding) {\n  // Follow the references upward from the binding to add generics.\n  for (let reference of binding.referencePaths) {\n    if (reference.node !== binding.identifier) {\n      genericize(reference, binding.identifier.name);\n    }\n  }\n}\n\nfunction genericize(path, name, seen = new Set()) {\n  if (seen.has(path.node)) return;\n  seen.add(path.node);\n\n  // Find the parent declaration of the reference, and add a generic if needed.\n  let parent = path.findParent(p => p.isDeclaration());\n  if (!parent.node.typeParameters) {\n    parent.node.typeParameters = t.tsTypeParameterDeclaration([]);\n  }\n  let params = parent.get('typeParameters');\n  let param = params.node.params.find(p => p.default.typeName.name === name);\n  if (!param) {\n    params.pushContainer('params', t.tsTypeParameter(null, t.tsTypeReference(t.identifier(name)), name[0]));\n  }\n\n  // Replace the reference with the generic, or add a type parameter.\n  if (path.node.name === name) {\n    path.replaceWith(t.identifier(name[0]));\n  } else {\n    if (!path.parent.typeParameters) {\n      path.parent.typeParameters = t.tsTypeParameterInstantiation([]);\n    }\n    let param = path.parent.typeParameters.params.find(p => p.typeName.name === name[0]);\n    if (!param) {\n      path.parentPath.get('typeParameters').pushContainer('params', t.tsTypeReference(t.identifier(name[0])));\n    }\n  }\n\n  // Keep going to all references of this reference.\n  let binding = path.scope.getBinding(parent.node.id.name);\n  for (let reference of binding.referencePaths) {\n    if (reference.node !== binding.identifier) {\n      genericize(reference, name, seen);\n    }\n  }\n}\n"
  },
  {
    "path": "scripts/build-flow.js",
    "content": "const fs = require('fs');\nconst { compiler, beautify } = require('flowgen');\n\nlet dir = `${__dirname}/../`;\nlet contents = fs.readFileSync(dir + '/node/index.d.ts', 'utf8').replace('`${PropertyStart}${string}`', 'string');\ncontents = contents.replace(/`.*`/g, 'string');\ncontents = contents.replace(/(string & \\{\\})/g, 'string');\nlet index = beautify(compiler.compileDefinitionString(contents, { inexact: false, interfaceRecords: true }));\nindex = index.replace('{ code: any }', '{| code: any |}');\nindex = index.replace(/from \"(.*?)\";/g, 'from \"$1.js.flow\";');\n// This Exclude type isn't right at all, but idk how to get it working for real...\nfs.writeFileSync(dir + '/node/index.js.flow', '// @flow\\n\\ntype Exclude<A, B> = A;\\n' + index)\n\nlet ast = beautify(compiler.compileDefinitionFile(dir + '/node/ast.d.ts', { inexact: false }));\nfs.writeFileSync(dir + '/node/ast.js.flow', '// @flow\\n\\n' + ast)\n\nlet targets = beautify(compiler.compileDefinitionFile(dir + '/node/targets.d.ts', { inexact: false }));\nfs.writeFileSync(dir + '/node/targets.js.flow', '// @flow\\n\\n' + targets)\n"
  },
  {
    "path": "scripts/build-npm.js",
    "content": "const fs = require('fs');\nconst pkg = require('../package.json');\n\nconst dir = `${__dirname}/..`;\n\n// Add `libc` fields only to platforms that have libc(Standard C library).\nconst triples = [\n  {\n    name: 'x86_64-apple-darwin',\n  },\n  {\n    name: 'x86_64-unknown-linux-gnu',\n    libc: 'glibc',\n  },\n  {\n    name: 'x86_64-pc-windows-msvc',\n  },\n  {\n    name: 'aarch64-pc-windows-msvc'\n  },\n  {\n    name: 'aarch64-apple-darwin',\n  },\n  {\n    name: 'aarch64-unknown-linux-gnu',\n    libc: 'glibc',\n  },\n  {\n    name: 'armv7-unknown-linux-gnueabihf',\n  },\n  {\n    name: 'aarch64-unknown-linux-musl',\n    libc: 'musl',\n  },\n  {\n    name: 'x86_64-unknown-linux-musl',\n    libc: 'musl',\n  },\n  {\n    name: 'x86_64-unknown-freebsd'\n  },\n  {\n    name: 'aarch64-linux-android'\n  }\n];\nconst cpuToNodeArch = {\n  x86_64: 'x64',\n  aarch64: 'arm64',\n  i686: 'ia32',\n  armv7: 'arm',\n};\nconst sysToNodePlatform = {\n  linux: 'linux',\n  freebsd: 'freebsd',\n  darwin: 'darwin',\n  windows: 'win32',\n  android: 'android'\n};\n\nlet optionalDependencies = {};\nlet cliOptionalDependencies = {};\n\ntry {\n  fs.mkdirSync(dir + '/npm');\n} catch (err) { }\n\nfor (let triple of triples) {\n  // Add the libc field to package.json to avoid downloading both\n  // `gnu` and `musl` packages in Linux.\n  const libc = triple.libc;\n  let [cpu, , os, abi] = triple.name.split('-');\n  cpu = cpuToNodeArch[cpu] || cpu;\n  os = sysToNodePlatform[os] || os;\n\n  let t = `${os}-${cpu}`;\n  if (abi) {\n    t += '-' + abi;\n  }\n\n  buildNode(triple.name, cpu, os, libc, t);\n  buildCLI(triple.name, cpu, os, libc, t);\n}\n\npkg.optionalDependencies = optionalDependencies;\nfs.writeFileSync(`${dir}/package.json`, JSON.stringify(pkg, false, 2) + '\\n');\n\nlet cliPkg = { ...pkg };\ncliPkg.name += '-cli';\ncliPkg.bin = {\n  'lightningcss': 'lightningcss'\n};\ndelete cliPkg.main;\ndelete cliPkg.napi;\ndelete cliPkg.exports;\ndelete cliPkg.devDependencies;\ndelete cliPkg.targets;\ndelete cliPkg.types;\ncliPkg.files = ['lightningcss', 'postinstall.js'];\ncliPkg.optionalDependencies = cliOptionalDependencies;\ncliPkg.scripts = {\n  postinstall: 'node postinstall.js'\n};\n\nfs.writeFileSync(`${dir}/cli/package.json`, JSON.stringify(cliPkg, false, 2) + '\\n');\nfs.copyFileSync(`${dir}/README.md`, `${dir}/cli/README.md`);\nfs.copyFileSync(`${dir}/LICENSE`, `${dir}/cli/LICENSE`);\n\nfunction buildNode(triple, cpu, os, libc, t) {\n  let name = `lightningcss.${t}.node`;\n\n  let pkg2 = { ...pkg };\n  pkg2.name += '-' + t;\n  pkg2.os = [os];\n  pkg2.cpu = [cpu];\n  if (libc) {\n    pkg2.libc = [libc];\n  }\n  pkg2.main = name;\n  pkg2.files = [name];\n  delete pkg2.exports;\n  delete pkg2.napi;\n  delete pkg2.devDependencies;\n  delete pkg2.dependencies;\n  delete pkg2.optionalDependencies;\n  delete pkg2.targets;\n  delete pkg2.scripts;\n  delete pkg2.types;\n\n  optionalDependencies[pkg2.name] = pkg.version;\n\n  try {\n    fs.mkdirSync(dir + '/npm/node-' + t);\n  } catch (err) { }\n  fs.writeFileSync(`${dir}/npm/node-${t}/package.json`, JSON.stringify(pkg2, false, 2) + '\\n');\n  fs.copyFileSync(`${dir}/artifacts/bindings-${triple}/${name}`, `${dir}/npm/node-${t}/${name}`);\n  fs.writeFileSync(`${dir}/npm/node-${t}/README.md`, `This is the ${triple} build of lightningcss. See https://github.com/parcel-bundler/lightningcss for details.`);\n  fs.copyFileSync(`${dir}/LICENSE`, `${dir}/npm/node-${t}/LICENSE`);\n}\n\nfunction buildCLI(triple, cpu, os, libc, t) {\n  let binary = os === 'win32' ? 'lightningcss.exe' : 'lightningcss';\n  let pkg2 = { ...pkg };\n  pkg2.name += '-cli-' + t;\n  pkg2.os = [os];\n  pkg2.cpu = [cpu];\n  pkg2.files = [binary];\n  if (libc) {\n    pkg2.libc = [libc];\n  }\n  delete pkg2.main;\n  delete pkg2.exports;\n  delete pkg2.napi;\n  delete pkg2.devDependencies;\n  delete pkg2.dependencies;\n  delete pkg2.optionalDependencies;\n  delete pkg2.targets;\n  delete pkg2.scripts;\n  delete pkg2.types;\n\n  cliOptionalDependencies[pkg2.name] = pkg.version;\n\n  try {\n    fs.mkdirSync(dir + '/npm/cli-' + t);\n  } catch (err) { }\n  fs.writeFileSync(`${dir}/npm/cli-${t}/package.json`, JSON.stringify(pkg2, false, 2) + '\\n');\n  fs.copyFileSync(`${dir}/artifacts/bindings-${triple}/${binary}`, `${dir}/npm/cli-${t}/${binary}`);\n  fs.chmodSync(`${dir}/npm/cli-${t}/${binary}`, 0o755); // Ensure execute bit is set.\n  fs.writeFileSync(`${dir}/npm/cli-${t}/README.md`, `This is the ${triple} build of lightningcss-cli. See https://github.com/parcel-bundler/lightningcss for details.`);\n  fs.copyFileSync(`${dir}/LICENSE`, `${dir}/npm/cli-${t}/LICENSE`);\n}\n"
  },
  {
    "path": "scripts/build-prefixes.js",
    "content": "const { execSync } = require('child_process');\nconst prefixes = require('autoprefixer/data/prefixes');\nconst browsers = require('caniuse-lite').agents;\nconst unpack = require('caniuse-lite').feature;\nconst features = require('caniuse-lite').features;\nconst mdn = require('@mdn/browser-compat-data');\nconst fs = require('fs');\n\nconst BROWSER_MAPPING = {\n  and_chr: 'chrome',\n  and_ff: 'firefox',\n  ie_mob: 'ie',\n  op_mob: 'opera',\n  and_qq: null,\n  and_uc: null,\n  baidu: null,\n  bb: null,\n  kaios: null,\n  op_mini: null,\n  oculus: null,\n};\n\nconst MDN_BROWSER_MAPPING = {\n  chrome_android: 'chrome',\n  firefox_android: 'firefox',\n  opera_android: 'opera',\n  safari_ios: 'ios_saf',\n  webview_ios: 'ios_saf',\n  samsunginternet_android: 'samsung',\n  webview_android: 'android',\n  oculus: null,\n};\n\nconst latestBrowserVersions = {};\nfor (let b in browsers) {\n  let versions = browsers[b].versions.slice(-10);\n  for (let i = versions.length - 1; i >= 0; i--) {\n    if (versions[i] != null && versions[i] != \"all\" && versions[i] != \"TP\") {\n      latestBrowserVersions[b] = versions[i];\n      break;\n    }\n  }\n}\n\n// Caniuse data for clip-path is incorrect.\n// https://github.com/Fyrd/caniuse/issues/6209\nprefixes['clip-path'].browsers = prefixes['clip-path'].browsers.filter(b => {\n  let [name, version] = b.split(' ');\n  return !(\n    (name === 'safari' && parseVersion(version) >= (9 << 16 | 1 << 8)) ||\n    (name === 'ios_saf' && parseVersion(version) >= (9 << 16 | 3 << 8))\n  );\n});\n\nprefixes['any-pseudo'] = {\n  browsers: Object.entries(mdn.css.selectors.is.__compat.support)\n    .flatMap(([key, value]) => {\n      if (Array.isArray(value)) {\n        key = MDN_BROWSER_MAPPING[key] || key;\n        let any = value.find(v => v.alternative_name?.includes('-any'))?.version_added;\n        let supported = value.find(x => x.version_added && !x.alternative_name)?.version_added;\n        if (any && supported) {\n          let parts = supported.split('.');\n          parts[0]--;\n          supported = parts.join('.');\n          return [`${key} ${any}}`, `${key} ${supported}`];\n        }\n      }\n\n      return [];\n    })\n}\n\n// Safari 4-13 supports background-clip: text with a prefix.\nprefixes['background-clip'].browsers.push('safari 13');\nprefixes['background-clip'].browsers.push('ios_saf 4', 'ios_saf 13');\n\nlet flexSpec = {};\nlet oldGradient = {};\nlet p = new Map();\nfor (let prop in prefixes) {\n  let browserMap = {};\n  for (let b of prefixes[prop].browsers) {\n    let [name, version, variant] = b.split(' ');\n    if (BROWSER_MAPPING[name] === null) {\n      continue;\n    }\n    let prefix = browsers[name].prefix_exceptions?.[version] || browsers[name].prefix;\n\n    // https://github.com/postcss/autoprefixer/blob/main/lib/hacks/backdrop-filter.js#L11\n    if (prefix === 'ms' && prop === 'backdrop-filter') {\n      prefix = 'webkit';\n    }\n\n    let origName = name;\n    let isCurrentVersion = version === latestBrowserVersions[name];\n    name = BROWSER_MAPPING[name] || name;\n    let v = parseVersion(version);\n    if (v == null) {\n      console.log('BAD VERSION', prop, name, version);\n      continue;\n    }\n    if (browserMap[name]?.[prefix] == null) {\n      browserMap[name] = browserMap[name] || {};\n      browserMap[name][prefix] = prefixes[prop].browsers.filter(b => b.startsWith(origName) || b.startsWith(name)).length === 1\n        ? isCurrentVersion ? [null, null] : [null, v]\n        : isCurrentVersion ? [v, null] : [v, v];\n    } else {\n      if (v < browserMap[name][prefix][0]) {\n        browserMap[name][prefix][0] = v;\n      }\n\n      if (isCurrentVersion && browserMap[name][prefix][0] != null) {\n        browserMap[name][prefix][1] = null;\n      } else if (v > browserMap[name][prefix][1] && browserMap[name][prefix][1] != null) {\n        browserMap[name][prefix][1] = v;\n      }\n    }\n\n    if (variant === '2009') {\n      if (flexSpec[name] == null) {\n        flexSpec[name] = [v, v];\n      } else {\n        if (v < flexSpec[name][0]) {\n          flexSpec[name][0] = v;\n        }\n\n        if (v > flexSpec[name][1]) {\n          flexSpec[name][1] = v;\n        }\n      }\n    } else if (variant === 'old' && prop.includes('gradient')) {\n      if (oldGradient[name] == null) {\n        oldGradient[name] = [v, v];\n      } else {\n        if (v < oldGradient[name][0]) {\n          oldGradient[name][0] = v;\n        }\n\n        if (v > oldGradient[name][1]) {\n          oldGradient[name][1] = v;\n        }\n      }\n    }\n  }\n  addValue(p, browserMap, prop);\n}\n\n\nfunction addValue(map, value, prop) {\n  let s = JSON.stringify(value);\n  let found = false;\n  for (let [key, val] of map) {\n    if (JSON.stringify(val) === s) {\n      key.push(prop);\n      found = true;\n      break;\n    }\n  }\n  if (!found) {\n    map.set([prop], value);\n  }\n}\n\nlet cssFeatures = [\n  'css-sel2',\n  'css-sel3',\n  'css-gencontent',\n  'css-first-letter',\n  'css-first-line',\n  'css-in-out-of-range',\n  'form-validation',\n  'css-any-link',\n  'css-default-pseudo',\n  'css-dir-pseudo',\n  'css-focus-within',\n  'css-focus-visible',\n  'css-indeterminate-pseudo',\n  'css-matches-pseudo',\n  'css-optional-pseudo',\n  'css-placeholder-shown',\n  'dialog',\n  'fullscreen',\n  'css-marker-pseudo',\n  'css-placeholder',\n  'css-selection',\n  'css-case-insensitive',\n  'css-read-only-write',\n  'css-autofill',\n  'css-namespaces',\n  'shadowdomv1',\n  'css-rrggbbaa',\n  'css-nesting',\n  'css-not-sel-list',\n  'css-has',\n  'font-family-system-ui',\n  'extended-system-fonts',\n  'calc'\n];\n\nlet cssFeatureMappings = {\n  'css-dir-pseudo': 'DirSelector',\n  'css-rrggbbaa': 'HexAlphaColors',\n  'css-not-sel-list': 'NotSelectorList',\n  'css-has': 'HasSelector',\n  'css-matches-pseudo': 'IsSelector',\n  'css-sel2': 'Selectors2',\n  'css-sel3': 'Selectors3',\n  'calc': 'CalcFunction'\n};\n\nlet cssFeatureOverrides = {\n  // Safari supports the ::marker pseudo element, but only supports styling some properties.\n  // However this does not break using the selector itself, so ignore for our purposes.\n  // https://bugs.webkit.org/show_bug.cgi?id=204163\n  // https://github.com/parcel-bundler/lightningcss/issues/508\n  'css-marker-pseudo': {\n    safari: {\n      'y #1': 'y'\n    }\n  }\n};\n\nlet compat = new Map();\nfor (let feature of cssFeatures) {\n  let data = unpack(features[feature]);\n  let overrides = cssFeatureOverrides[feature];\n  let browserMap = {};\n  for (let name in data.stats) {\n    if (BROWSER_MAPPING[name] === null) {\n      continue;\n    }\n\n    name = BROWSER_MAPPING[name] || name;\n    let browserOverrides = overrides?.[name];\n    for (let version in data.stats[name]) {\n      let value = data.stats[name][version];\n      value = browserOverrides?.[value] || value;\n      if (value === 'y') {\n        let v = parseVersion(version);\n        if (v == null) {\n          console.log('BAD VERSION', feature, name, version);\n          continue;\n        }\n\n        if (browserMap[name] == null || v < browserMap[name]) {\n          browserMap[name] = v;\n        }\n      }\n    }\n  }\n\n  let name = (cssFeatureMappings[feature] || feature).replace(/^css-/, '');\n  addValue(compat, browserMap, name);\n}\n\n// No browser supports custom media queries yet.\naddValue(compat, {}, 'custom-media-queries');\n\nlet mdnFeatures = {\n  doublePositionGradients: mdn.css.types.gradient['radial-gradient'].doubleposition.__compat.support,\n  clampFunction: mdn.css.types.clamp.__compat.support,\n  placeSelf: mdn.css.properties['place-self'].__compat.support,\n  placeContent: mdn.css.properties['place-content'].__compat.support,\n  placeItems: mdn.css.properties['place-items'].__compat.support,\n  overflowShorthand: mdn.css.properties['overflow'].multiple_keywords.__compat.support,\n  mediaRangeSyntax: mdn.css['at-rules'].media.range_syntax.__compat.support,\n  mediaIntervalSyntax: Object.fromEntries(\n    Object.entries(mdn.css['at-rules'].media.range_syntax.__compat.support)\n      .map(([browser, value]) => {\n        // Firefox supported only ranges and not intervals for a while.\n        if (Array.isArray(value)) {\n          value = value.filter(v => !v.partial_implementation)\n        } else if (value.partial_implementation) {\n          value = undefined;\n        }\n\n        return [browser, value];\n      })\n  ),\n  logicalBorders: mdn.css.properties['border-inline-start'].__compat.support,\n  logicalBorderShorthand: mdn.css.properties['border-inline'].__compat.support,\n  logicalBorderRadius: mdn.css.properties['border-start-start-radius'].__compat.support,\n  logicalMargin: mdn.css.properties['margin-inline-start'].__compat.support,\n  logicalMarginShorthand: mdn.css.properties['margin-inline'].__compat.support,\n  logicalPadding: mdn.css.properties['padding-inline-start'].__compat.support,\n  logicalPaddingShorthand: mdn.css.properties['padding-inline'].__compat.support,\n  logicalInset: mdn.css.properties['inset-inline-start'].__compat.support,\n  logicalSize: mdn.css.properties['inline-size'].__compat.support,\n  logicalTextAlign: mdn.css.properties['text-align'].start.__compat.support,\n  labColors: mdn.css.types.color.lab.__compat.support,\n  oklabColors: mdn.css.types.color.oklab.__compat.support,\n  colorFunction: mdn.css.types.color.color.__compat.support,\n  spaceSeparatedColorNotation: mdn.css.types.color.rgb.space_separated_parameters.__compat.support,\n  highlight: mdn.css.selectors.highlight.__compat.support,\n  textDecorationThicknessPercent: mdn.css.properties['text-decoration-thickness'].percentage.__compat.support,\n  textDecorationThicknessShorthand: mdn.css.properties['text-decoration'].includes_thickness.__compat.support,\n  cue: mdn.css.selectors.cue.__compat.support,\n  cueFunction: mdn.css.selectors.cue.selector_argument.__compat.support,\n  anyPseudo: Object.fromEntries(\n    Object.entries(mdn.css.selectors.is.__compat.support)\n      .map(([key, value]) => {\n        if (Array.isArray(value)) {\n          value = value\n            .filter(v => v.alternative_name?.includes('-any'))\n            .map(({ alternative_name, ...other }) => other);\n        }\n\n        if (value && value.length) {\n          return [key, value];\n        } else {\n          return [key, { version_added: false }];\n        }\n      })\n  ),\n  partPseudo: mdn.css.selectors.part.__compat.support,\n  imageSet: mdn.css.types.image['image-set'].__compat.support,\n  xResolutionUnit: mdn.css.types.resolution.x.__compat.support,\n  nthChildOf: mdn.css.selectors['nth-child'].of_syntax.__compat.support,\n  minFunction: mdn.css.types.min.__compat.support,\n  maxFunction: mdn.css.types.max.__compat.support,\n  roundFunction: mdn.css.types.round.__compat.support,\n  remFunction: mdn.css.types.rem.__compat.support,\n  modFunction: mdn.css.types.mod.__compat.support,\n  absFunction: mdn.css.types.abs.__compat.support,\n  signFunction: mdn.css.types.sign.__compat.support,\n  hypotFunction: mdn.css.types.hypot.__compat.support,\n  gradientInterpolationHints: mdn.css.types.gradient['linear-gradient'].interpolation_hints.__compat.support,\n  borderImageRepeatRound: mdn.css.properties['border-image-repeat'].round.__compat.support,\n  borderImageRepeatSpace: mdn.css.properties['border-image-repeat'].space.__compat.support,\n  fontSizeRem: mdn.css.properties['font-size'].rem_values.__compat.support,\n  fontSizeXXXLarge: mdn.css.properties['font-size']['xxx-large'].__compat.support,\n  fontStyleObliqueAngle: mdn.css.properties['font-style']['oblique-angle'].__compat.support,\n  fontWeightNumber: mdn.css.properties['font-weight'].number.__compat.support,\n  fontStretchPercentage: mdn.css.properties['font-stretch'].percentage.__compat.support,\n  lightDark: mdn.css.types.color['light-dark'].__compat.support,\n  accentSystemColor: mdn.css.types.color['system-color'].accentcolor_accentcolortext.__compat.support,\n  animationTimelineShorthand: mdn.css.properties.animation['animation-timeline_included'].__compat.support,\n  viewTransition: mdn.css.selectors['view-transition'].__compat.support,\n  detailsContent: mdn.css.selectors['details-content'].__compat.support,\n  targetText: mdn.css.selectors['target-text'].__compat.support,\n  picker: mdn.css.selectors.picker.__compat.support,\n  pickerIcon: mdn.css.selectors['picker-icon'].__compat.support,\n  checkmark: mdn.css.selectors.checkmark.__compat.support,\n  grammarError: mdn.css.selectors['grammar-error'].__compat.support,\n  spellingError: mdn.css.selectors['spelling-error'].__compat.support,\n  statePseudoClass: Object.fromEntries(\n    Object.entries(mdn.css.selectors.state.__compat.support)\n      .map(([browser, value]) => {\n        // Chrome/Edge 90-124 supported old :--foo syntax which was removed.\n        // Only include full :state(foo) support from 125+.\n        if (Array.isArray(value)) {\n          value = value.filter(v => !v.partial_implementation)\n        } else if (value.partial_implementation) {\n          value = undefined;\n        }\n\n        return [browser, value];\n      })\n  ),\n};\n\nfor (let key in mdn.css.types.length) {\n  if (key === '__compat') {\n    continue;\n  }\n\n  let feat = key.includes('_')\n    ? key.replace(/_([a-z])/g, (_, l) => l.toUpperCase())\n    : key + 'Unit';\n\n  mdnFeatures[feat] = mdn.css.types.length[key].__compat.support;\n}\n\nfor (let key in mdn.css.types.gradient) {\n  if (key === '__compat') {\n    continue;\n  }\n\n  let feat = key.replace(/-([a-z])/g, (_, l) => l.toUpperCase());\n  mdnFeatures[feat] = mdn.css.types.gradient[key].__compat.support;\n}\n\nconst nonStandardListStyleType = new Set([\n  // https://developer.mozilla.org/en-US/docs/Web/CSS/list-style-type#non-standard_extensions\n  'ethiopic-halehame',\n  'ethiopic-halehame-am',\n  'ethiopic-halehame-ti-er',\n  'ethiopic-halehame-ti-et',\n  'hangul',\n  'hangul-consonant',\n  'urdu',\n  'cjk-ideographic',\n  // https://github.com/w3c/csswg-drafts/issues/135\n  'upper-greek'\n]);\n\nfor (let key in mdn.css.properties['list-style-type']) {\n  if (key === '__compat' || nonStandardListStyleType.has(key) || mdn.css.properties['list-style-type'][key].__compat.support.chrome.version_removed) {\n    continue;\n  }\n\n  let feat = key[0].toUpperCase() + key.slice(1).replace(/-([a-z])/g, (_, l) => l.toUpperCase()) + 'ListStyleType';\n  mdnFeatures[feat] = mdn.css.properties['list-style-type'][key].__compat.support;\n}\n\nfor (let key in mdn.css.properties['width']) {\n  if (key === '__compat' || key === 'is_animatable') {\n    continue;\n  }\n\n  let feat = key[0].toUpperCase() + key.slice(1).replace(/[-_]([a-z])/g, (_, l) => l.toUpperCase()) + 'Size';\n  mdnFeatures[feat] = mdn.css.properties['width'][key].__compat.support;\n}\n\nObject.entries(mdn.css.properties.width.stretch.__compat.support)\n  .filter(([, v]) => v.alternative_name)\n  .forEach(([k, v]) => {\n    let name = v.alternative_name.slice(1).replace(/[-_]([a-z])/g, (_, l) => l.toUpperCase()) + 'Size';\n    mdnFeatures[name] ??= {};\n    mdnFeatures[name][k] = {version_added: v.version_added};\n  });\n\nfor (let feature in mdnFeatures) {\n  let browserMap = {};\n  for (let name in mdnFeatures[feature]) {\n    if (MDN_BROWSER_MAPPING[name] === null) {\n      continue;\n    }\n\n    let feat = mdnFeatures[feature][name];\n    let version;\n    if (Array.isArray(feat)) {\n      version = feat.filter(x => x.version_added && !x.alternative_name && !x.flags).sort((a, b) => parseVersion(a.version_added) < parseVersion(b.version_added) ? -1 : 1)[0].version_added;\n    } else if (!feat.alternative_name && !feat.flags) {\n      version = feat.version_added;\n    }\n\n    if (!version) {\n      continue;\n    }\n\n    let v = parseVersion(version);\n    if (v == null) {\n      console.log('BAD VERSION', feature, name, version);\n      continue;\n    }\n\n    name = MDN_BROWSER_MAPPING[name] || name;\n    browserMap[name] = v;\n  }\n\n  addValue(compat, browserMap, feature);\n}\n\naddValue(compat, {\n  safari: parseVersion('10.1'),\n  ios_saf: parseVersion('10.3')\n}, 'p3Colors');\n\naddValue(compat, {\n  // https://github.com/WebKit/WebKit/commit/baed0d8b0abf366e1d9a6105dc378c59a5f21575\n  safari: parseVersion('10.1'),\n  ios_saf: parseVersion('10.3')\n}, 'LangSelectorList');\n\nlet prefixMapping = {\n  webkit: 'WebKit',\n  moz: 'Moz',\n  ms: 'Ms',\n  o: 'O'\n};\n\nlet flags = [\n  'Nesting',\n  'NotSelectorList',\n  'DirSelector',\n  'LangSelectorList',\n  'IsSelector',\n  'TextDecorationThicknessPercent',\n  'MediaIntervalSyntax',\n  'MediaRangeSyntax',\n  'CustomMediaQueries',\n  'ClampFunction',\n  'ColorFunction',\n  'OklabColors',\n  'LabColors',\n  'P3Colors',\n  'HexAlphaColors',\n  'SpaceSeparatedColorNotation',\n  'FontFamilySystemUi',\n  'DoublePositionGradients',\n  'VendorPrefixes',\n  'LogicalProperties',\n  'LightDark',\n  ['Selectors', ['Nesting', 'NotSelectorList', 'DirSelector', 'LangSelectorList', 'IsSelector']],\n  ['MediaQueries', ['MediaIntervalSyntax', 'MediaRangeSyntax', 'CustomMediaQueries']],\n  ['Colors', ['ColorFunction', 'OklabColors', 'LabColors', 'P3Colors', 'HexAlphaColors', 'SpaceSeparatedColorNotation', 'LightDark']],\n];\n\nlet enumify = (f) => f.replace(/^@([a-z])/, (_, x) => 'At' + x.toUpperCase()).replace(/^::([a-z])/, (_, x) => 'PseudoElement' + x.toUpperCase()).replace(/^:([a-z])/, (_, x) => 'PseudoClass' + x.toUpperCase()).replace(/(^|-)([a-z])/g, (_, a, x) => x.toUpperCase())\n\nlet allBrowsers = Object.keys(browsers).filter(b => !(b in BROWSER_MAPPING)).sort();\nlet browsersRs = `pub struct Browsers {\n  pub ${allBrowsers.join(': Option<u32>,\\n  pub ')}: Option<u32>\n}`;\nlet flagsRs = `pub struct Features: u32 {\n    ${flags.map((flag, i) => {\n      if (Array.isArray(flag)) {\n        return `const ${flag[0]} = ${flag[1].map(f => `Self::${f}.bits()`).join(' | ')};`\n      } else {\n        return `const ${flag} = 1 << ${i};`;\n      }\n    }).join('\\n    ')}\n  }`;\nlet targets = fs.readFileSync('src/targets.rs', 'utf8')\n  .replace(/pub struct Browsers \\{((?:.|\\n)+?)\\}/, browsersRs)\n  .replace(/pub struct Features: u32 \\{((?:.|\\n)+?)\\}/, flagsRs);\n\nfs.writeFileSync('src/targets.rs', targets);\nexecSync('rustfmt src/targets.rs');\n\nlet targets_dts = `// This file is autogenerated by build-prefixes.js. DO NOT EDIT!\n\nexport interface Targets {\n  ${allBrowsers.join('?: number,\\n  ')}?: number\n}\n\nexport const Features: {\n  ${flags.map((flag, i) => {\n    if (Array.isArray(flag)) {\n      return `${flag[0]}: ${flag[1].reduce((p, f) => p | (1 << flags.indexOf(f)), 0)},`\n    } else {\n      return `${flag}: ${1 << i},`;\n    }\n  }).join('\\n  ')}\n};\n`;\n\nfs.writeFileSync('node/targets.d.ts', targets_dts);\n\nlet flagsJs = `// This file is autogenerated by build-prefixes.js. DO NOT EDIT!\n\nexports.Features = {\n  ${flags.map((flag, i) => {\n    if (Array.isArray(flag)) {\n      return `${flag[0]}: ${flag[1].reduce((p, f) => p | (1 << flags.indexOf(f)), 0)},`\n    } else {\n      return `${flag}: ${1 << i},`;\n    }\n  }).join('\\n  ')}\n};\n`;\n\nfs.writeFileSync('node/flags.js', flagsJs);\n\nlet s = `// This file is autogenerated by build-prefixes.js. DO NOT EDIT!\n\nuse crate::vendor_prefix::VendorPrefix;\nuse crate::targets::Browsers;\n\n#[allow(dead_code)]\npub enum Feature {\n  ${[...p.keys()].flat().map(enumify).sort().join(',\\n  ')}\n}\n\nimpl Feature {\n  pub fn prefixes_for(&self, browsers: Browsers) -> VendorPrefix {\n    let mut prefixes = VendorPrefix::None;\n    match self {\n      ${[...p].map(([features, versions]) => {\n  return `${features.map(name => `Feature::${enumify(name)}`).join(' |\\n      ')} => {\n        ${Object.entries(versions).map(([name, prefixes]) => {\n          let needsVersion = !Object.values(prefixes).every(([min, max]) => min == null && max == null);\n    return `if ${needsVersion ? `let Some(version) = browsers.${name}` : `browsers.${name}.is_some()`} {\n          ${Object.entries(prefixes).map(([prefix, [min, max]]) => {\n      if (!prefixMapping[prefix]) {\n        throw new Error('Missing prefix ' + prefix);\n      }\n      let addPrefix = `prefixes |= VendorPrefix::${prefixMapping[prefix]};`;\n      let condition;\n      if (min == null && max == null) {\n        return addPrefix;\n      } else if (min == null) {\n        condition = `version <= ${max}`;\n      } else if (max == null) {\n        condition = `version >= ${min}`;\n      } else if (min == max) {\n        condition = `version == ${min}`;\n      } else {\n        condition = `version >= ${min} && version <= ${max}`;\n      }\n\n      return `if ${condition} {\n            ${addPrefix}\n          }`\n    }).join('\\n          ')}\n        }`;\n  }).join('\\n        ')}\n      }`\n}).join(',\\n      ')}\n    }\n    prefixes\n  }\n}\n\npub fn is_flex_2009(browsers: Browsers) -> bool {\n  ${Object.entries(flexSpec).map(([name, [min, max]]) => {\n  return `if let Some(version) = browsers.${name} {\n    if version >= ${min} && version <= ${max} {\n      return true;\n    }\n  }`;\n}).join('\\n  ')}\n  false\n}\n\npub fn is_webkit_gradient(browsers: Browsers) -> bool {\n  ${Object.entries(oldGradient).map(([name, [min, max]]) => {\n  return `if let Some(version) = browsers.${name} {\n    if version >= ${min} && version <= ${max} {\n      return true;\n    }\n  }`;\n}).join('\\n  ')}\n  false\n}\n`;\n\nfs.writeFileSync('src/prefixes.rs', s);\nexecSync('rustfmt src/prefixes.rs');\n\nlet c = `// This file is autogenerated by build-prefixes.js. DO NOT EDIT!\n\nuse crate::targets::Browsers;\n\n#[allow(dead_code)]\n#[derive(Clone, Copy, PartialEq)]\npub enum Feature {\n  ${[...compat.keys()].flat().map(enumify).sort().join(',\\n  ')}\n}\n\nimpl Feature {\n  pub fn is_compatible(&self, browsers: Browsers) -> bool {\n    match self {\n      ${[...compat].map(([features, supportedBrowsers]) =>\n  `${features.map(name => `Feature::${enumify(name)}`).join(' |\\n      ')} => {` + (Object.entries(supportedBrowsers).length === 0 ? '\\n        return false\\n      }' : `\n        ${Object.entries(supportedBrowsers).map(([browser, min]) =>\n    `if let Some(version) = browsers.${browser} {\n          if version < ${min} {\n            return false\n          }\n        }`).join('\\n        ')}${Object.keys(supportedBrowsers).length === allBrowsers.length ? '' : `\\n        if ${allBrowsers.filter(b => !supportedBrowsers[b]).map(browser => `browsers.${browser}.is_some()`).join(' || ')} {\n          return false\n        }`}\n      }`\n  )).join('\\n      ')}\n    }\n    true\n  }\n\n  pub fn is_partially_compatible(&self, targets: Browsers) -> bool {\n    let mut browsers = Browsers::default();\n    ${allBrowsers.map(browser => `if targets.${browser}.is_some() {\n      browsers.${browser} = targets.${browser};\n      if self.is_compatible(browsers) {\n        return true\n      }\n      #[allow(unused_assignments)]\n      {\n        browsers.${browser} = None;\n      }\n    }\\n`).join('    ')}\n    false\n  }\n}\n`;\n\nfs.writeFileSync('src/compat.rs', c);\nexecSync('rustfmt src/compat.rs');\n\n\nfunction parseVersion(version) {\n  version = version.replace('≤', '');\n  let [major, minor = '0', patch = '0'] = version\n    .split('-')[0]\n    .split('.')\n    .map(v => parseInt(v, 10));\n\n  if (isNaN(major) || isNaN(minor) || isNaN(patch)) {\n    return null;\n  }\n\n  return major << 16 | minor << 8 | patch;\n}\n"
  },
  {
    "path": "scripts/build-wasm.js",
    "content": "const esbuild = require('esbuild');\nconst exec = require('child_process').execSync;\nconst fs = require('fs');\nconst pkg = require('../package.json');\n\nconst dir = `${__dirname}/..`;\n\nlet b = fs.readFileSync(`${dir}/node/browserslistToTargets.js`, 'utf8');\nb = b.replace('module.exports = browserslistToTargets;', 'export {browserslistToTargets};');\nfs.writeFileSync(`${dir}/wasm/browserslistToTargets.js`, b);\n\nlet flags = fs.readFileSync(`${dir}/node/flags.js`, 'utf8');\nflags = flags.replace('exports.Features =', 'export const Features =');\nfs.writeFileSync(`${dir}/wasm/flags.js`, flags);\n\nlet composeVisitors = fs.readFileSync(`${dir}/node/composeVisitors.js`, 'utf8');\ncomposeVisitors = composeVisitors.replace('module.exports = composeVisitors', 'export { composeVisitors }');\nfs.writeFileSync(`${dir}/wasm/composeVisitors.js`, composeVisitors);\n\nlet dts = fs.readFileSync(`${dir}/node/index.d.ts`, 'utf8');\ndts = dts.replace(/: Buffer/g, ': Uint8Array');\ndts += `\n/** Initializes the web assembly module. */\nexport default function init(input?: string | URL | Request): Promise<void>;\n`;\nfs.writeFileSync(`${dir}/wasm/index.d.ts`, dts);\nfs.copyFileSync(`${dir}/node/targets.d.ts`, `${dir}/wasm/targets.d.ts`);\nfs.copyFileSync(`${dir}/node/ast.d.ts`, `${dir}/wasm/ast.d.ts`);\nfs.cpSync(`${dir}/node_modules/napi-wasm`, `${dir}/wasm/node_modules/napi-wasm`, {recursive: true});\n\nlet readme = fs.readFileSync(`${dir}/README.md`, 'utf8');\nreadme = readme.replace('# ⚡️ Lightning CSS', '# ⚡️ lightningcss-wasm');\nfs.writeFileSync(`${dir}/wasm/README.md`, readme);\n\nconst cjsBuild = {\n  entryPoints: [\n    `${dir}/wasm/wasm-node.mjs`,\n    `${dir}/wasm/index.mjs`,\n  ],\n  bundle: true,\n  format: 'cjs',\n  platform: 'node',\n  packages: 'external',\n  outdir: `${dir}/wasm`,\n  outExtension: { '.js': '.cjs' },\n  inject: [`${dir}/wasm/import.meta.url-polyfill.js`],\n  define: { 'import.meta.url': 'import_meta_url' },\n};\nesbuild.build(cjsBuild).catch(console.error);\n\nlet wasmPkg = { ...pkg };\nwasmPkg.name = 'lightningcss-wasm';\nwasmPkg.type = 'module';\nwasmPkg.main = 'index.mjs';\nwasmPkg.module = 'index.mjs';\nwasmPkg.exports = {\n  '.': {\n    types: './index.d.ts',\n    node: {\n      import: './wasm-node.mjs',\n      require: './wasm-node.cjs'\n    },\n    default: {\n      import: './index.mjs',\n      require: './index.cjs'\n    }\n  },\n  // Allow esbuild to import the wasm file\n  // without copying it in the src directory.\n  // Simplifies loading it in the browser when used in a library.\n  './lightningcss_node.wasm': './lightningcss_node.wasm'\n};\nwasmPkg.types = 'index.d.ts';\nwasmPkg.sideEffects = false;\nwasmPkg.files = ['*.js', '*.cjs', '*.mjs', '*.d.ts', '*.flow', '*.wasm'];\nwasmPkg.dependencies = {\n  'napi-wasm': pkg.devDependencies['napi-wasm']\n};\nwasmPkg.bundledDependencies = ['napi-wasm']; // for stackblitz\ndelete wasmPkg.napi;\ndelete wasmPkg.devDependencies;\ndelete wasmPkg.optionalDependencies;\ndelete wasmPkg.targets;\ndelete wasmPkg.scripts;\nfs.writeFileSync(`${dir}/wasm/package.json`, JSON.stringify(wasmPkg, false, 2) + '\\n');\n"
  },
  {
    "path": "scripts/build.js",
    "content": "const { spawn, execSync } = require('child_process');\n\nlet release = process.argv.includes('--release');\nbuild().catch((err) => {\n  console.error(err);\n  process.exit(1);\n});\n\nasync function build() {\n  if (process.platform === 'darwin') {\n    setupMacBuild();\n  }\n\n  await new Promise((resolve, reject) => {\n    let args = ['build', '--platform', '--cargo-cwd', 'node'];\n    if (release) {\n      args.push('--release');\n    }\n\n    if (process.env.RUST_TARGET) {\n      args.push('--target', process.env.RUST_TARGET);\n    }\n\n    let yarn = spawn('napi', args, {\n      stdio: 'inherit',\n      cwd: __dirname + '/../',\n      shell: true,\n    });\n\n    yarn.on('error', reject);\n    yarn.on('close', resolve);\n  });\n}\n\n// This forces Clang/LLVM to be used as a C compiler instead of GCC.\n// This is necessary for cross-compilation for Apple Silicon in GitHub Actions.\nfunction setupMacBuild() {\n  process.env.CC = execSync('xcrun -f clang', { encoding: 'utf8' }).trim();\n  process.env.CXX = execSync('xcrun -f clang++', { encoding: 'utf8' }).trim();\n\n  let sysRoot = execSync('xcrun --sdk macosx --show-sdk-path', {\n    encoding: 'utf8',\n  }).trim();\n  process.env.CFLAGS = `-isysroot ${sysRoot} -isystem ${sysRoot}`;\n  process.env.MACOSX_DEPLOYMENT_TARGET = '10.9';\n}\n"
  },
  {
    "path": "selectors/Cargo.toml",
    "content": "[package]\nname = \"parcel_selectors\"\nversion = \"0.28.2\"\nauthors = [\"The Servo Project Developers\"]\ndocumentation = \"https://docs.rs/parcel_selectors/\"\ndescription = \"CSS Selectors matching for Rust - forked for lightningcss\"\nrepository = \"https://github.com/parcel-bundler/lightningcss\"\nreadme = \"README.md\"\nkeywords = [\"css\", \"selectors\"]\nlicense = \"MPL-2.0\"\nbuild = \"build.rs\"\nedition = \"2021\"\n\n[lib]\nname = \"parcel_selectors\"\npath = \"lib.rs\"\n\n[features]\nbench = []\njsonschema = [\"serde\", \"schemars\"]\ninto_owned = [\"static-self\"]\nsmallvec = [\"static-self/smallvec\"]\nserde = [\"dep:serde\", \"smallvec/serde\"]\n\n[dependencies]\nbitflags = \"2.2.1\"\ncssparser = \"0.33.0\"\nrustc-hash = \"2\"\nlog = \"0.4\"\nphf = \"0.11.2\"\nprecomputed-hash = \"0.1\"\nsmallvec = \"1.0\"\nserde = { version = \"1.0.201\", features = [\"derive\"], optional = true }\nschemars = { version = \"0.8.19\", features = [\"smallvec\"], optional = true }\nstatic-self = { version = \"0.1.2\", path = \"../static-self\", optional = true }\n\n[build-dependencies]\nphf_codegen = \"0.11\"\n"
  },
  {
    "path": "selectors/LICENSE",
    "content": " Mozilla Public License Version 2.0\n==================================\n\n1. Definitions\n--------------\n\n1.1. \"Contributor\"\nmeans each individual or legal entity that creates, contributes to\nthe creation of, or owns Covered Software.\n\n1.2. \"Contributor Version\"\nmeans the combination of the Contributions of others (if any) used\nby a Contributor and that particular Contributor's Contribution.\n\n1.3. \"Contribution\"\nmeans Covered Software of a particular Contributor.\n\n1.4. \"Covered Software\"\nmeans Source Code Form to which the initial Contributor has attached\nthe notice in Exhibit A, the Executable Form of such Source Code\nForm, and Modifications of such Source Code Form, in each case\nincluding portions thereof.\n\n1.5. \"Incompatible With Secondary Licenses\"\nmeans\n\n(a) that the initial Contributor has attached the notice described\nin Exhibit B to the Covered Software; or\n\n(b) that the Covered Software was made available under the terms of\nversion 1.1 or earlier of the License, but not also under the\nterms of a Secondary License.\n\n1.6. \"Executable Form\"\nmeans any form of the work other than Source Code Form.\n\n1.7. \"Larger Work\"\nmeans a work that combines Covered Software with other material, in\na separate file or files, that is not Covered Software.\n\n1.8. \"License\"\nmeans this document.\n\n1.9. \"Licensable\"\nmeans having the right to grant, to the maximum extent possible,\nwhether at the time of the initial grant or subsequently, any and\nall of the rights conveyed by this License.\n\n1.10. \"Modifications\"\nmeans any of the following:\n\n(a) any file in Source Code Form that results from an addition to,\ndeletion from, or modification of the contents of Covered\nSoftware; or\n\n(b) any new file in Source Code Form that contains any Covered\nSoftware.\n\n1.11. \"Patent Claims\" of a Contributor\nmeans any patent claim(s), including without limitation, method,\nprocess, and apparatus claims, in any patent Licensable by such\nContributor that would be infringed, but for the grant of the\nLicense, by the making, using, selling, offering for sale, having\nmade, import, or transfer of either its Contributions or its\nContributor Version.\n\n1.12. \"Secondary License\"\nmeans either the GNU General Public License, Version 2.0, the GNU\nLesser General Public License, Version 2.1, the GNU Affero General\nPublic License, Version 3.0, or any later versions of those\nlicenses.\n\n1.13. \"Source Code Form\"\nmeans the form of the work preferred for making modifications.\n\n1.14. \"You\" (or \"Your\")\nmeans an individual or a legal entity exercising rights under this\nLicense. For legal entities, \"You\" includes any entity that\ncontrols, is controlled by, or is under common control with You. For\npurposes of this definition, \"control\" means (a) the power, direct\nor indirect, to cause the direction or management of such entity,\nwhether by contract or otherwise, or (b) ownership of more than\nfifty percent (50%) of the outstanding shares or beneficial\nownership of such entity.\n\n2. License Grants and Conditions\n--------------------------------\n\n2.1. Grants\n\nEach Contributor hereby grants You a world-wide, royalty-free,\nnon-exclusive license:\n\n(a) under intellectual property rights (other than patent or trademark)\nLicensable by such Contributor to use, reproduce, make available,\nmodify, display, perform, distribute, and otherwise exploit its\nContributions, either on an unmodified basis, with Modifications, or\nas part of a Larger Work; and\n\n(b) under Patent Claims of such Contributor to make, use, sell, offer\nfor sale, have made, import, and otherwise transfer either its\nContributions or its Contributor Version.\n\n2.2. Effective Date\n\nThe licenses granted in Section 2.1 with respect to any Contribution\nbecome effective for each Contribution on the date the Contributor first\ndistributes such Contribution.\n\n2.3. Limitations on Grant Scope\n\nThe licenses granted in this Section 2 are the only rights granted under\nthis License. No additional rights or licenses will be implied from the\ndistribution or licensing of Covered Software under this License.\nNotwithstanding Section 2.1(b) above, no patent license is granted by a\nContributor:\n\n(a) for any code that a Contributor has removed from Covered Software;\nor\n\n(b) for infringements caused by: (i) Your and any other third party's\nmodifications of Covered Software, or (ii) the combination of its\nContributions with other software (except as part of its Contributor\nVersion); or\n\n(c) under Patent Claims infringed by Covered Software in the absence of\nits Contributions.\n\nThis License does not grant any rights in the trademarks, service marks,\nor logos of any Contributor (except as may be necessary to comply with\nthe notice requirements in Section 3.4).\n\n2.4. Subsequent Licenses\n\nNo Contributor makes additional grants as a result of Your choice to\ndistribute the Covered Software under a subsequent version of this\nLicense (see Section 10.2) or under the terms of a Secondary License (if\npermitted under the terms of Section 3.3).\n\n2.5. Representation\n\nEach Contributor represents that the Contributor believes its\nContributions are its original creation(s) or it has sufficient rights\nto grant the rights to its Contributions conveyed by this License.\n\n2.6. Fair Use\n\nThis License is not intended to limit any rights You have under\napplicable copyright doctrines of fair use, fair dealing, or other\nequivalents.\n\n2.7. Conditions\n\nSections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted\nin Section 2.1.\n\n3. Responsibilities\n-------------------\n\n3.1. Distribution of Source Form\n\nAll distribution of Covered Software in Source Code Form, including any\nModifications that You create or to which You contribute, must be under\nthe terms of this License. You must inform recipients that the Source\nCode Form of the Covered Software is governed by the terms of this\nLicense, and how they can obtain a copy of this License. You may not\nattempt to alter or restrict the recipients' rights in the Source Code\nForm.\n\n3.2. Distribution of Executable Form\n\nIf You distribute Covered Software in Executable Form then:\n\n(a) such Covered Software must also be made available in Source Code\nForm, as described in Section 3.1, and You must inform recipients of\nthe Executable Form how they can obtain a copy of such Source Code\nForm by reasonable means in a timely manner, at a charge no more\nthan the cost of distribution to the recipient; and\n\n(b) You may distribute such Executable Form under the terms of this\nLicense, or sublicense it under different terms, provided that the\nlicense for the Executable Form does not attempt to limit or alter\nthe recipients' rights in the Source Code Form under this License.\n\n3.3. Distribution of a Larger Work\n\nYou may create and distribute a Larger Work under terms of Your choice,\nprovided that You also comply with the requirements of this License for\nthe Covered Software. If the Larger Work is a combination of Covered\nSoftware with a work governed by one or more Secondary Licenses, and the\nCovered Software is not Incompatible With Secondary Licenses, this\nLicense permits You to additionally distribute such Covered Software\nunder the terms of such Secondary License(s), so that the recipient of\nthe Larger Work may, at their option, further distribute the Covered\nSoftware under the terms of either this License or such Secondary\nLicense(s).\n\n3.4. Notices\n\nYou may not remove or alter the substance of any license notices\n(including copyright notices, patent notices, disclaimers of warranty,\nor limitations of liability) contained within the Source Code Form of\nthe Covered Software, except that You may alter any license notices to\nthe extent required to remedy known factual inaccuracies.\n\n3.5. Application of Additional Terms\n\nYou may choose to offer, and to charge a fee for, warranty, support,\nindemnity or liability obligations to one or more recipients of Covered\nSoftware. However, You may do so only on Your own behalf, and not on\nbehalf of any Contributor. You must make it absolutely clear that any\nsuch warranty, support, indemnity, or liability obligation is offered by\nYou alone, and You hereby agree to indemnify every Contributor for any\nliability incurred by such Contributor as a result of warranty, support,\nindemnity or liability terms You offer. You may include additional\ndisclaimers of warranty and limitations of liability specific to any\njurisdiction.\n\n4. Inability to Comply Due to Statute or Regulation\n---------------------------------------------------\n\nIf it is impossible for You to comply with any of the terms of this\nLicense with respect to some or all of the Covered Software due to\nstatute, judicial order, or regulation then You must: (a) comply with\nthe terms of this License to the maximum extent possible; and (b)\ndescribe the limitations and the code they affect. Such description must\nbe placed in a text file included with all distributions of the Covered\nSoftware under this License. Except to the extent prohibited by statute\nor regulation, such description must be sufficiently detailed for a\nrecipient of ordinary skill to be able to understand it.\n\n5. Termination\n--------------\n\n5.1. The rights granted under this License will terminate automatically\nif You fail to comply with any of its terms. However, if You become\ncompliant, then the rights granted under this License from a particular\nContributor are reinstated (a) provisionally, unless and until such\nContributor explicitly and finally terminates Your grants, and (b) on an\nongoing basis, if such Contributor fails to notify You of the\nnon-compliance by some reasonable means prior to 60 days after You have\ncome back into compliance. Moreover, Your grants from a particular\nContributor are reinstated on an ongoing basis if such Contributor\nnotifies You of the non-compliance by some reasonable means, this is the\nfirst time You have received notice of non-compliance with this License\nfrom such Contributor, and You become compliant prior to 30 days after\nYour receipt of the notice.\n\n5.2. If You initiate litigation against any entity by asserting a patent\ninfringement claim (excluding declaratory judgment actions,\ncounter-claims, and cross-claims) alleging that a Contributor Version\ndirectly or indirectly infringes any patent, then the rights granted to\nYou by any and all Contributors for the Covered Software under Section\n2.1 of this License shall terminate.\n\n5.3. In the event of termination under Sections 5.1 or 5.2 above, all\nend user license agreements (excluding distributors and resellers) which\nhave been validly granted by You or Your distributors under this License\nprior to termination shall survive termination.\n\n************************************************************************\n* *\n* 6. Disclaimer of Warranty *\n* ------------------------- *\n* *\n* Covered Software is provided under this License on an \"as is\" *\n* basis, without warranty of any kind, either expressed, implied, or *\n* statutory, including, without limitation, warranties that the *\n* Covered Software is free of defects, merchantable, fit for a *\n* particular purpose or non-infringing. The entire risk as to the *\n* quality and performance of the Covered Software is with You. *\n* Should any Covered Software prove defective in any respect, You *\n* (not any Contributor) assume the cost of any necessary servicing, *\n* repair, or correction. This disclaimer of warranty constitutes an *\n* essential part of this License. No use of any Covered Software is *\n* authorized under this License except under this disclaimer. *\n* *\n************************************************************************\n\n************************************************************************\n* *\n* 7. Limitation of Liability *\n* -------------------------- *\n* *\n* Under no circumstances and under no legal theory, whether tort *\n* (including negligence), contract, or otherwise, shall any *\n* Contributor, or anyone who distributes Covered Software as *\n* permitted above, be liable to You for any direct, indirect, *\n* special, incidental, or consequential damages of any character *\n* including, without limitation, damages for lost profits, loss of *\n* goodwill, work stoppage, computer failure or malfunction, or any *\n* and all other commercial damages or losses, even if such party *\n* shall have been informed of the possibility of such damages. This *\n* limitation of liability shall not apply to liability for death or *\n* personal injury resulting from such party's negligence to the *\n* extent applicable law prohibits such limitation. Some *\n* jurisdictions do not allow the exclusion or limitation of *\n* incidental or consequential damages, so this exclusion and *\n* limitation may not apply to You. *\n* *\n************************************************************************\n\n8. Litigation\n-------------\n\nAny litigation relating to this License may be brought only in the\ncourts of a jurisdiction where the defendant maintains its principal\nplace of business and such litigation shall be governed by laws of that\njurisdiction, without reference to its conflict-of-law provisions.\nNothing in this Section shall prevent a party's ability to bring\ncross-claims or counter-claims.\n\n9. Miscellaneous\n----------------\n\nThis License represents the complete agreement concerning the subject\nmatter hereof. If any provision of this License is held to be\nunenforceable, such provision shall be reformed only to the extent\nnecessary to make it enforceable. Any law or regulation which provides\nthat the language of a contract shall be construed against the drafter\nshall not be used to construe this License against a Contributor.\n\n10. Versions of the License\n---------------------------\n\n10.1. New Versions\n\nMozilla Foundation is the license steward. Except as provided in Section\n10.3, no one other than the license steward has the right to modify or\npublish new versions of this License. Each version will be given a\ndistinguishing version number.\n\n10.2. Effect of New Versions\n\nYou may distribute the Covered Software under the terms of the version\nof the License under which You originally received the Covered Software,\nor under the terms of any subsequent version published by the license\nsteward.\n\n10.3. Modified Versions\n\nIf you create software not governed by this License, and you want to\ncreate a new license for such software, you may create and use a\nmodified version of this License if you rename the license and remove\nany references to the name of the license steward (except to note that\nsuch modified license differs from this License).\n\n10.4. Distributing Source Code Form that is Incompatible With Secondary\nLicenses\n\nIf You choose to distribute Source Code Form that is Incompatible With\nSecondary Licenses under the terms of this version of the License, the\nnotice described in Exhibit B of this License must be attached.\n\nExhibit A - Source Code Form License Notice\n-------------------------------------------\n\nThis Source Code Form is subject to the terms of the Mozilla Public\nLicense, v. 2.0. If a copy of the MPL was not distributed with this\nfile, You can obtain one at https://mozilla.org/MPL/2.0/.\n\nIf it is not possible or desirable to put the notice in a particular\nfile, then You may include the notice in a location (such as a LICENSE\nfile in a relevant directory) where a recipient would be likely to look\nfor such a notice.\n\nYou may add additional accurate notices of copyright ownership.\n\nExhibit B - \"Incompatible With Secondary Licenses\" Notice\n---------------------------------------------------------\n\nThis Source Code Form is \"Incompatible With Secondary Licenses\", as\ndefined by the Mozilla Public License, v. 2.0.\n\n"
  },
  {
    "path": "selectors/README.md",
    "content": "rust-selectors\n==============\n\nThis is a fork of the `selectors` crate, updated to use the latest version of `cssparser`.\n\n* [![Build Status](https://travis-ci.com/servo/rust-selectors.svg?branch=master)](\n  https://travis-ci.com/servo/rust-selectors)\n* [Documentation](https://docs.rs/selectors/)\n* [crates.io](https://crates.io/crates/selectors)\n\nCSS Selectors library for Rust.\nIncludes parsing and serialization of selectors,\nas well as matching against a generic tree of elements.\nPseudo-elements and most pseudo-classes are generic as well.\n\n**Warning:** breaking changes are made to this library fairly frequently\n(13 times in 2016, for example).\nHowever you can use this crate without updating it that often,\nold versions stay available on crates.io and Cargo will only automatically update\nto versions that are numbered as compatible.\n\nTo see how to use this library with your own tree representation,\nsee [Kuchiki’s `src/select.rs`](https://github.com/kuchiki-rs/kuchiki/blob/master/src/select.rs).\n(Note however that Kuchiki is not always up to date with the latest rust-selectors version,\nso that code may need to be tweaked.)\nIf you don’t already have a tree data structure,\nconsider using [Kuchiki](https://github.com/kuchiki-rs/kuchiki) itself.\n"
  },
  {
    "path": "selectors/attr.rs",
    "content": "/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at https://mozilla.org/MPL/2.0/. */\n\nuse crate::parser::SelectorImpl;\nuse cssparser::ToCss;\nuse std::fmt;\n\n#[derive(Clone, Eq, PartialEq, Hash)]\npub struct AttrSelectorWithOptionalNamespace<'i, Impl: SelectorImpl<'i>> {\n  pub namespace: Option<NamespaceConstraint<(Impl::NamespacePrefix, Impl::NamespaceUrl)>>,\n  pub local_name: Impl::LocalName,\n  pub local_name_lower: Impl::LocalName,\n  pub operation: ParsedAttrSelectorOperation<Impl::AttrValue>,\n  pub never_matches: bool,\n}\n\n#[cfg(feature = \"into_owned\")]\nimpl<'any, 'i, Impl: SelectorImpl<'i>, NewSel> static_self::IntoOwned<'any>\n  for AttrSelectorWithOptionalNamespace<'i, Impl>\nwhere\n  Impl: static_self::IntoOwned<'any, Owned = NewSel>,\n  NewSel: SelectorImpl<'any>,\n  Impl::LocalName: static_self::IntoOwned<'any, Owned = NewSel::LocalName>,\n  Impl::NamespacePrefix: static_self::IntoOwned<'any, Owned = NewSel::NamespacePrefix>,\n  Impl::NamespaceUrl: static_self::IntoOwned<'any, Owned = NewSel::NamespaceUrl>,\n  Impl::AttrValue: static_self::IntoOwned<'any, Owned = NewSel::AttrValue>,\n{\n  type Owned = AttrSelectorWithOptionalNamespace<'any, NewSel>;\n\n  fn into_owned(self) -> Self::Owned {\n    AttrSelectorWithOptionalNamespace {\n      namespace: self.namespace.into_owned(),\n      local_name: self.local_name.into_owned(),\n      local_name_lower: self.local_name_lower.into_owned(),\n      operation: self.operation.into_owned(),\n      never_matches: self.never_matches,\n    }\n  }\n}\n\nimpl<'i, Impl: SelectorImpl<'i>> AttrSelectorWithOptionalNamespace<'i, Impl> {\n  pub fn namespace(&self) -> Option<NamespaceConstraint<&Impl::NamespaceUrl>> {\n    self.namespace.as_ref().map(|ns| match ns {\n      NamespaceConstraint::Any => NamespaceConstraint::Any,\n      NamespaceConstraint::Specific((_, ref url)) => NamespaceConstraint::Specific(url),\n    })\n  }\n}\n\n#[derive(Clone, Eq, PartialEq, Hash)]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(\n  feature = \"jsonschema\",\n  derive(schemars::JsonSchema),\n  schemars(rename = \"NamespaceConstraint\")\n)]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum NamespaceConstraint<NamespaceUrl> {\n  Any,\n\n  /// Empty string for no namespace\n  Specific(NamespaceUrl),\n}\n\n#[derive(Clone, Eq, PartialEq, Hash)]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum ParsedAttrSelectorOperation<AttrValue> {\n  Exists,\n  WithValue {\n    operator: AttrSelectorOperator,\n    case_sensitivity: ParsedCaseSensitivity,\n    expected_value: AttrValue,\n  },\n}\n\npub enum AttrSelectorOperation<AttrValue> {\n  Exists,\n  WithValue {\n    operator: AttrSelectorOperator,\n    case_sensitivity: CaseSensitivity,\n    expected_value: AttrValue,\n  },\n}\n\nimpl<AttrValue> AttrSelectorOperation<AttrValue> {\n  pub fn eval_str(&self, element_attr_value: &str) -> bool\n  where\n    AttrValue: AsRef<str>,\n  {\n    match *self {\n      AttrSelectorOperation::Exists => true,\n      AttrSelectorOperation::WithValue {\n        operator,\n        case_sensitivity,\n        ref expected_value,\n      } => operator.eval_str(element_attr_value, expected_value.as_ref(), case_sensitivity),\n    }\n  }\n}\n\n#[derive(Clone, Copy, Eq, PartialEq, Hash)]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum AttrSelectorOperator {\n  Equal,\n  Includes,\n  DashMatch,\n  Prefix,\n  Substring,\n  Suffix,\n}\n\nimpl ToCss for AttrSelectorOperator {\n  fn to_css<W>(&self, dest: &mut W) -> fmt::Result\n  where\n    W: fmt::Write,\n  {\n    // https://drafts.csswg.org/cssom/#serializing-selectors\n    // See \"attribute selector\".\n    dest.write_str(match *self {\n      AttrSelectorOperator::Equal => \"=\",\n      AttrSelectorOperator::Includes => \"~=\",\n      AttrSelectorOperator::DashMatch => \"|=\",\n      AttrSelectorOperator::Prefix => \"^=\",\n      AttrSelectorOperator::Substring => \"*=\",\n      AttrSelectorOperator::Suffix => \"$=\",\n    })\n  }\n}\n\nimpl AttrSelectorOperator {\n  pub fn eval_str(\n    self,\n    element_attr_value: &str,\n    attr_selector_value: &str,\n    case_sensitivity: CaseSensitivity,\n  ) -> bool {\n    let e = element_attr_value.as_bytes();\n    let s = attr_selector_value.as_bytes();\n    let case = case_sensitivity;\n    match self {\n      AttrSelectorOperator::Equal => case.eq(e, s),\n      AttrSelectorOperator::Prefix => e.len() >= s.len() && case.eq(&e[..s.len()], s),\n      AttrSelectorOperator::Suffix => e.len() >= s.len() && case.eq(&e[(e.len() - s.len())..], s),\n      AttrSelectorOperator::Substring => case.contains(element_attr_value, attr_selector_value),\n      AttrSelectorOperator::Includes => element_attr_value\n        .split(SELECTOR_WHITESPACE)\n        .any(|part| case.eq(part.as_bytes(), s)),\n      AttrSelectorOperator::DashMatch => {\n        case.eq(e, s) || (e.get(s.len()) == Some(&b'-') && case.eq(&e[..s.len()], s))\n      }\n    }\n  }\n}\n\n/// The definition of whitespace per CSS Selectors Level 3 § 4.\npub static SELECTOR_WHITESPACE: &[char] = &[' ', '\\t', '\\n', '\\r', '\\x0C'];\n\n#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum ParsedCaseSensitivity {\n  // 's' was specified.\n  ExplicitCaseSensitive,\n  // 'i' was specified.\n  AsciiCaseInsensitive,\n  // No flags were specified and HTML says this is a case-sensitive attribute.\n  CaseSensitive,\n  // No flags were specified and HTML says this is a case-insensitive attribute.\n  AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument,\n}\n\nimpl Default for ParsedCaseSensitivity {\n  fn default() -> Self {\n    ParsedCaseSensitivity::CaseSensitive\n  }\n}\n\nimpl ParsedCaseSensitivity {\n  pub fn to_unconditional(self, is_html_element_in_html_document: bool) -> CaseSensitivity {\n    match self {\n      ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument\n        if is_html_element_in_html_document =>\n      {\n        CaseSensitivity::AsciiCaseInsensitive\n      }\n      ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument => CaseSensitivity::CaseSensitive,\n      ParsedCaseSensitivity::CaseSensitive | ParsedCaseSensitivity::ExplicitCaseSensitive => {\n        CaseSensitivity::CaseSensitive\n      }\n      ParsedCaseSensitivity::AsciiCaseInsensitive => CaseSensitivity::AsciiCaseInsensitive,\n    }\n  }\n}\n\n#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]\npub enum CaseSensitivity {\n  CaseSensitive,\n  AsciiCaseInsensitive,\n}\n\nimpl CaseSensitivity {\n  pub fn eq(self, a: &[u8], b: &[u8]) -> bool {\n    match self {\n      CaseSensitivity::CaseSensitive => a == b,\n      CaseSensitivity::AsciiCaseInsensitive => a.eq_ignore_ascii_case(b),\n    }\n  }\n\n  pub fn contains(self, haystack: &str, needle: &str) -> bool {\n    match self {\n      CaseSensitivity::CaseSensitive => haystack.contains(needle),\n      CaseSensitivity::AsciiCaseInsensitive => {\n        if let Some((&n_first_byte, n_rest)) = needle.as_bytes().split_first() {\n          haystack.bytes().enumerate().any(|(i, byte)| {\n            if !byte.eq_ignore_ascii_case(&n_first_byte) {\n              return false;\n            }\n            let after_this_byte = &haystack.as_bytes()[i + 1..];\n            match after_this_byte.get(..n_rest.len()) {\n              None => false,\n              Some(haystack_slice) => haystack_slice.eq_ignore_ascii_case(n_rest),\n            }\n          })\n        } else {\n          // any_str.contains(\"\") == true,\n          // though these cases should be handled with *NeverMatches and never go here.\n          true\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "selectors/bloom.rs",
    "content": "/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at https://mozilla.org/MPL/2.0/. */\n\n//! Counting and non-counting Bloom filters tuned for use as ancestor filters\n//! for selector matching.\n\nuse std::fmt::{self, Debug};\n\n// The top 8 bits of the 32-bit hash value are not used by the bloom filter.\n// Consumers may rely on this to pack hashes more efficiently.\npub const BLOOM_HASH_MASK: u32 = 0x00ffffff;\nconst KEY_SIZE: usize = 12;\n\nconst ARRAY_SIZE: usize = 1 << KEY_SIZE;\nconst KEY_MASK: u32 = (1 << KEY_SIZE) - 1;\n\n/// A counting Bloom filter with 8-bit counters.\npub type BloomFilter = CountingBloomFilter<BloomStorageU8>;\n\n/// A counting Bloom filter with parameterized storage to handle\n/// counters of different sizes.  For now we assume that having two hash\n/// functions is enough, but we may revisit that decision later.\n///\n/// The filter uses an array with 2**KeySize entries.\n///\n/// Assuming a well-distributed hash function, a Bloom filter with\n/// array size M containing N elements and\n/// using k hash function has expected false positive rate exactly\n///\n/// $  (1 - (1 - 1/M)^{kN})^k  $\n///\n/// because each array slot has a\n///\n/// $  (1 - 1/M)^{kN}  $\n///\n/// chance of being 0, and the expected false positive rate is the\n/// probability that all of the k hash functions will hit a nonzero\n/// slot.\n///\n/// For reasonable assumptions (M large, kN large, which should both\n/// hold if we're worried about false positives) about M and kN this\n/// becomes approximately\n///\n/// $$  (1 - \\exp(-kN/M))^k   $$\n///\n/// For our special case of k == 2, that's $(1 - \\exp(-2N/M))^2$,\n/// or in other words\n///\n/// $$    N/M = -0.5 * \\ln(1 - \\sqrt(r))   $$\n///\n/// where r is the false positive rate.  This can be used to compute\n/// the desired KeySize for a given load N and false positive rate r.\n///\n/// If N/M is assumed small, then the false positive rate can\n/// further be approximated as 4*N^2/M^2.  So increasing KeySize by\n/// 1, which doubles M, reduces the false positive rate by about a\n/// factor of 4, and a false positive rate of 1% corresponds to\n/// about M/N == 20.\n///\n/// What this means in practice is that for a few hundred keys using a\n/// KeySize of 12 gives false positive rates on the order of 0.25-4%.\n///\n/// Similarly, using a KeySize of 10 would lead to a 4% false\n/// positive rate for N == 100 and to quite bad false positive\n/// rates for larger N.\n#[derive(Clone, Default)]\npub struct CountingBloomFilter<S>\nwhere\n  S: BloomStorage,\n{\n  storage: S,\n}\n\nimpl<S> CountingBloomFilter<S>\nwhere\n  S: BloomStorage,\n{\n  /// Creates a new bloom filter.\n  #[inline]\n  pub fn new() -> Self {\n    Default::default()\n  }\n\n  #[inline]\n  pub fn clear(&mut self) {\n    self.storage = Default::default();\n  }\n\n  // Slow linear accessor to make sure the bloom filter is zeroed. This should\n  // never be used in release builds.\n  #[cfg(debug_assertions)]\n  pub fn is_zeroed(&self) -> bool {\n    self.storage.is_zeroed()\n  }\n\n  #[cfg(not(debug_assertions))]\n  pub fn is_zeroed(&self) -> bool {\n    unreachable!()\n  }\n\n  /// Inserts an item with a particular hash into the bloom filter.\n  #[inline]\n  pub fn insert_hash(&mut self, hash: u32) {\n    self.storage.adjust_first_slot(hash, true);\n    self.storage.adjust_second_slot(hash, true);\n  }\n\n  /// Removes an item with a particular hash from the bloom filter.\n  #[inline]\n  pub fn remove_hash(&mut self, hash: u32) {\n    self.storage.adjust_first_slot(hash, false);\n    self.storage.adjust_second_slot(hash, false);\n  }\n\n  /// Check whether the filter might contain an item with the given hash.\n  /// This can sometimes return true even if the item is not in the filter,\n  /// but will never return false for items that are actually in the filter.\n  #[inline]\n  pub fn might_contain_hash(&self, hash: u32) -> bool {\n    !self.storage.first_slot_is_empty(hash) && !self.storage.second_slot_is_empty(hash)\n  }\n}\n\nimpl<S> Debug for CountingBloomFilter<S>\nwhere\n  S: BloomStorage,\n{\n  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n    let mut slots_used = 0;\n    for i in 0..ARRAY_SIZE {\n      if !self.storage.slot_is_empty(i) {\n        slots_used += 1;\n      }\n    }\n    write!(f, \"BloomFilter({}/{})\", slots_used, ARRAY_SIZE)\n  }\n}\n\npub trait BloomStorage: Clone + Default {\n  fn slot_is_empty(&self, index: usize) -> bool;\n  fn adjust_slot(&mut self, index: usize, increment: bool);\n  fn is_zeroed(&self) -> bool;\n\n  #[inline]\n  fn first_slot_is_empty(&self, hash: u32) -> bool {\n    self.slot_is_empty(Self::first_slot_index(hash))\n  }\n\n  #[inline]\n  fn second_slot_is_empty(&self, hash: u32) -> bool {\n    self.slot_is_empty(Self::second_slot_index(hash))\n  }\n\n  #[inline]\n  fn adjust_first_slot(&mut self, hash: u32, increment: bool) {\n    self.adjust_slot(Self::first_slot_index(hash), increment)\n  }\n\n  #[inline]\n  fn adjust_second_slot(&mut self, hash: u32, increment: bool) {\n    self.adjust_slot(Self::second_slot_index(hash), increment)\n  }\n\n  #[inline]\n  fn first_slot_index(hash: u32) -> usize {\n    hash1(hash) as usize\n  }\n\n  #[inline]\n  fn second_slot_index(hash: u32) -> usize {\n    hash2(hash) as usize\n  }\n}\n\n/// Storage class for a CountingBloomFilter that has 8-bit counters.\npub struct BloomStorageU8 {\n  counters: [u8; ARRAY_SIZE],\n}\n\nimpl BloomStorage for BloomStorageU8 {\n  #[inline]\n  fn adjust_slot(&mut self, index: usize, increment: bool) {\n    let slot = &mut self.counters[index];\n    if *slot != 0xff {\n      // full\n      if increment {\n        *slot += 1;\n      } else {\n        *slot -= 1;\n      }\n    }\n  }\n\n  #[inline]\n  fn slot_is_empty(&self, index: usize) -> bool {\n    self.counters[index] == 0\n  }\n\n  #[inline]\n  fn is_zeroed(&self) -> bool {\n    self.counters.iter().all(|x| *x == 0)\n  }\n}\n\nimpl Default for BloomStorageU8 {\n  fn default() -> Self {\n    BloomStorageU8 {\n      counters: [0; ARRAY_SIZE],\n    }\n  }\n}\n\nimpl Clone for BloomStorageU8 {\n  fn clone(&self) -> Self {\n    BloomStorageU8 {\n      counters: self.counters,\n    }\n  }\n}\n\n/// Storage class for a CountingBloomFilter that has 1-bit counters.\npub struct BloomStorageBool {\n  counters: [u8; ARRAY_SIZE / 8],\n}\n\nimpl BloomStorage for BloomStorageBool {\n  #[inline]\n  fn adjust_slot(&mut self, index: usize, increment: bool) {\n    let bit = 1 << (index % 8);\n    let byte = &mut self.counters[index / 8];\n\n    // Since we have only one bit for storage, decrementing it\n    // should never do anything.  Assert against an accidental\n    // decrementing of a bit that was never set.\n    assert!(\n      increment || (*byte & bit) != 0,\n      \"should not decrement if slot is already false\"\n    );\n\n    if increment {\n      *byte |= bit;\n    }\n  }\n\n  #[inline]\n  fn slot_is_empty(&self, index: usize) -> bool {\n    let bit = 1 << (index % 8);\n    (self.counters[index / 8] & bit) == 0\n  }\n\n  #[inline]\n  fn is_zeroed(&self) -> bool {\n    self.counters.iter().all(|x| *x == 0)\n  }\n}\n\nimpl Default for BloomStorageBool {\n  fn default() -> Self {\n    BloomStorageBool {\n      counters: [0; ARRAY_SIZE / 8],\n    }\n  }\n}\n\nimpl Clone for BloomStorageBool {\n  fn clone(&self) -> Self {\n    BloomStorageBool {\n      counters: self.counters,\n    }\n  }\n}\n\n#[inline]\nfn hash1(hash: u32) -> u32 {\n  hash & KEY_MASK\n}\n\n#[inline]\nfn hash2(hash: u32) -> u32 {\n  (hash >> KEY_SIZE) & KEY_MASK\n}\n\n#[test]\nfn create_and_insert_some_stuff() {\n  use rustc_hash::FxHasher;\n  use std::hash::{Hash, Hasher};\n  use std::mem::transmute;\n\n  fn hash_as_str(i: usize) -> u32 {\n    let mut hasher = FxHasher::default();\n    let s = i.to_string();\n    s.hash(&mut hasher);\n    let hash: u64 = hasher.finish();\n    (hash >> 32) as u32 ^ (hash as u32)\n  }\n\n  let mut bf = BloomFilter::new();\n\n  // Statically assert that ARRAY_SIZE is a multiple of 8, which\n  // BloomStorageBool relies on.\n  unsafe {\n    transmute::<[u8; ARRAY_SIZE % 8], [u8; 0]>([]);\n  }\n\n  for i in 0_usize..1000 {\n    bf.insert_hash(hash_as_str(i));\n  }\n\n  for i in 0_usize..1000 {\n    assert!(bf.might_contain_hash(hash_as_str(i)));\n  }\n\n  let false_positives = (1001_usize..2000).filter(|i| bf.might_contain_hash(hash_as_str(*i))).count();\n\n  assert!(false_positives < 190, \"{} is not < 190\", false_positives); // 19%.\n\n  for i in 0_usize..100 {\n    bf.remove_hash(hash_as_str(i));\n  }\n\n  for i in 100_usize..1000 {\n    assert!(bf.might_contain_hash(hash_as_str(i)));\n  }\n\n  let false_positives = (0_usize..100).filter(|i| bf.might_contain_hash(hash_as_str(*i))).count();\n\n  assert!(false_positives < 20, \"{} is not < 20\", false_positives); // 20%.\n\n  bf.clear();\n\n  for i in 0_usize..2000 {\n    assert!(!bf.might_contain_hash(hash_as_str(i)));\n  }\n}\n\n#[cfg(feature = \"bench\")]\n#[cfg(test)]\nmod bench {\n  extern crate test;\n  use super::BloomFilter;\n\n  #[derive(Default)]\n  struct HashGenerator(u32);\n\n  impl HashGenerator {\n    fn next(&mut self) -> u32 {\n      // Each hash is split into two twelve-bit segments, which are used\n      // as an index into an array. We increment each by 64 so that we\n      // hit the next cache line, and then another 1 so that our wrapping\n      // behavior leads us to different entries each time.\n      //\n      // Trying to simulate cold caches is rather difficult with the cargo\n      // benchmarking setup, so it may all be moot depending on the number\n      // of iterations that end up being run. But we might as well.\n      self.0 += (65) + (65 << super::KEY_SIZE);\n      self.0\n    }\n  }\n\n  #[bench]\n  fn create_insert_1000_remove_100_lookup_100(b: &mut test::Bencher) {\n    b.iter(|| {\n      let mut gen1 = HashGenerator::default();\n      let mut gen2 = HashGenerator::default();\n      let mut bf = BloomFilter::new();\n      for _ in 0_usize..1000 {\n        bf.insert_hash(gen1.next());\n      }\n      for _ in 0_usize..100 {\n        bf.remove_hash(gen2.next());\n      }\n      for _ in 100_usize..200 {\n        test::black_box(bf.might_contain_hash(gen2.next()));\n      }\n    });\n  }\n\n  #[bench]\n  fn might_contain_10(b: &mut test::Bencher) {\n    let bf = BloomFilter::new();\n    let mut gen = HashGenerator::default();\n    b.iter(|| {\n      for _ in 0..10 {\n        test::black_box(bf.might_contain_hash(gen.next()));\n      }\n    });\n  }\n\n  #[bench]\n  fn clear(b: &mut test::Bencher) {\n    let mut bf = Box::new(BloomFilter::new());\n    b.iter(|| test::black_box(&mut bf).clear());\n  }\n\n  #[bench]\n  fn insert_10(b: &mut test::Bencher) {\n    let mut bf = BloomFilter::new();\n    let mut gen = HashGenerator::default();\n    b.iter(|| {\n      for _ in 0..10 {\n        test::black_box(bf.insert_hash(gen.next()));\n      }\n    });\n  }\n\n  #[bench]\n  fn remove_10(b: &mut test::Bencher) {\n    let mut bf = BloomFilter::new();\n    let mut gen = HashGenerator::default();\n    // Note: this will underflow, and that's ok.\n    b.iter(|| {\n      for _ in 0..10 {\n        bf.remove_hash(gen.next())\n      }\n    });\n  }\n}\n"
  },
  {
    "path": "selectors/build.rs",
    "content": "/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at https://mozilla.org/MPL/2.0/. */\n\nuse std::env;\nuse std::fs::File;\nuse std::io::{BufWriter, Write};\nuse std::path::Path;\n\nfn main() {\n  let path = Path::new(&env::var_os(\"OUT_DIR\").unwrap()).join(\"ascii_case_insensitive_html_attributes.rs\");\n  let mut file = BufWriter::new(File::create(&path).unwrap());\n\n  let mut set = phf_codegen::Set::new();\n  for name in ASCII_CASE_INSENSITIVE_HTML_ATTRIBUTES.split_whitespace() {\n    set.entry(name);\n  }\n  write!(\n    &mut file,\n    \"{{ static SET: ::phf::Set<&'static str> = {}; &SET }}\",\n    set.build(),\n  )\n  .unwrap();\n}\n\n/// <https://html.spec.whatwg.org/multipage/#selectors>\nstatic ASCII_CASE_INSENSITIVE_HTML_ATTRIBUTES: &str = r#\"\n    accept\n    accept-charset\n    align\n    alink\n    axis\n    bgcolor\n    charset\n    checked\n    clear\n    codetype\n    color\n    compact\n    declare\n    defer\n    dir\n    direction\n    disabled\n    enctype\n    face\n    frame\n    hreflang\n    http-equiv\n    lang\n    language\n    link\n    media\n    method\n    multiple\n    nohref\n    noresize\n    noshade\n    nowrap\n    readonly\n    rel\n    rev\n    rules\n    scope\n    scrolling\n    selected\n    shape\n    target\n    text\n    type\n    valign\n    valuetype\n    vlink\n\"#;\n"
  },
  {
    "path": "selectors/builder.rs",
    "content": "/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at https://mozilla.org/MPL/2.0/. */\n\n//! Helper module to build up a selector safely and efficiently.\n//!\n//! Our selector representation is designed to optimize matching, and has\n//! several requirements:\n//! * All simple selectors and combinators are stored inline in the same buffer\n//!   as Component instances.\n//! * We store the top-level compound selectors from right to left, i.e. in\n//!   matching order.\n//! * We store the simple selectors for each combinator from left to right, so\n//!   that we match the cheaper simple selectors first.\n//!\n//! Meeting all these constraints without extra memmove traffic during parsing\n//! is non-trivial. This module encapsulates those details and presents an\n//! easy-to-use API for the parser.\n\nuse crate::parser::{Combinator, Component, SelectorImpl};\nuse crate::sink::Push;\n// use servo_arc::{Arc, HeaderWithLength, ThinArc};\nuse smallvec::{self, SmallVec};\nuse std::cmp;\nuse std::iter;\nuse std::ops::{Add, AddAssign};\nuse std::ptr;\nuse std::slice;\n\n/// Top-level SelectorBuilder struct. This should be stack-allocated by the\n/// consumer and never moved (because it contains a lot of inline data that\n/// would be slow to memmov).\n///\n/// After instantiation, callers may call the push_simple_selector() and\n/// push_combinator() methods to append selector data as it is encountered\n/// (from left to right). Once the process is complete, callers should invoke\n/// build(), which transforms the contents of the SelectorBuilder into a heap-\n/// allocated Selector and leaves the builder in a drained state.\n#[derive(Debug)]\npub struct SelectorBuilder<'i, Impl: SelectorImpl<'i>> {\n  /// The entire sequence of simple selectors, from left to right, without combinators.\n  ///\n  /// We make this large because the result of parsing a selector is fed into a new\n  /// Arc-ed allocation, so any spilled vec would be a wasted allocation. Also,\n  /// Components are large enough that we don't have much cache locality benefit\n  /// from reserving stack space for fewer of them.\n  simple_selectors: SmallVec<[Component<'i, Impl>; 32]>,\n  /// The combinators, and the length of the compound selector to their left.\n  combinators: SmallVec<[(Combinator, usize); 16]>,\n  /// The length of the current compound selector.\n  current_len: usize,\n}\n\nimpl<'i, Impl: SelectorImpl<'i>> Default for SelectorBuilder<'i, Impl> {\n  #[inline(always)]\n  fn default() -> Self {\n    SelectorBuilder {\n      simple_selectors: SmallVec::new(),\n      combinators: SmallVec::new(),\n      current_len: 0,\n    }\n  }\n}\n\nimpl<'i, Impl: SelectorImpl<'i>> Push<Component<'i, Impl>> for SelectorBuilder<'i, Impl> {\n  fn push(&mut self, value: Component<'i, Impl>) {\n    self.push_simple_selector(value);\n  }\n}\n\nimpl<'i, Impl: SelectorImpl<'i>> SelectorBuilder<'i, Impl> {\n  /// Pushes a simple selector onto the current compound selector.\n  #[inline(always)]\n  pub fn push_simple_selector(&mut self, ss: Component<'i, Impl>) {\n    assert!(!ss.is_combinator());\n    self.simple_selectors.push(ss);\n    self.current_len += 1;\n  }\n\n  /// Completes the current compound selector and starts a new one, delimited\n  /// by the given combinator.\n  #[inline(always)]\n  pub fn push_combinator(&mut self, c: Combinator) {\n    self.combinators.push((c, self.current_len));\n    self.current_len = 0;\n  }\n\n  /// Returns true if combinators have ever been pushed to this builder.\n  #[inline(always)]\n  pub fn has_combinators(&self) -> bool {\n    !self.combinators.is_empty()\n  }\n\n  pub fn add_nesting_prefix(&mut self) {\n    self.combinators.insert(0, (Combinator::Descendant, 1));\n    self.simple_selectors.insert(0, Component::Nesting);\n  }\n\n  /// Consumes the builder, producing a Selector.\n  #[inline(always)]\n  pub fn build(\n    &mut self,\n    parsed_pseudo: bool,\n    parsed_slotted: bool,\n    parsed_part: bool,\n  ) -> (SpecificityAndFlags, Vec<Component<'i, Impl>>) {\n    // Compute the specificity and flags.\n    let specificity = specificity(self.simple_selectors.iter());\n    let mut flags = SelectorFlags::empty();\n    if parsed_pseudo {\n      flags |= SelectorFlags::HAS_PSEUDO;\n    }\n    if parsed_slotted {\n      flags |= SelectorFlags::HAS_SLOTTED;\n    }\n    if parsed_part {\n      flags |= SelectorFlags::HAS_PART;\n    }\n    self.build_with_specificity_and_flags(SpecificityAndFlags { specificity, flags })\n  }\n\n  /// Builds with an explicit SpecificityAndFlags. This is separated from build() so\n  /// that unit tests can pass an explicit specificity.\n  #[inline(always)]\n  pub fn build_with_specificity_and_flags(\n    &mut self,\n    spec: SpecificityAndFlags,\n  ) -> (SpecificityAndFlags, Vec<Component<'i, Impl>>) {\n    // Use a raw pointer to be able to call set_len despite \"borrowing\" the slice.\n    // This is similar to SmallVec::drain, but we use a slice here because\n    // we’re gonna traverse it non-linearly.\n    let raw_simple_selectors: *const [Component<Impl>] = &*self.simple_selectors;\n    unsafe {\n      // Panic-safety: if SelectorBuilderIter is not iterated to the end,\n      // some simple selectors will safely leak.\n      self.simple_selectors.set_len(0)\n    }\n    let (rest, current) = split_from_end(unsafe { &*raw_simple_selectors }, self.current_len);\n    let iter = SelectorBuilderIter {\n      current_simple_selectors: current.iter(),\n      rest_of_simple_selectors: rest,\n      combinators: self.combinators.drain(..).rev(),\n    };\n\n    (spec, iter.collect())\n  }\n}\n\nstruct SelectorBuilderIter<'a, 'i, Impl: SelectorImpl<'i>> {\n  current_simple_selectors: slice::Iter<'a, Component<'i, Impl>>,\n  rest_of_simple_selectors: &'a [Component<'i, Impl>],\n  combinators: iter::Rev<smallvec::Drain<'a, [(Combinator, usize); 16]>>,\n}\n\nimpl<'a, 'i, Impl: SelectorImpl<'i>> ExactSizeIterator for SelectorBuilderIter<'a, 'i, Impl> {\n  fn len(&self) -> usize {\n    self.current_simple_selectors.len() + self.rest_of_simple_selectors.len() + self.combinators.len()\n  }\n}\n\nimpl<'a, 'i, Impl: SelectorImpl<'i>> Iterator for SelectorBuilderIter<'a, 'i, Impl> {\n  type Item = Component<'i, Impl>;\n  #[inline(always)]\n  fn next(&mut self) -> Option<Self::Item> {\n    if let Some(simple_selector_ref) = self.current_simple_selectors.next() {\n      // Move a simple selector out of this slice iterator.\n      // This is safe because we’ve called SmallVec::set_len(0) above,\n      // so SmallVec::drop won’t drop this simple selector.\n      unsafe { Some(ptr::read(simple_selector_ref)) }\n    } else {\n      self.combinators.next().map(|(combinator, len)| {\n        let (rest, current) = split_from_end(self.rest_of_simple_selectors, len);\n        self.rest_of_simple_selectors = rest;\n        self.current_simple_selectors = current.iter();\n        Component::Combinator(combinator)\n      })\n    }\n  }\n\n  fn size_hint(&self) -> (usize, Option<usize>) {\n    (self.len(), Some(self.len()))\n  }\n}\n\nfn split_from_end<T>(s: &[T], at: usize) -> (&[T], &[T]) {\n  s.split_at(s.len() - at)\n}\n\nbitflags! {\n    /// Flags that indicate at which point of parsing a selector are we.\n    #[derive(Default, Clone, Copy, Debug, Eq, PartialEq, Hash)]\n    pub (crate) struct SelectorFlags : u8 {\n        const HAS_PSEUDO = 1 << 0;\n        const HAS_SLOTTED = 1 << 1;\n        const HAS_PART = 1 << 2;\n    }\n}\n\n#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]\npub struct SpecificityAndFlags {\n  /// There are two free bits here, since we use ten bits for each specificity\n  /// kind (id, class, element).\n  pub(crate) specificity: u32,\n  /// There's padding after this field due to the size of the flags.\n  pub(crate) flags: SelectorFlags,\n}\n\nimpl SpecificityAndFlags {\n  #[inline]\n  pub fn specificity(&self) -> u32 {\n    self.specificity\n  }\n\n  #[inline]\n  pub fn has_pseudo_element(&self) -> bool {\n    self.flags.intersects(SelectorFlags::HAS_PSEUDO)\n  }\n\n  #[inline]\n  pub fn is_slotted(&self) -> bool {\n    self.flags.intersects(SelectorFlags::HAS_SLOTTED)\n  }\n\n  #[inline]\n  pub fn is_part(&self) -> bool {\n    self.flags.intersects(SelectorFlags::HAS_PART)\n  }\n}\n\nconst MAX_10BIT: u32 = (1u32 << 10) - 1;\n\n#[derive(Clone, Copy, Default, Eq, Ord, PartialEq, PartialOrd)]\nstruct Specificity {\n  id_selectors: u32,\n  class_like_selectors: u32,\n  element_selectors: u32,\n}\nimpl Add for Specificity {\n  type Output = Specificity;\n\n  fn add(self, rhs: Self) -> Self::Output {\n    Specificity {\n      id_selectors: self.id_selectors + rhs.id_selectors,\n      class_like_selectors: self.class_like_selectors + rhs.class_like_selectors,\n      element_selectors: self.element_selectors + rhs.element_selectors,\n    }\n  }\n}\nimpl AddAssign for Specificity {\n  fn add_assign(&mut self, rhs: Self) {\n    self.id_selectors += rhs.id_selectors;\n    self.element_selectors += rhs.element_selectors;\n    self.class_like_selectors += rhs.class_like_selectors;\n  }\n}\n\nimpl From<u32> for Specificity {\n  #[inline]\n  fn from(value: u32) -> Specificity {\n    assert!(value <= MAX_10BIT << 20 | MAX_10BIT << 10 | MAX_10BIT);\n    Specificity {\n      id_selectors: value >> 20,\n      class_like_selectors: (value >> 10) & MAX_10BIT,\n      element_selectors: value & MAX_10BIT,\n    }\n  }\n}\n\nimpl From<Specificity> for u32 {\n  #[inline]\n  fn from(specificity: Specificity) -> u32 {\n    cmp::min(specificity.id_selectors, MAX_10BIT) << 20\n      | cmp::min(specificity.class_like_selectors, MAX_10BIT) << 10\n      | cmp::min(specificity.element_selectors, MAX_10BIT)\n  }\n}\n\nfn specificity<'i, Impl>(iter: slice::Iter<Component<'i, Impl>>) -> u32\nwhere\n  Impl: SelectorImpl<'i>,\n{\n  complex_selector_specificity(iter).into()\n}\n\nfn complex_selector_specificity<'i, Impl>(iter: slice::Iter<Component<'i, Impl>>) -> Specificity\nwhere\n  Impl: SelectorImpl<'i>,\n{\n  fn simple_selector_specificity<'i, Impl>(simple_selector: &Component<'i, Impl>, specificity: &mut Specificity)\n  where\n    Impl: SelectorImpl<'i>,\n  {\n    match *simple_selector {\n      Component::Combinator(..) => {\n        unreachable!(\"Found combinator in simple selectors vector?\");\n      }\n      Component::Part(..) | Component::PseudoElement(..) | Component::LocalName(..) => {\n        specificity.element_selectors += 1\n      }\n      Component::Slotted(ref selector) => {\n        specificity.element_selectors += 1;\n        // Note that due to the way ::slotted works we only compete with\n        // other ::slotted rules, so the above rule doesn't really\n        // matter, but we do it still for consistency with other\n        // pseudo-elements.\n        //\n        // See: https://github.com/w3c/csswg-drafts/issues/1915\n        *specificity += Specificity::from(selector.specificity());\n      }\n      Component::Host(ref selector) => {\n        specificity.class_like_selectors += 1;\n        if let Some(ref selector) = *selector {\n          // See: https://github.com/w3c/csswg-drafts/issues/1915\n          *specificity += Specificity::from(selector.specificity());\n        }\n      }\n      Component::ID(..) => {\n        specificity.id_selectors += 1;\n      }\n      Component::Class(..)\n      | Component::AttributeInNoNamespace { .. }\n      | Component::AttributeInNoNamespaceExists { .. }\n      | Component::AttributeOther(..)\n      | Component::Root\n      | Component::Empty\n      | Component::Scope\n      | Component::Nth(..)\n      | Component::NonTSPseudoClass(..) => {\n        specificity.class_like_selectors += 1;\n      }\n      Component::NthOf(ref nth_of_data) => {\n        // https://drafts.csswg.org/selectors/#specificity-rules:\n        //\n        //     The specificity of the :nth-last-child() pseudo-class,\n        //     like the :nth-child() pseudo-class, combines the\n        //     specificity of a regular pseudo-class with that of its\n        //     selector argument S.\n        specificity.class_like_selectors += 1;\n        let mut max = 0;\n        for selector in nth_of_data.selectors() {\n          max = std::cmp::max(selector.specificity(), max);\n        }\n        *specificity += Specificity::from(max);\n      }\n      Component::Negation(ref list) | Component::Is(ref list) | Component::Any(_, ref list) => {\n        // https://drafts.csswg.org/selectors/#specificity-rules:\n        //\n        //     The specificity of an :is() pseudo-class is replaced by the\n        //     specificity of the most specific complex selector in its\n        //     selector list argument.\n        let mut max = 0;\n        for selector in &**list {\n          max = std::cmp::max(selector.specificity(), max);\n        }\n        *specificity += Specificity::from(max);\n      }\n      Component::Where(..)\n      | Component::Has(..)\n      | Component::ExplicitUniversalType\n      | Component::ExplicitAnyNamespace\n      | Component::ExplicitNoNamespace\n      | Component::DefaultNamespace(..)\n      | Component::Namespace(..) => {\n        // Does not affect specificity\n      }\n      Component::Nesting => {\n        // TODO\n      }\n    }\n  }\n\n  let mut specificity = Default::default();\n  for simple_selector in iter {\n    simple_selector_specificity(&simple_selector, &mut specificity);\n  }\n  specificity\n}\n"
  },
  {
    "path": "selectors/context.rs",
    "content": "/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at https://mozilla.org/MPL/2.0/. */\n\nuse crate::attr::CaseSensitivity;\nuse crate::bloom::BloomFilter;\nuse crate::nth_index_cache::NthIndexCache;\nuse crate::parser::SelectorImpl;\nuse crate::tree::{Element, OpaqueElement};\n\n/// What kind of selector matching mode we should use.\n///\n/// There are two modes of selector matching. The difference is only noticeable\n/// in presence of pseudo-elements.\n#[derive(Clone, Copy, Debug, PartialEq)]\npub enum MatchingMode {\n  /// Don't ignore any pseudo-element selectors.\n  Normal,\n\n  /// Ignores any stateless pseudo-element selectors in the rightmost sequence\n  /// of simple selectors.\n  ///\n  /// This is useful, for example, to match against ::before when you aren't a\n  /// pseudo-element yourself.\n  ///\n  /// For example, in presence of `::before:hover`, it would never match, but\n  /// `::before` would be ignored as in \"matching\".\n  ///\n  /// It's required for all the selectors you match using this mode to have a\n  /// pseudo-element.\n  ForStatelessPseudoElement,\n}\n\n/// The mode to use when matching unvisited and visited links.\n#[derive(Clone, Copy, Debug, Eq, PartialEq)]\npub enum VisitedHandlingMode {\n  /// All links are matched as if they are unvisted.\n  AllLinksUnvisited,\n  /// All links are matched as if they are visited and unvisited (both :link\n  /// and :visited match).\n  ///\n  /// This is intended to be used from invalidation code, to be conservative\n  /// about whether we need to restyle a link.\n  AllLinksVisitedAndUnvisited,\n  /// A element's \"relevant link\" is the element being matched if it is a link\n  /// or the nearest ancestor link. The relevant link is matched as though it\n  /// is visited, and all other links are matched as if they are unvisited.\n  RelevantLinkVisited,\n}\n\nimpl VisitedHandlingMode {\n  #[inline]\n  pub fn matches_visited(&self) -> bool {\n    matches!(\n      *self,\n      VisitedHandlingMode::RelevantLinkVisited | VisitedHandlingMode::AllLinksVisitedAndUnvisited\n    )\n  }\n\n  #[inline]\n  pub fn matches_unvisited(&self) -> bool {\n    matches!(\n      *self,\n      VisitedHandlingMode::AllLinksUnvisited | VisitedHandlingMode::AllLinksVisitedAndUnvisited\n    )\n  }\n}\n\n/// Which quirks mode is this document in.\n///\n/// See: https://quirks.spec.whatwg.org/\n#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]\npub enum QuirksMode {\n  /// Quirks mode.\n  Quirks,\n  /// Limited quirks mode.\n  LimitedQuirks,\n  /// No quirks mode.\n  NoQuirks,\n}\n\nimpl QuirksMode {\n  #[inline]\n  pub fn classes_and_ids_case_sensitivity(self) -> CaseSensitivity {\n    match self {\n      QuirksMode::NoQuirks | QuirksMode::LimitedQuirks => CaseSensitivity::CaseSensitive,\n      QuirksMode::Quirks => CaseSensitivity::AsciiCaseInsensitive,\n    }\n  }\n}\n\n/// Data associated with the matching process for a element.  This context is\n/// used across many selectors for an element, so it's not appropriate for\n/// transient data that applies to only a single selector.\npub struct MatchingContext<'a, 'i, Impl>\nwhere\n  Impl: SelectorImpl<'i>,\n{\n  /// Input with the matching mode we should use when matching selectors.\n  matching_mode: MatchingMode,\n  /// Input with the bloom filter used to fast-reject selectors.\n  pub bloom_filter: Option<&'a BloomFilter>,\n  /// An optional cache to speed up nth-index-like selectors.\n  pub nth_index_cache: Option<&'a mut NthIndexCache>,\n  /// The element which is going to match :scope pseudo-class. It can be\n  /// either one :scope element, or the scoping element.\n  ///\n  /// Note that, although in theory there can be multiple :scope elements,\n  /// in current specs, at most one is specified, and when there is one,\n  /// scoping element is not relevant anymore, so we use a single field for\n  /// them.\n  ///\n  /// When this is None, :scope will match the root element.\n  ///\n  /// See https://drafts.csswg.org/selectors-4/#scope-pseudo\n  pub scope_element: Option<OpaqueElement>,\n\n  /// The current shadow host we're collecting :host rules for.\n  pub current_host: Option<OpaqueElement>,\n\n  /// Controls how matching for links is handled.\n  visited_handling: VisitedHandlingMode,\n\n  /// The current nesting level of selectors that we're matching.\n  ///\n  /// FIXME(emilio): Consider putting the mutable stuff in a Cell, then make\n  /// MatchingContext immutable again.\n  nesting_level: usize,\n\n  /// Whether we're inside a negation or not.\n  in_negation: bool,\n\n  /// An optional hook function for checking whether a pseudo-element\n  /// should match when matching_mode is ForStatelessPseudoElement.\n  pub pseudo_element_matching_fn: Option<&'a dyn Fn(&Impl::PseudoElement) -> bool>,\n\n  /// Extra implementation-dependent matching data.\n  pub extra_data: Impl::ExtraMatchingData,\n\n  quirks_mode: QuirksMode,\n  classes_and_ids_case_sensitivity: CaseSensitivity,\n  _impl: ::std::marker::PhantomData<Impl>,\n}\n\nimpl<'a, 'i, Impl> MatchingContext<'a, 'i, Impl>\nwhere\n  Impl: SelectorImpl<'i>,\n{\n  /// Constructs a new `MatchingContext`.\n  pub fn new(\n    matching_mode: MatchingMode,\n    bloom_filter: Option<&'a BloomFilter>,\n    nth_index_cache: Option<&'a mut NthIndexCache>,\n    quirks_mode: QuirksMode,\n  ) -> Self {\n    Self::new_for_visited(\n      matching_mode,\n      bloom_filter,\n      nth_index_cache,\n      VisitedHandlingMode::AllLinksUnvisited,\n      quirks_mode,\n    )\n  }\n\n  /// Constructs a new `MatchingContext` for use in visited matching.\n  pub fn new_for_visited(\n    matching_mode: MatchingMode,\n    bloom_filter: Option<&'a BloomFilter>,\n    nth_index_cache: Option<&'a mut NthIndexCache>,\n    visited_handling: VisitedHandlingMode,\n    quirks_mode: QuirksMode,\n  ) -> Self {\n    Self {\n      matching_mode,\n      bloom_filter,\n      visited_handling,\n      nth_index_cache,\n      quirks_mode,\n      classes_and_ids_case_sensitivity: quirks_mode.classes_and_ids_case_sensitivity(),\n      scope_element: None,\n      current_host: None,\n      nesting_level: 0,\n      in_negation: false,\n      pseudo_element_matching_fn: None,\n      extra_data: Default::default(),\n      _impl: ::std::marker::PhantomData,\n    }\n  }\n\n  /// Whether we're matching a nested selector.\n  #[inline]\n  pub fn is_nested(&self) -> bool {\n    self.nesting_level != 0\n  }\n\n  /// Whether we're matching inside a :not(..) selector.\n  #[inline]\n  pub fn in_negation(&self) -> bool {\n    self.in_negation\n  }\n\n  /// The quirks mode of the document.\n  #[inline]\n  pub fn quirks_mode(&self) -> QuirksMode {\n    self.quirks_mode\n  }\n\n  /// The matching-mode for this selector-matching operation.\n  #[inline]\n  pub fn matching_mode(&self) -> MatchingMode {\n    self.matching_mode\n  }\n\n  /// The case-sensitivity for class and ID selectors\n  #[inline]\n  pub fn classes_and_ids_case_sensitivity(&self) -> CaseSensitivity {\n    self.classes_and_ids_case_sensitivity\n  }\n\n  /// Runs F with a deeper nesting level.\n  #[inline]\n  pub fn nest<F, R>(&mut self, f: F) -> R\n  where\n    F: FnOnce(&mut Self) -> R,\n  {\n    self.nesting_level += 1;\n    let result = f(self);\n    self.nesting_level -= 1;\n    result\n  }\n\n  /// Runs F with a deeper nesting level, and marking ourselves in a negation,\n  /// for a :not(..) selector, for example.\n  #[inline]\n  pub fn nest_for_negation<F, R>(&mut self, f: F) -> R\n  where\n    F: FnOnce(&mut Self) -> R,\n  {\n    let old_in_negation = self.in_negation;\n    self.in_negation = true;\n    let result = self.nest(f);\n    self.in_negation = old_in_negation;\n    result\n  }\n\n  #[inline]\n  pub fn visited_handling(&self) -> VisitedHandlingMode {\n    self.visited_handling\n  }\n\n  /// Runs F with a different VisitedHandlingMode.\n  #[inline]\n  pub fn with_visited_handling_mode<F, R>(&mut self, handling_mode: VisitedHandlingMode, f: F) -> R\n  where\n    F: FnOnce(&mut Self) -> R,\n  {\n    let original_handling_mode = self.visited_handling;\n    self.visited_handling = handling_mode;\n    let result = f(self);\n    self.visited_handling = original_handling_mode;\n    result\n  }\n\n  /// Runs F with a given shadow host which is the root of the tree whose\n  /// rules we're matching.\n  #[inline]\n  pub fn with_shadow_host<F, E, R>(&mut self, host: Option<E>, f: F) -> R\n  where\n    E: Element<'i>,\n    F: FnOnce(&mut Self) -> R,\n  {\n    let original_host = self.current_host.take();\n    self.current_host = host.map(|h| h.opaque());\n    let result = f(self);\n    self.current_host = original_host;\n    result\n  }\n\n  /// Returns the current shadow host whose shadow root we're matching rules\n  /// against.\n  #[inline]\n  pub fn shadow_host(&self) -> Option<OpaqueElement> {\n    self.current_host\n  }\n}\n"
  },
  {
    "path": "selectors/lib.rs",
    "content": "/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at https://mozilla.org/MPL/2.0/. */\n\n// Make |cargo bench| work.\n#![cfg_attr(feature = \"bench\", feature(test))]\n\n#[macro_use]\nextern crate bitflags;\n#[macro_use]\nextern crate cssparser;\n#[macro_use]\nextern crate log;\n\npub mod attr;\npub mod bloom;\nmod builder;\npub mod context;\npub mod matching;\nmod nth_index_cache;\npub mod parser;\npub mod sink;\nmod tree;\npub mod visitor;\n\n#[cfg(all(feature = \"serde\"))]\nmod serialization;\n\npub use crate::nth_index_cache::NthIndexCache;\npub use crate::parser::{Parser, SelectorImpl, SelectorList};\npub use crate::tree::{Element, OpaqueElement};\n"
  },
  {
    "path": "selectors/matching.rs",
    "content": "/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at https://mozilla.org/MPL/2.0/. */\n\nuse crate::attr::{AttrSelectorOperation, NamespaceConstraint, ParsedAttrSelectorOperation};\nuse crate::bloom::{BloomFilter, BLOOM_HASH_MASK};\nuse crate::nth_index_cache::NthIndexCacheInner;\nuse crate::parser::{AncestorHashes, Combinator, Component, LocalName, NthType};\nuse crate::parser::{NonTSPseudoClass, Selector, SelectorImpl, SelectorIter, SelectorList};\nuse crate::tree::Element;\nuse smallvec::SmallVec;\nuse std::borrow::Borrow;\nuse std::iter;\n\npub use crate::context::*;\n\n// The bloom filter for descendant CSS selectors will have a <1% false\n// positive rate until it has this many selectors in it, then it will\n// rapidly increase.\npub static RECOMMENDED_SELECTOR_BLOOM_FILTER_SIZE: usize = 4096;\n\nbitflags! {\n    /// Set of flags that are set on either the element or its parent (depending\n    /// on the flag) if the element could potentially match a selector.\n    pub struct ElementSelectorFlags: usize {\n        /// When a child is added or removed from the parent, all the children\n        /// must be restyled, because they may match :nth-last-child,\n        /// :last-of-type, :nth-last-of-type, or :only-of-type.\n        const HAS_SLOW_SELECTOR = 1 << 0;\n\n        /// When a child is added or removed from the parent, any later\n        /// children must be restyled, because they may match :nth-child,\n        /// :first-of-type, or :nth-of-type.\n        const HAS_SLOW_SELECTOR_LATER_SIBLINGS = 1 << 1;\n\n        /// When a child is added or removed from the parent, the first and\n        /// last children must be restyled, because they may match :first-child,\n        /// :last-child, or :only-child.\n        const HAS_EDGE_CHILD_SELECTOR = 1 << 2;\n\n        /// The element has an empty selector, so when a child is appended we\n        /// might need to restyle the parent completely.\n        const HAS_EMPTY_SELECTOR = 1 << 3;\n    }\n}\n\nimpl ElementSelectorFlags {\n  /// Returns the subset of flags that apply to the element.\n  pub fn for_self(self) -> ElementSelectorFlags {\n    self & (ElementSelectorFlags::HAS_EMPTY_SELECTOR)\n  }\n\n  /// Returns the subset of flags that apply to the parent.\n  pub fn for_parent(self) -> ElementSelectorFlags {\n    self\n      & (ElementSelectorFlags::HAS_SLOW_SELECTOR\n        | ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS\n        | ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR)\n  }\n}\n\n/// Holds per-compound-selector data.\nstruct LocalMatchingContext<'a, 'b, 'i, Impl: SelectorImpl<'i>> {\n  shared: &'a mut MatchingContext<'b, 'i, Impl>,\n  matches_hover_and_active_quirk: MatchesHoverAndActiveQuirk,\n}\n\n#[inline(always)]\npub fn matches_selector_list<'i, E>(\n  selector_list: &SelectorList<'i, E::Impl>,\n  element: &E,\n  context: &mut MatchingContext<'_, 'i, E::Impl>,\n) -> bool\nwhere\n  E: Element<'i>,\n{\n  // This is pretty much any(..) but manually inlined because the compiler\n  // refuses to do so from querySelector / querySelectorAll.\n  for selector in &selector_list.0 {\n    let matches = matches_selector(selector, 0, None, element, context, &mut |_, _| {});\n\n    if matches {\n      return true;\n    }\n  }\n\n  false\n}\n\n#[inline(always)]\nfn may_match(hashes: &AncestorHashes, bf: &BloomFilter) -> bool {\n  // Check the first three hashes. Note that we can check for zero before\n  // masking off the high bits, since if any of the first three hashes is\n  // zero the fourth will be as well. We also take care to avoid the\n  // special-case complexity of the fourth hash until we actually reach it,\n  // because we usually don't.\n  //\n  // To be clear: this is all extremely hot.\n  for i in 0..3 {\n    let packed = hashes.packed_hashes[i];\n    if packed == 0 {\n      // No more hashes left - unable to fast-reject.\n      return true;\n    }\n\n    if !bf.might_contain_hash(packed & BLOOM_HASH_MASK) {\n      // Hooray! We fast-rejected on this hash.\n      return false;\n    }\n  }\n\n  // Now do the slighty-more-complex work of synthesizing the fourth hash,\n  // and check it against the filter if it exists.\n  let fourth = hashes.fourth_hash();\n  fourth == 0 || bf.might_contain_hash(fourth)\n}\n\n/// A result of selector matching, includes 3 failure types,\n///\n///   NotMatchedAndRestartFromClosestLaterSibling\n///   NotMatchedAndRestartFromClosestDescendant\n///   NotMatchedGlobally\n///\n/// When NotMatchedGlobally appears, stop selector matching completely since\n/// the succeeding selectors never matches.\n/// It is raised when\n///   Child combinator cannot find the candidate element.\n///   Descendant combinator cannot find the candidate element.\n///\n/// When NotMatchedAndRestartFromClosestDescendant appears, the selector\n/// matching does backtracking and restarts from the closest Descendant\n/// combinator.\n/// It is raised when\n///   NextSibling combinator cannot find the candidate element.\n///   LaterSibling combinator cannot find the candidate element.\n///   Child combinator doesn't match on the found element.\n///\n/// When NotMatchedAndRestartFromClosestLaterSibling appears, the selector\n/// matching does backtracking and restarts from the closest LaterSibling\n/// combinator.\n/// It is raised when\n///   NextSibling combinator doesn't match on the found element.\n///\n/// For example, when the selector \"d1 d2 a\" is provided and we cannot *find*\n/// an appropriate ancestor element for \"d1\", this selector matching raises\n/// NotMatchedGlobally since even if \"d2\" is moved to more upper element, the\n/// candidates for \"d1\" becomes less than before and d1 .\n///\n/// The next example is siblings. When the selector \"b1 + b2 ~ d1 a\" is\n/// provided and we cannot *find* an appropriate brother element for b1,\n/// the selector matching raises NotMatchedAndRestartFromClosestDescendant.\n/// The selectors (\"b1 + b2 ~\") doesn't match and matching restart from \"d1\".\n///\n/// The additional example is child and sibling. When the selector\n/// \"b1 + c1 > b2 ~ d1 a\" is provided and the selector \"b1\" doesn't match on\n/// the element, this \"b1\" raises NotMatchedAndRestartFromClosestLaterSibling.\n/// However since the selector \"c1\" raises\n/// NotMatchedAndRestartFromClosestDescendant. So the selector\n/// \"b1 + c1 > b2 ~ \" doesn't match and restart matching from \"d1\".\n#[derive(Clone, Copy, Eq, PartialEq)]\nenum SelectorMatchingResult {\n  Matched,\n  NotMatchedAndRestartFromClosestLaterSibling,\n  NotMatchedAndRestartFromClosestDescendant,\n  NotMatchedGlobally,\n}\n\n/// Whether the :hover and :active quirk applies.\n///\n/// https://quirks.spec.whatwg.org/#the-active-and-hover-quirk\n#[derive(Clone, Copy, Debug, PartialEq)]\nenum MatchesHoverAndActiveQuirk {\n  Yes,\n  No,\n}\n\n/// Matches a selector, fast-rejecting against a bloom filter.\n///\n/// We accept an offset to allow consumers to represent and match against\n/// partial selectors (indexed from the right). We use this API design, rather\n/// than having the callers pass a SelectorIter, because creating a SelectorIter\n/// requires dereferencing the selector to get the length, which adds an\n/// unnecessary cache miss for cases when we can fast-reject with AncestorHashes\n/// (which the caller can store inline with the selector pointer).\n#[inline(always)]\npub fn matches_selector<'i, E, F>(\n  selector: &Selector<'i, E::Impl>,\n  offset: usize,\n  hashes: Option<&AncestorHashes>,\n  element: &E,\n  context: &mut MatchingContext<'_, 'i, E::Impl>,\n  flags_setter: &mut F,\n) -> bool\nwhere\n  E: Element<'i>,\n  F: FnMut(&E, ElementSelectorFlags),\n{\n  // Use the bloom filter to fast-reject.\n  if let Some(hashes) = hashes {\n    if let Some(filter) = context.bloom_filter {\n      if !may_match(hashes, filter) {\n        return false;\n      }\n    }\n  }\n\n  matches_complex_selector(selector.iter_from(offset), element, context, flags_setter)\n}\n\n/// Whether a compound selector matched, and whether it was the rightmost\n/// selector inside the complex selector.\npub enum CompoundSelectorMatchingResult {\n  /// The selector was fully matched.\n  FullyMatched,\n  /// The compound selector matched, and the next combinator offset is\n  /// `next_combinator_offset`.\n  Matched { next_combinator_offset: usize },\n  /// The selector didn't match.\n  NotMatched,\n}\n\n/// Matches a compound selector belonging to `selector`, starting at offset\n/// `from_offset`, matching left to right.\n///\n/// Requires that `from_offset` points to a `Combinator`.\n///\n/// NOTE(emilio): This doesn't allow to match in the leftmost sequence of the\n/// complex selector, but it happens to be the case we don't need it.\npub fn matches_compound_selector_from<'i, E>(\n  selector: &Selector<'i, E::Impl>,\n  mut from_offset: usize,\n  context: &mut MatchingContext<'_, 'i, E::Impl>,\n  element: &E,\n) -> CompoundSelectorMatchingResult\nwhere\n  E: Element<'i>,\n{\n  if cfg!(debug_assertions) && from_offset != 0 {\n    selector.combinator_at_parse_order(from_offset - 1); // This asserts.\n  }\n\n  let mut local_context = LocalMatchingContext {\n    shared: context,\n    matches_hover_and_active_quirk: MatchesHoverAndActiveQuirk::No,\n  };\n\n  // Find the end of the selector or the next combinator, then match\n  // backwards, so that we match in the same order as\n  // matches_complex_selector, which is usually faster.\n  let start_offset = from_offset;\n  for component in selector.iter_raw_parse_order_from(from_offset) {\n    if matches!(*component, Component::Combinator(..)) {\n      debug_assert_ne!(from_offset, 0, \"Selector started with a combinator?\");\n      break;\n    }\n\n    from_offset += 1;\n  }\n\n  debug_assert!(from_offset >= 1);\n  debug_assert!(from_offset <= selector.len());\n\n  let iter = selector.iter_from(selector.len() - from_offset);\n  debug_assert!(\n    iter.clone().next().is_some()\n      || (from_offset != selector.len()\n        && matches!(\n          selector.combinator_at_parse_order(from_offset),\n          Combinator::SlotAssignment | Combinator::PseudoElement\n        )),\n    \"Got the math wrong: {:?} | {:?} | {} {}\",\n    selector,\n    selector.iter_raw_match_order().as_slice(),\n    from_offset,\n    start_offset\n  );\n\n  for component in iter {\n    if !matches_simple_selector(component, element, &mut local_context, &mut |_, _| {}) {\n      return CompoundSelectorMatchingResult::NotMatched;\n    }\n  }\n\n  if from_offset != selector.len() {\n    return CompoundSelectorMatchingResult::Matched {\n      next_combinator_offset: from_offset,\n    };\n  }\n\n  CompoundSelectorMatchingResult::FullyMatched\n}\n\n/// Matches a complex selector.\n#[inline(always)]\npub fn matches_complex_selector<'i, E, F>(\n  mut iter: SelectorIter<'_, 'i, E::Impl>,\n  element: &E,\n  context: &mut MatchingContext<'_, 'i, E::Impl>,\n  flags_setter: &mut F,\n) -> bool\nwhere\n  E: Element<'i>,\n  F: FnMut(&E, ElementSelectorFlags),\n{\n  // If this is the special pseudo-element mode, consume the ::pseudo-element\n  // before proceeding, since the caller has already handled that part.\n  if context.matching_mode() == MatchingMode::ForStatelessPseudoElement && !context.is_nested() {\n    // Consume the pseudo.\n    match *iter.next().unwrap() {\n      Component::PseudoElement(ref pseudo) => {\n        if let Some(ref f) = context.pseudo_element_matching_fn {\n          if !f(pseudo) {\n            return false;\n          }\n        }\n      }\n      _ => {\n        debug_assert!(\n          false,\n          \"Used MatchingMode::ForStatelessPseudoElement \\\n                     in a non-pseudo selector\"\n        );\n      }\n    }\n\n    if !iter.matches_for_stateless_pseudo_element() {\n      return false;\n    }\n\n    // Advance to the non-pseudo-element part of the selector.\n    let next_sequence = iter.next_sequence().unwrap();\n    debug_assert_eq!(next_sequence, Combinator::PseudoElement);\n  }\n\n  let result = matches_complex_selector_internal(iter, element, context, flags_setter, Rightmost::Yes);\n\n  matches!(result, SelectorMatchingResult::Matched)\n}\n\n#[inline]\nfn matches_hover_and_active_quirk<'i, Impl: SelectorImpl<'i>>(\n  selector_iter: &SelectorIter<'_, 'i, Impl>,\n  context: &MatchingContext<'_, 'i, Impl>,\n  rightmost: Rightmost,\n) -> MatchesHoverAndActiveQuirk {\n  if context.quirks_mode() != QuirksMode::Quirks {\n    return MatchesHoverAndActiveQuirk::No;\n  }\n\n  if context.is_nested() {\n    return MatchesHoverAndActiveQuirk::No;\n  }\n\n  // This compound selector had a pseudo-element to the right that we\n  // intentionally skipped.\n  if rightmost == Rightmost::Yes && context.matching_mode() == MatchingMode::ForStatelessPseudoElement {\n    return MatchesHoverAndActiveQuirk::No;\n  }\n\n  let all_match = selector_iter.clone().all(|simple| match *simple {\n    Component::LocalName(_)\n    | Component::AttributeInNoNamespaceExists { .. }\n    | Component::AttributeInNoNamespace { .. }\n    | Component::AttributeOther(_)\n    | Component::ID(_)\n    | Component::Class(_)\n    | Component::PseudoElement(_)\n    | Component::Negation(_)\n    | Component::Nth(_)\n    | Component::NthOf(..)\n    | Component::Empty => false,\n    Component::NonTSPseudoClass(ref pseudo_class) => pseudo_class.is_active_or_hover(),\n    _ => true,\n  });\n\n  if all_match {\n    MatchesHoverAndActiveQuirk::Yes\n  } else {\n    MatchesHoverAndActiveQuirk::No\n  }\n}\n\n#[derive(Clone, Copy, PartialEq)]\nenum Rightmost {\n  Yes,\n  No,\n}\n\n#[inline(always)]\nfn next_element_for_combinator<'i, E>(\n  element: &E,\n  combinator: Combinator,\n  selector: &SelectorIter<'_, 'i, E::Impl>,\n  context: &MatchingContext<'_, 'i, E::Impl>,\n) -> Option<E>\nwhere\n  E: Element<'i>,\n{\n  match combinator {\n    Combinator::NextSibling | Combinator::LaterSibling => element.prev_sibling_element(),\n    Combinator::Child | Combinator::Descendant | Combinator::Deep | Combinator::DeepDescendant => {\n      match element.parent_element() {\n        Some(e) => return Some(e),\n        None => {}\n      }\n\n      if !element.parent_node_is_shadow_root() {\n        return None;\n      }\n\n      // https://drafts.csswg.org/css-scoping/#host-element-in-tree:\n      //\n      //   For the purpose of Selectors, a shadow host also appears in\n      //   its shadow tree, with the contents of the shadow tree treated\n      //   as its children. (In other words, the shadow host is treated as\n      //   replacing the shadow root node.)\n      //\n      // and also:\n      //\n      //   When considered within its own shadow trees, the shadow host is\n      //   featureless. Only the :host, :host(), and :host-context()\n      //   pseudo-classes are allowed to match it.\n      //\n      // Since we know that the parent is a shadow root, we necessarily\n      // are in a shadow tree of the host, and the next selector will only\n      // match if the selector is a featureless :host selector.\n      if !selector.clone().is_featureless_host_selector() {\n        return None;\n      }\n\n      element.containing_shadow_host()\n    }\n    Combinator::Part => element.containing_shadow_host(),\n    Combinator::SlotAssignment => {\n      debug_assert!(element.assigned_slot().map_or(true, |s| s.is_html_slot_element()));\n      let scope = context.current_host?;\n      let mut current_slot = element.assigned_slot()?;\n      while current_slot.containing_shadow_host().unwrap().opaque() != scope {\n        current_slot = current_slot.assigned_slot()?;\n      }\n      Some(current_slot)\n    }\n    Combinator::PseudoElement => element.pseudo_element_originating_element(),\n  }\n}\n\nfn matches_complex_selector_internal<'i, E, F>(\n  mut selector_iter: SelectorIter<'_, 'i, E::Impl>,\n  element: &E,\n  context: &mut MatchingContext<'_, 'i, E::Impl>,\n  flags_setter: &mut F,\n  rightmost: Rightmost,\n) -> SelectorMatchingResult\nwhere\n  E: Element<'i>,\n  F: FnMut(&E, ElementSelectorFlags),\n{\n  debug!(\"Matching complex selector {:?} for {:?}\", selector_iter, element);\n\n  let matches_compound_selector =\n    matches_compound_selector(&mut selector_iter, element, context, flags_setter, rightmost);\n\n  let combinator = selector_iter.next_sequence();\n  if combinator.map_or(false, |c| c.is_sibling()) {\n    flags_setter(element, ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS);\n  }\n\n  if !matches_compound_selector {\n    return SelectorMatchingResult::NotMatchedAndRestartFromClosestLaterSibling;\n  }\n\n  let combinator = match combinator {\n    None => return SelectorMatchingResult::Matched,\n    Some(c) => c,\n  };\n\n  let candidate_not_found = match combinator {\n    Combinator::NextSibling | Combinator::LaterSibling => {\n      SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant\n    }\n    Combinator::Child\n    | Combinator::Deep\n    | Combinator::DeepDescendant\n    | Combinator::Descendant\n    | Combinator::SlotAssignment\n    | Combinator::Part\n    | Combinator::PseudoElement => SelectorMatchingResult::NotMatchedGlobally,\n  };\n\n  let mut next_element = next_element_for_combinator(element, combinator, &selector_iter, &context);\n\n  // Stop matching :visited as soon as we find a link, or a combinator for\n  // something that isn't an ancestor.\n  let mut visited_handling = if element.is_link() || combinator.is_sibling() {\n    VisitedHandlingMode::AllLinksUnvisited\n  } else {\n    context.visited_handling()\n  };\n\n  loop {\n    let element = match next_element {\n      None => return candidate_not_found,\n      Some(next_element) => next_element,\n    };\n\n    let result = context.with_visited_handling_mode(visited_handling, |context| {\n      matches_complex_selector_internal(selector_iter.clone(), &element, context, flags_setter, Rightmost::No)\n    });\n\n    match (result, combinator) {\n      // Return the status immediately.\n      (SelectorMatchingResult::Matched, _)\n      | (SelectorMatchingResult::NotMatchedGlobally, _)\n      | (_, Combinator::NextSibling) => {\n        return result;\n      }\n\n      // Upgrade the failure status to\n      // NotMatchedAndRestartFromClosestDescendant.\n      (_, Combinator::PseudoElement) | (_, Combinator::Child) => {\n        return SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant;\n      }\n\n      // If the failure status is\n      // NotMatchedAndRestartFromClosestDescendant and combinator is\n      // Combinator::LaterSibling, give up this Combinator::LaterSibling\n      // matching and restart from the closest descendant combinator.\n      (SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant, Combinator::LaterSibling) => {\n        return result;\n      }\n\n      // The Combinator::Descendant combinator and the status is\n      // NotMatchedAndRestartFromClosestLaterSibling or\n      // NotMatchedAndRestartFromClosestDescendant, or the\n      // Combinator::LaterSibling combinator and the status is\n      // NotMatchedAndRestartFromClosestDescendant, we can continue to\n      // matching on the next candidate element.\n      _ => {}\n    }\n\n    if element.is_link() {\n      visited_handling = VisitedHandlingMode::AllLinksUnvisited;\n    }\n\n    next_element = next_element_for_combinator(&element, combinator, &selector_iter, &context);\n  }\n}\n\n#[inline]\nfn matches_local_name<'i, E>(element: &E, local_name: &LocalName<'i, E::Impl>) -> bool\nwhere\n  E: Element<'i>,\n{\n  let name = select_name(\n    element.is_html_element_in_html_document(),\n    &local_name.name,\n    &local_name.lower_name,\n  )\n  .borrow();\n  element.has_local_name(name)\n}\n\n/// Determines whether the given element matches the given compound selector.\n#[inline]\nfn matches_compound_selector<'i, E, F>(\n  selector_iter: &mut SelectorIter<'_, 'i, E::Impl>,\n  element: &E,\n  context: &mut MatchingContext<'_, 'i, E::Impl>,\n  flags_setter: &mut F,\n  rightmost: Rightmost,\n) -> bool\nwhere\n  E: Element<'i>,\n  F: FnMut(&E, ElementSelectorFlags),\n{\n  let matches_hover_and_active_quirk = matches_hover_and_active_quirk(&selector_iter, context, rightmost);\n\n  // Handle some common cases first.\n  // We may want to get rid of this at some point if we can make the\n  // generic case fast enough.\n  let mut selector = selector_iter.next();\n  if let Some(&Component::LocalName(ref local_name)) = selector {\n    if !matches_local_name(element, local_name) {\n      return false;\n    }\n    selector = selector_iter.next();\n  }\n  let class_and_id_case_sensitivity = context.classes_and_ids_case_sensitivity();\n  if let Some(&Component::ID(ref id)) = selector {\n    if !element.has_id(id, class_and_id_case_sensitivity) {\n      return false;\n    }\n    selector = selector_iter.next();\n  }\n  while let Some(&Component::Class(ref class)) = selector {\n    if !element.has_class(class, class_and_id_case_sensitivity) {\n      return false;\n    }\n    selector = selector_iter.next();\n  }\n  let selector = match selector {\n    Some(s) => s,\n    None => return true,\n  };\n\n  let mut local_context = LocalMatchingContext {\n    shared: context,\n    matches_hover_and_active_quirk,\n  };\n  iter::once(selector)\n    .chain(selector_iter)\n    .all(|simple| matches_simple_selector(simple, element, &mut local_context, flags_setter))\n}\n\n/// Determines whether the given element matches the given single selector.\nfn matches_simple_selector<'i, E, F>(\n  selector: &Component<'i, E::Impl>,\n  element: &E,\n  context: &mut LocalMatchingContext<'_, '_, 'i, E::Impl>,\n  flags_setter: &mut F,\n) -> bool\nwhere\n  E: Element<'i>,\n  F: FnMut(&E, ElementSelectorFlags),\n{\n  debug_assert!(context.shared.is_nested() || !context.shared.in_negation());\n\n  match *selector {\n    Component::Combinator(_) => unreachable!(),\n    Component::Part(ref parts) => {\n      let mut hosts = SmallVec::<[E; 4]>::new();\n\n      let mut host = match element.containing_shadow_host() {\n        Some(h) => h,\n        None => return false,\n      };\n\n      let current_host = context.shared.current_host;\n      if current_host != Some(host.opaque()) {\n        loop {\n          let outer_host = host.containing_shadow_host();\n          if outer_host.as_ref().map(|h| h.opaque()) == current_host {\n            break;\n          }\n          let outer_host = match outer_host {\n            Some(h) => h,\n            None => return false,\n          };\n          // TODO(emilio): if worth it, we could early return if\n          // host doesn't have the exportparts attribute.\n          hosts.push(host);\n          host = outer_host;\n        }\n      }\n\n      // Translate the part into the right scope.\n      parts.iter().all(|part| {\n        let mut part = part.clone();\n        for host in hosts.iter().rev() {\n          part = match host.imported_part(&part) {\n            Some(p) => p,\n            None => return false,\n          };\n        }\n        element.is_part(&part)\n      })\n    }\n    Component::Slotted(ref selector) => {\n      // <slots> are never flattened tree slottables.\n      !element.is_html_slot_element()\n        && context\n          .shared\n          .nest(|context| matches_complex_selector(selector.iter(), element, context, flags_setter))\n    }\n    Component::PseudoElement(ref pseudo) => element.match_pseudo_element(pseudo, context.shared),\n    Component::LocalName(ref local_name) => matches_local_name(element, local_name),\n    Component::ExplicitUniversalType | Component::ExplicitAnyNamespace => true,\n    Component::Namespace(_, ref url) | Component::DefaultNamespace(ref url) => {\n      element.has_namespace(&url.borrow())\n    }\n    Component::ExplicitNoNamespace => {\n      let ns = crate::parser::namespace_empty_string::<E::Impl>();\n      element.has_namespace(&ns.borrow())\n    }\n    Component::ID(ref id) => element.has_id(id, context.shared.classes_and_ids_case_sensitivity()),\n    Component::Class(ref class) => element.has_class(class, context.shared.classes_and_ids_case_sensitivity()),\n    Component::AttributeInNoNamespaceExists {\n      ref local_name,\n      ref local_name_lower,\n    } => {\n      let is_html = element.is_html_element_in_html_document();\n      element.attr_matches(\n        &NamespaceConstraint::Specific(&crate::parser::namespace_empty_string::<E::Impl>()),\n        select_name(is_html, local_name, local_name_lower),\n        &AttrSelectorOperation::Exists,\n      )\n    }\n    Component::AttributeInNoNamespace {\n      ref local_name,\n      ref value,\n      operator,\n      case_sensitivity,\n      never_matches,\n    } => {\n      if never_matches {\n        return false;\n      }\n      let is_html = element.is_html_element_in_html_document();\n      element.attr_matches(\n        &NamespaceConstraint::Specific(&crate::parser::namespace_empty_string::<E::Impl>()),\n        local_name,\n        &AttrSelectorOperation::WithValue {\n          operator,\n          case_sensitivity: case_sensitivity.to_unconditional(is_html),\n          expected_value: value,\n        },\n      )\n    }\n    Component::AttributeOther(ref attr_sel) => {\n      if attr_sel.never_matches {\n        return false;\n      }\n      let is_html = element.is_html_element_in_html_document();\n      let empty_string;\n      let namespace = match attr_sel.namespace() {\n        Some(ns) => ns,\n        None => {\n          empty_string = crate::parser::namespace_empty_string::<E::Impl>();\n          NamespaceConstraint::Specific(&empty_string)\n        }\n      };\n      element.attr_matches(\n        &namespace,\n        select_name(is_html, &attr_sel.local_name, &attr_sel.local_name_lower),\n        &match attr_sel.operation {\n          ParsedAttrSelectorOperation::Exists => AttrSelectorOperation::Exists,\n          ParsedAttrSelectorOperation::WithValue {\n            operator,\n            case_sensitivity,\n            ref expected_value,\n          } => AttrSelectorOperation::WithValue {\n            operator,\n            case_sensitivity: case_sensitivity.to_unconditional(is_html),\n            expected_value,\n          },\n        },\n      )\n    }\n    Component::NonTSPseudoClass(ref pc) => {\n      if context.matches_hover_and_active_quirk == MatchesHoverAndActiveQuirk::Yes\n        && !context.shared.is_nested()\n        && pc.is_active_or_hover()\n        && !element.is_link()\n      {\n        return false;\n      }\n\n      element.match_non_ts_pseudo_class(pc, &mut context.shared, flags_setter)\n    }\n    Component::Root => element.is_root(),\n    Component::Empty => {\n      flags_setter(element, ElementSelectorFlags::HAS_EMPTY_SELECTOR);\n      element.is_empty()\n    }\n    Component::Host(ref selector) => {\n      context.shared.shadow_host().map_or(false, |host| host == element.opaque())\n        && selector.as_ref().map_or(true, |selector| {\n          context\n            .shared\n            .nest(|context| matches_complex_selector(selector.iter(), element, context, flags_setter))\n        })\n    }\n    Component::Scope => match context.shared.scope_element {\n      Some(ref scope_element) => element.opaque() == *scope_element,\n      None => element.is_root(),\n    },\n    Component::Nth(data) => match data.ty {\n      NthType::Child => match (data.a, data.b) {\n        (0, 1) => matches_first_child(element, flags_setter),\n        (a, b) => matches_generic_nth_child(element, context, a, b, false, false, flags_setter),\n      },\n      NthType::LastChild => match (data.a, data.b) {\n        (0, 1) => matches_last_child(element, flags_setter),\n        (a, b) => matches_generic_nth_child(element, context, a, b, false, true, flags_setter),\n      },\n      NthType::OnlyChild => {\n        matches_first_child(element, flags_setter) && matches_last_child(element, flags_setter)\n      }\n      NthType::OfType => matches_generic_nth_child(element, context, data.a, data.b, true, false, flags_setter),\n      NthType::LastOfType => matches_generic_nth_child(element, context, data.a, data.b, true, true, flags_setter),\n      NthType::OnlyOfType => {\n        matches_generic_nth_child(element, context, 0, 1, true, false, flags_setter)\n          && matches_generic_nth_child(element, context, 0, 1, true, true, flags_setter)\n      }\n      _ => todo!(),\n    },\n    Component::NthOf(..) => todo!(),\n    Component::Is(ref list) | Component::Where(ref list) | Component::Any(_, ref list) => {\n      context.shared.nest(|context| {\n        for selector in &**list {\n          if matches_complex_selector(selector.iter(), element, context, flags_setter) {\n            return true;\n          }\n        }\n        false\n      })\n    }\n    Component::Negation(ref list) => context.shared.nest_for_negation(|context| {\n      for selector in &**list {\n        if matches_complex_selector(selector.iter(), element, context, flags_setter) {\n          return false;\n        }\n      }\n      true\n    }),\n    Component::Nesting | Component::Has(..) => unreachable!(),\n  }\n}\n\n#[inline(always)]\nfn select_name<'a, T>(is_html: bool, local_name: &'a T, local_name_lower: &'a T) -> &'a T {\n  if is_html {\n    local_name_lower\n  } else {\n    local_name\n  }\n}\n\n#[inline]\nfn matches_generic_nth_child<'i, E, F>(\n  element: &E,\n  context: &mut LocalMatchingContext<'_, '_, 'i, E::Impl>,\n  a: i32,\n  b: i32,\n  is_of_type: bool,\n  is_from_end: bool,\n  flags_setter: &mut F,\n) -> bool\nwhere\n  E: Element<'i>,\n  F: FnMut(&E, ElementSelectorFlags),\n{\n  if element.ignores_nth_child_selectors() {\n    return false;\n  }\n\n  flags_setter(\n    element,\n    if is_from_end {\n      ElementSelectorFlags::HAS_SLOW_SELECTOR\n    } else {\n      ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS\n    },\n  );\n\n  // Grab a reference to the appropriate cache.\n  let mut cache = context.shared.nth_index_cache.as_mut().map(|c| c.get(is_of_type, is_from_end));\n\n  // Lookup or compute the index.\n  let index = if let Some(i) = cache.as_mut().and_then(|c| c.lookup(element.opaque())) {\n    i\n  } else {\n    let i = nth_child_index(element, is_of_type, is_from_end, cache.as_deref_mut());\n    if let Some(c) = cache.as_mut() {\n      c.insert(element.opaque(), i)\n    }\n    i\n  };\n  debug_assert_eq!(\n    index,\n    nth_child_index(element, is_of_type, is_from_end, None),\n    \"invalid cache\"\n  );\n\n  // Is there a non-negative integer n such that An+B=index?\n  match index.checked_sub(b) {\n    None => false,\n    Some(an) => match an.checked_div(a) {\n            Some(n) => n >= 0 && a * n == an,\n            None /* a == 0 */ => an == 0,\n        },\n  }\n}\n\n#[inline]\nfn nth_child_index<'i, E>(\n  element: &E,\n  is_of_type: bool,\n  is_from_end: bool,\n  mut cache: Option<&mut NthIndexCacheInner>,\n) -> i32\nwhere\n  E: Element<'i>,\n{\n  // The traversal mostly processes siblings left to right. So when we walk\n  // siblings to the right when computing NthLast/NthLastOfType we're unlikely\n  // to get cache hits along the way. As such, we take the hit of walking the\n  // siblings to the left checking the cache in the is_from_end case (this\n  // matches what Gecko does). The indices-from-the-left is handled during the\n  // regular look further below.\n  if let Some(ref mut c) = cache {\n    if is_from_end && !c.is_empty() {\n      let mut index: i32 = 1;\n      let mut curr = element.clone();\n      while let Some(e) = curr.prev_sibling_element() {\n        curr = e;\n        if !is_of_type || element.is_same_type(&curr) {\n          if let Some(i) = c.lookup(curr.opaque()) {\n            return i - index;\n          }\n          index += 1;\n        }\n      }\n    }\n  }\n\n  let mut index: i32 = 1;\n  let mut curr = element.clone();\n  let next = |e: E| {\n    if is_from_end {\n      e.next_sibling_element()\n    } else {\n      e.prev_sibling_element()\n    }\n  };\n  while let Some(e) = next(curr) {\n    curr = e;\n    if !is_of_type || element.is_same_type(&curr) {\n      // If we're computing indices from the left, check each element in the\n      // cache. We handle the indices-from-the-right case at the top of this\n      // function.\n      if !is_from_end {\n        if let Some(i) = cache.as_mut().and_then(|c| c.lookup(curr.opaque())) {\n          return i + index;\n        }\n      }\n      index += 1;\n    }\n  }\n\n  index\n}\n\n#[inline]\nfn matches_first_child<'i, E, F>(element: &E, flags_setter: &mut F) -> bool\nwhere\n  E: Element<'i>,\n  F: FnMut(&E, ElementSelectorFlags),\n{\n  flags_setter(element, ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR);\n  element.prev_sibling_element().is_none()\n}\n\n#[inline]\nfn matches_last_child<'i, E, F>(element: &E, flags_setter: &mut F) -> bool\nwhere\n  E: Element<'i>,\n  F: FnMut(&E, ElementSelectorFlags),\n{\n  flags_setter(element, ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR);\n  element.next_sibling_element().is_none()\n}\n"
  },
  {
    "path": "selectors/nth_index_cache.rs",
    "content": "/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at https://mozilla.org/MPL/2.0/. */\n\nuse crate::tree::OpaqueElement;\nuse rustc_hash::FxHashMap;\n\n/// A cache to speed up matching of nth-index-like selectors.\n///\n/// See [1] for some discussion around the design tradeoffs.\n///\n/// [1] https://bugzilla.mozilla.org/show_bug.cgi?id=1401855#c3\n#[derive(Default)]\npub struct NthIndexCache {\n  nth: NthIndexCacheInner,\n  nth_last: NthIndexCacheInner,\n  nth_of_type: NthIndexCacheInner,\n  nth_last_of_type: NthIndexCacheInner,\n}\n\nimpl NthIndexCache {\n  /// Gets the appropriate cache for the given parameters.\n  pub fn get(&mut self, is_of_type: bool, is_from_end: bool) -> &mut NthIndexCacheInner {\n    match (is_of_type, is_from_end) {\n      (false, false) => &mut self.nth,\n      (false, true) => &mut self.nth_last,\n      (true, false) => &mut self.nth_of_type,\n      (true, true) => &mut self.nth_last_of_type,\n    }\n  }\n}\n\n/// The concrete per-pseudo-class cache.\n#[derive(Default)]\npub struct NthIndexCacheInner(FxHashMap<OpaqueElement, i32>);\n\nimpl NthIndexCacheInner {\n  /// Does a lookup for a given element in the cache.\n  pub fn lookup(&mut self, el: OpaqueElement) -> Option<i32> {\n    self.0.get(&el).copied()\n  }\n\n  /// Inserts an entry into the cache.\n  pub fn insert(&mut self, element: OpaqueElement, index: i32) {\n    self.0.insert(element, index);\n  }\n\n  /// Returns whether the cache is empty.\n  pub fn is_empty(&self) -> bool {\n    self.0.is_empty()\n  }\n}\n"
  },
  {
    "path": "selectors/parser.rs",
    "content": "/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at https://mozilla.org/MPL/2.0/. */\n\nuse crate::attr::{AttrSelectorOperator, AttrSelectorWithOptionalNamespace};\nuse crate::attr::{NamespaceConstraint, ParsedAttrSelectorOperation};\nuse crate::attr::{ParsedCaseSensitivity, SELECTOR_WHITESPACE};\nuse crate::bloom::BLOOM_HASH_MASK;\nuse crate::builder::{SelectorBuilder, SelectorFlags, SpecificityAndFlags};\nuse crate::context::QuirksMode;\nuse crate::sink::Push;\npub use crate::visitor::SelectorVisitor;\nuse cssparser::parse_nth;\nuse cssparser::{BasicParseError, BasicParseErrorKind, ParseError, ParseErrorKind};\nuse cssparser::{CowRcStr, Delimiter, SourceLocation};\nuse cssparser::{Parser as CssParser, ToCss, Token};\nuse precomputed_hash::PrecomputedHash;\nuse smallvec::{smallvec, SmallVec};\nuse std::borrow::Borrow;\nuse std::fmt::{self, Debug};\nuse std::iter::Rev;\nuse std::slice;\n\n/// A trait that represents a pseudo-element.\npub trait PseudoElement<'i>: Sized + ToCss {\n  /// The `SelectorImpl` this pseudo-element is used for.\n  type Impl: SelectorImpl<'i>;\n\n  /// Whether the pseudo-element supports a given state selector to the right\n  /// of it.\n  fn accepts_state_pseudo_classes(&self) -> bool {\n    false\n  }\n\n  /// Whether this pseudo-element is valid after a ::slotted(..) pseudo.\n  fn valid_after_slotted(&self) -> bool {\n    false\n  }\n\n  fn is_webkit_scrollbar(&self) -> bool {\n    false\n  }\n\n  fn is_view_transition(&self) -> bool {\n    false\n  }\n\n  fn is_unknown(&self) -> bool {\n    false\n  }\n}\n\n/// A trait that represents a pseudo-class.\npub trait NonTSPseudoClass<'i>: Sized + ToCss {\n  /// The `SelectorImpl` this pseudo-element is used for.\n  type Impl: SelectorImpl<'i>;\n\n  /// Whether this pseudo-class is :active or :hover.\n  fn is_active_or_hover(&self) -> bool;\n\n  /// Whether this pseudo-class belongs to:\n  ///\n  /// https://drafts.csswg.org/selectors-4/#useraction-pseudos\n  fn is_user_action_state(&self) -> bool;\n\n  fn is_valid_before_webkit_scrollbar(&self) -> bool {\n    true\n  }\n\n  fn is_valid_after_webkit_scrollbar(&self) -> bool {\n    false\n  }\n\n  fn visit<V>(&self, _visitor: &mut V) -> bool\n  where\n    V: SelectorVisitor<'i, Impl = Self::Impl>,\n  {\n    true\n  }\n}\n\n/// Returns a Cow::Borrowed if `s` is already ASCII lowercase, and a\n/// Cow::Owned if `s` had to be converted into ASCII lowercase.\nfn to_ascii_lowercase<'i>(s: CowRcStr<'i>) -> CowRcStr<'i> {\n  if let Some(first_uppercase) = s.bytes().position(|byte| byte >= b'A' && byte <= b'Z') {\n    let mut string = s.to_string();\n    string[first_uppercase..].make_ascii_lowercase();\n    string.into()\n  } else {\n    s\n  }\n}\n\nbitflags! {\n    /// Flags that indicate at which point of parsing a selector are we.\n    #[derive(PartialEq, Eq, Clone, Copy)]\n    struct SelectorParsingState: u16 {\n        /// Whether we should avoid adding default namespaces to selectors that\n        /// aren't type or universal selectors.\n        const SKIP_DEFAULT_NAMESPACE = 1 << 0;\n\n        /// Whether we've parsed a ::slotted() pseudo-element already.\n        ///\n        /// If so, then we can only parse a subset of pseudo-elements, and\n        /// whatever comes after them if so.\n        const AFTER_SLOTTED = 1 << 1;\n        /// Whether we've parsed a ::part() pseudo-element already.\n        ///\n        /// If so, then we can only parse a subset of pseudo-elements, and\n        /// whatever comes after them if so.\n        const AFTER_PART = 1 << 2;\n        /// Whether we've parsed a pseudo-element (as in, an\n        /// `Impl::PseudoElement` thus not accounting for `::slotted` or\n        /// `::part`) already.\n        ///\n        /// If so, then other pseudo-elements and most other selectors are\n        /// disallowed.\n        const AFTER_PSEUDO_ELEMENT = 1 << 3;\n        /// Whether we've parsed a non-stateful pseudo-element (again, as-in\n        /// `Impl::PseudoElement`) already. If so, then other pseudo-classes are\n        /// disallowed. If this flag is set, `AFTER_PSEUDO_ELEMENT` must be set\n        /// as well.\n        const AFTER_NON_STATEFUL_PSEUDO_ELEMENT = 1 << 4;\n\n        /// Whether we are after any of the pseudo-like things.\n        const AFTER_PSEUDO = Self::AFTER_PART.bits() | Self::AFTER_SLOTTED.bits() | Self::AFTER_PSEUDO_ELEMENT.bits();\n\n        /// Whether we explicitly disallow combinators.\n        const DISALLOW_COMBINATORS = 1 << 5;\n\n        /// Whether we explicitly disallow pseudo-element-like things.\n        const DISALLOW_PSEUDOS = 1 << 6;\n\n        /// Whether we have seen a nesting selector.\n        const AFTER_NESTING = 1 << 7;\n\n        const AFTER_WEBKIT_SCROLLBAR = 1 << 8;\n        const AFTER_VIEW_TRANSITION = 1 << 9;\n        const AFTER_UNKNOWN_PSEUDO_ELEMENT = 1 << 10;\n    }\n}\n\nimpl SelectorParsingState {\n  #[inline]\n  fn allows_pseudos(self) -> bool {\n    // NOTE(emilio): We allow pseudos after ::part and such.\n    !self.intersects(Self::AFTER_PSEUDO_ELEMENT | Self::DISALLOW_PSEUDOS)\n  }\n\n  #[inline]\n  fn allows_slotted(self) -> bool {\n    !self.intersects(Self::AFTER_PSEUDO | Self::DISALLOW_PSEUDOS)\n  }\n\n  #[inline]\n  fn allows_part(self) -> bool {\n    !self.intersects(Self::AFTER_PSEUDO | Self::DISALLOW_PSEUDOS)\n  }\n\n  // TODO(emilio): Maybe some of these should be allowed, but this gets us on\n  // the safe side for now, matching previous behavior. Gotta be careful with\n  // the ones like :-moz-any, which allow nested selectors but don't carry the\n  // state, and so on.\n  #[inline]\n  fn allows_custom_functional_pseudo_classes(self) -> bool {\n    !self.intersects(Self::AFTER_PSEUDO)\n  }\n\n  #[inline]\n  fn allows_non_functional_pseudo_classes(self) -> bool {\n    !self.intersects(Self::AFTER_SLOTTED | Self::AFTER_NON_STATEFUL_PSEUDO_ELEMENT)\n  }\n\n  #[inline]\n  fn allows_tree_structural_pseudo_classes(self) -> bool {\n    !self.intersects(Self::AFTER_PSEUDO)\n  }\n\n  #[inline]\n  fn allows_combinators(self) -> bool {\n    !self.intersects(Self::DISALLOW_COMBINATORS)\n  }\n}\n\npub type SelectorParseError<'i> = ParseError<'i, SelectorParseErrorKind<'i>>;\n\n#[derive(Clone, Debug, PartialEq)]\npub enum SelectorParseErrorKind<'i> {\n  NoQualifiedNameInAttributeSelector(Token<'i>),\n  EmptySelector,\n  DanglingCombinator,\n  InvalidPseudoClassBeforeWebKitScrollbar,\n  InvalidPseudoClassAfterWebKitScrollbar,\n  InvalidPseudoClassAfterPseudoElement,\n  InvalidState,\n  MissingNestingSelector,\n  MissingNestingPrefix,\n  UnexpectedTokenInAttributeSelector(Token<'i>),\n  PseudoElementExpectedIdent(Token<'i>),\n  UnsupportedPseudoElement(CowRcStr<'i>),\n  UnsupportedPseudoClass(CowRcStr<'i>),\n  AmbiguousCssModuleClass(CowRcStr<'i>),\n  UnexpectedIdent(CowRcStr<'i>),\n  ExpectedNamespace(CowRcStr<'i>),\n  ExpectedBarInAttr(Token<'i>),\n  BadValueInAttr(Token<'i>),\n  InvalidQualNameInAttr(Token<'i>),\n  ExplicitNamespaceUnexpectedToken(Token<'i>),\n  ClassNeedsIdent(Token<'i>),\n  UnexpectedSelectorAfterPseudoElement(Token<'i>),\n}\n\nmacro_rules! with_all_bounds {\n    (\n        [ $( $InSelector: tt )* ]\n        [ $( $CommonBounds: tt )* ]\n        [ $( $FromStr: tt )* ]\n    ) => {\n        /// This trait allows to define the parser implementation in regards\n        /// of pseudo-classes/elements\n        ///\n        /// NB: We need Clone so that we can derive(Clone) on struct with that\n        /// are parameterized on SelectorImpl. See\n        /// <https://github.com/rust-lang/rust/issues/26925>\n        pub trait SelectorImpl<'i>: Clone + Debug + Sized + 'static {\n            type ExtraMatchingData: Sized + Default + 'static;\n            type AttrValue: $($InSelector)*;\n            type Identifier: $($InSelector)*;\n            type LocalName: $($InSelector)* + Borrow<Self::BorrowedLocalName>;\n            type NamespaceUrl: $($CommonBounds)* + $($FromStr)* + Default + Borrow<Self::BorrowedNamespaceUrl>;\n            type NamespacePrefix: $($InSelector)* + Default;\n            type BorrowedNamespaceUrl: ?Sized + Eq;\n            type BorrowedLocalName: ?Sized + Eq;\n\n            /// non tree-structural pseudo-classes\n            /// (see: https://drafts.csswg.org/selectors/#structural-pseudos)\n            type NonTSPseudoClass: $($CommonBounds)* + NonTSPseudoClass<'i, Impl = Self>;\n            type VendorPrefix: Sized + Eq + $($CommonBounds)* + ToCss;\n\n            /// pseudo-elements\n            type PseudoElement: $($CommonBounds)* + PseudoElement<'i, Impl = Self>;\n\n            fn to_css<W: fmt::Write>(selectors: &SelectorList<'i, Self>, dest: &mut W) -> fmt::Result {\n                serialize_selector_list(selectors.0.iter(), dest)\n            }\n        }\n    }\n}\n\nmacro_rules! with_bounds {\n    ( [ $( $CommonBounds: tt )* ] [ $( $FromStr: tt )* ]) => {\n        with_all_bounds! {\n            [$($CommonBounds)* + $($FromStr)* + ToCss]\n            [$($CommonBounds)*]\n            [$($FromStr)*]\n        }\n    }\n}\n\n#[cfg(feature = \"serde\")]\nwith_bounds! {\n    [Clone + PartialEq + Eq + std::hash::Hash]\n    [From<CowRcStr<'i>> + From<std::borrow::Cow<'i, str>> + AsRef<str>]\n}\n\n#[cfg(not(feature = \"serde\"))]\nwith_bounds! {\n    [Clone + PartialEq + Eq + std::hash::Hash]\n    [From<CowRcStr<'i>>]\n}\n\npub trait Parser<'i> {\n  type Impl: SelectorImpl<'i>;\n  type Error: 'i + From<SelectorParseErrorKind<'i>>;\n\n  /// Whether to parse the `::slotted()` pseudo-element.\n  fn parse_slotted(&self) -> bool {\n    false\n  }\n\n  /// Whether to parse the `::part()` pseudo-element.\n  fn parse_part(&self) -> bool {\n    false\n  }\n\n  /// Whether to parse the `:where` pseudo-class.\n  fn parse_is_and_where(&self) -> bool {\n    false\n  }\n\n  /// The error recovery that selector lists inside :is() and :where() have.\n  fn is_and_where_error_recovery(&self) -> ParseErrorRecovery {\n    ParseErrorRecovery::IgnoreInvalidSelector\n  }\n\n  /// Whether the given function name is an alias for the `:is()` function.\n  fn parse_any_prefix(&self, _name: &str) -> Option<<Self::Impl as SelectorImpl<'i>>::VendorPrefix> {\n    None\n  }\n\n  /// Whether to parse the `:host` pseudo-class.\n  fn parse_host(&self) -> bool {\n    false\n  }\n\n  /// Parses non-tree-structural pseudo-classes. Tree structural pseudo-classes,\n  /// like `:first-child`, are built into this library.\n  ///\n  /// This function can return an \"Err\" pseudo-element in order to support CSS2.1\n  /// pseudo-elements.\n  fn parse_non_ts_pseudo_class(\n    &self,\n    location: SourceLocation,\n    name: CowRcStr<'i>,\n  ) -> Result<<Self::Impl as SelectorImpl<'i>>::NonTSPseudoClass, ParseError<'i, Self::Error>> {\n    Err(location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClass(name)))\n  }\n\n  fn parse_non_ts_functional_pseudo_class<'t>(\n    &self,\n    name: CowRcStr<'i>,\n    arguments: &mut CssParser<'i, 't>,\n  ) -> Result<<Self::Impl as SelectorImpl<'i>>::NonTSPseudoClass, ParseError<'i, Self::Error>> {\n    Err(arguments.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClass(name)))\n  }\n\n  fn parse_pseudo_element(\n    &self,\n    location: SourceLocation,\n    name: CowRcStr<'i>,\n  ) -> Result<<Self::Impl as SelectorImpl<'i>>::PseudoElement, ParseError<'i, Self::Error>> {\n    Err(location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoElement(name)))\n  }\n\n  fn parse_functional_pseudo_element<'t>(\n    &self,\n    name: CowRcStr<'i>,\n    arguments: &mut CssParser<'i, 't>,\n  ) -> Result<<Self::Impl as SelectorImpl<'i>>::PseudoElement, ParseError<'i, Self::Error>> {\n    Err(arguments.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoElement(name)))\n  }\n\n  fn default_namespace(&self) -> Option<<Self::Impl as SelectorImpl<'i>>::NamespaceUrl> {\n    None\n  }\n\n  fn namespace_for_prefix(\n    &self,\n    _prefix: &<Self::Impl as SelectorImpl<'i>>::NamespacePrefix,\n  ) -> Option<<Self::Impl as SelectorImpl<'i>>::NamespaceUrl> {\n    None\n  }\n\n  fn is_nesting_allowed(&self) -> bool {\n    false\n  }\n\n  fn deep_combinator_enabled(&self) -> bool {\n    false\n  }\n}\n\n#[derive(Clone, Debug, PartialEq, Eq, Hash)]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(bound(\n    serialize = \"Impl::NonTSPseudoClass: serde::Serialize, Impl::PseudoElement: serde::Serialize, Impl::VendorPrefix: serde::Serialize\",\n    deserialize = \"Impl::NonTSPseudoClass: serde::Deserialize<'de>, Impl::PseudoElement: serde::Deserialize<'de>, Impl::VendorPrefix: serde::Deserialize<'de>\"\n  ))\n)]\n#[cfg_attr(\n  feature = \"jsonschema\",\n  derive(schemars::JsonSchema),\n  schemars(\n    rename = \"SelectorList\",\n    bound = \"Impl: schemars::JsonSchema, Impl::NonTSPseudoClass: schemars::JsonSchema, Impl::PseudoElement: schemars::JsonSchema, Impl::VendorPrefix: schemars::JsonSchema\"\n  )\n)]\npub struct SelectorList<'i, Impl: SelectorImpl<'i>>(\n  #[cfg_attr(feature = \"serde\", serde(borrow))] pub SmallVec<[Selector<'i, Impl>; 1]>,\n);\n\n#[cfg(feature = \"into_owned\")]\nimpl<'any, 'i, Impl: SelectorImpl<'i>, NewSel> static_self::IntoOwned<'any> for SelectorList<'i, Impl>\nwhere\n  Impl: static_self::IntoOwned<'any, Owned = NewSel>,\n  NewSel: SelectorImpl<'any>,\n  Component<'i, Impl>: static_self::IntoOwned<'any, Owned = Component<'any, NewSel>>,\n{\n  type Owned = SelectorList<'any, NewSel>;\n\n  fn into_owned(self) -> Self::Owned {\n    SelectorList(self.0.into_owned())\n  }\n}\n\n/// How to treat invalid selectors in a selector list.\npub enum ParseErrorRecovery {\n  /// Discard the entire selector list, this is the default behavior for\n  /// almost all of CSS.\n  DiscardList,\n  /// Ignore invalid selectors, potentially creating an empty selector list.\n  ///\n  /// This is the error recovery mode of :is() and :where()\n  IgnoreInvalidSelector,\n}\n\n#[derive(Eq, PartialEq, Clone, Copy)]\npub enum NestingRequirement {\n  None,\n  Prefixed,\n  Contained,\n  Implicit,\n}\n\nimpl<'i, Impl: SelectorImpl<'i>> SelectorList<'i, Impl> {\n  /// Parse a comma-separated list of Selectors.\n  /// <https://drafts.csswg.org/selectors/#grouping>\n  ///\n  /// Return the Selectors or Err if there is an invalid selector.\n  pub fn parse<'t, P>(\n    parser: &P,\n    input: &mut CssParser<'i, 't>,\n    error_recovery: ParseErrorRecovery,\n    nesting_requirement: NestingRequirement,\n  ) -> Result<Self, ParseError<'i, P::Error>>\n  where\n    P: Parser<'i, Impl = Impl>,\n  {\n    Self::parse_with_state(\n      parser,\n      input,\n      &mut SelectorParsingState::empty(),\n      error_recovery,\n      nesting_requirement,\n    )\n  }\n\n  #[inline]\n  fn parse_with_state<'t, P>(\n    parser: &P,\n    input: &mut CssParser<'i, 't>,\n    state: &mut SelectorParsingState,\n    recovery: ParseErrorRecovery,\n    nesting_requirement: NestingRequirement,\n  ) -> Result<Self, ParseError<'i, P::Error>>\n  where\n    P: Parser<'i, Impl = Impl>,\n  {\n    let original_state = *state;\n    let mut values = SmallVec::new();\n    loop {\n      let selector = input.parse_until_before(Delimiter::Comma, |input| {\n        let mut selector_state = original_state;\n        let result = parse_selector(parser, input, &mut selector_state, nesting_requirement);\n        if selector_state.contains(SelectorParsingState::AFTER_NESTING) {\n          state.insert(SelectorParsingState::AFTER_NESTING)\n        }\n        result\n      });\n\n      let was_ok = selector.is_ok();\n      match selector {\n        Ok(selector) => values.push(selector),\n        Err(err) => match recovery {\n          ParseErrorRecovery::DiscardList => return Err(err),\n          ParseErrorRecovery::IgnoreInvalidSelector => {}\n        },\n      }\n\n      loop {\n        match input.next() {\n          Err(_) => return Ok(SelectorList(values)),\n          Ok(&Token::Comma) => break,\n          Ok(_) => {\n            debug_assert!(!was_ok, \"Shouldn't have got a selector if getting here\");\n          }\n        }\n      }\n    }\n  }\n\n  pub fn parse_relative<'t, P>(\n    parser: &P,\n    input: &mut CssParser<'i, 't>,\n    error_recovery: ParseErrorRecovery,\n    nesting_requirement: NestingRequirement,\n  ) -> Result<Self, ParseError<'i, P::Error>>\n  where\n    P: Parser<'i, Impl = Impl>,\n  {\n    Self::parse_relative_with_state(\n      parser,\n      input,\n      &mut SelectorParsingState::empty(),\n      error_recovery,\n      nesting_requirement,\n    )\n  }\n\n  #[inline]\n  fn parse_relative_with_state<'t, P>(\n    parser: &P,\n    input: &mut CssParser<'i, 't>,\n    state: &mut SelectorParsingState,\n    recovery: ParseErrorRecovery,\n    nesting_requirement: NestingRequirement,\n  ) -> Result<Self, ParseError<'i, P::Error>>\n  where\n    P: Parser<'i, Impl = Impl>,\n  {\n    let original_state = *state;\n    let mut values = SmallVec::new();\n    loop {\n      let selector = input.parse_until_before(Delimiter::Comma, |input| {\n        let mut selector_state = original_state;\n        let result = parse_relative_selector(parser, input, &mut selector_state, nesting_requirement);\n        if selector_state.contains(SelectorParsingState::AFTER_NESTING) {\n          state.insert(SelectorParsingState::AFTER_NESTING)\n        }\n        result\n      });\n\n      let was_ok = selector.is_ok();\n      match selector {\n        Ok(selector) => values.push(selector),\n        Err(err) => match recovery {\n          ParseErrorRecovery::DiscardList => return Err(err),\n          ParseErrorRecovery::IgnoreInvalidSelector => {}\n        },\n      }\n\n      loop {\n        match input.next() {\n          Err(_) => return Ok(SelectorList(values)),\n          Ok(&Token::Comma) => break,\n          Ok(_) => {\n            debug_assert!(!was_ok, \"Shouldn't have got a selector if getting here\");\n          }\n        }\n      }\n    }\n  }\n\n  /// Creates a new SelectorList.\n  pub fn new(v: SmallVec<[Selector<'i, Impl>; 1]>) -> Self {\n    SelectorList(v)\n  }\n\n  /// Creates a SelectorList from a Vec of selectors. Used in tests.\n  pub fn from_vec(v: Vec<Selector<'i, Impl>>) -> Self {\n    SelectorList(SmallVec::from_vec(v))\n  }\n}\n\nimpl<'i, Impl: SelectorImpl<'i>> From<Selector<'i, Impl>> for SelectorList<'i, Impl> {\n  fn from(selector: Selector<'i, Impl>) -> Self {\n    SelectorList(smallvec![selector])\n  }\n}\n\nimpl<'i, Impl: SelectorImpl<'i>> From<Component<'i, Impl>> for SelectorList<'i, Impl> {\n  fn from(component: Component<'i, Impl>) -> Self {\n    SelectorList::from(Selector::from(component))\n  }\n}\n\n/// Parses one compound selector suitable for nested stuff like :-moz-any, etc.\nfn parse_inner_compound_selector<'i, 't, P, Impl>(\n  parser: &P,\n  input: &mut CssParser<'i, 't>,\n  state: &mut SelectorParsingState,\n) -> Result<Selector<'i, Impl>, ParseError<'i, P::Error>>\nwhere\n  P: Parser<'i, Impl = Impl>,\n  Impl: SelectorImpl<'i>,\n{\n  let mut child_state =\n    *state | SelectorParsingState::DISALLOW_PSEUDOS | SelectorParsingState::DISALLOW_COMBINATORS;\n  let result = parse_selector(parser, input, &mut child_state, NestingRequirement::None)?;\n  if child_state.contains(SelectorParsingState::AFTER_NESTING) {\n    state.insert(SelectorParsingState::AFTER_NESTING)\n  }\n  Ok(result)\n}\n\n/// Ancestor hashes for the bloom filter. We precompute these and store them\n/// inline with selectors to optimize cache performance during matching.\n/// This matters a lot.\n///\n/// We use 4 hashes, which is copied from Gecko, who copied it from WebKit.\n/// Note that increasing the number of hashes here will adversely affect the\n/// cache hit when fast-rejecting long lists of Rules with inline hashes.\n///\n/// Because the bloom filter only uses the bottom 24 bits of the hash, we pack\n/// the fourth hash into the upper bits of the first three hashes in order to\n/// shrink Rule (whose size matters a lot). This scheme minimizes the runtime\n/// overhead of the packing for the first three hashes (we just need to mask\n/// off the upper bits) at the expense of making the fourth somewhat more\n/// complicated to assemble, because we often bail out before checking all the\n/// hashes.\n#[derive(Clone, Debug, Eq, PartialEq)]\npub struct AncestorHashes {\n  pub packed_hashes: [u32; 3],\n}\n\nfn collect_ancestor_hashes<'i, Impl: SelectorImpl<'i>>(\n  iter: SelectorIter<'_, 'i, Impl>,\n  quirks_mode: QuirksMode,\n  hashes: &mut [u32; 4],\n  len: &mut usize,\n) -> bool\nwhere\n  Impl::Identifier: PrecomputedHash,\n  Impl::LocalName: PrecomputedHash,\n  Impl::NamespaceUrl: PrecomputedHash,\n{\n  for component in AncestorIter::new(iter) {\n    let hash = match *component {\n      Component::LocalName(LocalName {\n        ref name,\n        ref lower_name,\n      }) => {\n        // Only insert the local-name into the filter if it's all\n        // lowercase.  Otherwise we would need to test both hashes, and\n        // our data structures aren't really set up for that.\n        if name != lower_name {\n          continue;\n        }\n        name.precomputed_hash()\n      }\n      Component::DefaultNamespace(ref url) | Component::Namespace(_, ref url) => url.precomputed_hash(),\n      // In quirks mode, class and id selectors should match\n      // case-insensitively, so just avoid inserting them into the filter.\n      Component::ID(ref id) if quirks_mode != QuirksMode::Quirks => id.precomputed_hash(),\n      Component::Class(ref class) if quirks_mode != QuirksMode::Quirks => class.precomputed_hash(),\n      Component::Is(ref list) | Component::Where(ref list) => {\n        // :where and :is OR their selectors, so we can't put any hash\n        // in the filter if there's more than one selector, as that'd\n        // exclude elements that may match one of the other selectors.\n        if list.len() == 1 && !collect_ancestor_hashes(list[0].iter(), quirks_mode, hashes, len) {\n          return false;\n        }\n        continue;\n      }\n      _ => continue,\n    };\n\n    hashes[*len] = hash & BLOOM_HASH_MASK;\n    *len += 1;\n    if *len == hashes.len() {\n      return false;\n    }\n  }\n  true\n}\n\nimpl AncestorHashes {\n  pub fn new<'i, Impl: SelectorImpl<'i>>(selector: &Selector<'i, Impl>, quirks_mode: QuirksMode) -> Self\n  where\n    Impl::Identifier: PrecomputedHash,\n    Impl::LocalName: PrecomputedHash,\n    Impl::NamespaceUrl: PrecomputedHash,\n  {\n    // Compute ancestor hashes for the bloom filter.\n    let mut hashes = [0u32; 4];\n    let mut len = 0;\n    collect_ancestor_hashes(selector.iter(), quirks_mode, &mut hashes, &mut len);\n    debug_assert!(len <= 4);\n\n    // Now, pack the fourth hash (if it exists) into the upper byte of each of\n    // the other three hashes.\n    if len == 4 {\n      let fourth = hashes[3];\n      hashes[0] |= (fourth & 0x000000ff) << 24;\n      hashes[1] |= (fourth & 0x0000ff00) << 16;\n      hashes[2] |= (fourth & 0x00ff0000) << 8;\n    }\n\n    AncestorHashes {\n      packed_hashes: [hashes[0], hashes[1], hashes[2]],\n    }\n  }\n\n  /// Returns the fourth hash, reassembled from parts.\n  pub fn fourth_hash(&self) -> u32 {\n    ((self.packed_hashes[0] & 0xff000000) >> 24)\n      | ((self.packed_hashes[1] & 0xff000000) >> 16)\n      | ((self.packed_hashes[2] & 0xff000000) >> 8)\n  }\n}\n\npub fn namespace_empty_string<'i, Impl: SelectorImpl<'i>>() -> Impl::NamespaceUrl {\n  // Rust type’s default, not default namespace\n  Impl::NamespaceUrl::default()\n}\n\n/// A Selector stores a sequence of simple selectors and combinators. The\n/// iterator classes allow callers to iterate at either the raw sequence level or\n/// at the level of sequences of simple selectors separated by combinators. Most\n/// callers want the higher-level iterator.\n///\n/// We store compound selectors internally right-to-left (in matching order).\n/// Additionally, we invert the order of top-level compound selectors so that\n/// each one matches left-to-right. This is because matching namespace, local name,\n/// id, and class are all relatively cheap, whereas matching pseudo-classes might\n/// be expensive (depending on the pseudo-class). Since authors tend to put the\n/// pseudo-classes on the right, it's faster to start matching on the left.\n///\n/// This reordering doesn't change the semantics of selector matching, and we\n/// handle it in to_css to make it invisible to serialization.\n#[derive(Clone, PartialEq, Eq, Hash)]\npub struct Selector<'i, Impl: SelectorImpl<'i>>(SpecificityAndFlags, Vec<Component<'i, Impl>>);\n\n#[cfg(feature = \"into_owned\")]\nimpl<'any, 'i, Impl: SelectorImpl<'i>, NewSel> static_self::IntoOwned<'any> for Selector<'i, Impl>\nwhere\n  Impl: static_self::IntoOwned<'any, Owned = NewSel>,\n  NewSel: SelectorImpl<'any>,\n  Component<'i, Impl>: static_self::IntoOwned<'any, Owned = Component<'any, NewSel>>,\n{\n  type Owned = Selector<'any, NewSel>;\n\n  fn into_owned(self) -> Self::Owned {\n    Selector(self.0, self.1.into_owned())\n  }\n}\n\nimpl<'i, Impl: SelectorImpl<'i>> Selector<'i, Impl> {\n  #[inline]\n  pub fn specificity(&self) -> u32 {\n    self.0.specificity()\n  }\n\n  #[inline]\n  pub fn has_pseudo_element(&self) -> bool {\n    self.0.has_pseudo_element()\n  }\n\n  #[inline]\n  pub fn is_slotted(&self) -> bool {\n    self.0.is_slotted()\n  }\n\n  #[inline]\n  pub fn is_part(&self) -> bool {\n    self.0.is_part()\n  }\n\n  #[inline]\n  pub fn append(&mut self, component: Component<'i, Impl>) {\n    let index = self\n      .1\n      .iter()\n      .position(|c| matches!(*c, Component::Combinator(..) | Component::PseudoElement(..)))\n      .unwrap_or(self.1.len());\n    self.1.insert(index, component);\n  }\n\n  #[inline]\n  pub fn parts(&self) -> Option<&[Impl::Identifier]> {\n    if !self.is_part() {\n      return None;\n    }\n\n    let mut iter = self.iter();\n    if self.has_pseudo_element() {\n      // Skip the pseudo-element.\n      for _ in &mut iter {}\n\n      let combinator = iter.next_sequence()?;\n      debug_assert_eq!(combinator, Combinator::PseudoElement);\n    }\n\n    for component in iter {\n      if let Component::Part(ref part) = *component {\n        return Some(part);\n      }\n    }\n\n    debug_assert!(false, \"is_part() lied somehow?\");\n    None\n  }\n\n  #[inline]\n  pub fn pseudo_element(&self) -> Option<&Impl::PseudoElement> {\n    if !self.has_pseudo_element() {\n      return None;\n    }\n\n    for component in self.iter() {\n      if let Component::PseudoElement(ref pseudo) = *component {\n        return Some(pseudo);\n      }\n    }\n\n    debug_assert!(false, \"has_pseudo_element lied!\");\n    None\n  }\n\n  /// Whether this selector (pseudo-element part excluded) matches every element.\n  ///\n  /// Used for \"pre-computed\" pseudo-elements in components/style/stylist.rs\n  #[inline]\n  pub fn is_universal(&self) -> bool {\n    self.iter_raw_match_order().all(|c| {\n      matches!(\n        *c,\n        Component::ExplicitUniversalType\n          | Component::ExplicitAnyNamespace\n          | Component::Combinator(Combinator::PseudoElement)\n          | Component::PseudoElement(..)\n      )\n    })\n  }\n\n  #[inline]\n  pub fn has_combinator(&self) -> bool {\n    self\n      .iter_raw_match_order()\n      .any(|c| matches!(*c, Component::Combinator(combinator) if combinator.is_tree_combinator()))\n  }\n\n  /// Returns an iterator over this selector in matching order (right-to-left).\n  /// When a combinator is reached, the iterator will return None, and\n  /// next_sequence() may be called to continue to the next sequence.\n  #[inline]\n  pub fn iter(&self) -> SelectorIter<'_, 'i, Impl> {\n    SelectorIter {\n      iter: self.iter_raw_match_order(),\n      next_combinator: None,\n    }\n  }\n\n  /// Whether this selector is a featureless :host selector, with no\n  /// combinators to the left, and optionally has a pseudo-element to the\n  /// right.\n  #[inline]\n  pub fn is_featureless_host_selector_or_pseudo_element(&self) -> bool {\n    let mut iter = self.iter();\n    if !self.has_pseudo_element() {\n      return iter.is_featureless_host_selector();\n    }\n\n    // Skip the pseudo-element.\n    for _ in &mut iter {}\n\n    match iter.next_sequence() {\n      None => return false,\n      Some(combinator) => {\n        debug_assert_eq!(combinator, Combinator::PseudoElement);\n      }\n    }\n\n    iter.is_featureless_host_selector()\n  }\n\n  /// Returns an iterator over this selector in matching order (right-to-left),\n  /// skipping the rightmost |offset| Components.\n  #[inline]\n  pub fn iter_from(&self, offset: usize) -> SelectorIter<'_, 'i, Impl> {\n    let iter = self.1[offset..].iter();\n    SelectorIter {\n      iter,\n      next_combinator: None,\n    }\n  }\n\n  /// Returns the combinator at index `index` (zero-indexed from the right),\n  /// or panics if the component is not a combinator.\n  #[inline]\n  pub fn combinator_at_match_order(&self, index: usize) -> Combinator {\n    match self.1[index] {\n      Component::Combinator(c) => c,\n      ref other => panic!(\"Not a combinator: {:?}, {:?}, index: {}\", other, self, index),\n    }\n  }\n\n  /// Returns an iterator over the entire sequence of simple selectors and\n  /// combinators, in matching order (from right to left).\n  #[inline]\n  pub fn iter_raw_match_order(&self) -> slice::Iter<'_, Component<'i, Impl>> {\n    self.1.iter()\n  }\n\n  #[inline]\n  pub fn iter_mut_raw_match_order(&mut self) -> slice::IterMut<'_, Component<'i, Impl>> {\n    self.1.iter_mut()\n  }\n\n  /// Returns the combinator at index `index` (zero-indexed from the left),\n  /// or panics if the component is not a combinator.\n  #[inline]\n  pub fn combinator_at_parse_order(&self, index: usize) -> Combinator {\n    match self.1[self.len() - index - 1] {\n      Component::Combinator(c) => c,\n      ref other => panic!(\"Not a combinator: {:?}, {:?}, index: {}\", other, self, index),\n    }\n  }\n\n  /// Returns an iterator over the sequence of simple selectors and\n  /// combinators, in parse order (from left to right), starting from\n  /// `offset`.\n  #[inline]\n  pub fn iter_raw_parse_order_from(&self, offset: usize) -> Rev<slice::Iter<'_, Component<'i, Impl>>> {\n    self.1[..self.len() - offset].iter().rev()\n  }\n\n  /// Creates a Selector from a vec of Components, specified in parse order. Used in tests.\n  #[allow(unused)]\n  pub(crate) fn from_vec(vec: Vec<Component<'i, Impl>>, specificity: u32, flags: SelectorFlags) -> Self {\n    let mut builder = SelectorBuilder::default();\n    for component in vec.into_iter() {\n      if let Some(combinator) = component.as_combinator() {\n        builder.push_combinator(combinator);\n      } else {\n        builder.push_simple_selector(component);\n      }\n    }\n    let spec = SpecificityAndFlags { specificity, flags };\n    let (spec, components) = builder.build_with_specificity_and_flags(spec);\n    Selector(spec, components)\n  }\n\n  #[cfg(feature = \"serde\")]\n  #[inline]\n  pub(crate) fn new(spec: SpecificityAndFlags, components: Vec<Component<'i, Impl>>) -> Self {\n    Selector(spec, components)\n  }\n\n  /// Returns count of simple selectors and combinators in the Selector.\n  #[inline]\n  pub fn len(&self) -> usize {\n    self.1.len()\n  }\n\n  /// Traverse selector components inside `self`.\n  ///\n  /// Implementations of this method should call `SelectorVisitor` methods\n  /// or other impls of `Visit` as appropriate based on the fields of `Self`.\n  ///\n  /// A return value of `false` indicates terminating the traversal.\n  /// It should be propagated with an early return.\n  /// On the contrary, `true` indicates that all fields of `self` have been traversed:\n  ///\n  /// ```rust,ignore\n  /// if !visitor.visit_simple_selector(&self.some_simple_selector) {\n  ///     return false;\n  /// }\n  /// if !self.some_component.visit(visitor) {\n  ///     return false;\n  /// }\n  /// true\n  /// ```\n  pub fn visit<V>(&self, visitor: &mut V) -> bool\n  where\n    V: SelectorVisitor<'i, Impl = Impl>,\n  {\n    let mut current = self.iter();\n    let mut combinator = None;\n    loop {\n      if !visitor.visit_complex_selector(combinator) {\n        return false;\n      }\n\n      for selector in &mut current {\n        if !selector.visit(visitor) {\n          return false;\n        }\n      }\n\n      combinator = current.next_sequence();\n      if combinator.is_none() {\n        break;\n      }\n    }\n\n    true\n  }\n}\n\nimpl<'i, Impl: SelectorImpl<'i>> From<Component<'i, Impl>> for Selector<'i, Impl> {\n  fn from(component: Component<'i, Impl>) -> Self {\n    let mut builder = SelectorBuilder::default();\n    if let Some(combinator) = component.as_combinator() {\n      builder.push_combinator(combinator);\n    } else {\n      builder.push_simple_selector(component);\n    }\n    let (spec, components) = builder.build(false, false, false);\n    Selector(spec, components)\n  }\n}\n\nimpl<'i, Impl: SelectorImpl<'i>> From<Vec<Component<'i, Impl>>> for Selector<'i, Impl> {\n  fn from(vec: Vec<Component<'i, Impl>>) -> Self {\n    let mut builder = SelectorBuilder::default();\n    for component in vec.into_iter() {\n      if let Some(combinator) = component.as_combinator() {\n        builder.push_combinator(combinator);\n      } else {\n        builder.push_simple_selector(component);\n      }\n    }\n    let (spec, components) = builder.build(false, false, false);\n    Selector(spec, components)\n  }\n}\n\n#[derive(Clone)]\npub struct SelectorIter<'a, 'i, Impl: SelectorImpl<'i>> {\n  iter: slice::Iter<'a, Component<'i, Impl>>,\n  next_combinator: Option<Combinator>,\n}\n\nimpl<'a, 'i, Impl: 'a + SelectorImpl<'i>> SelectorIter<'a, 'i, Impl> {\n  /// Prepares this iterator to point to the next sequence to the left,\n  /// returning the combinator if the sequence was found.\n  #[inline]\n  pub fn next_sequence(&mut self) -> Option<Combinator> {\n    self.next_combinator.take()\n  }\n\n  /// Whether this selector is a featureless host selector, with no\n  /// combinators to the left.\n  #[inline]\n  pub(crate) fn is_featureless_host_selector(&mut self) -> bool {\n    self.selector_length() > 0\n      && self.all(|component| matches!(*component, Component::Host(..)))\n      && self.next_sequence().is_none()\n  }\n\n  #[inline]\n  pub(crate) fn matches_for_stateless_pseudo_element(&mut self) -> bool {\n    let first = match self.next() {\n      Some(c) => c,\n      // Note that this is the common path that we keep inline: the\n      // pseudo-element not having anything to its right.\n      None => return true,\n    };\n    self.matches_for_stateless_pseudo_element_internal(first)\n  }\n\n  #[inline(never)]\n  fn matches_for_stateless_pseudo_element_internal(&mut self, first: &Component<'i, Impl>) -> bool {\n    if !first.matches_for_stateless_pseudo_element() {\n      return false;\n    }\n    for component in self {\n      // The only other parser-allowed Components in this sequence are\n      // state pseudo-classes, or one of the other things that can contain\n      // them.\n      if !component.matches_for_stateless_pseudo_element() {\n        return false;\n      }\n    }\n    true\n  }\n\n  /// Returns remaining count of the simple selectors and combinators in the Selector.\n  #[inline]\n  pub fn selector_length(&self) -> usize {\n    self.iter.len()\n  }\n}\n\nimpl<'a, 'i, Impl: SelectorImpl<'i>> Iterator for SelectorIter<'a, 'i, Impl> {\n  type Item = &'a Component<'i, Impl>;\n\n  #[inline]\n  fn next(&mut self) -> Option<Self::Item> {\n    debug_assert!(self.next_combinator.is_none(), \"You should call next_sequence!\");\n    match *self.iter.next()? {\n      Component::Combinator(c) => {\n        self.next_combinator = Some(c);\n        None\n      }\n      ref x => Some(x),\n    }\n  }\n}\n\nimpl<'a, 'i, Impl: SelectorImpl<'i>> fmt::Debug for SelectorIter<'a, 'i, Impl> {\n  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n    let iter = self.iter.clone().rev();\n    for component in iter {\n      component.to_css(f)?\n    }\n    Ok(())\n  }\n}\n\n/// An iterator over all simple selectors belonging to ancestors.\nstruct AncestorIter<'a, 'i, Impl: SelectorImpl<'i>>(SelectorIter<'a, 'i, Impl>);\nimpl<'a, 'i, Impl: 'a + SelectorImpl<'i>> AncestorIter<'a, 'i, Impl> {\n  /// Creates an AncestorIter. The passed-in iterator is assumed to point to\n  /// the beginning of the child sequence, which will be skipped.\n  fn new(inner: SelectorIter<'a, 'i, Impl>) -> Self {\n    let mut result = AncestorIter(inner);\n    result.skip_until_ancestor();\n    result\n  }\n\n  /// Skips a sequence of simple selectors and all subsequent sequences until\n  /// a non-pseudo-element ancestor combinator is reached.\n  fn skip_until_ancestor(&mut self) {\n    loop {\n      while self.0.next().is_some() {}\n      // If this is ever changed to stop at the \"pseudo-element\"\n      // combinator, we will need to fix the way we compute hashes for\n      // revalidation selectors.\n      if self\n        .0\n        .next_sequence()\n        .map_or(true, |x| matches!(x, Combinator::Child | Combinator::Descendant))\n      {\n        break;\n      }\n    }\n  }\n}\n\nimpl<'a, 'i, Impl: SelectorImpl<'i>> Iterator for AncestorIter<'a, 'i, Impl> {\n  type Item = &'a Component<'i, Impl>;\n  fn next(&mut self) -> Option<Self::Item> {\n    // Grab the next simple selector in the sequence if available.\n    let next = self.0.next();\n    if next.is_some() {\n      return next;\n    }\n\n    // See if there are more sequences. If so, skip any non-ancestor sequences.\n    if let Some(combinator) = self.0.next_sequence() {\n      if !matches!(combinator, Combinator::Child | Combinator::Descendant) {\n        self.skip_until_ancestor();\n      }\n    }\n\n    self.0.next()\n  }\n}\n\n#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum Combinator {\n  Child,        //  >\n  Descendant,   // space\n  NextSibling,  // +\n  LaterSibling, // ~\n  /// A dummy combinator we use to the left of pseudo-elements.\n  ///\n  /// It serializes as the empty string, and acts effectively as a child\n  /// combinator in most cases.  If we ever actually start using a child\n  /// combinator for this, we will need to fix up the way hashes are computed\n  /// for revalidation selectors.\n  PseudoElement,\n  /// Another combinator used for ::slotted(), which represent the jump from\n  /// a node to its assigned slot.\n  SlotAssignment,\n  /// Another combinator used for `::part()`, which represents the jump from\n  /// the part to the containing shadow host.\n  Part,\n\n  /// Non-standard Vue >>> combinator.\n  /// https://vue-loader.vuejs.org/guide/scoped-css.html#deep-selectors\n  DeepDescendant,\n  /// Non-standard /deep/ combinator.\n  /// Appeared in early versions of the css-scoping-1 specification:\n  /// https://www.w3.org/TR/2014/WD-css-scoping-1-20140403/#deep-combinator\n  /// And still supported as an alias for >>> by Vue.\n  Deep,\n}\n\nimpl Combinator {\n  /// Returns true if this combinator is a child or descendant combinator.\n  #[inline]\n  pub fn is_ancestor(&self) -> bool {\n    matches!(\n      *self,\n      Combinator::Child | Combinator::Descendant | Combinator::PseudoElement | Combinator::SlotAssignment\n    )\n  }\n\n  /// Returns true if this combinator is a pseudo-element combinator.\n  #[inline]\n  pub fn is_pseudo_element(&self) -> bool {\n    matches!(*self, Combinator::PseudoElement)\n  }\n\n  /// Returns true if this combinator is a next- or later-sibling combinator.\n  #[inline]\n  pub fn is_sibling(&self) -> bool {\n    matches!(*self, Combinator::NextSibling | Combinator::LaterSibling)\n  }\n\n  #[inline]\n  pub fn is_tree_combinator(&self) -> bool {\n    matches!(\n      *self,\n      Combinator::Child | Combinator::Descendant | Combinator::NextSibling | Combinator::LaterSibling\n    )\n  }\n}\n\n/// An enum for the different types of :nth- pseudoclasses\n#[derive(Copy, Clone, Eq, PartialEq, Hash)]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum NthType {\n  Child,\n  LastChild,\n  OnlyChild,\n  OfType,\n  LastOfType,\n  OnlyOfType,\n  Col,\n  LastCol,\n}\n\nimpl NthType {\n  pub fn is_only(self) -> bool {\n    self == Self::OnlyChild || self == Self::OnlyOfType\n  }\n\n  pub fn is_of_type(self) -> bool {\n    self == Self::OfType || self == Self::LastOfType || self == Self::OnlyOfType\n  }\n\n  pub fn is_from_end(self) -> bool {\n    self == Self::LastChild || self == Self::LastOfType || self == Self::LastCol\n  }\n\n  pub fn allows_of_selector(self) -> bool {\n    self == Self::Child || self == Self::LastChild\n  }\n}\n\n/// The properties that comprise an :nth- pseudoclass as of Selectors 3 (e.g.,\n/// nth-child(An+B)).\n/// https://www.w3.org/TR/selectors-3/#nth-child-pseudo\n#[derive(Copy, Clone, Eq, PartialEq, Hash)]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub struct NthSelectorData {\n  pub ty: NthType,\n  pub is_function: bool,\n  pub a: i32,\n  pub b: i32,\n}\n\nimpl NthSelectorData {\n  /// Returns selector data for :only-{child,of-type}\n  #[inline]\n  pub const fn only(of_type: bool) -> Self {\n    Self {\n      ty: if of_type {\n        NthType::OnlyOfType\n      } else {\n        NthType::OnlyChild\n      },\n      is_function: false,\n      a: 0,\n      b: 1,\n    }\n  }\n\n  /// Returns selector data for :first-{child,of-type}\n  #[inline]\n  pub const fn first(of_type: bool) -> Self {\n    Self {\n      ty: if of_type { NthType::OfType } else { NthType::Child },\n      is_function: false,\n      a: 0,\n      b: 1,\n    }\n  }\n\n  /// Returns selector data for :last-{child,of-type}\n  #[inline]\n  pub const fn last(of_type: bool) -> Self {\n    Self {\n      ty: if of_type {\n        NthType::LastOfType\n      } else {\n        NthType::LastChild\n      },\n      is_function: false,\n      a: 0,\n      b: 1,\n    }\n  }\n\n  #[inline]\n  pub fn is_function(&self) -> bool {\n    self.a != 0 || self.b != 1\n  }\n\n  /// Writes the beginning of the selector.\n  #[inline]\n  pub fn write_start<W: fmt::Write>(&self, dest: &mut W, is_function: bool) -> fmt::Result {\n    dest.write_str(match self.ty {\n      NthType::Child if is_function => \":nth-child(\",\n      NthType::Child => \":first-child\",\n      NthType::LastChild if is_function => \":nth-last-child(\",\n      NthType::LastChild => \":last-child\",\n      NthType::OfType if is_function => \":nth-of-type(\",\n      NthType::OfType => \":first-of-type\",\n      NthType::LastOfType if is_function => \":nth-last-of-type(\",\n      NthType::LastOfType => \":last-of-type\",\n      NthType::OnlyChild => \":only-child\",\n      NthType::OnlyOfType => \":only-of-type\",\n      NthType::Col => \":nth-col(\",\n      NthType::LastCol => \":nth-last-col(\",\n    })\n  }\n\n  /// Serialize <an+b> (part of the CSS Syntax spec, but currently only used here).\n  /// <https://drafts.csswg.org/css-syntax-3/#serialize-an-anb-value>\n  #[inline]\n  pub fn write_affine<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result {\n    match (self.a, self.b) {\n      (0, 0) => dest.write_char('0'),\n\n      (1, 0) => dest.write_char('n'),\n      (-1, 0) => dest.write_str(\"-n\"),\n      (_, 0) => write!(dest, \"{}n\", self.a),\n\n      (2, 1) => dest.write_str(\"odd\"),\n\n      (0, _) => write!(dest, \"{}\", self.b),\n      (1, _) => write!(dest, \"n{:+}\", self.b),\n      (-1, _) => write!(dest, \"-n{:+}\", self.b),\n      (_, _) => write!(dest, \"{}n{:+}\", self.a, self.b),\n    }\n  }\n}\n\n/// The properties that comprise an :nth- pseudoclass as of Selectors 4 (e.g.,\n/// nth-child(An+B [of S]?)).\n/// https://www.w3.org/TR/selectors-4/#nth-child-pseudo\n#[derive(Clone, PartialEq, Eq, Hash)]\npub struct NthOfSelectorData<'i, Impl: SelectorImpl<'i>>(NthSelectorData, Box<[Selector<'i, Impl>]>);\n\n#[cfg(feature = \"into_owned\")]\nimpl<'any, 'i, Impl: SelectorImpl<'i>, NewSel> static_self::IntoOwned<'any> for NthOfSelectorData<'i, Impl>\nwhere\n  Impl: static_self::IntoOwned<'any, Owned = NewSel>,\n  NewSel: SelectorImpl<'any>,\n  Component<'i, Impl>: static_self::IntoOwned<'any, Owned = Component<'any, NewSel>>,\n{\n  type Owned = NthOfSelectorData<'any, NewSel>;\n\n  fn into_owned(self) -> Self::Owned {\n    NthOfSelectorData(self.0, self.1.into_owned())\n  }\n}\n\nimpl<'i, Impl: SelectorImpl<'i>> NthOfSelectorData<'i, Impl> {\n  /// Returns selector data for :nth-{,last-}{child,of-type}(An+B [of S])\n  #[inline]\n  pub fn new(nth_data: NthSelectorData, selectors: Box<[Selector<'i, Impl>]>) -> Self {\n    Self(nth_data, selectors)\n  }\n\n  /// Returns the An+B part of the selector\n  #[inline]\n  pub fn nth_data(&self) -> &NthSelectorData {\n    &self.0\n  }\n\n  /// Returns the selector list part of the selector\n  #[inline]\n  pub fn selectors(&self) -> &[Selector<'i, Impl>] {\n    &*self.1\n  }\n\n  pub fn clone_selectors(&self) -> Box<[Selector<'i, Impl>]> {\n    self.1.clone()\n  }\n}\n\n/// A CSS simple selector or combinator. We store both in the same enum for\n/// optimal packing and cache performance, see [1].\n///\n/// [1] https://bugzilla.mozilla.org/show_bug.cgi?id=1357973\n#[derive(Clone, PartialEq, Eq, Hash)]\npub enum Component<'i, Impl: SelectorImpl<'i>> {\n  Combinator(Combinator),\n\n  ExplicitAnyNamespace,\n  ExplicitNoNamespace,\n  DefaultNamespace(Impl::NamespaceUrl),\n  Namespace(Impl::NamespacePrefix, Impl::NamespaceUrl),\n\n  ExplicitUniversalType,\n  LocalName(LocalName<'i, Impl>),\n\n  ID(Impl::Identifier),\n  Class(Impl::Identifier),\n\n  AttributeInNoNamespaceExists {\n    local_name: Impl::LocalName,\n    local_name_lower: Impl::LocalName,\n  },\n  // Used only when local_name is already lowercase.\n  AttributeInNoNamespace {\n    local_name: Impl::LocalName,\n    operator: AttrSelectorOperator,\n    value: Impl::AttrValue,\n    case_sensitivity: ParsedCaseSensitivity,\n    never_matches: bool,\n  },\n  // Use a Box in the less common cases with more data to keep size_of::<Component>() small.\n  AttributeOther(Box<AttrSelectorWithOptionalNamespace<'i, Impl>>),\n\n  /// Pseudo-classes\n  Negation(Box<[Selector<'i, Impl>]>),\n  Root,\n  Empty,\n  Scope,\n  Nth(NthSelectorData),\n  NthOf(NthOfSelectorData<'i, Impl>),\n  NonTSPseudoClass(Impl::NonTSPseudoClass),\n  /// The ::slotted() pseudo-element:\n  ///\n  /// https://drafts.csswg.org/css-scoping/#slotted-pseudo\n  ///\n  /// The selector here is a compound selector, that is, no combinators.\n  ///\n  /// NOTE(emilio): This should support a list of selectors, but as of this\n  /// writing no other browser does, and that allows them to put ::slotted()\n  /// in the rule hash, so we do that too.\n  ///\n  /// See https://github.com/w3c/csswg-drafts/issues/2158\n  Slotted(Selector<'i, Impl>),\n  /// The `::part` pseudo-element.\n  ///   https://drafts.csswg.org/css-shadow-parts/#part\n  Part(Box<[Impl::Identifier]>),\n  /// The `:host` pseudo-class:\n  ///\n  /// https://drafts.csswg.org/css-scoping/#host-selector\n  ///\n  /// NOTE(emilio): This should support a list of selectors, but as of this\n  /// writing no other browser does, and that allows them to put :host()\n  /// in the rule hash, so we do that too.\n  ///\n  /// See https://github.com/w3c/csswg-drafts/issues/2158\n  Host(Option<Selector<'i, Impl>>),\n  /// The `:where` pseudo-class.\n  ///\n  /// https://drafts.csswg.org/selectors/#zero-matches\n  ///\n  /// The inner argument is conceptually a SelectorList, but we move the\n  /// selectors to the heap to keep Component small.\n  Where(Box<[Selector<'i, Impl>]>),\n  /// The `:is` pseudo-class.\n  ///\n  /// https://drafts.csswg.org/selectors/#matches-pseudo\n  ///\n  /// Same comment as above re. the argument.\n  Is(Box<[Selector<'i, Impl>]>),\n  Any(Impl::VendorPrefix, Box<[Selector<'i, Impl>]>),\n  /// The `:has` pseudo-class.\n  ///\n  /// https://www.w3.org/TR/selectors/#relational\n  Has(Box<[Selector<'i, Impl>]>),\n  /// An implementation-dependent pseudo-element selector.\n  PseudoElement(Impl::PseudoElement),\n  /// A nesting selector:\n  ///\n  /// https://drafts.csswg.org/css-nesting-1/#nest-selector\n  ///\n  /// NOTE: This is a lightningcss addition.\n  Nesting,\n}\n\n#[cfg(feature = \"into_owned\")]\nimpl<'any, 'i, Impl: SelectorImpl<'i>, NewSel> static_self::IntoOwned<'any> for Component<'i, Impl>\nwhere\n  Impl: static_self::IntoOwned<'any, Owned = NewSel>,\n  NewSel: SelectorImpl<'any>,\n  Impl::NamespaceUrl: static_self::IntoOwned<'any, Owned = NewSel::NamespaceUrl>,\n  Impl::NamespacePrefix: static_self::IntoOwned<'any, Owned = NewSel::NamespacePrefix>,\n  Impl::Identifier: static_self::IntoOwned<'any, Owned = NewSel::Identifier>,\n  Impl::LocalName: static_self::IntoOwned<'any, Owned = NewSel::LocalName>,\n  Impl::AttrValue: static_self::IntoOwned<'any, Owned = NewSel::AttrValue>,\n  Impl::NonTSPseudoClass: static_self::IntoOwned<'any, Owned = NewSel::NonTSPseudoClass>,\n  Impl::PseudoElement: static_self::IntoOwned<'any, Owned = NewSel::PseudoElement>,\n  Impl::VendorPrefix: static_self::IntoOwned<'any, Owned = NewSel::VendorPrefix>,\n{\n  type Owned = Component<'any, NewSel>;\n\n  fn into_owned(self) -> Self::Owned {\n    match self {\n      Component::Combinator(c) => Component::Combinator(c.into_owned()),\n      Component::ExplicitAnyNamespace => Component::ExplicitAnyNamespace,\n      Component::ExplicitNoNamespace => Component::ExplicitNoNamespace,\n      Component::DefaultNamespace(c) => Component::DefaultNamespace(c.into_owned()),\n      Component::Namespace(a, b) => Component::Namespace(a.into_owned(), b.into_owned()),\n      Component::ExplicitUniversalType => Component::ExplicitUniversalType,\n      Component::LocalName(c) => Component::LocalName(c.into_owned()),\n      Component::ID(c) => Component::ID(c.into_owned()),\n      Component::Class(c) => Component::Class(c.into_owned()),\n      Component::AttributeInNoNamespaceExists {\n        local_name,\n        local_name_lower,\n      } => Component::AttributeInNoNamespaceExists {\n        local_name: local_name.into_owned(),\n        local_name_lower: local_name_lower.into_owned(),\n      },\n      Component::AttributeInNoNamespace {\n        local_name,\n        operator,\n        value,\n        case_sensitivity,\n        never_matches,\n      } => {\n        let value = value.into_owned();\n        Component::AttributeInNoNamespace {\n          local_name: local_name.into_owned(),\n          operator,\n          value,\n          case_sensitivity,\n          never_matches,\n        }\n      }\n      Component::AttributeOther(c) => Component::AttributeOther(c.into_owned()),\n      Component::Negation(c) => Component::Negation(c.into_owned()),\n      Component::Root => Component::Root,\n      Component::Empty => Component::Empty,\n      Component::Scope => Component::Scope,\n      Component::Nth(c) => Component::Nth(c.into_owned()),\n      Component::NthOf(c) => Component::NthOf(c.into_owned()),\n      Component::NonTSPseudoClass(c) => Component::NonTSPseudoClass(c.into_owned()),\n      Component::Slotted(c) => Component::Slotted(c.into_owned()),\n      Component::Part(c) => Component::Part(c.into_owned()),\n      Component::Host(c) => Component::Host(c.into_owned()),\n      Component::Where(c) => Component::Where(c.into_owned()),\n      Component::Is(c) => Component::Is(c.into_owned()),\n      Component::Any(a, b) => Component::Any(a.into_owned(), b.into_owned()),\n      Component::Has(c) => Component::Has(c.into_owned()),\n      Component::PseudoElement(c) => Component::PseudoElement(c.into_owned()),\n      Component::Nesting => Component::Nesting,\n    }\n  }\n}\n\nimpl<'i, Impl: SelectorImpl<'i>> Component<'i, Impl> {\n  /// Returns true if this is a combinator.\n  pub fn is_combinator(&self) -> bool {\n    matches!(*self, Component::Combinator(_))\n  }\n\n  /// Returns the value as a combinator if applicable, None otherwise.\n  pub fn as_combinator(&self) -> Option<Combinator> {\n    match *self {\n      Component::Combinator(c) => Some(c),\n      _ => None,\n    }\n  }\n\n  /// Whether this component is valid after a pseudo-element. Only intended\n  /// for sanity-checking.\n  pub fn maybe_allowed_after_pseudo_element(&self) -> bool {\n    match *self {\n      Component::NonTSPseudoClass(..) => true,\n      Component::Negation(ref selectors) | Component::Is(ref selectors) | Component::Where(ref selectors) => {\n        selectors\n          .iter()\n          .all(|selector| selector.iter_raw_match_order().all(|c| c.maybe_allowed_after_pseudo_element()))\n      }\n      _ => false,\n    }\n  }\n\n  /// Whether a given selector should match for stateless pseudo-elements.\n  ///\n  /// This is a bit subtle: Only selectors that return true in\n  /// `maybe_allowed_after_pseudo_element` should end up here, and\n  /// `NonTSPseudoClass` never matches (as it is a stateless pseudo after\n  /// all).\n  fn matches_for_stateless_pseudo_element(&self) -> bool {\n    debug_assert!(\n      self.maybe_allowed_after_pseudo_element(),\n      \"Someone messed up pseudo-element parsing: {:?}\",\n      *self\n    );\n    match *self {\n      Component::Negation(ref selectors) => !selectors.iter().all(|selector| {\n        selector\n          .iter_raw_match_order()\n          .all(|c| c.matches_for_stateless_pseudo_element())\n      }),\n      Component::Is(ref selectors) | Component::Where(ref selectors) => selectors.iter().any(|selector| {\n        selector\n          .iter_raw_match_order()\n          .all(|c| c.matches_for_stateless_pseudo_element())\n      }),\n      _ => false,\n    }\n  }\n\n  pub fn visit<V>(&self, visitor: &mut V) -> bool\n  where\n    V: SelectorVisitor<'i, Impl = Impl>,\n  {\n    use self::Component::*;\n    if !visitor.visit_simple_selector(self) {\n      return false;\n    }\n\n    match *self {\n      Slotted(ref selector) => {\n        if !selector.visit(visitor) {\n          return false;\n        }\n      }\n      Host(Some(ref selector)) => {\n        if !selector.visit(visitor) {\n          return false;\n        }\n      }\n      AttributeInNoNamespaceExists {\n        ref local_name,\n        ref local_name_lower,\n      } => {\n        if !visitor.visit_attribute_selector(\n          &NamespaceConstraint::Specific(&namespace_empty_string::<Impl>()),\n          local_name,\n          local_name_lower,\n        ) {\n          return false;\n        }\n      }\n      AttributeInNoNamespace {\n        ref local_name,\n        never_matches,\n        ..\n      } if !never_matches => {\n        if !visitor.visit_attribute_selector(\n          &NamespaceConstraint::Specific(&namespace_empty_string::<Impl>()),\n          local_name,\n          local_name,\n        ) {\n          return false;\n        }\n      }\n      AttributeOther(ref attr_selector) if !attr_selector.never_matches => {\n        let empty_string;\n        let namespace = match attr_selector.namespace() {\n          Some(ns) => ns,\n          None => {\n            empty_string = crate::parser::namespace_empty_string::<Impl>();\n            NamespaceConstraint::Specific(&empty_string)\n          }\n        };\n        if !visitor.visit_attribute_selector(\n          &namespace,\n          &attr_selector.local_name,\n          &attr_selector.local_name_lower,\n        ) {\n          return false;\n        }\n      }\n\n      NonTSPseudoClass(ref pseudo_class) => {\n        if !pseudo_class.visit(visitor) {\n          return false;\n        }\n      }\n\n      Negation(ref list) | Is(ref list) | Where(ref list) => {\n        if !visitor.visit_selector_list(&list) {\n          return false;\n        }\n      }\n      NthOf(ref nth_of_data) => {\n        if !visitor.visit_selector_list(nth_of_data.selectors()) {\n          return false;\n        }\n      }\n      _ => {}\n    }\n\n    true\n  }\n}\n\n#[derive(Clone, Eq, PartialEq, Hash)]\npub struct LocalName<'i, Impl: SelectorImpl<'i>> {\n  pub name: Impl::LocalName,\n  pub lower_name: Impl::LocalName,\n}\n\n#[cfg(feature = \"into_owned\")]\nimpl<'any, 'i, Impl: SelectorImpl<'i>, NewSel> static_self::IntoOwned<'any> for LocalName<'i, Impl>\nwhere\n  Impl: static_self::IntoOwned<'any, Owned = NewSel>,\n  NewSel: SelectorImpl<'any>,\n  Impl::LocalName: static_self::IntoOwned<'any, Owned = NewSel::LocalName>,\n{\n  type Owned = LocalName<'any, NewSel>;\n\n  fn into_owned(self) -> Self::Owned {\n    LocalName {\n      name: self.name.into_owned(),\n      lower_name: self.lower_name.into_owned(),\n    }\n  }\n}\n\nimpl<'i, Impl: SelectorImpl<'i>> Debug for Selector<'i, Impl> {\n  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n    f.write_str(\"Selector(\")?;\n    self.to_css(f)?;\n    write!(f, \", specificity = 0x{:x})\", self.specificity())\n  }\n}\n\nimpl<'i, Impl: SelectorImpl<'i>> Debug for Component<'i, Impl> {\n  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n    self.to_css(f)\n  }\n}\nimpl<'i, Impl: SelectorImpl<'i>> Debug for AttrSelectorWithOptionalNamespace<'i, Impl> {\n  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n    self.to_css(f)\n  }\n}\nimpl<'i, Impl: SelectorImpl<'i>> Debug for LocalName<'i, Impl> {\n  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n    self.to_css(f)\n  }\n}\n\n#[cfg(feature = \"serde\")]\nimpl<'i, Impl: SelectorImpl<'i>> serde::Serialize for LocalName<'i, Impl>\nwhere\n  Impl::LocalName: serde::Serialize,\n{\n  fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>\n  where\n    S: serde::Serializer,\n  {\n    self.name.serialize(serializer)\n  }\n}\n\n#[cfg(feature = \"serde\")]\nimpl<'i, 'de: 'i, Impl: SelectorImpl<'i>> serde::Deserialize<'de> for LocalName<'i, Impl>\nwhere\n  Impl::LocalName: serde::Deserialize<'de>,\n{\n  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n  where\n    D: serde::Deserializer<'de>,\n  {\n    let name = Impl::LocalName::deserialize(deserializer)?;\n    let lower_name = to_ascii_lowercase(name.as_ref().to_string().into()).into();\n    Ok(LocalName { name, lower_name })\n  }\n}\n\nfn serialize_selector_list<'a, 'i: 'a, Impl, I, W>(iter: I, dest: &mut W) -> fmt::Result\nwhere\n  Impl: SelectorImpl<'i>,\n  I: Iterator<Item = &'a Selector<'i, Impl>>,\n  W: fmt::Write,\n{\n  let mut first = true;\n  for selector in iter {\n    if !first {\n      dest.write_str(\", \")?;\n    }\n    first = false;\n    selector.to_css(dest)?;\n  }\n  Ok(())\n}\n\nimpl<'i, Impl: SelectorImpl<'i>> ToCss for SelectorList<'i, Impl> {\n  fn to_css<W>(&self, dest: &mut W) -> fmt::Result\n  where\n    W: fmt::Write,\n  {\n    serialize_selector_list(self.0.iter(), dest)\n  }\n}\n\nimpl<'i, Impl: SelectorImpl<'i>> fmt::Display for SelectorList<'i, Impl> {\n  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {\n    Impl::to_css(self, f)\n  }\n}\n\nimpl<'i, Impl: SelectorImpl<'i>> ToCss for Selector<'i, Impl> {\n  fn to_css<W>(&self, dest: &mut W) -> fmt::Result\n  where\n    W: fmt::Write,\n  {\n    // Compound selectors invert the order of their contents, so we need to\n    // undo that during serialization.\n    //\n    // This two-iterator strategy involves walking over the selector twice.\n    // We could do something more clever, but selector serialization probably\n    // isn't hot enough to justify it, and the stringification likely\n    // dominates anyway.\n    //\n    // NB: A parse-order iterator is a Rev<>, which doesn't expose as_slice(),\n    // which we need for |split|. So we split by combinators on a match-order\n    // sequence and then reverse.\n\n    let mut combinators = self.iter_raw_match_order().rev().filter_map(|x| x.as_combinator());\n    let compound_selectors = self.iter_raw_match_order().as_slice().split(|x| x.is_combinator()).rev();\n\n    let mut combinators_exhausted = false;\n    for compound in compound_selectors {\n      debug_assert!(!combinators_exhausted);\n\n      // https://drafts.csswg.org/cssom/#serializing-selectors\n      if compound.is_empty() {\n        continue;\n      }\n\n      // 1. If there is only one simple selector in the compound selectors\n      //    which is a universal selector, append the result of\n      //    serializing the universal selector to s.\n      //\n      // Check if `!compound.empty()` first--this can happen if we have\n      // something like `... > ::before`, because we store `>` and `::`\n      // both as combinators internally.\n      //\n      // If we are in this case, after we have serialized the universal\n      // selector, we skip Step 2 and continue with the algorithm.\n      let (can_elide_namespace, first_non_namespace) = match compound[0] {\n        Component::ExplicitAnyNamespace | Component::ExplicitNoNamespace | Component::Namespace(..) => (false, 1),\n        Component::DefaultNamespace(..) => (true, 1),\n        _ => (true, 0),\n      };\n      let mut perform_step_2 = true;\n      let next_combinator = combinators.next();\n      if first_non_namespace == compound.len() - 1 {\n        match (next_combinator, &compound[first_non_namespace]) {\n          // We have to be careful here, because if there is a\n          // pseudo element \"combinator\" there isn't really just\n          // the one simple selector. Technically this compound\n          // selector contains the pseudo element selector as well\n          // -- Combinator::PseudoElement, just like\n          // Combinator::SlotAssignment, don't exist in the\n          // spec.\n          (Some(Combinator::PseudoElement), _) | (Some(Combinator::SlotAssignment), _) => (),\n          (_, &Component::ExplicitUniversalType) => {\n            // Iterate over everything so we serialize the namespace\n            // too.\n            for simple in compound.iter() {\n              simple.to_css(dest)?;\n            }\n            // Skip step 2, which is an \"otherwise\".\n            perform_step_2 = false;\n          }\n          _ => (),\n        }\n      }\n\n      // 2. Otherwise, for each simple selector in the compound selectors\n      //    that is not a universal selector of which the namespace prefix\n      //    maps to a namespace that is not the default namespace\n      //    serialize the simple selector and append the result to s.\n      //\n      // See https://github.com/w3c/csswg-drafts/issues/1606, which is\n      // proposing to change this to match up with the behavior asserted\n      // in cssom/serialize-namespaced-type-selectors.html, which the\n      // following code tries to match.\n      if perform_step_2 {\n        for simple in compound.iter() {\n          if let Component::ExplicitUniversalType = *simple {\n            // Can't have a namespace followed by a pseudo-element\n            // selector followed by a universal selector in the same\n            // compound selector, so we don't have to worry about the\n            // real namespace being in a different `compound`.\n            if can_elide_namespace {\n              continue;\n            }\n          }\n          simple.to_css(dest)?;\n        }\n      }\n\n      // 3. If this is not the last part of the chain of the selector\n      //    append a single SPACE (U+0020), followed by the combinator\n      //    \">\", \"+\", \"~\", \">>\", \"||\", as appropriate, followed by another\n      //    single SPACE (U+0020) if the combinator was not whitespace, to\n      //    s.\n      match next_combinator {\n        Some(c) => c.to_css(dest)?,\n        None => combinators_exhausted = true,\n      };\n\n      // 4. If this is the last part of the chain of the selector and\n      //    there is a pseudo-element, append \"::\" followed by the name of\n      //    the pseudo-element, to s.\n      //\n      // (we handle this above)\n    }\n\n    Ok(())\n  }\n}\n\nimpl ToCss for Combinator {\n  fn to_css<W>(&self, dest: &mut W) -> fmt::Result\n  where\n    W: fmt::Write,\n  {\n    match *self {\n      Combinator::Child => dest.write_str(\" > \"),\n      Combinator::Descendant => dest.write_str(\" \"),\n      Combinator::NextSibling => dest.write_str(\" + \"),\n      Combinator::LaterSibling => dest.write_str(\" ~ \"),\n      Combinator::DeepDescendant => dest.write_str(\" >>> \"),\n      Combinator::Deep => dest.write_str(\" /deep/ \"),\n      Combinator::PseudoElement | Combinator::Part | Combinator::SlotAssignment => Ok(()),\n    }\n  }\n}\n\nimpl<'i, Impl: SelectorImpl<'i>> ToCss for Component<'i, Impl> {\n  fn to_css<W>(&self, dest: &mut W) -> fmt::Result\n  where\n    W: fmt::Write,\n  {\n    use self::Component::*;\n\n    match *self {\n      Combinator(ref c) => c.to_css(dest),\n      Slotted(ref selector) => {\n        dest.write_str(\"::slotted(\")?;\n        selector.to_css(dest)?;\n        dest.write_char(')')\n      }\n      Part(ref part_names) => {\n        dest.write_str(\"::part(\")?;\n        for (i, name) in part_names.iter().enumerate() {\n          if i != 0 {\n            dest.write_char(' ')?;\n          }\n          name.to_css(dest)?;\n        }\n        dest.write_char(')')\n      }\n      PseudoElement(ref p) => p.to_css(dest),\n      ID(ref s) => {\n        dest.write_char('#')?;\n        s.to_css(dest)\n      }\n      Class(ref s) => {\n        dest.write_char('.')?;\n        s.to_css(dest)\n      }\n      LocalName(ref s) => s.to_css(dest),\n      ExplicitUniversalType => dest.write_char('*'),\n\n      DefaultNamespace(_) => Ok(()),\n      ExplicitNoNamespace => dest.write_char('|'),\n      ExplicitAnyNamespace => dest.write_str(\"*|\"),\n      Namespace(ref prefix, _) => {\n        prefix.to_css(dest)?;\n        dest.write_char('|')\n      }\n\n      AttributeInNoNamespaceExists { ref local_name, .. } => {\n        dest.write_char('[')?;\n        local_name.to_css(dest)?;\n        dest.write_char(']')\n      }\n      AttributeInNoNamespace {\n        ref local_name,\n        operator,\n        ref value,\n        case_sensitivity,\n        ..\n      } => {\n        dest.write_char('[')?;\n        local_name.to_css(dest)?;\n        operator.to_css(dest)?;\n        value.to_css(dest)?;\n        match case_sensitivity {\n          ParsedCaseSensitivity::CaseSensitive\n          | ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument => {}\n          ParsedCaseSensitivity::AsciiCaseInsensitive => dest.write_str(\" i\")?,\n          ParsedCaseSensitivity::ExplicitCaseSensitive => dest.write_str(\" s\")?,\n        }\n        dest.write_char(']')\n      }\n      AttributeOther(ref attr_selector) => attr_selector.to_css(dest),\n\n      // Pseudo-classes\n      Root => dest.write_str(\":root\"),\n      Empty => dest.write_str(\":empty\"),\n      Scope => dest.write_str(\":scope\"),\n      Host(ref selector) => {\n        dest.write_str(\":host\")?;\n        if let Some(ref selector) = *selector {\n          dest.write_char('(')?;\n          selector.to_css(dest)?;\n          dest.write_char(')')?;\n        }\n        Ok(())\n      }\n      Nth(ref nth_data) => {\n        nth_data.write_start(dest, nth_data.is_function())?;\n        if nth_data.is_function() {\n          nth_data.write_affine(dest)?;\n          dest.write_char(')')?;\n        }\n        Ok(())\n      }\n      NthOf(ref nth_of_data) => {\n        let nth_data = nth_of_data.nth_data();\n        nth_data.write_start(dest, true)?;\n        debug_assert!(\n          nth_data.is_function,\n          \"A selector must be a function to hold An+B notation\"\n        );\n        nth_data.write_affine(dest)?;\n        debug_assert!(\n          matches!(nth_data.ty, NthType::Child | NthType::LastChild),\n          \"Only :nth-child or :nth-last-child can be of a selector list\"\n        );\n        debug_assert!(\n          !nth_of_data.selectors().is_empty(),\n          \"The selector list should not be empty\"\n        );\n        dest.write_str(\" of \")?;\n        serialize_selector_list(nth_of_data.selectors().iter(), dest)?;\n        dest.write_char(')')\n      }\n      Is(ref list) | Where(ref list) | Negation(ref list) | Has(ref list) | Any(_, ref list) => {\n        match *self {\n          Where(..) => dest.write_str(\":where(\")?,\n          Is(..) => dest.write_str(\":is(\")?,\n          Negation(..) => dest.write_str(\":not(\")?,\n          Has(..) => dest.write_str(\":has(\")?,\n          Any(ref prefix, _) => {\n            dest.write_char(':')?;\n            prefix.to_css(dest)?;\n            dest.write_str(\"any(\")?;\n          }\n          _ => unreachable!(),\n        }\n        serialize_selector_list(list.iter(), dest)?;\n        dest.write_str(\")\")\n      }\n      NonTSPseudoClass(ref pseudo) => pseudo.to_css(dest),\n      Nesting => dest.write_char('&'),\n    }\n  }\n}\n\nimpl<'i, Impl: SelectorImpl<'i>> ToCss for AttrSelectorWithOptionalNamespace<'i, Impl> {\n  fn to_css<W>(&self, dest: &mut W) -> fmt::Result\n  where\n    W: fmt::Write,\n  {\n    dest.write_char('[')?;\n    match self.namespace {\n      Some(NamespaceConstraint::Specific((ref prefix, _))) => {\n        prefix.to_css(dest)?;\n        dest.write_char('|')?\n      }\n      Some(NamespaceConstraint::Any) => dest.write_str(\"*|\")?,\n      None => {}\n    }\n    self.local_name.to_css(dest)?;\n    match self.operation {\n      ParsedAttrSelectorOperation::Exists => {}\n      ParsedAttrSelectorOperation::WithValue {\n        operator,\n        case_sensitivity,\n        ref expected_value,\n      } => {\n        operator.to_css(dest)?;\n        expected_value.to_css(dest)?;\n        match case_sensitivity {\n          ParsedCaseSensitivity::CaseSensitive\n          | ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument => {}\n          ParsedCaseSensitivity::AsciiCaseInsensitive => dest.write_str(\" i\")?,\n          ParsedCaseSensitivity::ExplicitCaseSensitive => dest.write_str(\" s\")?,\n        }\n      }\n    }\n    dest.write_char(']')\n  }\n}\n\nimpl<'i, Impl: SelectorImpl<'i>> ToCss for LocalName<'i, Impl> {\n  fn to_css<W>(&self, dest: &mut W) -> fmt::Result\n  where\n    W: fmt::Write,\n  {\n    self.name.to_css(dest)\n  }\n}\n\n/// Build up a Selector.\n/// selector : simple_selector_sequence [ combinator simple_selector_sequence ]* ;\n///\n/// `Err` means invalid selector.\nfn parse_selector<'i, 't, P, Impl>(\n  parser: &P,\n  input: &mut CssParser<'i, 't>,\n  state: &mut SelectorParsingState,\n  nesting_requirement: NestingRequirement,\n) -> Result<Selector<'i, Impl>, ParseError<'i, P::Error>>\nwhere\n  P: Parser<'i, Impl = Impl>,\n  Impl: SelectorImpl<'i>,\n{\n  if nesting_requirement == NestingRequirement::Prefixed {\n    let state = input.state();\n    if !input.expect_delim('&').is_ok() {\n      return Err(input.new_custom_error(SelectorParseErrorKind::MissingNestingPrefix));\n    }\n    input.reset(&state);\n  }\n\n  let mut builder = SelectorBuilder::default();\n\n  'outer_loop: loop {\n    // Parse a sequence of simple selectors.\n    let empty = parse_compound_selector(parser, state, input, &mut builder)?;\n    if empty {\n      return Err(input.new_custom_error(if builder.has_combinators() {\n        SelectorParseErrorKind::DanglingCombinator\n      } else {\n        SelectorParseErrorKind::EmptySelector\n      }));\n    }\n\n    if state.intersects(SelectorParsingState::AFTER_PSEUDO) {\n      // Input should be exhausted here.\n      let source_location = input.current_source_location();\n      if let Ok(next) = input.next() {\n        let next = next.clone();\n        return Err(\n          source_location.new_custom_error(SelectorParseErrorKind::UnexpectedSelectorAfterPseudoElement(next)),\n        );\n      }\n      break;\n    }\n\n    // Parse a combinator.\n    let combinator;\n    let mut any_whitespace = false;\n    loop {\n      let before_this_token = input.state();\n      match input.next_including_whitespace() {\n        Err(_e) => break 'outer_loop,\n        Ok(&Token::WhiteSpace(_)) => any_whitespace = true,\n        Ok(&Token::Delim('>')) => {\n          if parser.deep_combinator_enabled()\n            && input\n              .try_parse(|input| {\n                input.expect_delim('>')?;\n                input.expect_delim('>')\n              })\n              .is_ok()\n          {\n            combinator = Combinator::DeepDescendant;\n          } else {\n            combinator = Combinator::Child;\n          }\n          break;\n        }\n        Ok(&Token::Delim('+')) => {\n          combinator = Combinator::NextSibling;\n          break;\n        }\n        Ok(&Token::Delim('~')) => {\n          combinator = Combinator::LaterSibling;\n          break;\n        }\n        Ok(&Token::Delim('/')) if parser.deep_combinator_enabled() => {\n          if input\n            .try_parse(|input| {\n              input.expect_ident_matching(\"deep\")?;\n              input.expect_delim('/')\n            })\n            .is_ok()\n          {\n            combinator = Combinator::Deep;\n            break;\n          } else {\n            break 'outer_loop;\n          }\n        }\n        Ok(_) => {\n          input.reset(&before_this_token);\n          if any_whitespace {\n            combinator = Combinator::Descendant;\n            break;\n          } else {\n            break 'outer_loop;\n          }\n        }\n      }\n    }\n\n    if !state.allows_combinators() {\n      return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));\n    }\n\n    builder.push_combinator(combinator);\n  }\n\n  if !state.contains(SelectorParsingState::AFTER_NESTING) {\n    match nesting_requirement {\n      NestingRequirement::Implicit => {\n        builder.add_nesting_prefix();\n      }\n      NestingRequirement::Contained | NestingRequirement::Prefixed => {\n        return Err(input.new_custom_error(SelectorParseErrorKind::MissingNestingSelector));\n      }\n      _ => {}\n    }\n  }\n\n  let has_pseudo_element = state\n    .intersects(SelectorParsingState::AFTER_PSEUDO_ELEMENT | SelectorParsingState::AFTER_UNKNOWN_PSEUDO_ELEMENT);\n  let slotted = state.intersects(SelectorParsingState::AFTER_SLOTTED);\n  let part = state.intersects(SelectorParsingState::AFTER_PART);\n  let (spec, components) = builder.build(has_pseudo_element, slotted, part);\n  Ok(Selector(spec, components))\n}\n\nimpl<'i, Impl: SelectorImpl<'i>> Selector<'i, Impl> {\n  /// Parse a selector, without any pseudo-element.\n  #[inline]\n  pub fn parse<'t, P>(parser: &P, input: &mut CssParser<'i, 't>) -> Result<Self, ParseError<'i, P::Error>>\n  where\n    P: Parser<'i, Impl = Impl>,\n  {\n    parse_selector(\n      parser,\n      input,\n      &mut SelectorParsingState::empty(),\n      NestingRequirement::None,\n    )\n  }\n}\n\nfn parse_relative_selector<'i, 't, P, Impl>(\n  parser: &P,\n  input: &mut CssParser<'i, 't>,\n  state: &mut SelectorParsingState,\n  mut nesting_requirement: NestingRequirement,\n) -> Result<Selector<'i, Impl>, ParseError<'i, P::Error>>\nwhere\n  P: Parser<'i, Impl = Impl>,\n  Impl: SelectorImpl<'i>,\n{\n  // https://www.w3.org/TR/selectors-4/#parse-relative-selector\n  let s = input.state();\n  let combinator = match input.next()? {\n    Token::Delim('>') => Some(Combinator::Child),\n    Token::Delim('+') => Some(Combinator::NextSibling),\n    Token::Delim('~') => Some(Combinator::LaterSibling),\n    _ => {\n      input.reset(&s);\n      None\n    }\n  };\n\n  let scope = if nesting_requirement == NestingRequirement::Implicit {\n    Component::Nesting\n  } else {\n    Component::Scope\n  };\n\n  if combinator.is_some() {\n    nesting_requirement = NestingRequirement::None;\n  }\n\n  let mut selector = parse_selector(parser, input, state, nesting_requirement)?;\n  if let Some(combinator) = combinator {\n    // https://www.w3.org/TR/selectors/#absolutizing\n    selector.1.push(Component::Combinator(combinator));\n    selector.1.push(scope);\n  }\n\n  Ok(selector)\n}\n\n/// * `Err(())`: Invalid selector, abort\n/// * `Ok(false)`: Not a type selector, could be something else. `input` was not consumed.\n/// * `Ok(true)`: Length 0 (`*|*`), 1 (`*|E` or `ns|*`) or 2 (`|E` or `ns|E`)\nfn parse_type_selector<'i, 't, P, Impl, S>(\n  parser: &P,\n  input: &mut CssParser<'i, 't>,\n  state: SelectorParsingState,\n  sink: &mut S,\n) -> Result<bool, ParseError<'i, P::Error>>\nwhere\n  P: Parser<'i, Impl = Impl>,\n  Impl: SelectorImpl<'i>,\n  S: Push<Component<'i, Impl>>,\n{\n  match parse_qualified_name(parser, input, /* in_attr_selector = */ false) {\n    Err(ParseError {\n      kind: ParseErrorKind::Basic(BasicParseErrorKind::EndOfInput),\n      ..\n    })\n    | Ok(OptionalQName::None(_)) => Ok(false),\n    Ok(OptionalQName::Some(namespace, local_name)) => {\n      if state.intersects(SelectorParsingState::AFTER_PSEUDO) {\n        return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));\n      }\n      match namespace {\n        QNamePrefix::ImplicitAnyNamespace => {}\n        QNamePrefix::ImplicitDefaultNamespace(url) => sink.push(Component::DefaultNamespace(url)),\n        QNamePrefix::ExplicitNamespace(prefix, url) => sink.push(match parser.default_namespace() {\n          Some(ref default_url) if url == *default_url => Component::DefaultNamespace(url),\n          _ => Component::Namespace(prefix, url),\n        }),\n        QNamePrefix::ExplicitNoNamespace => sink.push(Component::ExplicitNoNamespace),\n        QNamePrefix::ExplicitAnyNamespace => {\n          // Element type selectors that have no namespace\n          // component (no namespace separator) represent elements\n          // without regard to the element's namespace (equivalent\n          // to \"*|\") unless a default namespace has been declared\n          // for namespaced selectors (e.g. in CSS, in the style\n          // sheet). If a default namespace has been declared,\n          // such selectors will represent only elements in the\n          // default namespace.\n          // -- Selectors § 6.1.1\n          // So we'll have this act the same as the\n          // QNamePrefix::ImplicitAnyNamespace case.\n          // For lightning css this logic was removed, should be handled when matching.\n          sink.push(Component::ExplicitAnyNamespace)\n        }\n        QNamePrefix::ImplicitNoNamespace => {\n          unreachable!() // Not returned with in_attr_selector = false\n        }\n      }\n      match local_name {\n        Some(name) => sink.push(Component::LocalName(LocalName {\n          lower_name: to_ascii_lowercase(name.clone()).into(),\n          name: name.into(),\n        })),\n        None => sink.push(Component::ExplicitUniversalType),\n      }\n      Ok(true)\n    }\n    Err(e) => Err(e),\n  }\n}\n\n#[derive(Debug)]\nenum SimpleSelectorParseResult<'i, Impl: SelectorImpl<'i>> {\n  SimpleSelector(Component<'i, Impl>),\n  PseudoElement(Impl::PseudoElement),\n  SlottedPseudo(Selector<'i, Impl>),\n  PartPseudo(Box<[Impl::Identifier]>),\n}\n\n#[derive(Debug)]\nenum QNamePrefix<'i, Impl: SelectorImpl<'i>> {\n  ImplicitNoNamespace,                                          // `foo` in attr selectors\n  ImplicitAnyNamespace,                                         // `foo` in type selectors, without a default ns\n  ImplicitDefaultNamespace(Impl::NamespaceUrl),                 // `foo` in type selectors, with a default ns\n  ExplicitNoNamespace,                                          // `|foo`\n  ExplicitAnyNamespace,                                         // `*|foo`\n  ExplicitNamespace(Impl::NamespacePrefix, Impl::NamespaceUrl), // `prefix|foo`\n}\n\nenum OptionalQName<'i, Impl: SelectorImpl<'i>> {\n  Some(QNamePrefix<'i, Impl>, Option<CowRcStr<'i>>),\n  None(Token<'i>),\n}\n\n/// * `Err(())`: Invalid selector, abort\n/// * `Ok(None(token))`: Not a simple selector, could be something else. `input` was not consumed,\n///                      but the token is still returned.\n/// * `Ok(Some(namespace, local_name))`: `None` for the local name means a `*` universal selector\nfn parse_qualified_name<'i, 't, P, Impl>(\n  parser: &P,\n  input: &mut CssParser<'i, 't>,\n  in_attr_selector: bool,\n) -> Result<OptionalQName<'i, Impl>, ParseError<'i, P::Error>>\nwhere\n  P: Parser<'i, Impl = Impl>,\n  Impl: SelectorImpl<'i>,\n{\n  let default_namespace = |local_name| {\n    let namespace = match parser.default_namespace() {\n      Some(url) => QNamePrefix::ImplicitDefaultNamespace(url),\n      None => QNamePrefix::ImplicitAnyNamespace,\n    };\n    Ok(OptionalQName::Some(namespace, local_name))\n  };\n\n  let explicit_namespace = |input: &mut CssParser<'i, 't>, namespace| {\n    let location = input.current_source_location();\n    match input.next_including_whitespace() {\n      Ok(&Token::Delim('*')) if !in_attr_selector => Ok(OptionalQName::Some(namespace, None)),\n      Ok(&Token::Ident(ref local_name)) => Ok(OptionalQName::Some(namespace, Some(local_name.clone()))),\n      Ok(t) if in_attr_selector => {\n        let e = SelectorParseErrorKind::InvalidQualNameInAttr(t.clone());\n        Err(location.new_custom_error(e))\n      }\n      Ok(t) => Err(location.new_custom_error(SelectorParseErrorKind::ExplicitNamespaceUnexpectedToken(t.clone()))),\n      Err(e) => Err(e.into()),\n    }\n  };\n\n  let start = input.state();\n  // FIXME: remove clone() when lifetimes are non-lexical\n  match input.next_including_whitespace().map(|t| t.clone()) {\n    Ok(Token::Ident(value)) => {\n      let after_ident = input.state();\n      match input.next_including_whitespace() {\n        Ok(&Token::Delim('|')) => {\n          let prefix = value.clone().into();\n          let result = parser.namespace_for_prefix(&prefix);\n          let url = result.ok_or(\n            after_ident\n              .source_location()\n              .new_custom_error(SelectorParseErrorKind::ExpectedNamespace(value)),\n          )?;\n          explicit_namespace(input, QNamePrefix::ExplicitNamespace(prefix, url))\n        }\n        _ => {\n          input.reset(&after_ident);\n          if in_attr_selector {\n            Ok(OptionalQName::Some(QNamePrefix::ImplicitNoNamespace, Some(value)))\n          } else {\n            default_namespace(Some(value))\n          }\n        }\n      }\n    }\n    Ok(Token::Delim('*')) => {\n      let after_star = input.state();\n      // FIXME: remove clone() when lifetimes are non-lexical\n      match input.next_including_whitespace().map(|t| t.clone()) {\n        Ok(Token::Delim('|')) => explicit_namespace(input, QNamePrefix::ExplicitAnyNamespace),\n        result => {\n          input.reset(&after_star);\n          if in_attr_selector {\n            match result {\n              Ok(t) => Err(\n                after_star\n                  .source_location()\n                  .new_custom_error(SelectorParseErrorKind::ExpectedBarInAttr(t)),\n              ),\n              Err(e) => Err(e.into()),\n            }\n          } else {\n            default_namespace(None)\n          }\n        }\n      }\n    }\n    Ok(Token::Delim('|')) => explicit_namespace(input, QNamePrefix::ExplicitNoNamespace),\n    Ok(t) => {\n      input.reset(&start);\n      Ok(OptionalQName::None(t))\n    }\n    Err(e) => {\n      input.reset(&start);\n      Err(e.into())\n    }\n  }\n}\n\nfn parse_attribute_selector<'i, 't, P, Impl>(\n  parser: &P,\n  input: &mut CssParser<'i, 't>,\n) -> Result<Component<'i, Impl>, ParseError<'i, P::Error>>\nwhere\n  P: Parser<'i, Impl = Impl>,\n  Impl: SelectorImpl<'i>,\n{\n  let namespace;\n  let local_name;\n\n  input.skip_whitespace();\n\n  match parse_qualified_name(parser, input, /* in_attr_selector = */ true)? {\n    OptionalQName::None(t) => {\n      return Err(input.new_custom_error(SelectorParseErrorKind::NoQualifiedNameInAttributeSelector(t)));\n    }\n    OptionalQName::Some(_, None) => unreachable!(),\n    OptionalQName::Some(ns, Some(ln)) => {\n      local_name = ln;\n      namespace = match ns {\n        QNamePrefix::ImplicitNoNamespace | QNamePrefix::ExplicitNoNamespace => None,\n        QNamePrefix::ExplicitNamespace(prefix, url) => Some(NamespaceConstraint::Specific((prefix, url))),\n        QNamePrefix::ExplicitAnyNamespace => Some(NamespaceConstraint::Any),\n        QNamePrefix::ImplicitAnyNamespace | QNamePrefix::ImplicitDefaultNamespace(_) => {\n          unreachable!() // Not returned with in_attr_selector = true\n        }\n      }\n    }\n  }\n\n  let location = input.current_source_location();\n  let operator = match input.next() {\n    // [foo]\n    Err(_) => {\n      let local_name_lower = to_ascii_lowercase(local_name.clone()).into();\n      let local_name = local_name.into();\n      if let Some(namespace) = namespace {\n        return Ok(Component::AttributeOther(Box::new(AttrSelectorWithOptionalNamespace {\n          namespace: Some(namespace),\n          local_name,\n          local_name_lower,\n          operation: ParsedAttrSelectorOperation::Exists,\n          never_matches: false,\n        })));\n      } else {\n        return Ok(Component::AttributeInNoNamespaceExists {\n          local_name,\n          local_name_lower,\n        });\n      }\n    }\n\n    // [foo=bar]\n    Ok(&Token::Delim('=')) => AttrSelectorOperator::Equal,\n    // [foo~=bar]\n    Ok(&Token::IncludeMatch) => AttrSelectorOperator::Includes,\n    // [foo|=bar]\n    Ok(&Token::DashMatch) => AttrSelectorOperator::DashMatch,\n    // [foo^=bar]\n    Ok(&Token::PrefixMatch) => AttrSelectorOperator::Prefix,\n    // [foo*=bar]\n    Ok(&Token::SubstringMatch) => AttrSelectorOperator::Substring,\n    // [foo$=bar]\n    Ok(&Token::SuffixMatch) => AttrSelectorOperator::Suffix,\n    Ok(t) => {\n      return Err(\n        location.new_custom_error(SelectorParseErrorKind::UnexpectedTokenInAttributeSelector(t.clone())),\n      );\n    }\n  };\n\n  let value = match input.expect_ident_or_string() {\n    Ok(t) => t.clone(),\n    Err(BasicParseError {\n      kind: BasicParseErrorKind::UnexpectedToken(t),\n      location,\n    }) => return Err(location.new_custom_error(SelectorParseErrorKind::BadValueInAttr(t))),\n    Err(e) => return Err(e.into()),\n  };\n  let never_matches = match operator {\n    AttrSelectorOperator::Equal | AttrSelectorOperator::DashMatch => false,\n\n    AttrSelectorOperator::Includes => value.is_empty() || value.contains(SELECTOR_WHITESPACE),\n\n    AttrSelectorOperator::Prefix | AttrSelectorOperator::Substring | AttrSelectorOperator::Suffix => {\n      value.is_empty()\n    }\n  };\n\n  let attribute_flags = parse_attribute_flags(input)?;\n\n  let value = value.into();\n  let case_sensitivity;\n  // copied from to_ascii_lowercase function, so we can know whether it is already lower case.\n  let (local_name_lower_cow, local_name_is_ascii_lowercase) =\n    if let Some(first_uppercase) = local_name.bytes().position(|byte| byte >= b'A' && byte <= b'Z') {\n      let mut string = local_name.to_string();\n      string[first_uppercase..].make_ascii_lowercase();\n      (string.into(), false)\n    } else {\n      (local_name.clone(), true)\n    };\n  case_sensitivity = attribute_flags.to_case_sensitivity(local_name_lower_cow.as_ref(), namespace.is_some());\n  let local_name_lower = local_name_lower_cow.into();\n  let local_name = local_name.into();\n  if namespace.is_some() || !local_name_is_ascii_lowercase {\n    Ok(Component::AttributeOther(Box::new(AttrSelectorWithOptionalNamespace {\n      namespace,\n      local_name,\n      local_name_lower,\n      never_matches,\n      operation: ParsedAttrSelectorOperation::WithValue {\n        operator,\n        case_sensitivity,\n        expected_value: value,\n      },\n    })))\n  } else {\n    Ok(Component::AttributeInNoNamespace {\n      local_name,\n      operator,\n      value,\n      case_sensitivity,\n      never_matches,\n    })\n  }\n}\n\n/// An attribute selector can have 's' or 'i' as flags, or no flags at all.\nenum AttributeFlags {\n  // Matching should be case-sensitive ('s' flag).\n  CaseSensitive,\n  // Matching should be case-insensitive ('i' flag).\n  AsciiCaseInsensitive,\n  // No flags.  Matching behavior depends on the name of the attribute.\n  CaseSensitivityDependsOnName,\n}\n\nimpl AttributeFlags {\n  fn to_case_sensitivity(self, local_name: &str, have_namespace: bool) -> ParsedCaseSensitivity {\n    match self {\n      AttributeFlags::CaseSensitive => ParsedCaseSensitivity::ExplicitCaseSensitive,\n      AttributeFlags::AsciiCaseInsensitive => ParsedCaseSensitivity::AsciiCaseInsensitive,\n      AttributeFlags::CaseSensitivityDependsOnName => {\n        if !have_namespace\n          && include!(concat!(env!(\"OUT_DIR\"), \"/ascii_case_insensitive_html_attributes.rs\")).contains(local_name)\n        {\n          ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument\n        } else {\n          ParsedCaseSensitivity::CaseSensitive\n        }\n      }\n    }\n  }\n}\n\nfn parse_attribute_flags<'i, 't>(input: &mut CssParser<'i, 't>) -> Result<AttributeFlags, BasicParseError<'i>> {\n  let location = input.current_source_location();\n  let token = match input.next() {\n    Ok(t) => t,\n    Err(..) => {\n      // Selectors spec says language-defined; HTML says it depends on the\n      // exact attribute name.\n      return Ok(AttributeFlags::CaseSensitivityDependsOnName);\n    }\n  };\n\n  let ident = match *token {\n    Token::Ident(ref i) => i,\n    ref other => return Err(location.new_basic_unexpected_token_error(other.clone())),\n  };\n\n  Ok(match_ignore_ascii_case! {\n      ident,\n      \"i\" => AttributeFlags::AsciiCaseInsensitive,\n      \"s\" => AttributeFlags::CaseSensitive,\n      _ => return Err(location.new_basic_unexpected_token_error(token.clone())),\n  })\n}\n\n/// Level 3: Parse **one** simple_selector.  (Though we might insert a second\n/// implied \"<defaultns>|*\" type selector.)\nfn parse_negation<'i, 't, P, Impl>(\n  parser: &P,\n  input: &mut CssParser<'i, 't>,\n  state: &mut SelectorParsingState,\n) -> Result<Component<'i, Impl>, ParseError<'i, P::Error>>\nwhere\n  P: Parser<'i, Impl = Impl>,\n  Impl: SelectorImpl<'i>,\n{\n  let mut child_state =\n    *state | SelectorParsingState::SKIP_DEFAULT_NAMESPACE | SelectorParsingState::DISALLOW_PSEUDOS;\n  let list = SelectorList::parse_with_state(\n    parser,\n    input,\n    &mut child_state,\n    ParseErrorRecovery::DiscardList,\n    NestingRequirement::None,\n  )?;\n\n  if child_state.contains(SelectorParsingState::AFTER_NESTING) {\n    state.insert(SelectorParsingState::AFTER_NESTING)\n  }\n\n  Ok(Component::Negation(list.0.into_vec().into_boxed_slice()))\n}\n\n/// simple_selector_sequence\n/// : [ type_selector | universal ] [ HASH | class | attrib | pseudo | negation ]*\n/// | [ HASH | class | attrib | pseudo | negation ]+\n///\n/// `Err(())` means invalid selector.\n/// `Ok(true)` is an empty selector\nfn parse_compound_selector<'i, 't, P, Impl>(\n  parser: &P,\n  state: &mut SelectorParsingState,\n  input: &mut CssParser<'i, 't>,\n  builder: &mut SelectorBuilder<'i, Impl>,\n) -> Result<bool, ParseError<'i, P::Error>>\nwhere\n  P: Parser<'i, Impl = Impl>,\n  Impl: SelectorImpl<'i>,\n{\n  input.skip_whitespace();\n\n  let mut empty = true;\n  if parser.is_nesting_allowed() && input.try_parse(|input| input.expect_delim('&')).is_ok() {\n    state.insert(SelectorParsingState::AFTER_NESTING);\n    builder.push_simple_selector(Component::Nesting);\n    empty = false;\n  }\n\n  if parse_type_selector(parser, input, *state, builder)? {\n    empty = false;\n  }\n\n  loop {\n    let result = match parse_one_simple_selector(parser, input, state)? {\n      None => break,\n      Some(result) => result,\n    };\n\n    if empty {\n      if let Some(url) = parser.default_namespace() {\n        // If there was no explicit type selector, but there is a\n        // default namespace, there is an implicit \"<defaultns>|*\" type\n        // selector. Except for :host() or :not() / :is() / :where(),\n        // where we ignore it.\n        //\n        // https://drafts.csswg.org/css-scoping/#host-element-in-tree:\n        //\n        //     When considered within its own shadow trees, the shadow\n        //     host is featureless. Only the :host, :host(), and\n        //     :host-context() pseudo-classes are allowed to match it.\n        //\n        // https://drafts.csswg.org/selectors-4/#featureless:\n        //\n        //     A featureless element does not match any selector at all,\n        //     except those it is explicitly defined to match. If a\n        //     given selector is allowed to match a featureless element,\n        //     it must do so while ignoring the default namespace.\n        //\n        // https://drafts.csswg.org/selectors-4/#matches\n        //\n        //     Default namespace declarations do not affect the compound\n        //     selector representing the subject of any selector within\n        //     a :is() pseudo-class, unless that compound selector\n        //     contains an explicit universal selector or type selector.\n        //\n        //     (Similar quotes for :where() / :not())\n        //\n        let ignore_default_ns = state.intersects(SelectorParsingState::SKIP_DEFAULT_NAMESPACE)\n          || matches!(result, SimpleSelectorParseResult::SimpleSelector(Component::Host(..)));\n        if !ignore_default_ns {\n          builder.push_simple_selector(Component::DefaultNamespace(url));\n        }\n      }\n    }\n\n    empty = false;\n\n    match result {\n      SimpleSelectorParseResult::SimpleSelector(s) => {\n        builder.push_simple_selector(s);\n      }\n      SimpleSelectorParseResult::PartPseudo(part_names) => {\n        state.insert(SelectorParsingState::AFTER_PART);\n        builder.push_combinator(Combinator::Part);\n        builder.push_simple_selector(Component::Part(part_names));\n      }\n      SimpleSelectorParseResult::SlottedPseudo(selector) => {\n        state.insert(SelectorParsingState::AFTER_SLOTTED);\n        builder.push_combinator(Combinator::SlotAssignment);\n        builder.push_simple_selector(Component::Slotted(selector));\n      }\n      SimpleSelectorParseResult::PseudoElement(p) => {\n        if !p.is_unknown() {\n          state.insert(SelectorParsingState::AFTER_PSEUDO_ELEMENT);\n          builder.push_combinator(Combinator::PseudoElement);\n        } else {\n          state.insert(SelectorParsingState::AFTER_UNKNOWN_PSEUDO_ELEMENT);\n        }\n        if !p.accepts_state_pseudo_classes() {\n          state.insert(SelectorParsingState::AFTER_NON_STATEFUL_PSEUDO_ELEMENT);\n        }\n        if p.is_webkit_scrollbar() {\n          state.insert(SelectorParsingState::AFTER_WEBKIT_SCROLLBAR);\n        }\n        if p.is_view_transition() {\n          state.insert(SelectorParsingState::AFTER_VIEW_TRANSITION);\n        }\n        builder.push_simple_selector(Component::PseudoElement(p));\n      }\n    }\n  }\n  Ok(empty)\n}\n\nfn parse_is_or_where<'i, 't, P, Impl>(\n  parser: &P,\n  input: &mut CssParser<'i, 't>,\n  state: &mut SelectorParsingState,\n  component: impl FnOnce(Box<[Selector<'i, Impl>]>) -> Component<'i, Impl>,\n) -> Result<Component<'i, Impl>, ParseError<'i, P::Error>>\nwhere\n  P: Parser<'i, Impl = Impl>,\n  Impl: SelectorImpl<'i>,\n{\n  debug_assert!(parser.parse_is_and_where());\n  // https://drafts.csswg.org/selectors/#matches-pseudo:\n  //\n  //     Pseudo-elements cannot be represented by the matches-any\n  //     pseudo-class; they are not valid within :is().\n  //\n  let mut child_state =\n    *state | SelectorParsingState::SKIP_DEFAULT_NAMESPACE | SelectorParsingState::DISALLOW_PSEUDOS;\n  let inner = SelectorList::parse_with_state(\n    parser,\n    input,\n    &mut child_state,\n    parser.is_and_where_error_recovery(),\n    NestingRequirement::None,\n  )?;\n  if child_state.contains(SelectorParsingState::AFTER_NESTING) {\n    state.insert(SelectorParsingState::AFTER_NESTING)\n  }\n  Ok(component(inner.0.into_vec().into_boxed_slice()))\n}\n\nfn parse_has<'i, 't, P, Impl>(\n  parser: &P,\n  input: &mut CssParser<'i, 't>,\n  state: &mut SelectorParsingState,\n) -> Result<Component<'i, Impl>, ParseError<'i, P::Error>>\nwhere\n  P: Parser<'i, Impl = Impl>,\n  Impl: SelectorImpl<'i>,\n{\n  let mut child_state = *state;\n  let inner = SelectorList::parse_relative_with_state(\n    parser,\n    input,\n    &mut child_state,\n    parser.is_and_where_error_recovery(),\n    NestingRequirement::None,\n  )?;\n  if child_state.contains(SelectorParsingState::AFTER_NESTING) {\n    state.insert(SelectorParsingState::AFTER_NESTING)\n  }\n  Ok(Component::Has(inner.0.into_vec().into_boxed_slice()))\n}\n\nfn parse_functional_pseudo_class<'i, 't, P, Impl>(\n  parser: &P,\n  input: &mut CssParser<'i, 't>,\n  name: CowRcStr<'i>,\n  state: &mut SelectorParsingState,\n) -> Result<Component<'i, Impl>, ParseError<'i, P::Error>>\nwhere\n  P: Parser<'i, Impl = Impl>,\n  Impl: SelectorImpl<'i>,\n{\n  match_ignore_ascii_case! { &name,\n      \"nth-child\" => return parse_nth_pseudo_class(parser, input, *state, NthType::Child),\n      \"nth-of-type\" => return parse_nth_pseudo_class(parser, input, *state, NthType::OfType),\n      \"nth-last-child\" => return parse_nth_pseudo_class(parser, input, *state, NthType::LastChild),\n      \"nth-last-of-type\" => return parse_nth_pseudo_class(parser, input, *state, NthType::LastOfType),\n      \"nth-col\" => return parse_nth_pseudo_class(parser, input, *state, NthType::Col),\n      \"nth-last-col\" => return parse_nth_pseudo_class(parser, input, *state, NthType::LastCol),\n      \"is\" if parser.parse_is_and_where() => return parse_is_or_where(parser, input, state, Component::Is),\n      \"where\" if parser.parse_is_and_where() => return parse_is_or_where(parser, input, state, Component::Where),\n      \"has\" => return parse_has(parser, input, state),\n      \"host\" => {\n          if !state.allows_tree_structural_pseudo_classes() {\n              return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));\n          }\n          return Ok(Component::Host(Some(parse_inner_compound_selector(parser, input, state)?)));\n      },\n      \"not\" => {\n          return parse_negation(parser, input, state)\n      },\n      _ => {}\n  }\n\n  if let Some(prefix) = parser.parse_any_prefix(&name) {\n    return parse_is_or_where(parser, input, state, |selectors| Component::Any(prefix, selectors));\n  }\n\n  if !state.allows_custom_functional_pseudo_classes() {\n    return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));\n  }\n\n  P::parse_non_ts_functional_pseudo_class(parser, name, input).map(Component::NonTSPseudoClass)\n}\n\nfn parse_nth_pseudo_class<'i, 't, P, Impl>(\n  parser: &P,\n  input: &mut CssParser<'i, 't>,\n  state: SelectorParsingState,\n  ty: NthType,\n) -> Result<Component<'i, Impl>, ParseError<'i, P::Error>>\nwhere\n  P: Parser<'i, Impl = Impl>,\n  Impl: SelectorImpl<'i>,\n{\n  if !state.allows_tree_structural_pseudo_classes() {\n    return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));\n  }\n  let (a, b) = parse_nth(input)?;\n  let nth_data = NthSelectorData {\n    ty,\n    is_function: true,\n    a,\n    b,\n  };\n  if !ty.allows_of_selector() {\n    return Ok(Component::Nth(nth_data));\n  }\n\n  // Try to parse \"of <selector-list>\".\n  if input.try_parse(|i| i.expect_ident_matching(\"of\")).is_err() {\n    return Ok(Component::Nth(nth_data));\n  }\n  // Whitespace between \"of\" and the selector list is optional\n  // https://github.com/w3c/csswg-drafts/issues/8285\n  let mut child_state =\n    state | SelectorParsingState::SKIP_DEFAULT_NAMESPACE | SelectorParsingState::DISALLOW_PSEUDOS;\n  let selectors = SelectorList::parse_with_state(\n    parser,\n    input,\n    &mut child_state,\n    ParseErrorRecovery::IgnoreInvalidSelector,\n    NestingRequirement::None,\n  )?;\n  Ok(Component::NthOf(NthOfSelectorData::new(\n    nth_data,\n    selectors.0.into_vec().into_boxed_slice(),\n  )))\n}\n\n/// Returns whether the name corresponds to a CSS2 pseudo-element that\n/// can be specified with the single colon syntax (in addition to the\n/// double-colon syntax, which can be used for all pseudo-elements).\nfn is_css2_pseudo_element(name: &str) -> bool {\n  // ** Do not add to this list! **\n  match_ignore_ascii_case! { name,\n      \"before\" | \"after\" | \"first-line\" | \"first-letter\" => true,\n      _ => false,\n  }\n}\n\n/// Parse a simple selector other than a type selector.\n///\n/// * `Err(())`: Invalid selector, abort\n/// * `Ok(None)`: Not a simple selector, could be something else. `input` was not consumed.\n/// * `Ok(Some(_))`: Parsed a simple selector or pseudo-element\nfn parse_one_simple_selector<'i, 't, P, Impl>(\n  parser: &P,\n  input: &mut CssParser<'i, 't>,\n  state: &mut SelectorParsingState,\n) -> Result<Option<SimpleSelectorParseResult<'i, Impl>>, ParseError<'i, P::Error>>\nwhere\n  P: Parser<'i, Impl = Impl>,\n  Impl: SelectorImpl<'i>,\n{\n  let start = input.state();\n  let token_location = input.current_source_location();\n  let token = match input.next_including_whitespace().map(|t| t.clone()) {\n    Ok(t) => t,\n    Err(..) => {\n      input.reset(&start);\n      return Ok(None);\n    }\n  };\n\n  Ok(Some(match token {\n    Token::IDHash(id) => {\n      if state.intersects(SelectorParsingState::AFTER_PSEUDO) {\n        return Err(token_location.new_custom_error(\n          SelectorParseErrorKind::UnexpectedSelectorAfterPseudoElement(Token::IDHash(id)),\n        ));\n      }\n      let id = Component::ID(id.into());\n      SimpleSelectorParseResult::SimpleSelector(id)\n    }\n    Token::Delim('.') => {\n      if state.intersects(SelectorParsingState::AFTER_PSEUDO) {\n        return Err(token_location.new_custom_error(\n          SelectorParseErrorKind::UnexpectedSelectorAfterPseudoElement(Token::Delim('.')),\n        ));\n      }\n      let location = input.current_source_location();\n      let class = match *input.next_including_whitespace()? {\n        Token::Ident(ref class) => class.clone(),\n        ref t => {\n          let e = SelectorParseErrorKind::ClassNeedsIdent(t.clone());\n          return Err(location.new_custom_error(e));\n        }\n      };\n      let class = Component::Class(class.into());\n      SimpleSelectorParseResult::SimpleSelector(class)\n    }\n    Token::SquareBracketBlock => {\n      if state.intersects(SelectorParsingState::AFTER_PSEUDO) {\n        return Err(token_location.new_custom_error(\n          SelectorParseErrorKind::UnexpectedSelectorAfterPseudoElement(Token::SquareBracketBlock),\n        ));\n      }\n      let attr = input.parse_nested_block(|input| parse_attribute_selector(parser, input))?;\n      SimpleSelectorParseResult::SimpleSelector(attr)\n    }\n    Token::Colon => {\n      let location = input.current_source_location();\n      let (is_single_colon, next_token) = match input.next_including_whitespace()?.clone() {\n        Token::Colon => (false, input.next_including_whitespace()?.clone()),\n        t => (true, t),\n      };\n      let (name, is_functional) = match next_token {\n        Token::Ident(name) => (name, false),\n        Token::Function(name) => (name, true),\n        t => {\n          let e = SelectorParseErrorKind::PseudoElementExpectedIdent(t);\n          return Err(input.new_custom_error(e));\n        }\n      };\n      let is_pseudo_element = !is_single_colon || is_css2_pseudo_element(&name);\n      if is_pseudo_element {\n        if !state.allows_pseudos() {\n          return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));\n        }\n        let pseudo_element = if is_functional {\n          if P::parse_part(parser) && name.eq_ignore_ascii_case(\"part\") {\n            if !state.allows_part() {\n              return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));\n            }\n            let names = input.parse_nested_block(|input| {\n              let mut result = Vec::with_capacity(1);\n              result.push(input.expect_ident_cloned()?.into());\n              while !input.is_exhausted() {\n                result.push(input.expect_ident_cloned()?.into());\n              }\n              Ok(result.into_boxed_slice())\n            })?;\n            return Ok(Some(SimpleSelectorParseResult::PartPseudo(names)));\n          }\n          if P::parse_slotted(parser) && name.eq_ignore_ascii_case(\"slotted\") {\n            if !state.allows_slotted() {\n              return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));\n            }\n            let selector =\n              input.parse_nested_block(|input| parse_inner_compound_selector(parser, input, state))?;\n            return Ok(Some(SimpleSelectorParseResult::SlottedPseudo(selector)));\n          }\n          input.parse_nested_block(|input| P::parse_functional_pseudo_element(parser, name, input))?\n        } else {\n          P::parse_pseudo_element(parser, location, name)?\n        };\n\n        if state.intersects(SelectorParsingState::AFTER_SLOTTED) && !pseudo_element.valid_after_slotted() {\n          return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));\n        }\n        SimpleSelectorParseResult::PseudoElement(pseudo_element)\n      } else {\n        let pseudo_class = if is_functional {\n          input.parse_nested_block(|input| parse_functional_pseudo_class(parser, input, name, state))?\n        } else {\n          parse_simple_pseudo_class(parser, location, name, *state)?\n        };\n        SimpleSelectorParseResult::SimpleSelector(pseudo_class)\n      }\n    }\n    Token::Delim('&') if parser.is_nesting_allowed() => {\n      *state |= SelectorParsingState::AFTER_NESTING;\n      SimpleSelectorParseResult::SimpleSelector(Component::Nesting)\n    }\n    _ => {\n      input.reset(&start);\n      return Ok(None);\n    }\n  }))\n}\n\nfn parse_simple_pseudo_class<'i, P, Impl>(\n  parser: &P,\n  location: SourceLocation,\n  name: CowRcStr<'i>,\n  state: SelectorParsingState,\n) -> Result<Component<'i, Impl>, ParseError<'i, P::Error>>\nwhere\n  P: Parser<'i, Impl = Impl>,\n  Impl: SelectorImpl<'i>,\n{\n  if !state.allows_non_functional_pseudo_classes() {\n    return Err(location.new_custom_error(SelectorParseErrorKind::InvalidState));\n  }\n\n  if state.allows_tree_structural_pseudo_classes() {\n    match_ignore_ascii_case! { &name,\n        \"first-child\" => return Ok(Component::Nth(NthSelectorData::first(/* of_type = */ false))),\n        \"last-child\" => return Ok(Component::Nth(NthSelectorData::last(/* of_type = */ false))),\n        \"only-child\" => return Ok(Component::Nth(NthSelectorData::only(/* of_type = */ false))),\n        \"root\" => return Ok(Component::Root),\n        \"empty\" => return Ok(Component::Empty),\n        \"scope\" => return Ok(Component::Scope),\n        \"host\" if P::parse_host(parser) => return Ok(Component::Host(None)),\n        \"first-of-type\" => return Ok(Component::Nth(NthSelectorData::first(/* of_type = */ true))),\n        \"last-of-type\" => return Ok(Component::Nth(NthSelectorData::last(/* of_type = */ true))),\n        \"only-of-type\" => return Ok(Component::Nth(NthSelectorData::only(/* of_type = */ true))),\n        _ => {},\n    }\n  }\n\n  // The view-transition pseudo elements accept the :only-child pseudo class.\n  // https://w3c.github.io/csswg-drafts/css-view-transitions-1/#pseudo-root\n  if state.intersects(SelectorParsingState::AFTER_VIEW_TRANSITION) {\n    match_ignore_ascii_case! { &name,\n        \"only-child\" => return Ok(Component::Nth(NthSelectorData::only(/* of_type = */ false))),\n        _ => {}\n    }\n  }\n\n  let pseudo_class = P::parse_non_ts_pseudo_class(parser, location, name)?;\n  if state.intersects(SelectorParsingState::AFTER_WEBKIT_SCROLLBAR) {\n    if !pseudo_class.is_valid_after_webkit_scrollbar() {\n      return Err(location.new_custom_error(SelectorParseErrorKind::InvalidPseudoClassAfterWebKitScrollbar));\n    }\n  } else if state.intersects(SelectorParsingState::AFTER_PSEUDO_ELEMENT) {\n    if !pseudo_class.is_user_action_state() {\n      return Err(location.new_custom_error(SelectorParseErrorKind::InvalidPseudoClassAfterPseudoElement));\n    }\n  } else if !pseudo_class.is_valid_before_webkit_scrollbar() {\n    return Err(location.new_custom_error(SelectorParseErrorKind::InvalidPseudoClassBeforeWebKitScrollbar));\n  }\n  Ok(Component::NonTSPseudoClass(pseudo_class))\n}\n\n// NB: pub module in order to access the DummyParser\n#[cfg(test)]\npub mod tests {\n  use super::*;\n  use crate::builder::SelectorFlags;\n  use crate::parser;\n  use cssparser::{serialize_identifier, serialize_string, Parser as CssParser, ParserInput, ToCss};\n  use std::collections::HashMap;\n  use std::fmt;\n\n  #[derive(Clone, Debug, Eq, PartialEq, Hash)]\n  pub enum PseudoClass {\n    Hover,\n    Active,\n    Lang(String),\n  }\n\n  #[derive(Clone, Debug, Eq, PartialEq, Hash)]\n  pub enum PseudoElement {\n    Before,\n    After,\n  }\n\n  impl<'i> parser::PseudoElement<'i> for PseudoElement {\n    type Impl = DummySelectorImpl;\n\n    fn accepts_state_pseudo_classes(&self) -> bool {\n      true\n    }\n\n    fn valid_after_slotted(&self) -> bool {\n      true\n    }\n  }\n\n  impl<'i> parser::NonTSPseudoClass<'i> for PseudoClass {\n    type Impl = DummySelectorImpl;\n\n    #[inline]\n    fn is_active_or_hover(&self) -> bool {\n      matches!(*self, PseudoClass::Active | PseudoClass::Hover)\n    }\n\n    #[inline]\n    fn is_user_action_state(&self) -> bool {\n      self.is_active_or_hover()\n    }\n  }\n\n  impl ToCss for PseudoClass {\n    fn to_css<W>(&self, dest: &mut W) -> fmt::Result\n    where\n      W: fmt::Write,\n    {\n      match *self {\n        PseudoClass::Hover => dest.write_str(\":hover\"),\n        PseudoClass::Active => dest.write_str(\":active\"),\n        PseudoClass::Lang(ref lang) => {\n          dest.write_str(\":lang(\")?;\n          serialize_identifier(lang, dest)?;\n          dest.write_char(')')\n        }\n      }\n    }\n  }\n\n  impl ToCss for PseudoElement {\n    fn to_css<W>(&self, dest: &mut W) -> fmt::Result\n    where\n      W: fmt::Write,\n    {\n      match *self {\n        PseudoElement::Before => dest.write_str(\"::before\"),\n        PseudoElement::After => dest.write_str(\"::after\"),\n      }\n    }\n  }\n\n  #[derive(Clone, Debug, PartialEq)]\n  pub struct DummySelectorImpl;\n\n  #[derive(Default)]\n  pub struct DummyParser {\n    default_ns: Option<DummyAtom>,\n    ns_prefixes: HashMap<DummyAtom, DummyAtom>,\n  }\n\n  impl DummyParser {\n    fn default_with_namespace(default_ns: DummyAtom) -> DummyParser {\n      DummyParser {\n        default_ns: Some(default_ns),\n        ns_prefixes: Default::default(),\n      }\n    }\n  }\n\n  impl<'i> SelectorImpl<'i> for DummySelectorImpl {\n    type ExtraMatchingData = ();\n    type AttrValue = DummyAttrValue;\n    type Identifier = DummyAtom;\n    type LocalName = DummyAtom;\n    type NamespaceUrl = DummyAtom;\n    type NamespacePrefix = DummyAtom;\n    type BorrowedLocalName = DummyAtom;\n    type BorrowedNamespaceUrl = DummyAtom;\n    type NonTSPseudoClass = PseudoClass;\n    type PseudoElement = PseudoElement;\n    type VendorPrefix = u8;\n  }\n\n  #[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]\n  pub struct DummyAttrValue(String);\n\n  impl ToCss for DummyAttrValue {\n    fn to_css<W>(&self, dest: &mut W) -> fmt::Result\n    where\n      W: fmt::Write,\n    {\n      serialize_string(&self.0, dest)\n    }\n  }\n\n  impl AsRef<str> for DummyAttrValue {\n    fn as_ref(&self) -> &str {\n      self.0.as_ref()\n    }\n  }\n\n  impl<'a> From<&'a str> for DummyAttrValue {\n    fn from(string: &'a str) -> Self {\n      Self(string.into())\n    }\n  }\n\n  impl<'a> From<std::borrow::Cow<'a, str>> for DummyAttrValue {\n    fn from(string: std::borrow::Cow<'a, str>) -> Self {\n      Self(string.to_string())\n    }\n  }\n\n  impl<'a> From<CowRcStr<'a>> for DummyAttrValue {\n    fn from(string: CowRcStr<'a>) -> Self {\n      Self(string.to_string())\n    }\n  }\n\n  #[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]\n  pub struct DummyAtom(String);\n\n  impl ToCss for DummyAtom {\n    fn to_css<W>(&self, dest: &mut W) -> fmt::Result\n    where\n      W: fmt::Write,\n    {\n      serialize_identifier(&self.0, dest)\n    }\n  }\n\n  impl From<String> for DummyAtom {\n    fn from(string: String) -> Self {\n      DummyAtom(string)\n    }\n  }\n\n  impl<'a> From<&'a str> for DummyAtom {\n    fn from(string: &'a str) -> Self {\n      DummyAtom(string.into())\n    }\n  }\n\n  impl<'a> From<CowRcStr<'a>> for DummyAtom {\n    fn from(string: CowRcStr<'a>) -> Self {\n      DummyAtom(string.to_string())\n    }\n  }\n\n  impl AsRef<str> for DummyAtom {\n    fn as_ref(&self) -> &str {\n      self.0.as_ref()\n    }\n  }\n\n  impl<'a> From<std::borrow::Cow<'a, str>> for DummyAtom {\n    fn from(string: std::borrow::Cow<'a, str>) -> Self {\n      Self(string.to_string())\n    }\n  }\n\n  impl<'i> Parser<'i> for DummyParser {\n    type Impl = DummySelectorImpl;\n    type Error = SelectorParseErrorKind<'i>;\n\n    fn parse_slotted(&self) -> bool {\n      true\n    }\n\n    fn parse_is_and_where(&self) -> bool {\n      true\n    }\n\n    fn is_and_where_error_recovery(&self) -> ParseErrorRecovery {\n      ParseErrorRecovery::DiscardList\n    }\n\n    fn parse_part(&self) -> bool {\n      true\n    }\n\n    fn parse_non_ts_pseudo_class(\n      &self,\n      location: SourceLocation,\n      name: CowRcStr<'i>,\n    ) -> Result<PseudoClass, SelectorParseError<'i>> {\n      match_ignore_ascii_case! { &name,\n          \"hover\" => return Ok(PseudoClass::Hover),\n          \"active\" => return Ok(PseudoClass::Active),\n          _ => {}\n      }\n      Err(location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClass(name)))\n    }\n\n    fn parse_non_ts_functional_pseudo_class<'t>(\n      &self,\n      name: CowRcStr<'i>,\n      parser: &mut CssParser<'i, 't>,\n    ) -> Result<PseudoClass, SelectorParseError<'i>> {\n      match_ignore_ascii_case! { &name,\n          \"lang\" => {\n              let lang = parser.expect_ident_or_string()?.as_ref().to_owned();\n              return Ok(PseudoClass::Lang(lang));\n          },\n          _ => {}\n      }\n      Err(parser.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClass(name)))\n    }\n\n    fn parse_pseudo_element(\n      &self,\n      location: SourceLocation,\n      name: CowRcStr<'i>,\n    ) -> Result<PseudoElement, SelectorParseError<'i>> {\n      match_ignore_ascii_case! { &name,\n          \"before\" => return Ok(PseudoElement::Before),\n          \"after\" => return Ok(PseudoElement::After),\n          _ => {}\n      }\n      Err(location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoElement(name)))\n    }\n\n    fn default_namespace(&self) -> Option<DummyAtom> {\n      self.default_ns.clone()\n    }\n\n    fn namespace_for_prefix(&self, prefix: &DummyAtom) -> Option<DummyAtom> {\n      self.ns_prefixes.get(prefix).cloned()\n    }\n  }\n\n  fn parse<'i>(input: &'i str) -> Result<SelectorList<'i, DummySelectorImpl>, SelectorParseError<'i>> {\n    parse_ns(input, &DummyParser::default())\n  }\n\n  // fn parse_expected<'i, 'a>(\n  //   input: &'i str,\n  //   expected: Option<&'a str>,\n  // ) -> Result<SelectorList<'i, DummySelectorImpl>, SelectorParseError<'i>> {\n  //   parse_ns_expected(input, &DummyParser::default(), expected)\n  // }\n\n  fn parse_ns<'i>(\n    input: &'i str,\n    parser: &DummyParser,\n  ) -> Result<SelectorList<'i, DummySelectorImpl>, SelectorParseError<'i>> {\n    parse_ns_expected(input, parser, None)\n  }\n\n  fn parse_ns_expected<'i, 'a>(\n    input: &'i str,\n    parser: &DummyParser,\n    expected: Option<&'a str>,\n  ) -> Result<SelectorList<'i, DummySelectorImpl>, SelectorParseError<'i>> {\n    let mut parser_input = ParserInput::new(input);\n    let result = SelectorList::parse(\n      parser,\n      &mut CssParser::new(&mut parser_input),\n      ParseErrorRecovery::DiscardList,\n      NestingRequirement::None,\n    );\n    if let Ok(ref selectors) = result {\n      assert_eq!(selectors.0.len(), 1);\n      // We can't assume that the serialized parsed selector will equal\n      // the input; for example, if there is no default namespace, '*|foo'\n      // should serialize to 'foo'.\n      assert_eq!(\n        selectors.0[0].to_css_string(),\n        match expected {\n          Some(x) => x,\n          None => input,\n        }\n      );\n    }\n    result\n  }\n\n  fn specificity(a: u32, b: u32, c: u32) -> u32 {\n    a << 20 | b << 10 | c\n  }\n\n  #[test]\n  fn test_empty() {\n    let mut input = ParserInput::new(\":empty\");\n    let list = SelectorList::parse(\n      &DummyParser::default(),\n      &mut CssParser::new(&mut input),\n      ParseErrorRecovery::DiscardList,\n      NestingRequirement::None,\n    );\n    assert!(list.is_ok());\n  }\n\n  const MATHML: &str = \"http://www.w3.org/1998/Math/MathML\";\n  const SVG: &str = \"http://www.w3.org/2000/svg\";\n\n  #[test]\n  fn test_parsing() {\n    assert!(parse(\"\").is_err());\n    assert!(parse(\":lang(4)\").is_err());\n    assert!(parse(\":lang(en US)\").is_err());\n    assert_eq!(\n      parse(\"EeÉ\"),\n      Ok(SelectorList::from_vec(vec![Selector::from_vec(\n        vec![Component::LocalName(LocalName {\n          name: DummyAtom::from(\"EeÉ\"),\n          lower_name: DummyAtom::from(\"eeÉ\"),\n        })],\n        specificity(0, 0, 1),\n        Default::default(),\n      )]))\n    );\n    assert_eq!(\n      parse(\"|e\"),\n      Ok(SelectorList::from_vec(vec![Selector::from_vec(\n        vec![\n          Component::ExplicitNoNamespace,\n          Component::LocalName(LocalName {\n            name: DummyAtom::from(\"e\"),\n            lower_name: DummyAtom::from(\"e\"),\n          }),\n        ],\n        specificity(0, 0, 1),\n        Default::default(),\n      )]))\n    );\n    // When the default namespace is not set, *| should be elided.\n    // https://github.com/servo/servo/pull/17537\n    // assert_eq!(\n    //   parse_expected(\"*|e\", Some(\"e\")),\n    //   Ok(SelectorList::from_vec(vec![Selector::from_vec(\n    //     vec![Component::LocalName(LocalName {\n    //       name: DummyAtom::from(\"e\"),\n    //       lower_name: DummyAtom::from(\"e\"),\n    //     })],\n    //     specificity(0, 0, 1),\n    //     Default::default(),\n    //   )]))\n    // );\n    // When the default namespace is set, *| should _not_ be elided (as foo\n    // is no longer equivalent to *|foo--the former is only for foo in the\n    // default namespace).\n    // https://github.com/servo/servo/issues/16020\n    // assert_eq!(\n    //   parse_ns(\n    //     \"*|e\",\n    //     &DummyParser::default_with_namespace(DummyAtom::from(\"https://mozilla.org\"))\n    //   ),\n    //   Ok(SelectorList::from_vec(vec![Selector::from_vec(\n    //     vec![\n    //       Component::ExplicitAnyNamespace,\n    //       Component::LocalName(LocalName {\n    //         name: DummyAtom::from(\"e\"),\n    //         lower_name: DummyAtom::from(\"e\"),\n    //       }),\n    //     ],\n    //     specificity(0, 0, 1),\n    //     Default::default(),\n    //   )]))\n    // );\n    assert_eq!(\n      parse(\"*\"),\n      Ok(SelectorList::from_vec(vec![Selector::from_vec(\n        vec![Component::ExplicitUniversalType],\n        specificity(0, 0, 0),\n        Default::default(),\n      )]))\n    );\n    assert_eq!(\n      parse(\"|*\"),\n      Ok(SelectorList::from_vec(vec![Selector::from_vec(\n        vec![Component::ExplicitNoNamespace, Component::ExplicitUniversalType,],\n        specificity(0, 0, 0),\n        Default::default(),\n      )]))\n    );\n    // assert_eq!(\n    //   parse_expected(\"*|*\", Some(\"*\")),\n    //   Ok(SelectorList::from_vec(vec![Selector::from_vec(\n    //     vec![Component::ExplicitUniversalType],\n    //     specificity(0, 0, 0),\n    //     Default::default(),\n    //   )]))\n    // );\n    assert_eq!(\n      parse_ns(\n        \"*|*\",\n        &DummyParser::default_with_namespace(DummyAtom::from(\"https://mozilla.org\"))\n      ),\n      Ok(SelectorList::from_vec(vec![Selector::from_vec(\n        vec![Component::ExplicitAnyNamespace, Component::ExplicitUniversalType,],\n        specificity(0, 0, 0),\n        Default::default(),\n      )]))\n    );\n    assert_eq!(\n      parse(\".foo:lang(en-US)\"),\n      Ok(SelectorList::from_vec(vec![Selector::from_vec(\n        vec![\n          Component::Class(DummyAtom::from(\"foo\")),\n          Component::NonTSPseudoClass(PseudoClass::Lang(\"en-US\".to_owned())),\n        ],\n        specificity(0, 2, 0),\n        Default::default(),\n      )]))\n    );\n    assert_eq!(\n      parse(\"#bar\"),\n      Ok(SelectorList::from_vec(vec![Selector::from_vec(\n        vec![Component::ID(DummyAtom::from(\"bar\"))],\n        specificity(1, 0, 0),\n        Default::default(),\n      )]))\n    );\n    assert_eq!(\n      parse(\"e.foo#bar\"),\n      Ok(SelectorList::from_vec(vec![Selector::from_vec(\n        vec![\n          Component::LocalName(LocalName {\n            name: DummyAtom::from(\"e\"),\n            lower_name: DummyAtom::from(\"e\"),\n          }),\n          Component::Class(DummyAtom::from(\"foo\")),\n          Component::ID(DummyAtom::from(\"bar\")),\n        ],\n        specificity(1, 1, 1),\n        Default::default(),\n      )]))\n    );\n    assert_eq!(\n      parse(\"e.foo #bar\"),\n      Ok(SelectorList::from_vec(vec![Selector::from_vec(\n        vec![\n          Component::LocalName(LocalName {\n            name: DummyAtom::from(\"e\"),\n            lower_name: DummyAtom::from(\"e\"),\n          }),\n          Component::Class(DummyAtom::from(\"foo\")),\n          Component::Combinator(Combinator::Descendant),\n          Component::ID(DummyAtom::from(\"bar\")),\n        ],\n        specificity(1, 1, 1),\n        Default::default(),\n      )]))\n    );\n    // Default namespace does not apply to attribute selectors\n    // https://github.com/mozilla/servo/pull/1652\n    let mut parser = DummyParser::default();\n    assert_eq!(\n      parse_ns(\"[Foo]\", &parser),\n      Ok(SelectorList::from_vec(vec![Selector::from_vec(\n        vec![Component::AttributeInNoNamespaceExists {\n          local_name: DummyAtom::from(\"Foo\"),\n          local_name_lower: DummyAtom::from(\"foo\"),\n        }],\n        specificity(0, 1, 0),\n        Default::default(),\n      )]))\n    );\n    assert!(parse_ns(\"svg|circle\", &parser).is_err());\n    parser.ns_prefixes.insert(DummyAtom(\"svg\".into()), DummyAtom(SVG.into()));\n    assert_eq!(\n      parse_ns(\"svg|circle\", &parser),\n      Ok(SelectorList::from_vec(vec![Selector::from_vec(\n        vec![\n          Component::Namespace(DummyAtom(\"svg\".into()), SVG.into()),\n          Component::LocalName(LocalName {\n            name: DummyAtom::from(\"circle\"),\n            lower_name: DummyAtom::from(\"circle\"),\n          }),\n        ],\n        specificity(0, 0, 1),\n        Default::default(),\n      )]))\n    );\n    assert_eq!(\n      parse_ns(\"svg|*\", &parser),\n      Ok(SelectorList::from_vec(vec![Selector::from_vec(\n        vec![\n          Component::Namespace(DummyAtom(\"svg\".into()), SVG.into()),\n          Component::ExplicitUniversalType,\n        ],\n        specificity(0, 0, 0),\n        Default::default(),\n      )]))\n    );\n    // Default namespace does not apply to attribute selectors\n    // https://github.com/mozilla/servo/pull/1652\n    // but it does apply to implicit type selectors\n    // https://github.com/servo/rust-selectors/pull/82\n    parser.default_ns = Some(MATHML.into());\n    assert_eq!(\n      parse_ns(\"[Foo]\", &parser),\n      Ok(SelectorList::from_vec(vec![Selector::from_vec(\n        vec![\n          Component::DefaultNamespace(MATHML.into()),\n          Component::AttributeInNoNamespaceExists {\n            local_name: DummyAtom::from(\"Foo\"),\n            local_name_lower: DummyAtom::from(\"foo\"),\n          },\n        ],\n        specificity(0, 1, 0),\n        Default::default(),\n      )]))\n    );\n    // Default namespace does apply to type selectors\n    assert_eq!(\n      parse_ns(\"e\", &parser),\n      Ok(SelectorList::from_vec(vec![Selector::from_vec(\n        vec![\n          Component::DefaultNamespace(MATHML.into()),\n          Component::LocalName(LocalName {\n            name: DummyAtom::from(\"e\"),\n            lower_name: DummyAtom::from(\"e\"),\n          }),\n        ],\n        specificity(0, 0, 1),\n        Default::default(),\n      )]))\n    );\n    assert_eq!(\n      parse_ns(\"*\", &parser),\n      Ok(SelectorList::from_vec(vec![Selector::from_vec(\n        vec![\n          Component::DefaultNamespace(MATHML.into()),\n          Component::ExplicitUniversalType,\n        ],\n        specificity(0, 0, 0),\n        Default::default(),\n      )]))\n    );\n    assert_eq!(\n      parse_ns(\"*|*\", &parser),\n      Ok(SelectorList::from_vec(vec![Selector::from_vec(\n        vec![Component::ExplicitAnyNamespace, Component::ExplicitUniversalType,],\n        specificity(0, 0, 0),\n        Default::default(),\n      )]))\n    );\n    // Default namespace applies to universal and type selectors inside :not and :matches,\n    // but not otherwise.\n    assert_eq!(\n      parse_ns(\":not(.cl)\", &parser),\n      Ok(SelectorList::from_vec(vec![Selector::from_vec(\n        vec![\n          Component::DefaultNamespace(MATHML.into()),\n          Component::Negation(\n            vec![Selector::from_vec(\n              vec![Component::Class(DummyAtom::from(\"cl\"))],\n              specificity(0, 1, 0),\n              Default::default(),\n            )]\n            .into_boxed_slice()\n          ),\n        ],\n        specificity(0, 1, 0),\n        Default::default(),\n      )]))\n    );\n    assert_eq!(\n      parse_ns(\":not(*)\", &parser),\n      Ok(SelectorList::from_vec(vec![Selector::from_vec(\n        vec![\n          Component::DefaultNamespace(MATHML.into()),\n          Component::Negation(\n            vec![Selector::from_vec(\n              vec![\n                Component::DefaultNamespace(MATHML.into()),\n                Component::ExplicitUniversalType,\n              ],\n              specificity(0, 0, 0),\n              Default::default(),\n            )]\n            .into_boxed_slice(),\n          ),\n        ],\n        specificity(0, 0, 0),\n        Default::default(),\n      )]))\n    );\n    assert_eq!(\n      parse_ns(\":not(e)\", &parser),\n      Ok(SelectorList::from_vec(vec![Selector::from_vec(\n        vec![\n          Component::DefaultNamespace(MATHML.into()),\n          Component::Negation(\n            vec![Selector::from_vec(\n              vec![\n                Component::DefaultNamespace(MATHML.into()),\n                Component::LocalName(LocalName {\n                  name: DummyAtom::from(\"e\"),\n                  lower_name: DummyAtom::from(\"e\"),\n                }),\n              ],\n              specificity(0, 0, 1),\n              Default::default(),\n            ),]\n            .into_boxed_slice()\n          ),\n        ],\n        specificity(0, 0, 1),\n        Default::default(),\n      )]))\n    );\n    assert_eq!(\n      parse(\"[attr|=\\\"foo\\\"]\"),\n      Ok(SelectorList::from_vec(vec![Selector::from_vec(\n        vec![Component::AttributeInNoNamespace {\n          local_name: DummyAtom::from(\"attr\"),\n          operator: AttrSelectorOperator::DashMatch,\n          value: DummyAttrValue::from(\"foo\"),\n          never_matches: false,\n          case_sensitivity: ParsedCaseSensitivity::CaseSensitive,\n        }],\n        specificity(0, 1, 0),\n        Default::default(),\n      )]))\n    );\n    // https://github.com/mozilla/servo/issues/1723\n    assert_eq!(\n      parse(\"::before\"),\n      Ok(SelectorList::from_vec(vec![Selector::from_vec(\n        vec![\n          Component::Combinator(Combinator::PseudoElement),\n          Component::PseudoElement(PseudoElement::Before),\n        ],\n        specificity(0, 0, 1),\n        SelectorFlags::HAS_PSEUDO,\n      )]))\n    );\n    assert_eq!(\n      parse(\"::before:hover\"),\n      Ok(SelectorList::from_vec(vec![Selector::from_vec(\n        vec![\n          Component::Combinator(Combinator::PseudoElement),\n          Component::PseudoElement(PseudoElement::Before),\n          Component::NonTSPseudoClass(PseudoClass::Hover),\n        ],\n        specificity(0, 1, 1),\n        SelectorFlags::HAS_PSEUDO,\n      )]))\n    );\n    assert_eq!(\n      parse(\"::before:hover:hover\"),\n      Ok(SelectorList::from_vec(vec![Selector::from_vec(\n        vec![\n          Component::Combinator(Combinator::PseudoElement),\n          Component::PseudoElement(PseudoElement::Before),\n          Component::NonTSPseudoClass(PseudoClass::Hover),\n          Component::NonTSPseudoClass(PseudoClass::Hover),\n        ],\n        specificity(0, 2, 1),\n        SelectorFlags::HAS_PSEUDO,\n      )]))\n    );\n    assert!(parse(\"::before:hover:lang(foo)\").is_err());\n    assert!(parse(\"::before:hover .foo\").is_err());\n    assert!(parse(\"::before .foo\").is_err());\n    assert!(parse(\"::before ~ bar\").is_err());\n    assert!(parse(\"::before:active\").is_ok());\n\n    // https://github.com/servo/servo/issues/15335\n    assert!(parse(\":: before\").is_err());\n    assert_eq!(\n      parse(\"div ::after\"),\n      Ok(SelectorList::from_vec(vec![Selector::from_vec(\n        vec![\n          Component::LocalName(LocalName {\n            name: DummyAtom::from(\"div\"),\n            lower_name: DummyAtom::from(\"div\"),\n          }),\n          Component::Combinator(Combinator::Descendant),\n          Component::Combinator(Combinator::PseudoElement),\n          Component::PseudoElement(PseudoElement::After),\n        ],\n        specificity(0, 0, 2),\n        SelectorFlags::HAS_PSEUDO,\n      )]))\n    );\n    assert_eq!(\n      parse(\"#d1 > .ok\"),\n      Ok(SelectorList::from_vec(vec![Selector::from_vec(\n        vec![\n          Component::ID(DummyAtom::from(\"d1\")),\n          Component::Combinator(Combinator::Child),\n          Component::Class(DummyAtom::from(\"ok\")),\n        ],\n        (1 << 20) + (1 << 10) + (0 << 0),\n        Default::default(),\n      )]))\n    );\n    parser.default_ns = None;\n    assert!(parse(\":not(#provel.old)\").is_ok());\n    assert!(parse(\":not(#provel > old)\").is_ok());\n    assert!(parse(\"table[rules]:not([rules=\\\"none\\\"]):not([rules=\\\"\\\"])\").is_ok());\n    // https://github.com/servo/servo/issues/16017\n    assert_eq!(\n      parse_ns(\":not(*)\", &parser),\n      Ok(SelectorList::from_vec(vec![Selector::from_vec(\n        vec![Component::Negation(\n          vec![Selector::from_vec(\n            vec![Component::ExplicitUniversalType],\n            specificity(0, 0, 0),\n            Default::default(),\n          )]\n          .into_boxed_slice()\n        )],\n        specificity(0, 0, 0),\n        Default::default(),\n      )]))\n    );\n    assert_eq!(\n      parse_ns(\":not(|*)\", &parser),\n      Ok(SelectorList::from_vec(vec![Selector::from_vec(\n        vec![Component::Negation(\n          vec![Selector::from_vec(\n            vec![Component::ExplicitNoNamespace, Component::ExplicitUniversalType,],\n            specificity(0, 0, 0),\n            Default::default(),\n          )]\n          .into_boxed_slice(),\n        )],\n        specificity(0, 0, 0),\n        Default::default(),\n      )]))\n    );\n    // *| should be elided if there is no default namespace.\n    // https://github.com/servo/servo/pull/17537\n    // assert_eq!(\n    //   parse_ns_expected(\":not(*|*)\", &parser, Some(\":not(*)\")),\n    //   Ok(SelectorList::from_vec(vec![Selector::from_vec(\n    //     vec![Component::Negation(\n    //       vec![Selector::from_vec(\n    //         vec![Component::ExplicitUniversalType],\n    //         specificity(0, 0, 0),\n    //         Default::default()\n    //       )]\n    //       .into_boxed_slice()\n    //     )],\n    //     specificity(0, 0, 0),\n    //     Default::default(),\n    //   )]))\n    // );\n\n    assert!(parse(\"::slotted()\").is_err());\n    assert!(parse(\"::slotted(div)\").is_ok());\n    assert!(parse(\"::slotted(div).foo\").is_err());\n    assert!(parse(\"::slotted(div + bar)\").is_err());\n    assert!(parse(\"::slotted(div) + foo\").is_err());\n\n    assert!(parse(\"::part()\").is_err());\n    assert!(parse(\"::part(42)\").is_err());\n    assert!(parse(\"::part(foo bar)\").is_ok());\n    assert!(parse(\"::part(foo):hover\").is_ok());\n    assert!(parse(\"::part(foo) + bar\").is_err());\n\n    assert!(parse(\"div ::slotted(div)\").is_ok());\n    assert!(parse(\"div + slot::slotted(div)\").is_ok());\n    assert!(parse(\"div + slot::slotted(div.foo)\").is_ok());\n    assert!(parse(\"slot::slotted(div,foo)::first-line\").is_err());\n    assert!(parse(\"::slotted(div)::before\").is_ok());\n    assert!(parse(\"slot::slotted(div,foo)\").is_err());\n\n    assert!(parse(\"foo:where()\").is_err());\n    assert!(parse(\"foo:where(div, foo, .bar baz)\").is_ok());\n    assert!(parse(\"foo:where(::before)\").is_err());\n\n    assert!(parse(\"foo::details-content\").is_ok());\n    assert!(parse(\"foo::target-text\").is_ok());\n\n    assert!(parse(\"::highlight\").is_err());\n    assert!(parse(\"::highlight()\").is_err());\n    assert!(parse(\"::highlight(custom-highlight-name)\").is_ok());\n\n    assert!(parse(\"select::picker\").is_err());\n    assert!(parse(\"::picker()\").is_err());\n    assert!(parse(\"::picker(select)\").is_ok());\n    assert!(parse(\"select::picker-icon\").is_ok());\n    assert!(parse(\"option::checkmark\").is_ok());\n\n    assert!(parse(\"::grammar-error\").is_ok());\n    assert!(parse(\"::spelling-error\").is_ok());\n    assert!(parse(\"::part(mypart)::grammar-error\").is_ok());\n    assert!(parse(\"::part(mypart)::spelling-error\").is_ok());\n  }\n\n  #[test]\n  fn test_pseudo_iter() {\n    let selector = &parse(\"q::before\").unwrap().0[0];\n    assert!(!selector.is_universal());\n    let mut iter = selector.iter();\n    assert_eq!(iter.next(), Some(&Component::PseudoElement(PseudoElement::Before)));\n    assert_eq!(iter.next(), None);\n    let combinator = iter.next_sequence();\n    assert_eq!(combinator, Some(Combinator::PseudoElement));\n    assert!(matches!(iter.next(), Some(&Component::LocalName(..))));\n    assert_eq!(iter.next(), None);\n    assert_eq!(iter.next_sequence(), None);\n  }\n\n  #[test]\n  fn test_universal() {\n    let selector = &parse_ns(\n      \"*|*::before\",\n      &DummyParser::default_with_namespace(DummyAtom::from(\"https://mozilla.org\")),\n    )\n    .unwrap()\n    .0[0];\n    assert!(selector.is_universal());\n  }\n\n  #[test]\n  fn test_empty_pseudo_iter() {\n    let selector = &parse(\"::before\").unwrap().0[0];\n    assert!(selector.is_universal());\n    let mut iter = selector.iter();\n    assert_eq!(iter.next(), Some(&Component::PseudoElement(PseudoElement::Before)));\n    assert_eq!(iter.next(), None);\n    assert_eq!(iter.next_sequence(), Some(Combinator::PseudoElement));\n    assert_eq!(iter.next(), None);\n    assert_eq!(iter.next_sequence(), None);\n  }\n\n  struct TestVisitor {\n    seen: Vec<String>,\n  }\n\n  impl<'i> SelectorVisitor<'i> for TestVisitor {\n    type Impl = DummySelectorImpl;\n\n    fn visit_simple_selector(&mut self, s: &Component<DummySelectorImpl>) -> bool {\n      let mut dest = String::new();\n      s.to_css(&mut dest).unwrap();\n      self.seen.push(dest);\n      true\n    }\n  }\n\n  #[test]\n  fn visitor() {\n    let mut test_visitor = TestVisitor { seen: vec![] };\n    parse(\":not(:hover) ~ label\").unwrap().0[0].visit(&mut test_visitor);\n    assert!(test_visitor.seen.contains(&\":hover\".into()));\n\n    let mut test_visitor = TestVisitor { seen: vec![] };\n    parse(\"::before:hover\").unwrap().0[0].visit(&mut test_visitor);\n    assert!(test_visitor.seen.contains(&\":hover\".into()));\n  }\n}\n"
  },
  {
    "path": "selectors/serialization.rs",
    "content": "use crate::{\n  attr::{\n    AttrSelectorOperator, AttrSelectorWithOptionalNamespace, NamespaceConstraint, ParsedAttrSelectorOperation,\n    ParsedCaseSensitivity,\n  },\n  builder::SelectorBuilder,\n  parser::{Combinator, Component, LocalName, NthOfSelectorData, NthSelectorData, NthType, Selector},\n  SelectorImpl,\n};\nuse std::borrow::Cow;\n\nuse cssparser::CowRcStr;\n#[cfg(feature = \"jsonschema\")]\nuse schemars::JsonSchema;\n\n#[derive(serde::Serialize, serde::Deserialize)]\n#[serde(tag = \"type\", rename_all = \"kebab-case\")]\n#[cfg_attr(\n  feature = \"jsonschema\",\n  derive(schemars::JsonSchema),\n  schemars(\n    rename = \"SelectorComponent\",\n    bound = \"Impl: JsonSchema, Impl::NonTSPseudoClass: schemars::JsonSchema, Impl::PseudoElement: schemars::JsonSchema, Impl::VendorPrefix: schemars::JsonSchema, PseudoClass: schemars::JsonSchema, PseudoElement: schemars::JsonSchema, VendorPrefix: schemars::JsonSchema\"\n  )\n)]\nenum SerializedComponent<'i, 's, Impl: SelectorImpl<'s>, PseudoClass, PseudoElement, VendorPrefix> {\n  Combinator {\n    value: Combinator,\n  },\n  Universal,\n  #[serde(borrow)]\n  Namespace(Namespace<'i>),\n  Type {\n    name: Cow<'i, str>,\n  },\n  #[serde(rename = \"id\")]\n  ID {\n    name: Cow<'i, str>,\n  },\n  Class {\n    name: Cow<'i, str>,\n  },\n  Attribute(AttrSelector<'i>),\n  #[serde(\n    borrow,\n    bound(\n      serialize = \"PseudoClass: serde::Serialize, Impl::NonTSPseudoClass: serde::Serialize, Impl::PseudoElement: serde::Serialize, Impl::VendorPrefix: serde::Serialize, VendorPrefix: serde::Serialize\",\n      deserialize = \"PseudoClass: serde::Deserialize<'de>, Impl::NonTSPseudoClass: serde::Deserialize<'de>, Impl::PseudoElement: serde::Deserialize<'de>, Impl::VendorPrefix: serde::Deserialize<'de>, VendorPrefix: serde::Deserialize<'de>\"\n    )\n  )]\n  PseudoClass(SerializedPseudoClass<'s, Impl, PseudoClass, VendorPrefix>),\n  #[serde(\n    borrow,\n    bound(\n      serialize = \"PseudoElement: serde::Serialize\",\n      deserialize = \"PseudoElement: serde::Deserialize<'de>\"\n    )\n  )]\n  PseudoElement(SerializedPseudoElement<'i, 's, Impl, PseudoElement>),\n  Nesting,\n}\n\n#[derive(serde::Serialize, serde::Deserialize)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[serde(tag = \"kind\", rename_all = \"kebab-case\")]\nenum Namespace<'i> {\n  None,\n  Any,\n  Named { prefix: Cow<'i, str> },\n}\n\n#[derive(serde::Serialize, serde::Deserialize)]\n#[serde(tag = \"kind\", rename_all = \"kebab-case\")]\n#[cfg_attr(\n  feature = \"jsonschema\",\n  derive(schemars::JsonSchema),\n  schemars(\n    rename = \"TSPseudoClass\",\n    bound = \"Impl: JsonSchema, Impl::NonTSPseudoClass: schemars::JsonSchema, Impl::PseudoElement: schemars::JsonSchema, Impl::VendorPrefix: schemars::JsonSchema, VendorPrefix: schemars::JsonSchema\"\n  )\n)]\nenum TSPseudoClass<'s, Impl: SelectorImpl<'s>, VendorPrefix> {\n  Not {\n    #[serde(\n      borrow,\n      bound(\n        serialize = \"Impl::NonTSPseudoClass: serde::Serialize, Impl::PseudoElement: serde::Serialize\",\n        deserialize = \"Impl::NonTSPseudoClass: serde::Deserialize<'de>, Impl::PseudoElement: serde::Deserialize<'de>\"\n      )\n    )]\n    selectors: Box<[Selector<'s, Impl>]>,\n  },\n  FirstChild,\n  LastChild,\n  OnlyChild,\n  Root,\n  Empty,\n  Scope,\n  NthChild {\n    a: i32,\n    b: i32,\n    #[serde(\n      borrow,\n      bound(\n        serialize = \"Impl::NonTSPseudoClass: serde::Serialize, Impl::PseudoElement: serde::Serialize, Impl::VendorPrefix: serde::Serialize\",\n        deserialize = \"Impl::NonTSPseudoClass: serde::Deserialize<'de>, Impl::PseudoElement: serde::Deserialize<'de>, Impl::VendorPrefix: serde::Deserialize<'de>\"\n      )\n    )]\n    of: Option<Box<[Selector<'s, Impl>]>>,\n  },\n  NthLastChild {\n    a: i32,\n    b: i32,\n    #[serde(\n      borrow,\n      bound(\n        serialize = \"Impl::NonTSPseudoClass: serde::Serialize, Impl::PseudoElement: serde::Serialize, Impl::VendorPrefix: serde::Serialize\",\n        deserialize = \"Impl::NonTSPseudoClass: serde::Deserialize<'de>, Impl::PseudoElement: serde::Deserialize<'de>, Impl::VendorPrefix: serde::Deserialize<'de>\"\n      )\n    )]\n    of: Option<Box<[Selector<'s, Impl>]>>,\n  },\n  NthCol {\n    a: i32,\n    b: i32,\n  },\n  NthLastCol {\n    a: i32,\n    b: i32,\n  },\n  NthOfType {\n    a: i32,\n    b: i32,\n  },\n  NthLastOfType {\n    a: i32,\n    b: i32,\n  },\n  FirstOfType,\n  LastOfType,\n  OnlyOfType,\n  Host {\n    #[serde(\n      borrow,\n      bound(\n        serialize = \"Impl::NonTSPseudoClass: serde::Serialize, Impl::PseudoElement: serde::Serialize, Impl::VendorPrefix: serde::Serialize\",\n        deserialize = \"Impl::NonTSPseudoClass: serde::Deserialize<'de>, Impl::PseudoElement: serde::Deserialize<'de>, Impl::VendorPrefix: serde::Deserialize<'de>\"\n      )\n    )]\n    selectors: Option<Selector<'s, Impl>>,\n  },\n  Where {\n    #[serde(\n      borrow,\n      bound(\n        serialize = \"Impl::NonTSPseudoClass: serde::Serialize, Impl::PseudoElement: serde::Serialize, Impl::VendorPrefix: serde::Serialize\",\n        deserialize = \"Impl::NonTSPseudoClass: serde::Deserialize<'de>, Impl::PseudoElement: serde::Deserialize<'de>, Impl::VendorPrefix: serde::Deserialize<'de>\"\n      )\n    )]\n    selectors: Box<[Selector<'s, Impl>]>,\n  },\n  Is {\n    #[serde(\n      borrow,\n      bound(\n        serialize = \"Impl::NonTSPseudoClass: serde::Serialize, Impl::PseudoElement: serde::Serialize, Impl::VendorPrefix: serde::Serialize\",\n        deserialize = \"Impl::NonTSPseudoClass: serde::Deserialize<'de>, Impl::PseudoElement: serde::Deserialize<'de>, Impl::VendorPrefix: serde::Deserialize<'de>\"\n      )\n    )]\n    selectors: Box<[Selector<'s, Impl>]>,\n  },\n  #[serde(rename_all = \"camelCase\")]\n  Any {\n    vendor_prefix: VendorPrefix,\n    #[serde(\n      borrow,\n      bound(\n        serialize = \"Impl::NonTSPseudoClass: serde::Serialize, Impl::PseudoElement: serde::Serialize, Impl::VendorPrefix: serde::Serialize\",\n        deserialize = \"Impl::NonTSPseudoClass: serde::Deserialize<'de>, Impl::PseudoElement: serde::Deserialize<'de>, Impl::VendorPrefix: serde::Deserialize<'de>\"\n      )\n    )]\n    selectors: Box<[Selector<'s, Impl>]>,\n  },\n  Has {\n    #[serde(\n      borrow,\n      bound(\n        serialize = \"Impl::NonTSPseudoClass: serde::Serialize, Impl::PseudoElement: serde::Serialize, Impl::VendorPrefix: serde::Serialize\",\n        deserialize = \"Impl::NonTSPseudoClass: serde::Deserialize<'de>, Impl::PseudoElement: serde::Deserialize<'de>, Impl::VendorPrefix: serde::Deserialize<'de>\"\n      )\n    )]\n    selectors: Box<[Selector<'s, Impl>]>,\n  },\n}\n\n#[derive(serde::Serialize, serde::Deserialize)]\n#[serde(untagged, rename_all = \"kebab-case\")]\n#[cfg_attr(\n  feature = \"jsonschema\",\n  derive(schemars::JsonSchema),\n  schemars(\n    rename = \"PseudoClass\",\n    bound = \"Impl: JsonSchema, Impl::NonTSPseudoClass: schemars::JsonSchema, Impl::PseudoElement: schemars::JsonSchema, Impl::VendorPrefix: schemars::JsonSchema, PseudoClass: schemars::JsonSchema, VendorPrefix: schemars::JsonSchema\"\n  )\n)]\nenum SerializedPseudoClass<'s, Impl: SelectorImpl<'s>, PseudoClass, VendorPrefix> {\n  #[serde(\n    borrow,\n    bound(\n      serialize = \"Impl::NonTSPseudoClass: serde::Serialize, Impl::PseudoElement: serde::Serialize, Impl::VendorPrefix: serde::Serialize, VendorPrefix: serde::Serialize\",\n      deserialize = \"Impl::NonTSPseudoClass: serde::Deserialize<'de>, Impl::PseudoElement: serde::Deserialize<'de>, Impl::VendorPrefix: serde::Deserialize<'de>, VendorPrefix: serde::Deserialize<'de>\"\n    )\n  )]\n  TS(TSPseudoClass<'s, Impl, VendorPrefix>),\n  NonTS(PseudoClass),\n}\n\n#[derive(serde::Serialize, serde::Deserialize)]\n#[serde(tag = \"kind\", rename_all = \"kebab-case\")]\n#[cfg_attr(\n  feature = \"jsonschema\",\n  derive(schemars::JsonSchema),\n  schemars(\n    rename = \"BuiltinPseudoElement\",\n    bound = \"Impl: JsonSchema, Impl::NonTSPseudoClass: schemars::JsonSchema, Impl::PseudoElement: schemars::JsonSchema, Impl::VendorPrefix: schemars::JsonSchema\"\n  )\n)]\nenum BuiltinPseudoElement<'i, 's, Impl: SelectorImpl<'s>> {\n  Slotted {\n    #[serde(\n      borrow,\n      bound(\n        serialize = \"Impl::NonTSPseudoClass: serde::Serialize, Impl::PseudoElement: serde::Serialize, Impl::VendorPrefix: serde::Serialize\",\n        deserialize = \"Impl::NonTSPseudoClass: serde::Deserialize<'de>, Impl::PseudoElement: serde::Deserialize<'de>, Impl::VendorPrefix: serde::Deserialize<'de>\"\n      )\n    )]\n    selector: Selector<'s, Impl>,\n  },\n  Part {\n    names: Vec<Cow<'i, str>>,\n  },\n}\n\n#[derive(serde::Serialize, serde::Deserialize)]\n#[serde(untagged, rename_all = \"kebab-case\")]\n#[cfg_attr(\n  feature = \"jsonschema\",\n  derive(schemars::JsonSchema),\n  schemars(\n    rename = \"PseudoElement\",\n    bound = \"Impl: JsonSchema, Impl::NonTSPseudoClass: schemars::JsonSchema, Impl::PseudoElement: schemars::JsonSchema, Impl::VendorPrefix: schemars::JsonSchema, PseudoElement: schemars::JsonSchema\"\n  )\n)]\nenum SerializedPseudoElement<'i, 's, Impl: SelectorImpl<'s>, PseudoElement> {\n  #[serde(\n    borrow,\n    bound(\n      serialize = \"Impl::NonTSPseudoClass: serde::Serialize, Impl::PseudoElement: serde::Serialize, Impl::VendorPrefix: serde::Serialize\",\n      deserialize = \"Impl::NonTSPseudoClass: serde::Deserialize<'de>, Impl::PseudoElement: serde::Deserialize<'de>, Impl::VendorPrefix: serde::Deserialize<'de>\"\n    )\n  )]\n  Builtin(BuiltinPseudoElement<'i, 's, Impl>),\n  Custom(PseudoElement),\n}\n\n#[derive(serde::Serialize, serde::Deserialize)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\nstruct AttrSelector<'i> {\n  #[serde(borrow)]\n  namespace: Option<NamespaceConstraint<NamespaceValue<'i>>>,\n  name: Cow<'i, str>,\n  operation: Option<AttrOperation<'i>>,\n}\n\n#[derive(serde::Serialize, serde::Deserialize)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\nstruct NamespaceValue<'i> {\n  prefix: Cow<'i, str>,\n  url: Cow<'i, str>,\n}\n\n#[derive(serde::Serialize, serde::Deserialize)]\n#[serde(rename_all = \"camelCase\")]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\nstruct AttrOperation<'i> {\n  operator: AttrSelectorOperator,\n  value: Cow<'i, str>,\n  #[serde(default)]\n  case_sensitivity: ParsedCaseSensitivity,\n}\n\nimpl<'i, Impl: SelectorImpl<'i>> serde::Serialize for Component<'i, Impl>\nwhere\n  Impl::NonTSPseudoClass: serde::Serialize,\n  Impl::PseudoElement: serde::Serialize,\n  Impl::VendorPrefix: serde::Serialize,\n{\n  fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>\n  where\n    S: serde::Serializer,\n  {\n    let c: SerializedComponent<'_, 'i, Impl, _, _, _> = match self {\n      Component::Combinator(c) => SerializedComponent::Combinator { value: c.clone() },\n      Component::ExplicitUniversalType => SerializedComponent::Universal,\n      Component::ExplicitAnyNamespace => SerializedComponent::Namespace(Namespace::Any),\n      Component::ExplicitNoNamespace => SerializedComponent::Namespace(Namespace::None),\n      // can't actually happen anymore.\n      Component::DefaultNamespace(_url) => SerializedComponent::Namespace(Namespace::Any),\n      Component::Namespace(prefix, _url) => SerializedComponent::Namespace(Namespace::Named {\n        prefix: prefix.as_ref().into(),\n      }),\n      Component::LocalName(name) => SerializedComponent::Type {\n        name: name.name.as_ref().into(),\n      },\n      Component::ID(name) => SerializedComponent::ID {\n        name: name.as_ref().into(),\n      },\n      Component::Class(name) => SerializedComponent::Class {\n        name: name.as_ref().into(),\n      },\n      Component::AttributeInNoNamespace {\n        local_name,\n        operator,\n        value,\n        case_sensitivity,\n        ..\n      } => SerializedComponent::Attribute(AttrSelector {\n        namespace: None,\n        name: local_name.as_ref().into(),\n        operation: Some(AttrOperation {\n          operator: operator.clone(),\n          case_sensitivity: case_sensitivity.clone(),\n          value: value.as_ref().into(),\n        }),\n      }),\n      Component::AttributeInNoNamespaceExists { local_name, .. } => SerializedComponent::Attribute(AttrSelector {\n        namespace: None,\n        name: local_name.as_ref().into(),\n        operation: None,\n      }),\n      Component::AttributeOther(other) => SerializedComponent::Attribute(AttrSelector {\n        namespace: other.namespace.as_ref().map(|namespace| match namespace {\n          NamespaceConstraint::Any => NamespaceConstraint::Any,\n          NamespaceConstraint::Specific(s) => NamespaceConstraint::Specific(NamespaceValue {\n            prefix: s.0.as_ref().into(),\n            url: s.1.as_ref().into(),\n          }),\n        }),\n        name: other.local_name.as_ref().into(),\n        operation: match &other.operation {\n          ParsedAttrSelectorOperation::Exists => None,\n          ParsedAttrSelectorOperation::WithValue {\n            operator,\n            case_sensitivity,\n            expected_value,\n          } => Some(AttrOperation {\n            operator: operator.clone(),\n            case_sensitivity: case_sensitivity.clone(),\n            value: expected_value.as_ref().into(),\n          }),\n        },\n      }),\n      Component::NonTSPseudoClass(c) => SerializedComponent::PseudoClass(SerializedPseudoClass::NonTS(c)),\n      Component::Negation(s) => {\n        SerializedComponent::PseudoClass(SerializedPseudoClass::TS(TSPseudoClass::Not { selectors: s.clone() }))\n      }\n      Component::Root => SerializedComponent::PseudoClass(SerializedPseudoClass::TS(TSPseudoClass::Root)),\n      Component::Empty => SerializedComponent::PseudoClass(SerializedPseudoClass::TS(TSPseudoClass::Empty)),\n      Component::Scope => SerializedComponent::PseudoClass(SerializedPseudoClass::TS(TSPseudoClass::Scope)),\n      Component::Nth(nth) => serialize_nth(nth, None),\n      Component::NthOf(nth) => serialize_nth(nth.nth_data(), Some(nth.clone_selectors())),\n      Component::Host(s) => {\n        SerializedComponent::PseudoClass(SerializedPseudoClass::TS(TSPseudoClass::Host { selectors: s.clone() }))\n      }\n      Component::Where(s) => {\n        SerializedComponent::PseudoClass(SerializedPseudoClass::TS(TSPseudoClass::Where { selectors: s.clone() }))\n      }\n      Component::Is(s) => {\n        SerializedComponent::PseudoClass(SerializedPseudoClass::TS(TSPseudoClass::Is { selectors: s.clone() }))\n      }\n      Component::Any(v, s) => SerializedComponent::PseudoClass(SerializedPseudoClass::TS(TSPseudoClass::Any {\n        vendor_prefix: v.clone(),\n        selectors: s.clone(),\n      })),\n      Component::Has(s) => {\n        SerializedComponent::PseudoClass(SerializedPseudoClass::TS(TSPseudoClass::Has { selectors: s.clone() }))\n      }\n      Component::PseudoElement(e) => SerializedComponent::PseudoElement(SerializedPseudoElement::Custom(e)),\n      Component::Slotted(s) => {\n        SerializedComponent::PseudoElement(SerializedPseudoElement::Builtin(BuiltinPseudoElement::Slotted {\n          selector: s.clone(),\n        }))\n      }\n      Component::Part(p) => {\n        SerializedComponent::PseudoElement(SerializedPseudoElement::Builtin(BuiltinPseudoElement::Part {\n          names: p.iter().map(|name| name.as_ref().into()).collect(),\n        }))\n      }\n      Component::Nesting => SerializedComponent::Nesting,\n    };\n\n    c.serialize(serializer)\n  }\n}\n\nfn serialize_nth<'i, 's, Impl: SelectorImpl<'s>>(\n  nth: &NthSelectorData,\n  of: Option<Box<[Selector<'s, Impl>]>>,\n) -> SerializedComponent<'i, 's, Impl, &'s Impl::NonTSPseudoClass, &'s Impl::PseudoElement, Impl::VendorPrefix> {\n  match nth.ty {\n    NthType::Child if nth.is_function => {\n      SerializedComponent::PseudoClass(SerializedPseudoClass::TS(TSPseudoClass::NthChild {\n        a: nth.a,\n        b: nth.b,\n        of,\n      }))\n    }\n    NthType::Child => SerializedComponent::PseudoClass(SerializedPseudoClass::TS(TSPseudoClass::FirstChild)),\n    NthType::LastChild if nth.is_function => {\n      SerializedComponent::PseudoClass(SerializedPseudoClass::TS(TSPseudoClass::NthLastChild {\n        a: nth.a,\n        b: nth.b,\n        of,\n      }))\n    }\n    NthType::LastChild => SerializedComponent::PseudoClass(SerializedPseudoClass::TS(TSPseudoClass::LastChild)),\n    NthType::OfType if nth.is_function => {\n      SerializedComponent::PseudoClass(SerializedPseudoClass::TS(TSPseudoClass::NthOfType {\n        a: nth.a,\n        b: nth.b,\n      }))\n    }\n    NthType::OfType => SerializedComponent::PseudoClass(SerializedPseudoClass::TS(TSPseudoClass::FirstOfType)),\n    NthType::LastOfType if nth.is_function => {\n      SerializedComponent::PseudoClass(SerializedPseudoClass::TS(TSPseudoClass::NthLastOfType {\n        a: nth.a,\n        b: nth.b,\n      }))\n    }\n    NthType::LastOfType => SerializedComponent::PseudoClass(SerializedPseudoClass::TS(TSPseudoClass::LastOfType)),\n    NthType::OnlyChild => SerializedComponent::PseudoClass(SerializedPseudoClass::TS(TSPseudoClass::OnlyChild)),\n    NthType::OnlyOfType => SerializedComponent::PseudoClass(SerializedPseudoClass::TS(TSPseudoClass::OnlyOfType)),\n    NthType::Col => {\n      SerializedComponent::PseudoClass(SerializedPseudoClass::TS(TSPseudoClass::NthCol { a: nth.a, b: nth.b }))\n    }\n    NthType::LastCol => SerializedComponent::PseudoClass(SerializedPseudoClass::TS(TSPseudoClass::NthLastCol {\n      a: nth.a,\n      b: nth.b,\n    })),\n  }\n}\n\nimpl<'de: 'i, 'i, Impl: SelectorImpl<'i>> serde::Deserialize<'de> for Component<'i, Impl>\nwhere\n  Impl::NonTSPseudoClass: serde::Deserialize<'de>,\n  Impl::PseudoElement: serde::Deserialize<'de>,\n  Impl::VendorPrefix: serde::Deserialize<'de>,\n{\n  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n  where\n    D: serde::Deserializer<'de>,\n  {\n    let c: SerializedComponent<'i, '_, Impl, _, _, _> = SerializedComponent::deserialize(deserializer)?;\n    Ok(match c {\n      SerializedComponent::Combinator { value } => Component::Combinator(value),\n      SerializedComponent::Universal => Component::ExplicitUniversalType,\n      SerializedComponent::Namespace(n) => match n {\n        Namespace::Any => Component::ExplicitAnyNamespace,\n        Namespace::None => Component::ExplicitNoNamespace,\n        Namespace::Named { prefix } => Component::Namespace(prefix.into(), CowRcStr::from(\"\").into()),\n      },\n      SerializedComponent::Type { name } => {\n        let name: Impl::LocalName = name.into();\n        Component::LocalName(LocalName {\n          name: name.clone(),\n          lower_name: name,\n        })\n      }\n      SerializedComponent::ID { name } => Component::ID(name.into()),\n      SerializedComponent::Class { name } => Component::Class(name.into()),\n      SerializedComponent::Attribute(attr) => {\n        let (local_name_lower_cow, local_name_is_ascii_lowercase) =\n          if let Some(first_uppercase) = attr.name.bytes().position(|byte| byte >= b'A' && byte <= b'Z') {\n            let mut string = attr.name.to_string();\n            string[first_uppercase..].make_ascii_lowercase();\n            (string.into(), false)\n          } else {\n            (attr.name.clone(), true)\n          };\n\n        if attr.namespace.is_some() || (!local_name_is_ascii_lowercase && attr.operation.is_some()) {\n          Component::AttributeOther(Box::new(AttrSelectorWithOptionalNamespace {\n            namespace: match attr.namespace {\n              Some(NamespaceConstraint::Any) => Some(NamespaceConstraint::Any),\n              Some(NamespaceConstraint::Specific(c)) => {\n                Some(NamespaceConstraint::Specific((c.prefix.into(), c.url.into())))\n              }\n              None => None,\n            },\n            local_name: attr.name.into(),\n            local_name_lower: local_name_lower_cow.into(),\n            operation: match attr.operation {\n              None => ParsedAttrSelectorOperation::Exists,\n              Some(AttrOperation {\n                operator,\n                case_sensitivity,\n                value,\n              }) => ParsedAttrSelectorOperation::WithValue {\n                operator,\n                case_sensitivity,\n                expected_value: value.into(),\n              },\n            },\n            never_matches: false, // TODO\n          }))\n        } else {\n          match attr.operation {\n            None => Component::AttributeInNoNamespaceExists {\n              local_name: attr.name.into(),\n              local_name_lower: local_name_lower_cow.into(),\n            },\n            Some(AttrOperation {\n              operator,\n              case_sensitivity,\n              value,\n            }) => Component::AttributeInNoNamespace {\n              local_name: attr.name.into(),\n              operator,\n              value: value.into(),\n              case_sensitivity,\n              never_matches: false, // TODO\n            },\n          }\n        }\n      }\n      SerializedComponent::PseudoClass(c) => match c {\n        SerializedPseudoClass::NonTS(c) => Component::NonTSPseudoClass(c),\n        SerializedPseudoClass::TS(TSPseudoClass::Not { selectors }) => Component::Negation(selectors),\n        SerializedPseudoClass::TS(TSPseudoClass::FirstChild) => Component::Nth(NthSelectorData::first(false)),\n        SerializedPseudoClass::TS(TSPseudoClass::LastChild) => Component::Nth(NthSelectorData::last(false)),\n        SerializedPseudoClass::TS(TSPseudoClass::OnlyChild) => Component::Nth(NthSelectorData::only(false)),\n        SerializedPseudoClass::TS(TSPseudoClass::Root) => Component::Root,\n        SerializedPseudoClass::TS(TSPseudoClass::Empty) => Component::Empty,\n        SerializedPseudoClass::TS(TSPseudoClass::Scope) => Component::Scope,\n        SerializedPseudoClass::TS(TSPseudoClass::FirstOfType) => Component::Nth(NthSelectorData::first(true)),\n        SerializedPseudoClass::TS(TSPseudoClass::LastOfType) => Component::Nth(NthSelectorData::last(true)),\n        SerializedPseudoClass::TS(TSPseudoClass::OnlyOfType) => Component::Nth(NthSelectorData::only(true)),\n        SerializedPseudoClass::TS(\n          ref c @ TSPseudoClass::NthChild { a, b, ref of } | ref c @ TSPseudoClass::NthLastChild { a, b, ref of },\n        ) => {\n          let data = NthSelectorData {\n            ty: match c {\n              TSPseudoClass::NthChild { .. } => NthType::Child,\n              TSPseudoClass::NthLastChild { .. } => NthType::LastChild,\n              _ => unreachable!(),\n            },\n            is_function: true,\n            a,\n            b,\n          };\n          match of {\n            Some(of) => Component::NthOf(NthOfSelectorData::new(data, of.clone())),\n            None => Component::Nth(data),\n          }\n        }\n        SerializedPseudoClass::TS(\n          ref c @ TSPseudoClass::NthCol { a, b }\n          | ref c @ TSPseudoClass::NthLastCol { a, b }\n          | ref c @ TSPseudoClass::NthOfType { a, b }\n          | ref c @ TSPseudoClass::NthLastOfType { a, b },\n        ) => Component::Nth(NthSelectorData {\n          ty: match c {\n            TSPseudoClass::NthCol { .. } => NthType::Col,\n            TSPseudoClass::NthLastCol { .. } => NthType::LastCol,\n            TSPseudoClass::NthOfType { .. } => NthType::OfType,\n            TSPseudoClass::NthLastOfType { .. } => NthType::LastOfType,\n            _ => unreachable!(),\n          },\n          is_function: true,\n          a,\n          b,\n        }),\n        SerializedPseudoClass::TS(TSPseudoClass::Host { selectors }) => Component::Host(selectors),\n        SerializedPseudoClass::TS(TSPseudoClass::Where { selectors }) => Component::Where(selectors),\n        SerializedPseudoClass::TS(TSPseudoClass::Is { selectors }) => Component::Is(selectors),\n        SerializedPseudoClass::TS(TSPseudoClass::Any {\n          vendor_prefix,\n          selectors,\n        }) => Component::Any(vendor_prefix, selectors),\n        SerializedPseudoClass::TS(TSPseudoClass::Has { selectors }) => Component::Has(selectors),\n      },\n      SerializedComponent::PseudoElement(value) => match value {\n        SerializedPseudoElement::Custom(e) => Component::PseudoElement(e),\n        SerializedPseudoElement::Builtin(BuiltinPseudoElement::Part { names }) => {\n          Component::Part(names.into_iter().map(|name| name.into()).collect())\n        }\n        SerializedPseudoElement::Builtin(BuiltinPseudoElement::Slotted { selector }) => {\n          Component::Slotted(selector)\n        }\n      },\n      SerializedComponent::Nesting => Component::Nesting,\n    })\n  }\n}\n\nimpl<'i, Impl: SelectorImpl<'i>> serde::Serialize for Selector<'i, Impl>\nwhere\n  Impl::NonTSPseudoClass: serde::Serialize,\n  Impl::VendorPrefix: serde::Serialize,\n  Impl::PseudoElement: serde::Serialize,\n{\n  fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>\n  where\n    S: serde::Serializer,\n  {\n    use serde::ser::SerializeSeq;\n    let skipped_combinators = self\n      .iter_raw_match_order()\n      .filter(|c| {\n        matches!(\n          c,\n          Component::Combinator(Combinator::Part | Combinator::PseudoElement | Combinator::SlotAssignment)\n        )\n      })\n      .count();\n    let mut seq = serializer.serialize_seq(Some(self.len() - skipped_combinators))?;\n\n    let mut combinators = self.iter_raw_match_order().rev().filter(|x| x.is_combinator());\n    let compound_selectors = self.iter_raw_match_order().as_slice().split(|x| x.is_combinator()).rev();\n\n    for compound in compound_selectors {\n      if compound.is_empty() {\n        continue;\n      }\n\n      for component in compound {\n        seq.serialize_element(component)?;\n      }\n\n      if let Some(combinator) = combinators.next() {\n        if !matches!(\n          combinator,\n          Component::Combinator(Combinator::Part | Combinator::PseudoElement | Combinator::SlotAssignment)\n        ) {\n          seq.serialize_element(combinator)?;\n        }\n      }\n    }\n    seq.end()\n  }\n}\n\nimpl<'de: 'i, 'i, Impl: SelectorImpl<'i>> serde::Deserialize<'de> for Selector<'i, Impl>\nwhere\n  Impl::NonTSPseudoClass: serde::Deserialize<'de>,\n  Impl::VendorPrefix: serde::Deserialize<'de>,\n  Impl::PseudoElement: serde::Deserialize<'de>,\n{\n  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n  where\n    D: serde::Deserializer<'de>,\n  {\n    #[cfg(feature = \"serde\")]\n    struct SelectorVisitor<'i, Impl: SelectorImpl<'i>> {\n      marker: std::marker::PhantomData<Selector<'i, Impl>>,\n    }\n\n    #[cfg(feature = \"serde\")]\n    impl<'de: 'i, 'i, Impl: SelectorImpl<'i>> serde::de::Visitor<'de> for SelectorVisitor<'i, Impl>\n    where\n      Impl::NonTSPseudoClass: serde::Deserialize<'de>,\n      Impl::VendorPrefix: serde::Deserialize<'de>,\n      Impl::PseudoElement: serde::Deserialize<'de>,\n    {\n      type Value = Selector<'i, Impl>;\n\n      fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {\n        formatter.write_str(\"a list of components\")\n      }\n\n      fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>\n      where\n        A: serde::de::SeqAccess<'de>,\n      {\n        let mut builder = SelectorBuilder::default();\n        while let Some(component) = seq.next_element::<Component<'i, Impl>>()? {\n          if let Some(combinator) = component.as_combinator() {\n            builder.push_combinator(combinator);\n          } else {\n            match component {\n              Component::Slotted(_) => builder.push_combinator(Combinator::SlotAssignment),\n              Component::Part(_) => builder.push_combinator(Combinator::Part),\n              Component::PseudoElement(_) => builder.push_combinator(Combinator::PseudoElement),\n              _ => {}\n            }\n            builder.push_simple_selector(component);\n          }\n        }\n\n        let (spec, components) = builder.build(false, false, false);\n        Ok(Selector::new(spec, components))\n      }\n    }\n\n    deserializer.deserialize_seq(SelectorVisitor {\n      marker: std::marker::PhantomData,\n    })\n  }\n}\n\n#[cfg(feature = \"jsonschema\")]\nimpl<'i, Impl: SelectorImpl<'i>> schemars::JsonSchema for Selector<'i, Impl>\nwhere\n  Impl: schemars::JsonSchema,\n  Impl::NonTSPseudoClass: schemars::JsonSchema,\n  Impl::PseudoElement: schemars::JsonSchema,\n  Impl::VendorPrefix: schemars::JsonSchema,\n{\n  fn is_referenceable() -> bool {\n    true\n  }\n\n  fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {\n    Vec::<SerializedComponent<'_, '_, Impl, Impl::NonTSPseudoClass, Impl::PseudoElement, Impl::VendorPrefix>>::json_schema(gen)\n  }\n\n  fn schema_name() -> String {\n    \"Selector\".into()\n  }\n}\n"
  },
  {
    "path": "selectors/sink.rs",
    "content": "/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at https://mozilla.org/MPL/2.0/. */\n\n//! Small helpers to abstract over different containers.\n#![deny(missing_docs)]\n\nuse smallvec::{Array, SmallVec};\n\n/// A trait to abstract over a `push` method that may be implemented for\n/// different kind of types.\n///\n/// Used to abstract over `Array`, `SmallVec` and `Vec`, and also to implement a\n/// type which `push` method does only tweak a byte when we only need to check\n/// for the presence of something.\npub trait Push<T> {\n  /// Push a value into self.\n  fn push(&mut self, value: T);\n}\n\nimpl<T> Push<T> for Vec<T> {\n  fn push(&mut self, value: T) {\n    Vec::push(self, value);\n  }\n}\n\nimpl<A: Array> Push<A::Item> for SmallVec<A> {\n  fn push(&mut self, value: A::Item) {\n    SmallVec::push(self, value);\n  }\n}\n"
  },
  {
    "path": "selectors/tree.rs",
    "content": "/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at https://mozilla.org/MPL/2.0/. */\n\n//! Traits that nodes must implement. Breaks the otherwise-cyclic dependency\n//! between layout and style.\n\nuse crate::attr::{AttrSelectorOperation, CaseSensitivity, NamespaceConstraint};\nuse crate::matching::{ElementSelectorFlags, MatchingContext};\nuse crate::parser::SelectorImpl;\nuse std::fmt::Debug;\nuse std::ptr::NonNull;\n\n/// Opaque representation of an Element, for identity comparisons.\n#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]\npub struct OpaqueElement(NonNull<()>);\n\nunsafe impl Send for OpaqueElement {}\n\nimpl OpaqueElement {\n  /// Creates a new OpaqueElement from an arbitrarily-typed pointer.\n  pub fn new<T>(ptr: &T) -> Self {\n    unsafe { OpaqueElement(NonNull::new_unchecked(ptr as *const T as *const () as *mut ())) }\n  }\n}\n\npub trait Element<'i>: Sized + Clone + Debug {\n  type Impl: SelectorImpl<'i>;\n\n  /// Converts self into an opaque representation.\n  fn opaque(&self) -> OpaqueElement;\n\n  fn parent_element(&self) -> Option<Self>;\n\n  /// Whether the parent node of this element is a shadow root.\n  fn parent_node_is_shadow_root(&self) -> bool;\n\n  /// The host of the containing shadow root, if any.\n  fn containing_shadow_host(&self) -> Option<Self>;\n\n  /// The parent of a given pseudo-element, after matching a pseudo-element\n  /// selector.\n  ///\n  /// This is guaranteed to be called in a pseudo-element.\n  fn pseudo_element_originating_element(&self) -> Option<Self> {\n    debug_assert!(self.is_pseudo_element());\n    self.parent_element()\n  }\n\n  /// Whether we're matching on a pseudo-element.\n  fn is_pseudo_element(&self) -> bool;\n\n  /// Skips non-element nodes\n  fn prev_sibling_element(&self) -> Option<Self>;\n\n  /// Skips non-element nodes\n  fn next_sibling_element(&self) -> Option<Self>;\n\n  fn is_html_element_in_html_document(&self) -> bool;\n\n  fn has_local_name(&self, local_name: &<Self::Impl as SelectorImpl<'i>>::BorrowedLocalName) -> bool;\n\n  /// Empty string for no namespace\n  fn has_namespace(&self, ns: &<Self::Impl as SelectorImpl<'i>>::BorrowedNamespaceUrl) -> bool;\n\n  /// Whether this element and the `other` element have the same local name and namespace.\n  fn is_same_type(&self, other: &Self) -> bool;\n\n  fn attr_matches(\n    &self,\n    ns: &NamespaceConstraint<&<Self::Impl as SelectorImpl<'i>>::NamespaceUrl>,\n    local_name: &<Self::Impl as SelectorImpl<'i>>::LocalName,\n    operation: &AttrSelectorOperation<&<Self::Impl as SelectorImpl<'i>>::AttrValue>,\n  ) -> bool;\n\n  fn match_non_ts_pseudo_class<F>(\n    &self,\n    pc: &<Self::Impl as SelectorImpl<'i>>::NonTSPseudoClass,\n    context: &mut MatchingContext<'_, 'i, Self::Impl>,\n    flags_setter: &mut F,\n  ) -> bool\n  where\n    F: FnMut(&Self, ElementSelectorFlags);\n\n  fn match_pseudo_element(\n    &self,\n    pe: &<Self::Impl as SelectorImpl<'i>>::PseudoElement,\n    context: &mut MatchingContext<'_, 'i, Self::Impl>,\n  ) -> bool;\n\n  /// Whether this element is a `link`.\n  fn is_link(&self) -> bool;\n\n  /// Returns whether the element is an HTML <slot> element.\n  fn is_html_slot_element(&self) -> bool;\n\n  /// Returns the assigned <slot> element this element is assigned to.\n  ///\n  /// Necessary for the `::slotted` pseudo-class.\n  fn assigned_slot(&self) -> Option<Self> {\n    None\n  }\n\n  fn has_id(&self, id: &<Self::Impl as SelectorImpl<'i>>::Identifier, case_sensitivity: CaseSensitivity) -> bool;\n\n  fn has_class(\n    &self,\n    name: &<Self::Impl as SelectorImpl<'i>>::Identifier,\n    case_sensitivity: CaseSensitivity,\n  ) -> bool;\n\n  /// Returns the mapping from the `exportparts` attribute in the reverse\n  /// direction, that is, in an outer-tree -> inner-tree direction.\n  fn imported_part(\n    &self,\n    name: &<Self::Impl as SelectorImpl<'i>>::Identifier,\n  ) -> Option<<Self::Impl as SelectorImpl<'i>>::Identifier>;\n\n  fn is_part(&self, name: &<Self::Impl as SelectorImpl<'i>>::Identifier) -> bool;\n\n  /// Returns whether this element matches `:empty`.\n  ///\n  /// That is, whether it does not contain any child element or any non-zero-length text node.\n  /// See http://dev.w3.org/csswg/selectors-3/#empty-pseudo\n  fn is_empty(&self) -> bool;\n\n  /// Returns whether this element matches `:root`,\n  /// i.e. whether it is the root element of a document.\n  ///\n  /// Note: this can be false even if `.parent_element()` is `None`\n  /// if the parent node is a `DocumentFragment`.\n  fn is_root(&self) -> bool;\n\n  /// Returns whether this element should ignore matching nth child\n  /// selector.\n  fn ignores_nth_child_selectors(&self) -> bool {\n    false\n  }\n}\n"
  },
  {
    "path": "selectors/visitor.rs",
    "content": "/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at https://mozilla.org/MPL/2.0/. */\n\n//! Visitor traits for selectors.\n\n#![deny(missing_docs)]\n\nuse crate::attr::NamespaceConstraint;\nuse crate::parser::{Combinator, Component, Selector, SelectorImpl};\n\n/// A trait to visit selector properties.\n///\n/// All the `visit_foo` methods return a boolean indicating whether the\n/// traversal should continue or not.\npub trait SelectorVisitor<'i>: Sized {\n  /// The selector implementation this visitor wants to visit.\n  type Impl: SelectorImpl<'i>;\n\n  /// Visit an attribute selector that may match (there are other selectors\n  /// that may never match, like those containing whitespace or the empty\n  /// string).\n  fn visit_attribute_selector(\n    &mut self,\n    _namespace: &NamespaceConstraint<&<Self::Impl as SelectorImpl<'i>>::NamespaceUrl>,\n    _local_name: &<Self::Impl as SelectorImpl<'i>>::LocalName,\n    _local_name_lower: &<Self::Impl as SelectorImpl<'i>>::LocalName,\n  ) -> bool {\n    true\n  }\n\n  /// Visit a simple selector.\n  fn visit_simple_selector(&mut self, _: &Component<'i, Self::Impl>) -> bool {\n    true\n  }\n\n  /// Visit a nested selector list. The caller is responsible to call visit\n  /// into the internal selectors if / as needed.\n  ///\n  /// The default implementation does this.\n  fn visit_selector_list(&mut self, list: &[Selector<'i, Self::Impl>]) -> bool {\n    for nested in list {\n      if !nested.visit(self) {\n        return false;\n      }\n    }\n    true\n  }\n\n  /// Visits a complex selector.\n  ///\n  /// Gets the combinator to the right of the selector, or `None` if the\n  /// selector is the rightmost one.\n  fn visit_complex_selector(&mut self, _combinator_to_right: Option<Combinator>) -> bool {\n    true\n  }\n}\n"
  },
  {
    "path": "src/bundler.rs",
    "content": "//! CSS bundling.\n//!\n//! A [Bundler](Bundler) can be used to combine a CSS file and all of its dependencies\n//! into a single merged style sheet. It works together with a [SourceProvider](SourceProvider)\n//! (e.g. [FileProvider](FileProvider)) to read files from the file system or another source,\n//! and returns a [StyleSheet](super::stylesheet::StyleSheet) containing the rules from all\n//! of the dependencies of the entry file, recursively.\n//!\n//! Rules are bundled following `@import` order, and wrapped in the necessary `@media`, `@supports`,\n//! and `@layer` rules as appropriate to preserve the authored behavior.\n//!\n//! # Example\n//!\n//! ```no_run\n//! use std::path::Path;\n//! use lightningcss::{\n//!   bundler::{Bundler, FileProvider},\n//!   stylesheet::ParserOptions\n//! };\n//!\n//! let fs = FileProvider::new();\n//! let mut bundler = Bundler::new(&fs, None, ParserOptions::default());\n//! let stylesheet = bundler.bundle(Path::new(\"style.css\")).unwrap();\n//! ```\n\nuse crate::{\n  error::ErrorLocation,\n  parser::DefaultAtRuleParser,\n  properties::{\n    css_modules::Specifier,\n    custom::{\n      CustomProperty, EnvironmentVariableName, TokenList, TokenOrValue, UnparsedProperty, UnresolvedColor,\n    },\n    Property,\n  },\n  rules::{\n    layer::{LayerBlockRule, LayerName},\n    Location,\n  },\n  traits::{AtRuleParser, ToCss},\n  values::ident::DashedIdentReference,\n};\nuse crate::{\n  error::{Error, ParserError},\n  media_query::MediaList,\n  rules::{\n    import::ImportRule,\n    media::MediaRule,\n    supports::{SupportsCondition, SupportsRule},\n    CssRule, CssRuleList,\n  },\n  stylesheet::{ParserOptions, StyleSheet},\n};\nuse dashmap::DashMap;\nuse parcel_sourcemap::SourceMap;\nuse rayon::prelude::*;\nuse std::{\n  collections::HashSet,\n  fs,\n  path::{Path, PathBuf},\n  sync::Mutex,\n};\n\n/// A Bundler combines a CSS file and all imported dependencies together into\n/// a single merged style sheet.\npub struct Bundler<'a, 'o, 's, P, T: AtRuleParser<'a>> {\n  source_map: Option<Mutex<&'s mut SourceMap>>,\n  fs: &'a P,\n  source_indexes: DashMap<PathBuf, u32>,\n  stylesheets: Mutex<Vec<BundleStyleSheet<'a, 'o, T::AtRule>>>,\n  options: ParserOptions<'o, 'a>,\n  at_rule_parser: Mutex<AtRuleParserValue<'s, T>>,\n}\n\nenum AtRuleParserValue<'a, T> {\n  Owned(T),\n  Borrowed(&'a mut T),\n}\n\nstruct BundleStyleSheet<'i, 'o, T> {\n  stylesheet: Option<StyleSheet<'i, 'o, T>>,\n  dependencies: Vec<Dependency>,\n  css_modules_deps: Vec<u32>,\n  parent_source_index: u32,\n  parent_dep_index: u32,\n  layer: Option<Option<LayerName<'i>>>,\n  supports: Option<SupportsCondition<'i>>,\n  media: MediaList<'i>,\n  loc: Location,\n}\n\n#[derive(Debug, Clone)]\nenum Dependency {\n  File(u32),\n  External(String),\n}\n\n/// The result of [SourceProvider::resolve].\n#[derive(Debug)]\n#[cfg_attr(\n  any(feature = \"serde\", feature = \"nodejs\"),\n  derive(serde::Deserialize),\n  serde(rename_all = \"lowercase\")\n)]\npub enum ResolveResult {\n  /// An external URL.\n  External(String),\n  /// A file path.\n  #[serde(untagged)]\n  File(PathBuf),\n}\n\nimpl From<PathBuf> for ResolveResult {\n  fn from(path: PathBuf) -> Self {\n    ResolveResult::File(path)\n  }\n}\n\n/// A trait to provide the contents of files to a Bundler.\n///\n/// See [FileProvider](FileProvider) for an implementation that uses the\n/// file system.\npub trait SourceProvider: Send + Sync {\n  /// A custom error.\n  type Error: std::error::Error + Send + Sync;\n\n  /// Reads the contents of the given file path to a string.\n  fn read<'a>(&'a self, file: &Path) -> Result<&'a str, Self::Error>;\n\n  /// Resolves the given import specifier to a file path given the file\n  /// which the import originated from.\n  fn resolve(&self, specifier: &str, originating_file: &Path) -> Result<ResolveResult, Self::Error>;\n}\n\n/// Provides an implementation of [SourceProvider](SourceProvider)\n/// that reads files from the file system.\npub struct FileProvider {\n  inputs: Mutex<Vec<*mut String>>,\n}\n\nimpl FileProvider {\n  /// Creates a new FileProvider.\n  pub fn new() -> FileProvider {\n    FileProvider {\n      inputs: Mutex::new(Vec::new()),\n    }\n  }\n}\n\nunsafe impl Sync for FileProvider {}\nunsafe impl Send for FileProvider {}\n\nimpl SourceProvider for FileProvider {\n  type Error = std::io::Error;\n\n  fn read<'a>(&'a self, file: &Path) -> Result<&'a str, Self::Error> {\n    let source = fs::read_to_string(file)?;\n    let ptr = Box::into_raw(Box::new(source));\n    self.inputs.lock().unwrap().push(ptr);\n    // SAFETY: this is safe because the pointer is not dropped\n    // until the FileProvider is, and we never remove from the\n    // list of pointers stored in the vector.\n    Ok(unsafe { &*ptr })\n  }\n\n  fn resolve(&self, specifier: &str, originating_file: &Path) -> Result<ResolveResult, Self::Error> {\n    // Assume the specifier is a relative file path and join it with current path.\n    Ok(originating_file.with_file_name(specifier).into())\n  }\n}\n\nimpl Drop for FileProvider {\n  fn drop(&mut self) {\n    for ptr in self.inputs.lock().unwrap().iter() {\n      std::mem::drop(unsafe { Box::from_raw(*ptr) })\n    }\n  }\n}\n\n/// An error that could occur during bundling.\n#[derive(Debug)]\n#[cfg_attr(any(feature = \"serde\", feature = \"nodejs\"), derive(serde::Serialize))]\npub enum BundleErrorKind<'i, T: std::error::Error> {\n  /// A parser error occurred.\n  ParserError(ParserError<'i>),\n  /// An unsupported `@import` condition was encountered.\n  UnsupportedImportCondition,\n  /// An unsupported cascade layer combination was encountered.\n  UnsupportedLayerCombination,\n  /// Unsupported media query boolean logic was encountered.\n  UnsupportedMediaBooleanLogic,\n  /// An external module was referenced with a CSS module \"from\" clause.\n  ReferencedExternalModuleWithCssModuleFrom,\n  /// An external `@import` was found after a bundled `@import`.\n  /// This may result in unintended selector order.\n  ExternalImportAfterBundledImport,\n  /// A custom resolver error.\n  ResolverError(#[cfg_attr(any(feature = \"serde\", feature = \"nodejs\"), serde(skip))] T),\n}\n\nimpl<'i, T: std::error::Error> From<Error<ParserError<'i>>> for Error<BundleErrorKind<'i, T>> {\n  fn from(err: Error<ParserError<'i>>) -> Self {\n    Error {\n      kind: BundleErrorKind::ParserError(err.kind),\n      loc: err.loc,\n    }\n  }\n}\n\nimpl<'i, T: std::error::Error> std::fmt::Display for BundleErrorKind<'i, T> {\n  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n    use BundleErrorKind::*;\n    match self {\n      ParserError(err) => err.fmt(f),\n      UnsupportedImportCondition => write!(f, \"Unsupported import condition\"),\n      UnsupportedLayerCombination => write!(f, \"Unsupported layer combination in @import\"),\n      UnsupportedMediaBooleanLogic => write!(f, \"Unsupported boolean logic in @import media query\"),\n      ReferencedExternalModuleWithCssModuleFrom => {\n        write!(f, \"Referenced external module with CSS module \\\"from\\\" clause\")\n      }\n      ExternalImportAfterBundledImport => write!(\n        f,\n        \"An external `@import` was found after a bundled `@import`. This may result in unintended selector order.\"\n      ),\n      ResolverError(err) => std::fmt::Display::fmt(&err, f),\n    }\n  }\n}\n\nimpl<'i, T: std::error::Error> BundleErrorKind<'i, T> {\n  #[deprecated(note = \"use `BundleErrorKind::to_string()` or `std::fmt::Display` instead\")]\n  #[allow(missing_docs)]\n  pub fn reason(&self) -> String {\n    self.to_string()\n  }\n}\n\nimpl<'a, 'o, 's, P: SourceProvider> Bundler<'a, 'o, 's, P, DefaultAtRuleParser> {\n  /// Creates a new Bundler using the given source provider.\n  /// If a source map is given, the content of each source file included in the bundle will\n  /// be added accordingly.\n  pub fn new(\n    fs: &'a P,\n    source_map: Option<&'s mut SourceMap>,\n    options: ParserOptions<'o, 'a>,\n  ) -> Bundler<'a, 'o, 's, P, DefaultAtRuleParser> {\n    Bundler {\n      source_map: source_map.map(Mutex::new),\n      fs,\n      source_indexes: DashMap::new(),\n      stylesheets: Mutex::new(Vec::new()),\n      options,\n      at_rule_parser: Mutex::new(AtRuleParserValue::Owned(DefaultAtRuleParser)),\n    }\n  }\n}\n\nimpl<'a, 'o, 's, P: SourceProvider, T: AtRuleParser<'a> + Clone + Sync + Send> Bundler<'a, 'o, 's, P, T>\nwhere\n  T::AtRule: Sync + Send + ToCss + Clone,\n{\n  /// Creates a new Bundler using the given source provider.\n  /// If a source map is given, the content of each source file included in the bundle will\n  /// be added accordingly.\n  pub fn new_with_at_rule_parser(\n    fs: &'a P,\n    source_map: Option<&'s mut SourceMap>,\n    options: ParserOptions<'o, 'a>,\n    at_rule_parser: &'s mut T,\n  ) -> Self {\n    Bundler {\n      source_map: source_map.map(Mutex::new),\n      fs,\n      source_indexes: DashMap::new(),\n      stylesheets: Mutex::new(Vec::new()),\n      options,\n      at_rule_parser: Mutex::new(AtRuleParserValue::Borrowed(at_rule_parser)),\n    }\n  }\n\n  /// Bundles the given entry file and all dependencies into a single style sheet.\n  pub fn bundle<'e>(\n    &mut self,\n    entry: &'e Path,\n  ) -> Result<StyleSheet<'a, 'o, T::AtRule>, Error<BundleErrorKind<'a, P::Error>>> {\n    // Phase 1: load and parse all files. This is done in parallel.\n    self.load_file(\n      &entry,\n      ImportRule {\n        url: \"\".into(),\n        layer: None,\n        supports: None,\n        media: MediaList::new(),\n        loc: Location {\n          source_index: 0,\n          line: 0,\n          column: 0,\n        },\n      },\n    )?;\n\n    // Phase 2: determine the order that the files should be concatenated.\n    self.order();\n\n    // Phase 3: concatenate.\n    let mut rules: Vec<CssRule<'a, T::AtRule>> = Vec::new();\n    self.inline(&mut rules)?;\n\n    let sources = self\n      .stylesheets\n      .get_mut()\n      .unwrap()\n      .iter()\n      .flat_map(|s| s.stylesheet.as_ref().unwrap().sources.iter().cloned())\n      .collect();\n\n    let mut stylesheet = StyleSheet::new(sources, CssRuleList(rules), self.options.clone());\n\n    stylesheet.source_map_urls = self\n      .stylesheets\n      .get_mut()\n      .unwrap()\n      .iter()\n      .flat_map(|s| s.stylesheet.as_ref().unwrap().source_map_urls.iter().cloned())\n      .collect();\n\n    stylesheet.license_comments = self\n      .stylesheets\n      .get_mut()\n      .unwrap()\n      .iter()\n      .flat_map(|s| s.stylesheet.as_ref().unwrap().license_comments.iter().cloned())\n      .collect();\n\n    if let Some(config) = &self.options.css_modules {\n      if config.pattern.has_content_hash() {\n        stylesheet.content_hashes = Some(\n          self\n            .stylesheets\n            .get_mut()\n            .unwrap()\n            .iter()\n            .flat_map(|s| {\n              let s = s.stylesheet.as_ref().unwrap();\n              s.content_hashes.as_ref().unwrap().iter().cloned()\n            })\n            .collect(),\n        );\n      }\n    }\n\n    Ok(stylesheet)\n  }\n\n  fn find_filename(&self, source_index: u32) -> String {\n    // This function is only used for error handling, so it's ok if this is a bit slow.\n    let entry = self.source_indexes.iter().find(|x| *x.value() == source_index).unwrap();\n    entry.key().to_str().unwrap().into()\n  }\n\n  fn load_file(&self, file: &Path, rule: ImportRule<'a>) -> Result<u32, Error<BundleErrorKind<'a, P::Error>>> {\n    // Check if we already loaded this file.\n    let mut stylesheets = self.stylesheets.lock().unwrap();\n    let source_index = match self.source_indexes.get(file) {\n      Some(source_index) => {\n        // If we already loaded this file, combine the media queries and supports conditions\n        // from this import rule with the existing ones using a logical or operator.\n        let entry = &mut stylesheets[*source_index as usize];\n\n        // We cannot combine a media query and a supports query from different @import rules.\n        // e.g. @import \"a.css\" print; @import \"a.css\" supports(color: red);\n        // This would require duplicating the actual rules in the file.\n        if (!rule.media.media_queries.is_empty() && !entry.supports.is_none())\n          || (!entry.media.media_queries.is_empty() && !rule.supports.is_none())\n        {\n          return Err(Error {\n            kind: BundleErrorKind::UnsupportedImportCondition,\n            loc: Some(ErrorLocation::new(rule.loc, self.find_filename(rule.loc.source_index))),\n          });\n        }\n\n        if rule.media.media_queries.is_empty() {\n          entry.media.media_queries.clear();\n        } else if !entry.media.media_queries.is_empty() {\n          entry.media.or(&rule.media);\n        }\n\n        if let Some(supports) = rule.supports {\n          if let Some(existing_supports) = &mut entry.supports {\n            existing_supports.or(&supports)\n          }\n        } else {\n          entry.supports = None;\n        }\n\n        if let Some(layer) = &rule.layer {\n          if let Some(existing_layer) = &entry.layer {\n            // We can't OR layer names without duplicating all of the nested rules, so error for now.\n            if layer != existing_layer || (layer.is_none() && existing_layer.is_none()) {\n              return Err(Error {\n                kind: BundleErrorKind::UnsupportedLayerCombination,\n                loc: Some(ErrorLocation::new(rule.loc, self.find_filename(rule.loc.source_index))),\n              });\n            }\n          } else {\n            entry.layer = rule.layer;\n          }\n        }\n\n        return Ok(*source_index);\n      }\n      None => {\n        let source_index = stylesheets.len() as u32;\n        self.source_indexes.insert(file.to_owned(), source_index);\n\n        stylesheets.push(BundleStyleSheet {\n          stylesheet: None,\n          layer: rule.layer.clone(),\n          media: rule.media.clone(),\n          supports: rule.supports.clone(),\n          loc: rule.loc.clone(),\n          dependencies: Vec::new(),\n          css_modules_deps: Vec::new(),\n          parent_source_index: 0,\n          parent_dep_index: 0,\n        });\n\n        source_index\n      }\n    };\n\n    drop(stylesheets); // ensure we aren't holding the lock anymore\n\n    let code = self.fs.read(file).map_err(|e| Error {\n      kind: BundleErrorKind::ResolverError(e),\n      loc: if rule.loc.column == 0 {\n        None\n      } else {\n        Some(ErrorLocation::new(rule.loc, self.find_filename(rule.loc.source_index)))\n      },\n    })?;\n\n    let mut opts = self.options.clone();\n    let filename = file.to_str().unwrap();\n    opts.filename = filename.to_owned();\n    opts.source_index = source_index;\n\n    let mut stylesheet = {\n      let mut at_rule_parser = self.at_rule_parser.lock().unwrap();\n      let at_rule_parser = match &mut *at_rule_parser {\n        AtRuleParserValue::Owned(owned) => owned,\n        AtRuleParserValue::Borrowed(borrowed) => *borrowed,\n      };\n\n      StyleSheet::<T::AtRule>::parse_with(code, opts, at_rule_parser)?\n    };\n\n    if let Some(source_map) = &self.source_map {\n      // Only add source if we don't have an input source map.\n      // If we do, this will be handled by the printer when remapping locations.\n      let sm = stylesheet.source_map_url(0);\n      if sm.is_none() || !sm.unwrap().starts_with(\"data\") {\n        let mut source_map = source_map.lock().unwrap();\n        let source_index = source_map.add_source(filename);\n        let _ = source_map.set_source_content(source_index as usize, code);\n      }\n    }\n\n    // Collect and load dependencies for this stylesheet in parallel.\n    let dependencies: Result<Vec<Dependency>, _> = stylesheet\n      .rules\n      .0\n      .par_iter_mut()\n      .filter_map(|r| {\n        // Prepend parent layer name to @layer statements.\n        if let CssRule::LayerStatement(layer) = r {\n          if let Some(Some(parent_layer)) = &rule.layer {\n            for name in &mut layer.names {\n              name.0.insert_many(0, parent_layer.0.iter().cloned())\n            }\n          }\n        }\n\n        if let CssRule::Import(import) = r {\n          let specifier = &import.url;\n\n          // Combine media queries and supports conditions from parent\n          // stylesheet with @import rule using a logical and operator.\n          let mut media = rule.media.clone();\n          let result = media.and(&import.media).map_err(|_| Error {\n            kind: BundleErrorKind::UnsupportedMediaBooleanLogic,\n            loc: Some(ErrorLocation::new(\n              import.loc,\n              self.find_filename(import.loc.source_index),\n            )),\n          });\n\n          if let Err(e) = result {\n            return Some(Err(e));\n          }\n\n          let layer = if (rule.layer == Some(None) && import.layer.is_some())\n            || (import.layer == Some(None) && rule.layer.is_some())\n          {\n            // Cannot combine anonymous layers\n            return Some(Err(Error {\n              kind: BundleErrorKind::UnsupportedLayerCombination,\n              loc: Some(ErrorLocation::new(\n                import.loc,\n                self.find_filename(import.loc.source_index),\n              )),\n            }));\n          } else if let Some(Some(a)) = &rule.layer {\n            if let Some(Some(b)) = &import.layer {\n              let mut name = a.clone();\n              name.0.extend(b.0.iter().cloned());\n              Some(Some(name))\n            } else {\n              Some(Some(a.clone()))\n            }\n          } else {\n            import.layer.clone()\n          };\n\n          let result = match self.fs.resolve(&specifier, file) {\n            Ok(ResolveResult::File(path)) => self\n              .load_file(\n                &path,\n                ImportRule {\n                  layer,\n                  media,\n                  supports: combine_supports(rule.supports.clone(), &import.supports),\n                  url: \"\".into(),\n                  loc: import.loc,\n                },\n              )\n              .map(Dependency::File),\n            Ok(ResolveResult::External(url)) => Ok(Dependency::External(url)),\n            Err(err) => Err(Error {\n              kind: BundleErrorKind::ResolverError(err),\n              loc: Some(ErrorLocation::new(\n                import.loc,\n                self.find_filename(import.loc.source_index),\n              )),\n            }),\n          };\n\n          Some(result)\n        } else {\n          None\n        }\n      })\n      .collect();\n\n    // Collect CSS modules dependencies from the `composes` property.\n    let css_modules_deps: Result<Vec<u32>, _> = if self.options.css_modules.is_some() {\n      stylesheet\n        .rules\n        .0\n        .par_iter_mut()\n        .filter_map(|r| {\n          if let CssRule::Style(style) = r {\n            Some(\n              style\n                .declarations\n                .declarations\n                .par_iter_mut()\n                .chain(style.declarations.important_declarations.par_iter_mut())\n                .filter_map(|d| match d {\n                  Property::Composes(composes) => self\n                    .add_css_module_dep(file, &rule, style.loc, composes.loc, &mut composes.from)\n                    .map(|result| rayon::iter::Either::Left(rayon::iter::once(result))),\n\n                  // Handle variable references if the dashed_idents option is present.\n                  Property::Custom(CustomProperty { value, .. })\n                  | Property::Unparsed(UnparsedProperty { value, .. })\n                    if matches!(&self.options.css_modules, Some(css_modules) if css_modules.dashed_idents) =>\n                  {\n                    Some(rayon::iter::Either::Right(visit_vars(value).filter_map(|name| {\n                      self.add_css_module_dep(\n                        file,\n                        &rule,\n                        style.loc,\n                        // TODO: store loc in variable reference?\n                        crate::dependencies::Location {\n                          line: style.loc.line,\n                          column: style.loc.column,\n                        },\n                        &mut name.from,\n                      )\n                    })))\n                  }\n                  _ => None,\n                })\n                .flatten(),\n            )\n          } else {\n            None\n          }\n        })\n        .flatten()\n        .collect()\n    } else {\n      Ok(vec![])\n    };\n\n    let entry = &mut self.stylesheets.lock().unwrap()[source_index as usize];\n    entry.stylesheet = Some(stylesheet);\n    entry.dependencies = dependencies?;\n    entry.css_modules_deps = css_modules_deps?;\n\n    Ok(source_index)\n  }\n\n  fn add_css_module_dep(\n    &self,\n    file: &Path,\n    rule: &ImportRule<'a>,\n    style_loc: Location,\n    loc: crate::dependencies::Location,\n    specifier: &mut Option<Specifier>,\n  ) -> Option<Result<u32, Error<BundleErrorKind<'a, P::Error>>>> {\n    if let Some(Specifier::File(f)) = specifier {\n      let result = match self.fs.resolve(&f, file) {\n        Ok(ResolveResult::File(path)) => {\n          let res = self.load_file(\n            &path,\n            ImportRule {\n              layer: rule.layer.clone(),\n              media: rule.media.clone(),\n              supports: rule.supports.clone(),\n              url: \"\".into(),\n              loc: Location {\n                source_index: style_loc.source_index,\n                line: loc.line,\n                column: loc.column,\n              },\n            },\n          );\n\n          if let Ok(source_index) = res {\n            *specifier = Some(Specifier::SourceIndex(source_index));\n          }\n\n          res\n        }\n        Ok(ResolveResult::External(_)) => Err(Error {\n          kind: BundleErrorKind::ReferencedExternalModuleWithCssModuleFrom,\n          loc: Some(ErrorLocation::new(\n            style_loc,\n            self.find_filename(style_loc.source_index),\n          )),\n        }),\n        Err(err) => Err(Error {\n          kind: BundleErrorKind::ResolverError(err),\n          loc: Some(ErrorLocation::new(\n            style_loc,\n            self.find_filename(style_loc.source_index),\n          )),\n        }),\n      };\n      Some(result)\n    } else {\n      None\n    }\n  }\n\n  fn order(&mut self) {\n    process(self.stylesheets.get_mut().unwrap(), 0, &mut HashSet::new());\n\n    fn process<'i, T>(\n      stylesheets: &mut Vec<BundleStyleSheet<'i, '_, T>>,\n      source_index: u32,\n      visited: &mut HashSet<u32>,\n    ) {\n      if visited.contains(&source_index) {\n        return;\n      }\n\n      visited.insert(source_index);\n\n      let mut dep_index = 0;\n      for i in 0..stylesheets[source_index as usize].css_modules_deps.len() {\n        let dep_source_index = stylesheets[source_index as usize].css_modules_deps[i];\n        let resolved = &mut stylesheets[dep_source_index as usize];\n\n        // CSS modules preserve the first instance of composed stylesheets.\n        if !visited.contains(&dep_source_index) {\n          resolved.parent_dep_index = dep_index;\n          resolved.parent_source_index = source_index;\n          process(stylesheets, dep_source_index, visited);\n        }\n\n        dep_index += 1;\n      }\n\n      for i in 0..stylesheets[source_index as usize].dependencies.len() {\n        let Dependency::File(dep_source_index) = stylesheets[source_index as usize].dependencies[i] else {\n          continue;\n        };\n        let resolved = &mut stylesheets[dep_source_index as usize];\n\n        // In browsers, every instance of an @import is evaluated, so we preserve the last.\n        resolved.parent_dep_index = dep_index;\n        resolved.parent_source_index = source_index;\n\n        process(stylesheets, dep_source_index, visited);\n        dep_index += 1;\n      }\n    }\n  }\n\n  fn inline(\n    &mut self,\n    dest: &mut Vec<CssRule<'a, T::AtRule>>,\n  ) -> Result<(), Error<BundleErrorKind<'a, P::Error>>> {\n    fn process<'a, T, E: std::error::Error>(\n      stylesheets: &mut Vec<BundleStyleSheet<'a, '_, T>>,\n      source_index: u32,\n      dest: &mut Vec<CssRule<'a, T>>,\n      filename: &String,\n    ) -> Result<(), Error<BundleErrorKind<'a, E>>> {\n      let stylesheet = &mut stylesheets[source_index as usize];\n      let mut rules = std::mem::take(&mut stylesheet.stylesheet.as_mut().unwrap().rules.0);\n\n      // Hoist css modules deps\n      let mut dep_index = 0;\n      for i in 0..stylesheet.css_modules_deps.len() {\n        let dep_source_index = stylesheets[source_index as usize].css_modules_deps[i];\n        let resolved = &stylesheets[dep_source_index as usize];\n\n        // Include the dependency if this is the first instance as computed earlier.\n        if resolved.parent_source_index == source_index && resolved.parent_dep_index == dep_index as u32 {\n          process(stylesheets, dep_source_index, dest, filename)?;\n        }\n\n        dep_index += 1;\n      }\n\n      let mut import_index = 0;\n      let mut has_bundled_import = false;\n      for rule in &mut rules {\n        match rule {\n          CssRule::Import(import_rule) => {\n            let dep_source = &stylesheets[source_index as usize].dependencies[import_index];\n            match dep_source {\n              Dependency::File(dep_source_index) => {\n                let resolved = &stylesheets[*dep_source_index as usize];\n\n                // Include the dependency if this is the last instance as computed earlier.\n                if resolved.parent_source_index == source_index && resolved.parent_dep_index == dep_index {\n                  has_bundled_import = true;\n                  process(stylesheets, *dep_source_index, dest, filename)?;\n                }\n\n                *rule = CssRule::Ignored;\n                dep_index += 1;\n              }\n              Dependency::External(url) => {\n                if has_bundled_import {\n                  return Err(Error {\n                    kind: BundleErrorKind::ExternalImportAfterBundledImport,\n                    loc: Some(ErrorLocation {\n                      filename: filename.clone(),\n                      line: import_rule.loc.line,\n                      column: import_rule.loc.column,\n                    }),\n                  });\n                }\n                import_rule.url = url.to_owned().into();\n                let imp = std::mem::replace(rule, CssRule::Ignored);\n                dest.push(imp);\n              }\n            }\n            import_index += 1;\n          }\n          CssRule::LayerStatement(_) => {\n            // @layer rules are the only rules that may appear before an @import.\n            // We must preserve this order to ensure correctness.\n            let layer = std::mem::replace(rule, CssRule::Ignored);\n            dest.push(layer);\n          }\n          CssRule::Ignored => {}\n          _ => break,\n        }\n      }\n\n      // Wrap rules in the appropriate @layer, @media, and @supports rules.\n      let stylesheet = &mut stylesheets[source_index as usize];\n\n      if stylesheet.layer.is_some() {\n        rules = vec![CssRule::LayerBlock(LayerBlockRule {\n          name: stylesheet.layer.take().unwrap(),\n          rules: CssRuleList(rules),\n          loc: stylesheet.loc,\n        })]\n      }\n\n      if !stylesheet.media.media_queries.is_empty() {\n        rules = vec![CssRule::Media(MediaRule {\n          query: std::mem::replace(&mut stylesheet.media, MediaList::new()),\n          rules: CssRuleList(rules),\n          loc: stylesheet.loc,\n        })]\n      }\n\n      if stylesheet.supports.is_some() {\n        rules = vec![CssRule::Supports(SupportsRule {\n          condition: stylesheet.supports.take().unwrap(),\n          rules: CssRuleList(rules),\n          loc: stylesheet.loc,\n        })]\n      }\n\n      dest.extend(rules);\n      Ok(())\n    }\n\n    process(self.stylesheets.get_mut().unwrap(), 0, dest, &self.options.filename)\n  }\n}\n\nfn combine_supports<'a>(\n  a: Option<SupportsCondition<'a>>,\n  b: &Option<SupportsCondition<'a>>,\n) -> Option<SupportsCondition<'a>> {\n  if let Some(mut a) = a {\n    if let Some(b) = b {\n      a.and(b)\n    }\n    Some(a)\n  } else {\n    b.clone()\n  }\n}\n\nfn visit_vars<'a, 'b>(\n  token_list: &'b mut TokenList<'a>,\n) -> impl ParallelIterator<Item = &'b mut DashedIdentReference<'a>> {\n  let mut stack = vec![token_list.0.iter_mut()];\n  std::iter::from_fn(move || {\n    while !stack.is_empty() {\n      let iter = stack.last_mut().unwrap();\n      match iter.next() {\n        Some(TokenOrValue::Var(var)) => {\n          if let Some(fallback) = &mut var.fallback {\n            stack.push(fallback.0.iter_mut());\n          }\n          return Some(&mut var.name);\n        }\n        Some(TokenOrValue::Env(env)) => {\n          if let Some(fallback) = &mut env.fallback {\n            stack.push(fallback.0.iter_mut());\n          }\n          if let EnvironmentVariableName::Custom(name) = &mut env.name {\n            return Some(name);\n          }\n        }\n        Some(TokenOrValue::UnresolvedColor(color)) => match color {\n          UnresolvedColor::RGB { alpha, .. } | UnresolvedColor::HSL { alpha, .. } => {\n            stack.push(alpha.0.iter_mut());\n          }\n          UnresolvedColor::LightDark { light, dark } => {\n            stack.push(light.0.iter_mut());\n            stack.push(dark.0.iter_mut());\n          }\n        },\n        None => {\n          stack.pop();\n        }\n        _ => {}\n      }\n    }\n    None\n  })\n  .par_bridge()\n}\n\n#[cfg(test)]\nmod tests {\n  use super::*;\n  use crate::{\n    css_modules::{self, CssModuleExports, CssModuleReference},\n    parser::ParserFlags,\n    stylesheet::{MinifyOptions, PrinterOptions},\n    targets::{Browsers, Targets},\n  };\n  use indoc::indoc;\n  use std::collections::HashMap;\n\n  #[derive(Clone)]\n  struct TestProvider {\n    map: HashMap<PathBuf, String>,\n  }\n\n  impl SourceProvider for TestProvider {\n    type Error = std::io::Error;\n\n    fn read<'a>(&'a self, file: &Path) -> Result<&'a str, Self::Error> {\n      Ok(self.map.get(file).unwrap())\n    }\n\n    fn resolve(&self, specifier: &str, originating_file: &Path) -> Result<ResolveResult, Self::Error> {\n      if specifier.starts_with(\"https:\") {\n        Ok(ResolveResult::External(specifier.to_owned()))\n      } else {\n        Ok(originating_file.with_file_name(specifier).into())\n      }\n    }\n  }\n\n  /// Stand-in for a user-authored `SourceProvider` with application-specific logic.\n  struct CustomProvider {\n    map: HashMap<PathBuf, String>,\n  }\n\n  impl SourceProvider for CustomProvider {\n    type Error = std::io::Error;\n\n    /// Read files from in-memory map.\n    fn read<'a>(&'a self, file: &Path) -> Result<&'a str, Self::Error> {\n      Ok(self.map.get(file).unwrap())\n    }\n\n    /// Resolve by stripping a `foo:` prefix off any import. Specifiers without\n    /// this prefix fail with an error.\n    fn resolve(&self, specifier: &str, _originating_file: &Path) -> Result<ResolveResult, Self::Error> {\n      if specifier.starts_with(\"foo:\") {\n        Ok(Path::new(&specifier[\"foo:\".len()..]).to_path_buf().into())\n      } else {\n        let err = std::io::Error::new(\n          std::io::ErrorKind::NotFound,\n          format!(\n            \"Failed to resolve `{}`, specifier does not start with `foo:`.\",\n            &specifier\n          ),\n        );\n\n        Err(err)\n      }\n    }\n  }\n\n  macro_rules! fs(\n    { $($key:literal: $value:expr),* } => {\n      {\n        #[allow(unused_mut)]\n        let mut m = HashMap::new();\n        $(\n          m.insert(PathBuf::from($key), $value.to_owned());\n        )*\n        m\n      }\n    };\n  );\n\n  fn bundle<P: SourceProvider>(fs: P, entry: &str) -> String {\n    let mut bundler = Bundler::new(&fs, None, ParserOptions::default());\n    let stylesheet = bundler.bundle(Path::new(entry)).unwrap();\n    stylesheet.to_css(PrinterOptions::default()).unwrap().code\n  }\n\n  fn bundle_css_module<P: SourceProvider>(\n    fs: P,\n    entry: &str,\n    project_root: Option<&str>,\n  ) -> (String, CssModuleExports) {\n    bundle_css_module_with_pattern(fs, entry, project_root, \"[hash]_[local]\")\n  }\n\n  fn bundle_css_module_with_pattern<P: SourceProvider>(\n    fs: P,\n    entry: &str,\n    project_root: Option<&str>,\n    pattern: &'static str,\n  ) -> (String, CssModuleExports) {\n    let mut bundler = Bundler::new(\n      &fs,\n      None,\n      ParserOptions {\n        css_modules: Some(css_modules::Config {\n          dashed_idents: true,\n          pattern: css_modules::Pattern::parse(pattern).unwrap(),\n          ..Default::default()\n        }),\n        ..ParserOptions::default()\n      },\n    );\n    let mut stylesheet = bundler.bundle(Path::new(entry)).unwrap();\n    stylesheet.minify(MinifyOptions::default()).unwrap();\n    let res = stylesheet\n      .to_css(PrinterOptions {\n        project_root,\n        ..PrinterOptions::default()\n      })\n      .unwrap();\n    (res.code, res.exports.unwrap())\n  }\n\n  fn bundle_custom_media<P: SourceProvider>(fs: P, entry: &str) -> String {\n    let mut bundler = Bundler::new(\n      &fs,\n      None,\n      ParserOptions {\n        flags: ParserFlags::CUSTOM_MEDIA,\n        ..ParserOptions::default()\n      },\n    );\n    let mut stylesheet = bundler.bundle(Path::new(entry)).unwrap();\n    let targets = Targets {\n      browsers: Some(Browsers {\n        safari: Some(13 << 16),\n        ..Browsers::default()\n      }),\n      ..Default::default()\n    };\n    stylesheet\n      .minify(MinifyOptions {\n        targets,\n        ..MinifyOptions::default()\n      })\n      .unwrap();\n    stylesheet\n      .to_css(PrinterOptions {\n        targets,\n        ..PrinterOptions::default()\n      })\n      .unwrap()\n      .code\n  }\n\n  fn error_test<P: SourceProvider>(\n    fs: P,\n    entry: &str,\n    maybe_cb: Option<Box<dyn FnOnce(BundleErrorKind<P::Error>) -> ()>>,\n  ) {\n    let mut bundler = Bundler::new(&fs, None, ParserOptions::default());\n    let res = bundler.bundle(Path::new(entry));\n    match res {\n      Ok(_) => unreachable!(),\n      Err(e) => {\n        if let Some(cb) = maybe_cb {\n          cb(e.kind);\n        }\n      }\n    }\n  }\n\n  fn flatten_exports(exports: CssModuleExports) -> HashMap<String, String> {\n    let mut res = HashMap::new();\n    for (name, export) in &exports {\n      let mut classes = export.name.clone();\n      for composes in &export.composes {\n        classes.push(' ');\n        classes.push_str(match composes {\n          CssModuleReference::Local { name } => name,\n          CssModuleReference::Global { name } => name,\n          _ => unreachable!(),\n        })\n      }\n      res.insert(name.clone(), classes);\n    }\n    res\n  }\n\n  #[test]\n  fn test_bundle() {\n    let res = bundle(\n      TestProvider {\n        map: fs! {\n          \"/a.css\": r#\"\n          @import \"b.css\";\n          .a { color: red }\n        \"#,\n          \"/b.css\": r#\"\n          .b { color: green }\n        \"#\n        },\n      },\n      \"/a.css\",\n    );\n    assert_eq!(\n      res,\n      indoc! { r#\"\n      .b {\n        color: green;\n      }\n\n      .a {\n        color: red;\n      }\n    \"#}\n    );\n\n    let res = bundle(\n      TestProvider {\n        map: fs! {\n          \"/a.css\": r#\"\n          @import \"b.css\" print;\n          .a { color: red }\n        \"#,\n          \"/b.css\": r#\"\n          .b { color: green }\n        \"#\n        },\n      },\n      \"/a.css\",\n    );\n    assert_eq!(\n      res,\n      indoc! { r#\"\n      @media print {\n        .b {\n          color: green;\n        }\n      }\n\n      .a {\n        color: red;\n      }\n    \"#}\n    );\n\n    let res = bundle(\n      TestProvider {\n        map: fs! {\n          \"/a.css\": r#\"\n          @import \"b.css\" supports(color: green);\n          .a { color: red }\n        \"#,\n          \"/b.css\": r#\"\n          .b { color: green }\n        \"#\n        },\n      },\n      \"/a.css\",\n    );\n    assert_eq!(\n      res,\n      indoc! { r#\"\n      @supports (color: green) {\n        .b {\n          color: green;\n        }\n      }\n\n      .a {\n        color: red;\n      }\n    \"#}\n    );\n\n    let res = bundle(\n      TestProvider {\n        map: fs! {\n          \"/a.css\": r#\"\n          @import \"b.css\" supports(color: green) print;\n          .a { color: red }\n        \"#,\n          \"/b.css\": r#\"\n          .b { color: green }\n        \"#\n        },\n      },\n      \"/a.css\",\n    );\n    assert_eq!(\n      res,\n      indoc! { r#\"\n      @supports (color: green) {\n        @media print {\n          .b {\n            color: green;\n          }\n        }\n      }\n\n      .a {\n        color: red;\n      }\n    \"#}\n    );\n\n    let res = bundle(\n      TestProvider {\n        map: fs! {\n          \"/a.css\": r#\"\n          @import \"b.css\" print;\n          @import \"b.css\" screen;\n          .a { color: red }\n        \"#,\n          \"/b.css\": r#\"\n          .b { color: green }\n        \"#\n        },\n      },\n      \"/a.css\",\n    );\n    assert_eq!(\n      res,\n      indoc! { r#\"\n      @media print, screen {\n        .b {\n          color: green;\n        }\n      }\n\n      .a {\n        color: red;\n      }\n    \"#}\n    );\n\n    let res = bundle(\n      TestProvider {\n        map: fs! {\n          \"/a.css\": r#\"\n          @import \"b.css\" supports(color: red);\n          @import \"b.css\" supports(foo: bar);\n          .a { color: red }\n        \"#,\n          \"/b.css\": r#\"\n          .b { color: green }\n        \"#\n        },\n      },\n      \"/a.css\",\n    );\n    assert_eq!(\n      res,\n      indoc! { r#\"\n      @supports (color: red) or (foo: bar) {\n        .b {\n          color: green;\n        }\n      }\n\n      .a {\n        color: red;\n      }\n    \"#}\n    );\n\n    let res = bundle(\n      TestProvider {\n        map: fs! {\n          \"/a.css\": r#\"\n          @import \"b.css\" print;\n          .a { color: red }\n        \"#,\n          \"/b.css\": r#\"\n          @import \"c.css\" (color);\n          .b { color: yellow }\n        \"#,\n          \"/c.css\": r#\"\n          .c { color: green }\n        \"#\n        },\n      },\n      \"/a.css\",\n    );\n    assert_eq!(\n      res,\n      indoc! { r#\"\n      @media print and (color) {\n        .c {\n          color: green;\n        }\n      }\n\n      @media print {\n        .b {\n          color: #ff0;\n        }\n      }\n\n      .a {\n        color: red;\n      }\n    \"#}\n    );\n\n    let res = bundle(\n      TestProvider {\n        map: fs! {\n          \"/a.css\": r#\"\n          @import \"b.css\";\n          .a { color: red }\n        \"#,\n          \"/b.css\": r#\"\n          @import \"c.css\";\n        \"#,\n          \"/c.css\": r#\"\n          @import \"a.css\";\n          .c { color: green }\n        \"#\n        },\n      },\n      \"/a.css\",\n    );\n    assert_eq!(\n      res,\n      indoc! { r#\"\n      .c {\n        color: green;\n      }\n\n      .a {\n        color: red;\n      }\n    \"#}\n    );\n\n    let res = bundle(\n      TestProvider {\n        map: fs! {\n          \"/a.css\": r#\"\n          @import \"b/c.css\";\n          .a { color: red }\n        \"#,\n          \"/b/c.css\": r#\"\n          .b { color: green }\n        \"#\n        },\n      },\n      \"/a.css\",\n    );\n    assert_eq!(\n      res,\n      indoc! { r#\"\n      .b {\n        color: green;\n      }\n\n      .a {\n        color: red;\n      }\n    \"#}\n    );\n\n    let res = bundle(\n      TestProvider {\n        map: fs! {\n          \"/a.css\": r#\"\n          @import \"./b/c.css\";\n          .a { color: red }\n        \"#,\n          \"/b/c.css\": r#\"\n          .b { color: green }\n        \"#\n        },\n      },\n      \"/a.css\",\n    );\n    assert_eq!(\n      res,\n      indoc! { r#\"\n      .b {\n        color: green;\n      }\n\n      .a {\n        color: red;\n      }\n    \"#}\n    );\n\n    let res = bundle_custom_media(\n      TestProvider {\n        map: fs! {\n          \"/a.css\": r#\"\n          @import \"media.css\";\n          @import \"b.css\";\n          .a { color: red }\n        \"#,\n          \"/media.css\": r#\"\n          @custom-media --foo print;\n        \"#,\n          \"/b.css\": r#\"\n          @media (--foo) {\n            .a { color: green }\n          }\n        \"#\n        },\n      },\n      \"/a.css\",\n    );\n    assert_eq!(\n      res,\n      indoc! { r#\"\n      @media print {\n        .a {\n          color: green;\n        }\n      }\n\n      .a {\n        color: red;\n      }\n    \"#}\n    );\n\n    let res = bundle(\n      TestProvider {\n        map: fs! {\n          \"/a.css\": r#\"\n          @import \"b.css\" layer(foo);\n          .a { color: red }\n        \"#,\n          \"/b.css\": r#\"\n          .b { color: green }\n        \"#\n        },\n      },\n      \"/a.css\",\n    );\n    assert_eq!(\n      res,\n      indoc! { r#\"\n      @layer foo {\n        .b {\n          color: green;\n        }\n      }\n\n      .a {\n        color: red;\n      }\n    \"#}\n    );\n\n    let res = bundle(\n      TestProvider {\n        map: fs! {\n          \"/a.css\": r#\"\n          @import \"b.css\" layer;\n          .a { color: red }\n        \"#,\n          \"/b.css\": r#\"\n          .b { color: green }\n        \"#\n        },\n      },\n      \"/a.css\",\n    );\n    assert_eq!(\n      res,\n      indoc! { r#\"\n      @layer {\n        .b {\n          color: green;\n        }\n      }\n\n      .a {\n        color: red;\n      }\n    \"#}\n    );\n\n    let res = bundle(\n      TestProvider {\n        map: fs! {\n          \"/a.css\": r#\"\n          @import \"b.css\" layer(foo);\n          .a { color: red }\n        \"#,\n          \"/b.css\": r#\"\n          @import \"c.css\" layer(bar);\n          .b { color: green }\n        \"#,\n          \"/c.css\": r#\"\n          .c { color: green }\n        \"#\n        },\n      },\n      \"/a.css\",\n    );\n    assert_eq!(\n      res,\n      indoc! { r#\"\n      @layer foo.bar {\n        .c {\n          color: green;\n        }\n      }\n\n      @layer foo {\n        .b {\n          color: green;\n        }\n      }\n\n      .a {\n        color: red;\n      }\n    \"#}\n    );\n\n    let res = bundle(\n      TestProvider {\n        map: fs! {\n          \"/a.css\": r#\"\n          @import \"b.css\" layer(foo);\n          @import \"b.css\" layer(foo);\n        \"#,\n          \"/b.css\": r#\"\n          .b { color: green }\n        \"#\n        },\n      },\n      \"/a.css\",\n    );\n    assert_eq!(\n      res,\n      indoc! { r#\"\n      @layer foo {\n        .b {\n          color: green;\n        }\n      }\n    \"#}\n    );\n\n    let res = bundle(\n      TestProvider {\n        map: fs! {\n          \"/a.css\": r#\"\n          @layer bar, foo;\n          @import \"b.css\" layer(foo);\n\n          @layer bar {\n            div {\n              background: red;\n            }\n          }\n        \"#,\n          \"/b.css\": r#\"\n          @layer qux, baz;\n          @import \"c.css\" layer(baz);\n\n          @layer qux {\n            div {\n              background: green;\n            }\n          }\n        \"#,\n          \"/c.css\": r#\"\n          div {\n            background: yellow;\n          }\n        \"#\n        },\n      },\n      \"/a.css\",\n    );\n    assert_eq!(\n      res,\n      indoc! { r#\"\n      @layer bar, foo;\n      @layer foo.qux, foo.baz;\n\n      @layer foo.baz {\n        div {\n          background: #ff0;\n        }\n      }\n\n      @layer foo {\n        @layer qux {\n          div {\n            background: green;\n          }\n        }\n      }\n\n      @layer bar {\n        div {\n          background: red;\n        }\n      }\n    \"#}\n    );\n\n    // Layer order depends on @import conditions.\n    let res = bundle(\n      TestProvider {\n        map: fs! {\n          \"/a.css\": r#\"\n          @import \"b.css\" layer(bar) (min-width: 1000px);\n\n          @layer baz {\n            #box { background: purple }\n          }\n\n          @layer bar {\n            #box { background: yellow }\n          }\n        \"#,\n          \"/b.css\": r#\"\n          #box { background: green }\n        \"#\n        },\n      },\n      \"/a.css\",\n    );\n    assert_eq!(\n      res,\n      indoc! { r#\"\n      @media (width >= 1000px) {\n        @layer bar {\n          #box {\n            background: green;\n          }\n        }\n      }\n\n      @layer baz {\n        #box {\n          background: purple;\n        }\n      }\n\n      @layer bar {\n        #box {\n          background: #ff0;\n        }\n      }\n    \"#}\n    );\n\n    let res = bundle(\n      TestProvider {\n        map: fs! {\n          \"/a.css\": r#\"\n          @import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap');\n          @import './b.css';\n        \"#,\n          \"/b.css\": r#\"\n          .b { color: green }\n        \"#\n        },\n      },\n      \"/a.css\",\n    );\n    assert_eq!(\n      res,\n      indoc! { r#\"\n        @import \"https://fonts.googleapis.com/css2?family=Roboto&display=swap\";\n\n        .b {\n          color: green;\n        }\n    \"#}\n    );\n\n    error_test(\n      TestProvider {\n        map: fs! {\n          \"/a.css\": r#\"\n          @import './b.css';\n          @import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap');\n        \"#,\n          \"/b.css\": r#\"\n          .b { color: green }\n        \"#\n        },\n      },\n      \"/a.css\",\n      Some(Box::new(|err| {\n        assert!(matches!(err, BundleErrorKind::ExternalImportAfterBundledImport));\n      })),\n    );\n\n    error_test(\n      TestProvider {\n        map: fs! {\n          \"/a.css\": r#\"\n          @import \"b.css\" layer(foo);\n          @import \"b.css\" layer(bar);\n        \"#,\n          \"/b.css\": r#\"\n          .b { color: red }\n        \"#\n        },\n      },\n      \"/a.css\",\n      Some(Box::new(|err| {\n        assert!(matches!(err, BundleErrorKind::UnsupportedLayerCombination));\n      })),\n    );\n\n    error_test(\n      TestProvider {\n        map: fs! {\n          \"/a.css\": r#\"\n          @import \"b.css\" layer;\n          @import \"b.css\" layer;\n        \"#,\n          \"/b.css\": r#\"\n          .b { color: red }\n        \"#\n        },\n      },\n      \"/a.css\",\n      Some(Box::new(|err| {\n        assert!(matches!(err, BundleErrorKind::UnsupportedLayerCombination));\n      })),\n    );\n\n    error_test(\n      TestProvider {\n        map: fs! {\n          \"/a.css\": r#\"\n          @import \"b.css\" layer;\n          .a { color: red }\n        \"#,\n          \"/b.css\": r#\"\n          @import \"c.css\" layer;\n          .b { color: green }\n        \"#,\n          \"/c.css\": r#\"\n          .c { color: green }\n        \"#\n        },\n      },\n      \"/a.css\",\n      Some(Box::new(|err| {\n        assert!(matches!(err, BundleErrorKind::UnsupportedLayerCombination));\n      })),\n    );\n\n    error_test(\n      TestProvider {\n        map: fs! {\n          \"/a.css\": r#\"\n          @import \"b.css\" layer;\n          .a { color: red }\n        \"#,\n          \"/b.css\": r#\"\n          @import \"c.css\" layer(foo);\n          .b { color: green }\n        \"#,\n          \"/c.css\": r#\"\n          .c { color: green }\n        \"#\n        },\n      },\n      \"/a.css\",\n      Some(Box::new(|err| {\n        assert!(matches!(err, BundleErrorKind::UnsupportedLayerCombination));\n      })),\n    );\n\n    let res = bundle(\n      TestProvider {\n        map: fs! {\n          \"/index.css\": r#\"\n          @import \"a.css\";\n          @import \"b.css\";\n        \"#,\n          \"/a.css\": r#\"\n          @import \"./c.css\";\n          body { background: red; }\n        \"#,\n          \"/b.css\": r#\"\n          @import \"./c.css\";\n          body { color: red; }\n        \"#,\n          \"/c.css\": r#\"\n          body {\n            background: white;\n            color: black;\n          }\n        \"#\n        },\n      },\n      \"/index.css\",\n    );\n    assert_eq!(\n      res,\n      indoc! { r#\"\n      body {\n        background: red;\n      }\n\n      body {\n        background: #fff;\n        color: #000;\n      }\n\n      body {\n        color: red;\n      }\n    \"#}\n    );\n\n    let res = bundle(\n      TestProvider {\n        map: fs! {\n          \"/index.css\": r#\"\n          @import \"a.css\";\n          @import \"b.css\";\n          @import \"a.css\";\n        \"#,\n          \"/a.css\": r#\"\n          body { background: green; }\n        \"#,\n          \"/b.css\": r#\"\n          body { background: red; }\n        \"#\n        },\n      },\n      \"/index.css\",\n    );\n    assert_eq!(\n      res,\n      indoc! { r#\"\n      body {\n        background: red;\n      }\n\n      body {\n        background: green;\n      }\n    \"#}\n    );\n\n    let res = bundle(\n      CustomProvider {\n        map: fs! {\n          \"/a.css\": r#\"\n            @import \"foo:/b.css\";\n            .a { color: red; }\n          \"#,\n          \"/b.css\": \".b { color: green; }\"\n        },\n      },\n      \"/a.css\",\n    );\n    assert_eq!(\n      res,\n      indoc! { r#\"\n        .b {\n          color: green;\n        }\n\n        .a {\n          color: red;\n        }\n      \"# }\n    );\n\n    error_test(\n      CustomProvider {\n        map: fs! {\n          \"/a.css\": r#\"\n            /* Forgot to prefix with `foo:`. */\n            @import \"/b.css\";\n            .a { color: red; }\n          \"#,\n          \"/b.css\": \".b { color: green; }\"\n        },\n      },\n      \"/a.css\",\n      Some(Box::new(|err| {\n        let kind = match err {\n          BundleErrorKind::ResolverError(ref error) => error.kind(),\n          _ => unreachable!(),\n        };\n        assert!(matches!(kind, std::io::ErrorKind::NotFound));\n        assert!(err\n          .to_string()\n          .contains(\"Failed to resolve `/b.css`, specifier does not start with `foo:`.\"));\n      })),\n    );\n\n    // let res = bundle(fs! {\n    //   \"/a.css\": r#\"\n    //     @import \"b.css\" supports(color: red) (color);\n    //     @import \"b.css\" supports(foo: bar) (orientation: horizontal);\n    //     .a { color: red }\n    //   \"#,\n    //   \"/b.css\": r#\"\n    //     .b { color: green }\n    //   \"#\n    // }, \"/a.css\");\n\n    // let res = bundle(fs! {\n    //   \"/a.css\": r#\"\n    //     @import \"b.css\" not print;\n    //     .a { color: red }\n    //   \"#,\n    //   \"/b.css\": r#\"\n    //     @import \"c.css\" not screen;\n    //     .b { color: green }\n    //   \"#,\n    //   \"/c.css\": r#\"\n    //     .c { color: yellow }\n    //   \"#\n    // }, \"/a.css\");\n  }\n\n  #[test]\n  fn test_css_module() {\n    macro_rules! map {\n      { $($key:expr => $val:expr),* } => {\n        HashMap::from([\n          $(($key.to_owned(), $val.to_owned()),)*\n        ])\n      };\n    }\n\n    let (code, exports) = bundle_css_module(\n      TestProvider {\n        map: fs! {\n          \"/a.css\": r#\"\n          @import \"b.css\";\n          .a { color: red }\n        \"#,\n          \"/b.css\": r#\"\n          .a { color: green }\n        \"#\n        },\n      },\n      \"/a.css\",\n      None,\n    );\n    assert_eq!(\n      code,\n      indoc! { r#\"\n      ._9z6RGq_a {\n        color: green;\n      }\n\n      ._6lixEq_a {\n        color: red;\n      }\n    \"#}\n    );\n    assert_eq!(\n      flatten_exports(exports),\n      map! {\n        \"a\" => \"_6lixEq_a\"\n      }\n    );\n\n    let (code, exports) = bundle_css_module(\n      TestProvider {\n        map: fs! {\n          \"/a.css\": r#\"\n          .a { composes: x from './b.css'; color: red; }\n          .b { color: yellow }\n        \"#,\n          \"/b.css\": r#\"\n          .x { composes: y; background: green }\n          .y { font: Helvetica }\n        \"#\n        },\n      },\n      \"/a.css\",\n      None,\n    );\n    assert_eq!(\n      code,\n      indoc! { r#\"\n      ._8Cs9ZG_x {\n        background: green;\n      }\n\n      ._8Cs9ZG_y {\n        font: Helvetica;\n      }\n\n      ._6lixEq_a {\n        color: red;\n      }\n\n      ._6lixEq_b {\n        color: #ff0;\n      }\n    \"#}\n    );\n    assert_eq!(\n      flatten_exports(exports),\n      map! {\n        \"a\" => \"_6lixEq_a _8Cs9ZG_x _8Cs9ZG_y\",\n        \"b\" => \"_6lixEq_b\"\n      }\n    );\n\n    let (code, exports) = bundle_css_module(\n      TestProvider {\n        map: fs! {\n          \"/a.css\": r#\"\n          .a { composes: x from './b.css'; background: red; }\n        \"#,\n          \"/b.css\": r#\"\n          .a { background: red }\n        \"#\n        },\n      },\n      \"/a.css\",\n      None,\n    );\n    assert_eq!(\n      code,\n      indoc! { r#\"\n      ._8Cs9ZG_a {\n        background: red;\n      }\n\n      ._6lixEq_a {\n        background: red;\n      }\n    \"#}\n    );\n    assert_eq!(\n      flatten_exports(exports),\n      map! {\n        \"a\" => \"_6lixEq_a\"\n      }\n    );\n\n    let (code, exports) = bundle_css_module(\n      TestProvider {\n        map: fs! {\n          \"/a.css\": r#\"\n          .a {\n            background: var(--bg from \"./b.css\", var(--fallback from \"./b.css\"));\n            color: rgb(255 255 255 / var(--opacity from \"./b.css\"));\n            width: env(--env, var(--env-fallback from \"./env.css\"));\n          }\n        \"#,\n          \"/b.css\": r#\"\n          .b {\n            --bg: red;\n            --fallback: yellow;\n            --opacity: 0.5;\n          }\n        \"#,\n          \"/env.css\": r#\"\n          .env {\n            --env-fallback: 20px;\n          }\n        \"#\n        },\n      },\n      \"/a.css\",\n      None,\n    );\n    assert_eq!(\n      code,\n      indoc! { r#\"\n      ._8Cs9ZG_b {\n        --_8Cs9ZG_bg: red;\n        --_8Cs9ZG_fallback: yellow;\n        --_8Cs9ZG_opacity: .5;\n      }\n\n      .GbJUva_env {\n        --GbJUva_env-fallback: 20px;\n      }\n\n      ._6lixEq_a {\n        background: var(--_8Cs9ZG_bg, var(--_8Cs9ZG_fallback));\n        color: rgb(255 255 255 / var(--_8Cs9ZG_opacity));\n        width: env(--_6lixEq_env, var(--GbJUva_env-fallback));\n      }\n    \"#}\n    );\n    assert_eq!(\n      flatten_exports(exports),\n      map! {\n        \"a\" => \"_6lixEq_a\",\n        \"--env\" => \"--_6lixEq_env\"\n      }\n    );\n\n    // Hashes are stable between project roots.\n    let expected = indoc! { r#\"\n    .dyGcAa_b {\n      background: #ff0;\n    }\n\n    .CK9avG_a {\n      background: #fff;\n    }\n  \"#};\n\n    let (code, _) = bundle_css_module(\n      TestProvider {\n        map: fs! {\n          \"/foo/bar/a.css\": r#\"\n        @import \"b.css\";\n        .a {\n          background: white;\n        }\n      \"#,\n          \"/foo/bar/b.css\": r#\"\n        .b {\n          background: yellow;\n        }\n      \"#\n        },\n      },\n      \"/foo/bar/a.css\",\n      Some(\"/foo/bar\"),\n    );\n    assert_eq!(code, expected);\n\n    let (code, _) = bundle_css_module(\n      TestProvider {\n        map: fs! {\n          \"/x/y/z/a.css\": r#\"\n      @import \"b.css\";\n      .a {\n        background: white;\n      }\n    \"#,\n          \"/x/y/z/b.css\": r#\"\n      .b {\n        background: yellow;\n      }\n    \"#\n        },\n      },\n      \"/x/y/z/a.css\",\n      Some(\"/x/y/z\"),\n    );\n    assert_eq!(code, expected);\n\n    let (code, _) = bundle_css_module_with_pattern(\n      TestProvider {\n        map: fs! {\n          \"/a.css\": r#\"\n          @import \"b.css\";\n          .a { color: red }\n        \"#,\n          \"/b.css\": r#\"\n          .a { color: green }\n        \"#\n        },\n      },\n      \"/a.css\",\n      None,\n      \"[content-hash]-[local]\",\n    );\n    assert_eq!(\n      code,\n      indoc! { r#\"\n      .do5n2W-a {\n        color: green;\n      }\n\n      .pP97eq-a {\n        color: red;\n      }\n    \"#}\n    );\n  }\n\n  #[test]\n  fn test_source_map() {\n    let source = r#\".imported {\n      content: \"yay, file support!\";\n    }\n\n    .selector {\n      margin: 1em;\n      background-color: #f60;\n    }\n\n    .selector .nested {\n      margin: 0.5em;\n    }\n\n    /*# sourceMappingURL=data:application/json;base64,ewoJInZlcnNpb24iOiAzLAoJInNvdXJjZVJvb3QiOiAicm9vdCIsCgkiZmlsZSI6ICJzdGRvdXQiLAoJInNvdXJjZXMiOiBbCgkJInN0ZGluIiwKCQkic2Fzcy9fdmFyaWFibGVzLnNjc3MiLAoJCSJzYXNzL19kZW1vLnNjc3MiCgldLAoJInNvdXJjZXNDb250ZW50IjogWwoJCSJAaW1wb3J0IFwiX3ZhcmlhYmxlc1wiO1xuQGltcG9ydCBcIl9kZW1vXCI7XG5cbi5zZWxlY3RvciB7XG4gIG1hcmdpbjogJHNpemU7XG4gIGJhY2tncm91bmQtY29sb3I6ICRicmFuZENvbG9yO1xuXG4gIC5uZXN0ZWQge1xuICAgIG1hcmdpbjogJHNpemUgLyAyO1xuICB9XG59IiwKCQkiJGJyYW5kQ29sb3I6ICNmNjA7XG4kc2l6ZTogMWVtOyIsCgkJIi5pbXBvcnRlZCB7XG4gIGNvbnRlbnQ6IFwieWF5LCBmaWxlIHN1cHBvcnQhXCI7XG59IgoJXSwKCSJtYXBwaW5ncyI6ICJBRUFBLFNBQVMsQ0FBQztFQUNSLE9BQU8sRUFBRSxvQkFBcUI7Q0FDL0I7O0FGQ0QsU0FBUyxDQUFDO0VBQ1IsTUFBTSxFQ0hELEdBQUc7RURJUixnQkFBZ0IsRUNMTCxJQUFJO0NEVWhCOztBQVBELFNBQVMsQ0FJUCxPQUFPLENBQUM7RUFDTixNQUFNLEVDUEgsS0FBRztDRFFQIiwKCSJuYW1lcyI6IFtdCn0= */\"#;\n\n    let fs = TestProvider {\n      map: fs! {\n        \"/a.css\": r#\"\n        @import \"/b.css\";\n        .a { color: red; }\n      \"#,\n        \"/b.css\": source\n      },\n    };\n\n    let mut sm = parcel_sourcemap::SourceMap::new(\"/\");\n    let mut bundler = Bundler::new(&fs, Some(&mut sm), ParserOptions::default());\n    let mut stylesheet = bundler.bundle(Path::new(\"/a.css\")).unwrap();\n    stylesheet.minify(MinifyOptions::default()).unwrap();\n    stylesheet\n      .to_css(PrinterOptions {\n        source_map: Some(&mut sm),\n        minify: true,\n        ..PrinterOptions::default()\n      })\n      .unwrap();\n    let map = sm.to_json(None).unwrap();\n    assert_eq!(\n      map,\n      r#\"{\"version\":3,\"sourceRoot\":null,\"mappings\":\"ACAA,uCCGA,2CAAA,8BFDQ\",\"sources\":[\"a.css\",\"sass/_demo.scss\",\"stdin\"],\"sourcesContent\":[\"\\n        @import \\\"/b.css\\\";\\n        .a { color: red; }\\n      \",\".imported {\\n  content: \\\"yay, file support!\\\";\\n}\",\"@import \\\"_variables\\\";\\n@import \\\"_demo\\\";\\n\\n.selector {\\n  margin: $size;\\n  background-color: $brandColor;\\n\\n  .nested {\\n    margin: $size / 2;\\n  }\\n}\"],\"names\":[]}\"#\n    );\n  }\n\n  #[test]\n  fn test_license_comments() {\n    let res = bundle(\n      TestProvider {\n        map: fs! {\n          \"/a.css\": r#\"\n          /*! Copyright 2023 Someone awesome */\n          @import \"b.css\";\n          .a { color: red }\n        \"#,\n          \"/b.css\": r#\"\n          /*! Copyright 2023 Someone else */\n          .b { color: green }\n        \"#\n        },\n      },\n      \"/a.css\",\n    );\n    assert_eq!(\n      res,\n      indoc! { r#\"\n      /*! Copyright 2023 Someone awesome */\n      /*! Copyright 2023 Someone else */\n      .b {\n        color: green;\n      }\n\n      .a {\n        color: red;\n      }\n    \"#}\n    );\n  }\n}\n"
  },
  {
    "path": "src/compat.rs",
    "content": "// This file is autogenerated by build-prefixes.js. DO NOT EDIT!\n\nuse crate::targets::Browsers;\n\n#[allow(dead_code)]\n#[derive(Clone, Copy, PartialEq)]\npub enum Feature {\n  AbsFunction,\n  AccentSystemColor,\n  AfarListStyleType,\n  AmharicAbegedeListStyleType,\n  AmharicListStyleType,\n  AnchorSizeSize,\n  AnimationTimelineShorthand,\n  AnyLink,\n  AnyPseudo,\n  ArabicIndicListStyleType,\n  ArmenianListStyleType,\n  AsterisksListStyleType,\n  AutoSize,\n  Autofill,\n  BengaliListStyleType,\n  BinaryListStyleType,\n  BorderImageRepeatRound,\n  BorderImageRepeatSpace,\n  CalcFunction,\n  CambodianListStyleType,\n  CapUnit,\n  CaseInsensitive,\n  ChUnit,\n  Checkmark,\n  CircleListStyleType,\n  CjkDecimalListStyleType,\n  CjkEarthlyBranchListStyleType,\n  CjkHeavenlyStemListStyleType,\n  ClampFunction,\n  ColorFunction,\n  ConicGradient,\n  ContainerQueryLengthUnits,\n  Cue,\n  CueFunction,\n  CustomMediaQueries,\n  DecimalLeadingZeroListStyleType,\n  DecimalListStyleType,\n  DefaultPseudo,\n  DetailsContent,\n  DevanagariListStyleType,\n  Dialog,\n  DirSelector,\n  DiscListStyleType,\n  DisclosureClosedListStyleType,\n  DisclosureOpenListStyleType,\n  DoublePositionGradients,\n  EmUnit,\n  EthiopicAbegedeAmEtListStyleType,\n  EthiopicAbegedeGezListStyleType,\n  EthiopicAbegedeListStyleType,\n  EthiopicAbegedeTiErListStyleType,\n  EthiopicAbegedeTiEtListStyleType,\n  EthiopicHalehameAaErListStyleType,\n  EthiopicHalehameAaEtListStyleType,\n  EthiopicHalehameAmEtListStyleType,\n  EthiopicHalehameGezListStyleType,\n  EthiopicHalehameOmEtListStyleType,\n  EthiopicHalehameSidEtListStyleType,\n  EthiopicHalehameSoEtListStyleType,\n  EthiopicHalehameTigListStyleType,\n  EthiopicListStyleType,\n  EthiopicNumericListStyleType,\n  ExUnit,\n  ExtendedSystemFonts,\n  FirstLetter,\n  FirstLine,\n  FitContentFunctionSize,\n  FitContentSize,\n  FocusVisible,\n  FocusWithin,\n  FontFamilySystemUi,\n  FontSizeRem,\n  FontSizeXXXLarge,\n  FontStretchPercentage,\n  FontStyleObliqueAngle,\n  FontWeightNumber,\n  FootnotesListStyleType,\n  FormValidation,\n  Fullscreen,\n  Gencontent,\n  GeorgianListStyleType,\n  GradientInterpolationHints,\n  GrammarError,\n  GujaratiListStyleType,\n  GurmukhiListStyleType,\n  HasSelector,\n  HebrewListStyleType,\n  HexAlphaColors,\n  Highlight,\n  HiraganaIrohaListStyleType,\n  HiraganaListStyleType,\n  HypotFunction,\n  IcUnit,\n  ImageSet,\n  InOutOfRange,\n  IndeterminatePseudo,\n  IsSelector,\n  JapaneseFormalListStyleType,\n  JapaneseInformalListStyleType,\n  KannadaListStyleType,\n  KatakanaIrohaListStyleType,\n  KatakanaListStyleType,\n  KhmerListStyleType,\n  KoreanHangulFormalListStyleType,\n  KoreanHanjaFormalListStyleType,\n  KoreanHanjaInformalListStyleType,\n  LabColors,\n  LangSelectorList,\n  LaoListStyleType,\n  LhUnit,\n  LightDark,\n  LinearGradient,\n  LogicalBorderRadius,\n  LogicalBorderShorthand,\n  LogicalBorders,\n  LogicalInset,\n  LogicalMargin,\n  LogicalMarginShorthand,\n  LogicalPadding,\n  LogicalPaddingShorthand,\n  LogicalSize,\n  LogicalTextAlign,\n  LowerAlphaListStyleType,\n  LowerArmenianListStyleType,\n  LowerGreekListStyleType,\n  LowerHexadecimalListStyleType,\n  LowerLatinListStyleType,\n  LowerNorwegianListStyleType,\n  LowerRomanListStyleType,\n  MalayalamListStyleType,\n  MarkerPseudo,\n  MaxContentSize,\n  MaxFunction,\n  MediaIntervalSyntax,\n  MediaRangeSyntax,\n  MinContentSize,\n  MinFunction,\n  ModFunction,\n  MongolianListStyleType,\n  MyanmarListStyleType,\n  Namespaces,\n  Nesting,\n  NoneListStyleType,\n  NotSelectorList,\n  NthChildOf,\n  OctalListStyleType,\n  OklabColors,\n  OptionalPseudo,\n  OriyaListStyleType,\n  OromoListStyleType,\n  OverflowShorthand,\n  P3Colors,\n  PartPseudo,\n  PersianListStyleType,\n  Picker,\n  PickerIcon,\n  PlaceContent,\n  PlaceItems,\n  PlaceSelf,\n  Placeholder,\n  PlaceholderShown,\n  QUnit,\n  RadialGradient,\n  RcapUnit,\n  RchUnit,\n  ReadOnlyWrite,\n  RemFunction,\n  RemUnit,\n  RepeatingConicGradient,\n  RepeatingLinearGradient,\n  RepeatingRadialGradient,\n  RexUnit,\n  RicUnit,\n  RlhUnit,\n  RoundFunction,\n  Selection,\n  Selectors2,\n  Selectors3,\n  Shadowdomv1,\n  SidamaListStyleType,\n  SignFunction,\n  SimpChineseFormalListStyleType,\n  SimpChineseInformalListStyleType,\n  SomaliListStyleType,\n  SpaceSeparatedColorNotation,\n  SpellingError,\n  SquareListStyleType,\n  StatePseudoClass,\n  StretchSize,\n  StringListStyleType,\n  SymbolsListStyleType,\n  TamilListStyleType,\n  TargetText,\n  TeluguListStyleType,\n  TextDecorationThicknessPercent,\n  TextDecorationThicknessShorthand,\n  ThaiListStyleType,\n  TibetanListStyleType,\n  TigreListStyleType,\n  TigrinyaErAbegedeListStyleType,\n  TigrinyaErListStyleType,\n  TigrinyaEtAbegedeListStyleType,\n  TigrinyaEtListStyleType,\n  TradChineseFormalListStyleType,\n  TradChineseInformalListStyleType,\n  UpperAlphaListStyleType,\n  UpperArmenianListStyleType,\n  UpperHexadecimalListStyleType,\n  UpperLatinListStyleType,\n  UpperNorwegianListStyleType,\n  UpperRomanListStyleType,\n  VbUnit,\n  VhUnit,\n  ViUnit,\n  ViewTransition,\n  ViewportPercentageUnitsDynamic,\n  ViewportPercentageUnitsLarge,\n  ViewportPercentageUnitsSmall,\n  VmaxUnit,\n  VminUnit,\n  VwUnit,\n  WebkitFillAvailableSize,\n  XResolutionUnit,\n}\n\nimpl Feature {\n  pub fn is_compatible(&self, browsers: Browsers) -> bool {\n    match self {\n      Feature::Selectors2 => {\n        if let Some(version) = browsers.ie {\n          if version < 458752 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 786432 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 131072 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version < 262144 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 196864 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 589824 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 197120 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 131328 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 262144 {\n            return false;\n          }\n        }\n      }\n      Feature::Selectors3 => {\n        if let Some(version) = browsers.ie {\n          if version < 589824 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 786432 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 197888 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version < 262144 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 197120 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 591104 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 197120 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 131328 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 262144 {\n            return false;\n          }\n        }\n      }\n      Feature::Gencontent | Feature::FirstLine => {\n        if let Some(version) = browsers.ie {\n          if version < 589824 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 786432 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 131072 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version < 262144 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 196864 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 589824 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 197120 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 131328 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 262144 {\n            return false;\n          }\n        }\n      }\n      Feature::FirstLetter => {\n        if let Some(version) = browsers.ie {\n          if version < 589824 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 786432 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 197888 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version < 589824 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 327936 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 722432 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 327680 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 196608 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 262144 {\n            return false;\n          }\n        }\n      }\n      Feature::InOutOfRange => {\n        if let Some(version) = browsers.edge {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 3276800 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version < 3473408 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 655616 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 2621440 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 656128 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 9502720 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 327680 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::FormValidation => {\n        if let Some(version) = browsers.ie {\n          if version < 655360 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 786432 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 262144 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version < 655360 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 655616 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 655360 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 656128 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 263171 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 262144 {\n            return false;\n          }\n        }\n      }\n      Feature::AnyLink => {\n        if let Some(version) = browsers.edge {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 3276800 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version < 4259840 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 589824 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 3407872 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 589824 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 9502720 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 590336 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::DefaultPseudo => {\n        if let Some(version) = browsers.edge {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 262144 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version < 3342336 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 655616 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 2490368 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 656128 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 9502720 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 327680 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::DirSelector => {\n        if let Some(version) = browsers.edge {\n          if version < 7864320 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 3211264 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version < 7864320 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 1049600 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 6946816 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 1049600 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 9502720 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 1638400 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::FocusWithin => {\n        if let Some(version) = browsers.edge {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 3407872 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version < 3932160 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 655616 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 3080192 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 656128 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 9502720 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 524800 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::FocusVisible => {\n        if let Some(version) = browsers.edge {\n          if version < 5636096 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 5570560 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version < 5636096 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 984064 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 4718592 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 984064 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 9502720 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 917504 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::IndeterminatePseudo => {\n        if let Some(version) = browsers.edge {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 3342336 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version < 2555904 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 655616 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 1703936 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 656128 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 9502720 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 262144 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::IsSelector => {\n        if let Some(version) = browsers.edge {\n          if version < 5767168 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 5111808 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version < 5767168 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 917504 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 4915200 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 917504 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 9502720 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 983040 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::OptionalPseudo => {\n        if let Some(version) = browsers.ie {\n          if version < 655360 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 786432 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 262144 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version < 983040 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 327680 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 983040 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 327680 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 131840 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 262144 {\n            return false;\n          }\n        }\n      }\n      Feature::PlaceholderShown => {\n        if let Some(version) = browsers.edge {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 3342336 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version < 3080192 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 589824 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 2228224 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 589824 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 9502720 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 327680 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::Dialog => {\n        if let Some(version) = browsers.edge {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 6422528 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version < 2424832 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 984064 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 1572864 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 984064 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 9502720 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 262144 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::Fullscreen => {\n        if let Some(version) = browsers.edge {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 4194304 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version < 4653056 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 1049600 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 786688 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 655616 {\n            return false;\n          }\n        }\n        if browsers.android.is_some() || browsers.ie.is_some() || browsers.ios_saf.is_some() {\n          return false;\n        }\n      }\n      Feature::MarkerPseudo => {\n        if let Some(version) = browsers.edge {\n          if version < 5636096 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 4456448 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version < 5636096 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 4718592 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 9502720 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 917504 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() || browsers.ios_saf.is_some() || browsers.safari.is_some() {\n          return false;\n        }\n      }\n      Feature::Placeholder => {\n        if let Some(version) = browsers.edge {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 3342336 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version < 3735552 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 655616 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 2883584 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 656128 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 9502720 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 459264 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::Selection => {\n        if let Some(version) = browsers.ie {\n          if version < 589824 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 786432 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 4063232 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version < 262144 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 196864 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 591104 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 263168 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 262144 {\n            return false;\n          }\n        }\n        if browsers.ios_saf.is_some() {\n          return false;\n        }\n      }\n      Feature::CaseInsensitive => {\n        if let Some(version) = browsers.edge {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 3080192 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version < 3211264 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 589824 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 2359296 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 589824 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 9502720 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 327680 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::ReadOnlyWrite => {\n        if let Some(version) = browsers.edge {\n          if version < 851968 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 5111808 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version < 2359296 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 589824 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 1507328 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 589824 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 9502720 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 262144 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::Autofill => {\n        if let Some(version) = browsers.chrome {\n          if version < 7208960 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 7208960 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 6291456 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 983040 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 983040 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 1376256 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 9502720 {\n            return false;\n          }\n        }\n        if browsers.firefox.is_some() || browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::Namespaces => {\n        if let Some(version) = browsers.ie {\n          if version < 589824 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 786432 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 131072 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version < 262144 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 262144 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 589824 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 262656 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 131328 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 262144 {\n            return false;\n          }\n        }\n      }\n      Feature::Shadowdomv1 => {\n        if let Some(version) = browsers.edge {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 4128768 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version < 3473408 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 655360 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 2621440 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 720896 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 9502720 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 393728 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::HexAlphaColors => {\n        if let Some(version) = browsers.edge {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 3211264 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version < 4063232 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 655360 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 3407872 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 655360 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 9502720 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 524800 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::Nesting => {\n        if let Some(version) = browsers.edge {\n          if version < 7864320 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 7667712 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version < 7864320 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 1114624 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 6946816 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 1114624 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 9502720 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 1638400 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::NotSelectorList => {\n        if let Some(version) = browsers.edge {\n          if version < 5767168 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 5505024 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version < 5767168 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 589824 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 4915200 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 589824 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 9502720 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 983040 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::HasSelector => {\n        if let Some(version) = browsers.edge {\n          if version < 6881280 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 7929856 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version < 6881280 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 984064 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 5963776 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 984064 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 9502720 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 1310720 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::FontFamilySystemUi => {\n        if let Some(version) = browsers.edge {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 6029312 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version < 3670016 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 720896 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 2818048 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 720896 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 9502720 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 393728 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::ExtendedSystemFonts => {\n        if let Some(version) = browsers.safari {\n          if version < 852224 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 852992 {\n            return false;\n          }\n        }\n        if browsers.android.is_some()\n          || browsers.chrome.is_some()\n          || browsers.edge.is_some()\n          || browsers.firefox.is_some()\n          || browsers.ie.is_some()\n          || browsers.opera.is_some()\n          || browsers.samsung.is_some()\n        {\n          return false;\n        }\n      }\n      Feature::CalcFunction => {\n        if let Some(version) = browsers.edge {\n          if version < 786432 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 1048576 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version < 1703936 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 393472 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 983040 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 458752 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 9502720 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 262144 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::CustomMediaQueries | Feature::FitContentFunctionSize => return false,\n      Feature::DoublePositionGradients => {\n        if let Some(version) = browsers.chrome {\n          if version < 4653056 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 4194304 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 3276800 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 786688 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 786944 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 655360 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 4653056 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::ClampFunction => {\n        if let Some(version) = browsers.chrome {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 3735552 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 852224 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 852992 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 786432 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::PlaceSelf | Feature::PlaceItems => {\n        if let Some(version) = browsers.chrome {\n          if version < 3866624 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 2949120 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 2818048 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 720896 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 720896 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 458752 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 3866624 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::PlaceContent => {\n        if let Some(version) = browsers.chrome {\n          if version < 3866624 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 2949120 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 2818048 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 589824 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 589824 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 458752 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 3866624 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::OverflowShorthand => {\n        if let Some(version) = browsers.chrome {\n          if version < 4456448 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 3997696 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 3145728 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 852224 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 852992 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 655360 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 4456448 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::MediaRangeSyntax => {\n        if let Some(version) = browsers.chrome {\n          if version < 6815744 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 6815744 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 4128768 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 4653056 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 1049600 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 1049600 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 1310720 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 6815744 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::MediaIntervalSyntax => {\n        if let Some(version) = browsers.chrome {\n          if version < 6815744 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 6815744 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 6684672 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 4653056 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 1049600 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 1049600 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 1310720 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 6815744 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::LogicalBorders => {\n        if let Some(version) = browsers.chrome {\n          if version < 4521984 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 2686976 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 3145728 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 786688 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 786944 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 655360 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 4521984 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::LogicalBorderShorthand | Feature::LogicalMarginShorthand | Feature::LogicalPaddingShorthand => {\n        if let Some(version) = browsers.chrome {\n          if version < 5701632 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 5701632 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 4325376 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 4063232 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 917760 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 918784 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 917504 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 5701632 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::LogicalBorderRadius => {\n        if let Some(version) = browsers.chrome {\n          if version < 5832704 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 5832704 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 4325376 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 4128768 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 983040 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 983040 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 983040 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 5832704 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::LogicalMargin | Feature::LogicalPadding => {\n        if let Some(version) = browsers.chrome {\n          if version < 4521984 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 2686976 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 3145728 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 786688 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 786944 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 655360 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 5701632 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::LogicalInset => {\n        if let Some(version) = browsers.chrome {\n          if version < 5701632 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 5701632 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 4128768 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 4063232 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 917760 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 918784 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 917504 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 5701632 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::LogicalSize => {\n        if let Some(version) = browsers.chrome {\n          if version < 3735552 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 2686976 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 2818048 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 786688 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 786944 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 458752 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 3735552 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::LogicalTextAlign => {\n        if let Some(version) = browsers.chrome {\n          if version < 1179648 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 262144 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 917504 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 196864 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 131072 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 65536 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 2424832 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::LabColors => {\n        if let Some(version) = browsers.chrome {\n          if version < 7274496 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 7274496 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 7405568 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 4915200 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 983040 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 983040 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 1441792 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 7274496 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::OklabColors => {\n        if let Some(version) = browsers.chrome {\n          if version < 7274496 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 7274496 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 7405568 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 4915200 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 984064 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 984064 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 1441792 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 7274496 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::ColorFunction => {\n        if let Some(version) = browsers.chrome {\n          if version < 7274496 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 7274496 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 7405568 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 4915200 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 655616 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 656128 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 1441792 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 7274496 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::SpaceSeparatedColorNotation => {\n        if let Some(version) = browsers.chrome {\n          if version < 4259840 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 3407872 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 3080192 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 786688 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 786944 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 589824 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 4259840 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::Highlight => {\n        if let Some(version) = browsers.chrome {\n          if version < 6881280 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 6881280 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 9175040 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 4718592 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 1114624 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 1114624 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 1310720 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 6881280 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::TextDecorationThicknessPercent => {\n        if let Some(version) = browsers.chrome {\n          if version < 5701632 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 5701632 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 4063232 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 1115136 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 1115136 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 917504 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 5701632 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::TextDecorationThicknessShorthand => {\n        if let Some(version) = browsers.chrome {\n          if version < 5701632 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 5701632 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 4063232 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 1704448 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 1704448 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 917504 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 5701632 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::Cue => {\n        if let Some(version) = browsers.chrome {\n          if version < 1703936 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 3604480 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 917504 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 458752 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 458752 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 66816 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 263168 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::CueFunction => {\n        if let Some(version) = browsers.chrome {\n          if version < 1703936 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 917504 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 458752 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 458752 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 66816 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 263168 {\n            return false;\n          }\n        }\n        if browsers.firefox.is_some() || browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::AnyPseudo => {\n        if let Some(version) = browsers.chrome {\n          if version < 1179648 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 262144 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 917504 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 327680 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 327680 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 65536 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 263168 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::PartPseudo => {\n        if let Some(version) = browsers.chrome {\n          if version < 4784128 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 3407872 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 852224 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 852992 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 720896 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 4784128 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::ImageSet => {\n        if let Some(version) = browsers.chrome {\n          if version < 1638400 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 5767168 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 917504 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 393216 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 393216 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 66816 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 263168 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::XResolutionUnit => {\n        if let Some(version) = browsers.chrome {\n          if version < 4456448 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 4063232 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 3145728 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 1048576 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 1048576 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 655360 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 4456448 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::NthChildOf => {\n        if let Some(version) = browsers.chrome {\n          if version < 7274496 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 7274496 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 7405568 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 4915200 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 589824 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 589824 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 1441792 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 7274496 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::MinFunction | Feature::MaxFunction => {\n        if let Some(version) = browsers.chrome {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 3735552 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 721152 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 721664 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 786432 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::RoundFunction | Feature::RemFunction | Feature::ModFunction => {\n        if let Some(version) = browsers.chrome {\n          if version < 8192000 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 8192000 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 7733248 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 5439488 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 984064 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 984064 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 1769472 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 8192000 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::AbsFunction | Feature::SignFunction => {\n        if let Some(version) = browsers.chrome {\n          if version < 9043968 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 9043968 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 7733248 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 5963776 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 984064 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 984064 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 9043968 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() || browsers.samsung.is_some() {\n          return false;\n        }\n      }\n      Feature::HypotFunction => {\n        if let Some(version) = browsers.chrome {\n          if version < 7864320 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 7864320 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 7733248 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 5242880 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 984064 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 984064 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 1638400 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 7864320 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::GradientInterpolationHints => {\n        if let Some(version) = browsers.chrome {\n          if version < 2621440 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 2359296 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 1769472 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 458752 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 458752 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 262144 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 2621440 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::BorderImageRepeatRound => {\n        if let Some(version) = browsers.chrome {\n          if version < 1966080 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 786432 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 983040 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ie {\n          if version < 720896 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 1179648 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 590080 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 590592 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 131072 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 263168 {\n            return false;\n          }\n        }\n      }\n      Feature::BorderImageRepeatSpace => {\n        if let Some(version) = browsers.chrome {\n          if version < 3670016 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 786432 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 3276800 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ie {\n          if version < 720896 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 2818048 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 590080 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 590592 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 393216 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 3670016 {\n            return false;\n          }\n        }\n      }\n      Feature::FontSizeRem => {\n        if let Some(version) = browsers.chrome {\n          if version < 2752512 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 786432 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 2031616 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ie {\n          if version < 589824 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 1835008 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 458752 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 458752 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 262144 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 2752512 {\n            return false;\n          }\n        }\n      }\n      Feature::FontSizeXXXLarge => {\n        if let Some(version) = browsers.chrome {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 3735552 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 1049600 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 1049600 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 786432 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::FontStyleObliqueAngle => {\n        if let Some(version) = browsers.chrome {\n          if version < 4063232 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 3997696 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 3014656 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 721152 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 721664 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 524288 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 4063232 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::FontWeightNumber => {\n        if let Some(version) = browsers.chrome {\n          if version < 4063232 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 1114112 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 3997696 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 3014656 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 720896 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 720896 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 524288 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 4063232 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::FontStretchPercentage => {\n        if let Some(version) = browsers.chrome {\n          if version < 4063232 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 1179648 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 3997696 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 3014656 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 721152 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 721664 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 524288 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 4063232 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::LightDark => {\n        if let Some(version) = browsers.chrome {\n          if version < 8060928 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 8060928 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 7864320 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 5373952 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 1115392 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 1115392 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 1769472 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 8060928 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::AccentSystemColor => {\n        if let Some(version) = browsers.firefox {\n          if version < 6750208 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 1049856 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 1049856 {\n            return false;\n          }\n        }\n        if browsers.android.is_some()\n          || browsers.chrome.is_some()\n          || browsers.edge.is_some()\n          || browsers.ie.is_some()\n          || browsers.opera.is_some()\n          || browsers.samsung.is_some()\n        {\n          return false;\n        }\n      }\n      Feature::AnimationTimelineShorthand => {\n        if let Some(version) = browsers.chrome {\n          if version < 7536640 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 7536640 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 5046272 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 1507328 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 7536640 {\n            return false;\n          }\n        }\n        if browsers.firefox.is_some()\n          || browsers.ie.is_some()\n          || browsers.ios_saf.is_some()\n          || browsers.safari.is_some()\n        {\n          return false;\n        }\n      }\n      Feature::ViewTransition => {\n        if let Some(version) = browsers.chrome {\n          if version < 7143424 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 7143424 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 9437184 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 4849664 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 1179648 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 1179648 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 1376256 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 7143424 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::DetailsContent => {\n        if let Some(version) = browsers.chrome {\n          if version < 8585216 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 8585216 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 9371648 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 5701632 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 1180672 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 1180672 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 1900544 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 8585216 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::TargetText => {\n        if let Some(version) = browsers.chrome {\n          if version < 5832704 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 5832704 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 8585216 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 4128768 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 1180160 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 1180160 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 983040 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 5832704 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::Picker => {\n        if let Some(version) = browsers.chrome {\n          if version < 8847360 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 8847360 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 5832704 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 1900544 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 8847360 {\n            return false;\n          }\n        }\n        if browsers.firefox.is_some()\n          || browsers.ie.is_some()\n          || browsers.ios_saf.is_some()\n          || browsers.safari.is_some()\n        {\n          return false;\n        }\n      }\n      Feature::PickerIcon | Feature::Checkmark => {\n        if let Some(version) = browsers.chrome {\n          if version < 8716288 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 8716288 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 5767168 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 1900544 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 8716288 {\n            return false;\n          }\n        }\n        if browsers.firefox.is_some()\n          || browsers.ie.is_some()\n          || browsers.ios_saf.is_some()\n          || browsers.safari.is_some()\n        {\n          return false;\n        }\n      }\n      Feature::GrammarError | Feature::SpellingError => {\n        if let Some(version) = browsers.chrome {\n          if version < 7929856 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 7929856 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 5308416 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 1115136 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 1115136 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 1638400 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 7929856 {\n            return false;\n          }\n        }\n        if browsers.firefox.is_some() || browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::StatePseudoClass => {\n        if let Some(version) = browsers.chrome {\n          if version < 8192000 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 8192000 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 8257536 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 5439488 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 1115136 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 1115136 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 1769472 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 8192000 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::QUnit => {\n        if let Some(version) = browsers.chrome {\n          if version < 4128768 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 3211264 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 3014656 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 852224 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 852992 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 524288 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 4128768 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::CapUnit => {\n        if let Some(version) = browsers.chrome {\n          if version < 7733248 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 7733248 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 6356992 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 1114624 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 1114624 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 1638400 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 7733248 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::ChUnit => {\n        if let Some(version) = browsers.chrome {\n          if version < 1769472 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 786432 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 262144 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ie {\n          if version < 589824 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 983040 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 458752 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 458752 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 66816 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 263168 {\n            return false;\n          }\n        }\n      }\n      Feature::ContainerQueryLengthUnits => {\n        if let Some(version) = browsers.chrome {\n          if version < 6881280 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 6881280 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 7208960 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 4718592 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 1048576 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 1048576 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 1310720 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 6881280 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::EmUnit => {\n        if let Some(version) = browsers.chrome {\n          if version < 1179648 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 786432 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 262144 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ie {\n          if version < 196608 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 655616 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 65536 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 65536 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 65536 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 65536 {\n            return false;\n          }\n        }\n      }\n      Feature::ExUnit\n      | Feature::CircleListStyleType\n      | Feature::DecimalListStyleType\n      | Feature::DiscListStyleType\n      | Feature::SquareListStyleType => {\n        if let Some(version) = browsers.chrome {\n          if version < 1179648 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 786432 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 262144 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ie {\n          if version < 262144 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 655616 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 65536 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 65536 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 65536 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 263168 {\n            return false;\n          }\n        }\n      }\n      Feature::IcUnit => {\n        if let Some(version) = browsers.chrome {\n          if version < 6946816 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 6946816 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 6356992 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 4718592 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 984064 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 984064 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 1310720 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 6946816 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::LhUnit => {\n        if let Some(version) = browsers.chrome {\n          if version < 7143424 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 7143424 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 7864320 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 4849664 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 1049600 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 1049600 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 1376256 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 7143424 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::RcapUnit => {\n        if let Some(version) = browsers.chrome {\n          if version < 7733248 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 7733248 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 9633792 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 1114624 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 1114624 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 1638400 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 7733248 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::RchUnit | Feature::RexUnit | Feature::RicUnit => {\n        if let Some(version) = browsers.chrome {\n          if version < 7274496 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 7274496 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 9633792 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 4915200 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 1114624 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 1114624 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 1441792 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 7274496 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::RemUnit => {\n        if let Some(version) = browsers.chrome {\n          if version < 1179648 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 786432 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 262144 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ie {\n          if version < 589824 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 786432 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 327680 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 262144 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 65536 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 131072 {\n            return false;\n          }\n        }\n      }\n      Feature::RlhUnit => {\n        if let Some(version) = browsers.chrome {\n          if version < 7274496 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 7274496 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 7864320 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 4915200 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 1049600 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 1049600 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 1441792 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 7274496 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::VbUnit\n      | Feature::ViUnit\n      | Feature::ViewportPercentageUnitsDynamic\n      | Feature::ViewportPercentageUnitsLarge\n      | Feature::ViewportPercentageUnitsSmall => {\n        if let Some(version) = browsers.chrome {\n          if version < 7077888 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 7077888 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 6619136 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 4784128 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 984064 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 984064 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 1376256 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 7077888 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::VhUnit | Feature::VwUnit => {\n        if let Some(version) = browsers.chrome {\n          if version < 1638400 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 786432 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 1245184 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ie {\n          if version < 589824 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 917504 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 393216 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 393216 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 66816 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 263168 {\n            return false;\n          }\n        }\n      }\n      Feature::VmaxUnit => {\n        if let Some(version) = browsers.chrome {\n          if version < 1703936 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 1048576 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 1245184 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 917504 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 458752 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 458752 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 66816 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 66816 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::VminUnit => {\n        if let Some(version) = browsers.chrome {\n          if version < 1703936 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 786432 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 1245184 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ie {\n          if version < 655360 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 917504 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 458752 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 458752 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 66816 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 263168 {\n            return false;\n          }\n        }\n      }\n      Feature::ConicGradient | Feature::RepeatingConicGradient => {\n        if let Some(version) = browsers.chrome {\n          if version < 4521984 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 5439488 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 3145728 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 786688 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 786944 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 655360 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 4521984 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::LinearGradient | Feature::RepeatingLinearGradient => {\n        if let Some(version) = browsers.chrome {\n          if version < 1179648 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 786432 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 262144 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ie {\n          if version < 655360 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 720896 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 327936 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 327680 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 65536 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 263168 {\n            return false;\n          }\n        }\n      }\n      Feature::RadialGradient => {\n        if let Some(version) = browsers.chrome {\n          if version < 1179648 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 786432 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 262144 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ie {\n          if version < 655360 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 786432 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 327936 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 327680 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 65536 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 263168 {\n            return false;\n          }\n        }\n      }\n      Feature::RepeatingRadialGradient => {\n        if let Some(version) = browsers.chrome {\n          if version < 1179648 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 786432 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 655360 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ie {\n          if version < 655360 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 786432 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 327936 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 327680 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 65536 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 263168 {\n            return false;\n          }\n        }\n      }\n      Feature::AfarListStyleType\n      | Feature::AmharicListStyleType\n      | Feature::AmharicAbegedeListStyleType\n      | Feature::EthiopicListStyleType\n      | Feature::EthiopicAbegedeListStyleType\n      | Feature::EthiopicAbegedeAmEtListStyleType\n      | Feature::EthiopicAbegedeGezListStyleType\n      | Feature::EthiopicAbegedeTiErListStyleType\n      | Feature::EthiopicAbegedeTiEtListStyleType\n      | Feature::EthiopicHalehameAaErListStyleType\n      | Feature::EthiopicHalehameAaEtListStyleType\n      | Feature::EthiopicHalehameAmEtListStyleType\n      | Feature::EthiopicHalehameGezListStyleType\n      | Feature::EthiopicHalehameOmEtListStyleType\n      | Feature::EthiopicHalehameSidEtListStyleType\n      | Feature::EthiopicHalehameSoEtListStyleType\n      | Feature::EthiopicHalehameTigListStyleType\n      | Feature::LowerHexadecimalListStyleType\n      | Feature::LowerNorwegianListStyleType\n      | Feature::UpperHexadecimalListStyleType\n      | Feature::UpperNorwegianListStyleType => {\n        if let Some(version) = browsers.chrome {\n          if version < 1179648 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 5963776 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 917504 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 327680 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 262656 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 65536 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 196608 {\n            return false;\n          }\n        }\n        if browsers.firefox.is_some() || browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::ArabicIndicListStyleType\n      | Feature::BengaliListStyleType\n      | Feature::CjkEarthlyBranchListStyleType\n      | Feature::CjkHeavenlyStemListStyleType\n      | Feature::DevanagariListStyleType\n      | Feature::GujaratiListStyleType\n      | Feature::GurmukhiListStyleType\n      | Feature::KannadaListStyleType\n      | Feature::KhmerListStyleType\n      | Feature::LaoListStyleType\n      | Feature::MalayalamListStyleType\n      | Feature::MyanmarListStyleType\n      | Feature::OriyaListStyleType\n      | Feature::PersianListStyleType\n      | Feature::TeluguListStyleType\n      | Feature::ThaiListStyleType => {\n        if let Some(version) = browsers.chrome {\n          if version < 1179648 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 262144 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 917504 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 327680 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 262656 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 65536 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 263168 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::ArmenianListStyleType\n      | Feature::DecimalLeadingZeroListStyleType\n      | Feature::GeorgianListStyleType\n      | Feature::LowerAlphaListStyleType\n      | Feature::LowerGreekListStyleType\n      | Feature::LowerRomanListStyleType\n      | Feature::UpperAlphaListStyleType\n      | Feature::UpperLatinListStyleType\n      | Feature::UpperRomanListStyleType => {\n        if let Some(version) = browsers.chrome {\n          if version < 1179648 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 786432 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 262144 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ie {\n          if version < 524288 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 655616 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 65536 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 65536 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 65536 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 263168 {\n            return false;\n          }\n        }\n      }\n      Feature::AsterisksListStyleType | Feature::FootnotesListStyleType => {\n        if let Some(version) = browsers.chrome {\n          if version < 1179648 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 5963776 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 917504 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 327936 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 327680 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 65536 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 263168 {\n            return false;\n          }\n        }\n        if browsers.firefox.is_some() || browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::BinaryListStyleType\n      | Feature::OctalListStyleType\n      | Feature::OromoListStyleType\n      | Feature::SidamaListStyleType\n      | Feature::SomaliListStyleType\n      | Feature::TigreListStyleType\n      | Feature::TigrinyaErListStyleType\n      | Feature::TigrinyaErAbegedeListStyleType\n      | Feature::TigrinyaEtListStyleType\n      | Feature::TigrinyaEtAbegedeListStyleType => {\n        if let Some(version) = browsers.chrome {\n          if version < 1179648 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 5963776 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 917504 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 327680 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 262656 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 65536 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 263168 {\n            return false;\n          }\n        }\n        if browsers.firefox.is_some() || browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::CambodianListStyleType | Feature::MongolianListStyleType | Feature::TibetanListStyleType => {\n        if let Some(version) = browsers.chrome {\n          if version < 1179648 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 2162688 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 917504 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 327680 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 262656 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 65536 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 263168 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::CjkDecimalListStyleType => {\n        if let Some(version) = browsers.chrome {\n          if version < 5963776 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 5963776 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 1835008 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 4194304 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 983040 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 983040 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 1048576 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 5963776 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::DisclosureClosedListStyleType | Feature::DisclosureOpenListStyleType => {\n        if let Some(version) = browsers.chrome {\n          if version < 5832704 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 5832704 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 2162688 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 4128768 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 983040 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 983040 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 983040 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 5832704 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::EthiopicNumericListStyleType\n      | Feature::JapaneseFormalListStyleType\n      | Feature::JapaneseInformalListStyleType\n      | Feature::TamilListStyleType => {\n        if let Some(version) = browsers.chrome {\n          if version < 5963776 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 5963776 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 262144 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 4194304 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 983040 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 983040 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 1048576 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 5963776 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::HebrewListStyleType\n      | Feature::HiraganaListStyleType\n      | Feature::HiraganaIrohaListStyleType\n      | Feature::KatakanaListStyleType\n      | Feature::KatakanaIrohaListStyleType\n      | Feature::NoneListStyleType\n      | Feature::AutoSize => {\n        if let Some(version) = browsers.chrome {\n          if version < 1179648 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 786432 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 262144 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ie {\n          if version < 720896 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 917504 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 65536 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 65536 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 65536 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 263168 {\n            return false;\n          }\n        }\n      }\n      Feature::KoreanHangulFormalListStyleType\n      | Feature::KoreanHanjaFormalListStyleType\n      | Feature::KoreanHanjaInformalListStyleType => {\n        if let Some(version) = browsers.chrome {\n          if version < 2949120 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 1835008 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 2097152 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 983040 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 983040 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 327680 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 2949120 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::LowerArmenianListStyleType | Feature::UpperArmenianListStyleType => {\n        if let Some(version) = browsers.chrome {\n          if version < 1179648 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 2162688 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 917504 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 327936 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 327680 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 65536 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 263168 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::LowerLatinListStyleType => {\n        if let Some(version) = browsers.chrome {\n          if version < 1179648 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 786432 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 262144 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ie {\n          if version < 524288 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 655616 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 65536 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 65536 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 327680 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 263168 {\n            return false;\n          }\n        }\n      }\n      Feature::SimpChineseFormalListStyleType\n      | Feature::SimpChineseInformalListStyleType\n      | Feature::TradChineseFormalListStyleType\n      | Feature::TradChineseInformalListStyleType => {\n        if let Some(version) = browsers.chrome {\n          if version < 2949120 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 262144 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 2097152 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 983040 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 983040 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 327680 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 2949120 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::StringListStyleType => {\n        if let Some(version) = browsers.chrome {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 2555904 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 3735552 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 917760 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 918784 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 786432 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::SymbolsListStyleType => {\n        if let Some(version) = browsers.firefox {\n          if version < 2293760 {\n            return false;\n          }\n        }\n        if browsers.android.is_some()\n          || browsers.chrome.is_some()\n          || browsers.edge.is_some()\n          || browsers.ie.is_some()\n          || browsers.ios_saf.is_some()\n          || browsers.opera.is_some()\n          || browsers.safari.is_some()\n          || browsers.samsung.is_some()\n        {\n          return false;\n        }\n      }\n      Feature::AnchorSizeSize => {\n        if let Some(version) = browsers.chrome {\n          if version < 8192000 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 8192000 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 9633792 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 5439488 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 1703936 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 1703936 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 1769472 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 8192000 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::FitContentSize => {\n        if let Some(version) = browsers.chrome {\n          if version < 1638400 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 262144 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 917504 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 458752 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 458752 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 66816 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 263168 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::MaxContentSize => {\n        if let Some(version) = browsers.chrome {\n          if version < 1638400 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 262144 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 2818048 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 720896 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 720896 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 66816 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 263168 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::MinContentSize => {\n        if let Some(version) = browsers.chrome {\n          if version < 3014656 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 5177344 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version < 262144 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 2162688 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 720896 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 720896 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 327680 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 3014656 {\n            return false;\n          }\n        }\n        if browsers.ie.is_some() {\n          return false;\n        }\n      }\n      Feature::StretchSize => {\n        if let Some(version) = browsers.chrome {\n          if version < 9043968 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version < 9043968 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version < 5963776 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version < 9043968 {\n            return false;\n          }\n        }\n        if browsers.firefox.is_some()\n          || browsers.ie.is_some()\n          || browsers.ios_saf.is_some()\n          || browsers.safari.is_some()\n          || browsers.samsung.is_some()\n        {\n          return false;\n        }\n      }\n      Feature::WebkitFillAvailableSize => {\n        if let Some(version) = browsers.firefox {\n          if version < 9568256 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version < 458752 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 458752 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version < 327680 {\n            return false;\n          }\n        }\n        if browsers.android.is_some()\n          || browsers.chrome.is_some()\n          || browsers.edge.is_some()\n          || browsers.ie.is_some()\n          || browsers.opera.is_some()\n        {\n          return false;\n        }\n      }\n      Feature::P3Colors | Feature::LangSelectorList => {\n        if let Some(version) = browsers.safari {\n          if version < 655616 {\n            return false;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version < 656128 {\n            return false;\n          }\n        }\n        if browsers.android.is_some()\n          || browsers.chrome.is_some()\n          || browsers.edge.is_some()\n          || browsers.firefox.is_some()\n          || browsers.ie.is_some()\n          || browsers.opera.is_some()\n          || browsers.samsung.is_some()\n        {\n          return false;\n        }\n      }\n    }\n    true\n  }\n\n  pub fn is_partially_compatible(&self, targets: Browsers) -> bool {\n    let mut browsers = Browsers::default();\n    if targets.android.is_some() {\n      browsers.android = targets.android;\n      if self.is_compatible(browsers) {\n        return true;\n      }\n      #[allow(unused_assignments)]\n      {\n        browsers.android = None;\n      }\n    }\n    if targets.chrome.is_some() {\n      browsers.chrome = targets.chrome;\n      if self.is_compatible(browsers) {\n        return true;\n      }\n      #[allow(unused_assignments)]\n      {\n        browsers.chrome = None;\n      }\n    }\n    if targets.edge.is_some() {\n      browsers.edge = targets.edge;\n      if self.is_compatible(browsers) {\n        return true;\n      }\n      #[allow(unused_assignments)]\n      {\n        browsers.edge = None;\n      }\n    }\n    if targets.firefox.is_some() {\n      browsers.firefox = targets.firefox;\n      if self.is_compatible(browsers) {\n        return true;\n      }\n      #[allow(unused_assignments)]\n      {\n        browsers.firefox = None;\n      }\n    }\n    if targets.ie.is_some() {\n      browsers.ie = targets.ie;\n      if self.is_compatible(browsers) {\n        return true;\n      }\n      #[allow(unused_assignments)]\n      {\n        browsers.ie = None;\n      }\n    }\n    if targets.ios_saf.is_some() {\n      browsers.ios_saf = targets.ios_saf;\n      if self.is_compatible(browsers) {\n        return true;\n      }\n      #[allow(unused_assignments)]\n      {\n        browsers.ios_saf = None;\n      }\n    }\n    if targets.opera.is_some() {\n      browsers.opera = targets.opera;\n      if self.is_compatible(browsers) {\n        return true;\n      }\n      #[allow(unused_assignments)]\n      {\n        browsers.opera = None;\n      }\n    }\n    if targets.safari.is_some() {\n      browsers.safari = targets.safari;\n      if self.is_compatible(browsers) {\n        return true;\n      }\n      #[allow(unused_assignments)]\n      {\n        browsers.safari = None;\n      }\n    }\n    if targets.samsung.is_some() {\n      browsers.samsung = targets.samsung;\n      if self.is_compatible(browsers) {\n        return true;\n      }\n      #[allow(unused_assignments)]\n      {\n        browsers.samsung = None;\n      }\n    }\n\n    false\n  }\n}\n"
  },
  {
    "path": "src/context.rs",
    "content": "use std::collections::HashSet;\n\nuse crate::compat::Feature;\nuse crate::declaration::DeclarationBlock;\nuse crate::media_query::{\n  MediaCondition, MediaFeatureId, MediaFeatureName, MediaFeatureValue, MediaList, MediaQuery, MediaType,\n  QueryFeature,\n};\nuse crate::properties::custom::UnparsedProperty;\nuse crate::properties::Property;\nuse crate::rules::media::MediaRule;\nuse crate::rules::supports::{SupportsCondition, SupportsRule};\nuse crate::rules::{style::StyleRule, CssRule, CssRuleList};\nuse crate::selector::{Direction, PseudoClass};\nuse crate::targets::Targets;\nuse crate::values::ident::Ident;\nuse crate::vendor_prefix::VendorPrefix;\nuse parcel_selectors::parser::Component;\n\n#[derive(Debug)]\npub(crate) struct SupportsEntry<'i> {\n  pub condition: SupportsCondition<'i>,\n  pub declarations: Vec<Property<'i>>,\n  pub important_declarations: Vec<Property<'i>>,\n}\n\n#[derive(Debug, PartialEq)]\npub(crate) enum DeclarationContext {\n  None,\n  StyleRule,\n  Keyframes,\n  StyleAttribute,\n}\n\n#[derive(Debug)]\npub(crate) struct PropertyHandlerContext<'i, 'o> {\n  pub targets: Targets,\n  pub is_important: bool,\n  supports: Vec<SupportsEntry<'i>>,\n  ltr: Vec<Property<'i>>,\n  rtl: Vec<Property<'i>>,\n  dark: Vec<Property<'i>>,\n  pub context: DeclarationContext,\n  pub unused_symbols: &'o HashSet<String>,\n}\n\nimpl<'i, 'o> PropertyHandlerContext<'i, 'o> {\n  pub fn new(targets: Targets, unused_symbols: &'o HashSet<String>) -> Self {\n    PropertyHandlerContext {\n      targets,\n      is_important: false,\n      supports: Vec::new(),\n      ltr: Vec::new(),\n      rtl: Vec::new(),\n      dark: Vec::new(),\n      context: DeclarationContext::None,\n      unused_symbols,\n    }\n  }\n\n  pub fn child(&self, context: DeclarationContext) -> Self {\n    PropertyHandlerContext {\n      targets: self.targets,\n      is_important: false,\n      supports: Vec::new(),\n      ltr: Vec::new(),\n      rtl: Vec::new(),\n      dark: Vec::new(),\n      context,\n      unused_symbols: self.unused_symbols,\n    }\n  }\n\n  pub fn should_compile_logical(&self, feature: Feature) -> bool {\n    // Don't convert logical properties in style attributes because\n    // our fallbacks rely on extra rules to define --ltr and --rtl.\n    if self.context == DeclarationContext::StyleAttribute {\n      return false;\n    }\n\n    self.targets.should_compile_logical(feature)\n  }\n\n  pub fn add_logical_rule(&mut self, ltr: Property<'i>, rtl: Property<'i>) {\n    self.ltr.push(ltr);\n    self.rtl.push(rtl);\n  }\n\n  pub fn add_dark_rule(&mut self, property: Property<'i>) {\n    self.dark.push(property);\n  }\n\n  pub fn get_additional_rules<T>(&self, style_rule: &StyleRule<'i, T>) -> Vec<CssRule<'i, T>> {\n    // TODO: :dir/:lang raises the specificity of the selector. Use :where to lower it?\n    let mut dest = Vec::new();\n\n    macro_rules! rule {\n      ($dir: ident, $decls: ident) => {\n        let mut selectors = style_rule.selectors.clone();\n        for selector in &mut selectors.0 {\n          selector.append(Component::NonTSPseudoClass(PseudoClass::Dir {\n            direction: Direction::$dir,\n          }));\n        }\n\n        let rule = StyleRule {\n          selectors,\n          vendor_prefix: VendorPrefix::None,\n          declarations: DeclarationBlock {\n            declarations: self.$decls.clone(),\n            important_declarations: vec![],\n          },\n          rules: CssRuleList(vec![]),\n          loc: style_rule.loc.clone(),\n        };\n\n        dest.push(CssRule::Style(rule));\n      };\n    }\n\n    if !self.ltr.is_empty() {\n      rule!(Ltr, ltr);\n    }\n\n    if !self.rtl.is_empty() {\n      rule!(Rtl, rtl);\n    }\n\n    if !self.dark.is_empty() {\n      dest.push(CssRule::Media(MediaRule {\n        query: MediaList {\n          media_queries: vec![MediaQuery {\n            qualifier: None,\n            media_type: MediaType::All,\n            condition: Some(MediaCondition::Feature(QueryFeature::Plain {\n              name: MediaFeatureName::Standard(MediaFeatureId::PrefersColorScheme),\n              value: MediaFeatureValue::Ident(Ident(\"dark\".into())),\n            })),\n          }],\n        },\n        rules: CssRuleList(vec![CssRule::Style(StyleRule {\n          selectors: style_rule.selectors.clone(),\n          vendor_prefix: VendorPrefix::None,\n          declarations: DeclarationBlock {\n            declarations: self.dark.clone(),\n            important_declarations: vec![],\n          },\n          rules: CssRuleList(vec![]),\n          loc: style_rule.loc.clone(),\n        })]),\n        loc: style_rule.loc.clone(),\n      }))\n    }\n\n    dest\n  }\n\n  pub fn add_conditional_property(&mut self, condition: SupportsCondition<'i>, property: Property<'i>) {\n    if self.context != DeclarationContext::StyleRule {\n      return;\n    }\n\n    if let Some(entry) = self.supports.iter_mut().find(|supports| condition == supports.condition) {\n      if self.is_important {\n        entry.important_declarations.push(property);\n      } else {\n        entry.declarations.push(property);\n      }\n    } else {\n      let mut important_declarations = Vec::new();\n      let mut declarations = Vec::new();\n      if self.is_important {\n        important_declarations.push(property);\n      } else {\n        declarations.push(property);\n      }\n      self.supports.push(SupportsEntry {\n        condition,\n        important_declarations,\n        declarations,\n      });\n    }\n  }\n\n  pub fn add_unparsed_fallbacks(&mut self, unparsed: &mut UnparsedProperty<'i>) {\n    if self.context != DeclarationContext::StyleRule && self.context != DeclarationContext::StyleAttribute {\n      return;\n    }\n\n    let fallbacks = unparsed.value.get_fallbacks(self.targets);\n    for (condition, fallback) in fallbacks {\n      self.add_conditional_property(\n        condition,\n        Property::Unparsed(UnparsedProperty {\n          property_id: unparsed.property_id.clone(),\n          value: fallback,\n        }),\n      );\n    }\n  }\n\n  pub fn get_supports_rules<T>(&self, style_rule: &StyleRule<'i, T>) -> Vec<CssRule<'i, T>> {\n    if self.supports.is_empty() {\n      return Vec::new();\n    }\n\n    let mut dest = Vec::new();\n    for entry in &self.supports {\n      dest.push(CssRule::Supports(SupportsRule {\n        condition: entry.condition.clone(),\n        rules: CssRuleList(vec![CssRule::Style(StyleRule {\n          selectors: style_rule.selectors.clone(),\n          vendor_prefix: VendorPrefix::None,\n          declarations: DeclarationBlock {\n            declarations: entry.declarations.clone(),\n            important_declarations: entry.important_declarations.clone(),\n          },\n          rules: CssRuleList(vec![]),\n          loc: style_rule.loc.clone(),\n        })]),\n        loc: style_rule.loc.clone(),\n      }));\n    }\n\n    dest\n  }\n\n  pub fn reset(&mut self) {\n    self.supports.clear();\n    self.ltr.clear();\n    self.rtl.clear();\n    self.dark.clear();\n  }\n}\n"
  },
  {
    "path": "src/css_modules.rs",
    "content": "//! CSS module exports.\n//!\n//! [CSS modules](https://github.com/css-modules/css-modules) are a way of locally scoping names in a\n//! CSS file. This includes class names, ids, keyframe animation names, and any other places where the\n//! [CustomIdent](super::values::ident::CustomIdent) type is used.\n//!\n//! CSS modules can be enabled using the `css_modules` option when parsing a style sheet. When the\n//! style sheet is printed, hashes will be added to any declared names, and references to those names\n//! will be updated accordingly. A map of the original names to compiled (hashed) names will be returned.\n\nuse crate::error::PrinterErrorKind;\nuse crate::properties::css_modules::{Composes, Specifier};\nuse crate::selector::SelectorList;\nuse data_encoding::{Encoding, Specification};\nuse lazy_static::lazy_static;\nuse pathdiff::diff_paths;\n#[cfg(any(feature = \"serde\", feature = \"nodejs\"))]\nuse serde::Serialize;\nuse smallvec::{smallvec, SmallVec};\nuse std::borrow::Cow;\nuse std::collections::hash_map::DefaultHasher;\nuse std::collections::HashMap;\nuse std::fmt::Write;\nuse std::hash::{Hash, Hasher};\nuse std::path::Path;\n\n/// Configuration for CSS modules.\n#[derive(Clone, Debug)]\npub struct Config<'i> {\n  /// The name pattern to use when renaming class names and other identifiers.\n  /// Default is `[hash]_[local]`.\n  pub pattern: Pattern<'i>,\n  /// Whether to rename dashed identifiers, e.g. custom properties.\n  pub dashed_idents: bool,\n  /// Whether to scope animation names.\n  /// Default is `true`.\n  pub animation: bool,\n  /// Whether to scope grid names.\n  /// Default is `true`.\n  pub grid: bool,\n  /// Whether to scope custom identifiers\n  /// Default is `true`.\n  pub custom_idents: bool,\n  /// Whether to scope container names.\n  /// Default is `true`.\n  pub container: bool,\n  /// Whether to check for pure CSS modules.\n  pub pure: bool,\n}\n\nimpl<'i> Default for Config<'i> {\n  fn default() -> Self {\n    Config {\n      pattern: Default::default(),\n      dashed_idents: Default::default(),\n      animation: true,\n      grid: true,\n      container: true,\n      custom_idents: true,\n      pure: false,\n    }\n  }\n}\n\n/// A CSS modules class name pattern.\n#[derive(Clone, Debug)]\npub struct Pattern<'i> {\n  /// The list of segments in the pattern.\n  pub segments: SmallVec<[Segment<'i>; 2]>,\n}\n\nimpl<'i> Default for Pattern<'i> {\n  fn default() -> Self {\n    Pattern {\n      segments: smallvec![Segment::Hash, Segment::Literal(\"_\"), Segment::Local],\n    }\n  }\n}\n\n/// An error that occurred while parsing a CSS modules name pattern.\n#[derive(Debug)]\npub enum PatternParseError {\n  /// An unknown placeholder segment was encountered at the given index.\n  UnknownPlaceholder(String, usize),\n  /// An opening bracket with no following closing bracket was found at the given index.\n  UnclosedBrackets(usize),\n}\n\nimpl std::fmt::Display for PatternParseError {\n  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n    use PatternParseError::*;\n    match self {\n      UnknownPlaceholder(p, i) => write!(\n        f,\n        \"Error parsing CSS modules pattern: unknown placeholder \\\"{}\\\" at index {}\",\n        p, i\n      ),\n      UnclosedBrackets(i) => write!(f, \"Error parsing CSS modules pattern: unclosed brackets at index {}\", i),\n    }\n  }\n}\n\nimpl std::error::Error for PatternParseError {}\n\nimpl<'i> Pattern<'i> {\n  /// Parse a pattern from a string.\n  pub fn parse(mut input: &'i str) -> Result<Self, PatternParseError> {\n    let mut segments = SmallVec::new();\n    let mut start_idx: usize = 0;\n    while !input.is_empty() {\n      if input.starts_with('[') {\n        if let Some(end_idx) = input.find(']') {\n          let segment = match &input[0..=end_idx] {\n            \"[name]\" => Segment::Name,\n            \"[local]\" => Segment::Local,\n            \"[hash]\" => Segment::Hash,\n            \"[content-hash]\" => Segment::ContentHash,\n            s => return Err(PatternParseError::UnknownPlaceholder(s.into(), start_idx)),\n          };\n          segments.push(segment);\n          start_idx += end_idx + 1;\n          input = &input[end_idx + 1..];\n        } else {\n          return Err(PatternParseError::UnclosedBrackets(start_idx));\n        }\n      } else {\n        let end_idx = input.find('[').unwrap_or_else(|| input.len());\n        segments.push(Segment::Literal(&input[0..end_idx]));\n        start_idx += end_idx;\n        input = &input[end_idx..];\n      }\n    }\n\n    Ok(Pattern { segments })\n  }\n\n  /// Whether the pattern contains any `[content-hash]` segments.\n  pub fn has_content_hash(&self) -> bool {\n    self.segments.iter().any(|s| matches!(s, Segment::ContentHash))\n  }\n\n  /// Write the substituted pattern to a destination.\n  pub fn write<W, E>(\n    &self,\n    hash: &str,\n    path: &Path,\n    local: &str,\n    content_hash: &str,\n    mut write: W,\n  ) -> Result<(), E>\n  where\n    W: FnMut(&str) -> Result<(), E>,\n  {\n    for segment in &self.segments {\n      match segment {\n        Segment::Literal(s) => {\n          write(s)?;\n        }\n        Segment::Name => {\n          let stem = path.file_stem().unwrap().to_str().unwrap();\n          if stem.contains('.') {\n            write(&stem.replace('.', \"-\"))?;\n          } else {\n            write(stem)?;\n          }\n        }\n        Segment::Local => {\n          write(local)?;\n        }\n        Segment::Hash => {\n          write(hash)?;\n        }\n        Segment::ContentHash => {\n          write(content_hash)?;\n        }\n      }\n    }\n    Ok(())\n  }\n\n  #[inline]\n  fn write_to_string(\n    &self,\n    mut res: String,\n    hash: &str,\n    path: &Path,\n    local: &str,\n    content_hash: &str,\n  ) -> Result<String, std::fmt::Error> {\n    self.write(hash, path, local, content_hash, |s| res.write_str(s))?;\n    Ok(res)\n  }\n}\n\n/// A segment in a CSS modules class name pattern.\n///\n/// See [Pattern](Pattern).\n#[derive(Clone, Debug)]\npub enum Segment<'i> {\n  /// A literal string segment.\n  Literal(&'i str),\n  /// The base file name.\n  Name,\n  /// The original class name.\n  Local,\n  /// A hash of the file name.\n  Hash,\n  /// A hash of the file contents.\n  ContentHash,\n}\n\n/// A referenced name within a CSS module, e.g. via the `composes` property.\n///\n/// See [CssModuleExport](CssModuleExport).\n#[derive(PartialEq, Debug, Clone)]\n#[cfg_attr(any(feature = \"serde\", feature = \"nodejs\"), derive(Serialize))]\n#[cfg_attr(\n  any(feature = \"serde\", feature = \"nodejs\"),\n  serde(tag = \"type\", rename_all = \"lowercase\")\n)]\npub enum CssModuleReference {\n  /// A local reference.\n  Local {\n    /// The local (compiled) name for the reference.\n    name: String,\n  },\n  /// A global reference.\n  Global {\n    /// The referenced global name.\n    name: String,\n  },\n  /// A reference to an export in a different file.\n  Dependency {\n    /// The name to reference within the dependency.\n    name: String,\n    /// The dependency specifier for the referenced file.\n    specifier: String,\n  },\n}\n\n/// An exported value from a CSS module.\n#[derive(PartialEq, Debug, Clone)]\n#[cfg_attr(any(feature = \"serde\", feature = \"nodejs\"), derive(Serialize))]\n#[cfg_attr(any(feature = \"serde\", feature = \"nodejs\"), serde(rename_all = \"camelCase\"))]\npub struct CssModuleExport {\n  /// The local (compiled) name for this export.\n  pub name: String,\n  /// Other names that are composed by this export.\n  pub composes: Vec<CssModuleReference>,\n  /// Whether the export is referenced in this file.\n  pub is_referenced: bool,\n}\n\n/// A map of exported names to values.\npub type CssModuleExports = HashMap<String, CssModuleExport>;\n\n/// A map of placeholders to references.\npub type CssModuleReferences = HashMap<String, CssModuleReference>;\n\nlazy_static! {\n  static ref ENCODER: Encoding = {\n    let mut spec = Specification::new();\n    spec\n      .symbols\n      .push_str(\"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_-\");\n    spec.encoding().unwrap()\n  };\n}\n\npub(crate) struct CssModule<'a, 'b, 'c> {\n  pub config: &'a Config<'b>,\n  pub sources: Vec<&'c Path>,\n  pub hashes: Vec<String>,\n  pub content_hashes: &'a Option<Vec<String>>,\n  pub exports_by_source_index: Vec<CssModuleExports>,\n  pub references: &'a mut HashMap<String, CssModuleReference>,\n}\n\nimpl<'a, 'b, 'c> CssModule<'a, 'b, 'c> {\n  pub fn new(\n    config: &'a Config<'b>,\n    sources: &'c Vec<String>,\n    project_root: Option<&'c str>,\n    references: &'a mut HashMap<String, CssModuleReference>,\n    content_hashes: &'a Option<Vec<String>>,\n  ) -> Self {\n    let project_root = project_root.map(|p| Path::new(p));\n    let sources: Vec<&Path> = sources.iter().map(|filename| Path::new(filename)).collect();\n    let hashes = sources\n      .iter()\n      .map(|path| {\n        // Make paths relative to project root so hashes are stable.\n        let source = match project_root {\n          Some(project_root) if path.is_absolute() => {\n            diff_paths(path, project_root).map_or(Cow::Borrowed(*path), Cow::Owned)\n          }\n          _ => Cow::Borrowed(*path),\n        };\n        hash(\n          &source.to_string_lossy(),\n          matches!(config.pattern.segments[0], Segment::Hash),\n        )\n      })\n      .collect();\n    Self {\n      config,\n      exports_by_source_index: sources.iter().map(|_| HashMap::new()).collect(),\n      sources,\n      hashes,\n      content_hashes,\n      references,\n    }\n  }\n\n  pub fn add_local(&mut self, exported: &str, local: &str, source_index: u32) {\n    self.exports_by_source_index[source_index as usize]\n      .entry(exported.into())\n      .or_insert_with(|| CssModuleExport {\n        name: self\n          .config\n          .pattern\n          .write_to_string(\n            String::new(),\n            &self.hashes[source_index as usize],\n            &self.sources[source_index as usize],\n            local,\n            if let Some(content_hashes) = &self.content_hashes {\n              &content_hashes[source_index as usize]\n            } else {\n              \"\"\n            },\n          )\n          .unwrap(),\n        composes: vec![],\n        is_referenced: false,\n      });\n  }\n\n  pub fn add_dashed(&mut self, local: &str, source_index: u32) {\n    self.exports_by_source_index[source_index as usize]\n      .entry(local.into())\n      .or_insert_with(|| CssModuleExport {\n        name: self\n          .config\n          .pattern\n          .write_to_string(\n            \"--\".into(),\n            &self.hashes[source_index as usize],\n            &self.sources[source_index as usize],\n            &local[2..],\n            if let Some(content_hashes) = &self.content_hashes {\n              &content_hashes[source_index as usize]\n            } else {\n              \"\"\n            },\n          )\n          .unwrap(),\n        composes: vec![],\n        is_referenced: false,\n      });\n  }\n\n  pub fn reference(&mut self, name: &str, source_index: u32) {\n    match self.exports_by_source_index[source_index as usize].entry(name.into()) {\n      std::collections::hash_map::Entry::Occupied(mut entry) => {\n        entry.get_mut().is_referenced = true;\n      }\n      std::collections::hash_map::Entry::Vacant(entry) => {\n        entry.insert(CssModuleExport {\n          name: self\n            .config\n            .pattern\n            .write_to_string(\n              String::new(),\n              &self.hashes[source_index as usize],\n              &self.sources[source_index as usize],\n              name,\n              if let Some(content_hashes) = &self.content_hashes {\n                &content_hashes[source_index as usize]\n              } else {\n                \"\"\n              },\n            )\n            .unwrap(),\n          composes: vec![],\n          is_referenced: true,\n        });\n      }\n    }\n  }\n\n  pub fn reference_dashed(&mut self, name: &str, from: &Option<Specifier>, source_index: u32) -> Option<String> {\n    let (reference, key) = match from {\n      Some(Specifier::Global) => return Some(name[2..].into()),\n      Some(Specifier::File(file)) => (\n        CssModuleReference::Dependency {\n          name: name.to_string(),\n          specifier: file.to_string(),\n        },\n        file.as_ref(),\n      ),\n      Some(Specifier::SourceIndex(source_index)) => {\n        return Some(\n          self\n            .config\n            .pattern\n            .write_to_string(\n              String::new(),\n              &self.hashes[*source_index as usize],\n              &self.sources[*source_index as usize],\n              &name[2..],\n              if let Some(content_hashes) = &self.content_hashes {\n                &content_hashes[*source_index as usize]\n              } else {\n                \"\"\n              },\n            )\n            .unwrap(),\n        )\n      }\n      None => {\n        // Local export. Mark as used.\n        match self.exports_by_source_index[source_index as usize].entry(name.into()) {\n          std::collections::hash_map::Entry::Occupied(mut entry) => {\n            entry.get_mut().is_referenced = true;\n          }\n          std::collections::hash_map::Entry::Vacant(entry) => {\n            entry.insert(CssModuleExport {\n              name: self\n                .config\n                .pattern\n                .write_to_string(\n                  \"--\".into(),\n                  &self.hashes[source_index as usize],\n                  &self.sources[source_index as usize],\n                  &name[2..],\n                  if let Some(content_hashes) = &self.content_hashes {\n                    &content_hashes[source_index as usize]\n                  } else {\n                    \"\"\n                  },\n                )\n                .unwrap(),\n              composes: vec![],\n              is_referenced: true,\n            });\n          }\n        }\n        return None;\n      }\n    };\n\n    let hash = hash(\n      &format!(\"{}_{}_{}\", self.hashes[source_index as usize], name, key),\n      false,\n    );\n    let name = format!(\"--{}\", hash);\n\n    self.references.insert(name.clone(), reference);\n    Some(hash)\n  }\n\n  pub fn handle_composes(\n    &mut self,\n    selectors: &SelectorList,\n    composes: &Composes,\n    source_index: u32,\n  ) -> Result<(), PrinterErrorKind> {\n    for sel in &selectors.0 {\n      if sel.len() == 1 {\n        match sel.iter_raw_match_order().next().unwrap() {\n          parcel_selectors::parser::Component::Class(ref id) => {\n            for name in &composes.names {\n              let reference = match &composes.from {\n                None => CssModuleReference::Local {\n                  name: self\n                    .config\n                    .pattern\n                    .write_to_string(\n                      String::new(),\n                      &self.hashes[source_index as usize],\n                      &self.sources[source_index as usize],\n                      name.0.as_ref(),\n                      if let Some(content_hashes) = &self.content_hashes {\n                        &content_hashes[source_index as usize]\n                      } else {\n                        \"\"\n                      },\n                    )\n                    .unwrap(),\n                },\n                Some(Specifier::SourceIndex(dep_source_index)) => {\n                  if let Some(entry) =\n                    self.exports_by_source_index[*dep_source_index as usize].get(&name.0.as_ref().to_owned())\n                  {\n                    let name = entry.name.clone();\n                    let composes = entry.composes.clone();\n                    let export = self.exports_by_source_index[source_index as usize]\n                      .get_mut(&id.0.as_ref().to_owned())\n                      .unwrap();\n\n                    export.composes.push(CssModuleReference::Local { name });\n                    export.composes.extend(composes);\n                  }\n                  continue;\n                }\n                Some(Specifier::Global) => CssModuleReference::Global {\n                  name: name.0.as_ref().into(),\n                },\n                Some(Specifier::File(file)) => CssModuleReference::Dependency {\n                  name: name.0.to_string(),\n                  specifier: file.to_string(),\n                },\n              };\n\n              let export = self.exports_by_source_index[source_index as usize]\n                .get_mut(&id.0.as_ref().to_owned())\n                .unwrap();\n              if !export.composes.contains(&reference) {\n                export.composes.push(reference);\n              }\n            }\n            continue;\n          }\n          _ => {}\n        }\n      }\n\n      // The composes property can only be used within a simple class selector.\n      return Err(PrinterErrorKind::InvalidComposesSelector);\n    }\n\n    Ok(())\n  }\n}\n\npub(crate) fn hash(s: &str, at_start: bool) -> String {\n  let mut hasher = DefaultHasher::new();\n  s.hash(&mut hasher);\n  let hash = hasher.finish() as u32;\n\n  let hash = ENCODER.encode(&hash.to_le_bytes());\n  if at_start && matches!(hash.as_bytes()[0], b'0'..=b'9') {\n    format!(\"_{}\", hash)\n  } else {\n    hash\n  }\n}\n"
  },
  {
    "path": "src/declaration.rs",
    "content": "//! CSS declarations.\n\nuse std::borrow::Cow;\nuse std::ops::Range;\n\nuse crate::context::{DeclarationContext, PropertyHandlerContext};\nuse crate::error::{ParserError, PrinterError, PrinterErrorKind};\nuse crate::parser::ParserOptions;\nuse crate::printer::Printer;\nuse crate::properties::box_shadow::BoxShadowHandler;\nuse crate::properties::custom::{CustomProperty, CustomPropertyName};\nuse crate::properties::masking::MaskHandler;\nuse crate::properties::text::{Direction, UnicodeBidi};\nuse crate::properties::{\n  align::AlignHandler,\n  animation::AnimationHandler,\n  background::BackgroundHandler,\n  border::BorderHandler,\n  contain::ContainerHandler,\n  display::DisplayHandler,\n  flex::FlexHandler,\n  font::FontHandler,\n  grid::GridHandler,\n  list::ListStyleHandler,\n  margin_padding::*,\n  outline::OutlineHandler,\n  overflow::OverflowHandler,\n  position::PositionHandler,\n  prefix_handler::{FallbackHandler, PrefixHandler},\n  size::SizeHandler,\n  text::TextDecorationHandler,\n  transform::TransformHandler,\n  transition::TransitionHandler,\n  ui::ColorSchemeHandler,\n};\nuse crate::properties::{Property, PropertyId};\nuse crate::selector::SelectorList;\nuse crate::traits::{PropertyHandler, ToCss};\nuse crate::values::ident::DashedIdent;\nuse crate::values::string::CowArcStr;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse cssparser::*;\nuse indexmap::IndexMap;\nuse smallvec::SmallVec;\n\n/// A CSS declaration block.\n///\n/// Properties are separated into a list of `!important` declararations,\n/// and a list of normal declarations. This reduces memory usage compared\n/// with storing a boolean along with each property.\n#[derive(Debug, PartialEq, Clone, Default)]\n#[cfg_attr(feature = \"visitor\", derive(Visit), visit(visit_declaration_block, PROPERTIES))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"camelCase\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct DeclarationBlock<'i> {\n  /// A list of `!important` declarations in the block.\n  #[cfg_attr(feature = \"serde\", serde(borrow, default))]\n  pub important_declarations: Vec<Property<'i>>,\n  /// A list of normal declarations in the block.\n  #[cfg_attr(feature = \"serde\", serde(default))]\n  pub declarations: Vec<Property<'i>>,\n}\n\nimpl<'i> DeclarationBlock<'i> {\n  /// Parses a declaration block from CSS syntax.\n  pub fn parse<'a, 'o, 't>(\n    input: &mut Parser<'i, 't>,\n    options: &'a ParserOptions<'o, 'i>,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let mut important_declarations = DeclarationList::new();\n    let mut declarations = DeclarationList::new();\n    let mut decl_parser = PropertyDeclarationParser {\n      important_declarations: &mut important_declarations,\n      declarations: &mut declarations,\n      options,\n    };\n    let mut parser = RuleBodyParser::new(input, &mut decl_parser);\n    while let Some(res) = parser.next() {\n      if let Err((err, _)) = res {\n        if options.error_recovery {\n          options.warn(err);\n          continue;\n        }\n        return Err(err);\n      }\n    }\n\n    Ok(DeclarationBlock {\n      important_declarations,\n      declarations,\n    })\n  }\n\n  /// Parses a declaration block from a string.\n  pub fn parse_string<'o>(\n    input: &'i str,\n    options: ParserOptions<'o, 'i>,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let mut input = ParserInput::new(input);\n    let mut parser = Parser::new(&mut input);\n    let result = Self::parse(&mut parser, &options)?;\n    parser.expect_exhausted()?;\n    Ok(result)\n  }\n\n  /// Returns an empty declaration block.\n  pub fn new() -> Self {\n    Self {\n      declarations: vec![],\n      important_declarations: vec![],\n    }\n  }\n\n  /// Returns the total number of declarations in the block.\n  pub fn len(&self) -> usize {\n    self.declarations.len() + self.important_declarations.len()\n  }\n}\n\nimpl<'i> ToCss for DeclarationBlock<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    let len = self.declarations.len() + self.important_declarations.len();\n    let mut i = 0;\n\n    macro_rules! write {\n      ($decls: expr, $important: literal) => {\n        for decl in &$decls {\n          decl.to_css(dest, $important)?;\n          if i != len - 1 {\n            dest.write_char(';')?;\n            dest.whitespace()?;\n          }\n          i += 1;\n        }\n      };\n    }\n\n    write!(self.declarations, false);\n    write!(self.important_declarations, true);\n    Ok(())\n  }\n}\n\nimpl<'i> DeclarationBlock<'i> {\n  /// Writes the declarations to a CSS block, including starting and ending braces.\n  pub fn to_css_block<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    dest.whitespace()?;\n    dest.write_char('{')?;\n    dest.indent();\n    dest.newline()?;\n\n    self.to_css_declarations(dest, false, &parcel_selectors::SelectorList(SmallVec::new()), 0)?;\n\n    dest.dedent();\n    dest.newline()?;\n    dest.write_char('}')\n  }\n\n  pub(crate) fn has_printable_declarations(&self) -> bool {\n    if self.len() > 1 {\n      return true;\n    }\n\n    if self.declarations.len() == 1 {\n      !matches!(self.declarations[0], crate::properties::Property::Composes(_))\n    } else if self.important_declarations.len() == 1 {\n      !matches!(self.important_declarations[0], crate::properties::Property::Composes(_))\n    } else {\n      false\n    }\n  }\n\n  /// Writes the declarations to a CSS declaration block.\n  pub fn to_css_declarations<W>(\n    &self,\n    dest: &mut Printer<W>,\n    has_nested_rules: bool,\n    selectors: &SelectorList,\n    source_index: u32,\n  ) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    let mut i = 0;\n    let len = self.len();\n\n    macro_rules! write {\n      ($decls: expr, $important: literal) => {\n        for decl in &$decls {\n          // The CSS modules `composes` property is handled specially, and omitted during printing.\n          // We need to add the classes it references to the list for the selectors in this rule.\n          if let crate::properties::Property::Composes(composes) = &decl {\n            if dest.is_nested() && dest.css_module.is_some() {\n              return Err(dest.error(PrinterErrorKind::InvalidComposesNesting, composes.loc));\n            }\n\n            if let Some(css_module) = &mut dest.css_module {\n              css_module\n                .handle_composes(&selectors, &composes, source_index)\n                .map_err(|e| dest.error(e, composes.loc))?;\n              continue;\n            }\n          }\n\n          if i > 0 {\n            dest.newline()?;\n          }\n\n          decl.to_css(dest, $important)?;\n          if i != len - 1 || !dest.minify || has_nested_rules {\n            dest.write_char(';')?;\n          }\n\n          i += 1;\n        }\n      };\n    }\n\n    write!(self.declarations, false);\n    write!(self.important_declarations, true);\n    Ok(())\n  }\n}\n\nimpl<'i> DeclarationBlock<'i> {\n  pub(crate) fn minify(\n    &mut self,\n    handler: &mut DeclarationHandler<'i>,\n    important_handler: &mut DeclarationHandler<'i>,\n    context: &mut PropertyHandlerContext<'i, '_>,\n  ) {\n    macro_rules! handle {\n      ($decls: expr, $handler: expr, $important: literal) => {\n        for decl in $decls.iter() {\n          context.is_important = $important;\n          let handled = $handler.handle_property(decl, context);\n\n          if !handled {\n            $handler.decls.push(decl.clone());\n          }\n        }\n      };\n    }\n\n    handle!(self.important_declarations, important_handler, true);\n    handle!(self.declarations, handler, false);\n\n    handler.finalize(context);\n    important_handler.finalize(context);\n    self.important_declarations = std::mem::take(&mut important_handler.decls);\n    self.declarations = std::mem::take(&mut handler.decls);\n  }\n\n  /// Returns whether the declaration block is empty.\n  pub fn is_empty(&self) -> bool {\n    return self.declarations.is_empty() && self.important_declarations.is_empty();\n  }\n\n  pub(crate) fn property_location<'t>(\n    &self,\n    input: &mut Parser<'i, 't>,\n    index: usize,\n  ) -> Result<(Range<SourceLocation>, Range<SourceLocation>), ParseError<'i, ParserError<'i>>> {\n    // Skip to the requested property index.\n    for _ in 0..index {\n      input.expect_ident()?;\n      input.expect_colon()?;\n      input.parse_until_after(Delimiter::Semicolon, |parser| {\n        while parser.next().is_ok() {}\n        Ok(())\n      })?;\n    }\n\n    // Get property name range.\n    input.skip_whitespace();\n    let key_start = input.current_source_location();\n    input.expect_ident()?;\n    let key_end = input.current_source_location();\n    let key_range = key_start..key_end;\n\n    input.expect_colon()?;\n    input.skip_whitespace();\n\n    // Get value range.\n    let val_start = input.current_source_location();\n    input.parse_until_before(Delimiter::Semicolon, |parser| {\n      while parser.next().is_ok() {}\n      Ok(())\n    })?;\n    let val_end = input.current_source_location();\n    let val_range = val_start..val_end;\n\n    Ok((key_range, val_range))\n  }\n}\n\nimpl<'i> DeclarationBlock<'i> {\n  /// Returns an iterator over all properties in the declaration.\n  pub fn iter(&self) -> impl std::iter::DoubleEndedIterator<Item = (&Property<'i>, bool)> {\n    self\n      .declarations\n      .iter()\n      .map(|property| (property, false))\n      .chain(self.important_declarations.iter().map(|property| (property, true)))\n  }\n\n  /// Returns a mutable iterator over all properties in the declaration.\n  pub fn iter_mut(&mut self) -> impl std::iter::DoubleEndedIterator<Item = &mut Property<'i>> {\n    self.declarations.iter_mut().chain(self.important_declarations.iter_mut())\n  }\n\n  /// Returns the value for a given property id based on the properties in this declaration block.\n  ///\n  /// If the property is a shorthand, the result will be a combined value of all of the included\n  /// longhands, or `None` if some of the longhands are not declared. Otherwise, the value will be\n  /// either an explicitly declared longhand, or a value extracted from a shorthand property.\n  pub fn get<'a>(&'a self, property_id: &PropertyId) -> Option<(Cow<'a, Property<'i>>, bool)> {\n    if property_id.is_shorthand() {\n      if let Some((shorthand, important)) = property_id.shorthand_value(&self) {\n        return Some((Cow::Owned(shorthand), important));\n      }\n    } else {\n      for (property, important) in self.iter().rev() {\n        if property.property_id() == *property_id {\n          return Some((Cow::Borrowed(property), important));\n        }\n\n        if let Some(val) = property.longhand(&property_id) {\n          return Some((Cow::Owned(val), important));\n        }\n      }\n    }\n\n    None\n  }\n\n  /// Sets the value and importance for a given property, replacing any existing declarations.\n  ///\n  /// If the property already exists within the declaration block, it is updated in place. Otherwise,\n  /// a new declaration is appended. When updating a longhand property and a shorthand is defined which\n  /// includes the longhand, the shorthand will be updated rather than appending a new declaration.\n  pub fn set(&mut self, property: Property<'i>, important: bool) {\n    let property_id = property.property_id();\n    let declarations = if important {\n      // Remove any non-important properties with this id.\n      self.declarations.retain(|decl| decl.property_id() != property_id);\n      &mut self.important_declarations\n    } else {\n      // Remove any important properties with this id.\n      self.important_declarations.retain(|decl| decl.property_id() != property_id);\n      &mut self.declarations\n    };\n\n    let longhands = property_id.longhands().unwrap_or_else(|| vec![property.property_id()]);\n\n    for decl in declarations.iter_mut().rev() {\n      {\n        // If any of the longhands being set are in the same logical property group as any of the\n        // longhands in this property, but in a different category (i.e. logical or physical),\n        // then we cannot modify in place, and need to append a new property.\n        let id = decl.property_id();\n        let id_longhands = id.longhands().unwrap_or_else(|| vec![id]);\n        if longhands.iter().any(|longhand| {\n          let logical_group = longhand.logical_group();\n          let category = longhand.category();\n\n          logical_group.is_some()\n            && id_longhands.iter().any(|id_longhand| {\n              logical_group == id_longhand.logical_group() && category != id_longhand.category()\n            })\n        }) {\n          break;\n        }\n      }\n\n      if decl.property_id() == property_id {\n        *decl = property;\n        return;\n      }\n\n      // Update shorthand.\n      if decl.set_longhand(&property).is_ok() {\n        return;\n      }\n    }\n\n    declarations.push(property)\n  }\n\n  /// Removes all declarations of the given property id from the declaration block.\n  ///\n  /// When removing a longhand property and a shorthand is defined which includes the longhand,\n  /// the shorthand will be split apart into its component longhand properties, minus the property\n  /// to remove. When removing a shorthand, all included longhand properties are also removed.\n  pub fn remove(&mut self, property_id: &PropertyId) {\n    fn remove<'i, 'a>(declarations: &mut Vec<Property<'i>>, property_id: &PropertyId<'a>) {\n      let longhands = property_id.longhands().unwrap_or(vec![]);\n      let mut i = 0;\n      while i < declarations.len() {\n        let replacement = {\n          let property = &declarations[i];\n          let id = property.property_id();\n          if id == *property_id || longhands.contains(&id) {\n            // If the property matches the requested property id, or is a longhand\n            // property that is included in the requested shorthand, remove it.\n            None\n          } else if longhands.is_empty() && id.longhands().unwrap_or(vec![]).contains(&property_id) {\n            // If this is a shorthand property that includes the requested longhand,\n            // split it apart into its component longhands, excluding the requested one.\n            Some(\n              id.longhands()\n                .unwrap()\n                .iter()\n                .filter_map(|longhand| {\n                  if *longhand == *property_id {\n                    None\n                  } else {\n                    property.longhand(longhand)\n                  }\n                })\n                .collect::<Vec<Property>>(),\n            )\n          } else {\n            i += 1;\n            continue;\n          }\n        };\n\n        match replacement {\n          Some(properties) => {\n            let count = properties.len();\n            declarations.splice(i..i + 1, properties);\n            i += count;\n          }\n          None => {\n            declarations.remove(i);\n          }\n        }\n      }\n    }\n\n    remove(&mut self.declarations, property_id);\n    remove(&mut self.important_declarations, property_id);\n  }\n}\n\nstruct PropertyDeclarationParser<'a, 'o, 'i> {\n  important_declarations: &'a mut Vec<Property<'i>>,\n  declarations: &'a mut Vec<Property<'i>>,\n  options: &'a ParserOptions<'o, 'i>,\n}\n\n/// Parse a declaration within {} block: `color: blue`\nimpl<'a, 'o, 'i> cssparser::DeclarationParser<'i> for PropertyDeclarationParser<'a, 'o, 'i> {\n  type Declaration = ();\n  type Error = ParserError<'i>;\n\n  fn parse_value<'t>(\n    &mut self,\n    name: CowRcStr<'i>,\n    input: &mut cssparser::Parser<'i, 't>,\n  ) -> Result<Self::Declaration, cssparser::ParseError<'i, Self::Error>> {\n    parse_declaration(\n      name,\n      input,\n      &mut self.declarations,\n      &mut self.important_declarations,\n      &self.options,\n    )\n  }\n}\n\n/// Default methods reject all at rules.\nimpl<'a, 'o, 'i> AtRuleParser<'i> for PropertyDeclarationParser<'a, 'o, 'i> {\n  type Prelude = ();\n  type AtRule = ();\n  type Error = ParserError<'i>;\n}\n\nimpl<'a, 'o, 'i> QualifiedRuleParser<'i> for PropertyDeclarationParser<'a, 'o, 'i> {\n  type Prelude = ();\n  type QualifiedRule = ();\n  type Error = ParserError<'i>;\n}\n\nimpl<'a, 'o, 'i> RuleBodyItemParser<'i, (), ParserError<'i>> for PropertyDeclarationParser<'a, 'o, 'i> {\n  fn parse_qualified(&self) -> bool {\n    false\n  }\n\n  fn parse_declarations(&self) -> bool {\n    true\n  }\n}\n\npub(crate) fn parse_declaration<'i, 't>(\n  name: CowRcStr<'i>,\n  input: &mut cssparser::Parser<'i, 't>,\n  declarations: &mut DeclarationList<'i>,\n  important_declarations: &mut DeclarationList<'i>,\n  options: &ParserOptions<'_, 'i>,\n) -> Result<(), cssparser::ParseError<'i, ParserError<'i>>> {\n  // Stop if we hit a `{` token in a non-custom property to\n  // avoid ambiguity between nested rules and declarations.\n  // https://github.com/w3c/csswg-drafts/issues/9317\n  let property_id = PropertyId::from(CowArcStr::from(name));\n  let mut delimiters = Delimiter::Bang;\n  if !matches!(property_id, PropertyId::Custom(CustomPropertyName::Custom(..))) {\n    delimiters = delimiters | Delimiter::CurlyBracketBlock;\n  }\n  let property = input.parse_until_before(delimiters, |input| Property::parse(property_id, input, options))?;\n  let important = input\n    .try_parse(|input| {\n      input.expect_delim('!')?;\n      input.expect_ident_matching(\"important\")\n    })\n    .is_ok();\n  input.expect_exhausted()?;\n  if important {\n    important_declarations.push(property);\n  } else {\n    declarations.push(property);\n  }\n  Ok(())\n}\n\npub(crate) type DeclarationList<'i> = Vec<Property<'i>>;\n\n#[derive(Default)]\npub(crate) struct DeclarationHandler<'i> {\n  background: BackgroundHandler<'i>,\n  border: BorderHandler<'i>,\n  outline: OutlineHandler,\n  flex: FlexHandler,\n  grid: GridHandler<'i>,\n  align: AlignHandler,\n  size: SizeHandler,\n  margin: MarginHandler<'i>,\n  padding: PaddingHandler<'i>,\n  scroll_margin: ScrollMarginHandler<'i>,\n  scroll_padding: ScrollPaddingHandler<'i>,\n  font: FontHandler<'i>,\n  text: TextDecorationHandler<'i>,\n  list: ListStyleHandler<'i>,\n  transition: TransitionHandler<'i>,\n  animation: AnimationHandler<'i>,\n  display: DisplayHandler<'i>,\n  position: PositionHandler,\n  inset: InsetHandler<'i>,\n  overflow: OverflowHandler,\n  transform: TransformHandler,\n  box_shadow: BoxShadowHandler,\n  mask: MaskHandler<'i>,\n  container: ContainerHandler<'i>,\n  color_scheme: ColorSchemeHandler,\n  fallback: FallbackHandler,\n  prefix: PrefixHandler,\n  direction: Option<Direction>,\n  unicode_bidi: Option<UnicodeBidi>,\n  custom_properties: IndexMap<DashedIdent<'i>, usize>,\n  decls: DeclarationList<'i>,\n}\n\nimpl<'i> DeclarationHandler<'i> {\n  pub fn handle_property(\n    &mut self,\n    property: &Property<'i>,\n    context: &mut PropertyHandlerContext<'i, '_>,\n  ) -> bool {\n    self.background.handle_property(property, &mut self.decls, context)\n      || self.border.handle_property(property, &mut self.decls, context)\n      || self.outline.handle_property(property, &mut self.decls, context)\n      || self.flex.handle_property(property, &mut self.decls, context)\n      || self.grid.handle_property(property, &mut self.decls, context)\n      || self.align.handle_property(property, &mut self.decls, context)\n      || self.size.handle_property(property, &mut self.decls, context)\n      || self.margin.handle_property(property, &mut self.decls, context)\n      || self.padding.handle_property(property, &mut self.decls, context)\n      || self.scroll_margin.handle_property(property, &mut self.decls, context)\n      || self.scroll_padding.handle_property(property, &mut self.decls, context)\n      || self.font.handle_property(property, &mut self.decls, context)\n      || self.text.handle_property(property, &mut self.decls, context)\n      || self.list.handle_property(property, &mut self.decls, context)\n      || self.transition.handle_property(property, &mut self.decls, context)\n      || self.animation.handle_property(property, &mut self.decls, context)\n      || self.display.handle_property(property, &mut self.decls, context)\n      || self.position.handle_property(property, &mut self.decls, context)\n      || self.inset.handle_property(property, &mut self.decls, context)\n      || self.overflow.handle_property(property, &mut self.decls, context)\n      || self.transform.handle_property(property, &mut self.decls, context)\n      || self.box_shadow.handle_property(property, &mut self.decls, context)\n      || self.mask.handle_property(property, &mut self.decls, context)\n      || self.container.handle_property(property, &mut self.decls, context)\n      || self.color_scheme.handle_property(property, &mut self.decls, context)\n      || self.fallback.handle_property(property, &mut self.decls, context)\n      || self.prefix.handle_property(property, &mut self.decls, context)\n      || self.handle_all(property)\n      || self.handle_custom_property(property, context)\n  }\n\n  fn handle_custom_property(\n    &mut self,\n    property: &Property<'i>,\n    context: &mut PropertyHandlerContext<'i, '_>,\n  ) -> bool {\n    if let Property::Custom(custom) = property {\n      if context.unused_symbols.contains(custom.name.as_ref()) {\n        return true;\n      }\n\n      if let CustomPropertyName::Custom(name) = &custom.name {\n        if let Some(index) = self.custom_properties.get(name) {\n          if self.decls[*index] == *property {\n            return true;\n          }\n          let mut custom = custom.clone();\n          self.add_conditional_fallbacks(&mut custom, context);\n          self.decls[*index] = Property::Custom(custom);\n        } else {\n          self.custom_properties.insert(name.clone(), self.decls.len());\n          let mut custom = custom.clone();\n          self.add_conditional_fallbacks(&mut custom, context);\n          self.decls.push(Property::Custom(custom));\n        }\n\n        return true;\n      }\n    }\n\n    false\n  }\n\n  fn handle_all(&mut self, property: &Property<'i>) -> bool {\n    // The `all` property resets all properies except `unicode-bidi`, `direction`, and custom properties.\n    // https://drafts.csswg.org/css-cascade-5/#all-shorthand\n    match property {\n      Property::UnicodeBidi(bidi) => {\n        self.unicode_bidi = Some(*bidi);\n        true\n      }\n      Property::Direction(direction) => {\n        self.direction = Some(*direction);\n        true\n      }\n      Property::All(keyword) => {\n        let mut handler = DeclarationHandler {\n          unicode_bidi: self.unicode_bidi.clone(),\n          direction: self.direction.clone(),\n          ..Default::default()\n        };\n        for (key, index) in self.custom_properties.drain(..) {\n          handler.custom_properties.insert(key, handler.decls.len());\n          handler.decls.push(self.decls[index].clone());\n        }\n        handler.decls.push(Property::All(keyword.clone()));\n        *self = handler;\n        true\n      }\n      _ => false,\n    }\n  }\n\n  fn add_conditional_fallbacks(\n    &self,\n    custom: &mut CustomProperty<'i>,\n    context: &mut PropertyHandlerContext<'i, '_>,\n  ) {\n    if context.context != DeclarationContext::Keyframes {\n      let fallbacks = custom.value.get_fallbacks(context.targets);\n      for (condition, fallback) in fallbacks {\n        context.add_conditional_property(\n          condition,\n          Property::Custom(CustomProperty {\n            name: custom.name.clone(),\n            value: fallback,\n          }),\n        );\n      }\n    }\n  }\n\n  pub fn finalize(&mut self, context: &mut PropertyHandlerContext<'i, '_>) {\n    if let Some(direction) = std::mem::take(&mut self.direction) {\n      self.decls.push(Property::Direction(direction));\n    }\n    if let Some(unicode_bidi) = std::mem::take(&mut self.unicode_bidi) {\n      self.decls.push(Property::UnicodeBidi(unicode_bidi));\n    }\n\n    self.background.finalize(&mut self.decls, context);\n    self.border.finalize(&mut self.decls, context);\n    self.outline.finalize(&mut self.decls, context);\n    self.flex.finalize(&mut self.decls, context);\n    self.grid.finalize(&mut self.decls, context);\n    self.align.finalize(&mut self.decls, context);\n    self.size.finalize(&mut self.decls, context);\n    self.margin.finalize(&mut self.decls, context);\n    self.padding.finalize(&mut self.decls, context);\n    self.scroll_margin.finalize(&mut self.decls, context);\n    self.scroll_padding.finalize(&mut self.decls, context);\n    self.font.finalize(&mut self.decls, context);\n    self.text.finalize(&mut self.decls, context);\n    self.list.finalize(&mut self.decls, context);\n    self.transition.finalize(&mut self.decls, context);\n    self.animation.finalize(&mut self.decls, context);\n    self.display.finalize(&mut self.decls, context);\n    self.position.finalize(&mut self.decls, context);\n    self.inset.finalize(&mut self.decls, context);\n    self.overflow.finalize(&mut self.decls, context);\n    self.transform.finalize(&mut self.decls, context);\n    self.box_shadow.finalize(&mut self.decls, context);\n    self.mask.finalize(&mut self.decls, context);\n    self.container.finalize(&mut self.decls, context);\n    self.color_scheme.finalize(&mut self.decls, context);\n    self.fallback.finalize(&mut self.decls, context);\n    self.prefix.finalize(&mut self.decls, context);\n    self.custom_properties.clear();\n  }\n}\n"
  },
  {
    "path": "src/dependencies.rs",
    "content": "//! Dependency analysis.\n//!\n//! Dependencies in CSS can be analyzed using the `analyze_dependencies` option\n//! when printing a style sheet. These include other style sheets referenved via\n//! the `@import` rule, as well as `url()` references. See [PrinterOptions](PrinterOptions).\n//!\n//! When dependency analysis is enabled, `@import` rules are removed, and `url()`\n//! dependencies are replaced with hashed placeholders that can be substituted with\n//! the final urls later (e.g. after bundling and content hashing).\n\nuse crate::css_modules::hash;\nuse crate::printer::PrinterOptions;\nuse crate::rules::import::ImportRule;\nuse crate::traits::ToCss;\nuse crate::values::url::Url;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse cssparser::SourceLocation;\n#[cfg(any(feature = \"serde\", feature = \"nodejs\"))]\nuse serde::Serialize;\n\n/// Options for `analyze_dependencies` in `PrinterOptions`.\n#[derive(Default)]\npub struct DependencyOptions {\n  /// Whether to remove `@import` rules.\n  pub remove_imports: bool,\n}\n\n/// A dependency.\n#[derive(Debug)]\n#[cfg_attr(any(feature = \"serde\", feature = \"nodejs\"), derive(Serialize))]\n#[cfg_attr(\n  any(feature = \"serde\", feature = \"nodejs\"),\n  serde(tag = \"type\", rename_all = \"lowercase\")\n)]\npub enum Dependency {\n  /// An `@import` dependency.\n  Import(ImportDependency),\n  /// A `url()` dependency.\n  Url(UrlDependency),\n}\n\n/// An `@import` dependency.\n#[derive(Debug)]\n#[cfg_attr(any(feature = \"serde\", feature = \"nodejs\"), derive(Serialize))]\npub struct ImportDependency {\n  /// The url to import.\n  pub url: String,\n  /// The placeholder that the URL was replaced with.\n  pub placeholder: String,\n  /// An optional `supports()` condition.\n  pub supports: Option<String>,\n  /// A media query.\n  pub media: Option<String>,\n  /// The location of the dependency in the source file.\n  pub loc: SourceRange,\n}\n\nimpl ImportDependency {\n  /// Creates a new dependency from an `@import` rule.\n  pub fn new(rule: &ImportRule, filename: &str) -> ImportDependency {\n    let supports = if let Some(supports) = &rule.supports {\n      let s = supports.to_css_string(PrinterOptions::default()).unwrap();\n      Some(s)\n    } else {\n      None\n    };\n\n    let media = if !rule.media.media_queries.is_empty() {\n      let s = rule.media.to_css_string(PrinterOptions::default()).unwrap();\n      Some(s)\n    } else {\n      None\n    };\n\n    let placeholder = hash(&format!(\"{}_{}\", filename, rule.url), false);\n\n    ImportDependency {\n      url: rule.url.as_ref().to_owned(),\n      placeholder,\n      supports,\n      media,\n      loc: SourceRange::new(\n        filename,\n        Location {\n          line: rule.loc.line + 1,\n          column: rule.loc.column,\n        },\n        8,\n        rule.url.len() + 2,\n      ), // TODO: what about @import url(...)?\n    }\n  }\n}\n\n/// A `url()` dependency.\n#[derive(Debug)]\n#[cfg_attr(any(feature = \"serde\", feature = \"nodejs\"), derive(Serialize))]\npub struct UrlDependency {\n  /// The url of the dependency.\n  pub url: String,\n  /// The placeholder that the URL was replaced with.\n  pub placeholder: String,\n  /// The location of the dependency in the source file.\n  pub loc: SourceRange,\n}\n\nimpl UrlDependency {\n  /// Creates a new url dependency.\n  pub fn new(url: &Url, filename: &str) -> UrlDependency {\n    let placeholder = hash(&format!(\"{}_{}\", filename, url.url), false);\n    UrlDependency {\n      url: url.url.to_string(),\n      placeholder,\n      loc: SourceRange::new(filename, url.loc, 4, url.url.len()),\n    }\n  }\n}\n\n/// Represents the range of source code where a dependency was found.\n#[derive(Debug)]\n#[cfg_attr(any(feature = \"serde\", feature = \"nodejs\"), derive(Serialize))]\n#[cfg_attr(any(feature = \"serde\", feature = \"nodejs\"), serde(rename_all = \"camelCase\"))]\npub struct SourceRange {\n  /// The filename in which the dependency was found.\n  pub file_path: String,\n  /// The starting line and column position of the dependency.\n  pub start: Location,\n  /// The ending line and column position of the dependency.\n  pub end: Location,\n}\n\n/// A line and column position within a source file.\n#[derive(Debug, Clone, Copy, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(any(feature = \"serde\", feature = \"nodejs\"), derive(serde::Serialize))]\n#[cfg_attr(any(feature = \"serde\"), derive(serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub struct Location {\n  /// The line number, starting from 1.\n  pub line: u32,\n  /// The column number, starting from 1.\n  pub column: u32,\n}\n\nimpl From<SourceLocation> for Location {\n  fn from(loc: SourceLocation) -> Location {\n    Location {\n      line: loc.line + 1,\n      column: loc.column,\n    }\n  }\n}\n\nimpl SourceRange {\n  fn new(filename: &str, loc: Location, offset: u32, len: usize) -> SourceRange {\n    SourceRange {\n      file_path: filename.into(),\n      start: Location {\n        line: loc.line,\n        column: loc.column + offset,\n      },\n      end: Location {\n        line: loc.line,\n        column: loc.column + offset + (len as u32) - 1,\n      },\n    }\n  }\n}\n"
  },
  {
    "path": "src/error.rs",
    "content": "//! Error types.\n\nuse crate::properties::custom::Token;\nuse crate::rules::Location;\nuse crate::values::string::CowArcStr;\nuse cssparser::{BasicParseErrorKind, ParseError, ParseErrorKind};\nuse parcel_selectors::parser::SelectorParseErrorKind;\n#[cfg(any(feature = \"serde\", feature = \"nodejs\"))]\nuse serde::Serialize;\n#[cfg(feature = \"into_owned\")]\nuse static_self::IntoOwned;\nuse std::fmt;\n\n/// An error with a source location.\n#[derive(Debug, PartialEq, Clone)]\n#[cfg_attr(any(feature = \"serde\", feature = \"nodejs\"), derive(serde::Serialize))]\n#[cfg_attr(any(feature = \"serde\"), derive(serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct Error<T> {\n  /// The type of error that occurred.\n  pub kind: T,\n  /// The location where the error occurred.\n  pub loc: Option<ErrorLocation>,\n}\n\nimpl<T: fmt::Display> fmt::Display for Error<T> {\n  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n    self.kind.fmt(f)?;\n    if let Some(loc) = &self.loc {\n      write!(f, \" at {}\", loc)?;\n    }\n    Ok(())\n  }\n}\n\nimpl<T: fmt::Display + fmt::Debug> std::error::Error for Error<T> {}\n\n/// A line and column location within a source file.\n#[derive(Debug, PartialEq, Clone)]\n#[cfg_attr(any(feature = \"serde\", feature = \"nodejs\"), derive(serde::Serialize))]\n#[cfg_attr(any(feature = \"serde\"), derive(serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct ErrorLocation {\n  /// The filename in which the error occurred.\n  pub filename: String,\n  /// The line number, starting from 0.\n  pub line: u32,\n  /// The column number, starting from 1.\n  pub column: u32,\n}\n\nimpl ErrorLocation {\n  /// Create a new error location from a source location and filename.\n  pub fn new(loc: Location, filename: String) -> Self {\n    ErrorLocation {\n      filename,\n      line: loc.line,\n      column: loc.column,\n    }\n  }\n}\n\nimpl fmt::Display for ErrorLocation {\n  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n    write!(f, \"{}:{}:{}\", self.filename, self.line, self.column)\n  }\n}\n\n/// A parser error.\n#[derive(Debug, PartialEq, Clone)]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(any(feature = \"serde\", feature = \"nodejs\"), derive(Serialize))]\n#[cfg_attr(any(feature = \"serde\", feature = \"nodejs\"), serde(tag = \"type\", content = \"value\"))]\npub enum ParserError<'i> {\n  /// An at rule body was invalid.\n  AtRuleBodyInvalid,\n  /// An at rule prelude was invalid\n  AtRulePreludeInvalid,\n  /// An unknown or unsupported at rule was encountered.\n  AtRuleInvalid(CowArcStr<'i>),\n  /// Unexpectedly encountered the end of input data.\n  EndOfInput,\n  /// A declaration was invalid.\n  InvalidDeclaration,\n  /// A media query was invalid.\n  InvalidMediaQuery,\n  /// The brackets in a condition cannot be empty.\n  EmptyBracketInCondition,\n  /// Invalid CSS nesting.\n  InvalidNesting,\n  /// The @nest rule is deprecated.\n  DeprecatedNestRule,\n  /// The @value rule (of CSS modules) is deprecated.\n  DeprecatedCssModulesValueRule,\n  /// An invalid selector in an `@page` rule.\n  InvalidPageSelector,\n  /// An invalid value was encountered.\n  InvalidValue,\n  /// Invalid qualified rule.\n  QualifiedRuleInvalid,\n  /// A selector was invalid.\n  SelectorError(SelectorError<'i>),\n  /// An `@import` rule was encountered after any rule besides `@charset` or `@layer`.\n  UnexpectedImportRule,\n  /// A `@namespace` rule was encountered after any rules besides `@charset`, `@import`, or `@layer`.\n  UnexpectedNamespaceRule,\n  /// An unexpected token was encountered.\n  UnexpectedToken(#[cfg_attr(any(feature = \"serde\", feature = \"nodejs\"), serde(skip))] Token<'i>),\n  /// Maximum nesting depth was reached.\n  MaximumNestingDepth,\n}\n\nimpl<'i> fmt::Display for ParserError<'i> {\n  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n    use ParserError::*;\n    match self {\n      AtRuleBodyInvalid => write!(f, \"Invalid @ rule body\"),\n      AtRulePreludeInvalid => write!(f, \"Invalid @ rule prelude\"),\n      AtRuleInvalid(name) => write!(f, \"Unknown at rule: @{}\", name),\n      EndOfInput => write!(f, \"Unexpected end of input\"),\n      InvalidDeclaration => write!(f, \"Invalid declaration\"),\n      InvalidMediaQuery => write!(f, \"Invalid media query\"),\n      EmptyBracketInCondition => write!(f, \"The brackets cannot be empty\"),\n      InvalidNesting => write!(f, \"Invalid nesting\"),\n      DeprecatedNestRule => write!(f, \"The @nest rule is deprecated\"),\n      DeprecatedCssModulesValueRule => write!(f, \"The @value rule is deprecated\"),\n      InvalidPageSelector => write!(f, \"Invalid page selector\"),\n      InvalidValue => write!(f, \"Invalid value\"),\n      QualifiedRuleInvalid => write!(f, \"Invalid qualified rule\"),\n      SelectorError(s) => s.fmt(f),\n      UnexpectedImportRule => write!(\n        f,\n        \"@import rules must precede all rules aside from @charset and @layer statements\"\n      ),\n      UnexpectedNamespaceRule => write!(\n        f,\n        \"@namespaces rules must precede all rules aside from @charset, @import, and @layer statements\"\n      ),\n      UnexpectedToken(token) => write!(f, \"Unexpected token {:?}\", token),\n      MaximumNestingDepth => write!(f, \"Overflowed the maximum nesting depth\"),\n    }\n  }\n}\n\nimpl<'i> Error<ParserError<'i>> {\n  /// Creates an error from a cssparser error.\n  pub fn from(err: ParseError<'i, ParserError<'i>>, filename: String) -> Error<ParserError<'i>> {\n    let kind = match err.kind {\n      ParseErrorKind::Basic(b) => match &b {\n        BasicParseErrorKind::UnexpectedToken(t) => ParserError::UnexpectedToken(t.into()),\n        BasicParseErrorKind::EndOfInput => ParserError::EndOfInput,\n        BasicParseErrorKind::AtRuleInvalid(a) => ParserError::AtRuleInvalid(a.into()),\n        BasicParseErrorKind::AtRuleBodyInvalid => ParserError::AtRuleBodyInvalid,\n        BasicParseErrorKind::QualifiedRuleInvalid => ParserError::QualifiedRuleInvalid,\n      },\n      ParseErrorKind::Custom(c) => c,\n    };\n\n    Error {\n      kind,\n      loc: Some(ErrorLocation {\n        filename,\n        line: err.location.line,\n        column: err.location.column,\n      }),\n    }\n  }\n\n  /// Consumes the value and returns an owned clone.\n  #[cfg(feature = \"into_owned\")]\n  pub fn into_owned<'x>(self) -> Error<ParserError<'static>> {\n    Error {\n      kind: self.kind.into_owned(),\n      loc: self.loc,\n    }\n  }\n}\n\nimpl<'i> From<SelectorParseErrorKind<'i>> for ParserError<'i> {\n  fn from(err: SelectorParseErrorKind<'i>) -> ParserError<'i> {\n    ParserError::SelectorError(err.into())\n  }\n}\n\nimpl<'i> ParserError<'i> {\n  #[deprecated(note = \"use `ParserError::to_string()` or `fmt::Display` instead\")]\n  #[allow(missing_docs)]\n  pub fn reason(&self) -> String {\n    self.to_string()\n  }\n}\n\n/// A selector parsing error.\n#[derive(Debug, PartialEq, Clone)]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(any(feature = \"serde\", feature = \"nodejs\"), derive(Serialize))]\n#[cfg_attr(any(feature = \"serde\", feature = \"nodejs\"), serde(tag = \"type\", content = \"value\"))]\npub enum SelectorError<'i> {\n  /// An unexpected token was found in an attribute selector.\n  BadValueInAttr(#[cfg_attr(any(feature = \"serde\", feature = \"nodejs\"), serde(skip))] Token<'i>),\n  /// An unexpected token was found in a class selector.\n  ClassNeedsIdent(#[cfg_attr(any(feature = \"serde\", feature = \"nodejs\"), serde(skip))] Token<'i>),\n  /// A dangling combinator was found.\n  DanglingCombinator,\n  /// An empty selector.\n  EmptySelector,\n  /// A `|` was expected in an attribute selector.\n  ExpectedBarInAttr(#[cfg_attr(any(feature = \"serde\", feature = \"nodejs\"), serde(skip))] Token<'i>),\n  /// A namespace was expected.\n  ExpectedNamespace(CowArcStr<'i>),\n  /// An unexpected token was encountered in a namespace.\n  ExplicitNamespaceUnexpectedToken(#[cfg_attr(any(feature = \"serde\", feature = \"nodejs\"), serde(skip))] Token<'i>),\n  /// An invalid pseudo class was encountered after a pseudo element.\n  InvalidPseudoClassAfterPseudoElement,\n  /// An invalid pseudo class was encountered after a `-webkit-scrollbar` pseudo element.\n  InvalidPseudoClassAfterWebKitScrollbar,\n  /// A `-webkit-scrollbar` state was encountered before a `-webkit-scrollbar` pseudo element.\n  InvalidPseudoClassBeforeWebKitScrollbar,\n  /// Invalid qualified name in attribute selector.\n  InvalidQualNameInAttr(#[cfg_attr(any(feature = \"serde\", feature = \"nodejs\"), serde(skip))] Token<'i>),\n  /// The current token is not allowed in this state.\n  InvalidState,\n  /// The selector is required to have the `&` nesting selector at the start.\n  MissingNestingPrefix,\n  /// The selector is missing a `&` nesting selector.\n  MissingNestingSelector,\n  /// No qualified name in attribute selector.\n  NoQualifiedNameInAttributeSelector(\n    #[cfg_attr(any(feature = \"serde\", feature = \"nodejs\"), serde(skip))] Token<'i>,\n  ),\n  /// An Invalid token was encountered in a pseudo element.\n  PseudoElementExpectedIdent(#[cfg_attr(any(feature = \"serde\", feature = \"nodejs\"), serde(skip))] Token<'i>),\n  /// An unexpected identifier was encountered.\n  UnexpectedIdent(CowArcStr<'i>),\n  /// An unexpected token was encountered inside an attribute selector.\n  UnexpectedTokenInAttributeSelector(\n    #[cfg_attr(any(feature = \"serde\", feature = \"nodejs\"), serde(skip))] Token<'i>,\n  ),\n\n  /// An unsupported pseudo class was encountered.\n  UnsupportedPseudoClass(CowArcStr<'i>),\n\n  /// An unsupported pseudo element was encountered.\n  UnsupportedPseudoElement(CowArcStr<'i>),\n\n  /// Ambiguous CSS module class.\n  AmbiguousCssModuleClass(CowArcStr<'i>),\n\n  /// An unexpected token was encountered after a pseudo element.\n  UnexpectedSelectorAfterPseudoElement(\n    #[cfg_attr(any(feature = \"serde\", feature = \"nodejs\"), serde(skip))] Token<'i>,\n  ),\n}\n\nimpl<'i> fmt::Display for SelectorError<'i> {\n  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n    use SelectorError::*;\n    match self {\n      InvalidState => write!(f, \"Invalid state\"),\n      BadValueInAttr(token) => write!(f, \"Invalid value in attribute selector: {:?}\", token),\n      ClassNeedsIdent(token) => write!(f, \"Expected identifier in class selector, got {:?}\", token),\n      DanglingCombinator => write!(f, \"Invalid dangling combinator in selector\"),\n      EmptySelector => write!(f, \"Invalid empty selector\"),\n      ExpectedBarInAttr(name) => write!(f, \"Expected | in attribute, got {:?}\", name),\n      ExpectedNamespace(name) => write!(f, \"Expected namespace: {}\", name),\n      ExplicitNamespaceUnexpectedToken(token) => write!(f, \"Unexpected token in namespace selector: {:?}\", token),\n      InvalidPseudoClassAfterPseudoElement => write!(f, \"Invalid pseudo class after pseudo element, only user action pseudo classes (e.g. :hover, :active) are allowed\"),\n      InvalidPseudoClassAfterWebKitScrollbar => write!(f, \"Invalid pseudo class after ::-webkit-scrollbar pseudo element\"),\n      InvalidPseudoClassBeforeWebKitScrollbar => write!(f, \"Pseudo class must be prefixed by a ::-webkit-scrollbar pseudo element\"),\n      InvalidQualNameInAttr(token) => write!(f, \"Invalid qualified name in attribute selector: {:?}\", token),\n      MissingNestingPrefix => write!(f, \"A nested rule must start with a nesting selector (&) as prefix of each selector, or start with @nest\"),\n      MissingNestingSelector => write!(f, \"A nesting selector (&) is required in each selector of a @nest rule\"),\n      NoQualifiedNameInAttributeSelector(token) => write!(f, \"No qualified name in attribute selector: {:?}.\", token),\n      PseudoElementExpectedIdent(token) => write!(f, \"Invalid token in pseudo element: {:?}\", token),\n      UnexpectedIdent(name) => write!(f, \"Unexpected identifier: {}\", name),\n      UnexpectedTokenInAttributeSelector(token) => write!(f, \"Unexpected token in attribute selector: {:?}\", token),\n      UnsupportedPseudoClass(name) =>write!(f, \"'{name}' is not recognized as a valid pseudo-class. Did you mean '::{name}' (pseudo-element) or is this a typo?\"),\n      UnsupportedPseudoElement(name) => write!(f, \"'{name}' is not recognized as a valid pseudo-element. Did you mean ':{name}' (pseudo-class) or is this a typo?\"),\n      AmbiguousCssModuleClass(_) => write!(f, \"Ambiguous CSS module class not supported\"),\n      UnexpectedSelectorAfterPseudoElement(token) => {\n        write!(\n          f,\n          \"Pseudo-elements like '::before' or '::after' can't be followed by selectors like '{token:?}'\"\n        )\n      },\n    }\n  }\n}\n\nimpl<'i> From<SelectorParseErrorKind<'i>> for SelectorError<'i> {\n  fn from(err: SelectorParseErrorKind<'i>) -> Self {\n    match &err {\n      SelectorParseErrorKind::NoQualifiedNameInAttributeSelector(t) => {\n        SelectorError::NoQualifiedNameInAttributeSelector(t.into())\n      }\n      SelectorParseErrorKind::EmptySelector => SelectorError::EmptySelector,\n      SelectorParseErrorKind::DanglingCombinator => SelectorError::DanglingCombinator,\n      SelectorParseErrorKind::InvalidPseudoClassBeforeWebKitScrollbar => {\n        SelectorError::InvalidPseudoClassBeforeWebKitScrollbar\n      }\n      SelectorParseErrorKind::InvalidPseudoClassAfterWebKitScrollbar => {\n        SelectorError::InvalidPseudoClassAfterWebKitScrollbar\n      }\n      SelectorParseErrorKind::InvalidPseudoClassAfterPseudoElement => {\n        SelectorError::InvalidPseudoClassAfterPseudoElement\n      }\n      SelectorParseErrorKind::InvalidState => SelectorError::InvalidState,\n      SelectorParseErrorKind::MissingNestingSelector => SelectorError::MissingNestingSelector,\n      SelectorParseErrorKind::MissingNestingPrefix => SelectorError::MissingNestingPrefix,\n      SelectorParseErrorKind::UnexpectedTokenInAttributeSelector(t) => {\n        SelectorError::UnexpectedTokenInAttributeSelector(t.into())\n      }\n      SelectorParseErrorKind::PseudoElementExpectedIdent(t) => SelectorError::PseudoElementExpectedIdent(t.into()),\n      SelectorParseErrorKind::UnsupportedPseudoClass(t) => SelectorError::UnsupportedPseudoClass(t.into()),\n      SelectorParseErrorKind::UnsupportedPseudoElement(t) => SelectorError::UnsupportedPseudoElement(t.into()),\n      SelectorParseErrorKind::UnexpectedIdent(t) => SelectorError::UnexpectedIdent(t.into()),\n      SelectorParseErrorKind::ExpectedNamespace(t) => SelectorError::ExpectedNamespace(t.into()),\n      SelectorParseErrorKind::ExpectedBarInAttr(t) => SelectorError::ExpectedBarInAttr(t.into()),\n      SelectorParseErrorKind::BadValueInAttr(t) => SelectorError::BadValueInAttr(t.into()),\n      SelectorParseErrorKind::InvalidQualNameInAttr(t) => SelectorError::InvalidQualNameInAttr(t.into()),\n      SelectorParseErrorKind::ExplicitNamespaceUnexpectedToken(t) => {\n        SelectorError::ExplicitNamespaceUnexpectedToken(t.into())\n      }\n      SelectorParseErrorKind::ClassNeedsIdent(t) => SelectorError::ClassNeedsIdent(t.into()),\n      SelectorParseErrorKind::AmbiguousCssModuleClass(name) => SelectorError::AmbiguousCssModuleClass(name.into()),\n      SelectorParseErrorKind::UnexpectedSelectorAfterPseudoElement(t) => {\n        SelectorError::UnexpectedSelectorAfterPseudoElement(t.into())\n      }\n    }\n  }\n}\n\n#[derive(Debug, PartialEq)]\npub(crate) struct ErrorWithLocation<T> {\n  pub kind: T,\n  pub loc: Location,\n}\n\nimpl<T: fmt::Display> fmt::Display for ErrorWithLocation<T> {\n  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n    self.kind.fmt(f)\n  }\n}\n\nimpl<T: fmt::Display + fmt::Debug> std::error::Error for ErrorWithLocation<T> {}\n\npub(crate) type MinifyError = ErrorWithLocation<MinifyErrorKind>;\n\n/// A transformation error.\n#[derive(Debug, PartialEq)]\n#[cfg_attr(any(feature = \"serde\", feature = \"nodejs\"), derive(Serialize))]\n#[cfg_attr(any(feature = \"serde\", feature = \"nodejs\"), serde(tag = \"type\"))]\npub enum MinifyErrorKind {\n  /// A circular `@custom-media` rule was detected.\n  CircularCustomMedia {\n    /// The name of the `@custom-media` rule that was referenced circularly.\n    name: String,\n  },\n  /// Attempted to reference a custom media rule that doesn't exist.\n  CustomMediaNotDefined {\n    /// The name of the `@custom-media` rule that was not defined.\n    name: String,\n  },\n  /// Boolean logic with media types in @custom-media rules is not supported.\n  UnsupportedCustomMediaBooleanLogic {\n    /// The source location of the `@custom-media` rule with unsupported boolean logic.\n    custom_media_loc: Location,\n  },\n  /// A CSS module selector did not contain at least one class or id selector.\n  ImpureCSSModuleSelector,\n}\n\nimpl fmt::Display for MinifyErrorKind {\n  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n    use MinifyErrorKind::*;\n    match self {\n      CircularCustomMedia { name } => write!(f, \"Circular custom media query {} detected\", name),\n      CustomMediaNotDefined { name } => write!(f, \"Custom media query {} is not defined\", name),\n      UnsupportedCustomMediaBooleanLogic { .. } => write!(\n        f,\n        \"Boolean logic with media types in @custom-media rules is not supported by Lightning CSS\"\n      ),\n      ImpureCSSModuleSelector => write!(\n        f,\n        \"A selector in CSS modules should contain at least one class or ID selector\"\n      ),\n    }\n  }\n}\n\nimpl MinifyErrorKind {\n  #[deprecated(note = \"use `MinifyErrorKind::to_string()` or `fmt::Display` instead\")]\n  #[allow(missing_docs)]\n  pub fn reason(&self) -> String {\n    self.to_string()\n  }\n}\n\n/// A printer error.\npub type PrinterError = Error<PrinterErrorKind>;\n\n/// A printer error type.\n#[derive(Debug, PartialEq)]\n#[cfg_attr(any(feature = \"serde\", feature = \"nodejs\"), derive(Serialize))]\n#[cfg_attr(any(feature = \"serde\", feature = \"nodejs\"), serde(tag = \"type\"))]\npub enum PrinterErrorKind {\n  /// An ambiguous relative `url()` was encountered in a custom property declaration.\n  AmbiguousUrlInCustomProperty {\n    /// The ambiguous URL.\n    url: String,\n  },\n  /// A [std::fmt::Error](std::fmt::Error) was encountered in the underlying destination.\n  FmtError,\n  /// The CSS modules `composes` property cannot be used within nested rules.\n  InvalidComposesNesting,\n  /// The CSS modules `composes` property can only be used with a simple class selector.\n  InvalidComposesSelector,\n  /// The CSS modules pattern must end with `[local]` for use in CSS grid.\n  InvalidCssModulesPatternInGrid,\n}\n\nimpl From<fmt::Error> for PrinterError {\n  fn from(_: fmt::Error) -> PrinterError {\n    PrinterError {\n      kind: PrinterErrorKind::FmtError,\n      loc: None,\n    }\n  }\n}\n\nimpl fmt::Display for PrinterErrorKind {\n  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n    use PrinterErrorKind::*;\n    match self {\n      AmbiguousUrlInCustomProperty { url } => write!(f, \"Ambiguous url('{}') in custom property. Relative paths are resolved from the location the var() is used, not where the custom property is defined. Use an absolute URL instead\", url),\n      FmtError => write!(f, \"Printer error\"),\n      InvalidComposesNesting => write!(f, \"The `composes` property cannot be used within nested rules\"),\n      InvalidComposesSelector => write!(f, \"The `composes` property cannot be used with a simple class selector\"),\n      InvalidCssModulesPatternInGrid => write!(f, \"The CSS modules `pattern` config must end with `[local]` for use in CSS grid line names.\"),\n    }\n  }\n}\n\nimpl PrinterErrorKind {\n  #[deprecated(note = \"use `PrinterErrorKind::to_string()` or `fmt::Display` instead\")]\n  #[allow(missing_docs)]\n  pub fn reason(&self) -> String {\n    self.to_string()\n  }\n}\n"
  },
  {
    "path": "src/lib.rs",
    "content": "//! Lightning CSS is a CSS parser, transformer, and minifier based on the\n//! [cssparser](https://github.com/servo/rust-cssparser) crate used in Firefox.\n//! It supports fully parsing all CSS rules, properties, and values into normalized\n//! structures exactly how a browser would. Once parsed, the CSS can be transformed\n//! to add or remove vendor prefixes, or lower syntax for older browsers as appropriate.\n//! The style sheet can also be minified to merge longhand properties into shorthands,\n//! merge adjacent rules, reduce `calc()` expressions, and more. Finally, the style sheet\n//! can be printed back to CSS syntax, either minified to remove whitespace and compress\n//! the output as much as possible, or pretty printed.\n//!\n//! The [StyleSheet](stylesheet::StyleSheet) struct is the main entrypoint for Lightning CSS,\n//! and supports parsing and transforming entire CSS files. You can also parse and manipulate\n//! individual CSS [rules](rules), [properties](properties), or [values](values). The [bundler](bundler)\n//! module also can be used to combine a CSS file and all of its dependencies together into a single\n//! style sheet. See the individual module documentation for more details and examples.\n\n#![deny(missing_docs)]\n#![cfg_attr(docsrs, feature(doc_cfg))]\n\n#[cfg(feature = \"bundler\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"bundler\")))]\npub mod bundler;\nmod compat;\nmod context;\npub mod css_modules;\npub mod declaration;\npub mod dependencies;\npub mod error;\nmod logical;\nmod macros;\npub mod media_query;\nmod parser;\nmod prefixes;\npub mod printer;\npub mod properties;\npub mod rules;\npub mod selector;\npub mod stylesheet;\npub mod targets;\npub mod traits;\npub mod values;\npub mod vendor_prefix;\n#[cfg(feature = \"visitor\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"visitor\")))]\npub mod visitor;\n\n#[cfg(feature = \"serde\")]\nmod serialization;\n\n#[cfg(test)]\nmod test_helpers;\n\n#[cfg(test)]\nmod tests {\n  use crate::css_modules::{CssModuleExport, CssModuleExports, CssModuleReference, CssModuleReferences};\n  use crate::dependencies::Dependency;\n  use crate::error::{Error, ErrorLocation, MinifyErrorKind, ParserError, PrinterErrorKind, SelectorError};\n  use crate::parser::ParserFlags;\n  use crate::properties::custom::Token;\n  use crate::properties::Property;\n  use crate::rules::CssRule;\n  use crate::rules::Location;\n  use crate::stylesheet::*;\n  use crate::test_helpers::panic_with_test_error;\n  use crate::targets::{Browsers, Features, Targets};\n  use crate::traits::{Parse, ToCss};\n  use crate::values::color::CssColor;\n  use crate::vendor_prefix::VendorPrefix;\n  use cssparser::SourceLocation;\n  use indoc::indoc;\n  use pretty_assertions::assert_eq;\n  use std::collections::HashMap;\n  use std::sync::{Arc, RwLock};\n\n  #[track_caller]\n  fn test(source: &str, expected: &str) {\n    test_with_options(source, expected, ParserOptions::default())\n  }\n\n  #[track_caller]\n  fn test_with_options<'i, 'o>(source: &'i str, expected: &'i str, options: ParserOptions<'o, 'i>) {\n    let mut stylesheet = match StyleSheet::parse(&source, options) {\n      Ok(stylesheet) => stylesheet,\n      Err(e) => panic_with_test_error(\"test_with_options\", \"parse\", source, e),\n    };\n    if let Err(e) = stylesheet.minify(MinifyOptions::default()) {\n      panic_with_test_error(\"test_with_options\", \"minify\", source, e);\n    }\n    let res = match stylesheet.to_css(PrinterOptions::default()) {\n      Ok(res) => res,\n      Err(e) => panic_with_test_error(\"test_with_options\", \"print\", source, e),\n    };\n    assert_eq!(res.code, expected);\n  }\n\n  #[track_caller]\n  fn test_with_printer_options<'i, 'o>(source: &'i str, expected: &'i str, options: PrinterOptions<'o>) {\n    let mut stylesheet = match StyleSheet::parse(&source, ParserOptions::default()) {\n      Ok(stylesheet) => stylesheet,\n      Err(e) => panic_with_test_error(\"test_with_printer_options\", \"parse\", source, e),\n    };\n    if let Err(e) = stylesheet.minify(MinifyOptions::default()) {\n      panic_with_test_error(\"test_with_printer_options\", \"minify\", source, e);\n    }\n    let res = match stylesheet.to_css(options) {\n      Ok(res) => res,\n      Err(e) => panic_with_test_error(\"test_with_printer_options\", \"print\", source, e),\n    };\n    assert_eq!(res.code, expected);\n  }\n\n  #[track_caller]\n  fn minify_test(source: &str, expected: &str) {\n    minify_test_with_options(source, expected, ParserOptions::default())\n  }\n\n  #[track_caller]\n  fn minify_test_with_options<'i, 'o>(source: &'i str, expected: &'i str, options: ParserOptions<'o, 'i>) {\n    let mut stylesheet = match StyleSheet::parse(&source, options) {\n      Ok(stylesheet) => stylesheet,\n      Err(e) => panic_with_test_error(\"minify_test_with_options\", \"parse\", source, e),\n    };\n    if let Err(e) = stylesheet.minify(MinifyOptions::default()) {\n      panic_with_test_error(\"minify_test_with_options\", \"minify\", source, e);\n    }\n    let res = match stylesheet.to_css(PrinterOptions {\n      minify: true,\n      ..PrinterOptions::default()\n    }) {\n      Ok(res) => res,\n      Err(e) => panic_with_test_error(\"minify_test_with_options\", \"print\", source, e),\n    };\n    assert_eq!(res.code, expected);\n  }\n\n  #[track_caller]\n  fn minify_error_test_with_options<'i, 'o>(\n    source: &'i str,\n    error: MinifyErrorKind,\n    options: ParserOptions<'o, 'i>,\n  ) {\n    let mut stylesheet = match StyleSheet::parse(&source, options) {\n      Ok(stylesheet) => stylesheet,\n      Err(e) => panic_with_test_error(\"minify_error_test_with_options\", \"parse\", source, e),\n    };\n    match stylesheet.minify(MinifyOptions::default()) {\n      Err(e) => assert_eq!(e.kind, error),\n      Ok(()) => panic!(\n        \"minify_error_test_with_options: expected minify error {:?}, but minification succeeded.\\nsource:\\n{source}\",\n        error\n      ),\n    }\n  }\n\n  #[track_caller]\n  fn prefix_test(source: &str, expected: &str, targets: Browsers) {\n    let mut stylesheet = match StyleSheet::parse(&source, ParserOptions::default()) {\n      Ok(stylesheet) => stylesheet,\n      Err(e) => panic_with_test_error(\"prefix_test\", \"parse\", source, e),\n    };\n    if let Err(e) = stylesheet.minify(MinifyOptions {\n      targets: targets.into(),\n      ..MinifyOptions::default()\n    }) {\n      panic_with_test_error(\"prefix_test\", \"minify\", source, e);\n    }\n    let res = match stylesheet.to_css(PrinterOptions {\n      targets: targets.into(),\n      ..PrinterOptions::default()\n    }) {\n      Ok(res) => res,\n      Err(e) => panic_with_test_error(\"prefix_test\", \"print\", source, e),\n    };\n    assert_eq!(res.code, expected);\n  }\n\n  #[track_caller]\n  fn attr_test(source: &str, expected: &str, minify: bool, targets: Option<Browsers>) {\n    let mut attr = match StyleAttribute::parse(source, ParserOptions::default()) {\n      Ok(attr) => attr,\n      Err(e) => panic_with_test_error(\"attr_test\", \"parse\", source, e),\n    };\n    attr.minify(MinifyOptions {\n      targets: targets.into(),\n      ..MinifyOptions::default()\n    });\n    let res = match attr.to_css(PrinterOptions {\n      targets: targets.into(),\n      minify,\n      ..PrinterOptions::default()\n    }) {\n      Ok(res) => res,\n      Err(e) => panic_with_test_error(\"attr_test\", \"print\", source, e),\n    };\n    assert_eq!(res.code, expected);\n  }\n\n  #[track_caller]\n  fn nesting_test(source: &str, expected: &str) {\n    nesting_test_with_targets(\n      source,\n      expected,\n      Browsers {\n        chrome: Some(95 << 16),\n        ..Browsers::default()\n      }\n      .into(),\n    );\n  }\n\n  #[track_caller]\n  fn nesting_test_with_targets(source: &str, expected: &str, targets: Targets) {\n    let mut stylesheet = match StyleSheet::parse(&source, ParserOptions::default()) {\n      Ok(stylesheet) => stylesheet,\n      Err(e) => panic_with_test_error(\"nesting_test_with_targets\", \"parse\", source, e),\n    };\n    if let Err(e) = stylesheet.minify(MinifyOptions {\n      targets,\n      ..MinifyOptions::default()\n    }) {\n      panic_with_test_error(\"nesting_test_with_targets\", \"minify\", source, e);\n    }\n    let res = match stylesheet.to_css(PrinterOptions {\n      targets,\n      ..PrinterOptions::default()\n    }) {\n      Ok(res) => res,\n      Err(e) => panic_with_test_error(\"nesting_test_with_targets\", \"print\", source, e),\n    };\n    assert_eq!(res.code, expected);\n  }\n\n  #[track_caller]\n  fn nesting_test_no_targets(source: &str, expected: &str) {\n    let mut stylesheet = match StyleSheet::parse(&source, ParserOptions::default()) {\n      Ok(stylesheet) => stylesheet,\n      Err(e) => panic_with_test_error(\"nesting_test_no_targets\", \"parse\", source, e),\n    };\n    if let Err(e) = stylesheet.minify(MinifyOptions::default()) {\n      panic_with_test_error(\"nesting_test_no_targets\", \"minify\", source, e);\n    }\n    let res = match stylesheet.to_css(PrinterOptions::default()) {\n      Ok(res) => res,\n      Err(e) => panic_with_test_error(\"nesting_test_no_targets\", \"print\", source, e),\n    };\n    assert_eq!(res.code, expected);\n  }\n\n  #[track_caller]\n  fn css_modules_test<'i>(\n    source: &'i str,\n    expected: &str,\n    expected_exports: CssModuleExports,\n    expected_references: CssModuleReferences,\n    config: crate::css_modules::Config<'i>,\n    minify: bool,\n  ) {\n    let mut stylesheet = match StyleSheet::parse(\n      &source,\n      ParserOptions {\n        filename: \"test.css\".into(),\n        css_modules: Some(config),\n        ..ParserOptions::default()\n      },\n    ) {\n      Ok(stylesheet) => stylesheet,\n      Err(e) => panic_with_test_error(\"css_modules_test\", \"parse\", source, e),\n    };\n    if let Err(e) = stylesheet.minify(MinifyOptions::default()) {\n      panic_with_test_error(\"css_modules_test\", \"minify\", source, e);\n    }\n    let res = match stylesheet.to_css(PrinterOptions {\n      minify,\n      ..Default::default()\n    }) {\n      Ok(res) => res,\n      Err(e) => panic_with_test_error(\"css_modules_test\", \"print\", source, e),\n    };\n    assert_eq!(res.code, expected);\n    match res.exports {\n      Some(exports) => assert_eq!(exports, expected_exports),\n      None => panic!(\"css_modules_test: expected CSS module exports, but got None.\\nsource:\\n{source}\"),\n    }\n    match res.references {\n      Some(references) => assert_eq!(references, expected_references),\n      None => panic!(\"css_modules_test: expected CSS module references, but got None.\\nsource:\\n{source}\"),\n    }\n  }\n\n  #[track_caller]\n  fn custom_media_test(source: &str, expected: &str) {\n    let mut stylesheet = match StyleSheet::parse(\n      &source,\n      ParserOptions {\n        flags: ParserFlags::CUSTOM_MEDIA,\n        ..ParserOptions::default()\n      },\n    ) {\n      Ok(stylesheet) => stylesheet,\n      Err(e) => panic_with_test_error(\"custom_media_test\", \"parse\", source, e),\n    };\n    if let Err(e) = stylesheet.minify(MinifyOptions {\n      targets: Browsers {\n        chrome: Some(95 << 16),\n        ..Browsers::default()\n      }\n      .into(),\n      ..MinifyOptions::default()\n    }) {\n      panic_with_test_error(\"custom_media_test\", \"minify\", source, e);\n    }\n    let res = match stylesheet.to_css(PrinterOptions::default()) {\n      Ok(res) => res,\n      Err(e) => panic_with_test_error(\"custom_media_test\", \"print\", source, e),\n    };\n    assert_eq!(res.code, expected);\n  }\n\n  fn error_test(source: &str, error: ParserError) {\n    let res = StyleSheet::parse(&source, ParserOptions::default());\n    match res {\n      Ok(_) => unreachable!(),\n      Err(e) => assert_eq!(e.kind, error),\n    }\n  }\n\n  fn error_recovery_test(source: &str) -> Vec<Error<ParserError<'_>>> {\n    let warnings = Arc::new(RwLock::default());\n    {\n      let res = StyleSheet::parse(\n        &source,\n        ParserOptions {\n          error_recovery: true,\n          warnings: Some(warnings.clone()),\n          ..Default::default()\n        },\n      );\n      match res {\n        Ok(..) => {}\n        Err(e) => unreachable!(\"parser error should be recovered, but got {e:?}\"),\n      }\n    }\n    let warnings = match Arc::into_inner(warnings) {\n      Some(warnings) => warnings,\n      None => panic!(\"error_recovery_test: expected a single Arc owner for warnings\"),\n    };\n    match warnings.into_inner() {\n      Ok(warnings) => warnings,\n      Err(e) => panic!(\"error_recovery_test: warnings lock is poisoned: {e}\"),\n    }\n  }\n\n  fn css_modules_error_test(source: &str, error: ParserError) {\n    let res = StyleSheet::parse(\n      &source,\n      ParserOptions {\n        css_modules: Some(Default::default()),\n        ..Default::default()\n      },\n    );\n    match res {\n      Ok(_) => unreachable!(),\n      Err(e) => assert_eq!(e.kind, error),\n    }\n  }\n\n  macro_rules! map(\n    { $($key:expr => $name:literal $(referenced: $referenced: literal)? $($value:literal $(global: $global: literal)? $(from $from:literal)?)*),* } => {\n      {\n        #[allow(unused_mut)]\n        let mut m = HashMap::new();\n        $(\n          #[allow(unused_mut)]\n          let mut v = Vec::new();\n          #[allow(unused_macros)]\n          macro_rules! insert {\n            ($local:literal from $specifier:literal) => {\n              v.push(CssModuleReference::Dependency {\n                name: $local.into(),\n                specifier: $specifier.into()\n              });\n            };\n            ($local:literal global: $is_global: literal) => {\n              v.push(CssModuleReference::Global {\n                name: $local.into()\n              });\n            };\n            ($local:literal) => {\n              v.push(CssModuleReference::Local {\n                name: $local.into()\n              });\n            };\n          }\n          $(\n            insert!($value $(global: $global)? $(from $from)?);\n          )*\n\n          macro_rules! is_referenced {\n            ($ref: literal) => {\n              $ref\n            };\n            () => {\n              false\n            };\n          }\n\n          m.insert($key.into(), CssModuleExport {\n            name: $name.into(),\n            composes: v,\n            is_referenced: is_referenced!($($referenced)?)\n          });\n        )*\n        m\n      }\n    };\n  );\n\n  #[test]\n  pub fn test_border_spacing() {\n    minify_test(\n      r#\"\n      .foo {\n        border-spacing: 0px;\n      }\n    \"#,\n      indoc! {\".foo{border-spacing:0}\"\n      },\n    );\n    minify_test(\n      r#\"\n      .foo {\n        border-spacing: 0px 0px;\n      }\n    \"#,\n      indoc! {\".foo{border-spacing:0}\"\n      },\n    );\n\n    minify_test(\n      r#\"\n      .foo {\n        border-spacing: 12px   0px;\n      }\n    \"#,\n      indoc! {\".foo{border-spacing:12px 0}\"\n      },\n    );\n\n    minify_test(\n      r#\"\n      .foo {\n        border-spacing: calc(3px * 2) calc(5px * 0);\n      }\n    \"#,\n      indoc! {\".foo{border-spacing:6px 0}\"\n      },\n    );\n\n    minify_test(\n      r#\"\n      .foo {\n        border-spacing: calc(3px * 2) max(0px, 8px);\n      }\n    \"#,\n      indoc! {\".foo{border-spacing:6px 8px}\"\n      },\n    );\n\n    // TODO: The `<length>` in border-spacing cannot have a negative value,\n    // we may need to implement NonNegativeLength like Servo does.\n    // Servo Code: https://github.com/servo/servo/blob/08bc2d53579c9ab85415d4363888881b91df073b/components/style/values/specified/length.rs#L875\n    // CSSWG issue: https://lists.w3.org/Archives/Public/www-style/2008Sep/0161.html\n    // `border-spacing = <length> <length>?`\n    minify_test(\n      r#\"\n      .foo {\n        border-spacing: -20px;\n      }\n    \"#,\n      indoc! {\".foo{border-spacing:-20px}\"\n      },\n    );\n  }\n\n  #[test]\n  pub fn test_math_fn() {\n    // max()\n    minify_test(\n      r#\"\n      .foo {\n        color: rgb(max(255, 100), 0, 0);\n      }\n    \"#,\n      indoc! {\".foo{color:red}\"\n      },\n    );\n    // min()\n    minify_test(\n      r#\"\n      .foo {\n        color: rgb(min(255, 500), 0, 0);\n      }\n    \"#,\n      indoc! {\".foo{color:red}\"\n      },\n    );\n    // abs()\n    minify_test(\n      r#\"\n      .foo {\n        color: rgb(abs(-255), 0, 0);\n      }\n    \"#,\n      indoc! {\".foo{color:red}\"\n      },\n    );\n    // clamp()\n    minify_test(\n      r#\"\n      .foo {\n        flex: clamp(1, 5.20, 20);\n        color: rgb(clamp(0, 255, 300), 0, 0);\n      }\n    \"#,\n      indoc! {\".foo{color:red;flex:5.2}\"\n      },\n    );\n    // round()\n    minify_test(\n      r#\"\n      .round-color {\n        color: rgb(round(down, 255.6, 1), 0, 0);\n      }\n    \"#,\n      indoc! {\".round-color{color:red}\"\n      },\n    );\n    // hypot()\n    minify_test(\n      r#\"\n      .hypot-color {\n        color: rgb(hypot(255, 0), 0, 0);\n      }\n    \"#,\n      indoc! {\".hypot-color{color:red}\"\n      },\n    );\n    // sign(), sign(50) = 1\n    minify_test(\n      r#\"\n      .sign-color {\n        color: rgb(sign(50), 0, 0);\n      }\n    \"#,\n      indoc! {\".sign-color{color:#010000}\"\n      },\n    );\n    // rem(), rem(21, 2) = 1\n    minify_test(\n      r#\"\n      .rem-color {\n        color: rgb(rem(21, 2), 0, 0);\n      }\n    \"#,\n      indoc! {\".rem-color{color:#010000}\"\n      },\n    );\n    // max() in width\n    minify_test(\n      r#\"\n      .foo {\n        width: max(200px,   5px);\n      }\n    \"#,\n      indoc! {\".foo{width:200px}\"\n      },\n    );\n    // max() in opacity\n    minify_test(\n      r#\"\n      .foo {\n        opacity: max(1, 0.2);\n        filter: invert(min(1, 0.5));\n      }\n    \"#,\n      indoc! {\".foo{opacity:1;filter:invert(.5)}\"\n      },\n    );\n    // TODO: support calc in Integer\n    // minify_test(\n    //   r#\"\n    //   .foo {\n    //     z-index: max(100,    20);\n    //   }\n    // \"#,\n    //   indoc! {\".foo{z-index:100}\"\n    //   },\n    // );\n  }\n\n  #[test]\n  pub fn test_border() {\n    test(\n      r#\"\n      .foo {\n        border-left: 2px solid red;\n        border-right: 2px solid red;\n        border-bottom: 2px solid red;\n        border-top: 2px solid red;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border: 2px solid red;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        border-left-color: red;\n        border-right-color: red;\n        border-bottom-color: red;\n        border-top-color: red;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-color: red;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        border-left-width: thin;\n        border-right-width: thin;\n        border-bottom-width: thin;\n        border-top-width: thin;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-width: thin;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        border-left-style: dotted;\n        border-right-style: dotted;\n        border-bottom-style: dotted;\n        border-top-style: dotted;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-style: dotted;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        border-left-width: thin;\n        border-left-style: dotted;\n        border-left-color: red;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-left: thin dotted red;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        border-left-width: thick;\n        border-left: thin dotted red;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-left: thin dotted red;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        border-left-width: thick;\n        border: thin dotted red;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border: thin dotted red;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        border: thin dotted red;\n        border-right-width: thick;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border: thin dotted red;\n        border-right-width: thick;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        border: thin dotted red;\n        border-right: thick dotted red;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border: thin dotted red;\n        border-right-width: thick;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        border: thin dotted red;\n        border-right-width: thick;\n        border-right-style: solid;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border: thin dotted red;\n        border-right: thick solid red;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        border-top: thin dotted red;\n        border-block-start: thick solid green;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-top: thin dotted red;\n        border-block-start: thick solid green;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        border: thin dotted red;\n        border-block-start-width: thick;\n        border-left-width: medium;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border: thin dotted red;\n        border-block-start-width: thick;\n        border-left-width: medium;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        border-block-start: thin dotted red;\n        border-inline-end: thin dotted red;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-block-start: thin dotted red;\n        border-inline-end: thin dotted red;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        border-block-start-width: thin;\n        border-block-start-style: dotted;\n        border-block-start-color: red;\n        border-inline-end: thin dotted red;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-block-start: thin dotted red;\n        border-inline-end: thin dotted red;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        border-block-start: thin dotted red;\n        border-block-end: thin dotted red;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-block: thin dotted red;\n      }\n    \"#\n      },\n    );\n\n    minify_test(\n      r#\"\n      .foo {\n        border: none;\n      }\n    \"#,\n      indoc! {\".foo{border:none}\"\n      },\n    );\n\n    minify_test(\".foo { border-width: 0 0 1px; }\", \".foo{border-width:0 0 1px}\");\n    test(\n      r#\"\n      .foo {\n        border-block-width: 1px;\n        border-inline-width: 1px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-width: 1px;\n      }\n    \"#\n      },\n    );\n    test(\n      r#\"\n      .foo {\n        border-block-start-width: 1px;\n        border-block-end-width: 1px;\n        border-inline-start-width: 1px;\n        border-inline-end-width: 1px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-width: 1px;\n      }\n    \"#\n      },\n    );\n    test(\n      r#\"\n      .foo {\n        border-block-start-width: 1px;\n        border-block-end-width: 1px;\n        border-inline-start-width: 2px;\n        border-inline-end-width: 2px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-block-width: 1px;\n        border-inline-width: 2px;\n      }\n    \"#\n      },\n    );\n    test(\n      r#\"\n      .foo {\n        border-block-start-width: 1px;\n        border-block-end-width: 1px;\n        border-inline-start-width: 2px;\n        border-inline-end-width: 3px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-block-width: 1px;\n        border-inline-width: 2px 3px;\n      }\n    \"#\n      },\n    );\n\n    minify_test(\n      \".foo { border-bottom: 1px solid var(--spectrum-global-color-gray-200)}\",\n      \".foo{border-bottom:1px solid var(--spectrum-global-color-gray-200)}\",\n    );\n    test(\n      r#\"\n      .foo {\n        border-width: 0;\n        border-bottom: var(--test, 1px) solid;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-width: 0;\n        border-bottom: var(--test, 1px) solid;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        border: 1px solid black;\n        border-width: 1px 1px 0 0;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border: 1px solid #000;\n        border-width: 1px 1px 0 0;\n      }\n    \"#},\n    );\n\n    test(\n      r#\"\n      .foo {\n        border-top: 1px solid black;\n        border-bottom: 1px solid black;\n        border-left: 2px solid black;\n        border-right: 2px solid black;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border: 1px solid #000;\n        border-width: 1px 2px;\n      }\n    \"#},\n    );\n\n    test(\n      r#\"\n      .foo {\n        border-top: 1px solid black;\n        border-bottom: 1px solid black;\n        border-left: 2px solid black;\n        border-right: 1px solid black;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border: 1px solid #000;\n        border-left-width: 2px;\n      }\n    \"#},\n    );\n\n    test(\n      r#\"\n      .foo {\n        border-top: 1px solid black;\n        border-bottom: 1px solid black;\n        border-left: 1px solid red;\n        border-right: 1px solid red;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border: 1px solid #000;\n        border-color: #000 red;\n      }\n    \"#},\n    );\n\n    test(\n      r#\"\n      .foo {\n        border-block-start: 1px solid black;\n        border-block-end: 1px solid black;\n        border-inline-start: 1px solid red;\n        border-inline-end: 1px solid red;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border: 1px solid #000;\n        border-inline-color: red;\n      }\n    \"#},\n    );\n\n    test(\n      r#\"\n      .foo {\n        border-block-start: 1px solid black;\n        border-block-end: 1px solid black;\n        border-inline-start: 2px solid black;\n        border-inline-end: 2px solid black;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border: 1px solid #000;\n        border-inline-width: 2px;\n      }\n    \"#},\n    );\n\n    test(\n      r#\"\n      .foo {\n        border-block-start: 1px solid black;\n        border-block-end: 1px solid black;\n        border-inline-start: 2px solid red;\n        border-inline-end: 2px solid red;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border: 1px solid #000;\n        border-inline: 2px solid red;\n      }\n    \"#},\n    );\n\n    test(\n      r#\"\n      .foo {\n        border-block-start: 1px solid black;\n        border-block-end: 1px solid black;\n        border-inline-start: 2px solid red;\n        border-inline-end: 3px solid red;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border: 1px solid #000;\n        border-inline-start: 2px solid red;\n        border-inline-end: 3px solid red;\n      }\n    \"#},\n    );\n\n    test(\n      r#\"\n      .foo {\n        border-block-start: 2px solid black;\n        border-block-end: 1px solid black;\n        border-inline-start: 2px solid red;\n        border-inline-end: 2px solid red;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border: 2px solid red;\n        border-block-start-color: #000;\n        border-block-end: 1px solid #000;\n      }\n    \"#},\n    );\n\n    test(\n      r#\"\n      .foo {\n        border-block-start: 2px solid red;\n        border-block-end: 1px solid red;\n        border-inline-start: 2px solid red;\n        border-inline-end: 2px solid red;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border: 2px solid red;\n        border-block-end-width: 1px;\n      }\n    \"#},\n    );\n\n    test(\n      r#\"\n      .foo {\n        border-block-start: 2px solid red;\n        border-block-end: 2px solid red;\n        border-inline-start: 2px solid red;\n        border-inline-end: 1px solid red;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border: 2px solid red;\n        border-inline-end-width: 1px;\n      }\n    \"#},\n    );\n\n    test(\n      r#\"\n      .foo {\n        border: 1px solid currentColor;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border: 1px solid;\n      }\n    \"#\n      },\n    );\n\n    minify_test(\n      r#\"\n      .foo {\n        border: 1px solid currentColor;\n      }\n    \"#,\n      \".foo{border:1px solid}\",\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        border-block: 2px solid red;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-top: 2px solid red;\n        border-bottom: 2px solid red;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        border-block-start: 2px solid red;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-top: 2px solid red;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        border-block-end: 2px solid red;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-bottom: 2px solid red;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        border-inline: 2px solid red;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-left: 2px solid red;\n        border-right: 2px solid red;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        border-block-width: 2px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-block-start-width: 2px;\n        border-block-end-width: 2px;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(13 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        border-block-width: 2px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-block-width: 2px;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(15 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        border-inline-start: 2px solid red;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo:not(:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        border-left: 2px solid red;\n      }\n\n      .foo:not(:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        border-left: 2px solid red;\n      }\n\n      .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        border-right: 2px solid red;\n      }\n\n      .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        border-right: 2px solid red;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        border-inline-start-width: 2px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo:not(:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        border-left-width: 2px;\n      }\n\n      .foo:not(:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        border-left-width: 2px;\n      }\n\n      .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        border-right-width: 2px;\n      }\n\n      .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        border-right-width: 2px;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        border-inline-end: 2px solid red;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo:not(:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        border-right: 2px solid red;\n      }\n\n      .foo:not(:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        border-right: 2px solid red;\n      }\n\n      .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        border-left: 2px solid red;\n      }\n\n      .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        border-left: 2px solid red;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        border-inline-start: 2px solid red;\n        border-inline-end: 5px solid green;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo:not(:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        border-left: 2px solid red;\n        border-right: 5px solid green;\n      }\n\n      .foo:not(:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        border-left: 2px solid red;\n        border-right: 5px solid green;\n      }\n\n      .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        border-left: 5px solid green;\n        border-right: 2px solid red;\n      }\n\n      .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        border-left: 5px solid green;\n        border-right: 2px solid red;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        border-inline-start: 2px solid red;\n        border-inline-end: 5px solid green;\n      }\n\n      .bar {\n        border-inline-start: 1px dotted gray;\n        border-inline-end: 1px solid black;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo:not(:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        border-left: 2px solid red;\n        border-right: 5px solid green;\n      }\n\n      .foo:not(:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        border-left: 2px solid red;\n        border-right: 5px solid green;\n      }\n\n      .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        border-left: 5px solid green;\n        border-right: 2px solid red;\n      }\n\n      .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        border-left: 5px solid green;\n        border-right: 2px solid red;\n      }\n\n      .bar:not(:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        border-left: 1px dotted gray;\n        border-right: 1px solid #000;\n      }\n\n      .bar:not(:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        border-left: 1px dotted gray;\n        border-right: 1px solid #000;\n      }\n\n      .bar:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        border-left: 1px solid #000;\n        border-right: 1px dotted gray;\n      }\n\n      .bar:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        border-left: 1px solid #000;\n        border-right: 1px dotted gray;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        border-inline-width: 2px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-left-width: 2px;\n        border-right-width: 2px;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        border-inline-width: 2px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-left-width: 2px;\n        border-right-width: 2px;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        border-inline-style: solid;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-left-style: solid;\n        border-right-style: solid;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        border-inline-color: red;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-left-color: red;\n        border-right-color: red;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        border-inline-end: var(--test);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo:not(:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        border-right: var(--test);\n      }\n\n      .foo:not(:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        border-right: var(--test);\n      }\n\n      .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        border-left: var(--test);\n      }\n\n      .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        border-left: var(--test);\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        border-inline-start: var(--start);\n        border-inline-end: var(--end);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo:not(:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        border-left: var(--start);\n        border-right: var(--end);\n      }\n\n      .foo:not(:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        border-left: var(--start);\n        border-right: var(--end);\n      }\n\n      .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        border-right: var(--start);\n        border-left: var(--end);\n      }\n\n      .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        border-right: var(--start);\n        border-left: var(--end);\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    for prop in &[\n      \"border-inline-start-color\",\n      \"border-inline-end-color\",\n      \"border-block-start-color\",\n      \"border-block-end-color\",\n      \"border-top-color\",\n      \"border-bottom-color\",\n      \"border-left-color\",\n      \"border-right-color\",\n      \"border-color\",\n      \"border-block-color\",\n      \"border-inline-color\",\n    ] {\n      prefix_test(\n        &format!(\n          r#\"\n        .foo {{\n          {}: lab(40% 56.6 39);\n        }}\n      \"#,\n          prop\n        ),\n        &format!(\n          indoc! {r#\"\n        .foo {{\n          {}: #b32323;\n          {}: lab(40% 56.6 39);\n        }}\n      \"#},\n          prop, prop\n        ),\n        Browsers {\n          chrome: Some(90 << 16),\n          ..Browsers::default()\n        },\n      );\n    }\n\n    for prop in &[\n      \"border\",\n      \"border-inline\",\n      \"border-block\",\n      \"border-left\",\n      \"border-right\",\n      \"border-top\",\n      \"border-bottom\",\n      \"border-block-start\",\n      \"border-block-end\",\n      \"border-inline-start\",\n      \"border-inline-end\",\n    ] {\n      prefix_test(\n        &format!(\n          r#\"\n        .foo {{\n          {}: 2px solid lab(40% 56.6 39);\n        }}\n      \"#,\n          prop\n        ),\n        &format!(\n          indoc! {r#\"\n        .foo {{\n          {}: 2px solid #b32323;\n          {}: 2px solid lab(40% 56.6 39);\n        }}\n      \"#},\n          prop, prop\n        ),\n        Browsers {\n          chrome: Some(90 << 16),\n          ..Browsers::default()\n        },\n      );\n    }\n\n    for prop in &[\n      \"border\",\n      \"border-inline\",\n      \"border-block\",\n      \"border-left\",\n      \"border-right\",\n      \"border-top\",\n      \"border-bottom\",\n      \"border-block-start\",\n      \"border-block-end\",\n      \"border-inline-start\",\n      \"border-inline-end\",\n    ] {\n      prefix_test(\n        &format!(\n          r#\"\n        .foo {{\n          {}: var(--border-width) solid lab(40% 56.6 39);\n        }}\n      \"#,\n          prop\n        ),\n        &format!(\n          indoc! {r#\"\n        .foo {{\n          {}: var(--border-width) solid #b32323;\n        }}\n\n        @supports (color: lab(0% 0 0)) {{\n          .foo {{\n            {}: var(--border-width) solid lab(40% 56.6 39);\n          }}\n        }}\n      \"#},\n          prop, prop\n        ),\n        Browsers {\n          chrome: Some(90 << 16),\n          ..Browsers::default()\n        },\n      );\n\n      prefix_test(\n        &format!(\n          r#\"\n        @supports (color: lab(0% 0 0)) {{\n          .foo {{\n            {}: var(--border-width) solid lab(40% 56.6 39);\n          }}\n        }}\n      \"#,\n          prop\n        ),\n        &format!(\n          indoc! {r#\"\n        @supports (color: lab(0% 0 0)) {{\n          .foo {{\n            {}: var(--border-width) solid lab(40% 56.6 39);\n          }}\n        }}\n      \"#},\n          prop,\n        ),\n        Browsers {\n          chrome: Some(90 << 16),\n          ..Browsers::default()\n        },\n      );\n    }\n\n    prefix_test(\n      r#\"\n      .foo {\n        border-inline-start-color: lab(40% 56.6 39);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo:not(:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        border-left-color: #b32323;\n        border-left-color: lab(40% 56.6 39);\n      }\n\n      .foo:not(:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        border-left-color: #b32323;\n        border-left-color: lab(40% 56.6 39);\n      }\n\n      .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        border-right-color: #b32323;\n        border-right-color: lab(40% 56.6 39);\n      }\n\n      .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        border-right-color: #b32323;\n        border-right-color: lab(40% 56.6 39);\n      }\n    \"#},\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        border-inline-end-color: lab(40% 56.6 39);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo:not(:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        border-right-color: #b32323;\n        border-right-color: lab(40% 56.6 39);\n      }\n\n      .foo:not(:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        border-right-color: #b32323;\n        border-right-color: lab(40% 56.6 39);\n      }\n\n      .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        border-left-color: #b32323;\n        border-left-color: lab(40% 56.6 39);\n      }\n\n      .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        border-left-color: #b32323;\n        border-left-color: lab(40% 56.6 39);\n      }\n    \"#},\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        border-inline-start-color: lab(40% 56.6 39);\n        border-inline-end-color: lch(50.998% 135.363 338);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo:not(:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        border-left-color: #b32323;\n        border-left-color: lab(40% 56.6 39);\n        border-right-color: #ee00be;\n        border-right-color: lch(50.998% 135.363 338);\n      }\n\n      .foo:not(:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        border-left-color: #b32323;\n        border-left-color: lab(40% 56.6 39);\n        border-right-color: #ee00be;\n        border-right-color: lch(50.998% 135.363 338);\n      }\n\n      .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        border-left-color: #ee00be;\n        border-left-color: lch(50.998% 135.363 338);\n        border-right-color: #b32323;\n        border-right-color: lab(40% 56.6 39);\n      }\n\n      .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        border-left-color: #ee00be;\n        border-left-color: lch(50.998% 135.363 338);\n        border-right-color: #b32323;\n        border-right-color: lab(40% 56.6 39);\n      }\n    \"#},\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        border-inline-start-color: lab(40% 56.6 39);\n        border-inline-end-color: lch(50.998% 135.363 338);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo:not(:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        border-left-color: #b32323;\n        border-left-color: color(display-p3 .643308 .192455 .167712);\n        border-left-color: lab(40% 56.6 39);\n        border-right-color: #ee00be;\n        border-right-color: color(display-p3 .972962 -.362078 .804206);\n        border-right-color: lch(50.998% 135.363 338);\n      }\n\n      .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        border-left-color: #ee00be;\n        border-left-color: color(display-p3 .972962 -.362078 .804206);\n        border-left-color: lch(50.998% 135.363 338);\n        border-right-color: #b32323;\n        border-right-color: color(display-p3 .643308 .192455 .167712);\n        border-right-color: lab(40% 56.6 39);\n      }\n    \"#},\n      Browsers {\n        chrome: Some(8 << 16),\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        border-inline-start: 2px solid lab(40% 56.6 39);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo:not(:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        border-left: 2px solid #b32323;\n        border-left: 2px solid lab(40% 56.6 39);\n      }\n\n      .foo:not(:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        border-left: 2px solid #b32323;\n        border-left: 2px solid lab(40% 56.6 39);\n      }\n\n      .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        border-right: 2px solid #b32323;\n        border-right: 2px solid lab(40% 56.6 39);\n      }\n\n      .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        border-right: 2px solid #b32323;\n        border-right: 2px solid lab(40% 56.6 39);\n      }\n    \"#},\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        border-inline-end: 2px solid lab(40% 56.6 39);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo:not(:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        border-right: 2px solid #b32323;\n        border-right: 2px solid lab(40% 56.6 39);\n      }\n\n      .foo:not(:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        border-right: 2px solid #b32323;\n        border-right: 2px solid lab(40% 56.6 39);\n      }\n\n      .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        border-left: 2px solid #b32323;\n        border-left: 2px solid lab(40% 56.6 39);\n      }\n\n      .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        border-left: 2px solid #b32323;\n        border-left: 2px solid lab(40% 56.6 39);\n      }\n    \"#},\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        border-inline-end: var(--border-width) solid lab(40% 56.6 39);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo:not(:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        border-right: var(--border-width) solid #b32323;\n      }\n\n      .foo:not(:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        border-right: var(--border-width) solid #b32323;\n      }\n\n      @supports (color: lab(0% 0 0)) {\n        .foo:not(:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n          border-right: var(--border-width) solid lab(40% 56.6 39);\n        }\n      }\n\n      .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        border-left: var(--border-width) solid #b32323;\n      }\n\n      .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        border-left: var(--border-width) solid #b32323;\n      }\n\n      @supports (color: lab(0% 0 0)) {\n        .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n          border-left: var(--border-width) solid lab(40% 56.6 39);\n        }\n      }\n    \"#},\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        border-inline-start: 2px solid red;\n        border-inline-end: 2px solid red;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-inline-start: 2px solid red;\n        border-inline-end: 2px solid red;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(13 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        border-inline-start: 2px solid red;\n        border-inline-end: 2px solid red;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-inline: 2px solid red;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(15 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        border-width: 22px;\n        border-width: max(2cqw, 22px);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-width: 22px;\n        border-width: max(2cqw, 22px);\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        border-width: 22px;\n        border-width: max(2cqw, 22px);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-width: max(2cqw, 22px);\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(16 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        border-color: #4263eb;\n        border-color: color(display-p3 0 .5 1);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-color: #4263eb;\n        border-color: color(display-p3 0 .5 1);\n      }\n    \"#\n      },\n      Browsers {\n        chrome: Some(99 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        border-color: #4263eb;\n        border-color: color(display-p3 0 .5 1);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-color: color(display-p3 0 .5 1);\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(16 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        border: 1px solid #4263eb;\n        border-color: color(display-p3 0 .5 1);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border: 1px solid #4263eb;\n        border-color: color(display-p3 0 .5 1);\n      }\n    \"#\n      },\n      Browsers {\n        chrome: Some(99 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        border: 1px solid #4263eb;\n        border-color: color(display-p3 0 .5 1);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border: 1px solid color(display-p3 0 .5 1);\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(16 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        border-color: var(--fallback);\n        border-color: color(display-p3 0 .5 1);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-color: var(--fallback);\n        border-color: color(display-p3 0 .5 1);\n      }\n    \"#\n      },\n      Browsers {\n        chrome: Some(99 << 16),\n        ..Browsers::default()\n      },\n    );\n  }\n\n  #[test]\n  pub fn test_border_image() {\n    test(\n      r#\"\n      .foo {\n        border-image: url(test.png) 60;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-image: url(\"test.png\") 60;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        border-image: url(test.png) 60;\n        border-image-source: url(foo.png);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-image: url(\"foo.png\") 60;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        border-image-source: url(foo.png);\n        border-image-slice: 10 40 10 40 fill;\n        border-image-width: 10px;\n        border-image-outset: 0;\n        border-image-repeat: round round;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-image: url(\"foo.png\") 10 40 fill / 10px round;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        border-image: url(foo.png) 60;\n        border-image-source: var(--test);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-image: url(\"foo.png\") 60;\n        border-image-source: var(--test);\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        -webkit-border-image: url(\"test.png\") 60;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-border-image: url(\"test.png\") 60;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        -webkit-border-image: url(\"test.png\") 60;\n        border-image: url(\"test.png\") 60;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-border-image: url(\"test.png\") 60;\n        border-image: url(\"test.png\") 60;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        -webkit-border-image: url(\"test.png\") 60;\n        border-image-source: url(foo.png);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-border-image: url(\"test.png\") 60;\n        border-image-source: url(\"foo.png\");\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        border: 1px solid red;\n        border-image: url(test.png) 60;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border: 1px solid red;\n        border-image: url(\"test.png\") 60;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        border-image: url(test.png) 60;\n        border: 1px solid red;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border: 1px solid red;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        border: 1px solid red;\n        border-image: var(--border-image);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border: 1px solid red;\n        border-image: var(--border-image);\n      }\n    \"#\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        border-image: url(\"test.png\") 60;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-border-image: url(\"test.png\") 60;\n        -moz-border-image: url(\"test.png\") 60;\n        -o-border-image: url(\"test.png\") 60;\n        border-image: url(\"test.png\") 60;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(4 << 16),\n        firefox: Some(4 << 16),\n        opera: Some(12 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        border-image: url(foo.png) 10 40 fill / 10px round;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-image: url(\"foo.png\") 10 40 fill / 10px round;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(4 << 16),\n        firefox: Some(4 << 16),\n        opera: Some(12 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        border-image: var(--test) 60;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-border-image: var(--test) 60;\n        -moz-border-image: var(--test) 60;\n        -o-border-image: var(--test) 60;\n        border-image: var(--test) 60;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(4 << 16),\n        firefox: Some(4 << 16),\n        opera: Some(12 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        -webkit-border-image: url(foo.png) 60;\n        -moz-border-image: url(foo.png) 60;\n        -o-border-image: url(foo.png) 60;\n        border-image: url(foo.png) 60;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-image: url(\"foo.png\") 60;\n      }\n    \"#\n      },\n      Browsers {\n        chrome: Some(15 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        border-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) 60;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-border-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ff0f0e), to(#7773ff)) 60;\n        -webkit-border-image: -webkit-linear-gradient(top, #ff0f0e, #7773ff) 60;\n        border-image: linear-gradient(#ff0f0e, #7773ff) 60;\n        border-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) 60;\n      }\n    \"#\n      },\n      Browsers {\n        chrome: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        border-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) 60;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-border-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ff0f0e), to(#7773ff)) 60;\n        -webkit-border-image: -webkit-linear-gradient(top, #ff0f0e, #7773ff) 60;\n        -moz-border-image: -moz-linear-gradient(top, #ff0f0e, #7773ff) 60;\n        border-image: linear-gradient(#ff0f0e, #7773ff) 60;\n        border-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) 60;\n      }\n    \"#\n      },\n      Browsers {\n        chrome: Some(8 << 16),\n        firefox: Some(4 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        border-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) 60;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-image: -webkit-linear-gradient(top, #ff0f0e, #7773ff) 60;\n        border-image: -moz-linear-gradient(top, #ff0f0e, #7773ff) 60;\n        border-image: linear-gradient(#ff0f0e, #7773ff) 60;\n        border-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) 60;\n      }\n    \"#\n      },\n      Browsers {\n        chrome: Some(15 << 16),\n        firefox: Some(15 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        border-image-source: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364));\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-image-source: -webkit-linear-gradient(top, #ff0f0e, #7773ff);\n        border-image-source: linear-gradient(#ff0f0e, #7773ff);\n        border-image-source: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364));\n      }\n    \"#\n      },\n      Browsers {\n        chrome: Some(15 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        border-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) var(--foo);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-image: linear-gradient(#ff0f0e, #7773ff) var(--foo);\n      }\n\n      @supports (color: lab(0% 0 0)) {\n        .foo {\n          border-image: linear-gradient(lab(56.208% 94.4644 98.8928), lab(51% 70.4544 -115.586)) var(--foo);\n        }\n      }\n    \"#\n      },\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        border-image-source: linear-gradient(red, green);\n        border-image-source: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364));\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-image-source: linear-gradient(red, green);\n        border-image-source: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364));\n      }\n    \"#\n      },\n      Browsers {\n        chrome: Some(95 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        border-image-source: linear-gradient(red, green);\n        border-image-source: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364));\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-image-source: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364));\n      }\n    \"#\n      },\n      Browsers {\n        chrome: Some(112 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        border-image: linear-gradient(red, green);\n        border-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364));\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-image: linear-gradient(red, green);\n        border-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364));\n      }\n    \"#\n      },\n      Browsers {\n        chrome: Some(95 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        border-image: var(--fallback);\n        border-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364));\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-image: var(--fallback);\n        border-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364));\n      }\n    \"#\n      },\n      Browsers {\n        chrome: Some(95 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        border-image: url(\"fallback.png\") 10 40 fill / 10px;\n        border-image: url(\"main.png\") 10 40 fill / 10px space;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-image: url(\"fallback.png\") 10 40 fill / 10px;\n        border-image: url(\"main.png\") 10 40 fill / 10px space;\n      }\n    \"#\n      },\n      Browsers {\n        chrome: Some(50 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        border-image: url(\"fallback.png\") 10 40 fill / 10px;\n        border-image: url(\"main.png\") 10 40 fill / 10px space;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-image: url(\"main.png\") 10 40 fill / 10px space;\n      }\n    \"#\n      },\n      Browsers {\n        chrome: Some(56 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    minify_test(\".foo { border: none green }\", \".foo{border:green}\");\n  }\n\n  #[test]\n  pub fn test_border_radius() {\n    test(\n      r#\"\n      .foo {\n        border-radius: 10px 100px 10px 100px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-radius: 10px 100px;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        border-radius: 10px 100px 10px 100px / 120px 120px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-radius: 10px 100px / 120px;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        border-top-left-radius: 10px 120px;\n        border-top-right-radius: 100px 120px;\n        border-bottom-right-radius: 100px 120px;\n        border-bottom-left-radius: 10px 120px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-radius: 10px 100px 100px 10px / 120px;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        border-top-left-radius: 4px 2px;\n        border-top-right-radius: 3px 4px;\n        border-bottom-right-radius: 6px 2px;\n        border-bottom-left-radius: 3px 4px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-radius: 4px 3px 6px / 2px 4px;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        border-top-left-radius: 1% 2%;\n        border-top-right-radius: 3% 4%;\n        border-bottom-right-radius: 5% 6%;\n        border-bottom-left-radius: 7% 8%;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-radius: 1% 3% 5% 7% / 2% 4% 6% 8%;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        border-radius: 10px 100px 10px 100px / 120px 120px;\n        border-start-start-radius: 10px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-radius: 10px 100px / 120px;\n        border-start-start-radius: 10px;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        border-start-start-radius: 10px;\n        border-radius: 10px 100px 10px 100px / 120px 120px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-radius: 10px 100px / 120px;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        border-top-left-radius: 10px 120px;\n        border-top-right-radius: 100px 120px;\n        border-start-start-radius: 10px;\n        border-bottom-right-radius: 100px 120px;\n        border-bottom-left-radius: 10px 120px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-top-left-radius: 10px 120px;\n        border-top-right-radius: 100px 120px;\n        border-start-start-radius: 10px;\n        border-bottom-right-radius: 100px 120px;\n        border-bottom-left-radius: 10px 120px;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        border-radius: 10px;\n        border-top-left-radius: 20px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-radius: 20px 10px 10px;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        border-radius: 10px;\n        border-top-left-radius: var(--test);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-radius: 10px;\n        border-top-left-radius: var(--test);\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        -webkit-border-radius: 10px 100px 10px 100px;\n        -moz-border-radius: 10px 100px 10px 100px;\n        border-radius: 10px 100px 10px 100px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-border-radius: 10px 100px;\n        -moz-border-radius: 10px 100px;\n        border-radius: 10px 100px;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        -webkit-border-radius: 10px 100px 10px 100px;\n        -moz-border-radius: 20px;\n        border-radius: 30px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-border-radius: 10px 100px;\n        -moz-border-radius: 20px;\n        border-radius: 30px;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        -webkit-border-top-left-radius: 10px;\n        -moz-border-top-left-radius: 10px;\n        border-top-left-radius: 10px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-border-top-left-radius: 10px;\n        -moz-border-top-left-radius: 10px;\n        border-top-left-radius: 10px;\n      }\n    \"#\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        border-radius: 30px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-border-radius: 30px;\n        -moz-border-radius: 30px;\n        border-radius: 30px;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(4 << 16),\n        firefox: Some(3 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        border-top-left-radius: 30px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-border-top-left-radius: 30px;\n        -moz-border-top-left-radius: 30px;\n        border-top-left-radius: 30px;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(4 << 16),\n        firefox: Some(3 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        -webkit-border-radius: 30px;\n        -moz-border-radius: 30px;\n        border-radius: 30px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-radius: 30px;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(14 << 16),\n        firefox: Some(46 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        -webkit-border-top-left-radius: 30px;\n        -moz-border-top-left-radius: 30px;\n        border-top-left-radius: 30px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        border-top-left-radius: 30px;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(14 << 16),\n        firefox: Some(46 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        -webkit-border-radius: 30px;\n        -moz-border-radius: 30px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-border-radius: 30px;\n        -moz-border-radius: 30px;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(14 << 16),\n        firefox: Some(46 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        -webkit-border-top-left-radius: 30px;\n        -moz-border-top-right-radius: 30px;\n        border-bottom-right-radius: 30px;\n        border-bottom-left-radius: 30px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-border-top-left-radius: 30px;\n        -moz-border-top-right-radius: 30px;\n        border-bottom-right-radius: 30px;\n        border-bottom-left-radius: 30px;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(14 << 16),\n        firefox: Some(46 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        border-radius: var(--test);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-border-radius: var(--test);\n        -moz-border-radius: var(--test);\n        border-radius: var(--test);\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(4 << 16),\n        firefox: Some(3 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        border-start-start-radius: 5px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo:not(:lang(ae, ar, arc, bcc, bqi, ckb, dv, fa, glk, he, ku, mzn, nqo, pnb, ps, sd, ug, ur, yi)) {\n        border-top-left-radius: 5px;\n      }\n\n      .foo:lang(ae, ar, arc, bcc, bqi, ckb, dv, fa, glk, he, ku, mzn, nqo, pnb, ps, sd, ug, ur, yi) {\n        border-top-right-radius: 5px;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(12 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        border-start-start-radius: 5px;\n        border-start-end-radius: 10px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo:not(:lang(ae, ar, arc, bcc, bqi, ckb, dv, fa, glk, he, ku, mzn, nqo, pnb, ps, sd, ug, ur, yi)) {\n        border-top-left-radius: 5px;\n        border-top-right-radius: 10px;\n      }\n\n      .foo:lang(ae, ar, arc, bcc, bqi, ckb, dv, fa, glk, he, ku, mzn, nqo, pnb, ps, sd, ug, ur, yi) {\n        border-top-left-radius: 10px;\n        border-top-right-radius: 5px;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(12 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        border-end-end-radius: 10px;\n        border-end-start-radius: 5px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo:not(:lang(ae, ar, arc, bcc, bqi, ckb, dv, fa, glk, he, ku, mzn, nqo, pnb, ps, sd, ug, ur, yi)) {\n        border-bottom-right-radius: 10px;\n        border-bottom-left-radius: 5px;\n      }\n\n      .foo:lang(ae, ar, arc, bcc, bqi, ckb, dv, fa, glk, he, ku, mzn, nqo, pnb, ps, sd, ug, ur, yi) {\n        border-bottom-right-radius: 5px;\n        border-bottom-left-radius: 10px;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(12 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        border-start-start-radius: var(--radius);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo:not(:lang(ae, ar, arc, bcc, bqi, ckb, dv, fa, glk, he, ku, mzn, nqo, pnb, ps, sd, ug, ur, yi)) {\n        border-top-left-radius: var(--radius);\n      }\n\n      .foo:lang(ae, ar, arc, bcc, bqi, ckb, dv, fa, glk, he, ku, mzn, nqo, pnb, ps, sd, ug, ur, yi) {\n        border-top-right-radius: var(--radius);\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(12 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        border-start-start-radius: var(--start);\n        border-start-end-radius: var(--end);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo:not(:lang(ae, ar, arc, bcc, bqi, ckb, dv, fa, glk, he, ku, mzn, nqo, pnb, ps, sd, ug, ur, yi)) {\n        border-top-left-radius: var(--start);\n        border-top-right-radius: var(--end);\n      }\n\n      .foo:lang(ae, ar, arc, bcc, bqi, ckb, dv, fa, glk, he, ku, mzn, nqo, pnb, ps, sd, ug, ur, yi) {\n        border-top-right-radius: var(--start);\n        border-top-left-radius: var(--end);\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(12 << 16),\n        ..Browsers::default()\n      },\n    );\n  }\n\n  #[test]\n  pub fn test_outline() {\n    test(\n      r#\"\n      .foo {\n        outline-width: 2px;\n        outline-style: solid;\n        outline-color: blue;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        outline: 2px solid #00f;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        outline: 2px solid blue;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        outline: 2px solid #00f;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        outline: 2px solid red;\n        outline-color: blue;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        outline: 2px solid #00f;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        outline: 2px solid yellow;\n        outline-color: var(--color);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        outline: 2px solid #ff0;\n        outline-color: var(--color);\n      }\n    \"#\n      },\n    );\n\n    prefix_test(\n      \".foo { outline-color: lab(40% 56.6 39) }\",\n      indoc! { r#\"\n        .foo {\n          outline-color: #b32323;\n          outline-color: lab(40% 56.6 39);\n        }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { outline: 2px solid lab(40% 56.6 39) }\",\n      indoc! { r#\"\n        .foo {\n          outline: 2px solid #b32323;\n          outline: 2px solid lab(40% 56.6 39);\n        }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { outline: var(--width) solid lab(40% 56.6 39) }\",\n      indoc! { r#\"\n        .foo {\n          outline: var(--width) solid #b32323;\n        }\n\n        @supports (color: lab(0% 0 0)) {\n          .foo {\n            outline: var(--width) solid lab(40% 56.6 39);\n          }\n        }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n  }\n\n  #[test]\n  pub fn test_margin() {\n    test(\n      r#\"\n      .foo {\n        margin-left: 10px;\n        margin-right: 10px;\n        margin-top: 20px;\n        margin-bottom: 20px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        margin: 20px 10px;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        margin-block-start: 15px;\n        margin-block-end: 15px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        margin-block: 15px;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        margin-left: 10px;\n        margin-right: 10px;\n        margin-inline-start: 15px;\n        margin-inline-end: 15px;\n        margin-top: 20px;\n        margin-bottom: 20px;\n\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        margin-left: 10px;\n        margin-right: 10px;\n        margin-inline: 15px;\n        margin-top: 20px;\n        margin-bottom: 20px;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        margin: 10px;\n        margin-top: 20px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        margin: 20px 10px 10px;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        margin: 10px;\n        margin-top: var(--top);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        margin: 10px;\n        margin-top: var(--top);\n      }\n    \"#\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        margin-inline-start: 2px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo:not(:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        margin-left: 2px;\n      }\n\n      .foo:not(:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        margin-left: 2px;\n      }\n\n      .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        margin-right: 2px;\n      }\n\n      .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        margin-right: 2px;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        margin-inline-start: 2px;\n        margin-inline-end: 4px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo:not(:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        margin-left: 2px;\n        margin-right: 4px;\n      }\n\n      .foo:not(:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        margin-left: 2px;\n        margin-right: 4px;\n      }\n\n      .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        margin-left: 4px;\n        margin-right: 2px;\n      }\n\n      .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        margin-left: 4px;\n        margin-right: 2px;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        margin-inline: 2px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        margin-left: 2px;\n        margin-right: 2px;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        margin-block-start: 2px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        margin-top: 2px;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        margin-block-end: 2px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        margin-bottom: 2px;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        margin-inline-start: 2px;\n        margin-inline-end: 2px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        margin-inline-start: 2px;\n        margin-inline-end: 2px;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(13 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        margin-inline: 2px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        margin-inline-start: 2px;\n        margin-inline-end: 2px;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(13 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        margin-inline-start: 2px;\n        margin-inline-end: 2px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        margin-inline: 2px;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(15 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        margin-inline: 2px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        margin-inline: 2px;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(15 << 16),\n        ..Browsers::default()\n      },\n    );\n  }\n\n  #[test]\n  fn test_length() {\n    for prop in &[\n      \"margin-right\",\n      \"margin\",\n      \"padding-right\",\n      \"padding\",\n      \"width\",\n      \"height\",\n      \"min-height\",\n      \"max-height\",\n      \"line-height\",\n      \"border-radius\",\n    ] {\n      prefix_test(\n        &format!(\n          r#\"\n        .foo {{\n          {}: 22px;\n          {}: max(4%, 22px);\n        }}\n      \"#,\n          prop, prop\n        ),\n        &format!(\n          indoc! {r#\"\n        .foo {{\n          {}: 22px;\n          {}: max(4%, 22px);\n        }}\n      \"#\n          },\n          prop, prop\n        ),\n        Browsers {\n          safari: Some(10 << 16),\n          ..Browsers::default()\n        },\n      );\n\n      prefix_test(\n        &format!(\n          r#\"\n        .foo {{\n          {}: 22px;\n          {}: max(4%, 22px);\n        }}\n      \"#,\n          prop, prop\n        ),\n        &format!(\n          indoc! {r#\"\n        .foo {{\n          {}: max(4%, 22px);\n        }}\n      \"#\n          },\n          prop\n        ),\n        Browsers {\n          safari: Some(14 << 16),\n          ..Browsers::default()\n        },\n      );\n\n      prefix_test(\n        &format!(\n          r#\"\n        .foo {{\n          {}: 22px;\n          {}: max(2cqw, 22px);\n        }}\n      \"#,\n          prop, prop\n        ),\n        &format!(\n          indoc! {r#\"\n        .foo {{\n          {}: 22px;\n          {}: max(2cqw, 22px);\n        }}\n      \"#\n          },\n          prop, prop\n        ),\n        Browsers {\n          safari: Some(14 << 16),\n          ..Browsers::default()\n        },\n      );\n      prefix_test(\n        &format!(\n          r#\"\n        .foo {{\n          {}: 22px;\n          {}: max(2cqw, 22px);\n        }}\n      \"#,\n          prop, prop\n        ),\n        &format!(\n          indoc! {r#\"\n        .foo {{\n          {}: max(2cqw, 22px);\n        }}\n      \"#\n          },\n          prop\n        ),\n        Browsers {\n          safari: Some(16 << 16),\n          ..Browsers::default()\n        },\n      );\n    }\n  }\n\n  #[test]\n  pub fn test_padding() {\n    test(\n      r#\"\n      .foo {\n        padding-left: 10px;\n        padding-right: 10px;\n        padding-top: 20px;\n        padding-bottom: 20px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        padding: 20px 10px;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        padding-block-start: 15px;\n        padding-block-end: 15px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        padding-block: 15px;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        padding-left: 10px;\n        padding-right: 10px;\n        padding-inline-start: 15px;\n        padding-inline-end: 15px;\n        padding-top: 20px;\n        padding-bottom: 20px;\n\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        padding-left: 10px;\n        padding-right: 10px;\n        padding-inline: 15px;\n        padding-top: 20px;\n        padding-bottom: 20px;\n      }\n    \"#\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        padding-inline-start: 2px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo:not(:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        padding-left: 2px;\n      }\n\n      .foo:not(:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        padding-left: 2px;\n      }\n\n      .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        padding-right: 2px;\n      }\n\n      .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        padding-right: 2px;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        padding-inline-start: 2px;\n        padding-inline-end: 4px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo:not(:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        padding-left: 2px;\n        padding-right: 4px;\n      }\n\n      .foo:not(:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        padding-left: 2px;\n        padding-right: 4px;\n      }\n\n      .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        padding-left: 4px;\n        padding-right: 2px;\n      }\n\n      .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        padding-left: 4px;\n        padding-right: 2px;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        padding-inline-start: var(--padding);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo:not(:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        padding-left: var(--padding);\n      }\n\n      .foo:not(:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        padding-left: var(--padding);\n      }\n\n      .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        padding-right: var(--padding);\n      }\n\n      .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        padding-right: var(--padding);\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        padding-inline: 2px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        padding-left: 2px;\n        padding-right: 2px;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        padding-block-start: 2px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        padding-top: 2px;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        padding-block-end: 2px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        padding-bottom: 2px;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        padding-top: 1px;\n        padding-left: 2px;\n        padding-bottom: 3px;\n        padding-right: 4px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        padding: 1px 4px 3px 2px;\n      }\n    \"#},\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        padding-inline-start: 2px;\n        padding-inline-end: 2px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        padding-inline-start: 2px;\n        padding-inline-end: 2px;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(13 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        padding-inline-start: 2px;\n        padding-inline-end: 2px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        padding-inline: 2px;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(15 << 16),\n        ..Browsers::default()\n      },\n    );\n  }\n\n  #[test]\n  fn test_scroll_padding() {\n    prefix_test(\n      r#\"\n      .foo {\n        scroll-padding-inline: 2px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        scroll-padding-inline: 2px;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n  }\n\n  #[test]\n  fn test_size() {\n    prefix_test(\n      r#\"\n      .foo {\n        block-size: 25px;\n        inline-size: 25px;\n        min-block-size: 25px;\n        min-inline-size: 25px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        height: 25px;\n        min-height: 25px;\n        width: 25px;\n        min-width: 25px;\n      }\n    \"#},\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        block-size: 25px;\n        min-block-size: 25px;\n        inline-size: 25px;\n        min-inline-size: 25px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        block-size: 25px;\n        min-block-size: 25px;\n        inline-size: 25px;\n        min-inline-size: 25px;\n      }\n    \"#},\n      Browsers {\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        block-size: var(--size);\n        min-block-size: var(--size);\n        inline-size: var(--size);\n        min-inline-size: var(--size);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        height: var(--size);\n        min-height: var(--size);\n        width: var(--size);\n        min-width: var(--size);\n      }\n    \"#},\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    for (in_prop, out_prop) in [\n      (\"width\", \"width\"),\n      (\"height\", \"height\"),\n      (\"block-size\", \"height\"),\n      (\"inline-size\", \"width\"),\n      (\"min-width\", \"min-width\"),\n      (\"min-height\", \"min-height\"),\n      (\"min-block-size\", \"min-height\"),\n      (\"min-inline-size\", \"min-width\"),\n      (\"max-width\", \"max-width\"),\n      (\"max-height\", \"max-height\"),\n      (\"max-block-size\", \"max-height\"),\n      (\"max-inline-size\", \"max-width\"),\n    ] {\n      prefix_test(\n        &format!(\n          r#\"\n        .foo {{\n          {}: stretch;\n        }}\n      \"#,\n          in_prop\n        ),\n        &format!(\n          indoc! {r#\"\n        .foo {{\n          {}: -webkit-fill-available;\n          {}: -moz-available;\n          {}: stretch;\n        }}\n      \"#},\n          out_prop, out_prop, out_prop\n        ),\n        Browsers {\n          safari: Some(8 << 16),\n          firefox: Some(4 << 16),\n          ..Browsers::default()\n        },\n      );\n\n      prefix_test(\n        &format!(\n          r#\"\n        .foo {{\n          {}: -webkit-fill-available;\n        }}\n      \"#,\n          in_prop\n        ),\n        &format!(\n          indoc! {r#\"\n        .foo {{\n          {}: -webkit-fill-available;\n        }}\n      \"#},\n          out_prop\n        ),\n        Browsers {\n          safari: Some(8 << 16),\n          firefox: Some(4 << 16),\n          ..Browsers::default()\n        },\n      );\n\n      prefix_test(\n        &format!(\n          r#\"\n        .foo {{\n          {}: 100vw;\n          {}: -webkit-fill-available;\n        }}\n      \"#,\n          in_prop, in_prop\n        ),\n        &format!(\n          indoc! {r#\"\n        .foo {{\n          {}: 100vw;\n          {}: -webkit-fill-available;\n        }}\n      \"#},\n          out_prop, out_prop\n        ),\n        Browsers {\n          safari: Some(8 << 16),\n          firefox: Some(4 << 16),\n          ..Browsers::default()\n        },\n      );\n\n      prefix_test(\n        &format!(\n          r#\"\n        .foo {{\n          {}: fit-content;\n        }}\n      \"#,\n          in_prop\n        ),\n        &format!(\n          indoc! {r#\"\n        .foo {{\n          {}: -webkit-fit-content;\n          {}: -moz-fit-content;\n          {}: fit-content;\n        }}\n      \"#},\n          out_prop, out_prop, out_prop\n        ),\n        Browsers {\n          safari: Some(8 << 16),\n          firefox: Some(4 << 16),\n          ..Browsers::default()\n        },\n      );\n\n      prefix_test(\n        &format!(\n          r#\"\n        .foo {{\n          {}: fit-content(50%);\n        }}\n      \"#,\n          in_prop\n        ),\n        &format!(\n          indoc! {r#\"\n        .foo {{\n          {}: fit-content(50%);\n        }}\n      \"#},\n          out_prop\n        ),\n        Browsers {\n          safari: Some(8 << 16),\n          firefox: Some(4 << 16),\n          ..Browsers::default()\n        },\n      );\n\n      prefix_test(\n        &format!(\n          r#\"\n        .foo {{\n          {}: min-content;\n        }}\n      \"#,\n          in_prop\n        ),\n        &format!(\n          indoc! {r#\"\n        .foo {{\n          {}: -webkit-min-content;\n          {}: -moz-min-content;\n          {}: min-content;\n        }}\n      \"#},\n          out_prop, out_prop, out_prop\n        ),\n        Browsers {\n          safari: Some(8 << 16),\n          firefox: Some(4 << 16),\n          ..Browsers::default()\n        },\n      );\n\n      prefix_test(\n        &format!(\n          r#\"\n        .foo {{\n          {}: max-content;\n        }}\n      \"#,\n          in_prop\n        ),\n        &format!(\n          indoc! {r#\"\n        .foo {{\n          {}: -webkit-max-content;\n          {}: -moz-max-content;\n          {}: max-content;\n        }}\n      \"#},\n          out_prop, out_prop, out_prop\n        ),\n        Browsers {\n          safari: Some(8 << 16),\n          firefox: Some(4 << 16),\n          ..Browsers::default()\n        },\n      );\n\n      prefix_test(\n        &format!(\n          r#\"\n        .foo {{\n          {}: 100%;\n          {}: max-content;\n        }}\n      \"#,\n          in_prop, in_prop\n        ),\n        &format!(\n          indoc! {r#\"\n        .foo {{\n          {}: 100%;\n          {}: max-content;\n        }}\n      \"#},\n          out_prop, out_prop\n        ),\n        Browsers {\n          safari: Some(8 << 16),\n          firefox: Some(4 << 16),\n          ..Browsers::default()\n        },\n      );\n\n      prefix_test(\n        &format!(\n          r#\"\n        .foo {{\n          {}: var(--fallback);\n          {}: max-content;\n        }}\n      \"#,\n          in_prop, in_prop\n        ),\n        &format!(\n          indoc! {r#\"\n        .foo {{\n          {}: var(--fallback);\n          {}: max-content;\n        }}\n      \"#},\n          out_prop, out_prop\n        ),\n        Browsers {\n          safari: Some(8 << 16),\n          firefox: Some(4 << 16),\n          ..Browsers::default()\n        },\n      );\n    }\n\n    minify_test(\".foo { aspect-ratio: auto }\", \".foo{aspect-ratio:auto}\");\n    minify_test(\".foo { aspect-ratio: 2 / 3 }\", \".foo{aspect-ratio:2/3}\");\n    minify_test(\".foo { aspect-ratio: auto 2 / 3 }\", \".foo{aspect-ratio:auto 2/3}\");\n    minify_test(\".foo { aspect-ratio: 2 / 3 auto }\", \".foo{aspect-ratio:auto 2/3}\");\n\n    minify_test(\n      \".foo { width: 200px; width: var(--foo); }\",\n      \".foo{width:200px;width:var(--foo)}\",\n    );\n    minify_test(\n      \".foo { width: var(--foo); width: 200px; }\",\n      \".foo{width:var(--foo);width:200px}\",\n    );\n  }\n\n  #[test]\n  pub fn test_background() {\n    test(\n      r#\"\n      .foo {\n        background: url(img.png);\n        background-position-x: 20px;\n        background-position-y: 10px;\n        background-size: 50px 100px;\n        background-repeat: repeat no-repeat;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        background: url(\"img.png\") 20px 10px / 50px 100px repeat-x;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        background-color: red;\n        background-position: 0% 0%;\n        background-size: auto;\n        background-repeat: repeat;\n        background-clip: border-box;\n        background-origin: padding-box;\n        background-attachment: scroll;\n        background-image: none\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        background: red;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        background-color: gray;\n        background-position: 40% 50%;\n        background-size: 10em auto;\n        background-repeat: round;\n        background-clip: border-box;\n        background-origin: border-box;\n        background-attachment: fixed;\n        background-image: url('chess.png');\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        background: gray url(\"chess.png\") 40% / 10em round fixed border-box;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        background: url(img.png), url(test.jpg) gray;\n        background-position-x: right 20px, 10px;\n        background-position-y: top 20px, 15px;\n        background-size: 50px 50px, auto;\n        background-repeat: repeat no-repeat, no-repeat;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        background: url(\"img.png\") right 20px top 20px / 50px 50px repeat-x, gray url(\"test.jpg\") 10px 15px no-repeat;\n      }\n    \"#\n      },\n    );\n\n    minify_test(\n      r#\"\n      .foo {\n        background-position: center center;\n      }\n    \"#,\n      indoc! {\".foo{background-position:50%}\"\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        background: url(img.png) gray;\n        background-clip: content-box;\n        -webkit-background-clip: text;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        background: gray url(\"img.png\") padding-box content-box;\n        -webkit-background-clip: text;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        background: url(img.png) gray;\n        -webkit-background-clip: text;\n        background-clip: content-box;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        background: gray url(\"img.png\");\n        -webkit-background-clip: text;\n        background-clip: content-box;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        background: url(img.png) gray;\n        background-position: var(--pos);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        background: gray url(\"img.png\");\n        background-position: var(--pos);\n      }\n    \"#\n      },\n    );\n\n    minify_test(\n      \".foo { background-position: bottom left }\",\n      \".foo{background-position:0 100%}\",\n    );\n    minify_test(\n      \".foo { background-position: left 10px center }\",\n      \".foo{background-position:10px}\",\n    );\n    minify_test(\n      \".foo { background-position: right 10px center }\",\n      \".foo{background-position:right 10px center}\",\n    );\n    minify_test(\n      \".foo { background-position: center top 10px }\",\n      \".foo{background-position:50% 10px}\",\n    );\n    minify_test(\n      \".foo { background-position: center bottom 10px }\",\n      \".foo{background-position:center bottom 10px}\",\n    );\n    minify_test(\n      \".foo { background-position: center 10px }\",\n      \".foo{background-position:50% 10px}\",\n    );\n    minify_test(\n      \".foo { background-position: right 10px top 20px }\",\n      \".foo{background-position:right 10px top 20px}\",\n    );\n    minify_test(\n      \".foo { background-position: left 10px top 20px }\",\n      \".foo{background-position:10px 20px}\",\n    );\n    minify_test(\n      \".foo { background-position: left 10px bottom 20px }\",\n      \".foo{background-position:left 10px bottom 20px}\",\n    );\n    minify_test(\n      \".foo { background-position: left 10px top }\",\n      \".foo{background-position:10px 0}\",\n    );\n    minify_test(\n      \".foo { background-position: bottom right }\",\n      \".foo{background-position:100% 100%}\",\n    );\n    minify_test(\n      \".foo { background-position: center top }\",\n      \".foo{background-position:top}\",\n    );\n    minify_test(\n      \".foo { background-position: center bottom }\",\n      \".foo{background-position:bottom}\",\n    );\n    minify_test(\n      \".foo { background-position: left center }\",\n      \".foo{background-position:0}\",\n    );\n    minify_test(\n      \".foo { background-position: right center }\",\n      \".foo{background-position:100%}\",\n    );\n    minify_test(\n      \".foo { background-position: 20px center }\",\n      \".foo{background-position:20px}\",\n    );\n\n    minify_test(\n      \".foo { background: url('img-sprite.png') no-repeat bottom right }\",\n      \".foo{background:url(img-sprite.png) 100% 100% no-repeat}\",\n    );\n    minify_test(\".foo { background: transparent }\", \".foo{background:0 0}\");\n\n    minify_test(\".foo { background: url(\\\"data:image/svg+xml,%3Csvg width='168' height='24' xmlns='http://www.w3.org/2000/svg'%3E%3C/svg%3E\\\") }\", \".foo{background:url(\\\"data:image/svg+xml,%3Csvg width='168' height='24' xmlns='http://www.w3.org/2000/svg'%3E%3C/svg%3E\\\")}\");\n\n    test(\n      r#\"\n      .foo {\n        background: url(img.png);\n        background-clip: text;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        background: url(\"img.png\") text;\n      }\n    \"#\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        background: url(img.png);\n        background-clip: text;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        background: url(\"img.png\");\n        -webkit-background-clip: text;\n        background-clip: text;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        background: url(img.png);\n        background-clip: text;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        background: url(\"img.png\") text;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        background: url(img.png) text;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        background: url(\"img.png\");\n        -webkit-background-clip: text;\n        background-clip: text;\n      }\n    \"#\n      },\n      Browsers {\n        chrome: Some(45 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        background: url(img.png);\n        -webkit-background-clip: text;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        background: url(\"img.png\");\n        -webkit-background-clip: text;\n      }\n    \"#\n      },\n      Browsers {\n        chrome: Some(45 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        background: url(img.png);\n        background-clip: text;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        background: url(\"img.png\");\n        -webkit-background-clip: text;\n        background-clip: text;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(14 << 16),\n        chrome: Some(95 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        background-image: url(img.png);\n        background-clip: text;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        background-image: url(\"img.png\");\n        -webkit-background-clip: text;\n        background-clip: text;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        -webkit-background-clip: text;\n        background-clip: text;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-background-clip: text;\n        background-clip: text;\n      }\n    \"#\n      },\n      Browsers {\n        chrome: Some(45 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        background-image: url(img.png);\n        background-clip: text;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        background-image: url(\"img.png\");\n        background-clip: text;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    minify_test(\".foo { background: none center }\", \".foo{background:50%}\");\n    minify_test(\".foo { background: none }\", \".foo{background:0 0}\");\n\n    prefix_test(\n      r#\"\n      .foo {\n        background: lab(51.5117% 43.3777 -29.0443);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        background: #af5cae;\n        background: lab(51.5117% 43.3777 -29.0443);\n      }\n    \"#\n      },\n      Browsers {\n        chrome: Some(95 << 16),\n        safari: Some(15 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        background: lab(51.5117% 43.3777 -29.0443) url(foo.png);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        background: #af5cae url(\"foo.png\");\n        background: lab(51.5117% 43.3777 -29.0443) url(\"foo.png\");\n      }\n    \"#\n      },\n      Browsers {\n        chrome: Some(95 << 16),\n        safari: Some(15 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        background: lab(51.5117% 43.3777 -29.0443) linear-gradient(lab(52.2319% 40.1449 59.9171), lab(47.7776% -34.2947 -7.65904));\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        background: #af5cae linear-gradient(#c65d07, #00807c);\n        background: lab(51.5117% 43.3777 -29.0443) linear-gradient(lab(52.2319% 40.1449 59.9171), lab(47.7776% -34.2947 -7.65904));\n      }\n    \"#\n      },\n      Browsers {\n        chrome: Some(95 << 16),\n        safari: Some(15 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    test(\n      \".foo { background: calc(var(--v) / 0.3)\",\n      indoc! {r#\"\n      .foo {\n        background: calc(var(--v) / .3);\n      }\n    \"#},\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        background-color: #4263eb;\n        background-color: color(display-p3 0 .5 1);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        background-color: #4263eb;\n        background-color: color(display-p3 0 .5 1);\n      }\n    \"#\n      },\n      Browsers {\n        chrome: Some(99 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        background-color: #4263eb;\n        background-color: color(display-p3 0 .5 1);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        background-color: color(display-p3 0 .5 1);\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(16 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        background-image: linear-gradient(red, green);\n        background-image: linear-gradient(lch(50% 132 50), lch(50% 130 150));\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        background-image: linear-gradient(red, green);\n        background-image: linear-gradient(lch(50% 132 50), lch(50% 130 150));\n      }\n    \"#\n      },\n      Browsers {\n        chrome: Some(99 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        background-image: linear-gradient(red, green);\n        background-image: linear-gradient(lch(50% 132 50), lch(50% 130 150));\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        background-image: linear-gradient(lch(50% 132 50), lch(50% 130 150));\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(16 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        background: #4263eb;\n        background: color(display-p3 0 .5 1);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        background: #4263eb;\n        background: color(display-p3 0 .5 1);\n      }\n    \"#\n      },\n      Browsers {\n        chrome: Some(99 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        background: #4263eb;\n        background: color(display-p3 0 .5 1);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        background: color(display-p3 0 .5 1);\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(16 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        background: linear-gradient(red, green);\n        background: linear-gradient(lch(50% 132 50), lch(50% 130 150));\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        background: linear-gradient(red, green);\n        background: linear-gradient(lch(50% 132 50), lch(50% 130 150));\n      }\n    \"#\n      },\n      Browsers {\n        chrome: Some(99 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        background: red;\n        background: linear-gradient(lch(50% 132 50), lch(50% 130 150));\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        background: red;\n        background: linear-gradient(lch(50% 132 50), lch(50% 130 150));\n      }\n    \"#\n      },\n      Browsers {\n        chrome: Some(99 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        background: linear-gradient(red, green);\n        background: linear-gradient(lch(50% 132 50), lch(50% 130 150));\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        background: linear-gradient(lch(50% 132 50), lch(50% 130 150));\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(16 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        background: var(--fallback);\n        background: linear-gradient(lch(50% 132 50), lch(50% 130 150));\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        background: var(--fallback);\n        background: linear-gradient(lch(50% 132 50), lch(50% 130 150));\n      }\n    \"#\n      },\n      Browsers {\n        chrome: Some(99 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        background: red url(foo.png);\n        background: lch(50% 132 50) url(foo.png);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        background: red url(\"foo.png\");\n        background: lch(50% 132 50) url(\"foo.png\");\n      }\n    \"#\n      },\n      Browsers {\n        chrome: Some(99 << 16),\n        ..Browsers::default()\n      },\n    );\n  }\n\n  #[test]\n  pub fn test_flex() {\n    test(\n      r#\"\n      .foo {\n        flex-direction: column;\n        flex-wrap: wrap;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        flex-flow: column wrap;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        flex-direction: row;\n        flex-wrap: wrap;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        flex-flow: wrap;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        flex-direction: row;\n        flex-wrap: nowrap;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        flex-flow: row;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        flex-direction: column;\n        flex-wrap: nowrap;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        flex-flow: column;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        flex-grow: 1;\n        flex-shrink: 1;\n        flex-basis: 0%;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        flex: 1;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        flex-grow: 1;\n        flex-shrink: 1;\n        flex-basis: 0;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        flex: 1 1 0;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        flex-grow: 1;\n        flex-shrink: 1;\n        flex-basis: 0px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        flex: 1 1 0;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        flex-grow: 1;\n        flex-shrink: 2;\n        flex-basis: 0%;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        flex: 1 2;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        flex-grow: 2;\n        flex-shrink: 1;\n        flex-basis: 0%;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        flex: 2;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        flex-grow: 2;\n        flex-shrink: 2;\n        flex-basis: 0%;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        flex: 2 2;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        flex-grow: 1;\n        flex-shrink: 1;\n        flex-basis: 10px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        flex: 10px;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        flex-grow: 2;\n        flex-shrink: 1;\n        flex-basis: 10px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        flex: 2 10px;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        flex-grow: 1;\n        flex-shrink: 0;\n        flex-basis: 0%;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        flex: 1 0;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        flex-grow: 1;\n        flex-shrink: 0;\n        flex-basis: auto;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        flex: 1 0 auto;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        flex-grow: 1;\n        flex-shrink: 1;\n        flex-basis: auto;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        flex: auto;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        flex: 0 0;\n        flex-grow: 1;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        flex: 1 0;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        flex: 0 0;\n        flex-grow: var(--grow);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        flex: 0 0;\n        flex-grow: var(--grow);\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        align-content: center;\n        justify-content: center;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        place-content: center;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        align-content: first baseline;\n        justify-content: safe right;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        place-content: baseline safe right;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        place-content: first baseline unsafe left;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        place-content: baseline unsafe left;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        place-content: center center;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        place-content: center;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        align-self: center;\n        justify-self: center;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        place-self: center;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        align-self: center;\n        justify-self: unsafe left;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        place-self: center unsafe left;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        align-items: center;\n        justify-items: center;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        place-items: center;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        align-items: center;\n        justify-items: legacy left;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        place-items: center legacy left;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        place-items: center;\n        justify-items: var(--justify);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        place-items: center;\n        justify-items: var(--justify);\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        row-gap: 10px;\n        column-gap: 20px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        gap: 10px 20px;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        row-gap: 10px;\n        column-gap: 10px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        gap: 10px;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        gap: 10px;\n        column-gap: 20px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        gap: 10px 20px;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        column-gap: 20px;\n        gap: 10px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        gap: 10px;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        row-gap: normal;\n        column-gap: 20px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        gap: normal 20px;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        -webkit-flex-grow: 1;\n        -webkit-flex-shrink: 1;\n        -webkit-flex-basis: auto;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-flex: auto;\n      }\n    \"#\n      },\n    );\n    test(\n      r#\"\n      .foo {\n        -webkit-flex-grow: 1;\n        -webkit-flex-shrink: 1;\n        -webkit-flex-basis: auto;\n        flex-grow: 1;\n        flex-shrink: 1;\n        flex-basis: auto;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-flex: auto;\n        flex: auto;\n      }\n    \"#\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        -webkit-box-orient: horizontal;\n        -webkit-box-direction: normal;\n        flex-direction: row;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-box-orient: horizontal;\n        -webkit-box-direction: normal;\n        -webkit-flex-direction: row;\n        flex-direction: row;\n      }\n    \"#},\n      Browsers {\n        safari: Some(4 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        flex-direction: row;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-box-orient: horizontal;\n        -moz-box-orient: horizontal;\n        -webkit-box-direction: normal;\n        -moz-box-direction: normal;\n        -webkit-flex-direction: row;\n        -ms-flex-direction: row;\n        flex-direction: row;\n      }\n    \"#},\n      Browsers {\n        safari: Some(4 << 16),\n        firefox: Some(4 << 16),\n        ie: Some(10 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        -webkit-box-orient: horizontal;\n        -webkit-box-direction: normal;\n        -moz-box-orient: horizontal;\n        -moz-box-direction: normal;\n        -webkit-flex-direction: row;\n        -ms-flex-direction: row;\n        flex-direction: row;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        flex-direction: row;\n      }\n    \"#},\n      Browsers {\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        flex-wrap: wrap;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-box-lines: multiple;\n        -moz-box-lines: multiple;\n        -webkit-flex-wrap: wrap;\n        -ms-flex-wrap: wrap;\n        flex-wrap: wrap;\n      }\n    \"#},\n      Browsers {\n        safari: Some(4 << 16),\n        firefox: Some(4 << 16),\n        ie: Some(10 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        -webkit-box-lines: multiple;\n        -moz-box-lines: multiple;\n        -webkit-flex-wrap: wrap;\n        -ms-flex-wrap: wrap;\n        flex-wrap: wrap;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        flex-wrap: wrap;\n      }\n    \"#},\n      Browsers {\n        safari: Some(11 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        flex-flow: row wrap;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-box-orient: horizontal;\n        -moz-box-orient: horizontal;\n        -webkit-box-direction: normal;\n        -moz-box-direction: normal;\n        -webkit-flex-flow: wrap;\n        -ms-flex-flow: wrap;\n        flex-flow: wrap;\n      }\n    \"#},\n      Browsers {\n        safari: Some(4 << 16),\n        firefox: Some(4 << 16),\n        ie: Some(10 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        -webkit-box-orient: horizontal;\n        -moz-box-orient: horizontal;\n        -webkit-box-direction: normal;\n        -moz-box-direction: normal;\n        -webkit-flex-flow: wrap;\n        -ms-flex-flow: wrap;\n        flex-flow: wrap;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        flex-flow: wrap;\n      }\n    \"#},\n      Browsers {\n        safari: Some(11 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        flex-grow: 1;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-box-flex: 1;\n        -moz-box-flex: 1;\n        -ms-flex-positive: 1;\n        -webkit-flex-grow: 1;\n        flex-grow: 1;\n      }\n    \"#},\n      Browsers {\n        safari: Some(4 << 16),\n        firefox: Some(4 << 16),\n        ie: Some(10 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        -webkit-box-flex: 1;\n        -moz-box-flex: 1;\n        -ms-flex-positive: 1;\n        -webkit-flex-grow: 1;\n        flex-grow: 1;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        flex-grow: 1;\n      }\n    \"#},\n      Browsers {\n        safari: Some(11 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        flex-shrink: 1;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -ms-flex-negative: 1;\n        -webkit-flex-shrink: 1;\n        flex-shrink: 1;\n      }\n    \"#},\n      Browsers {\n        safari: Some(4 << 16),\n        firefox: Some(4 << 16),\n        ie: Some(10 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        -ms-flex-negative: 1;\n        -webkit-flex-shrink: 1;\n        flex-shrink: 1;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        flex-shrink: 1;\n      }\n    \"#},\n      Browsers {\n        safari: Some(11 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        flex-basis: 1px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -ms-flex-preferred-size: 1px;\n        -webkit-flex-basis: 1px;\n        flex-basis: 1px;\n      }\n    \"#},\n      Browsers {\n        safari: Some(4 << 16),\n        firefox: Some(4 << 16),\n        ie: Some(10 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        -ms-flex-preferred-size: 1px;\n        -webkit-flex-basis: 1px;\n        flex-basis: 1px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        flex-basis: 1px;\n      }\n    \"#},\n      Browsers {\n        safari: Some(11 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        flex: 1;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-box-flex: 1;\n        -moz-box-flex: 1;\n        -webkit-flex: 1;\n        -ms-flex: 1;\n        flex: 1;\n      }\n    \"#},\n      Browsers {\n        safari: Some(4 << 16),\n        firefox: Some(4 << 16),\n        ie: Some(10 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        -webkit-box-flex: 1;\n        -moz-box-flex: 1;\n        -webkit-flex: 1;\n        -ms-flex: 1;\n        flex: 1;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        flex: 1;\n      }\n    \"#},\n      Browsers {\n        safari: Some(11 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        align-content: space-between;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -ms-flex-line-pack: justify;\n        -webkit-align-content: space-between;\n        align-content: space-between;\n      }\n    \"#},\n      Browsers {\n        safari: Some(4 << 16),\n        firefox: Some(4 << 16),\n        ie: Some(10 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        -ms-flex-line-pack: justify;\n        -webkit-align-content: space-between;\n        align-content: space-between;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        align-content: space-between;\n      }\n    \"#},\n      Browsers {\n        safari: Some(11 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        justify-content: space-between;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-box-pack: justify;\n        -moz-box-pack: justify;\n        -ms-flex-pack: justify;\n        -webkit-justify-content: space-between;\n        justify-content: space-between;\n      }\n    \"#},\n      Browsers {\n        safari: Some(4 << 16),\n        firefox: Some(4 << 16),\n        ie: Some(10 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        -webkit-box-pack: justify;\n        -moz-box-pack: justify;\n        -ms-flex-pack: justify;\n        -webkit-justify-content: space-between;\n        justify-content: space-between;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        justify-content: space-between;\n      }\n    \"#},\n      Browsers {\n        safari: Some(11 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        place-content: space-between flex-end;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -ms-flex-line-pack: justify;\n        -webkit-box-pack: end;\n        -moz-box-pack: end;\n        -ms-flex-pack: end;\n        -webkit-align-content: space-between;\n        align-content: space-between;\n        -webkit-justify-content: flex-end;\n        justify-content: flex-end;\n      }\n    \"#},\n      Browsers {\n        safari: Some(4 << 16),\n        firefox: Some(4 << 16),\n        ie: Some(10 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        -ms-flex-line-pack: justify;\n        -webkit-box-pack: end;\n        -moz-box-pack: end;\n        -ms-flex-pack: end;\n        -webkit-align-content: space-between;\n        -webkit-justify-content: flex-end;\n        place-content: space-between flex-end;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        place-content: space-between flex-end;\n      }\n    \"#},\n      Browsers {\n        safari: Some(11 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        place-content: space-between flex-end;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        align-content: space-between;\n        justify-content: flex-end;\n      }\n    \"#},\n      Browsers {\n        chrome: Some(30 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        place-content: space-between flex-end;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        place-content: space-between flex-end;\n      }\n    \"#},\n      Browsers {\n        chrome: Some(60 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        align-self: flex-end;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -ms-flex-item-align: end;\n        -webkit-align-self: flex-end;\n        align-self: flex-end;\n      }\n    \"#},\n      Browsers {\n        safari: Some(4 << 16),\n        firefox: Some(4 << 16),\n        ie: Some(10 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        -ms-flex-item-align: end;\n        -webkit-align-self: flex-end;\n        align-self: flex-end;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        align-self: flex-end;\n      }\n    \"#},\n      Browsers {\n        safari: Some(11 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        place-self: center flex-end;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -ms-flex-item-align: center;\n        -webkit-align-self: center;\n        align-self: center;\n        justify-self: flex-end;\n      }\n    \"#},\n      Browsers {\n        safari: Some(4 << 16),\n        firefox: Some(4 << 16),\n        ie: Some(10 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        -ms-flex-item-align: center;\n        -webkit-align-self: center;\n        place-self: center flex-end;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        place-self: center flex-end;\n      }\n    \"#},\n      Browsers {\n        safari: Some(11 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        place-self: center flex-end;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        align-self: center;\n        justify-self: flex-end;\n      }\n    \"#},\n      Browsers {\n        chrome: Some(57 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        place-self: center flex-end;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        place-self: center flex-end;\n      }\n    \"#},\n      Browsers {\n        chrome: Some(59 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        align-items: flex-end;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-box-align: end;\n        -moz-box-align: end;\n        -ms-flex-align: end;\n        -webkit-align-items: flex-end;\n        align-items: flex-end;\n      }\n    \"#},\n      Browsers {\n        safari: Some(4 << 16),\n        firefox: Some(4 << 16),\n        ie: Some(10 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        -webkit-box-align: end;\n        -moz-box-align: end;\n        -ms-flex-align: end;\n        -webkit-align-items: flex-end;\n        align-items: flex-end;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        align-items: flex-end;\n      }\n    \"#},\n      Browsers {\n        safari: Some(11 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        place-items: flex-end center;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-box-align: end;\n        -moz-box-align: end;\n        -ms-flex-align: end;\n        -webkit-align-items: flex-end;\n        align-items: flex-end;\n        justify-items: center;\n      }\n    \"#},\n      Browsers {\n        safari: Some(4 << 16),\n        firefox: Some(4 << 16),\n        ie: Some(10 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        -webkit-box-align: end;\n        -moz-box-align: end;\n        -ms-flex-align: end;\n        -webkit-align-items: flex-end;\n        place-items: flex-end center;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        place-items: flex-end center;\n      }\n    \"#},\n      Browsers {\n        safari: Some(11 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        place-items: flex-end center;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        align-items: flex-end;\n        justify-items: center;\n      }\n    \"#},\n      Browsers {\n        safari: Some(10 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        order: 1;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-box-ordinal-group: 1;\n        -moz-box-ordinal-group: 1;\n        -ms-flex-order: 1;\n        -webkit-order: 1;\n        order: 1;\n      }\n    \"#},\n      Browsers {\n        safari: Some(4 << 16),\n        firefox: Some(4 << 16),\n        ie: Some(10 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        -webkit-box-ordinal-group: 1;\n        -moz-box-ordinal-group: 1;\n        -ms-flex-order: 1;\n        -webkit-order: 1;\n        order: 1;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        order: 1;\n      }\n    \"#},\n      Browsers {\n        safari: Some(11 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        -ms-flex: 0 0 8%;\n        flex: 0 0 5%;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -ms-flex: 0 0 8%;\n        flex: 0 0 5%;\n      }\n    \"#},\n      Browsers {\n        safari: Some(11 << 16),\n        ..Browsers::default()\n      },\n    );\n  }\n\n  #[test]\n  fn test_font() {\n    test(\n      r#\"\n      .foo {\n        font-family: \"Helvetica\", \"Times New Roman\", sans-serif;\n        font-size: 12px;\n        font-weight: bold;\n        font-style: italic;\n        font-stretch: expanded;\n        font-variant-caps: small-caps;\n        line-height: 1.2em;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        font: italic small-caps bold expanded 12px / 1.2em Helvetica, Times New Roman, sans-serif;\n      }\n    \"#\n      },\n    );\n\n    minify_test(\n      r#\"\n      .foo {\n        font-family: \"Helvetica\", \"Times New Roman\", sans-serif;\n        font-size: 12px;\n        font-weight: bold;\n        font-style: italic;\n        font-stretch: expanded;\n        font-variant-caps: small-caps;\n        line-height: 1.2em;\n      }\n    \"#,\n      indoc! {\".foo{font:italic small-caps 700 125% 12px/1.2em Helvetica,Times New Roman,sans-serif}\"\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        font: 12px \"Helvetica\", \"Times New Roman\", sans-serif;\n        line-height: 1.2em;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        font: 12px / 1.2em Helvetica, Times New Roman, sans-serif;\n      }\n    \"#\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        font: 12px \"Helvetica\", \"Times New Roman\", sans-serif;\n        line-height: var(--lh);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        font: 12px Helvetica, Times New Roman, sans-serif;\n        line-height: var(--lh);\n      }\n    \"#\n      },\n    );\n\n    minify_test(\n      r#\"\n      .foo {\n        font-family: \"Helvetica\", \"Times New Roman\", sans-serif;\n        font-size: 12px;\n        font-stretch: expanded;\n      }\n    \"#,\n      indoc! {\".foo{font-family:Helvetica,Times New Roman,sans-serif;font-size:12px;font-stretch:125%}\"\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        font-family: \"Helvetica\", \"Times New Roman\", sans-serif;\n        font-size: 12px;\n        font-weight: bold;\n        font-style: italic;\n        font-stretch: expanded;\n        font-variant-caps: all-small-caps;\n        line-height: 1.2em;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        font: italic bold expanded 12px / 1.2em Helvetica, Times New Roman, sans-serif;\n        font-variant-caps: all-small-caps;\n      }\n    \"#\n      },\n    );\n\n    minify_test(\n      \".foo { font: normal normal 600 9px/normal Charcoal; }\",\n      \".foo{font:600 9px Charcoal}\",\n    );\n    minify_test(\n      \".foo { font: normal normal 500 medium/normal Charcoal; }\",\n      \".foo{font:500 medium Charcoal}\",\n    );\n    minify_test(\n      \".foo { font: normal normal 400 medium Charcoal; }\",\n      \".foo{font:400 medium Charcoal}\",\n    );\n    minify_test(\n      \".foo { font: normal normal 500 medium/10px Charcoal; }\",\n      \".foo{font:500 medium/10px Charcoal}\",\n    );\n    minify_test(\n      \".foo { font-family: 'sans-serif'; }\",\n      \".foo{font-family:\\\"sans-serif\\\"}\",\n    );\n    minify_test(\".foo { font-family: sans-serif; }\", \".foo{font-family:sans-serif}\");\n    minify_test(\".foo { font-family: 'default'; }\", \".foo{font-family:\\\"default\\\"}\");\n    minify_test(\".foo { font-family: default; }\", \".foo{font-family:default}\");\n    minify_test(\".foo { font-family: 'inherit'; }\", \".foo{font-family:\\\"inherit\\\"}\");\n    minify_test(\".foo { font-family: inherit; }\", \".foo{font-family:inherit}\");\n    minify_test(\".foo { font-family: inherit test; }\", \".foo{font-family:inherit test}\");\n    minify_test(\n      \".foo { font-family: 'inherit test'; }\",\n      \".foo{font-family:inherit test}\",\n    );\n    minify_test(\".foo { font-family: revert; }\", \".foo{font-family:revert}\");\n    minify_test(\".foo { font-family: 'revert'; }\", \".foo{font-family:\\\"revert\\\"}\");\n    minify_test(\".foo { font-family: revert-layer; }\", \".foo{font-family:revert-layer}\");\n    minify_test(\n      \".foo { font-family: revert-layer, serif; }\",\n      \".foo{font-family:revert-layer,serif}\",\n    );\n    minify_test(\n      \".foo { font-family: 'revert', sans-serif; }\",\n      \".foo{font-family:\\\"revert\\\",sans-serif}\",\n    );\n    minify_test(\n      \".foo { font-family: 'revert', foo, sans-serif; }\",\n      \".foo{font-family:\\\"revert\\\",foo,sans-serif}\",\n    );\n    minify_test(\".foo { font-family: ''; }\", \".foo{font-family:\\\"\\\"}\");\n\n    // font-family in @font-face\n    minify_test(\n      \"@font-face { font-family: 'revert'; }\",\n      \"@font-face{font-family:\\\"revert\\\"}\",\n    );\n    minify_test(\n      \"@font-face { font-family: 'revert-layer'; }\",\n      \"@font-face{font-family:\\\"revert-layer\\\"}\",\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        font-family: Helvetica, system-ui, sans-serif;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        font-family: Helvetica, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Noto Sans, Ubuntu, Cantarell, Helvetica Neue, sans-serif;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        font: 100%/1.5 Helvetica, system-ui, sans-serif;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        font: 100% / 1.5 Helvetica, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Noto Sans, Ubuntu, Cantarell, Helvetica Neue, sans-serif;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"Noto Sans\", sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Noto Sans, Ubuntu, Cantarell, Helvetica Neue, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;\n      }\n    \"#\n      },\n      Browsers {\n        firefox: Some(91 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        font-size: 22px;\n        font-size: max(2cqw, 22px);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        font-size: 22px;\n        font-size: max(2cqw, 22px);\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        font-size: 22px;\n        font-size: max(2cqw, 22px);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        font-size: max(2cqw, 22px);\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(16 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        font-size: 22px;\n        font-size: xxx-large;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        font-size: 22px;\n        font-size: xxx-large;\n      }\n    \"#\n      },\n      Browsers {\n        chrome: Some(70 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        font-size: 22px;\n        font-size: xxx-large;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        font-size: xxx-large;\n      }\n    \"#\n      },\n      Browsers {\n        chrome: Some(80 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        font-weight: 700;\n        font-weight: 789;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        font-weight: 700;\n        font-weight: 789;\n      }\n    \"#\n      },\n      Browsers {\n        chrome: Some(60 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        font-weight: 700;\n        font-weight: 789;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        font-weight: 789;\n      }\n    \"#\n      },\n      Browsers {\n        chrome: Some(80 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        font-family: Helvetica;\n        font-family: system-ui;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        font-family: Helvetica;\n        font-family: system-ui;\n      }\n    \"#\n      },\n      Browsers {\n        chrome: Some(50 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        font-family: Helvetica;\n        font-family: system-ui;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        font-family: system-ui;\n      }\n    \"#\n      },\n      Browsers {\n        chrome: Some(80 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        font-style: oblique;\n        font-style: oblique 40deg;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        font-style: oblique;\n        font-style: oblique 40deg;\n      }\n    \"#\n      },\n      Browsers {\n        firefox: Some(50 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        font-style: oblique;\n        font-style: oblique 40deg;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        font-style: oblique 40deg;\n      }\n    \"#\n      },\n      Browsers {\n        firefox: Some(80 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        font: 22px Helvetica;\n        font: xxx-large system-ui;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        font: 22px Helvetica;\n        font: xxx-large system-ui;\n      }\n    \"#\n      },\n      Browsers {\n        chrome: Some(70 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        font: 22px Helvetica;\n        font: xxx-large system-ui;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        font: xxx-large system-ui;\n      }\n    \"#\n      },\n      Browsers {\n        chrome: Some(80 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        font: var(--fallback);\n        font: xxx-large system-ui;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        font: var(--fallback);\n        font: xxx-large system-ui;\n      }\n    \"#\n      },\n      Browsers {\n        chrome: Some(50 << 16),\n        ..Browsers::default()\n      },\n    );\n  }\n\n  #[test]\n  fn test_vertical_align() {\n    minify_test(\".foo { vertical-align: middle }\", \".foo{vertical-align:middle}\");\n    minify_test(\".foo { vertical-align: 0.3em }\", \".foo{vertical-align:.3em}\");\n  }\n\n  #[test]\n  fn test_selectors() {\n    minify_test(\":nth-col(2n) {width: 20px}\", \":nth-col(2n){width:20px}\");\n    minify_test(\":nth-col(10n-1) {width: 20px}\", \":nth-col(10n-1){width:20px}\");\n    minify_test(\":nth-col(-n+2) {width: 20px}\", \":nth-col(-n+2){width:20px}\");\n    minify_test(\":nth-col(even) {width: 20px}\", \":nth-col(2n){width:20px}\");\n    minify_test(\":nth-col(odd) {width: 20px}\", \":nth-col(odd){width:20px}\");\n    minify_test(\":nth-last-col(2n) {width: 20px}\", \":nth-last-col(2n){width:20px}\");\n    minify_test(\":nth-last-col(10n-1) {width: 20px}\", \":nth-last-col(10n-1){width:20px}\");\n    minify_test(\":nth-last-col(-n+2) {width: 20px}\", \":nth-last-col(-n+2){width:20px}\");\n    minify_test(\":nth-last-col(even) {width: 20px}\", \":nth-last-col(2n){width:20px}\");\n    minify_test(\":nth-last-col(odd) {width: 20px}\", \":nth-last-col(odd){width:20px}\");\n    minify_test(\":nth-child(odd) {width: 20px}\", \":nth-child(odd){width:20px}\");\n    minify_test(\":nth-child(2n) {width: 20px}\", \":nth-child(2n){width:20px}\");\n    minify_test(\":nth-child(2n+1) {width: 20px}\", \":nth-child(odd){width:20px}\");\n    minify_test(\":first-child {width: 20px}\", \":first-child{width:20px}\");\n    minify_test(\":nth-child(1) {width: 20px}\", \":first-child{width:20px}\");\n    minify_test(\":nth-last-child(1) {width: 20px}\", \":last-child{width:20px}\");\n    minify_test(\":nth-of-type(1) {width: 20px}\", \":first-of-type{width:20px}\");\n    minify_test(\":nth-last-of-type(1) {width: 20px}\", \":last-of-type{width:20px}\");\n    minify_test(\n      \":nth-child(even of li.important) {width: 20px}\",\n      \":nth-child(2n of li.important){width:20px}\",\n    );\n    minify_test(\n      \":nth-child(1 of li.important) {width: 20px}\",\n      \":nth-child(1 of li.important){width:20px}\",\n    );\n    minify_test(\n      \":nth-last-child(even of li.important) {width: 20px}\",\n      \":nth-last-child(2n of li.important){width:20px}\",\n    );\n    minify_test(\n      \":nth-last-child(1 of li.important) {width: 20px}\",\n      \":nth-last-child(1 of li.important){width:20px}\",\n    );\n    minify_test(\n      \":nth-last-child(1 of.important) {width: 20px}\",\n      \":nth-last-child(1 of .important){width:20px}\",\n    );\n\n    minify_test(\"[foo=\\\"baz\\\"] {color:red}\", \"[foo=baz]{color:red}\");\n    minify_test(\"[foo=\\\"foo bar\\\"] {color:red}\", \"[foo=foo\\\\ bar]{color:red}\");\n    minify_test(\"[foo=\\\"foo bar baz\\\"] {color:red}\", \"[foo=\\\"foo bar baz\\\"]{color:red}\");\n    minify_test(\"[foo=\\\"\\\"] {color:red}\", \"[foo=\\\"\\\"]{color:red}\");\n    minify_test(\n      \".test:not([foo=\\\"bar\\\"]) {color:red}\",\n      \".test:not([foo=bar]){color:red}\",\n    );\n    minify_test(\".test + .foo {color:red}\", \".test+.foo{color:red}\");\n    minify_test(\".test ~ .foo {color:red}\", \".test~.foo{color:red}\");\n    minify_test(\".test .foo {color:red}\", \".test .foo{color:red}\");\n    minify_test(\n      \".custom-range::-webkit-slider-thumb:active {color:red}\",\n      \".custom-range::-webkit-slider-thumb:active{color:red}\",\n    );\n    minify_test(\".test:not(.foo, .bar) {color:red}\", \".test:not(.foo,.bar){color:red}\");\n    minify_test(\".test:is(.foo, .bar) {color:red}\", \".test:is(.foo,.bar){color:red}\");\n    minify_test(\n      \".test:where(.foo, .bar) {color:red}\",\n      \".test:where(.foo,.bar){color:red}\",\n    );\n    minify_test(\n      \".test:where(.foo, .bar) {color:red}\",\n      \".test:where(.foo,.bar){color:red}\",\n    );\n    minify_test(\":host {color:red}\", \":host{color:red}\");\n    minify_test(\":host(.foo) {color:red}\", \":host(.foo){color:red}\");\n    minify_test(\"::slotted(span) {color:red\", \"::slotted(span){color:red}\");\n    minify_test(\n      \"custom-element::part(foo) {color:red}\",\n      \"custom-element::part(foo){color:red}\",\n    );\n    minify_test(\".sm\\\\:text-5xl { font-size: 3rem }\", \".sm\\\\:text-5xl{font-size:3rem}\");\n    minify_test(\"a:has(> img) {color:red}\", \"a:has(>img){color:red}\");\n    minify_test(\"dt:has(+ dt) {color:red}\", \"dt:has(+dt){color:red}\");\n    minify_test(\n      \"section:not(:has(h1, h2, h3, h4, h5, h6)) {color:red}\",\n      \"section:not(:has(h1,h2,h3,h4,h5,h6)){color:red}\",\n    );\n    minify_test(\n      \":has(.sibling ~ .target) {color:red}\",\n      \":has(.sibling~.target){color:red}\",\n    );\n    minify_test(\".x:has(> .a > .b) {color:red}\", \".x:has(>.a>.b){color:red}\");\n    minify_test(\".x:has(.bar, #foo) {color:red}\", \".x:has(.bar,#foo){color:red}\");\n    minify_test(\".x:has(span + span) {color:red}\", \".x:has(span+span){color:red}\");\n    minify_test(\"a:has(:visited) {color:red}\", \"a:has(:visited){color:red}\");\n    for element in [\n      \"-webkit-scrollbar\",\n      \"-webkit-scrollbar-button\",\n      \"-webkit-scrollbar-track\",\n      \"-webkit-scrollbar-track-piece\",\n      \"-webkit-scrollbar-thumb\",\n      \"-webkit-scrollbar-corner\",\n      \"-webkit-resizer\",\n    ] {\n      for class in [\n        \"enabled\",\n        \"disabled\",\n        \"hover\",\n        \"active\",\n        \"horizontal\",\n        \"vertical\",\n        \"decrement\",\n        \"increment\",\n        \"start\",\n        \"end\",\n        \"double-button\",\n        \"single-button\",\n        \"no-button\",\n        \"corner-present\",\n        \"window-inactive\",\n      ] {\n        minify_test(\n          &format!(\"::{}:{} {{color:red}}\", element, class),\n          &format!(\"::{}:{}{{color:red}}\", element, class),\n        );\n      }\n    }\n    for class in [\n      \"horizontal\",\n      \"vertical\",\n      \"decrement\",\n      \"increment\",\n      \"start\",\n      \"end\",\n      \"double-button\",\n      \"single-button\",\n      \"no-button\",\n      \"corner-present\",\n      \"window-inactive\",\n    ] {\n      error_test(\n        &format!(\":{} {{color:red}}\", class),\n        ParserError::SelectorError(SelectorError::InvalidPseudoClassBeforeWebKitScrollbar),\n      );\n    }\n    for element in [\n      \"-webkit-scrollbar\",\n      \"-webkit-scrollbar-button\",\n      \"-webkit-scrollbar-track\",\n      \"-webkit-scrollbar-track-piece\",\n      \"-webkit-scrollbar-thumb\",\n      \"-webkit-scrollbar-corner\",\n      \"-webkit-resizer\",\n    ] {\n      error_test(\n        &format!(\"::{}:focus {{color:red}}\", element),\n        ParserError::SelectorError(SelectorError::InvalidPseudoClassAfterWebKitScrollbar),\n      );\n    }\n\n    error_test(\n      \"a::first-letter:last-child {color:red}\",\n      ParserError::SelectorError(SelectorError::InvalidPseudoClassAfterPseudoElement),\n    );\n    minify_test(\n      \"a:last-child::first-letter {color:red}\",\n      \"a:last-child:first-letter{color:red}\",\n    );\n\n    prefix_test(\n      \".test:not(.foo, .bar) {color:red}\",\n      indoc! {r#\"\n      .test:not(:-webkit-any(.foo, .bar)) {\n        color: red;\n      }\n\n      .test:not(:is(.foo, .bar)) {\n        color: red;\n      }\n      \"#},\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      \".test:not(.foo, .bar) {color:red}\",\n      indoc! {r#\"\n      .test:not(.foo, .bar) {\n        color: red;\n      }\n      \"#},\n      Browsers {\n        safari: Some(11 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    minify_test(\"a:lang(en) {color:red}\", \"a:lang(en){color:red}\");\n    minify_test(\"a:lang(en, fr) {color:red}\", \"a:lang(en,fr){color:red}\");\n    minify_test(\"a:lang('en') {color:red}\", \"a:lang(en){color:red}\");\n    minify_test(\n      \"a:-webkit-any(.foo, .bar) {color:red}\",\n      \"a:-webkit-any(.foo,.bar){color:red}\",\n    );\n    minify_test(\"a:-moz-any(.foo, .bar) {color:red}\", \"a:-moz-any(.foo,.bar){color:red}\");\n\n    prefix_test(\n      \"a:is(.foo, .bar) {color:red}\",\n      indoc! {r#\"\n      a:-webkit-any(.foo, .bar) {\n        color: red;\n      }\n\n      a:-moz-any(.foo, .bar) {\n        color: red;\n      }\n\n      a:is(.foo, .bar) {\n        color: red;\n      }\n      \"#},\n      Browsers {\n        safari: Some(11 << 16),\n        firefox: Some(50 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \"a:is(.foo > .bar) {color:red}\",\n      indoc! {r#\"\n      a:is(.foo > .bar) {\n        color: red;\n      }\n      \"#},\n      Browsers {\n        safari: Some(11 << 16),\n        firefox: Some(50 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \"a:lang(en, fr) {color:red}\",\n      indoc! {r#\"\n      a:-webkit-any(:lang(en), :lang(fr)) {\n        color: red;\n      }\n\n      a:-moz-any(:lang(en), :lang(fr)) {\n        color: red;\n      }\n\n      a:is(:lang(en), :lang(fr)) {\n        color: red;\n      }\n      \"#},\n      Browsers {\n        safari: Some(11 << 16),\n        firefox: Some(50 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \"a:lang(en, fr) {color:red}\",\n      indoc! {r#\"\n      a:is(:lang(en), :lang(fr)) {\n        color: red;\n      }\n      \"#},\n      Browsers {\n        safari: Some(14 << 16),\n        firefox: Some(88 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \"a:lang(en, fr) {color:red}\",\n      indoc! {r#\"\n      a:lang(en, fr) {\n        color: red;\n      }\n      \"#},\n      Browsers {\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \"a:dir(rtl) {color:red}\",\n      indoc! {r#\"\n      a:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        color: red;\n      }\n\n      a:-moz-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        color: red;\n      }\n\n      a:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        color: red;\n      }\n      \"#},\n      Browsers {\n        safari: Some(11 << 16),\n        firefox: Some(50 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \"a:dir(ltr) {color:red}\",\n      indoc! {r#\"\n      a:not(:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        color: red;\n      }\n\n      a:not(:-moz-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        color: red;\n      }\n\n      a:not(:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        color: red;\n      }\n      \"#},\n      Browsers {\n        safari: Some(11 << 16),\n        firefox: Some(50 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \"a:dir(rtl) {color:red}\",\n      indoc! {r#\"\n      a:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        color: red;\n      }\n      \"#},\n      Browsers {\n        safari: Some(14 << 16),\n        firefox: Some(88 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \"a:dir(ltr) {color:red}\",\n      indoc! {r#\"\n      a:not(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        color: red;\n      }\n      \"#},\n      Browsers {\n        safari: Some(14 << 16),\n        firefox: Some(88 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \"a:dir(rtl) {color:red}\",\n      indoc! {r#\"\n      a:lang(ae, ar, arc, bcc, bqi, ckb, dv, fa, glk, he, ku, mzn, nqo, pnb, ps, sd, ug, ur, yi) {\n        color: red;\n      }\n      \"#},\n      Browsers {\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \"a:dir(ltr) {color:red}\",\n      indoc! {r#\"\n      a:not(:lang(ae, ar, arc, bcc, bqi, ckb, dv, fa, glk, he, ku, mzn, nqo, pnb, ps, sd, ug, ur, yi)) {\n        color: red;\n      }\n      \"#},\n      Browsers {\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \"a:is(:dir(rtl)) {color:red}\",\n      indoc! {r#\"\n      a:lang(ae, ar, arc, bcc, bqi, ckb, dv, fa, glk, he, ku, mzn, nqo, pnb, ps, sd, ug, ur, yi) {\n        color: red;\n      }\n      \"#},\n      Browsers {\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \"a:where(:dir(rtl)) {color:red}\",\n      indoc! {r#\"\n      a:where(:lang(ae, ar, arc, bcc, bqi, ckb, dv, fa, glk, he, ku, mzn, nqo, pnb, ps, sd, ug, ur, yi)) {\n        color: red;\n      }\n      \"#},\n      Browsers {\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \"a:has(:dir(rtl)) {color:red}\",\n      indoc! {r#\"\n      a:has(:lang(ae, ar, arc, bcc, bqi, ckb, dv, fa, glk, he, ku, mzn, nqo, pnb, ps, sd, ug, ur, yi)) {\n        color: red;\n      }\n      \"#},\n      Browsers {\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \"a:not(:dir(rtl)) {color:red}\",\n      indoc! {r#\"\n      a:not(:lang(ae, ar, arc, bcc, bqi, ckb, dv, fa, glk, he, ku, mzn, nqo, pnb, ps, sd, ug, ur, yi)) {\n        color: red;\n      }\n      \"#},\n      Browsers {\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \"a:dir(rtl)::after {color:red}\",\n      indoc! {r#\"\n      a:lang(ae, ar, arc, bcc, bqi, ckb, dv, fa, glk, he, ku, mzn, nqo, pnb, ps, sd, ug, ur, yi):after {\n        color: red;\n      }\n      \"#},\n      Browsers {\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \"a:dir(rtl) div {color:red}\",\n      indoc! {r#\"\n      a:lang(ae, ar, arc, bcc, bqi, ckb, dv, fa, glk, he, ku, mzn, nqo, pnb, ps, sd, ug, ur, yi) div {\n        color: red;\n      }\n      \"#},\n      Browsers {\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    minify_test(\".foo::cue {color: red}\", \".foo::cue{color:red}\");\n    minify_test(\".foo::cue-region {color: red}\", \".foo::cue-region{color:red}\");\n    minify_test(\".foo::cue(b) {color: red}\", \".foo::cue(b){color:red}\");\n    minify_test(\".foo::cue-region(b) {color: red}\", \".foo::cue-region(b){color:red}\");\n    minify_test(\n      \"::cue(v[voice='active']) {color: yellow;}\",\n      \"::cue(v[voice=active]){color:#ff0}\",\n    );\n    minify_test(\":foo(bar) { color: yellow }\", \":foo(bar){color:#ff0}\");\n    minify_test(\"::foo(bar) { color: yellow }\", \"::foo(bar){color:#ff0}\");\n    minify_test(\"::foo(*) { color: yellow }\", \"::foo(*){color:#ff0}\");\n\n    minify_test(\":is(.foo) { color: yellow }\", \".foo{color:#ff0}\");\n    minify_test(\":is(#foo) { color: yellow }\", \"#foo{color:#ff0}\");\n    minify_test(\"a:is(.foo) { color: yellow }\", \"a.foo{color:#ff0}\");\n    minify_test(\"a:is([data-test]) { color: yellow }\", \"a[data-test]{color:#ff0}\");\n    minify_test(\".foo:is(a) { color: yellow }\", \".foo:is(a){color:#ff0}\");\n    minify_test(\".foo:is(*|a) { color: yellow }\", \".foo:is(*|a){color:#ff0}\");\n    minify_test(\".foo:is(*) { color: yellow }\", \".foo:is(*){color:#ff0}\");\n    minify_test(\n      \"@namespace svg url(http://www.w3.org/2000/svg); .foo:is(svg|a) { color: yellow }\",\n      \"@namespace svg \\\"http://www.w3.org/2000/svg\\\";.foo:is(svg|a){color:#ff0}\",\n    );\n    minify_test(\"a:is(.foo .bar) { color: yellow }\", \"a:is(.foo .bar){color:#ff0}\");\n    minify_test(\":is(.foo, .bar) { color: yellow }\", \":is(.foo,.bar){color:#ff0}\");\n    minify_test(\"a:is(:not(.foo)) { color: yellow }\", \"a:not(.foo){color:#ff0}\");\n    minify_test(\"a:is(:first-child) { color: yellow }\", \"a:first-child{color:#ff0}\");\n    minify_test(\"a:is(:has(.foo)) { color: yellow }\", \"a:has(.foo){color:#ff0}\");\n    minify_test(\"a:is(:is(.foo)) { color: yellow }\", \"a.foo{color:#ff0}\");\n    minify_test(\":host(:hover) {color: red}\", \":host(:hover){color:red}\");\n    minify_test(\"::slotted(:hover) {color: red}\", \"::slotted(:hover){color:red}\");\n\n    minify_test(\n      \":root::view-transition {position: fixed}\",\n      \":root::view-transition{position:fixed}\",\n    );\n    minify_test(\n      \":root:active-view-transition {position: fixed}\",\n      \":root:active-view-transition{position:fixed}\",\n    );\n    minify_test(\n      \":root:active-view-transition-type(slide-in) {position: fixed}\",\n      \":root:active-view-transition-type(slide-in){position:fixed}\",\n    );\n    minify_test(\n      \":root:active-view-transition-type(slide-in, reverse) {position: fixed}\",\n      \":root:active-view-transition-type(slide-in,reverse){position:fixed}\",\n    );\n\n    for name in &[\n      \"view-transition-group\",\n      \"view-transition-image-pair\",\n      \"view-transition-new\",\n      \"view-transition-old\",\n    ] {\n      minify_test(\n        &format!(\":root::{}(*) {{position: fixed}}\", name),\n        &format!(\":root::{}(*){{position:fixed}}\", name),\n      );\n      minify_test(\n        &format!(\":root::{}(*.class) {{position: fixed}}\", name),\n        &format!(\":root::{}(*.class){{position:fixed}}\", name),\n      );\n      minify_test(\n        &format!(\":root::{}(*.class.class) {{position: fixed}}\", name),\n        &format!(\":root::{}(*.class.class){{position:fixed}}\", name),\n      );\n      minify_test(\n        &format!(\":root::{}(foo) {{position: fixed}}\", name),\n        &format!(\":root::{}(foo){{position:fixed}}\", name),\n      );\n      minify_test(\n        &format!(\":root::{}(foo.class) {{position: fixed}}\", name),\n        &format!(\":root::{}(foo.class){{position:fixed}}\", name),\n      );\n      minify_test(\n        &format!(\":root::{}(foo.bar.baz) {{position: fixed}}\", name),\n        &format!(\":root::{}(foo.bar.baz){{position:fixed}}\", name),\n      );\n      minify_test(\n        &format!(\":root::{}(foo):only-child {{position: fixed}}\", name),\n        &format!(\":root::{}(foo):only-child{{position:fixed}}\", name),\n      );\n      minify_test(\n        &format!(\":root::{}(foo.bar.baz):only-child {{position: fixed}}\", name),\n        &format!(\":root::{}(foo.bar.baz):only-child{{position:fixed}}\", name),\n      );\n      minify_test(\n        &format!(\":root::{}(.foo) {{position: fixed}}\", name),\n        &format!(\":root::{}(.foo){{position:fixed}}\", name),\n      );\n      minify_test(\n        &format!(\":root::{}(.foo.bar) {{position: fixed}}\", name),\n        &format!(\":root::{}(.foo.bar){{position:fixed}}\", name),\n      );\n      minify_test(\n        &format!(\":root::{}(  .foo.bar  ) {{position: fixed}}\", name),\n        &format!(\":root::{}(.foo.bar){{position:fixed}}\", name),\n      );\n      error_test(\n        &format!(\":root::{}(foo):first-child {{position: fixed}}\", name),\n        ParserError::SelectorError(SelectorError::InvalidPseudoClassAfterPseudoElement),\n      );\n      error_test(\n        &format!(\":root::{}(foo)::before {{position: fixed}}\", name),\n        ParserError::SelectorError(SelectorError::InvalidState),\n      );\n      error_test(\n        &format!(\":root::{}(*.*) {{position: fixed}}\", name),\n        ParserError::SelectorError(SelectorError::InvalidState),\n      );\n      error_test(\n        &format!(\":root::{}(*. cls) {{position: fixed}}\", name),\n        ParserError::SelectorError(SelectorError::InvalidState),\n      );\n      error_test(\n        &format!(\":root::{}(foo .bar) {{position: fixed}}\", name),\n        ParserError::SelectorError(SelectorError::InvalidState),\n      );\n      error_test(\n        &format!(\":root::{}(*.cls. c) {{position: fixed}}\", name),\n        ParserError::SelectorError(SelectorError::InvalidState),\n      );\n      error_test(\n        &format!(\":root::{}(*.cls>cls) {{position: fixed}}\", name),\n        ParserError::SelectorError(SelectorError::InvalidState),\n      );\n      error_test(\n        &format!(\":root::{}(*.cls.foo.*) {{position: fixed}}\", name),\n        ParserError::SelectorError(SelectorError::InvalidState),\n      );\n    }\n    minify_test(\n      \"wa-checkbox:state(disabled) {color:red}\",\n      \"wa-checkbox:state(disabled){color:red}\",\n    );\n    minify_test(\n      \"button:state(checked) {background:blue}\",\n      \"button:state(checked){background:#00f}\",\n    );\n    minify_test(\n      \"input:state(custom-state) {border:1px solid}\",\n      \"input:state(custom-state){border:1px solid}\",\n    );\n    minify_test(\n      \"button:active:not(:state(disabled))::part(control) {border:1px solid}\",\n      \"button:active:not(:state(disabled))::part(control){border:1px solid}\",\n    );\n    // Test nested CSS with :state() selector\n    nesting_test(\n      r#\"\n        custom-element {\n          color: blue;\n          &:state(loading) {\n            opacity: 0.5;\n            & .spinner {\n              display: block;\n            }\n          }\n          &:state(error) {\n            border: 2px solid red;\n          }\n        }\n      \"#,\n      indoc! {r#\"\n        custom-element {\n          color: #00f;\n        }\n\n        custom-element:state(loading) {\n          opacity: .5;\n        }\n\n        custom-element:state(loading) .spinner {\n          display: block;\n        }\n\n        custom-element:state(error) {\n          border: 2px solid red;\n        }\n      \"#},\n    );\n    minify_test(\".foo ::deep .bar {width: 20px}\", \".foo ::deep .bar{width:20px}\");\n    minify_test(\".foo::deep .bar {width: 20px}\", \".foo::deep .bar{width:20px}\");\n    minify_test(\".foo ::deep.bar {width: 20px}\", \".foo ::deep.bar{width:20px}\");\n    minify_test(\".foo ::unknown .bar {width: 20px}\", \".foo ::unknown .bar{width:20px}\");\n    minify_test(\n      \".foo ::unknown(foo) .bar {width: 20px}\",\n      \".foo ::unknown(foo) .bar{width:20px}\",\n    );\n    minify_test(\n      \".foo ::unknown:only-child {width: 20px}\",\n      \".foo ::unknown:only-child{width:20px}\",\n    );\n    minify_test(\n      \".foo ::unknown(.foo) .bar {width: 20px}\",\n      \".foo ::unknown(.foo) .bar{width:20px}\",\n    );\n    minify_test(\n      \".foo ::unknown(.foo .bar / .baz) .bar {width: 20px}\",\n      \".foo ::unknown(.foo .bar / .baz) .bar{width:20px}\",\n    );\n    minify_test(\n      \".foo ::unknown(something(foo)) .bar {width: 20px}\",\n      \".foo ::unknown(something(foo)) .bar{width:20px}\",\n    );\n    minify_test(\n      \".foo ::unknown([abc]) .bar {width: 20px}\",\n      \".foo ::unknown([abc]) .bar{width:20px}\",\n    );\n\n    let deep_options = ParserOptions {\n      flags: ParserFlags::DEEP_SELECTOR_COMBINATOR,\n      ..ParserOptions::default()\n    };\n\n    error_test(\n      \".foo >>> .bar {width: 20px}\",\n      ParserError::SelectorError(SelectorError::DanglingCombinator),\n    );\n    error_test(\n      \".foo /deep/ .bar {width: 20px}\",\n      ParserError::SelectorError(SelectorError::DanglingCombinator),\n    );\n    minify_test_with_options(\n      \".foo >>> .bar {width: 20px}\",\n      \".foo>>>.bar{width:20px}\",\n      deep_options.clone(),\n    );\n    minify_test_with_options(\n      \".foo /deep/ .bar {width: 20px}\",\n      \".foo /deep/ .bar{width:20px}\",\n      deep_options.clone(),\n    );\n\n    let pure_css_module_options = ParserOptions {\n      css_modules: Some(crate::css_modules::Config {\n        pure: true,\n        ..Default::default()\n      }),\n      ..ParserOptions::default()\n    };\n\n    minify_error_test_with_options(\n      \"div {width: 20px}\",\n      MinifyErrorKind::ImpureCSSModuleSelector,\n      pure_css_module_options.clone(),\n    );\n    minify_error_test_with_options(\n      \":global(.foo) {width: 20px}\",\n      MinifyErrorKind::ImpureCSSModuleSelector,\n      pure_css_module_options.clone(),\n    );\n    minify_error_test_with_options(\n      \"[foo=bar] {width: 20px}\",\n      MinifyErrorKind::ImpureCSSModuleSelector,\n      pure_css_module_options.clone(),\n    );\n    minify_error_test_with_options(\n      \"div, .foo {width: 20px}\",\n      MinifyErrorKind::ImpureCSSModuleSelector,\n      pure_css_module_options.clone(),\n    );\n    minify_test_with_options(\n      \":local(.foo) {width: 20px}\",\n      \"._8Z4fiW_foo{width:20px}\",\n      pure_css_module_options.clone(),\n    );\n    minify_test_with_options(\n      \"div.my-class {color: red;}\",\n      \"div._8Z4fiW_my-class{color:red}\",\n      pure_css_module_options.clone(),\n    );\n    minify_test_with_options(\n      \"#id {color: red;}\",\n      \"#_8Z4fiW_id{color:red}\",\n      pure_css_module_options.clone(),\n    );\n    minify_test_with_options(\n      \"a .my-class{color: red;}\",\n      \"a ._8Z4fiW_my-class{color:red}\",\n      pure_css_module_options.clone(),\n    );\n    minify_test_with_options(\n      \".my-class a {color: red;}\",\n      \"._8Z4fiW_my-class a{color:red}\",\n      pure_css_module_options.clone(),\n    );\n    minify_test_with_options(\n      \".my-class:is(a) {color: red;}\",\n      \"._8Z4fiW_my-class:is(a){color:red}\",\n      pure_css_module_options.clone(),\n    );\n    minify_test_with_options(\n      \"div:has(.my-class) {color: red;}\",\n      \"div:has(._8Z4fiW_my-class){color:red}\",\n      pure_css_module_options.clone(),\n    );\n    minify_test_with_options(\n      \".foo { html &:hover { a_value: some-value; } }\",\n      \"._8Z4fiW_foo{html &:hover{a_value:some-value}}\",\n      pure_css_module_options.clone(),\n    );\n    minify_test_with_options(\n      \".foo { span { color: red; } }\",\n      \"._8Z4fiW_foo{& span{color:red}}\",\n      pure_css_module_options.clone(),\n    );\n    minify_error_test_with_options(\n      \"html { .foo { span { color: red; } } }\",\n      MinifyErrorKind::ImpureCSSModuleSelector,\n      pure_css_module_options.clone(),\n    );\n    minify_test_with_options(\n      \".foo { div { span { color: red; } } }\",\n      \"._8Z4fiW_foo{& div{& span{color:red}}}\",\n      pure_css_module_options.clone(),\n    );\n    minify_error_test_with_options(\n      \"@scope (div) { .foo { color: red } }\",\n      MinifyErrorKind::ImpureCSSModuleSelector,\n      pure_css_module_options.clone(),\n    );\n    minify_error_test_with_options(\n      \"@scope (.a) to (div) { .foo { color: red } }\",\n      MinifyErrorKind::ImpureCSSModuleSelector,\n      pure_css_module_options.clone(),\n    );\n    minify_error_test_with_options(\n      \"@scope (.a) to (.b) { div { color: red } }\",\n      MinifyErrorKind::ImpureCSSModuleSelector,\n      pure_css_module_options.clone(),\n    );\n    minify_test_with_options(\n      \"@scope (.a) to (.b) { .foo { color: red } }\",\n      \"@scope(._8Z4fiW_a) to (._8Z4fiW_b){._8Z4fiW_foo{color:red}}\",\n      pure_css_module_options.clone(),\n    );\n    minify_test_with_options(\n      \"/* cssmodules-pure-no-check */ :global(.foo) { color: red }\",\n      \".foo{color:red}\",\n      pure_css_module_options.clone(),\n    );\n    minify_test_with_options(\n      \"/*! some license */ /* cssmodules-pure-no-check */ :global(.foo) { color: red }\",\n      \"/*! some license */\\n.foo{color:red}\",\n      pure_css_module_options.clone(),\n    );\n\n    error_test(\n      \"input.defaultCheckbox::before h1 {width: 20px}\",\n      ParserError::SelectorError(SelectorError::UnexpectedSelectorAfterPseudoElement(Token::Ident(\n        \"h1\".into(),\n      ))),\n    );\n    error_test(\n      \"input.defaultCheckbox::before .my-class {width: 20px}\",\n      ParserError::SelectorError(SelectorError::UnexpectedSelectorAfterPseudoElement(Token::Delim('.'))),\n    );\n    error_test(\n      \"input.defaultCheckbox::before.my-class {width: 20px}\",\n      ParserError::SelectorError(SelectorError::UnexpectedSelectorAfterPseudoElement(Token::Delim('.'))),\n    );\n    error_test(\n      \"input.defaultCheckbox::before #id {width: 20px}\",\n      ParserError::SelectorError(SelectorError::UnexpectedSelectorAfterPseudoElement(Token::IDHash(\n        \"id\".into(),\n      ))),\n    );\n    error_test(\n      \"input.defaultCheckbox::before#id {width: 20px}\",\n      ParserError::SelectorError(SelectorError::UnexpectedSelectorAfterPseudoElement(Token::IDHash(\n        \"id\".into(),\n      ))),\n    );\n    error_test(\n      \"input.defaultCheckbox::before [attr] {width: 20px}\",\n      ParserError::SelectorError(SelectorError::UnexpectedSelectorAfterPseudoElement(\n        Token::SquareBracketBlock,\n      )),\n    );\n    error_test(\n      \"input.defaultCheckbox::before[attr] {width: 20px}\",\n      ParserError::SelectorError(SelectorError::UnexpectedSelectorAfterPseudoElement(\n        Token::SquareBracketBlock,\n      )),\n    );\n  }\n\n  #[test]\n  fn test_keyframes() {\n    minify_test(\n      r#\"\n      @keyframes \"test\" {\n        100% {\n          background: blue\n        }\n      }\n    \"#,\n      \"@keyframes test{to{background:#00f}}\",\n    );\n    minify_test(\n      r#\"\n      @keyframes test {\n        100% {\n          background: blue\n        }\n      }\n    \"#,\n      \"@keyframes test{to{background:#00f}}\",\n    );\n\n    // named animation range percentages\n    minify_test(\n      r#\"\n      @keyframes test {\n        entry 0% {\n          background: blue\n        }\n        exit 100% {\n          background: green\n        }\n      }\n    \"#,\n      \"@keyframes test{entry 0%{background:#00f}exit 100%{background:green}}\",\n    );\n\n    // CSS-wide keywords and `none` cannot remove quotes.\n    minify_test(\n      r#\"\n      @keyframes \"revert\" {\n        from {\n          background: green;\n        }\n      }\n    \"#,\n      \"@keyframes \\\"revert\\\"{0%{background:green}}\",\n    );\n\n    minify_test(\n      r#\"\n      @keyframes \"none\" {\n        from {\n          background: green;\n        }\n      }\n    \"#,\n      \"@keyframes \\\"none\\\"{0%{background:green}}\",\n    );\n\n    // named animation ranges cannot be used with to or from\n    minify_test(\n      r#\"\n      @keyframes test {\n        entry to {\n          background: blue\n        }\n      }\n    \"#,\n      \"@keyframes test{}\",\n    );\n\n    // CSS-wide keywords without quotes throws an error.\n    error_test(\n      r#\"\n      @keyframes revert {}\n    \"#,\n      ParserError::UnexpectedToken(Token::Ident(\"revert\".into())),\n    );\n\n    error_test(\n      r#\"\n      @keyframes revert-layer {}\n    \"#,\n      ParserError::UnexpectedToken(Token::Ident(\"revert-layer\".into())),\n    );\n\n    error_test(\n      r#\"\n      @keyframes none {}\n    \"#,\n      ParserError::UnexpectedToken(Token::Ident(\"none\".into())),\n    );\n\n    error_test(\n      r#\"\n      @keyframes NONE {}\n    \"#,\n      ParserError::UnexpectedToken(Token::Ident(\"NONE\".into())),\n    );\n\n    minify_test(\n      r#\"\n      @-webkit-keyframes test {\n        from {\n          background: green;\n          background-color: red;\n        }\n\n        100% {\n          background: blue\n        }\n      }\n    \"#,\n      \"@-webkit-keyframes test{0%{background:red}to{background:#00f}}\",\n    );\n    minify_test(\n      r#\"\n      @-moz-keyframes test {\n        from {\n          background: green;\n          background-color: red;\n        }\n\n        100% {\n          background: blue\n        }\n      }\n    \"#,\n      \"@-moz-keyframes test{0%{background:red}to{background:#00f}}\",\n    );\n    minify_test(r#\"\n      @-webkit-keyframes test {\n        from {\n          background: green;\n          background-color: red;\n        }\n\n        100% {\n          background: blue\n        }\n      }\n      @-moz-keyframes test {\n        from {\n          background: green;\n          background-color: red;\n        }\n\n        100% {\n          background: blue\n        }\n      }\n    \"#, \"@-webkit-keyframes test{0%{background:red}to{background:#00f}}@-moz-keyframes test{0%{background:red}to{background:#00f}}\");\n\n    prefix_test(\n      r#\"\n      @keyframes test {\n        from {\n          background: green;\n        }\n        to {\n          background: blue\n        }\n      }\n    \"#,\n      indoc! { r#\"\n      @-webkit-keyframes test {\n        from {\n          background: green;\n        }\n\n        to {\n          background: #00f;\n        }\n      }\n\n      @-moz-keyframes test {\n        from {\n          background: green;\n        }\n\n        to {\n          background: #00f;\n        }\n      }\n\n      @keyframes test {\n        from {\n          background: green;\n        }\n\n        to {\n          background: #00f;\n        }\n      }\n    \"#},\n      Browsers {\n        safari: Some(5 << 16),\n        firefox: Some(6 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      @-webkit-keyframes test {\n        from {\n          background: green;\n        }\n\n        to {\n          background: blue;\n        }\n      }\n      @-moz-keyframes test {\n        from {\n          background: green;\n        }\n\n        to {\n          background: blue;\n        }\n      }\n      @keyframes test {\n        from {\n          background: green;\n        }\n        to {\n          background: blue\n        }\n      }\n    \"#,\n      indoc! { r#\"\n      @keyframes test {\n        from {\n          background: green;\n        }\n\n        to {\n          background: #00f;\n        }\n      }\n    \"#},\n      Browsers {\n        safari: Some(10 << 16),\n        firefox: Some(17 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      @-webkit-keyframes test1 {\n        from {\n          background: green;\n        }\n\n        to {\n          background: blue;\n        }\n      }\n\n      @-moz-keyframes test2 {\n        from {\n          background: green;\n        }\n\n        to {\n          background: blue;\n        }\n      }\n\n      @keyframes test3 {\n        from {\n          background: green;\n        }\n        to {\n          background: blue\n        }\n      }\n    \"#,\n      indoc! { r#\"\n      @-webkit-keyframes test1 {\n        from {\n          background: green;\n        }\n\n        to {\n          background: #00f;\n        }\n      }\n\n      @-moz-keyframes test2 {\n        from {\n          background: green;\n        }\n\n        to {\n          background: #00f;\n        }\n      }\n\n      @keyframes test3 {\n        from {\n          background: green;\n        }\n\n        to {\n          background: #00f;\n        }\n      }\n    \"#},\n      Browsers {\n        safari: Some(10 << 16),\n        firefox: Some(17 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      @-webkit-keyframes test {\n        from {\n          background: green;\n        }\n\n        to {\n          background: red;\n        }\n      }\n      @-moz-keyframes test {\n        from {\n          background: green;\n        }\n\n        to {\n          background: pink;\n        }\n      }\n      @keyframes test {\n        from {\n          background: green;\n        }\n        to {\n          background: blue\n        }\n      }\n    \"#,\n      indoc! { r#\"\n      @-webkit-keyframes test {\n        from {\n          background: green;\n        }\n\n        to {\n          background: red;\n        }\n      }\n\n      @-moz-keyframes test {\n        from {\n          background: green;\n        }\n\n        to {\n          background: pink;\n        }\n      }\n\n      @keyframes test {\n        from {\n          background: green;\n        }\n\n        to {\n          background: #00f;\n        }\n      }\n    \"#},\n      Browsers {\n        safari: Some(10 << 16),\n        firefox: Some(17 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    minify_test(\n      r#\"\n      @keyframes test {\n        100% {\n          background: blue\n        }\n      }\n\n      @keyframes test {\n        100% {\n          background: red\n        }\n      }\n    \"#,\n      \"@keyframes test{to{background:red}}\",\n    );\n    minify_test(\n      r#\"\n      @keyframes test {\n        100% {\n          background: blue\n        }\n      }\n\n      @-webkit-keyframes test {\n        100% {\n          background: red\n        }\n      }\n    \"#,\n      \"@keyframes test{to{background:#00f}}@-webkit-keyframes test{to{background:red}}\",\n    );\n  }\n\n  #[test]\n  fn test_important() {\n    test(\n      r#\"\n      .foo {\n        align-items: center;\n        justify-items: center !important;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        align-items: center;\n        justify-items: center !important;\n      }\n    \"#},\n    );\n\n    test(\n      r#\"\n      .foo {\n        justify-items: center !important;\n        align-items: center;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        align-items: center;\n        justify-items: center !important;\n      }\n    \"#},\n    );\n\n    minify_test(\n      r#\"\n      .foo {\n        font-family: SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace !important;\n      }\n    \"#,\n      \".foo{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace!important}\",\n    );\n  }\n\n  #[test]\n  fn test_calc() {\n    minify_test(\".foo { width: calc(20px * 2) }\", \".foo{width:40px}\");\n    minify_test(\".foo { font-size: calc(100vw / 35) }\", \".foo{font-size:2.85714vw}\");\n    minify_test(\".foo { width: calc(20px * 2 * 3) }\", \".foo{width:120px}\");\n    minify_test(\".foo { width: calc(20px + 30px) }\", \".foo{width:50px}\");\n    minify_test(\".foo { width: calc(20px + 30px + 40px) }\", \".foo{width:90px}\");\n    minify_test(\".foo { width: calc(100% - 30px) }\", \".foo{width:calc(100% - 30px)}\");\n    minify_test(\n      \".foo { width: calc(100% - 30px + 20px) }\",\n      \".foo{width:calc(100% - 10px)}\",\n    );\n    minify_test(\n      \".foo { width: calc(20px + 100% - 30px) }\",\n      \".foo{width:calc(100% - 10px)}\",\n    );\n    minify_test(\n      \".foo { width: calc(20px + 100% + 10vw - 30px) }\",\n      \".foo{width:calc(100% - 10px + 10vw)}\",\n    );\n    minify_test(\n      \".foo { width: calc(20px + 100% - 30px) }\",\n      \".foo{width:calc(100% - 10px)}\",\n    );\n    minify_test(\n      \".foo { width: calc(2 * (100% - 20px)) }\",\n      \".foo{width:calc(200% - 40px)}\",\n    );\n    minify_test(\n      \".foo { width: calc((100% - 20px) * 2) }\",\n      \".foo{width:calc(200% - 40px)}\",\n    );\n    minify_test(\".foo { width: calc(100% - 20px * 2) }\", \".foo{width:calc(100% - 40px)}\");\n    minify_test(\".foo { width: calc(1px + 1px) }\", \".foo{width:2px}\");\n    minify_test(\".foo { width: calc(100vw / 2) }\", \".foo{width:50vw}\");\n    minify_test(\".foo { width: calc(50px - (20px - 30px)) }\", \".foo{width:60px}\");\n    minify_test(\".foo { width: calc(100px - (100px - 100%)) }\", \".foo{width:100%}\");\n    minify_test(\n      \".foo { width: calc(100px + (100px - 100%)) }\",\n      \".foo{width:calc(200px - 100%)}\",\n    );\n    minify_test(\n      \".foo { width: calc(1px - (2em + 3%)) }\",\n      \".foo{width:calc(1px + -2em - 3%)}\",\n    ); // TODO: fix sign\n    minify_test(\n      \".foo { width: calc((100vw - 50em) / 2) }\",\n      \".foo{width:calc(50vw - 25em)}\",\n    );\n    minify_test(\n      \".foo { width: calc(1px - (2em + 4vh + 3%)) }\",\n      \".foo{width:calc(1px + -2em - 4vh - 3%)}\",\n    ); // TODO\n    minify_test(\n      \".foo { width: calc(1px + (2em + (3vh + 4px))) }\",\n      \".foo{width:calc(2em + 3vh + 5px)}\",\n    );\n    minify_test(\n      \".foo { width: calc(1px - (2em + 4px - 6vh) / 2) }\",\n      \".foo{width:calc(-1em - 1px + 3vh)}\",\n    );\n    minify_test(\n      \".foo { width: calc(100% - calc(50% + 25px)) }\",\n      \".foo{width:calc(50% - 25px)}\",\n    );\n    minify_test(\".foo { width: calc(1px/100) }\", \".foo{width:.01px}\");\n    minify_test(\n      \".foo { width: calc(100vw / 2 - 6px + 0px) }\",\n      \".foo{width:calc(50vw - 6px)}\",\n    );\n    minify_test(\".foo { width: calc(1px + 1) }\", \".foo{width:calc(1px + 1)}\");\n    minify_test(\n      \".foo { width: calc( (1em - calc( 10px + 1em)) / 2) }\",\n      \".foo{width:-5px}\",\n    );\n    minify_test(\n      \".foo { width: calc((100px - 1em) + (-50px + 1em)) }\",\n      \".foo{width:50px}\",\n    );\n    minify_test(\n      \".foo { width: calc(100% + (2 * 100px) - ((75.37% - 63.5px) - 900px)) }\",\n      \".foo{width:calc(24.63% + 1163.5px)}\",\n    );\n    minify_test(\n      \".foo { width: calc(((((100% + (2 * 30px) + 63.5px) / 0.7537) - (100vw - 60px)) / 2) + 30px) }\",\n      \".foo{width:calc(66.3394% + 141.929px - 50vw)}\",\n    );\n    minify_test(\n      \".foo { width: calc(((75.37% - 63.5px) - 900px) + (2 * 100px)) }\",\n      \".foo{width:calc(75.37% - 763.5px)}\",\n    );\n    minify_test(\n      \".foo { width: calc((900px - (10% - 63.5px)) + (2 * 100px)) }\",\n      \".foo{width:calc(1163.5px - 10%)}\",\n    );\n    minify_test(\".foo { width: calc(500px/0) }\", \".foo{width:calc(500px/0)}\");\n    minify_test(\".foo { width: calc(500px/2px) }\", \".foo{width:calc(500px/2px)}\");\n    minify_test(\".foo { width: calc(100% / 3 * 3) }\", \".foo{width:100%}\");\n    minify_test(\".foo { width: calc(+100px + +100px) }\", \".foo{width:200px}\");\n    minify_test(\".foo { width: calc(+100px - +100px) }\", \".foo{width:0}\");\n    minify_test(\".foo { width: calc(200px * +1) }\", \".foo{width:200px}\");\n    minify_test(\".foo { width: calc(200px / +1) }\", \".foo{width:200px}\");\n    minify_test(\".foo { width: calc(1.1e+1px + 1.1e+1px) }\", \".foo{width:22px}\");\n    minify_test(\".foo { border-width: calc(1px + 2px) }\", \".foo{border-width:3px}\");\n    minify_test(\n      \".foo { border-width: calc(1em + 2px + 2em + 3px) }\",\n      \".foo{border-width:calc(3em + 5px)}\",\n    );\n\n    minify_test(\n      \".foo { border-width: min(1em, 2px) }\",\n      \".foo{border-width:min(1em,2px)}\",\n    );\n    minify_test(\n      \".foo { border-width: min(1em + 2em, 2px + 2px) }\",\n      \".foo{border-width:min(3em,4px)}\",\n    );\n    minify_test(\n      \".foo { border-width: min(1em + 2px, 2px + 1em) }\",\n      \".foo{border-width:min(1em + 2px,2px + 1em)}\",\n    );\n    minify_test(\n      \".foo { border-width: min(1em + 2px + 2px, 2px + 1em + 1px) }\",\n      \".foo{border-width:min(1em + 4px,3px + 1em)}\",\n    );\n    minify_test(\n      \".foo { border-width: min(2px + 1px, 3px + 4px) }\",\n      \".foo{border-width:3px}\",\n    );\n    minify_test(\n      \".foo { border-width: min(1px, 1em, 2px, 3in) }\",\n      \".foo{border-width:min(1px,1em)}\",\n    );\n\n    minify_test(\n      \".foo { border-width: max(1em, 2px) }\",\n      \".foo{border-width:max(1em,2px)}\",\n    );\n    minify_test(\n      \".foo { border-width: max(1em + 2em, 2px + 2px) }\",\n      \".foo{border-width:max(3em,4px)}\",\n    );\n    minify_test(\n      \".foo { border-width: max(1em + 2px, 2px + 1em) }\",\n      \".foo{border-width:max(1em + 2px,2px + 1em)}\",\n    );\n    minify_test(\n      \".foo { border-width: max(1em + 2px + 2px, 2px + 1em + 1px) }\",\n      \".foo{border-width:max(1em + 4px,3px + 1em)}\",\n    );\n    minify_test(\n      \".foo { border-width: max(2px + 1px, 3px + 4px) }\",\n      \".foo{border-width:7px}\",\n    );\n    minify_test(\n      \".foo { border-width: max(1px, 1em, 2px, 3in) }\",\n      \".foo{border-width:max(3in,1em)}\",\n    );\n\n    minify_test(\".foo { border-width: clamp(1px, 2px, 3px) }\", \".foo{border-width:2px}\");\n    minify_test(\".foo { border-width: clamp(1px, 10px, 3px) }\", \".foo{border-width:3px}\");\n    minify_test(\".foo { border-width: clamp(5px, 2px, 10px) }\", \".foo{border-width:5px}\");\n    minify_test(\n      \".foo { border-width: clamp(100px, 2px, 10px) }\",\n      \".foo{border-width:100px}\",\n    );\n    minify_test(\n      \".foo { border-width: clamp(5px + 5px, 5px + 7px, 10px + 20px) }\",\n      \".foo{border-width:12px}\",\n    );\n\n    minify_test(\n      \".foo { border-width: clamp(1em, 2px, 4vh) }\",\n      \".foo{border-width:clamp(1em,2px,4vh)}\",\n    );\n    minify_test(\n      \".foo { border-width: clamp(1em, 2em, 4vh) }\",\n      \".foo{border-width:clamp(1em,2em,4vh)}\",\n    );\n    minify_test(\n      \".foo { border-width: clamp(1em, 2vh, 4vh) }\",\n      \".foo{border-width:max(1em,2vh)}\",\n    );\n    minify_test(\n      \".foo { border-width: clamp(1px, 1px + 2em, 4px) }\",\n      \".foo{border-width:clamp(1px,1px + 2em,4px)}\",\n    );\n    minify_test(\".foo { border-width: clamp(1px, 2pt, 1in) }\", \".foo{border-width:2pt}\");\n    minify_test(\n      \".foo { width: clamp(-100px, 0px, 50% - 50vw); }\",\n      \".foo{width:clamp(-100px,0px,50% - 50vw)}\",\n    );\n\n    minify_test(\n      \".foo { top: calc(-1 * clamp(1.75rem, 8vw, 4rem)) }\",\n      \".foo{top:calc(-1*clamp(1.75rem,8vw,4rem))}\",\n    );\n    minify_test(\n      \".foo { top: calc(-1 * min(1.75rem, 8vw, 4rem)) }\",\n      \".foo{top:calc(-1*min(1.75rem,8vw))}\",\n    );\n    minify_test(\n      \".foo { top: calc(-1 * max(1.75rem, 8vw, 4rem)) }\",\n      \".foo{top:calc(-1*max(4rem,8vw))}\",\n    );\n    minify_test(\n      \".foo { top: calc(clamp(1.75rem, 8vw, 4rem) * -1) }\",\n      \".foo{top:calc(-1*clamp(1.75rem,8vw,4rem))}\",\n    );\n    minify_test(\n      \".foo { top: calc(min(1.75rem, 8vw, 4rem) * -1) }\",\n      \".foo{top:calc(-1*min(1.75rem,8vw))}\",\n    );\n    minify_test(\n      \".foo { top: calc(max(1.75rem, 8vw, 4rem) * -1) }\",\n      \".foo{top:calc(-1*max(4rem,8vw))}\",\n    );\n    minify_test(\n      \".foo { top: calc(clamp(1.75rem, 8vw, 4rem) / 2) }\",\n      \".foo{top:calc(clamp(1.75rem,8vw,4rem)/2)}\",\n    );\n    minify_test(\n      \".foo { top: calc(min(1.75rem, 8vw, 4rem) / 2) }\",\n      \".foo{top:calc(min(1.75rem,8vw)/2)}\",\n    );\n    minify_test(\n      \".foo { top: calc(max(1.75rem, 8vw, 4rem) / 2) }\",\n      \".foo{top:calc(max(4rem,8vw)/2)}\",\n    );\n    minify_test(\n      \".foo { top: calc(0.5 * clamp(1.75rem, 8vw, 4rem)) }\",\n      \".foo{top:calc(clamp(1.75rem,8vw,4rem)/2)}\",\n    );\n    minify_test(\n      \".foo { top: calc(1 * clamp(1.75rem, 8vw, 4rem)) }\",\n      \".foo{top:calc(clamp(1.75rem,8vw,4rem))}\",\n    );\n    minify_test(\n      \".foo { top: calc(2 * clamp(1.75rem, 8vw, 4rem) / 2) }\",\n      \".foo{top:calc(clamp(1.75rem,8vw,4rem))}\",\n    );\n\n    minify_test(\".foo { width: max(0px, 1vw) }\", \".foo{width:max(0px,1vw)}\");\n\n    prefix_test(\n      \".foo { border-width: clamp(1em, 2px, 4vh) }\",\n      indoc! { r#\"\n        .foo {\n          border-width: max(1em, min(2px, 4vh));\n        }\n      \"#},\n      Browsers {\n        safari: Some(12 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { border-width: clamp(1em, 2px, 4vh) }\",\n      indoc! { r#\"\n        .foo {\n          border-width: clamp(1em, 2px, 4vh);\n        }\n      \"#},\n      Browsers {\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    minify_test(\".foo { width: calc(1vh + 2vh) }\", \".foo{width:3vh}\");\n    minify_test(\".foo { width: calc(1dvh + 2dvh) }\", \".foo{width:3dvh}\");\n    minify_test(\".foo { width: calc(1lvh + 2lvh) }\", \".foo{width:3lvh}\");\n    minify_test(\".foo { width: calc(1svh + 2svh) }\", \".foo{width:3svh}\");\n    minify_test(\".foo { width: calc(1sVmin + 2Svmin) }\", \".foo{width:3svmin}\");\n    minify_test(\".foo { width: calc(1ic + 2ic) }\", \".foo{width:3ic}\");\n    minify_test(\".foo { width: calc(1ric + 2ric) }\", \".foo{width:3ric}\");\n    minify_test(\".foo { width: calc(1cap + 2cap) }\", \".foo{width:3cap}\");\n    minify_test(\".foo { width: calc(1lh + 2lh) }\", \".foo{width:3lh}\");\n    minify_test(\".foo { width: calc(1x + 2x) }\", \".foo{width:calc(1x + 2x)}\");\n    minify_test(\n      \".foo { left: calc(50% - 100px + clamp(0px, calc(50vw - 50px), 100px)) }\",\n      \".foo{left:calc(50% - 100px + clamp(0px,50vw - 50px,100px))}\",\n    );\n    minify_test(\n      \".foo { left: calc(10px + min(10px, 1rem) + max(2px, 1vw)) }\",\n      \".foo{left:calc(10px + min(10px,1rem) + max(2px,1vw))}\",\n    );\n    minify_test(\".foo { width: round(22px, 5px) }\", \".foo{width:20px}\");\n    minify_test(\".foo { width: round(nearest, 22px, 5px) }\", \".foo{width:20px}\");\n    minify_test(\".foo { width: round(down, 22px, 5px) }\", \".foo{width:20px}\");\n    minify_test(\".foo { width: round(to-zero, 22px, 5px) }\", \".foo{width:20px}\");\n    minify_test(\".foo { width: round(up, 22px, 5px) }\", \".foo{width:25px}\");\n    minify_test(\".foo { width: round(23px, 5px) }\", \".foo{width:25px}\");\n    minify_test(\".foo { width: round(nearest, 23px, 5px) }\", \".foo{width:25px}\");\n    minify_test(\".foo { width: round(down, 23px, 5px) }\", \".foo{width:20px}\");\n    minify_test(\".foo { width: round(to-zero, 23px, 5px) }\", \".foo{width:20px}\");\n    minify_test(\".foo { width: round(up, 23px, 5px) }\", \".foo{width:25px}\");\n    minify_test(\".foo { width: round(22px, 5vw) }\", \".foo{width:round(22px,5vw)}\");\n    minify_test(\".foo { rotate: round(22deg, 5deg) }\", \".foo{rotate:20deg}\");\n    minify_test(\".foo { rotate: round(22deg, 5deg) }\", \".foo{rotate:20deg}\");\n    minify_test(\n      \".foo { transition-duration: round(22ms, 5ms) }\",\n      \".foo{transition-duration:20ms}\",\n    );\n    minify_test(\".foo { margin: round(to-zero, -23px, 5px) }\", \".foo{margin:-20px}\");\n    minify_test(\".foo { margin: round(nearest, -23px, 5px) }\", \".foo{margin:-25px}\");\n    minify_test(\".foo { margin: calc(10px * round(22, 5)) }\", \".foo{margin:200px}\");\n    minify_test(\".foo { width: rem(18px, 5px) }\", \".foo{width:3px}\");\n    minify_test(\".foo { width: rem(-18px, 5px) }\", \".foo{width:-3px}\");\n    minify_test(\".foo { width: rem(18px, 5vw) }\", \".foo{width:rem(18px,5vw)}\");\n    minify_test(\".foo { rotate: rem(-140deg, -90deg) }\", \".foo{rotate:-50deg}\");\n    minify_test(\".foo { rotate: rem(140deg, -90deg) }\", \".foo{rotate:50deg}\");\n    minify_test(\".foo { width: calc(10px * rem(18, 5)) }\", \".foo{width:30px}\");\n    minify_test(\".foo { width: mod(18px, 5px) }\", \".foo{width:3px}\");\n    minify_test(\".foo { width: mod(-18px, 5px) }\", \".foo{width:2px}\");\n    minify_test(\".foo { rotate: mod(-140deg, -90deg) }\", \".foo{rotate:-50deg}\");\n    minify_test(\".foo { rotate: mod(140deg, -90deg) }\", \".foo{rotate:-40deg}\");\n    minify_test(\".foo { width: mod(18px, 5vw) }\", \".foo{width:mod(18px,5vw)}\");\n    minify_test(\n      \".foo { transform: rotateX(mod(140deg, -90deg)) rotateY(rem(140deg, -90deg)) }\",\n      \".foo{transform:rotateX(-40deg)rotateY(50deg)}\",\n    );\n    minify_test(\".foo { width: calc(10px * mod(18, 5)) }\", \".foo{width:30px}\");\n\n    minify_test(\n      \".foo { width: calc(100% - 30px - 0) }\",\n      \".foo{width:calc(100% - 30px - 0)}\",\n    );\n    minify_test(\n      \".foo { width: calc(100% - 30px - 1 - 2) }\",\n      \".foo{width:calc(100% - 30px - 3)}\",\n    );\n    minify_test(\n      \".foo { width: calc(1 - 2 - 100% - 30px) }\",\n      \".foo{width:calc(-1 - 100% - 30px)}\",\n    );\n    minify_test(\n      \".foo { width: calc(2 * min(1px, 1vmin) - min(1px, 1vmin)); }\",\n      \".foo{width:calc(2*min(1px,1vmin) - min(1px,1vmin))}\",\n    );\n    minify_test(\n      \".foo { width: calc(100% - clamp(1.125rem, 1.25vw, 1.2375rem) - clamp(1.125rem, 1.25vw, 1.2375rem)); }\",\n      \".foo{width:calc(100% - clamp(1.125rem,1.25vw,1.2375rem) - clamp(1.125rem,1.25vw,1.2375rem))}\",\n    );\n    minify_test(\n      \".foo { width: calc(100% - 2 (2 * var(--card-margin))); }\",\n      \".foo{width:calc(100% - 2 (2 * var(--card-margin)))}\",\n    );\n\n    test(\n      indoc! {r#\"\n    .test {\n      width: calc(var(--test) + 2px);\n      width: calc(var(--test) - 2px);\n    }\n    \"#},\n      indoc! {r#\"\n    .test {\n      width: calc(var(--test) + 2px);\n      width: calc(var(--test) - 2px);\n    }\n    \"#},\n    );\n  }\n\n  #[test]\n  fn test_trig() {\n    minify_test(\".foo { width: calc(2px * pi); }\", \".foo{width:6.28319px}\");\n    minify_test(\".foo { width: calc(2px / pi); }\", \".foo{width:.63662px}\");\n    // minify_test(\n    //   \".foo { width: calc(2px * infinity); }\",\n    //   \".foo{width:calc(2px*infinity)}\",\n    // );\n    // minify_test(\n    //   \".foo { width: calc(2px * -infinity); }\",\n    //   \".foo{width:calc(2px*-infinity)}\",\n    // );\n    minify_test(\".foo { width: calc(100px * sin(45deg))\", \".foo{width:70.7107px}\");\n    minify_test(\".foo { width: calc(100px * sin(.125turn))\", \".foo{width:70.7107px}\");\n    minify_test(\n      \".foo { width: calc(100px * sin(3.14159265 / 4))\",\n      \".foo{width:70.7107px}\",\n    );\n    minify_test(\".foo { width: calc(100px * sin(pi / 4))\", \".foo{width:70.7107px}\");\n    minify_test(\n      \".foo { width: calc(100px * sin(22deg + 23deg))\",\n      \".foo{width:70.7107px}\",\n    );\n\n    minify_test(\".foo { width: calc(2px * cos(45deg))\", \".foo{width:1.41421px}\");\n    minify_test(\".foo { width: calc(2px * tan(45deg))\", \".foo{width:2px}\");\n\n    minify_test(\".foo { rotate: asin(sin(45deg))\", \".foo{rotate:45deg}\");\n    minify_test(\".foo { rotate: asin(1)\", \".foo{rotate:90deg}\");\n    minify_test(\".foo { rotate: asin(-1)\", \".foo{rotate:-90deg}\");\n    minify_test(\".foo { rotate: asin(0.5)\", \".foo{rotate:30deg}\");\n    minify_test(\".foo { rotate: asin(45deg)\", \".foo{rotate:asin(45deg)}\"); // invalid\n    minify_test(\".foo { rotate: asin(-20)\", \".foo{rotate:asin(-20)}\"); // evaluates to NaN\n    minify_test(\".foo { width: asin(sin(45deg))\", \".foo{width:asin(sin(45deg))}\"); // invalid\n\n    minify_test(\".foo { rotate: acos(cos(45deg))\", \".foo{rotate:45deg}\");\n    minify_test(\".foo { rotate: acos(-1)\", \".foo{rotate:180deg}\");\n    minify_test(\".foo { rotate: acos(0)\", \".foo{rotate:90deg}\");\n    minify_test(\".foo { rotate: acos(1)\", \".foo{rotate:0deg}\");\n    minify_test(\".foo { rotate: acos(45deg)\", \".foo{rotate:acos(45deg)}\"); // invalid\n    minify_test(\".foo { rotate: acos(-20)\", \".foo{rotate:acos(-20)}\"); // evaluates to NaN\n\n    minify_test(\".foo { rotate: atan(tan(45deg))\", \".foo{rotate:45deg}\");\n    minify_test(\".foo { rotate: atan(1)\", \".foo{rotate:45deg}\");\n    minify_test(\".foo { rotate: atan(0)\", \".foo{rotate:0deg}\");\n    minify_test(\".foo { rotate: atan(45deg)\", \".foo{rotate:atan(45deg)}\"); // invalid\n\n    minify_test(\".foo { rotate: atan2(1px, -1px)\", \".foo{rotate:135deg}\");\n    minify_test(\".foo { rotate: atan2(1vw, -1vw)\", \".foo{rotate:135deg}\");\n    minify_test(\".foo { rotate: atan2(1, -1)\", \".foo{rotate:135deg}\");\n    minify_test(\".foo { rotate: atan2(1ms, -1ms)\", \".foo{rotate:135deg}\");\n    minify_test(\".foo { rotate: atan2(1%, -1%)\", \".foo{rotate:135deg}\");\n    minify_test(\".foo { rotate: atan2(1deg, -1deg)\", \".foo{rotate:135deg}\");\n    minify_test(\".foo { rotate: atan2(1cm, 1mm)\", \".foo{rotate:84.2894deg}\");\n    minify_test(\".foo { rotate: atan2(0, -1)\", \".foo{rotate:180deg}\");\n    minify_test(\".foo { rotate: atan2(-1, 1)\", \".foo{rotate:-45deg}\");\n    // incompatible units\n    minify_test(\".foo { rotate: atan2(1px, -1vw)\", \".foo{rotate:atan2(1px, -1vw)}\");\n\n    minify_test(\".foo { transform: rotate(acos(1)) }\", \".foo{transform:rotate(0)}\");\n    minify_test(\".foo { transform: rotate(atan(0)) }\", \".foo{transform:rotate(0)}\");\n  }\n\n  #[test]\n  fn test_exp() {\n    minify_test(\".foo { width: hypot()\", \".foo{width:hypot()}\");\n    minify_test(\".foo { width: hypot(1px)\", \".foo{width:1px}\");\n    minify_test(\".foo { width: hypot(1px, 2px)\", \".foo{width:2.23607px}\");\n    minify_test(\".foo { width: hypot(1px, 2px, 3px)\", \".foo{width:3.74166px}\");\n    minify_test(\".foo { width: hypot(1px, 2vw)\", \".foo{width:hypot(1px,2vw)}\");\n    minify_test(\".foo { width: hypot(1px, 2px, 3vw)\", \".foo{width:hypot(1px,2px,3vw)}\");\n    minify_test(\".foo { width: calc(100px * hypot(3, 4))\", \".foo{width:500px}\");\n    minify_test(\".foo { width: calc(1px * pow(2, sqrt(100))\", \".foo{width:1024px}\");\n    minify_test(\".foo { width: calc(100px * pow(2, pow(2, 2)\", \".foo{width:1600px}\");\n    minify_test(\".foo { width: calc(1px * log(1))\", \".foo{width:0}\");\n    minify_test(\".foo { width: calc(1px * log(10, 10))\", \".foo{width:1px}\");\n    minify_test(\".foo { width: calc(1px * exp(0))\", \".foo{width:1px}\");\n    minify_test(\".foo { width: calc(1px * log(e))\", \".foo{width:1px}\");\n    minify_test(\".foo { width: calc(1px * (e - exp(1)))\", \".foo{width:0}\");\n    minify_test(\n      \".foo { width: calc(1px * (exp(log(1) + exp(0)*2))\",\n      \".foo{width:7.38906px}\",\n    );\n  }\n\n  #[test]\n  fn test_sign() {\n    minify_test(\".foo { width: abs(1px)\", \".foo{width:1px}\");\n    minify_test(\".foo { width: abs(-1px)\", \".foo{width:1px}\");\n    minify_test(\".foo { width: abs(1%)\", \".foo{width:abs(1%)}\"); // spec says percentages must be against resolved value\n\n    minify_test(\".foo { width: calc(10px * sign(-1vw)\", \".foo{width:-10px}\");\n    minify_test(\n      \".foo { width: calc(10px * sign(1%)\",\n      \".foo{width:calc(10px * sign(1%))}\",\n    );\n  }\n\n  #[test]\n  fn test_box_shadow() {\n    minify_test(\n      \".foo { box-shadow: 64px 64px 12px 40px rgba(0,0,0,0.4) }\",\n      \".foo{box-shadow:64px 64px 12px 40px #0006}\",\n    );\n    minify_test(\n      \".foo { box-shadow: 12px 12px 0px 8px rgba(0,0,0,0.4) inset }\",\n      \".foo{box-shadow:inset 12px 12px 0 8px #0006}\",\n    );\n    minify_test(\n      \".foo { box-shadow: inset 12px 12px 0px 8px rgba(0,0,0,0.4) }\",\n      \".foo{box-shadow:inset 12px 12px 0 8px #0006}\",\n    );\n    minify_test(\n      \".foo { box-shadow: 12px 12px 8px 0px rgba(0,0,0,0.4) }\",\n      \".foo{box-shadow:12px 12px 8px #0006}\",\n    );\n    minify_test(\n      \".foo { box-shadow: 12px 12px 0px 0px rgba(0,0,0,0.4) }\",\n      \".foo{box-shadow:12px 12px #0006}\",\n    );\n    minify_test(\n      \".foo { box-shadow: 64px 64px 12px 40px rgba(0,0,0,0.4), 12px 12px 0px 8px rgba(0,0,0,0.4) inset }\",\n      \".foo{box-shadow:64px 64px 12px 40px #0006,inset 12px 12px 0 8px #0006}\",\n    );\n\n    prefix_test(\n      \".foo { box-shadow: 12px 12px lab(40% 56.6 39) }\",\n      indoc! { r#\"\n        .foo {\n          box-shadow: 12px 12px #b32323;\n          box-shadow: 12px 12px lab(40% 56.6 39);\n        }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { box-shadow: 12px 12px lab(40% 56.6 39) }\",\n      indoc! { r#\"\n        .foo {\n          -webkit-box-shadow: 12px 12px #b32323;\n          box-shadow: 12px 12px #b32323;\n          box-shadow: 12px 12px lab(40% 56.6 39);\n        }\n      \"#},\n      Browsers {\n        chrome: Some(4 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { box-shadow: 12px 12px lab(40% 56.6 39), 12px 12px yellow }\",\n      indoc! { r#\"\n        .foo {\n          -webkit-box-shadow: 12px 12px #b32323, 12px 12px #ff0;\n          box-shadow: 12px 12px #b32323, 12px 12px #ff0;\n          box-shadow: 12px 12px lab(40% 56.6 39), 12px 12px #ff0;\n        }\n      \"#},\n      Browsers {\n        chrome: Some(4 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { -webkit-box-shadow: 12px 12px #0006 }\",\n      indoc! { r#\"\n        .foo {\n          -webkit-box-shadow: 12px 12px rgba(0, 0, 0, .4);\n        }\n      \"#},\n      Browsers {\n        chrome: Some(4 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo {\n        -webkit-box-shadow: 12px 12px #0006;\n        -moz-box-shadow: 12px 12px #0009;\n      }\",\n      indoc! { r#\"\n        .foo {\n          -webkit-box-shadow: 12px 12px rgba(0, 0, 0, .4);\n          -moz-box-shadow: 12px 12px rgba(0, 0, 0, .6);\n        }\n      \"#},\n      Browsers {\n        chrome: Some(4 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo {\n        -webkit-box-shadow: 12px 12px #0006;\n        -moz-box-shadow: 12px 12px #0006;\n        box-shadow: 12px 12px #0006;\n      }\",\n      indoc! { r#\"\n        .foo {\n          box-shadow: 12px 12px #0006;\n        }\n      \"#},\n      Browsers {\n        chrome: Some(95 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { box-shadow: var(--foo) 12px lab(40% 56.6 39) }\",\n      indoc! { r#\"\n        .foo {\n          box-shadow: var(--foo) 12px #b32323;\n        }\n\n        @supports (color: lab(0% 0 0)) {\n          .foo {\n            box-shadow: var(--foo) 12px lab(40% 56.6 39);\n          }\n        }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        box-shadow: 0px 0px 22px red;\n        box-shadow: 0px 0px max(2cqw, 22px) red;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        box-shadow: 0 0 22px red;\n        box-shadow: 0 0 max(2cqw, 22px) red;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        box-shadow: 0px 0px 22px red;\n        box-shadow: 0px 0px max(2cqw, 22px) red;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        box-shadow: 0 0 max(2cqw, 22px) red;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(16 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        box-shadow: 0px 0px 22px red;\n        box-shadow: 0px 0px 22px lab(40% 56.6 39);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        box-shadow: 0 0 22px red;\n        box-shadow: 0 0 22px lab(40% 56.6 39);\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        box-shadow: 0px 0px 22px red;\n        box-shadow: 0px 0px 22px lab(40% 56.6 39);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        box-shadow: 0 0 22px lab(40% 56.6 39);\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(16 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        box-shadow: var(--fallback);\n        box-shadow: 0px 0px 22px lab(40% 56.6 39);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        box-shadow: var(--fallback);\n        box-shadow: 0 0 22px lab(40% 56.6 39);\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(16 << 16),\n        ..Browsers::default()\n      },\n    );\n  }\n\n  #[test]\n  fn test_media() {\n    minify_test(\n      \"@media (min-width: 240px) { .foo { color: chartreuse }}\",\n      \"@media (width>=240px){.foo{color:#7fff00}}\",\n    );\n    minify_test(\n      \"@media (width < 240px) { .foo { color: chartreuse }}\",\n      \"@media (width<240px){.foo{color:#7fff00}}\",\n    );\n    minify_test(\n      \"@media (width <= 240px) { .foo { color: chartreuse }}\",\n      \"@media (width<=240px){.foo{color:#7fff00}}\",\n    );\n    minify_test(\n      \"@media (width > 240px) { .foo { color: chartreuse }}\",\n      \"@media (width>240px){.foo{color:#7fff00}}\",\n    );\n    minify_test(\n      \"@media (width >= 240px) { .foo { color: chartreuse }}\",\n      \"@media (width>=240px){.foo{color:#7fff00}}\",\n    );\n    minify_test(\n      \"@media (240px < width) { .foo { color: chartreuse }}\",\n      \"@media (width>240px){.foo{color:#7fff00}}\",\n    );\n    minify_test(\n      \"@media (240px <= width) { .foo { color: chartreuse }}\",\n      \"@media (width>=240px){.foo{color:#7fff00}}\",\n    );\n    minify_test(\n      \"@media (240px > width) { .foo { color: chartreuse }}\",\n      \"@media (width<240px){.foo{color:#7fff00}}\",\n    );\n    minify_test(\n      \"@media (240px >= width) { .foo { color: chartreuse }}\",\n      \"@media (width<=240px){.foo{color:#7fff00}}\",\n    );\n    minify_test(\n      \"@media (100px < width < 200px) { .foo { color: chartreuse }}\",\n      \"@media (100px<width<200px){.foo{color:#7fff00}}\",\n    );\n    minify_test(\n      \"@media (100px <= width <= 200px) { .foo { color: chartreuse }}\",\n      \"@media (100px<=width<=200px){.foo{color:#7fff00}}\",\n    );\n    minify_test(\n      \"@media (min-width: 30em) and (max-width: 50em) { .foo { color: chartreuse }}\",\n      \"@media (width>=30em) and (width<=50em){.foo{color:#7fff00}}\",\n    );\n    minify_test(\n      \"@media screen, print { .foo { color: chartreuse }}\",\n      \"@media screen,print{.foo{color:#7fff00}}\",\n    );\n    minify_test(\n      \"@media (hover: hover) { .foo { color: chartreuse }}\",\n      \"@media (hover:hover){.foo{color:#7fff00}}\",\n    );\n    minify_test(\n      \"@media (hover) { .foo { color: chartreuse }}\",\n      \"@media (hover){.foo{color:#7fff00}}\",\n    );\n    minify_test(\n      \"@media (aspect-ratio: 11/5) { .foo { color: chartreuse }}\",\n      \"@media (aspect-ratio:11/5){.foo{color:#7fff00}}\",\n    );\n    minify_test(\n      \"@media (aspect-ratio: 2/1) { .foo { color: chartreuse }}\",\n      \"@media (aspect-ratio:2){.foo{color:#7fff00}}\",\n    );\n    minify_test(\n      \"@media (aspect-ratio: 2) { .foo { color: chartreuse }}\",\n      \"@media (aspect-ratio:2){.foo{color:#7fff00}}\",\n    );\n    minify_test(\n      \"@media not screen and (color) { .foo { color: chartreuse }}\",\n      \"@media not screen and (color){.foo{color:#7fff00}}\",\n    );\n    minify_test(\n      \"@media only screen and (color) { .foo { color: chartreuse }}\",\n      \"@media only screen and (color){.foo{color:#7fff00}}\",\n    );\n    minify_test(\n      \"@media (update: slow) or (hover: none) { .foo { color: chartreuse }}\",\n      \"@media (update:slow) or (hover:none){.foo{color:#7fff00}}\",\n    );\n    minify_test(\n      \"@media (width < 600px) and (height < 600px) { .foo { color: chartreuse }}\",\n      \"@media (width<600px) and (height<600px){.foo{color:#7fff00}}\",\n    );\n    minify_test(\n      \"@media (not (color)) or (hover) { .foo { color: chartreuse }}\",\n      \"@media (not (color)) or (hover){.foo{color:#7fff00}}\",\n    );\n    error_test(\n      \"@media (example, all,), speech { .foo { color: chartreuse }}\",\n      ParserError::UnexpectedToken(Token::Comma),\n    );\n    error_test(\n      \"@media &test, speech { .foo { color: chartreuse }}\",\n      ParserError::UnexpectedToken(Token::Delim('&')),\n    );\n    error_test(\n      \"@media &test { .foo { color: chartreuse }}\",\n      ParserError::UnexpectedToken(Token::Delim('&')),\n    );\n    minify_test(\n      \"@media (min-width: calc(200px + 40px)) { .foo { color: chartreuse }}\",\n      \"@media (width>=240px){.foo{color:#7fff00}}\",\n    );\n    minify_test(\n      \"@media (min-width: calc(1em + 5px)) { .foo { color: chartreuse }}\",\n      \"@media (width>=calc(1em + 5px)){.foo{color:#7fff00}}\",\n    );\n    minify_test(\"@media { .foo { color: chartreuse }}\", \".foo{color:#7fff00}\");\n    minify_test(\"@media all { .foo { color: chartreuse }}\", \".foo{color:#7fff00}\");\n    minify_test(\n      \"@media not (((color) or (hover))) { .foo { color: chartreuse }}\",\n      \"@media not ((color) or (hover)){.foo{color:#7fff00}}\",\n    );\n    minify_test(\n      \"@media (hover) and ((color) and (test)) { .foo { color: chartreuse }}\",\n      \"@media (hover) and (color) and (test){.foo{color:#7fff00}}\",\n    );\n    minify_test(\n      \"@media (grid: 1) { .foo { color: chartreuse }}\",\n      \"@media (grid:1){.foo{color:#7fff00}}\",\n    );\n    minify_test(\n      \"@media (width >= calc(2px + 4px)) { .foo { color: chartreuse }}\",\n      \"@media (width>=6px){.foo{color:#7fff00}}\",\n    );\n\n    prefix_test(\n      r#\"\n        @media (width >= 240px) {\n          .foo {\n            color: chartreuse;\n          }\n        }\n      \"#,\n      indoc! { r#\"\n        @media (min-width: 240px) {\n          .foo {\n            color: #7fff00;\n          }\n        }\n      \"#},\n      Browsers {\n        firefox: Some(60 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n        @media (width >= 240px) {\n          .foo {\n            color: chartreuse;\n          }\n        }\n      \"#,\n      indoc! { r#\"\n        @media (width >= 240px) {\n          .foo {\n            color: #7fff00;\n          }\n        }\n      \"#},\n      Browsers {\n        firefox: Some(64 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n        @media (color > 2) {\n          .foo {\n            color: chartreuse;\n          }\n        }\n      \"#,\n      indoc! { r#\"\n        @media not (max-color: 2) {\n          .foo {\n            color: #7fff00;\n          }\n        }\n      \"#},\n      Browsers {\n        firefox: Some(60 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n        @media (color < 2) {\n          .foo {\n            color: chartreuse;\n          }\n        }\n      \"#,\n      indoc! { r#\"\n        @media not (min-color: 2) {\n          .foo {\n            color: #7fff00;\n          }\n        }\n      \"#},\n      Browsers {\n        firefox: Some(60 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n        @media (width > 240px) {\n          .foo {\n            color: chartreuse;\n          }\n        }\n      \"#,\n      indoc! { r#\"\n        @media not (max-width: 240px) {\n          .foo {\n            color: #7fff00;\n          }\n        }\n      \"#},\n      Browsers {\n        firefox: Some(60 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n        @media (width <= 240px) {\n          .foo {\n            color: chartreuse;\n          }\n        }\n      \"#,\n      indoc! { r#\"\n        @media (max-width: 240px) {\n          .foo {\n            color: #7fff00;\n          }\n        }\n      \"#},\n      Browsers {\n        firefox: Some(60 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n        @media (width <= 240px) {\n          .foo {\n            color: chartreuse;\n          }\n        }\n      \"#,\n      indoc! { r#\"\n        @media (width <= 240px) {\n          .foo {\n            color: #7fff00;\n          }\n        }\n      \"#},\n      Browsers {\n        firefox: Some(64 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n        @media (width < 240px) {\n          .foo {\n            color: chartreuse;\n          }\n        }\n      \"#,\n      indoc! { r#\"\n        @media not (min-width: 240px) {\n          .foo {\n            color: #7fff00;\n          }\n        }\n      \"#},\n      Browsers {\n        firefox: Some(60 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n        @media not (width < 240px) {\n          .foo {\n            color: chartreuse;\n          }\n        }\n      \"#,\n      indoc! { r#\"\n        @media (min-width: 240px) {\n          .foo {\n            color: #7fff00;\n          }\n        }\n      \"#},\n      Browsers {\n        firefox: Some(60 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    test(\n      r#\"\n        @media not (width < 240px) {\n          .foo {\n            color: chartreuse;\n          }\n        }\n      \"#,\n      indoc! { r#\"\n        @media (width >= 240px) {\n          .foo {\n            color: #7fff00;\n          }\n        }\n      \"#},\n    );\n\n    prefix_test(\n      r#\"\n        @media (width < 240px) and (hover) {\n          .foo {\n            color: chartreuse;\n          }\n        }\n      \"#,\n      indoc! { r#\"\n        @media (not (min-width: 240px)) and (hover) {\n          .foo {\n            color: #7fff00;\n          }\n        }\n      \"#},\n      Browsers {\n        firefox: Some(60 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n        @media (100px <= width <= 200px) {\n          .foo {\n            color: chartreuse;\n          }\n        }\n      \"#,\n      indoc! { r#\"\n        @media (min-width: 100px) and (max-width: 200px) {\n          .foo {\n            color: #7fff00;\n          }\n        }\n      \"#},\n      Browsers {\n        firefox: Some(85 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n        @media not (100px <= width <= 200px) {\n          .foo {\n            color: chartreuse;\n          }\n        }\n      \"#,\n      indoc! { r#\"\n        @media not ((min-width: 100px) and (max-width: 200px)) {\n          .foo {\n            color: #7fff00;\n          }\n        }\n      \"#},\n      Browsers {\n        firefox: Some(85 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n        @media (hover) and (100px <= width <= 200px) {\n          .foo {\n            color: chartreuse;\n          }\n        }\n      \"#,\n      indoc! { r#\"\n        @media (hover) and (min-width: 100px) and (max-width: 200px) {\n          .foo {\n            color: #7fff00;\n          }\n        }\n      \"#},\n      Browsers {\n        firefox: Some(85 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n        @media (hover) or (100px <= width <= 200px) {\n          .foo {\n            color: chartreuse;\n          }\n        }\n      \"#,\n      indoc! { r#\"\n        @media (hover) or ((min-width: 100px) and (max-width: 200px)) {\n          .foo {\n            color: #7fff00;\n          }\n        }\n      \"#},\n      Browsers {\n        firefox: Some(85 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n        @media (100px < width < 200px) {\n          .foo {\n            color: chartreuse;\n          }\n        }\n      \"#,\n      indoc! { r#\"\n        @media (not (max-width: 100px)) and (not (min-width: 200px)) {\n          .foo {\n            color: #7fff00;\n          }\n        }\n      \"#},\n      Browsers {\n        firefox: Some(85 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n        @media not (100px < width < 200px) {\n          .foo {\n            color: chartreuse;\n          }\n        }\n      \"#,\n      indoc! { r#\"\n        @media not ((not (max-width: 100px)) and (not (min-width: 200px))) {\n          .foo {\n            color: #7fff00;\n          }\n        }\n      \"#},\n      Browsers {\n        firefox: Some(85 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n        @media (200px >= width >= 100px) {\n          .foo {\n            color: chartreuse;\n          }\n        }\n      \"#,\n      indoc! { r#\"\n        @media (max-width: 200px) and (min-width: 100px) {\n          .foo {\n            color: #7fff00;\n          }\n        }\n      \"#},\n      Browsers {\n        firefox: Some(85 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    test(\n      r#\"\n      @media not all {\n        .a {\n          color: green;\n        }\n      }\n      \"#,\n      \"\\n\",\n    );\n\n    prefix_test(\n      r#\"\n      @media (width > calc(1px + 1rem)) {\n        .foo { color: yellow; }\n      }\n      \"#,\n      indoc! { r#\"\n        @media not (max-width: calc(1px + 1rem)) {\n          .foo {\n            color: #ff0;\n          }\n        }\n      \"#},\n      Browsers {\n        chrome: Some(85 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      @media (width > max(10px, 1rem)) {\n        .foo { color: yellow; }\n      }\n      \"#,\n      indoc! { r#\"\n        @media not (max-width: max(10px, 1rem)) {\n          .foo {\n            color: #ff0;\n          }\n        }\n      \"#},\n      Browsers {\n        chrome: Some(85 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      @media (width > 0) {\n        .foo { color: yellow; }\n      }\n      \"#,\n      indoc! { r#\"\n        @media not (max-width: 0) {\n          .foo {\n            color: #ff0;\n          }\n        }\n      \"#},\n      Browsers {\n        chrome: Some(85 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      @media (min-resolution: 2dppx) {\n        .foo { color: yellow; }\n      }\n      \"#,\n      indoc! { r#\"\n        @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 2dppx) {\n          .foo {\n            color: #ff0;\n          }\n        }\n      \"#},\n      Browsers {\n        safari: Some(15 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      @media (min-resolution: 2dppx) {\n        .foo { color: yellow; }\n      }\n      \"#,\n      indoc! { r#\"\n        @media (min--moz-device-pixel-ratio: 2), (min-resolution: 2dppx) {\n          .foo {\n            color: #ff0;\n          }\n        }\n      \"#},\n      Browsers {\n        firefox: Some(10 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      @media (resolution > 2dppx) {\n        .foo { color: yellow; }\n      }\n      \"#,\n      indoc! { r#\"\n        @media not (-webkit-max-device-pixel-ratio: 2), not (max-resolution: 2dppx) {\n          .foo {\n            color: #ff0;\n          }\n        }\n      \"#},\n      Browsers {\n        safari: Some(15 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      @media (resolution >= 300dpi) {\n        .foo { color: yellow; }\n      }\n      \"#,\n      indoc! { r#\"\n        @media (-webkit-min-device-pixel-ratio: 3.125), (min-resolution: 300dpi) {\n          .foo {\n            color: #ff0;\n          }\n        }\n      \"#},\n      Browsers {\n        safari: Some(15 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      @media (min-resolution: 113.38dpcm) {\n        .foo { color: yellow; }\n      }\n      \"#,\n      indoc! { r#\"\n        @media (-webkit-min-device-pixel-ratio: 2.99985), (min--moz-device-pixel-ratio: 2.99985), (min-resolution: 113.38dpcm) {\n          .foo {\n            color: #ff0;\n          }\n        }\n      \"#},\n      Browsers {\n        safari: Some(15 << 16),\n        firefox: Some(10 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      @media (color) and (min-resolution: 2dppx) {\n        .foo { color: yellow; }\n      }\n      \"#,\n      indoc! { r#\"\n        @media (color) and (-webkit-min-device-pixel-ratio: 2), (color) and (min-resolution: 2dppx) {\n          .foo {\n            color: #ff0;\n          }\n        }\n      \"#},\n      Browsers {\n        safari: Some(15 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      @media (min-resolution: 2dppx),\n             (min-resolution: 192dpi) {\n        .foo { color: yellow; }\n      }\n      \"#,\n      indoc! { r#\"\n        @media (-webkit-min-device-pixel-ratio: 2), (min--moz-device-pixel-ratio: 2), (min-resolution: 2dppx), (min-resolution: 192dpi) {\n          .foo {\n            color: #ff0;\n          }\n        }\n      \"#},\n      Browsers {\n        safari: Some(15 << 16),\n        firefox: Some(10 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      @media only screen and (min-resolution: 124.8dpi) {\n        .foo { color: yellow; }\n      }\n      \"#,\n      indoc! { r#\"\n        @media only screen and (-webkit-min-device-pixel-ratio: 1.3), only screen and (min--moz-device-pixel-ratio: 1.3), only screen and (min-resolution: 124.8dpi) {\n          .foo {\n            color: #ff0;\n          }\n        }\n      \"#},\n      Browsers {\n        safari: Some(15 << 16),\n        firefox: Some(10 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    test_with_printer_options(\n      r#\"\n        @media (width < 256px) or (hover: none) {\n          .foo {\n            color: #fff;\n          }\n        }\n      \"#,\n      indoc! { r#\"\n        @media (not (min-width: 256px)) or (hover: none) {\n          .foo {\n            color: #fff;\n          }\n        }\n      \"#},\n      PrinterOptions {\n        targets: Targets {\n          browsers: None,\n          include: Features::MediaRangeSyntax,\n          exclude: Features::empty(),\n        },\n        ..Default::default()\n      },\n    );\n\n    error_test(\n      \"@media (min-width: hi) { .foo { color: chartreuse }}\",\n      ParserError::InvalidMediaQuery,\n    );\n    error_test(\n      \"@media (width >= hi) { .foo { color: chartreuse }}\",\n      ParserError::InvalidMediaQuery,\n    );\n    error_test(\n      \"@media (width >= 2/1) { .foo { color: chartreuse }}\",\n      ParserError::UnexpectedToken(Token::Delim('/')),\n    );\n    error_test(\n      \"@media (600px <= min-height) { .foo { color: chartreuse }}\",\n      ParserError::InvalidMediaQuery,\n    );\n    error_test(\n      \"@media (scan >= 1) { .foo { color: chartreuse }}\",\n      ParserError::InvalidMediaQuery,\n    );\n    error_test(\n      \"@media (min-scan: interlace) { .foo { color: chartreuse }}\",\n      ParserError::InvalidMediaQuery,\n    );\n    error_test(\n      \"@media (1px <= width <= bar) { .foo { color: chartreuse }}\",\n      ParserError::InvalidMediaQuery,\n    );\n    error_test(\n      \"@media (1px <= min-width <= 2px) { .foo { color: chartreuse }}\",\n      ParserError::InvalidMediaQuery,\n    );\n    error_test(\n      \"@media (1px <= scan <= 2px) { .foo { color: chartreuse }}\",\n      ParserError::InvalidMediaQuery,\n    );\n    error_test(\n      \"@media (grid: 10) { .foo { color: chartreuse }}\",\n      ParserError::InvalidMediaQuery,\n    );\n    error_test(\n      \"@media (prefers-color-scheme = dark) { .foo { color: chartreuse }}\",\n      ParserError::InvalidMediaQuery,\n    );\n    error_test(\n      \"@media unknown(foo) {}\",\n      ParserError::UnexpectedToken(crate::properties::custom::Token::Function(\"unknown\".into())),\n    );\n\n    // empty brackets should return a clearer error message\n    error_test(\"@media () {}\", ParserError::EmptyBracketInCondition);\n    error_test(\"@media screen and () {}\", ParserError::EmptyBracketInCondition);\n\n    error_recovery_test(\"@media unknown(foo) {}\");\n  }\n\n  #[test]\n  fn test_merge_layers() {\n    test(\n      r#\"\n      @layer foo {\n        .foo {\n          color: red;\n        }\n      }\n      @layer foo {\n        .foo {\n          background: #fff;\n        }\n\n        .baz {\n          color: #fff;\n        }\n      }\n      \"#,\n      indoc! {r#\"\n      @layer foo {\n        .foo {\n          color: red;\n          background: #fff;\n        }\n\n        .baz {\n          color: #fff;\n        }\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      @layer a {}\n      @layer b {}\n\n      @layer b {\n        foo {\n          color: red;\n        }\n      }\n\n      @layer a {\n        bar {\n          color: yellow;\n        }\n      }\n      \"#,\n      indoc! {r#\"\n      @layer a {\n        bar {\n          color: #ff0;\n        }\n      }\n\n      @layer b {\n        foo {\n          color: red;\n        }\n      }\n    \"#},\n    );\n\n    test(\n      r#\"\n      @layer a {}\n      @layer b {}\n\n      @layer b {\n        foo {\n          color: red;\n        }\n      }\n      \"#,\n      indoc! {r#\"\n      @layer a;\n\n      @layer b {\n        foo {\n          color: red;\n        }\n      }\n    \"#},\n    );\n\n    test(\n      r#\"\n      @layer a;\n      @layer b;\n      @layer c;\n      \"#,\n      indoc! {r#\"\n      @layer a, b, c;\n    \"#},\n    );\n\n    test(\n      r#\"\n      @layer a {}\n      @layer b {}\n      @layer c {}\n      \"#,\n      indoc! {r#\"\n      @layer a, b, c;\n    \"#},\n    );\n\n    test(\n      r#\"\n      @layer a;\n      @layer b {\n        .foo {\n          color: red;\n        }\n      }\n      @layer c {}\n      \"#,\n      indoc! {r#\"\n      @layer a;\n\n      @layer b {\n        .foo {\n          color: red;\n        }\n      }\n\n      @layer c;\n    \"#},\n    );\n\n    test(\n      r#\"\n      @layer a, b;\n      @layer c {}\n\n      @layer d {\n        foo {\n          color: red;\n        }\n      }\n      \"#,\n      indoc! {r#\"\n      @layer a, b, c;\n\n      @layer d {\n        foo {\n          color: red;\n        }\n      }\n    \"#},\n    );\n\n    test(\n      r#\"\n      @layer a;\n      @layer b;\n      @import \"a.css\" layer(x);\n      @layer c;\n\n      @layer d {\n        foo {\n          color: red;\n        }\n      }\n      \"#,\n      indoc! {r#\"\n      @layer a, b;\n      @import \"a.css\" layer(x);\n      @layer c;\n\n      @layer d {\n        foo {\n          color: red;\n        }\n      }\n    \"#},\n    );\n\n    test(\n      r#\"\n      @layer a, b, c;\n\n      @layer a {\n        foo {\n          color: red;\n        }\n      }\n      \"#,\n      indoc! {r#\"\n      @layer a {\n        foo {\n          color: red;\n        }\n      }\n\n      @layer b, c;\n    \"#},\n    );\n\n    test(\n      r#\"\n      @layer a;\n      @import \"foo.css\";\n\n      @layer a {\n        foo {\n          color: red;\n        }\n      }\n      \"#,\n      indoc! {r#\"\n      @layer a;\n      @import \"foo.css\";\n\n      @layer a {\n        foo {\n          color: red;\n        }\n      }\n    \"#},\n    );\n  }\n\n  #[test]\n  fn test_merge_rules() {\n    test(\n      r#\"\n      .foo {\n        color: red;\n      }\n      .bar {\n        color: red;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo, .bar {\n        color: red;\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      .foo {\n        color: red;\n      }\n      .foo {\n        background: green;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        color: red;\n        background: green;\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      .foo {\n        color: red;\n      }\n      .foo {\n        background: green !important;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        color: red;\n        background: green !important;\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      .foo {\n        background: red;\n      }\n      .foo {\n        background: green;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        background: green;\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      .foo {\n        --foo: red;\n        --foo: purple;\n      }\n      .foo {\n        --foo: green;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        --foo: green;\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      .foo {\n        color: red;\n      }\n\n      .bar {\n        background: green;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        color: red;\n      }\n\n      .bar {\n        background: green;\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      .foo {\n        color: red;\n      }\n\n      .baz {\n        color: blue;\n      }\n\n      .bar {\n        color: red;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        color: red;\n      }\n\n      .baz {\n        color: #00f;\n      }\n\n      .bar {\n        color: red;\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      .foo {\n        background: red;\n      }\n      .bar {\n        background: red;\n      }\n      .foo {\n        color: green;\n      }\n      .bar {\n        color: green;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo, .bar {\n        color: green;\n        background: red;\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      .foo, .bar {\n        background: red;\n      }\n      .foo {\n        color: green;\n      }\n      .bar {\n        color: green;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo, .bar {\n        color: green;\n        background: red;\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      .foo {\n        background: red;\n      }\n      .foo {\n        color: green;\n      }\n      .bar {\n        background: red;\n      }\n      .bar {\n        color: green;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo, .bar {\n        color: green;\n        background: red;\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      [foo=\"bar\"] {\n        color: red;\n      }\n      .bar {\n        color: red;\n      }\n    \"#,\n      indoc! {r#\"\n      [foo=\"bar\"], .bar {\n        color: red;\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      .a {\n        color: red;\n      }\n      .b {\n        color: green;\n      }\n      .a {\n        color: red;\n      }\n    \"#,\n      indoc! {r#\"\n      .b {\n        color: green;\n      }\n\n      .a {\n        color: red;\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      .a {\n        color: red;\n      }\n      .b {\n        color: green;\n      }\n      .a {\n        color: pink;\n      }\n    \"#,\n      indoc! {r#\"\n      .b {\n        color: green;\n      }\n\n      .a {\n        color: pink;\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      .a:foo(#000) {\n        color: red;\n      }\n      .b {\n        color: green;\n      }\n      .a:foo(#ff0) {\n        color: pink;\n      }\n    \"#,\n      indoc! {r#\"\n      .a:foo(#000) {\n        color: red;\n      }\n\n      .b {\n        color: green;\n      }\n\n      .a:foo(#ff0) {\n        color: pink;\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      .a {\n        border-radius: 10px;\n      }\n      .b {\n        color: green;\n      }\n      .a {\n        border-radius: 10px;\n      }\n    \"#,\n      indoc! {r#\"\n      .b {\n        color: green;\n      }\n\n      .a {\n        border-radius: 10px;\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      .a {\n        border-radius: 10px;\n      }\n      .b {\n        color: green;\n      }\n      .a {\n        -webkit-border-radius: 10px;\n      }\n    \"#,\n      indoc! {r#\"\n      .a {\n        border-radius: 10px;\n      }\n\n      .b {\n        color: green;\n      }\n\n      .a {\n        -webkit-border-radius: 10px;\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      .a {\n        border-radius: 10px;\n      }\n      .b {\n        color: green;\n      }\n      .a {\n        border-radius: var(--foo);\n      }\n    \"#,\n      indoc! {r#\"\n      .b {\n        color: green;\n      }\n\n      .a {\n        border-radius: var(--foo);\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      .a {\n        border-radius: 10px;\n      }\n      .b {\n        color: green;\n      }\n      .c {\n        border-radius: 20px;\n      }\n    \"#,\n      indoc! {r#\"\n      .a {\n        border-radius: 10px;\n      }\n\n      .b {\n        color: green;\n      }\n\n      .c {\n        border-radius: 20px;\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      @media print {\n        .a {\n          color: red;\n        }\n        .b {\n          color: green;\n        }\n        .a {\n          color: red;\n        }\n      }\n    \"#,\n      indoc! {r#\"\n      @media print {\n        .b {\n          color: green;\n        }\n\n        .a {\n          color: red;\n        }\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      .a {\n        border-radius: 10px;\n      }\n      .b {\n        color: green;\n      }\n      .a {\n        border-radius: 20px;\n        color: pink;\n      }\n    \"#,\n      indoc! {r#\"\n      .a {\n        border-radius: 10px;\n      }\n\n      .b {\n        color: green;\n      }\n\n      .a {\n        color: pink;\n        border-radius: 20px;\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      .a {\n        color: red;\n      }\n      .b {\n        color: green;\n      }\n      .a {\n        color: red;\n      }\n      .b {\n        color: green;\n      }\n    \"#,\n      indoc! {r#\"\n      .a {\n        color: red;\n      }\n\n      .b {\n        color: green;\n      }\n    \"#},\n    );\n\n    prefix_test(\n      r#\"\n      [foo=\"bar\"] {\n        color: red;\n      }\n      .bar {\n        color: red;\n      }\n    \"#,\n      indoc! {r#\"\n      [foo=\"bar\"] {\n        color: red;\n      }\n\n      .bar {\n        color: red;\n      }\n    \"#},\n      Browsers {\n        ie: Some(6 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      [foo=\"bar\"] {\n        color: red;\n      }\n      .bar {\n        color: red;\n      }\n    \"#,\n      indoc! {r#\"\n      [foo=\"bar\"], .bar {\n        color: red;\n      }\n    \"#},\n      Browsers {\n        ie: Some(10 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    test(\n      r#\"\n      .foo:-moz-read-only {\n        color: red;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo:-moz-read-only {\n        color: red;\n      }\n    \"#},\n    );\n\n    test(\n      r#\"\n      .foo:-moz-read-only {\n        color: red;\n      }\n\n      .foo:read-only {\n        color: red;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo:-moz-read-only {\n        color: red;\n      }\n\n      .foo:read-only {\n        color: red;\n      }\n    \"#},\n    );\n\n    prefix_test(\n      r#\"\n      .foo:-moz-read-only {\n        color: red;\n      }\n\n      .foo:read-only {\n        color: red;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo:read-only {\n        color: red;\n      }\n    \"#},\n      Browsers {\n        firefox: Some(85 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo:-moz-read-only {\n        color: red;\n      }\n\n      .bar {\n        color: yellow;\n      }\n\n      .foo:read-only {\n        color: red;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo:-moz-read-only {\n        color: red;\n      }\n\n      .bar {\n        color: #ff0;\n      }\n\n      .foo:read-only {\n        color: red;\n      }\n    \"#},\n      Browsers {\n        firefox: Some(85 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo:-moz-read-only {\n        color: red;\n      }\n\n      .foo:read-only {\n        color: red;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo:-moz-read-only {\n        color: red;\n      }\n\n      .foo:read-only {\n        color: red;\n      }\n    \"#},\n      Browsers {\n        firefox: Some(36 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo:read-only {\n        color: red;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo:-moz-read-only {\n        color: red;\n      }\n\n      .foo:read-only {\n        color: red;\n      }\n    \"#},\n      Browsers {\n        firefox: Some(36 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo:-webkit-full-screen {\n        color: red;\n      }\n      .foo:-moz-full-screen {\n        color: red;\n      }\n      .foo:-ms-fullscreen {\n        color: red;\n      }\n      .foo:fullscreen {\n        color: red;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo:fullscreen {\n        color: red;\n      }\n    \"#},\n      Browsers {\n        chrome: Some(96 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo:fullscreen {\n        color: red;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo:-webkit-full-screen {\n        color: red;\n      }\n\n      .foo:-moz-full-screen {\n        color: red;\n      }\n\n      .foo:-ms-fullscreen {\n        color: red;\n      }\n\n      .foo:fullscreen {\n        color: red;\n      }\n    \"#},\n      Browsers {\n        chrome: Some(45 << 16),\n        firefox: Some(45 << 16),\n        ie: Some(11 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo::placeholder {\n        color: red;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo::-webkit-input-placeholder {\n        color: red;\n      }\n\n      .foo::-moz-placeholder {\n        color: red;\n      }\n\n      .foo::-ms-input-placeholder {\n        color: red;\n      }\n\n      .foo::placeholder {\n        color: red;\n      }\n    \"#},\n      Browsers {\n        chrome: Some(45 << 16),\n        firefox: Some(45 << 16),\n        ie: Some(11 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo::file-selector-button {\n        color: red;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo::-webkit-file-upload-button {\n        color: red;\n      }\n\n      .foo::-ms-browse {\n        color: red;\n      }\n\n      .foo::file-selector-button {\n        color: red;\n      }\n    \"#},\n      Browsers {\n        chrome: Some(84 << 16),\n        ie: Some(10 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo::file-selector-button {\n        margin-inline-start: 2px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo:not(:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)))::-webkit-file-upload-button {\n        margin-left: 2px;\n      }\n\n      .foo:not(:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)))::-ms-browse {\n        margin-left: 2px;\n      }\n\n      .foo:not(:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)))::file-selector-button {\n        margin-left: 2px;\n      }\n\n      .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))::-webkit-file-upload-button {\n        margin-right: 2px;\n      }\n\n      .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))::-ms-browse {\n        margin-right: 2px;\n      }\n\n      .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))::file-selector-button {\n        margin-right: 2px;\n      }\n    \"#},\n      Browsers {\n        chrome: Some(84 << 16),\n        ie: Some(10 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo:placeholder-shown .bar { color: red; }\n      .foo:autofill .baz { color: red; }\n      \"#,\n      indoc! {r#\"\n      .foo:placeholder-shown .bar {\n        color: red;\n      }\n\n      .foo:-webkit-autofill .baz {\n        color: red;\n      }\n\n      .foo:autofill .baz {\n        color: red;\n      }\n      \"#},\n      Browsers {\n        chrome: Some(103 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo:placeholder-shown .bar,.foo:autofill .baz{color:red}\n      \"#,\n      indoc! {r#\"\n      :-webkit-any(.foo:placeholder-shown .bar, .foo:-webkit-autofill .baz) {\n        color: red;\n      }\n\n      :is(.foo:placeholder-shown .bar, .foo:autofill .baz) {\n        color: red;\n      }\n      \"#},\n      Browsers {\n        chrome: Some(103 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo:placeholder-shown .bar, .foo:-webkit-autofill .baz {\n        color: red;\n      }\n\n      .foo:placeholder-shown .bar, .foo:autofill .baz {\n        color: red;\n      }\n      \"#,\n      indoc! {r#\"\n      :-webkit-any(.foo:placeholder-shown .bar, .foo:-webkit-autofill .baz) {\n        color: red;\n      }\n\n      :is(.foo:placeholder-shown .bar, .foo:autofill .baz) {\n        color: red;\n      }\n      \"#},\n      Browsers {\n        chrome: Some(103 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    test(\n      r#\"\n      .foo:placeholder-shown .bar, .foo:-webkit-autofill .baz {\n        color: red;\n      }\n\n      .foo:placeholder-shown .bar, .foo:autofill .baz {\n        color: red;\n      }\n      \"#,\n      indoc! {r#\"\n      .foo:placeholder-shown .bar, .foo:-webkit-autofill .baz {\n        color: red;\n      }\n\n      .foo:placeholder-shown .bar, .foo:autofill .baz {\n        color: red;\n      }\n      \"#},\n    );\n\n    prefix_test(\n      r#\"\n      :hover, :focus-visible {\n        color: red;\n      }\n      \"#,\n      indoc! {r#\"\n      :hover {\n        color: red;\n      }\n\n      :focus-visible {\n        color: red;\n      }\n      \"#},\n      Browsers {\n        safari: Some(13 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        color: red;\n      }\n\n      :hover, :focus-visible {\n        color: red;\n      }\n      \"#,\n      indoc! {r#\"\n      .foo, :hover {\n        color: red;\n      }\n\n      :focus-visible {\n        color: red;\n      }\n      \"#},\n      Browsers {\n        safari: Some(13 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      :hover, :focus-visible {\n        margin-inline-start: 24px;\n      }\n      \"#,\n      indoc! {r#\"\n      :hover:not(:lang(ae, ar, arc, bcc, bqi, ckb, dv, fa, glk, he, ku, mzn, nqo, pnb, ps, sd, ug, ur, yi)) {\n        margin-left: 24px;\n      }\n\n      :hover:lang(ae, ar, arc, bcc, bqi, ckb, dv, fa, glk, he, ku, mzn, nqo, pnb, ps, sd, ug, ur, yi) {\n        margin-right: 24px;\n      }\n\n      :focus-visible:not(:lang(ae, ar, arc, bcc, bqi, ckb, dv, fa, glk, he, ku, mzn, nqo, pnb, ps, sd, ug, ur, yi)) {\n        margin-left: 24px;\n      }\n\n      :focus-visible:lang(ae, ar, arc, bcc, bqi, ckb, dv, fa, glk, he, ku, mzn, nqo, pnb, ps, sd, ug, ur, yi) {\n        margin-right: 24px;\n      }\n      \"#},\n      Browsers {\n        safari: Some(11 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      :focus-within, :focus-visible {\n        color: red;\n      }\n      \"#,\n      indoc! {r#\"\n      :focus-within {\n        color: red;\n      }\n\n      :focus-visible {\n        color: red;\n      }\n      \"#},\n      Browsers {\n        safari: Some(9 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      :hover, :focus-visible {\n        color: red;\n      }\n      \"#,\n      indoc! {r#\"\n      :is(:hover, :focus-visible) {\n        color: red;\n      }\n      \"#},\n      Browsers {\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      a::after:hover, a::after:focus-visible {\n        color: red;\n      }\n      \"#,\n      indoc! {r#\"\n      a:after:hover {\n        color: red;\n      }\n\n      a:after:focus-visible {\n        color: red;\n      }\n      \"#},\n      Browsers {\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      a:not(:hover), a:not(:focus-visible) {\n        color: red;\n      }\n      \"#,\n      indoc! {r#\"\n      :is(a:not(:hover), a:not(:focus-visible)) {\n        color: red;\n      }\n      \"#},\n      Browsers {\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      a:has(:hover), a:has(:focus-visible) {\n        color: red;\n      }\n      \"#,\n      indoc! {r#\"\n      :is(a:has(:hover), a:has(:focus-visible)) {\n        color: red;\n      }\n      \"#},\n      Browsers {\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo.foo:hover, .bar:focus-visible {\n        color: red;\n      }\n      \"#,\n      indoc! {r#\"\n      .foo.foo:hover {\n        color: red;\n      }\n\n      .bar:focus-visible {\n        color: red;\n      }\n      \"#},\n      Browsers {\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      a::unknown-a, a::unknown-b {\n        color: red;\n      }\n      \"#,\n      indoc! {r#\"\n      a::unknown-a {\n        color: red;\n      }\n\n      a::unknown-b {\n        color: red;\n      }\n      \"#},\n      Browsers {\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    nesting_test_with_targets(\n      r#\"\n      .foo {\n        padding-inline-start: 3px;\n\n        .bar {\n          padding-inline-start: 5px;\n        }\n      }\n      \"#,\n      indoc! {r#\"\n      .foo:not(:lang(ae, ar, arc, bcc, bqi, ckb, dv, fa, glk, he, ku, mzn, nqo, pnb, ps, sd, ug, ur, yi)) {\n        padding-left: 3px;\n      }\n\n      .foo:lang(ae, ar, arc, bcc, bqi, ckb, dv, fa, glk, he, ku, mzn, nqo, pnb, ps, sd, ug, ur, yi) {\n        padding-right: 3px;\n      }\n\n      .foo .bar:not(:lang(ae, ar, arc, bcc, bqi, ckb, dv, fa, glk, he, ku, mzn, nqo, pnb, ps, sd, ug, ur, yi)) {\n        padding-left: 5px;\n      }\n\n      .foo .bar:lang(ae, ar, arc, bcc, bqi, ckb, dv, fa, glk, he, ku, mzn, nqo, pnb, ps, sd, ug, ur, yi) {\n        padding-right: 5px;\n      }\n      \"#},\n      Browsers {\n        safari: Some(12 << 16),\n        ..Browsers::default()\n      }\n      .into(),\n    );\n\n    prefix_test(\n      r#\"\n      .foo::part(header), .foo::part(body) {\n        display: none\n      }\n      \"#,\n      indoc! {r#\"\n      .foo::part(header), .foo::part(body) {\n        display: none;\n      }\n      \"#},\n      Browsers {\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo :is(.bar) {\n        color: red;\n      }\n      \"#,\n      indoc! {r#\"\n        .foo .bar {\n          color: red;\n        }\n      \"#},\n      Browsers {\n        chrome: Some(87 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo :is(.bar), .bar :is(.baz) {\n        color: red;\n      }\n      \"#,\n      indoc! {r#\"\n        .foo .bar, .bar .baz {\n          color: red;\n        }\n      \"#},\n      Browsers {\n        chrome: Some(87 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo :is(.bar:focus-visible), .bar :is(.baz:hover) {\n        color: red;\n      }\n      \"#,\n      indoc! {r#\"\n        .bar .baz:hover {\n          color: red;\n        }\n\n        .foo .bar:focus-visible {\n          color: red;\n        }\n      \"#},\n      Browsers {\n        chrome: Some(85 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      *,\n      ::before,\n      ::after,\n      ::backdrop {\n        padding: 5px;\n      }\n      \"#,\n      indoc! {r#\"\n        *, :before, :after {\n          padding: 5px;\n        }\n\n        ::-webkit-backdrop {\n          padding: 5px;\n        }\n\n        ::backdrop {\n          padding: 5px;\n        }\n      \"#},\n      Browsers {\n        chrome: Some(33 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    test(\n      r#\"\n      .foo:-webkit-any(.bar, .baz):after {\n        color: red;\n      }\n\n      .foo:is(.bar, .baz):after {\n        color: red;\n      }\n      \"#,\n      indoc! {r#\"\n        .foo:-webkit-any(.bar, .baz):after {\n          color: red;\n        }\n\n        .foo:is(.bar, .baz):after {\n          color: red;\n        }\n      \"#},\n    );\n\n    prefix_test(\n      r#\"\n      .foo:-webkit-any(.bar, .baz):after {\n        color: red;\n      }\n\n      .foo:is(.bar, .baz):after {\n        color: red;\n      }\n      \"#,\n      indoc! {r#\"\n        .foo:is(.bar, .baz):after {\n          color: red;\n        }\n      \"#},\n      Browsers {\n        safari: Some(16 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo:-webkit-any(.bar):after {\n        color: red;\n      }\n\n      .foo:is(.bar, .baz):after {\n        color: red;\n      }\n      \"#,\n      indoc! {r#\"\n        .foo:-webkit-any(.bar):after {\n          color: red;\n        }\n\n        .foo:is(.bar, .baz):after {\n          color: red;\n        }\n      \"#},\n      Browsers {\n        safari: Some(16 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo:-webkit-any(.bar, .baz):after {\n        color: red;\n      }\n\n      .foo:is(.bar, .baz):after {\n        color: red;\n      }\n      \"#,\n      indoc! {r#\"\n        .foo:-webkit-any(.bar, .baz):after {\n          color: red;\n        }\n\n        .foo:is(.bar, .baz):after {\n          color: red;\n        }\n      \"#},\n      Browsers {\n        safari: Some(12 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo:-webkit-any(.bar, .baz):after {\n        color: red;\n      }\n\n      .foo:-moz-any(.bar, .baz):after {\n        color: red;\n      }\n      \"#,\n      indoc! {r#\"\n        .foo:-webkit-any(.bar, .baz):after {\n          color: red;\n        }\n\n        .foo:-moz-any(.bar, .baz):after {\n          color: red;\n        }\n      \"#},\n      Browsers {\n        safari: Some(12 << 16),\n        firefox: Some(67 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .a {\n        padding-inline: var(--foo);\n      }\n\n      .a:-webkit-any(.b, .c) {\n        padding-inline: var(--foo);\n      }\n      \"#,\n      indoc! {r#\"\n        .a {\n          padding-inline: var(--foo);\n        }\n\n        .a:-webkit-any(.b, .c) {\n          padding-inline: var(--foo);\n        }\n      \"#},\n      Browsers {\n        safari: Some(12 << 16),\n        ..Browsers::default()\n      },\n    );\n  }\n\n  #[test]\n  fn test_merge_media_rules() {\n    test(\n      r#\"\n      @media (hover) {\n        .foo {\n          color: red;\n        }\n      }\n      @media (hover) {\n        .foo {\n          background: #fff;\n        }\n\n        .baz {\n          color: #fff;\n        }\n      }\n      \"#,\n      indoc! {r#\"\n      @media (hover) {\n        .foo {\n          color: red;\n          background: #fff;\n        }\n\n        .baz {\n          color: #fff;\n        }\n      }\n    \"#},\n    );\n\n    test(\n      r#\"\n      @media (hover) {\n        .foo {\n          color: red;\n        }\n      }\n      @media (min-width: 250px) {\n        .foo {\n          background: #fff;\n        }\n\n        .baz {\n          color: #fff;\n        }\n      }\n      \"#,\n      indoc! {r#\"\n      @media (hover) {\n        .foo {\n          color: red;\n        }\n      }\n\n      @media (width >= 250px) {\n        .foo {\n          background: #fff;\n        }\n\n        .baz {\n          color: #fff;\n        }\n      }\n    \"#},\n    );\n  }\n\n  #[test]\n  fn test_merge_supports() {\n    test(\n      r#\"\n      @supports (flex: 1) {\n        .foo {\n          color: red;\n        }\n      }\n      @supports (flex: 1) {\n        .foo {\n          background: #fff;\n        }\n\n        .baz {\n          color: #fff;\n        }\n      }\n      \"#,\n      indoc! {r#\"\n      @supports (flex: 1) {\n        .foo {\n          color: red;\n          background: #fff;\n        }\n\n        .baz {\n          color: #fff;\n        }\n      }\n    \"#},\n    );\n\n    test(\n      r#\"\n      @supports (flex: 1) {\n        .foo {\n          color: red;\n        }\n      }\n      @supports (display: grid) {\n        .foo {\n          background: #fff;\n        }\n\n        .baz {\n          color: #fff;\n        }\n      }\n      \"#,\n      indoc! {r#\"\n      @supports (flex: 1) {\n        .foo {\n          color: red;\n        }\n      }\n\n      @supports (display: grid) {\n        .foo {\n          background: #fff;\n        }\n\n        .baz {\n          color: #fff;\n        }\n      }\n    \"#},\n    );\n  }\n\n  #[test]\n  fn test_opacity() {\n    minify_test(\".foo { opacity: 0 }\", \".foo{opacity:0}\");\n    minify_test(\".foo { opacity: 0% }\", \".foo{opacity:0}\");\n    minify_test(\".foo { opacity: 0.5 }\", \".foo{opacity:.5}\");\n    minify_test(\".foo { opacity: 50% }\", \".foo{opacity:.5}\");\n    minify_test(\".foo { opacity: 1 }\", \".foo{opacity:1}\");\n    minify_test(\".foo { opacity: 100% }\", \".foo{opacity:1}\");\n  }\n\n  #[test]\n  fn test_transitions() {\n    minify_test(\".foo { transition-duration: 500ms }\", \".foo{transition-duration:.5s}\");\n    minify_test(\".foo { transition-duration: .5s }\", \".foo{transition-duration:.5s}\");\n    minify_test(\".foo { transition-duration: 99ms }\", \".foo{transition-duration:99ms}\");\n    minify_test(\".foo { transition-duration: .099s }\", \".foo{transition-duration:99ms}\");\n    minify_test(\".foo { transition-duration: 2000ms }\", \".foo{transition-duration:2s}\");\n    minify_test(\".foo { transition-duration: 2s }\", \".foo{transition-duration:2s}\");\n    minify_test(\n      \".foo { transition-duration: calc(1s - 50ms) }\",\n      \".foo{transition-duration:.95s}\",\n    );\n    minify_test(\n      \".foo { transition-duration: calc(1s - 50ms + 2s) }\",\n      \".foo{transition-duration:2.95s}\",\n    );\n    minify_test(\n      \".foo { transition-duration: calc((1s - 50ms) * 2) }\",\n      \".foo{transition-duration:1.9s}\",\n    );\n    minify_test(\n      \".foo { transition-duration: calc(2 * (1s - 50ms)) }\",\n      \".foo{transition-duration:1.9s}\",\n    );\n    minify_test(\n      \".foo { transition-duration: calc((2s + 50ms) - (1s - 50ms)) }\",\n      \".foo{transition-duration:1.1s}\",\n    );\n    minify_test(\n      \".foo { transition-duration: 500ms, 50ms }\",\n      \".foo{transition-duration:.5s,50ms}\",\n    );\n    minify_test(\".foo { transition-delay: 500ms }\", \".foo{transition-delay:.5s}\");\n    minify_test(\n      \".foo { transition-property: background }\",\n      \".foo{transition-property:background}\",\n    );\n    minify_test(\n      \".foo { transition-property: background, opacity }\",\n      \".foo{transition-property:background,opacity}\",\n    );\n    minify_test(\n      \".foo { transition-timing-function: linear }\",\n      \".foo{transition-timing-function:linear}\",\n    );\n    minify_test(\n      \".foo { transition-timing-function: ease }\",\n      \".foo{transition-timing-function:ease}\",\n    );\n    minify_test(\n      \".foo { transition-timing-function: ease-in }\",\n      \".foo{transition-timing-function:ease-in}\",\n    );\n    minify_test(\n      \".foo { transition-timing-function: ease-out }\",\n      \".foo{transition-timing-function:ease-out}\",\n    );\n    minify_test(\n      \".foo { transition-timing-function: ease-in-out }\",\n      \".foo{transition-timing-function:ease-in-out}\",\n    );\n    minify_test(\n      \".foo { transition-timing-function: cubic-bezier(0.25, 0.1, 0.25, 1) }\",\n      \".foo{transition-timing-function:ease}\",\n    );\n    minify_test(\n      \".foo { transition-timing-function: cubic-bezier(0.42, 0, 1, 1) }\",\n      \".foo{transition-timing-function:ease-in}\",\n    );\n    minify_test(\n      \".foo { transition-timing-function: cubic-bezier(0, 0, 0.58, 1) }\",\n      \".foo{transition-timing-function:ease-out}\",\n    );\n    minify_test(\n      \".foo { transition-timing-function: cubic-bezier(0.42, 0, 0.58, 1) }\",\n      \".foo{transition-timing-function:ease-in-out}\",\n    );\n    minify_test(\n      \".foo { transition-timing-function: cubic-bezier(0.58, 0.2, 0.11, 1.2) }\",\n      \".foo{transition-timing-function:cubic-bezier(.58,.2,.11,1.2)}\",\n    );\n    minify_test(\n      \".foo { transition-timing-function: step-start }\",\n      \".foo{transition-timing-function:step-start}\",\n    );\n    minify_test(\n      \".foo { transition-timing-function: step-end }\",\n      \".foo{transition-timing-function:step-end}\",\n    );\n    minify_test(\n      \".foo { transition-timing-function: steps(1, start) }\",\n      \".foo{transition-timing-function:step-start}\",\n    );\n    minify_test(\n      \".foo { transition-timing-function: steps(1, jump-start) }\",\n      \".foo{transition-timing-function:step-start}\",\n    );\n    minify_test(\n      \".foo { transition-timing-function: steps(1, end) }\",\n      \".foo{transition-timing-function:step-end}\",\n    );\n    minify_test(\n      \".foo { transition-timing-function: steps(1, jump-end) }\",\n      \".foo{transition-timing-function:step-end}\",\n    );\n    minify_test(\n      \".foo { transition-timing-function: steps(5, jump-start) }\",\n      \".foo{transition-timing-function:steps(5,start)}\",\n    );\n    minify_test(\n      \".foo { transition-timing-function: steps(5, jump-end) }\",\n      \".foo{transition-timing-function:steps(5,end)}\",\n    );\n    minify_test(\n      \".foo { transition-timing-function: steps(5, jump-both) }\",\n      \".foo{transition-timing-function:steps(5,jump-both)}\",\n    );\n    minify_test(\n      \".foo { transition-timing-function: ease-in-out, cubic-bezier(0.42, 0, 1, 1) }\",\n      \".foo{transition-timing-function:ease-in-out,ease-in}\",\n    );\n    minify_test(\n      \".foo { transition-timing-function: cubic-bezier(0.42, 0, 1, 1), cubic-bezier(0.58, 0.2, 0.11, 1.2) }\",\n      \".foo{transition-timing-function:ease-in,cubic-bezier(.58,.2,.11,1.2)}\",\n    );\n    minify_test(\n      \".foo { transition-timing-function: step-start, steps(5, jump-start) }\",\n      \".foo{transition-timing-function:step-start,steps(5,start)}\",\n    );\n    minify_test(\".foo { transition: width 2s ease }\", \".foo{transition:width 2s}\");\n    minify_test(\n      \".foo { transition: width 2s ease, height 1000ms cubic-bezier(0.25, 0.1, 0.25, 1) }\",\n      \".foo{transition:width 2s,height 1s}\",\n    );\n    minify_test(\".foo { transition: width 2s 1s }\", \".foo{transition:width 2s 1s}\");\n    minify_test(\".foo { transition: width 2s ease 1s }\", \".foo{transition:width 2s 1s}\");\n    minify_test(\n      \".foo { transition: ease-in 1s width 4s }\",\n      \".foo{transition:width 1s ease-in 4s}\",\n    );\n    minify_test(\".foo { transition: opacity 0s .6s }\", \".foo{transition:opacity 0s .6s}\");\n    test(\n      r#\"\n      .foo {\n        transition-property: opacity;\n        transition-duration: 0.09s;\n        transition-timing-function: ease-in-out;\n        transition-delay: 500ms;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        transition: opacity 90ms ease-in-out .5s;\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      .foo {\n        transition: opacity 2s;\n        transition-timing-function: ease;\n        transition-delay: 500ms;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        transition: opacity 2s .5s;\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      .foo {\n        transition: opacity 500ms;\n        transition-timing-function: var(--ease);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        transition: opacity .5s;\n        transition-timing-function: var(--ease);\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      .foo {\n        transition-property: opacity;\n        transition-duration: 0.09s;\n        transition-timing-function: ease-in-out;\n        transition-delay: 500ms;\n        transition: color 2s;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        transition: color 2s;\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      .foo {\n        transition-property: opacity, color;\n        transition-duration: 2s, 4s;\n        transition-timing-function: ease-in-out, ease-in;\n        transition-delay: 500ms, 0s;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        transition: opacity 2s ease-in-out .5s, color 4s ease-in;\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      .foo {\n        transition-property: opacity, color;\n        transition-duration: 2s;\n        transition-timing-function: ease-in-out;\n        transition-delay: 500ms;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        transition: opacity 2s ease-in-out .5s, color 2s ease-in-out .5s;\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      .foo {\n        transition-property: opacity, color, width, height;\n        transition-duration: 2s, 4s;\n        transition-timing-function: ease;\n        transition-delay: 0s;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        transition: opacity 2s, color 4s, width 2s, height 4s;\n      }\n    \"#},\n    );\n\n    test(\n      r#\"\n      .foo {\n        -webkit-transition-property: opacity, color;\n        -webkit-transition-duration: 2s, 4s;\n        -webkit-transition-timing-function: ease-in-out, ease-in;\n        -webkit-transition-delay: 500ms, 0s;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-transition: opacity 2s ease-in-out .5s, color 4s ease-in;\n      }\n    \"#},\n    );\n\n    test(\n      r#\"\n      .foo {\n        -webkit-transition-property: opacity, color;\n        -webkit-transition-duration: 2s, 4s;\n        -webkit-transition-timing-function: ease-in-out, ease-in;\n        -webkit-transition-delay: 500ms, 0s;\n        -moz-transition-property: opacity, color;\n        -moz-transition-duration: 2s, 4s;\n        -moz-transition-timing-function: ease-in-out, ease-in;\n        -moz-transition-delay: 500ms, 0s;\n        transition-property: opacity, color;\n        transition-duration: 2s, 4s;\n        transition-timing-function: ease-in-out, ease-in;\n        transition-delay: 500ms, 0s;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-transition: opacity 2s ease-in-out .5s, color 4s ease-in;\n        -moz-transition: opacity 2s ease-in-out .5s, color 4s ease-in;\n        transition: opacity 2s ease-in-out .5s, color 4s ease-in;\n      }\n    \"#},\n    );\n\n    test(\n      r#\"\n      .foo {\n        -webkit-transition-property: opacity, color;\n        -moz-transition-property: opacity, color;\n        transition-property: opacity, color;\n        -webkit-transition-duration: 2s, 4s;\n        -moz-transition-duration: 2s, 4s;\n        transition-duration: 2s, 4s;\n        -webkit-transition-timing-function: ease-in-out, ease-in;\n        transition-timing-function: ease-in-out, ease-in;\n        -moz-transition-timing-function: ease-in-out, ease-in;\n        -webkit-transition-delay: 500ms, 0s;\n        -moz-transition-delay: 500ms, 0s;\n        transition-delay: 500ms, 0s;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-transition: opacity 2s ease-in-out .5s, color 4s ease-in;\n        -moz-transition: opacity 2s ease-in-out .5s, color 4s ease-in;\n        transition: opacity 2s ease-in-out .5s, color 4s ease-in;\n      }\n    \"#},\n    );\n\n    test(\n      r#\"\n      .foo {\n        -webkit-transition-property: opacity;\n        -moz-transition-property: color;\n        transition-property: opacity, color;\n        -webkit-transition-duration: 2s;\n        -moz-transition-duration: 4s;\n        transition-duration: 2s, 4s;\n        -webkit-transition-timing-function: ease-in-out;\n        -moz-transition-timing-function: ease-in-out;\n        transition-timing-function: ease-in-out, ease-in;\n        -webkit-transition-delay: 500ms;\n        -moz-transition-delay: 0s;\n        transition-delay: 500ms, 0s;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-transition-property: opacity;\n        -moz-transition-property: color;\n        transition-property: opacity, color;\n        -webkit-transition-duration: 2s;\n        -moz-transition-duration: 4s;\n        transition-duration: 2s, 4s;\n        -webkit-transition-timing-function: ease-in-out;\n        -moz-transition-timing-function: ease-in-out;\n        -webkit-transition-delay: .5s;\n        transition-timing-function: ease-in-out, ease-in;\n        -moz-transition-delay: 0s;\n        transition-delay: .5s, 0s;\n      }\n    \"#},\n    );\n\n    test(\n      r#\"\n      .foo {\n        -webkit-transition-property: opacity;\n        transition-property: opacity, color;\n        -moz-transition-property: color;\n        -webkit-transition-duration: 2s;\n        transition-duration: 2s, 4s;\n        -moz-transition-duration: 4s;\n        -webkit-transition-timing-function: ease-in-out;\n        transition-timing-function: ease-in-out, ease-in;\n        -moz-transition-timing-function: ease-in-out;\n        -webkit-transition-delay: 500ms;\n        transition-delay: 500ms, 0s;\n        -moz-transition-delay: 0s;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-transition-property: opacity;\n        transition-property: opacity, color;\n        -moz-transition-property: color;\n        -webkit-transition-duration: 2s;\n        transition-duration: 2s, 4s;\n        -moz-transition-duration: 4s;\n        -webkit-transition-timing-function: ease-in-out;\n        transition-timing-function: ease-in-out, ease-in;\n        -webkit-transition-delay: .5s;\n        -moz-transition-timing-function: ease-in-out;\n        transition-delay: .5s, 0s;\n        -moz-transition-delay: 0s;\n      }\n    \"#},\n    );\n\n    test(\n      r#\"\n      .foo {\n        transition: opacity 2s;\n        -webkit-transition-duration: 2s;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        transition: opacity 2s;\n        -webkit-transition-duration: 2s;\n      }\n    \"#},\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        transition-property: margin-inline-start;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo:not(:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        transition-property: margin-left;\n      }\n\n      .foo:not(:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        transition-property: margin-left;\n      }\n\n      .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        transition-property: margin-right;\n      }\n\n      .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        transition-property: margin-right;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        transition-property: margin-inline-start, padding-inline-start;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo:not(:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        transition-property: margin-left, padding-left;\n      }\n\n      .foo:not(:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        transition-property: margin-left, padding-left;\n      }\n\n      .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        transition-property: margin-right, padding-right;\n      }\n\n      .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        transition-property: margin-right, padding-right;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        transition-property: margin-inline-start, opacity, padding-inline-start, color;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo:not(:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        transition-property: margin-left, opacity, padding-left, color;\n      }\n\n      .foo:not(:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        transition-property: margin-left, opacity, padding-left, color;\n      }\n\n      .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        transition-property: margin-right, opacity, padding-right, color;\n      }\n\n      .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        transition-property: margin-right, opacity, padding-right, color;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        transition-property: margin-block;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        transition-property: margin-top, margin-bottom;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        transition: margin-inline-start 2s;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo:not(:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        transition: margin-left 2s;\n      }\n\n      .foo:not(:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        transition: margin-left 2s;\n      }\n\n      .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        transition: margin-right 2s;\n      }\n\n      .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        transition: margin-right 2s;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        transition: margin-inline-start 2s, padding-inline-start 2s;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo:not(:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        transition: margin-left 2s, padding-left 2s;\n      }\n\n      .foo:not(:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        transition: margin-left 2s, padding-left 2s;\n      }\n\n      .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        transition: margin-right 2s, padding-right 2s;\n      }\n\n      .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        transition: margin-right 2s, padding-right 2s;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        transition: margin-block-start 2s;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        transition: margin-top 2s;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        transition: transform;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-transition: -webkit-transform, transform;\n        transition: -webkit-transform, transform;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(6 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        transition: border-start-start-radius;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo:not(:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        -webkit-transition: -webkit-border-top-left-radius, border-top-left-radius;\n        transition: -webkit-border-top-left-radius, border-top-left-radius;\n      }\n\n      .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        -webkit-transition: -webkit-border-top-right-radius, border-top-right-radius;\n        transition: -webkit-border-top-right-radius, border-top-right-radius;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(4 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        transition: border-start-start-radius;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo:not(:lang(ae, ar, arc, bcc, bqi, ckb, dv, fa, glk, he, ku, mzn, nqo, pnb, ps, sd, ug, ur, yi)) {\n        transition: border-top-left-radius;\n      }\n\n      .foo:lang(ae, ar, arc, bcc, bqi, ckb, dv, fa, glk, he, ku, mzn, nqo, pnb, ps, sd, ug, ur, yi) {\n        transition: border-top-right-radius;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(12 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        -webkit-transition: background 200ms;\n        -moz-transition: background 200ms;\n        transition: background 230ms;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-transition: background .2s;\n        -moz-transition: background .2s;\n        transition: background .23s;\n      }\n    \"#},\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        -webkit-transition: background 200ms;\n        -moz-transition: background 200ms;\n        transition: background 230ms;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-transition: background .2s;\n        -moz-transition: background .2s;\n        transition: background .23s;\n      }\n    \"#},\n      Browsers {\n        chrome: Some(95 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n       .foo {\n         transition-property: -webkit-backdrop-filter, backdrop-filter;\n       }\n       .bar {\n         transition-property: backdrop-filter;\n       }\n       .baz {\n         transition-property: -webkit-backdrop-filter;\n       }\n     \"#,\n      indoc! {r#\"\n       .foo, .bar {\n         transition-property: -webkit-backdrop-filter, backdrop-filter;\n       }\n\n       .baz {\n         transition-property: -webkit-backdrop-filter;\n       }\n     \"#\n      },\n      Browsers {\n        safari: Some(15 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n       .foo {\n         transition-property: -webkit-border-radius, -webkit-border-radius, -moz-border-radius;\n       }\n     \"#,\n      indoc! {r#\"\n       .foo {\n         transition-property: -webkit-border-radius, -moz-border-radius;\n       }\n     \"#\n      },\n      Browsers {\n        safari: Some(15 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n       .foo {\n         transition: -webkit-backdrop-filter, backdrop-filter;\n       }\n       .bar {\n         transition: backdrop-filter;\n       }\n       .baz {\n         transition: -webkit-backdrop-filter;\n       }\n     \"#,\n      indoc! {r#\"\n       .foo, .bar {\n         transition: -webkit-backdrop-filter, backdrop-filter;\n       }\n\n       .baz {\n         transition: -webkit-backdrop-filter;\n       }\n     \"#\n      },\n      Browsers {\n        safari: Some(15 << 16),\n        ..Browsers::default()\n      },\n    );\n  }\n\n  #[test]\n  fn test_animation() {\n    minify_test(\".foo { animation-name: test }\", \".foo{animation-name:test}\");\n    minify_test(\".foo { animation-name: \\\"test\\\" }\", \".foo{animation-name:test}\");\n    minify_test(\".foo { animation-name: foo, bar }\", \".foo{animation-name:foo,bar}\");\n    minify_test(\".foo { animation-name: \\\"none\\\" }\", \".foo{animation-name:\\\"none\\\"}\");\n    minify_test(\n      \".foo { animation-name: \\\"none\\\", foo }\",\n      \".foo{animation-name:\\\"none\\\",foo}\",\n    );\n    let name = crate::properties::animation::AnimationName::parse_string(\"default\");\n    assert!(matches!(name, Err(..)));\n\n    minify_test(\".foo { animation-name: none }\", \".foo{animation-name:none}\");\n    minify_test(\".foo { animation-name: none, none }\", \".foo{animation-name:none,none}\");\n\n    // Test CSS-wide keywords\n    minify_test(\".foo { animation-name: unset }\", \".foo{animation-name:unset}\");\n    minify_test(\".foo { animation-name: \\\"unset\\\" }\", \".foo{animation-name:\\\"unset\\\"}\");\n    minify_test(\".foo { animation-name: \\\"revert\\\" }\", \".foo{animation-name:\\\"revert\\\"}\");\n    minify_test(\n      \".foo { animation-name: \\\"unset\\\", \\\"revert\\\"}\",\n      \".foo{animation-name:\\\"unset\\\",\\\"revert\\\"}\",\n    );\n    minify_test(\n      \".foo { animation-name: foo, \\\"revert\\\"}\",\n      \".foo{animation-name:foo,\\\"revert\\\"}\",\n    );\n    minify_test(\n      \".foo { animation-name: \\\"string\\\", \\\"revert\\\"}\",\n      \".foo{animation-name:string,\\\"revert\\\"}\",\n    );\n    minify_test(\n      \".foo { animation-name: \\\"string\\\", foo, \\\"revert\\\"}\",\n      \".foo{animation-name:string,foo,\\\"revert\\\"}\",\n    );\n    minify_test(\n      \".foo { animation-name: \\\"default\\\" }\",\n      \".foo{animation-name:\\\"default\\\"}\",\n    );\n    minify_test(\".foo { animation-duration: 100ms }\", \".foo{animation-duration:.1s}\");\n    minify_test(\n      \".foo { animation-duration: 100ms, 2000ms }\",\n      \".foo{animation-duration:.1s,2s}\",\n    );\n    minify_test(\n      \".foo { animation-timing-function: ease }\",\n      \".foo{animation-timing-function:ease}\",\n    );\n    minify_test(\n      \".foo { animation-timing-function: cubic-bezier(0.42, 0, 1, 1) }\",\n      \".foo{animation-timing-function:ease-in}\",\n    );\n    minify_test(\n      \".foo { animation-timing-function: ease, cubic-bezier(0.42, 0, 1, 1) }\",\n      \".foo{animation-timing-function:ease,ease-in}\",\n    );\n    minify_test(\n      \".foo { animation-iteration-count: 5 }\",\n      \".foo{animation-iteration-count:5}\",\n    );\n    minify_test(\n      \".foo { animation-iteration-count: 2.5 }\",\n      \".foo{animation-iteration-count:2.5}\",\n    );\n    minify_test(\n      \".foo { animation-iteration-count: 2.0 }\",\n      \".foo{animation-iteration-count:2}\",\n    );\n    minify_test(\n      \".foo { animation-iteration-count: infinite }\",\n      \".foo{animation-iteration-count:infinite}\",\n    );\n    minify_test(\n      \".foo { animation-iteration-count: 1, infinite }\",\n      \".foo{animation-iteration-count:1,infinite}\",\n    );\n    minify_test(\n      \".foo { animation-direction: reverse }\",\n      \".foo{animation-direction:reverse}\",\n    );\n    minify_test(\n      \".foo { animation-direction: alternate, reverse }\",\n      \".foo{animation-direction:alternate,reverse}\",\n    );\n    minify_test(\n      \".foo { animation-play-state: paused }\",\n      \".foo{animation-play-state:paused}\",\n    );\n    minify_test(\n      \".foo { animation-play-state: running, paused }\",\n      \".foo{animation-play-state:running,paused}\",\n    );\n    minify_test(\".foo { animation-delay: 100ms }\", \".foo{animation-delay:.1s}\");\n    minify_test(\n      \".foo { animation-delay: 100ms, 2000ms }\",\n      \".foo{animation-delay:.1s,2s}\",\n    );\n    minify_test(\n      \".foo { animation-fill-mode: forwards }\",\n      \".foo{animation-fill-mode:forwards}\",\n    );\n    minify_test(\n      \".foo { animation-fill-mode: Backwards,forwards }\",\n      \".foo{animation-fill-mode:backwards,forwards}\",\n    );\n    minify_test(\".foo { animation: none }\", \".foo{animation:none}\");\n    minify_test(\".foo { animation: \\\"none\\\" }\", \".foo{animation:\\\"none\\\"}\");\n    minify_test(\".foo { animation: \\\"None\\\" }\", \".foo{animation:\\\"None\\\"}\");\n    minify_test(\".foo { animation: \\\"none\\\", none }\", \".foo{animation:\\\"none\\\",none}\");\n    minify_test(\".foo { animation: none, none }\", \".foo{animation:none,none}\");\n    minify_test(\".foo { animation: \\\"none\\\" none }\", \".foo{animation:\\\"none\\\"}\");\n    minify_test(\".foo { animation: none none }\", \".foo{animation:none}\");\n\n    // Test animation-name + animation-fill-mode\n    minify_test(\n      \".foo { animation: 2s both \\\"none\\\"}\",\n      \".foo{animation:2s both \\\"none\\\"}\",\n    );\n    minify_test(\n      \".foo { animation: both \\\"none\\\" 2s}\",\n      \".foo{animation:2s both \\\"none\\\"}\",\n    );\n    minify_test(\".foo { animation: \\\"none\\\" 2s none}\", \".foo{animation:2s \\\"none\\\"}\");\n    minify_test(\".foo { animation: none \\\"none\\\" 2s}\", \".foo{animation:2s \\\"none\\\"}\");\n    minify_test(\n      \".foo { animation: none, \\\"none\\\" 2s forwards}\",\n      \".foo{animation:none,2s forwards \\\"none\\\"}\",\n    );\n\n    minify_test(\".foo { animation: \\\"unset\\\" }\", \".foo{animation:\\\"unset\\\"}\");\n    minify_test(\".foo { animation: \\\"string\\\" .5s }\", \".foo{animation:.5s string}\");\n    minify_test(\".foo { animation: \\\"unset\\\" .5s }\", \".foo{animation:.5s \\\"unset\\\"}\");\n    minify_test(\n      \".foo { animation: none, \\\"unset\\\" .5s}\",\n      \".foo{animation:none,.5s \\\"unset\\\"}\",\n    );\n    minify_test(\n      \".foo { animation: \\\"unset\\\" 0s 3s infinite, none }\",\n      \".foo{animation:0s 3s infinite \\\"unset\\\",none}\",\n    );\n\n    minify_test(\".foo { animation: \\\"infinite\\\" 2s 1 }\", \".foo{animation:2s 1 infinite}\");\n    minify_test(\".foo { animation: \\\"paused\\\" 2s }\", \".foo{animation:2s running paused}\");\n    minify_test(\n      \".foo { animation: \\\"forwards\\\" 2s }\",\n      \".foo{animation:2s none forwards}\",\n    );\n    minify_test(\n      \".foo { animation: \\\"reverse\\\" 2s }\",\n      \".foo{animation:2s normal reverse}\",\n    );\n    minify_test(\n      \".foo { animation: \\\"reverse\\\" 2s alternate }\",\n      \".foo{animation:2s alternate reverse}\",\n    );\n\n    minify_test(\n      \".foo { animation: 3s ease-in 1s infinite reverse both running slidein }\",\n      \".foo{animation:3s ease-in 1s infinite reverse both slidein}\",\n    );\n    minify_test(\n      \".foo { animation: 3s slidein paused ease 1s 1 reverse both }\",\n      \".foo{animation:3s 1s reverse both paused slidein}\",\n    );\n    minify_test(\".foo { animation: 3s ease ease }\", \".foo{animation:3s ease ease}\");\n    minify_test(\n      \".foo { animation: 3s cubic-bezier(0.25, 0.1, 0.25, 1) foo }\",\n      \".foo{animation:3s foo}\",\n    );\n    minify_test(\n      \".foo { animation: foo 0s 3s infinite }\",\n      \".foo{animation:0s 3s infinite foo}\",\n    );\n    minify_test(\".foo { animation: foo 3s --test }\", \".foo{animation:3s foo --test}\");\n    minify_test(\".foo { animation: foo 3s scroll() }\", \".foo{animation:3s foo scroll()}\");\n    minify_test(\n      \".foo { animation: foo 3s scroll(block) }\",\n      \".foo{animation:3s foo scroll()}\",\n    );\n    minify_test(\n      \".foo { animation: foo 3s scroll(root inline) }\",\n      \".foo{animation:3s foo scroll(root inline)}\",\n    );\n    minify_test(\n      \".foo { animation: foo 3s scroll(inline root) }\",\n      \".foo{animation:3s foo scroll(root inline)}\",\n    );\n    minify_test(\n      \".foo { animation: foo 3s scroll(inline nearest) }\",\n      \".foo{animation:3s foo scroll(inline)}\",\n    );\n    minify_test(\n      \".foo { animation: foo 3s view(block) }\",\n      \".foo{animation:3s foo view()}\",\n    );\n    minify_test(\n      \".foo { animation: foo 3s view(inline) }\",\n      \".foo{animation:3s foo view(inline)}\",\n    );\n    minify_test(\n      \".foo { animation: foo 3s view(inline 10px 10px) }\",\n      \".foo{animation:3s foo view(inline 10px)}\",\n    );\n    minify_test(\n      \".foo { animation: foo 3s view(inline 10px 12px) }\",\n      \".foo{animation:3s foo view(inline 10px 12px)}\",\n    );\n    minify_test(\n      \".foo { animation: foo 3s view(inline auto auto) }\",\n      \".foo{animation:3s foo view(inline)}\",\n    );\n    minify_test(\".foo { animation: foo 3s auto }\", \".foo{animation:3s foo}\");\n    minify_test(\".foo { animation-composition: add }\", \".foo{animation-composition:add}\");\n    test(\n      r#\"\n      .foo {\n        animation-name: foo;\n        animation-duration: 0.09s;\n        animation-timing-function: ease-in-out;\n        animation-iteration-count: 2;\n        animation-direction: alternate;\n        animation-play-state: running;\n        animation-delay: 100ms;\n        animation-fill-mode: forwards;\n        animation-timeline: auto;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        animation: 90ms ease-in-out .1s 2 alternate forwards foo;\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      .foo {\n        animation-name: foo, bar;\n        animation-duration: 0.09s, 200ms;\n        animation-timing-function: ease-in-out, ease;\n        animation-iteration-count: 2, 1;\n        animation-direction: alternate, normal;\n        animation-play-state: running, paused;\n        animation-delay: 100ms, 0s;\n        animation-fill-mode: forwards, none;\n        animation-timeline: auto, auto;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        animation: 90ms ease-in-out .1s 2 alternate forwards foo, .2s paused bar;\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      .foo {\n        animation: bar 200ms;\n        animation-timing-function: ease-in-out;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        animation: .2s ease-in-out bar;\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      .foo {\n        animation: bar 200ms;\n        animation-timing-function: var(--ease);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        animation: .2s bar;\n        animation-timing-function: var(--ease);\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      .foo {\n        animation-name: foo, bar;\n        animation-duration: 0.09s;\n        animation-timing-function: ease-in-out;\n        animation-iteration-count: 2;\n        animation-direction: alternate;\n        animation-play-state: running;\n        animation-delay: 100ms;\n        animation-fill-mode: forwards;\n        animation-timeline: auto;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        animation-name: foo, bar;\n        animation-duration: 90ms;\n        animation-timing-function: ease-in-out;\n        animation-iteration-count: 2;\n        animation-direction: alternate;\n        animation-play-state: running;\n        animation-delay: .1s;\n        animation-fill-mode: forwards;\n        animation-timeline: auto;\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      .foo {\n        animation-name: foo;\n        animation-duration: 0.09s;\n        animation-timing-function: ease-in-out;\n        animation-iteration-count: 2;\n        animation-direction: alternate;\n        animation-play-state: running;\n        animation-delay: 100ms;\n        animation-fill-mode: forwards;\n        animation-timeline: scroll();\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        animation: 90ms ease-in-out .1s 2 alternate forwards foo scroll();\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      .foo {\n        animation-name: foo;\n        animation-duration: 0.09s;\n        animation-timing-function: ease-in-out;\n        animation-iteration-count: 2;\n        animation-direction: alternate;\n        animation-play-state: running;\n        animation-delay: 100ms;\n        animation-fill-mode: forwards;\n        animation-timeline: scroll(), view();\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        animation-name: foo;\n        animation-duration: 90ms;\n        animation-timing-function: ease-in-out;\n        animation-iteration-count: 2;\n        animation-direction: alternate;\n        animation-play-state: running;\n        animation-delay: .1s;\n        animation-fill-mode: forwards;\n        animation-timeline: scroll(), view();\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      .foo {\n        -webkit-animation-name: foo;\n        -webkit-animation-duration: 0.09s;\n        -webkit-animation-timing-function: ease-in-out;\n        -webkit-animation-iteration-count: 2;\n        -webkit-animation-direction: alternate;\n        -webkit-animation-play-state: running;\n        -webkit-animation-delay: 100ms;\n        -webkit-animation-fill-mode: forwards;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-animation: 90ms ease-in-out .1s 2 alternate forwards foo;\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      .foo {\n        -moz-animation: bar 200ms;\n        -moz-animation-timing-function: ease-in-out;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -moz-animation: .2s ease-in-out bar;\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      .foo {\n        -webkit-animation: bar 200ms;\n        -webkit-animation-timing-function: ease-in-out;\n        -moz-animation: bar 200ms;\n        -moz-animation-timing-function: ease-in-out;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-animation: .2s ease-in-out bar;\n        -moz-animation: .2s ease-in-out bar;\n      }\n    \"#},\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        animation: .2s ease-in-out bar;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-animation: .2s ease-in-out bar;\n        -moz-animation: .2s ease-in-out bar;\n        animation: .2s ease-in-out bar;\n      }\n    \"#},\n      Browsers {\n        firefox: Some(6 << 16),\n        safari: Some(6 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        -webkit-animation: .2s ease-in-out bar;\n        -moz-animation: .2s ease-in-out bar;\n        animation: .2s ease-in-out bar;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        animation: .2s ease-in-out bar;\n      }\n    \"#},\n      Browsers {\n        firefox: Some(20 << 16),\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        animation: 200ms var(--ease) bar;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-animation: .2s var(--ease) bar;\n        -moz-animation: .2s var(--ease) bar;\n        animation: .2s var(--ease) bar;\n      }\n    \"#},\n      Browsers {\n        firefox: Some(6 << 16),\n        safari: Some(6 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        animation: .2s ease-in-out bar scroll();\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        animation: .2s ease-in-out bar;\n        animation-timeline: scroll();\n      }\n    \"#},\n      Browsers {\n        safari: Some(16 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        animation: .2s ease-in-out bar scroll();\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        animation: .2s ease-in-out bar scroll();\n      }\n    \"#},\n      Browsers {\n        chrome: Some(120 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        animation: .2s ease-in-out bar scroll();\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-animation: .2s ease-in-out bar;\n        animation: .2s ease-in-out bar;\n        animation-timeline: scroll();\n      }\n    \"#},\n      Browsers {\n        safari: Some(6 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    minify_test(\n      \".foo { animation-range-start: entry 10% }\",\n      \".foo{animation-range-start:entry 10%}\",\n    );\n    minify_test(\n      \".foo { animation-range-start: entry 0% }\",\n      \".foo{animation-range-start:entry}\",\n    );\n    minify_test(\n      \".foo { animation-range-start: entry }\",\n      \".foo{animation-range-start:entry}\",\n    );\n    minify_test(\".foo { animation-range-start: 50% }\", \".foo{animation-range-start:50%}\");\n    minify_test(\n      \".foo { animation-range-end: exit 10% }\",\n      \".foo{animation-range-end:exit 10%}\",\n    );\n    minify_test(\n      \".foo { animation-range-end: exit 100% }\",\n      \".foo{animation-range-end:exit}\",\n    );\n    minify_test(\".foo { animation-range-end: exit }\", \".foo{animation-range-end:exit}\");\n    minify_test(\".foo { animation-range-end: 50% }\", \".foo{animation-range-end:50%}\");\n    minify_test(\n      \".foo { animation-range: entry 10% exit 90% }\",\n      \".foo{animation-range:entry 10% exit 90%}\",\n    );\n    minify_test(\n      \".foo { animation-range: entry 0% exit 100% }\",\n      \".foo{animation-range:entry exit}\",\n    );\n    minify_test(\".foo { animation-range: entry }\", \".foo{animation-range:entry}\");\n    minify_test(\n      \".foo { animation-range: entry 0% entry 100% }\",\n      \".foo{animation-range:entry}\",\n    );\n    minify_test(\".foo { animation-range: 50% normal }\", \".foo{animation-range:50%}\");\n    minify_test(\n      \".foo { animation-range: normal normal }\",\n      \".foo{animation-range:normal}\",\n    );\n    test(\n      r#\"\n      .foo {\n        animation-range-start: entry 10%;\n        animation-range-end: exit 90%;\n      }\n      \"#,\n      indoc! {r#\"\n      .foo {\n        animation-range: entry 10% exit 90%;\n      }\n      \"#},\n    );\n    test(\n      r#\"\n      .foo {\n        animation-range-start: entry 0%;\n        animation-range-end: entry 100%;\n      }\n      \"#,\n      indoc! {r#\"\n      .foo {\n        animation-range: entry;\n      }\n      \"#},\n    );\n    test(\n      r#\"\n      .foo {\n        animation-range-start: entry 0%;\n        animation-range-end: exit 100%;\n      }\n      \"#,\n      indoc! {r#\"\n      .foo {\n        animation-range: entry exit;\n      }\n      \"#},\n    );\n    test(\n      r#\"\n      .foo {\n        animation-range-start: 10%;\n        animation-range-end: normal;\n      }\n      \"#,\n      indoc! {r#\"\n      .foo {\n        animation-range: 10%;\n      }\n      \"#},\n    );\n    test(\n      r#\"\n      .foo {\n        animation-range-start: 10%;\n        animation-range-end: 90%;\n      }\n      \"#,\n      indoc! {r#\"\n      .foo {\n        animation-range: 10% 90%;\n      }\n      \"#},\n    );\n    test(\n      r#\"\n      .foo {\n        animation-range-start: entry 10%;\n        animation-range-end: exit 100%;\n      }\n      \"#,\n      indoc! {r#\"\n      .foo {\n        animation-range: entry 10% exit;\n      }\n      \"#},\n    );\n    test(\n      r#\"\n      .foo {\n        animation-range-start: 10%;\n        animation-range-end: exit 90%;\n      }\n      \"#,\n      indoc! {r#\"\n      .foo {\n        animation-range: 10% exit 90%;\n      }\n      \"#},\n    );\n    test(\n      r#\"\n      .foo {\n        animation-range-start: entry 10%;\n        animation-range-end: 90%;\n      }\n      \"#,\n      indoc! {r#\"\n      .foo {\n        animation-range: entry 10% 90%;\n      }\n      \"#},\n    );\n    test(\n      r#\"\n      .foo {\n        animation-range: entry;\n        animation-range-end: 90%;\n      }\n      \"#,\n      indoc! {r#\"\n      .foo {\n        animation-range: entry 90%;\n      }\n      \"#},\n    );\n    test(\n      r#\"\n      .foo {\n        animation-range: entry;\n        animation-range-end: var(--end);\n      }\n      \"#,\n      indoc! {r#\"\n      .foo {\n        animation-range: entry;\n        animation-range-end: var(--end);\n      }\n      \"#},\n    );\n    test(\n      r#\"\n      .foo {\n        animation-range-start: entry 10%, entry 50%;\n        animation-range-end: exit 90%;\n      }\n      \"#,\n      indoc! {r#\"\n      .foo {\n        animation-range-start: entry 10%, entry 50%;\n        animation-range-end: exit 90%;\n      }\n      \"#},\n    );\n    test(\n      r#\"\n      .foo {\n        animation-range-start: entry 10%, entry 50%;\n        animation-range-end: exit 90%, exit 100%;\n      }\n      \"#,\n      indoc! {r#\"\n      .foo {\n        animation-range: entry 10% exit 90%, entry 50% exit;\n      }\n      \"#},\n    );\n    test(\n      r#\"\n      .foo {\n        animation-range: entry;\n        animation-range-end: 90%;\n        animation: spin 100ms;\n      }\n      \"#,\n      indoc! {r#\"\n      .foo {\n        animation: .1s spin;\n      }\n      \"#},\n    );\n    test(\n      r#\"\n      .foo {\n        animation: spin 100ms;\n        animation-range: entry;\n        animation-range-end: 90%;\n      }\n      \"#,\n      indoc! {r#\"\n      .foo {\n        animation: .1s spin;\n        animation-range: entry 90%;\n      }\n      \"#},\n    );\n    test(\n      r#\"\n      .foo {\n        animation-range: entry;\n        animation-range-end: 90%;\n        animation: var(--animation) 100ms;\n      }\n      \"#,\n      indoc! {r#\"\n      .foo {\n        animation: var(--animation) .1s;\n      }\n      \"#},\n    );\n  }\n\n  #[test]\n  fn test_transform() {\n    test(\n      \".foo { transform: perspective(500px)translate3d(10px, 0, 20px)rotateY(30deg) }\",\n      indoc! {r#\"\n      .foo {\n        transform: perspective(500px) translate3d(10px, 0, 20px) rotateY(30deg);\n      }\n      \"#},\n    );\n    test(\n      \".foo { transform: translate3d(12px,50%,3em)scale(2,.5) }\",\n      indoc! {r#\"\n      .foo {\n        transform: translate3d(12px, 50%, 3em) scale(2, .5);\n      }\n      \"#},\n    );\n    test(\n      \".foo { transform:matrix(1,2,-1,1,80,80) }\",\n      indoc! {r#\"\n      .foo {\n        transform: matrix(1, 2, -1, 1, 80, 80);\n      }\n      \"#},\n    );\n\n    minify_test(\n      \".foo { transform: scale(  0.5 )translateX(10px ) }\",\n      \".foo{transform:scale(.5)translate(10px)}\",\n    );\n    minify_test(\n      \".foo { transform: translate(2px, 3px)\",\n      \".foo{transform:translate(2px,3px)}\",\n    );\n    minify_test(\n      \".foo { transform: translate(2px, 0px)\",\n      \".foo{transform:translate(2px)}\",\n    );\n    minify_test(\n      \".foo { transform: translate(0px, 2px)\",\n      \".foo{transform:translateY(2px)}\",\n    );\n    minify_test(\".foo { transform: translateX(2px)\", \".foo{transform:translate(2px)}\");\n    minify_test(\".foo { transform: translateY(2px)\", \".foo{transform:translateY(2px)}\");\n    minify_test(\".foo { transform: translateZ(2px)\", \".foo{transform:translateZ(2px)}\");\n    minify_test(\n      \".foo { transform: translate3d(2px, 3px, 4px)\",\n      \".foo{transform:translate3d(2px,3px,4px)}\",\n    );\n    minify_test(\n      \".foo { transform: translate3d(10%, 20%, 4px)\",\n      \".foo{transform:translate3d(10%,20%,4px)}\",\n    );\n    minify_test(\n      \".foo { transform: translate3d(2px, 0px, 0px)\",\n      \".foo{transform:translate(2px)}\",\n    );\n    minify_test(\n      \".foo { transform: translate3d(0px, 2px, 0px)\",\n      \".foo{transform:translateY(2px)}\",\n    );\n    minify_test(\n      \".foo { transform: translate3d(0px, 0px, 2px)\",\n      \".foo{transform:translateZ(2px)}\",\n    );\n    minify_test(\n      \".foo { transform: translate3d(2px, 3px, 0px)\",\n      \".foo{transform:translate(2px,3px)}\",\n    );\n    minify_test(\".foo { transform: scale(2, 3)\", \".foo{transform:scale(2,3)}\");\n    minify_test(\".foo { transform: scale(10%, 20%)\", \".foo{transform:scale(.1,.2)}\");\n    minify_test(\".foo { transform: scale(2, 2)\", \".foo{transform:scale(2)}\");\n    minify_test(\".foo { transform: scale(2, 1)\", \".foo{transform:scaleX(2)}\");\n    minify_test(\".foo { transform: scale(1, 2)\", \".foo{transform:scaleY(2)}\");\n    minify_test(\".foo { transform: scaleX(2)\", \".foo{transform:scaleX(2)}\");\n    minify_test(\".foo { transform: scaleY(2)\", \".foo{transform:scaleY(2)}\");\n    minify_test(\".foo { transform: scaleZ(2)\", \".foo{transform:scaleZ(2)}\");\n    minify_test(\".foo { transform: scale3d(2, 3, 4)\", \".foo{transform:scale3d(2,3,4)}\");\n    minify_test(\".foo { transform: scale3d(2, 1, 1)\", \".foo{transform:scaleX(2)}\");\n    minify_test(\".foo { transform: scale3d(1, 2, 1)\", \".foo{transform:scaleY(2)}\");\n    minify_test(\".foo { transform: scale3d(1, 1, 2)\", \".foo{transform:scaleZ(2)}\");\n    minify_test(\".foo { transform: scale3d(2, 2, 1)\", \".foo{transform:scale(2)}\");\n\n    // transform: scale(), Convert <percentage> to <number>\n    test(\n      \".foo { transform: scale3d(50%, 1, 200%) }\",\n      indoc! {r#\"\n      .foo {\n        transform: scale3d(.5, 1, 2);\n      }\n      \"#},\n    );\n    minify_test(\".foo { transform: scale(1%) }\", \".foo{transform:scale(.01)}\");\n    minify_test(\".foo { transform: scale(0%) }\", \".foo{transform:scale(0)}\");\n    minify_test(\".foo { transform: scale(0.0%) }\", \".foo{transform:scale(0)}\");\n    minify_test(\".foo { transform: scale(-0%) }\", \".foo{transform:scale(0)}\");\n    minify_test(\".foo { transform: scale(-0) }\", \".foo{transform:scale(0)}\");\n    minify_test(\".foo { transform: scale(-0.0) }\", \".foo{transform:scale(0)}\");\n    minify_test(\".foo { transform: scale(100%) }\", \".foo{transform:scale(1)}\");\n    minify_test(\".foo { transform: scale(-100%) }\", \".foo{transform:scale(-1)}\");\n    minify_test(\".foo { transform: scale(68%) }\", \".foo{transform:scale(.68)}\");\n    minify_test(\".foo { transform: scale(5.96%) }\", \".foo{transform:scale(.0596)}\");\n    // Match WPT coverage for repeated and multi-value percentages.\n    minify_test(\".foo { transform: scale(100%, 100%) }\", \".foo{transform:scale(1)}\");\n    minify_test(\".foo { transform: scale3d(100%, 100%, 1) }\", \".foo{transform:scale(1)}\");\n    minify_test(\".foo { transform: scale(-100%, -100%) }\", \".foo{transform:scale(-1)}\");\n    minify_test(\".foo { transform: scale3d(-100%, -100%, 1) }\", \".foo{transform:scale(-1)}\");\n    minify_test(\".foo { transform: scale(100%, 200%) }\", \".foo{transform:scaleY(2)}\");\n    minify_test(\".foo { transform: scale3d(100%, 200%, 1) }\", \".foo{transform:scaleY(2)}\");\n    minify_test(\".foo { transform: scale3d(100%, 100%, 0%) }\", \".foo{transform:scaleZ(0)}\");\n    minify_test(\".foo { transform: scale3d(100%, 100%, 100%) }\", \".foo{transform:scale(1)}\");\n    minify_test(\".foo { transform: scale3d(-0%, -0%, -0%) }\", \".foo{transform:scale3d(0,0,0)}\");\n    // Additional edge cases: mixed inputs and computed percentages.\n    minify_test(\".foo { transform: scale(2, 100%) }\", \".foo{transform:scaleX(2)}\");\n    minify_test(\".foo { transform: scale(2, -50%) }\", \".foo{transform:scale(2,-.5)}\");\n    minify_test(\".foo { transform: scale(-90%, -1) }\", \".foo{transform:scale(-.9,-1)}\");\n    minify_test(\".foo { transform: scale(calc(10% + 20%)) }\", \".foo{transform:scale(.3)}\");\n    minify_test(\".foo { transform: scale(calc(150% - 50%), 200%) }\", \".foo{transform:scaleY(2)}\");\n    minify_test(\".foo { transform: scale(200%, calc(50% - 80%)) }\", \".foo{transform:scale(2,-.3)}\");\n    // TODO: For infinite decimals, please do not attempt to resolve calc\n    // Expected: calc(1 / 3)\n    // https://github.com/parcel-bundler/lightningcss/issues/12\n    minify_test(\".foo { transform: scale(calc(100% / 3)) }\", \".foo{transform:scale(.333333)}\");\n    // Transform::ScaleX/Y/Z\n    minify_test(\".foo { transform: scaleX(10%) }\", \".foo{transform:scaleX(.1)}\");\n    minify_test(\".foo { transform: scaleY(20%) }\", \".foo{transform:scaleY(.2)}\");\n    minify_test(\".foo { transform: scaleZ(30%) }\", \".foo{transform:scaleZ(.3)}\");\n    minify_test(\".foo { transform: scaleX(0%) }\", \".foo{transform:scaleX(0)}\");\n    minify_test(\".foo { transform: scaleX(-0%) }\", \".foo{transform:scaleX(0)}\");\n    minify_test(\".foo { transform: scaleX(calc(10% + 20%)) }\", \".foo{transform:scaleX(.3)}\");\n    minify_test(\".foo { transform: scaleX(calc(180% - 20%)) }\", \".foo{transform:scaleX(1.6)}\");\n    minify_test(\".foo { transform: scaleX(calc(50% - 80%)) }\", \".foo{transform:scaleX(-.3)}\");\n\n    minify_test(\".foo { transform: rotate(20deg)\", \".foo{transform:rotate(20deg)}\");\n    minify_test(\".foo { transform: rotateX(20deg)\", \".foo{transform:rotateX(20deg)}\");\n    minify_test(\".foo { transform: rotateY(20deg)\", \".foo{transform:rotateY(20deg)}\");\n    minify_test(\".foo { transform: rotateZ(20deg)\", \".foo{transform:rotate(20deg)}\");\n    minify_test(\".foo { transform: rotate(360deg)\", \".foo{transform:rotate(360deg)}\");\n    minify_test(\n      \".foo { transform: rotate3d(2, 3, 4, 20deg)\",\n      \".foo{transform:rotate3d(2,3,4,20deg)}\",\n    );\n    minify_test(\n      \".foo { transform: rotate3d(1, 0, 0, 20deg)\",\n      \".foo{transform:rotateX(20deg)}\",\n    );\n    minify_test(\n      \".foo { transform: rotate3d(0, 1, 0, 20deg)\",\n      \".foo{transform:rotateY(20deg)}\",\n    );\n    minify_test(\n      \".foo { transform: rotate3d(0, 0, 1, 20deg)\",\n      \".foo{transform:rotate(20deg)}\",\n    );\n    minify_test(\".foo { transform: rotate(405deg)}\", \".foo{transform:rotate(405deg)}\");\n    minify_test(\".foo { transform: rotateX(405deg)}\", \".foo{transform:rotateX(405deg)}\");\n    minify_test(\".foo { transform: rotateY(405deg)}\", \".foo{transform:rotateY(405deg)}\");\n    minify_test(\".foo { transform: rotate(-200deg)}\", \".foo{transform:rotate(-200deg)}\");\n    minify_test(\".foo { transform: rotate(0)\", \".foo{transform:rotate(0)}\");\n    minify_test(\".foo { transform: rotate(0deg)\", \".foo{transform:rotate(0)}\");\n    minify_test(\n      \".foo { transform: rotateX(-200deg)}\",\n      \".foo{transform:rotateX(-200deg)}\",\n    );\n    minify_test(\n      \".foo { transform: rotateY(-200deg)}\",\n      \".foo{transform:rotateY(-200deg)}\",\n    );\n    minify_test(\n      \".foo { transform: rotate3d(1, 1, 0, -200deg)\",\n      \".foo{transform:rotate3d(1,1,0,-200deg)}\",\n    );\n    minify_test(\".foo { transform: skew(20deg)\", \".foo{transform:skew(20deg)}\");\n    minify_test(\".foo { transform: skew(20deg, 0deg)\", \".foo{transform:skew(20deg)}\");\n    minify_test(\".foo { transform: skew(0deg, 20deg)\", \".foo{transform:skewY(20deg)}\");\n    minify_test(\".foo { transform: skewX(20deg)\", \".foo{transform:skew(20deg)}\");\n    minify_test(\".foo { transform: skewY(20deg)\", \".foo{transform:skewY(20deg)}\");\n    minify_test(\n      \".foo { transform: perspective(10px)\",\n      \".foo{transform:perspective(10px)}\",\n    );\n    minify_test(\n      \".foo { transform: matrix(1, 2, -1, 1, 80, 80)\",\n      \".foo{transform:matrix(1,2,-1,1,80,80)}\",\n    );\n    minify_test(\n      \".foo { transform: matrix3d(1, 0, 0, 0, 0, 1, 6, 0, 0, 0, 1, 0, 50, 100, 0, 1.1)\",\n      \".foo{transform:matrix3d(1,0,0,0,0,1,6,0,0,0,1,0,50,100,0,1.1)}\",\n    );\n    // TODO: Re-enable with a better solution\n    //       See: https://github.com/parcel-bundler/lightningcss/issues/288\n    // minify_test(\n    //   \".foo{transform:translate(100px,200px) rotate(45deg) skew(10deg) scale(2)}\",\n    //   \".foo{transform:matrix(1.41421,1.41421,-1.16485,1.66358,100,200)}\",\n    // );\n    // minify_test(\n    //   \".foo{transform:translate(200px,300px) translate(100px,200px) scale(2)}\",\n    //   \".foo{transform:matrix(2,0,0,2,300,500)}\",\n    // );\n    minify_test(\n      \".foo{transform:translate(100px,200px) rotate(45deg)}\",\n      \".foo{transform:translate(100px,200px)rotate(45deg)}\",\n    );\n    minify_test(\n      \".foo{transform:rotate3d(1, 1, 1, 45deg) translate3d(100px, 100px, 10px)}\",\n      \".foo{transform:rotate3d(1,1,1,45deg)translate3d(100px,100px,10px)}\",\n    );\n    // TODO: Re-enable with a better solution\n    //       See: https://github.com/parcel-bundler/lightningcss/issues/288\n    // minify_test(\n    //   \".foo{transform:translate3d(100px, 100px, 10px) skew(10deg) scale3d(2, 3, 4)}\",\n    //   \".foo{transform:matrix3d(2,0,0,0,.528981,3,0,0,0,0,4,0,100,100,10,1)}\",\n    // );\n    // minify_test(\n    //   \".foo{transform:matrix3d(0.804737854124365, 0.5058793634016805, -0.31061721752604554, 0, -0.31061721752604554, 0.804737854124365, 0.5058793634016805, 0, 0.5058793634016805, -0.31061721752604554, 0.804737854124365, 0, 100, 100, 10, 1)}\",\n    //   \".foo{transform:translate3d(100px,100px,10px)rotate3d(1,1,1,45deg)}\"\n    // );\n    // minify_test(\n    //   \".foo{transform:matrix3d(1, 0, 0, 0, 0, 0.7071067811865476, 0.7071067811865475, 0, 0, -0.7071067811865475, 0.7071067811865476, 0, 100, 100, 10, 1)}\",\n    //   \".foo{transform:translate3d(100px,100px,10px)rotateX(45deg)}\"\n    // );\n    // minify_test(\n    //   \".foo{transform:translate3d(100px, 200px, 10px) translate(100px, 100px)}\",\n    //   \".foo{transform:translate3d(200px,300px,10px)}\",\n    // );\n    // minify_test(\n    //   \".foo{transform:rotate(45deg) rotate(45deg)}\",\n    //   \".foo{transform:rotate(90deg)}\",\n    // );\n    // minify_test(\n    //   \".foo{transform:matrix(0.7071067811865476, 0.7071067811865475, -0.7071067811865475, 0.7071067811865476, 100, 100)}\",\n    //   \".foo{transform:translate(100px,100px)rotate(45deg)}\"\n    // );\n    // minify_test(\n    //   \".foo{transform:translateX(2in) translateX(50px)}\",\n    //   \".foo{transform:translate(242px)}\",\n    // );\n    minify_test(\n      \".foo{transform:translateX(calc(2in + 50px))}\",\n      \".foo{transform:translate(242px)}\",\n    );\n    minify_test(\".foo{transform:translateX(50%)}\", \".foo{transform:translate(50%)}\");\n    minify_test(\n      \".foo{transform:translateX(calc(50% - 100px + 20px))}\",\n      \".foo{transform:translate(calc(50% - 80px))}\",\n    );\n    minify_test(\n      \".foo{transform:rotate(calc(10deg + 20deg))}\",\n      \".foo{transform:rotate(30deg)}\",\n    );\n    minify_test(\n      \".foo{transform:rotate(calc(10deg + 0.349066rad))}\",\n      \".foo{transform:rotate(30deg)}\",\n    );\n    minify_test(\n      \".foo{transform:rotate(calc(10deg + 1.5turn))}\",\n      \".foo{transform:rotate(550deg)}\",\n    );\n    minify_test(\n      \".foo{transform:rotate(calc(10deg * 2))}\",\n      \".foo{transform:rotate(20deg)}\",\n    );\n    minify_test(\n      \".foo{transform:rotate(calc(-10deg * 2))}\",\n      \".foo{transform:rotate(-20deg)}\",\n    );\n    minify_test(\n      \".foo{transform:rotate(calc(10deg + var(--test)))}\",\n      \".foo{transform:rotate(calc(10deg + var(--test)))}\",\n    );\n    minify_test(\".foo { transform: scale(calc(.1 + .2))\", \".foo{transform:scale(.3)}\");\n\n    minify_test(\n      \".foo { -webkit-transform: scale(calc(10% + 20%))\",\n      \".foo{-webkit-transform:scale(.3)}\",\n    );\n\n    minify_test(\".foo { translate: 1px 2px 3px }\", \".foo{translate:1px 2px 3px}\");\n    minify_test(\".foo { translate: 1px 0px 0px }\", \".foo{translate:1px}\");\n    minify_test(\".foo { translate: 1px 2px 0px }\", \".foo{translate:1px 2px}\");\n    minify_test(\".foo { translate: 1px 0px 2px }\", \".foo{translate:1px 0 2px}\");\n    minify_test(\".foo { translate: none }\", \".foo{translate:none}\");\n    minify_test(\".foo { rotate: none }\", \".foo{rotate:none}\");\n    minify_test(\".foo { rotate: 0deg }\", \".foo{rotate:0deg}\");\n    minify_test(\".foo { rotate: -0deg }\", \".foo{rotate:0deg}\");\n    minify_test(\".foo { rotate: 10deg }\", \".foo{rotate:10deg}\");\n    minify_test(\".foo { rotate: z 10deg }\", \".foo{rotate:10deg}\");\n    minify_test(\".foo { rotate: 0 0 1 10deg }\", \".foo{rotate:10deg}\");\n    minify_test(\".foo { rotate: x 10deg }\", \".foo{rotate:x 10deg}\");\n    minify_test(\".foo { rotate: 1 0 0 10deg }\", \".foo{rotate:x 10deg}\");\n    minify_test(\".foo { rotate: 2 0 0 10deg }\", \".foo{rotate:x 10deg}\");\n    minify_test(\".foo { rotate: 0 2 0 10deg }\", \".foo{rotate:y 10deg}\");\n    minify_test(\".foo { rotate: 0 0 2 10deg }\", \".foo{rotate:10deg}\");\n    minify_test(\".foo { rotate: 0 0 5.3 10deg }\", \".foo{rotate:10deg}\");\n    minify_test(\".foo { rotate: 0 0 1 0deg }\", \".foo{rotate:0deg}\");\n    minify_test(\".foo { rotate: 10deg 0 0 -1 }\", \".foo{rotate:-10deg}\");\n    minify_test(\".foo { rotate: 10deg 0 0 -233 }\", \".foo{rotate:-10deg}\");\n    minify_test(\".foo { rotate: -1 0 0 0deg }\", \".foo{rotate:x 0deg}\");\n    minify_test(\".foo { rotate: 0deg 0 0 1 }\", \".foo{rotate:0deg}\");\n    minify_test(\".foo { rotate: 0deg 0 0 -1 }\", \".foo{rotate:0deg}\");\n    minify_test(\".foo { rotate: 0 1 0 10deg }\", \".foo{rotate:y 10deg}\");\n    minify_test(\".foo { rotate: x 0rad }\", \".foo{rotate:x 0deg}\");\n    // TODO: In minify mode, convert units to the shortest form.\n    // minify_test(\".foo { rotate: y 0turn }\", \".foo{rotate:y 0deg}\");\n    minify_test(\".foo { rotate: z 0deg }\", \".foo{rotate:0deg}\");\n    minify_test(\".foo { rotate: 10deg y }\", \".foo{rotate:y 10deg}\");\n    minify_test(\".foo { rotate: 1 1 1 10deg }\", \".foo{rotate:1 1 1 10deg}\");\n    minify_test(\".foo { scale: 1 }\", \".foo{scale:1}\");\n    minify_test(\".foo { scale: 1 1 }\", \".foo{scale:1}\");\n    minify_test(\".foo { scale: 1 1 1 }\", \".foo{scale:1}\");\n    minify_test(\".foo { scale: none }\", \".foo{scale:none}\");\n    minify_test(\".foo { scale: 1 0 }\", \".foo{scale:1 0}\");\n    minify_test(\".foo { scale: 1 0 1 }\", \".foo{scale:1 0}\");\n    minify_test(\".foo { scale: 1 0 0 }\", \".foo{scale:1 0 0}\");\n\n    // scale, Convert <percentage> to <number>\n    test(\n      \".foo { scale: 50% 1 200% }\",\n      indoc! {r#\"\n      .foo {\n        scale: .5 1 2;\n      }\n      \"#},\n    );\n    minify_test(\".foo { scale: 1% }\", \".foo{scale:.01}\");\n    minify_test(\".foo { scale: 0% }\", \".foo{scale:0}\");\n    minify_test(\".foo { scale: 0.0% }\", \".foo{scale:0}\");\n    minify_test(\".foo { scale: -0% }\", \".foo{scale:0}\");\n    minify_test(\".foo { scale: -0 }\", \".foo{scale:0}\");\n    minify_test(\".foo { scale: -0.0 }\", \".foo{scale:0}\");\n    minify_test(\".foo { scale: 100% }\", \".foo{scale:1}\");\n    minify_test(\".foo { scale: -100% }\", \".foo{scale:-1}\");\n    minify_test(\".foo { scale: 68% }\", \".foo{scale:.68}\");\n    minify_test(\".foo { scale: 5.96% }\", \".foo{scale:.0596}\");\n    // Match WPT coverage for repeated and multi-value percentages.\n    minify_test(\".foo { scale: 100% 100% }\", \".foo{scale:1}\");\n    minify_test(\".foo { scale: 100% 100% 1 }\", \".foo{scale:1}\");\n    minify_test(\".foo { scale: -100% -100% }\", \".foo{scale:-1}\");\n    minify_test(\".foo { scale: -100% -100% 1 }\", \".foo{scale:-1}\");\n    minify_test(\".foo { scale: 100% 200% }\", \".foo{scale:1 2}\");\n    minify_test(\".foo { scale: 100% 200% 1 }\", \".foo{scale:1 2}\");\n    minify_test(\".foo { scale: 100% 100% 0% }\", \".foo{scale:1 1 0}\");\n    minify_test(\".foo { scale: 100% 100% 100% }\", \".foo{scale:1}\");\n    minify_test(\".foo { scale: -0% -0% -0% }\", \".foo{scale:0 0 0}\");\n    // Additional edge cases: mixed inputs and computed percentages.\n    minify_test(\".foo { scale: 2 100% }\", \".foo{scale:2 1}\");\n    minify_test(\".foo { scale: 2 -50% }\", \".foo{scale:2 -.5}\");\n    minify_test(\".foo { scale: -90% -1 }\", \".foo{scale:-.9 -1}\");\n    minify_test(\".foo { scale: calc(10% + 20%) }\", \".foo{scale:.3}\");\n    minify_test(\".foo { scale: calc(150% - 50%) 200% }\", \".foo{scale:1 2}\");\n    minify_test(\".foo { scale: 200% calc(50% - 80%) }\", \".foo{scale:2 -.3}\");\n    // TODO: For infinite decimals, please do not attempt to resolve calc\n    // Expected: calc(1 / 3)\n    // https://github.com/parcel-bundler/lightningcss/issues/12\n    minify_test(\".foo { scale: calc(100% / 3) }\", \".foo{scale:.333333}\");\n\n    assert_eq!(\n      Property::Scale(crate::properties::transform::Scale::XYZ {\n        x: crate::values::percentage::NumberOrPercentage::Percentage(crate::values::percentage::Percentage(0.5)),\n        y: crate::values::percentage::NumberOrPercentage::Percentage(crate::values::percentage::Percentage(2.0)),\n        z: crate::values::percentage::NumberOrPercentage::Percentage(crate::values::percentage::Percentage(1.0)),\n      })\n      .to_css_string(\n        false,\n        PrinterOptions {\n          minify: true,\n          ..PrinterOptions::default()\n        },\n      )\n      .unwrap(),\n      \"scale:.5 2\"\n    );\n    assert_eq!(\n      Property::Transform(\n        crate::properties::transform::TransformList(vec![crate::properties::transform::Transform::ScaleX(\n          crate::values::percentage::NumberOrPercentage::Percentage(crate::values::percentage::Percentage(0.1)),\n        )]),\n        VendorPrefix::None,\n      )\n      .to_css_string(\n        false,\n        PrinterOptions {\n          minify: true,\n          ..PrinterOptions::default()\n        },\n      )\n      .unwrap(),\n      \"transform:scaleX(.1)\"\n    );\n\n    // TODO: Re-enable with a better solution\n    //       See: https://github.com/parcel-bundler/lightningcss/issues/288\n    // minify_test(\".foo { transform: scale(3); scale: 0.5 }\", \".foo{transform:scale(1.5)}\");\n    minify_test(\".foo { scale: 0.5; transform: scale(3); }\", \".foo{transform:scale(3)}\");\n\n    prefix_test(\n      r#\"\n      .foo {\n        transform: scale(0.5);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-transform: scale(.5);\n        -moz-transform: scale(.5);\n        transform: scale(.5);\n      }\n    \"#},\n      Browsers {\n        firefox: Some(6 << 16),\n        safari: Some(6 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        transform: var(--transform);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-transform: var(--transform);\n        -moz-transform: var(--transform);\n        transform: var(--transform);\n      }\n    \"#},\n      Browsers {\n        firefox: Some(6 << 16),\n        safari: Some(6 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        transform: translateX(-50%);\n        transform: translateX(20px);\n      }\n      \"#,\n      indoc! {r#\"\n      .foo {\n        transform: translateX(20px);\n      }\n      \"#},\n    );\n  }\n\n  #[test]\n  pub fn test_gradients() {\n    minify_test(\n      \".foo { background: linear-gradient(yellow, blue) }\",\n      \".foo{background:linear-gradient(#ff0,#00f)}\",\n    );\n    minify_test(\n      \".foo { background: linear-gradient(to bottom, yellow, blue); }\",\n      \".foo{background:linear-gradient(#ff0,#00f)}\",\n    );\n    minify_test(\n      \".foo { background: linear-gradient(180deg, yellow, blue); }\",\n      \".foo{background:linear-gradient(#ff0,#00f)}\",\n    );\n    minify_test(\n      \".foo { background: linear-gradient(0.5turn, yellow, blue); }\",\n      \".foo{background:linear-gradient(#ff0,#00f)}\",\n    );\n    minify_test(\n      \".foo { background: linear-gradient(yellow 10%, blue 20%) }\",\n      \".foo{background:linear-gradient(#ff0 10%,#00f 20%)}\",\n    );\n    minify_test(\n      \".foo { background: linear-gradient(to top, blue, yellow); }\",\n      \".foo{background:linear-gradient(#ff0,#00f)}\",\n    );\n    minify_test(\n      \".foo { background: linear-gradient(to top, blue 10%, yellow 20%); }\",\n      \".foo{background:linear-gradient(#ff0 80%,#00f 90%)}\",\n    );\n    minify_test(\n      \".foo { background: linear-gradient(to top, blue 10px, yellow 20px); }\",\n      \".foo{background:linear-gradient(0deg,#00f 10px,#ff0 20px)}\",\n    );\n    minify_test(\n      \".foo { background: linear-gradient(135deg, yellow, blue); }\",\n      \".foo{background:linear-gradient(135deg,#ff0,#00f)}\",\n    );\n    minify_test(\n      \".foo { background: linear-gradient(yellow, blue 20%, #0f0); }\",\n      \".foo{background:linear-gradient(#ff0,#00f 20%,#0f0)}\",\n    );\n    minify_test(\n      \".foo { background: linear-gradient(to top right, red, white, blue) }\",\n      \".foo{background:linear-gradient(to top right,red,#fff,#00f)}\",\n    );\n    minify_test(\n      \".foo { background: linear-gradient(yellow, blue calc(10% * 2), #0f0); }\",\n      \".foo{background:linear-gradient(#ff0,#00f 20%,#0f0)}\",\n    );\n    minify_test(\n      \".foo { background: linear-gradient(yellow, 20%, blue); }\",\n      \".foo{background:linear-gradient(#ff0,20%,#00f)}\",\n    );\n    minify_test(\n      \".foo { background: linear-gradient(yellow, 50%, blue); }\",\n      \".foo{background:linear-gradient(#ff0,#00f)}\",\n    );\n    minify_test(\n      \".foo { background: linear-gradient(yellow, 20px, blue); }\",\n      \".foo{background:linear-gradient(#ff0,20px,#00f)}\",\n    );\n    minify_test(\n      \".foo { background: linear-gradient(yellow, 50px, blue); }\",\n      \".foo{background:linear-gradient(#ff0,50px,#00f)}\",\n    );\n    minify_test(\n      \".foo { background: linear-gradient(yellow, 50px, blue); }\",\n      \".foo{background:linear-gradient(#ff0,50px,#00f)}\",\n    );\n    minify_test(\n      \".foo { background: linear-gradient(yellow, red 30% 40%, blue); }\",\n      \".foo{background:linear-gradient(#ff0,red 30% 40%,#00f)}\",\n    );\n    minify_test(\n      \".foo { background: linear-gradient(yellow, red 30%, red 40%, blue); }\",\n      \".foo{background:linear-gradient(#ff0,red 30% 40%,#00f)}\",\n    );\n    minify_test(\n      \".foo { background: linear-gradient(0, yellow, blue); }\",\n      \".foo{background:linear-gradient(#00f,#ff0)}\",\n    );\n    minify_test(\n      \".foo { background: -webkit-linear-gradient(yellow, blue) }\",\n      \".foo{background:-webkit-linear-gradient(#ff0,#00f)}\",\n    );\n    minify_test(\n      \".foo { background: -webkit-linear-gradient(bottom, yellow, blue); }\",\n      \".foo{background:-webkit-linear-gradient(#ff0,#00f)}\",\n    );\n    minify_test(\n      \".foo { background: -webkit-linear-gradient(top right, red, white, blue) }\",\n      \".foo{background:-webkit-linear-gradient(top right,red,#fff,#00f)}\",\n    );\n    minify_test(\n      \".foo { background: -moz-linear-gradient(yellow, blue) }\",\n      \".foo{background:-moz-linear-gradient(#ff0,#00f)}\",\n    );\n    minify_test(\n      \".foo { background: -moz-linear-gradient(bottom, yellow, blue); }\",\n      \".foo{background:-moz-linear-gradient(#ff0,#00f)}\",\n    );\n    minify_test(\n      \".foo { background: -moz-linear-gradient(top right, red, white, blue) }\",\n      \".foo{background:-moz-linear-gradient(top right,red,#fff,#00f)}\",\n    );\n    minify_test(\n      \".foo { background: -o-linear-gradient(yellow, blue) }\",\n      \".foo{background:-o-linear-gradient(#ff0,#00f)}\",\n    );\n    minify_test(\n      \".foo { background: -o-linear-gradient(bottom, yellow, blue); }\",\n      \".foo{background:-o-linear-gradient(#ff0,#00f)}\",\n    );\n    minify_test(\n      \".foo { background: -o-linear-gradient(top right, red, white, blue) }\",\n      \".foo{background:-o-linear-gradient(top right,red,#fff,#00f)}\",\n    );\n    minify_test(\n      \".foo { background: -webkit-gradient(linear, left top, left bottom, from(blue), to(yellow)) }\",\n      \".foo{background:-webkit-gradient(linear,0 0,0 100%,from(#00f),to(#ff0))}\",\n    );\n    minify_test(\n      \".foo { background: -webkit-gradient(linear, left top, left bottom, from(blue), color-stop(50%, red), to(yellow)) }\",\n      \".foo{background:-webkit-gradient(linear,0 0,0 100%,from(#00f),color-stop(.5,red),to(#ff0))}\"\n    );\n    minify_test(\n      \".foo { background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, blue), color-stop(50%, red), color-stop(100%, yellow)) }\",\n      \".foo{background:-webkit-gradient(linear,0 0,0 100%,from(#00f),color-stop(.5,red),to(#ff0))}\"\n    );\n    minify_test(\n      \".foo { background: repeating-linear-gradient(yellow 10px, blue 50px) }\",\n      \".foo{background:repeating-linear-gradient(#ff0 10px,#00f 50px)}\",\n    );\n    minify_test(\n      \".foo { background: -webkit-repeating-linear-gradient(yellow 10px, blue 50px) }\",\n      \".foo{background:-webkit-repeating-linear-gradient(#ff0 10px,#00f 50px)}\",\n    );\n    minify_test(\n      \".foo { background: -moz-repeating-linear-gradient(yellow 10px, blue 50px) }\",\n      \".foo{background:-moz-repeating-linear-gradient(#ff0 10px,#00f 50px)}\",\n    );\n    minify_test(\n      \".foo { background: -o-repeating-linear-gradient(yellow 10px, blue 50px) }\",\n      \".foo{background:-o-repeating-linear-gradient(#ff0 10px,#00f 50px)}\",\n    );\n    minify_test(\n      \".foo { background: radial-gradient(yellow, blue) }\",\n      \".foo{background:radial-gradient(#ff0,#00f)}\",\n    );\n    minify_test(\n      \".foo { background: radial-gradient(at top left, yellow, blue) }\",\n      \".foo{background:radial-gradient(at 0 0,#ff0,#00f)}\",\n    );\n    minify_test(\n      \".foo { background: radial-gradient(5em circle at top left, yellow, blue) }\",\n      \".foo{background:radial-gradient(5em at 0 0,#ff0,#00f)}\",\n    );\n    minify_test(\n      \".foo { background: radial-gradient(circle at 100%, #333, #333 50%, #eee 75%, #333 75%) }\",\n      \".foo{background:radial-gradient(circle at 100%,#333,#333 50%,#eee 75%,#333 75%)}\",\n    );\n    minify_test(\n      \".foo { background: radial-gradient(farthest-corner circle at 100% 50%, #333, #333 50%, #eee 75%, #333 75%) }\",\n      \".foo{background:radial-gradient(circle at 100%,#333,#333 50%,#eee 75%,#333 75%)}\"\n    );\n    minify_test(\n      \".foo { background: radial-gradient(farthest-corner circle at 50% 50%, #333, #333 50%, #eee 75%, #333 75%) }\",\n      \".foo{background:radial-gradient(circle,#333,#333 50%,#eee 75%,#333 75%)}\"\n    );\n    minify_test(\n      \".foo { background: radial-gradient(ellipse at top, #e66465, transparent) }\",\n      \".foo{background:radial-gradient(at top,#e66465,#0000)}\",\n    );\n    minify_test(\n      \".foo { background: radial-gradient(20px, yellow, blue) }\",\n      \".foo{background:radial-gradient(20px,#ff0,#00f)}\",\n    );\n    minify_test(\n      \".foo { background: radial-gradient(circle 20px, yellow, blue) }\",\n      \".foo{background:radial-gradient(20px,#ff0,#00f)}\",\n    );\n    minify_test(\n      \".foo { background: radial-gradient(20px 40px, yellow, blue) }\",\n      \".foo{background:radial-gradient(20px 40px,#ff0,#00f)}\",\n    );\n    minify_test(\n      \".foo { background: radial-gradient(ellipse 20px 40px, yellow, blue) }\",\n      \".foo{background:radial-gradient(20px 40px,#ff0,#00f)}\",\n    );\n    minify_test(\n      \".foo { background: radial-gradient(ellipse calc(20px + 10px) 40px, yellow, blue) }\",\n      \".foo{background:radial-gradient(30px 40px,#ff0,#00f)}\",\n    );\n    minify_test(\n      \".foo { background: radial-gradient(circle farthest-side, yellow, blue) }\",\n      \".foo{background:radial-gradient(circle farthest-side,#ff0,#00f)}\",\n    );\n    minify_test(\n      \".foo { background: radial-gradient(farthest-side circle, yellow, blue) }\",\n      \".foo{background:radial-gradient(circle farthest-side,#ff0,#00f)}\",\n    );\n    minify_test(\n      \".foo { background: radial-gradient(ellipse farthest-side, yellow, blue) }\",\n      \".foo{background:radial-gradient(farthest-side,#ff0,#00f)}\",\n    );\n    minify_test(\n      \".foo { background: radial-gradient(farthest-side ellipse, yellow, blue) }\",\n      \".foo{background:radial-gradient(farthest-side,#ff0,#00f)}\",\n    );\n    minify_test(\n      \".foo { background: -webkit-radial-gradient(yellow, blue) }\",\n      \".foo{background:-webkit-radial-gradient(#ff0,#00f)}\",\n    );\n    minify_test(\n      \".foo { background: -moz-radial-gradient(yellow, blue) }\",\n      \".foo{background:-moz-radial-gradient(#ff0,#00f)}\",\n    );\n    minify_test(\n      \".foo { background: -o-radial-gradient(yellow, blue) }\",\n      \".foo{background:-o-radial-gradient(#ff0,#00f)}\",\n    );\n    minify_test(\n      \".foo { background: repeating-radial-gradient(circle 20px, yellow, blue) }\",\n      \".foo{background:repeating-radial-gradient(20px,#ff0,#00f)}\",\n    );\n    minify_test(\n      \".foo { background: -webkit-repeating-radial-gradient(circle 20px, yellow, blue) }\",\n      \".foo{background:-webkit-repeating-radial-gradient(20px,#ff0,#00f)}\",\n    );\n    minify_test(\n      \".foo { background: -moz-repeating-radial-gradient(circle 20px, yellow, blue) }\",\n      \".foo{background:-moz-repeating-radial-gradient(20px,#ff0,#00f)}\",\n    );\n    minify_test(\n      \".foo { background: -o-repeating-radial-gradient(circle 20px, yellow, blue) }\",\n      \".foo{background:-o-repeating-radial-gradient(20px,#ff0,#00f)}\",\n    );\n    minify_test(\n      \".foo { background: -webkit-gradient(radial, center center, 0, center center, 100, from(blue), to(yellow)) }\",\n      \".foo{background:-webkit-gradient(radial,50% 50%,0,50% 50%,100,from(#00f),to(#ff0))}\"\n    );\n    minify_test(\n      \".foo { background: conic-gradient(#f06, gold) }\",\n      \".foo{background:conic-gradient(#f06,gold)}\",\n    );\n    minify_test(\n      \".foo { background: conic-gradient(at 50% 50%, #f06, gold) }\",\n      \".foo{background:conic-gradient(#f06,gold)}\",\n    );\n    minify_test(\n      \".foo { background: conic-gradient(from 0deg, #f06, gold) }\",\n      \".foo{background:conic-gradient(#f06,gold)}\",\n    );\n    minify_test(\n      \".foo { background: conic-gradient(from 0, #f06, gold) }\",\n      \".foo{background:conic-gradient(#f06,gold)}\",\n    );\n    minify_test(\n      \".foo { background: conic-gradient(from 0deg at center, #f06, gold) }\",\n      \".foo{background:conic-gradient(#f06,gold)}\",\n    );\n    minify_test(\n      \".foo { background: conic-gradient(white -50%, black 150%) }\",\n      \".foo{background:conic-gradient(#fff -50%,#000 150%)}\",\n    );\n    minify_test(\n      \".foo { background: conic-gradient(white -180deg, black 540deg) }\",\n      \".foo{background:conic-gradient(#fff -180deg,#000 540deg)}\",\n    );\n    minify_test(\n      \".foo { background: conic-gradient(from 45deg, white, black, white) }\",\n      \".foo{background:conic-gradient(from 45deg,#fff,#000,#fff)}\",\n    );\n    minify_test(\n      \".foo { background: repeating-conic-gradient(from 45deg, white, black, white) }\",\n      \".foo{background:repeating-conic-gradient(from 45deg,#fff,#000,#fff)}\",\n    );\n    minify_test(\n      \".foo { background: repeating-conic-gradient(black 0deg 25%, white 0deg 50%) }\",\n      \".foo{background:repeating-conic-gradient(#000 0deg 25%,#fff 0deg 50%)}\",\n    );\n\n    test(\n      r#\"\n        .foo {\n          background: -webkit-gradient(linear, left top, left bottom, from(red), to(blue));\n          background: -webkit-linear-gradient(red, blue);\n          background: -moz-linear-gradient(red, blue);\n          background: -o-linear-gradient(red, blue);\n          background: linear-gradient(red, blue);\n        }\n      \"#,\n      indoc! {r#\"\n        .foo {\n          background: -webkit-gradient(linear, left top, left bottom, from(red), to(#00f));\n          background: -webkit-linear-gradient(red, #00f);\n          background: -moz-linear-gradient(red, #00f);\n          background: -o-linear-gradient(red, #00f);\n          background: linear-gradient(red, #00f);\n        }\n      \"#},\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        background: -webkit-gradient(linear, left top, left bottom, from(red), to(blue));\n        background: -webkit-linear-gradient(red, blue);\n        background: -moz-linear-gradient(red, blue);\n        background: -o-linear-gradient(red, blue);\n        background: linear-gradient(red, blue);\n      }\n      \"#,\n      indoc! {r#\"\n      .foo {\n        background: linear-gradient(red, #00f);\n      }\n      \"#},\n      Browsers {\n        chrome: Some(95 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        background: -webkit-gradient(linear, left top, left bottom, from(red), to(blue));\n        background: -webkit-linear-gradient(red, blue);\n        background: -moz-linear-gradient(red, blue);\n        background: -o-linear-gradient(red, blue);\n      }\n      \"#,\n      indoc! {r#\"\n      .foo {\n        background: -webkit-gradient(linear, left top, left bottom, from(red), to(#00f));\n        background: -webkit-linear-gradient(red, #00f);\n        background: -moz-linear-gradient(red, #00f);\n        background: -o-linear-gradient(red, #00f);\n      }\n      \"#},\n      Browsers {\n        chrome: Some(95 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        background-image: linear-gradient(red, blue);\n      }\n      \"#,\n      indoc! {r#\"\n      .foo {\n        background-image: -webkit-gradient(linear, 0 0, 0 100%, from(red), to(#00f));\n        background-image: -webkit-linear-gradient(top, red, #00f);\n        background-image: linear-gradient(red, #00f);\n      }\n      \"#},\n      Browsers {\n        chrome: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        background-image: linear-gradient(to right, red, blue);\n      }\n      \"#,\n      indoc! {r#\"\n      .foo {\n        background-image: -webkit-gradient(linear, 0 0, 100% 0, from(red), to(#00f));\n        background-image: -webkit-linear-gradient(left, red, #00f);\n        background-image: linear-gradient(to right, red, #00f);\n      }\n      \"#},\n      Browsers {\n        chrome: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        background-image: linear-gradient(to top, red, blue);\n      }\n      \"#,\n      indoc! {r#\"\n      .foo {\n        background-image: -webkit-gradient(linear, 0 100%, 0 0, from(red), to(#00f));\n        background-image: -webkit-linear-gradient(red, #00f);\n        background-image: linear-gradient(to top, red, #00f);\n      }\n      \"#},\n      Browsers {\n        chrome: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        background-image: linear-gradient(to left, red, blue);\n      }\n      \"#,\n      indoc! {r#\"\n      .foo {\n        background-image: -webkit-gradient(linear, 100% 0, 0 0, from(red), to(#00f));\n        background-image: -webkit-linear-gradient(right, red, #00f);\n        background-image: linear-gradient(to left, red, #00f);\n      }\n      \"#},\n      Browsers {\n        chrome: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        background-image: linear-gradient(to left bottom, red, blue);\n      }\n      \"#,\n      indoc! {r#\"\n      .foo {\n        background-image: -webkit-gradient(linear, 100% 0, 0 100%, from(red), to(#00f));\n        background-image: -webkit-linear-gradient(top right, red, #00f);\n        background-image: linear-gradient(to bottom left, red, #00f);\n      }\n      \"#},\n      Browsers {\n        chrome: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        background-image: linear-gradient(to top right, red, blue);\n      }\n      \"#,\n      indoc! {r#\"\n      .foo {\n        background-image: -webkit-gradient(linear, 0 100%, 100% 0, from(red), to(#00f));\n        background-image: -webkit-linear-gradient(bottom left, red, #00f);\n        background-image: linear-gradient(to top right, red, #00f);\n      }\n      \"#},\n      Browsers {\n        chrome: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        background-image: linear-gradient(90deg, red, blue);\n      }\n      \"#,\n      indoc! {r#\"\n      .foo {\n        background-image: -webkit-gradient(linear, 0 0, 100% 0, from(red), to(#00f));\n        background-image: -webkit-linear-gradient(0deg, red, #00f);\n        background-image: linear-gradient(90deg, red, #00f);\n      }\n      \"#},\n      Browsers {\n        chrome: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        background-image: linear-gradient(45deg, red, blue);\n      }\n      \"#,\n      indoc! {r#\"\n      .foo {\n        background-image: -webkit-linear-gradient(45deg, red, #00f);\n        background-image: linear-gradient(45deg, red, #00f);\n      }\n      \"#},\n      Browsers {\n        chrome: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        background-image: linear-gradient(red, blue);\n      }\n      \"#,\n      indoc! {r#\"\n      .foo {\n        background-image: -webkit-linear-gradient(top, red, #00f);\n        background-image: linear-gradient(red, #00f);\n      }\n      \"#},\n      Browsers {\n        chrome: Some(10 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        background-image: radial-gradient(20px, red, blue);\n      }\n      \"#,\n      indoc! {r#\"\n      .foo {\n        background-image: -webkit-gradient(radial, center center, 0, center center, 20, from(red), to(#00f));\n        background-image: -webkit-radial-gradient(20px, red, #00f);\n        background-image: radial-gradient(20px, red, #00f);\n      }\n      \"#},\n      Browsers {\n        chrome: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        background-image: radial-gradient(20px at top left, red, blue);\n      }\n      \"#,\n      indoc! {r#\"\n      .foo {\n        background-image: -webkit-gradient(radial, left top, 0, left top, 20, from(red), to(#00f));\n        background-image: -webkit-radial-gradient(20px at 0 0, red, #00f);\n        background-image: radial-gradient(20px at 0 0, red, #00f);\n      }\n      \"#},\n      Browsers {\n        chrome: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        background-image: radial-gradient(red, blue);\n      }\n      \"#,\n      indoc! {r#\"\n      .foo {\n        background-image: -webkit-radial-gradient(red, #00f);\n        background-image: radial-gradient(red, #00f);\n      }\n      \"#},\n      Browsers {\n        chrome: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        background-image: -webkit-gradient(radial, left top, 0, left top, 20, from(red), to(#00f));\n        background-image: -webkit-radial-gradient(20px at 0% 0%, red, #00f);\n        background-image: radial-gradient(20px at 0% 0%, red, #00f);\n      }\n      \"#,\n      indoc! {r#\"\n      .foo {\n        background-image: radial-gradient(20px at 0 0, red, #00f);\n      }\n      \"#},\n      Browsers {\n        chrome: Some(30 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        background: -webkit-gradient(radial, left top, 0, left top, 20, from(red), to(#00f));\n        background: -webkit-radial-gradient(20px at 0% 0%, red, #00f);\n        background: radial-gradient(20px at 0% 0%, red, #00f);\n      }\n      \"#,\n      indoc! {r#\"\n      .foo {\n        background: radial-gradient(20px at 0 0, red, #00f);\n      }\n      \"#},\n      Browsers {\n        chrome: Some(30 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        background: radial-gradient(red, blue);\n      }\n      \"#,\n      indoc! {r#\"\n      .foo {\n        background: -webkit-radial-gradient(red, #00f);\n        background: radial-gradient(red, #00f);\n      }\n      \"#},\n      Browsers {\n        chrome: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        background: radial-gradient(red, blue), linear-gradient(yellow, red), url(bg.jpg);\n      }\n      \"#,\n      indoc! {r#\"\n      .foo {\n        background: -webkit-gradient(linear, 0 0, 0 100%, from(#ff0), to(red)), url(\"bg.jpg\");\n        background: -webkit-radial-gradient(red, #00f), -webkit-linear-gradient(top, #ff0, red), url(\"bg.jpg\");\n        background: -moz-radial-gradient(red, #00f), -moz-linear-gradient(top, #ff0, red), url(\"bg.jpg\");\n        background: -o-radial-gradient(red, #00f), -o-linear-gradient(top, #ff0, red), url(\"bg.jpg\");\n        background: radial-gradient(red, #00f), linear-gradient(#ff0, red), url(\"bg.jpg\");\n      }\n      \"#},\n      Browsers {\n        chrome: Some(8 << 16),\n        firefox: Some(4 << 16),\n        opera: Some(11 << 16 | 5 << 8),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        background: linear-gradient(yellow, red 30% 40%, blue);\n      }\n      \"#,\n      indoc! {r#\"\n      .foo {\n        background: linear-gradient(#ff0, red 30%, red 40%, #00f);\n      }\n      \"#},\n      Browsers {\n        chrome: Some(70 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        background: linear-gradient(yellow, red 30% 40%, blue);\n      }\n      \"#,\n      indoc! {r#\"\n      .foo {\n        background: linear-gradient(#ff0, red 30% 40%, #00f);\n      }\n      \"#},\n      Browsers {\n        chrome: Some(71 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { background: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) }\",\n      indoc! { r#\"\n        .foo {\n          background: linear-gradient(#ff0f0e, #7773ff);\n          background: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364));\n        }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { background: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) }\",\n      indoc! { r#\"\n        .foo {\n          background: linear-gradient(#ff0f0e, #7773ff);\n          background: linear-gradient(color(display-p3 1 .0000153435 -.00000303562), color(display-p3 .440289 .28452 1.23485));\n          background: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364));\n        }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { background: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) }\",\n      indoc! { r#\"\n        .foo {\n          background: -webkit-linear-gradient(top, #ff0f0e, #7773ff);\n          background: linear-gradient(#ff0f0e, #7773ff);\n          background: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364));\n        }\n      \"#},\n      Browsers {\n        chrome: Some(20 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { background: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) }\",\n      indoc! { r#\"\n        .foo {\n          background: -webkit-gradient(linear, 0 0, 0 100%, from(#ff0f0e), to(#7773ff));\n          background: -webkit-linear-gradient(top, #ff0f0e, #7773ff);\n          background: linear-gradient(#ff0f0e, #7773ff);\n          background: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364));\n        }\n      \"#},\n      Browsers {\n        chrome: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { background: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) }\",\n      indoc! { r#\"\n        .foo {\n          background: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364));\n        }\n      \"#},\n      Browsers {\n        safari: Some(15 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { background-image: linear-gradient(oklab(59.686% 0.1009 0.1192), oklab(54.0% -0.10 -0.02)); }\",\n      indoc! { r#\"\n        .foo {\n          background-image: linear-gradient(lab(52.2319% 40.1449 59.9171), lab(47.7776% -34.2947 -7.65904));\n        }\n      \"#},\n      Browsers {\n        safari: Some(15 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { background-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) }\",\n      indoc! { r#\"\n        .foo {\n          background-image: linear-gradient(#ff0f0e, #7773ff);\n          background-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364));\n        }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { background-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) }\",\n      indoc! { r#\"\n        .foo {\n          background-image: linear-gradient(#ff0f0e, #7773ff);\n          background-image: linear-gradient(color(display-p3 1 .0000153435 -.00000303562), color(display-p3 .440289 .28452 1.23485));\n          background-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364));\n        }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { background-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) }\",\n      indoc! { r#\"\n        .foo {\n          background-image: -webkit-linear-gradient(top, #ff0f0e, #7773ff);\n          background-image: linear-gradient(#ff0f0e, #7773ff);\n          background-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364));\n        }\n      \"#},\n      Browsers {\n        chrome: Some(20 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { background-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) }\",\n      indoc! { r#\"\n        .foo {\n          background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ff0f0e), to(#7773ff));\n          background-image: -webkit-linear-gradient(top, #ff0f0e, #7773ff);\n          background-image: linear-gradient(#ff0f0e, #7773ff);\n          background-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364));\n        }\n      \"#},\n      Browsers {\n        chrome: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { background-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) }\",\n      indoc! { r#\"\n        .foo {\n          background-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364));\n        }\n      \"#},\n      Browsers {\n        safari: Some(15 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { background-image: linear-gradient(oklab(59.686% 0.1009 0.1192), oklab(54.0% -0.10 -0.02)); }\",\n      indoc! { r#\"\n        .foo {\n          background-image: linear-gradient(lab(52.2319% 40.1449 59.9171), lab(47.7776% -34.2947 -7.65904));\n        }\n      \"#},\n      Browsers {\n        safari: Some(15 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    // Test cases from https://github.com/postcss/autoprefixer/blob/541295c0e6dd348db2d3f52772b59cd403c59d29/test/cases/gradient.css\n    prefix_test(\n      r#\"\n        a {\n          background: linear-gradient(350.5deg, white, black), linear-gradient(-130deg, black, white), linear-gradient(45deg, black, white);\n        }\n        b {\n          background-image: linear-gradient(rgba(0,0,0,1), white), linear-gradient(white, black);\n        }\n        strong {\n          background: linear-gradient(to top, transparent, rgba(0, 0, 0, 0.8) 20px, #000 30px, #000) no-repeat;\n        }\n        div {\n          background-image: radial-gradient(to left, white, black), repeating-linear-gradient(to bottom right, black, white), repeating-radial-gradient(to top, aqua, red);\n        }\n        .old-radial {\n          background: radial-gradient(0 50%, ellipse farthest-corner, black, white);\n        }\n        .simple1 {\n          background: linear-gradient(black, white);\n        }\n        .simple2 {\n          background: linear-gradient(to left, black 0%, rgba(0, 0, 0, 0.5)50%, white 100%);\n        }\n        .simple3 {\n          background: linear-gradient(to left, black 50%, white 100%);\n        }\n        .simple4 {\n          background: linear-gradient(to right top, black, white);\n        }\n        .direction {\n          background: linear-gradient(top left, black, rgba(0, 0, 0, 0.5), white);\n        }\n        .silent {\n          background: -webkit-linear-gradient(top left, black, white);\n        }\n        .radial {\n          background: radial-gradient(farthest-side at 0 50%, white, black);\n        }\n        .second {\n          background: red linear-gradient(red, blue);\n          background: url('logo.png'), linear-gradient(#fff, #000);\n        }\n        .px {\n          background: linear-gradient(black 0, white 100px);\n        }\n        .list {\n          list-style-image: linear-gradient(white, black);\n        }\n        .mask {\n          mask: linear-gradient(white, black);\n        }\n        .newline {\n          background-image:\n              linear-gradient( white, black ),\n              linear-gradient( black, white );\n        }\n        .convert {\n          background: linear-gradient(0deg, white, black);\n          background: linear-gradient(90deg, white, black);\n          background: linear-gradient(180deg, white, black);\n          background: linear-gradient(270deg, white, black);\n        }\n        .grad {\n          background: linear-gradient(1grad, white, black);\n        }\n        .rad {\n          background: linear-gradient(1rad, white, black);\n        }\n        .turn {\n          background: linear-gradient(0.3turn, white, black);\n        }\n        .norm {\n          background: linear-gradient(-90deg, white, black);\n        }\n        .mask {\n          mask-image: radial-gradient(circle at 86% 86%, transparent 8px, black 8px);\n        }\n        .cover {\n          background: radial-gradient(ellipse cover at center, white, black);\n        }\n        .contain {\n          background: radial-gradient(contain at center, white, black);\n        }\n        .no-div {\n          background: linear-gradient(black);\n        }\n        .background-shorthand {\n          background: radial-gradient(#FFF, transparent) 0 0 / cover no-repeat #F0F;\n        }\n        .background-advanced {\n          background: radial-gradient(ellipse farthest-corner at 5px 15px, rgba(214, 168, 18, 0.7) 0%, rgba(255, 21, 177, 0.7) 50%, rgba(210, 7, 148, 0.7) 95%),\n                      radial-gradient(#FFF, transparent),\n                      url(path/to/image.jpg) 50%/cover;\n        }\n        .multiradial {\n          mask-image: radial-gradient(circle closest-corner at 100% 50%, #000, transparent);\n        }\n        .broken {\n          mask-image: radial-gradient(white, black);\n        }\n        .loop {\n          background-image: url(\"https://test.com/lol(test.png\"), radial-gradient(yellow, black, yellow);\n        }\n        .unitless-zero {\n          background-image: linear-gradient(0, green, blue);\n          background: repeating-linear-gradient(0, blue, red 33.3%)\n        }\n        .zero-grad {\n          background: linear-gradient(0grad, green, blue);\n          background-image: repeating-linear-gradient(0grad, blue, red 33.3%)\n        }\n        .zero-rad {\n          background: linear-gradient(0rad, green, blue);\n        }\n        .zero-turn {\n          background: linear-gradient(0turn, green, blue);\n        }\n      \"#,\n      indoc! { r#\"\n        a {\n          background: -webkit-linear-gradient(99.5deg, #fff, #000), -webkit-linear-gradient(220deg, #000, #fff), -webkit-linear-gradient(45deg, #000, #fff);\n          background: -o-linear-gradient(99.5deg, #fff, #000), -o-linear-gradient(220deg, #000, #fff), -o-linear-gradient(45deg, #000, #fff);\n          background: linear-gradient(350.5deg, #fff, #000), linear-gradient(-130deg, #000, #fff), linear-gradient(45deg, #000, #fff);\n        }\n\n        b {\n          background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#000), to(#fff)), -webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#000));\n          background-image: -webkit-linear-gradient(top, #000, #fff), -webkit-linear-gradient(top, #fff, #000);\n          background-image: -o-linear-gradient(top, #000, #fff), -o-linear-gradient(top, #fff, #000);\n          background-image: linear-gradient(#000, #fff), linear-gradient(#fff, #000);\n        }\n\n        strong {\n          background: -webkit-linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, .8) 20px, #000 30px, #000) no-repeat;\n          background: -o-linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, .8) 20px, #000 30px, #000) no-repeat;\n          background: linear-gradient(to top, rgba(0, 0, 0, 0), rgba(0, 0, 0, .8) 20px, #000 30px, #000) no-repeat;\n        }\n\n        div {\n          background-image: radial-gradient(to left, white, black), repeating-linear-gradient(to bottom right, black, white), repeating-radial-gradient(to top, aqua, red);\n        }\n\n        .old-radial {\n          background: radial-gradient(0 50%, ellipse farthest-corner, black, white);\n        }\n\n        .simple1 {\n          background: -webkit-gradient(linear, 0 0, 0 100%, from(#000), to(#fff));\n          background: -webkit-linear-gradient(top, #000, #fff);\n          background: -o-linear-gradient(top, #000, #fff);\n          background: linear-gradient(#000, #fff);\n        }\n\n        .simple2 {\n          background: -webkit-gradient(linear, 100% 0, 0 0, from(#000), color-stop(.5, rgba(0, 0, 0, .5)), to(#fff));\n          background: -webkit-linear-gradient(right, #000 0%, rgba(0, 0, 0, .5) 50%, #fff 100%);\n          background: -o-linear-gradient(right, #000 0%, rgba(0, 0, 0, .5) 50%, #fff 100%);\n          background: linear-gradient(to left, #000 0%, rgba(0, 0, 0, .5) 50%, #fff 100%);\n        }\n\n        .simple3 {\n          background: -webkit-gradient(linear, 100% 0, 0 0, color-stop(.5, #000), to(#fff));\n          background: -webkit-linear-gradient(right, #000 50%, #fff 100%);\n          background: -o-linear-gradient(right, #000 50%, #fff 100%);\n          background: linear-gradient(to left, #000 50%, #fff 100%);\n        }\n\n        .simple4 {\n          background: -webkit-gradient(linear, 0 100%, 100% 0, from(#000), to(#fff));\n          background: -webkit-linear-gradient(bottom left, #000, #fff);\n          background: -o-linear-gradient(bottom left, #000, #fff);\n          background: linear-gradient(to top right, #000, #fff);\n        }\n\n        .direction {\n          background: linear-gradient(top left, black, rgba(0, 0, 0, .5), white);\n        }\n\n        .silent {\n          background: -webkit-gradient(linear, 100% 100%, 0 0, from(#000), to(#fff));\n          background: -webkit-linear-gradient(top left, #000, #fff);\n        }\n\n        .radial {\n          background: -webkit-radial-gradient(farthest-side at 0, #fff, #000);\n          background: -o-radial-gradient(farthest-side at 0, #fff, #000);\n          background: radial-gradient(farthest-side at 0, #fff, #000);\n        }\n\n        .second {\n          background: red -webkit-gradient(linear, 0 0, 0 100%, from(red), to(#00f));\n          background: red -webkit-linear-gradient(top, red, #00f);\n          background: red -o-linear-gradient(top, red, #00f);\n          background: red linear-gradient(red, #00f);\n          background: url(\"logo.png\"), linear-gradient(#fff, #000);\n        }\n\n        .px {\n          background: -webkit-linear-gradient(top, #000 0, #fff 100px);\n          background: -o-linear-gradient(top, #000 0, #fff 100px);\n          background: linear-gradient(#000 0, #fff 100px);\n        }\n\n        .list {\n          list-style-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#000));\n          list-style-image: -webkit-linear-gradient(top, #fff, #000);\n          list-style-image: -o-linear-gradient(top, #fff, #000);\n          list-style-image: linear-gradient(#fff, #000);\n        }\n\n        .mask {\n          -webkit-mask: -webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#000));\n          -webkit-mask: -webkit-linear-gradient(top, #fff, #000);\n          -webkit-mask: -o-linear-gradient(top, #fff, #000);\n          mask: -o-linear-gradient(top, #fff, #000);\n          -webkit-mask: linear-gradient(#fff, #000);\n          mask: linear-gradient(#fff, #000);\n        }\n\n        .newline {\n          background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#000)), -webkit-gradient(linear, 0 0, 0 100%, from(#000), to(#fff));\n          background-image: -webkit-linear-gradient(top, #fff, #000), -webkit-linear-gradient(top, #000, #fff);\n          background-image: -o-linear-gradient(top, #fff, #000), -o-linear-gradient(top, #000, #fff);\n          background-image: linear-gradient(#fff, #000), linear-gradient(#000, #fff);\n        }\n\n        .convert {\n          background: -webkit-gradient(linear, 0 100%, 0 0, from(#fff), to(#000));\n          background: -webkit-linear-gradient(90deg, #fff, #000);\n          background: -o-linear-gradient(90deg, #fff, #000);\n          background: linear-gradient(0deg, #fff, #000);\n          background: linear-gradient(90deg, #fff, #000);\n          background: linear-gradient(#fff, #000);\n          background: linear-gradient(270deg, #fff, #000);\n        }\n\n        .grad {\n          background: -webkit-linear-gradient(89.1deg, #fff, #000);\n          background: -o-linear-gradient(89.1deg, #fff, #000);\n          background: linear-gradient(1grad, #fff, #000);\n        }\n\n        .rad {\n          background: -webkit-linear-gradient(32.704deg, #fff, #000);\n          background: -o-linear-gradient(32.704deg, #fff, #000);\n          background: linear-gradient(57.2958deg, #fff, #000);\n        }\n\n        .turn {\n          background: -webkit-linear-gradient(342deg, #fff, #000);\n          background: -o-linear-gradient(342deg, #fff, #000);\n          background: linear-gradient(.3turn, #fff, #000);\n        }\n\n        .norm {\n          background: -webkit-linear-gradient(#fff, #000);\n          background: -o-linear-gradient(#fff, #000);\n          background: linear-gradient(-90deg, #fff, #000);\n        }\n\n        .mask {\n          -webkit-mask-image: -webkit-radial-gradient(circle at 86% 86%, rgba(0, 0, 0, 0) 8px, #000 8px);\n          -webkit-mask-image: -o-radial-gradient(circle at 86% 86%, rgba(0, 0, 0, 0) 8px, #000 8px);\n          mask-image: -o-radial-gradient(circle at 86% 86%, rgba(0, 0, 0, 0) 8px, #000 8px);\n          -webkit-mask-image: radial-gradient(circle at 86% 86%, rgba(0, 0, 0, 0) 8px, #000 8px);\n          mask-image: radial-gradient(circle at 86% 86%, rgba(0, 0, 0, 0) 8px, #000 8px);\n        }\n\n        .cover {\n          background: radial-gradient(ellipse cover at center, white, black);\n        }\n\n        .contain {\n          background: radial-gradient(contain at center, white, black);\n        }\n\n        .no-div {\n          background: -webkit-gradient(linear, 0 0, 0 100%, from(#000));\n          background: -webkit-linear-gradient(top, #000);\n          background: -o-linear-gradient(top, #000);\n          background: linear-gradient(#000);\n        }\n\n        .background-shorthand {\n          background: #f0f -webkit-radial-gradient(#fff, rgba(0, 0, 0, 0)) 0 0 / cover no-repeat;\n          background: #f0f -o-radial-gradient(#fff, rgba(0, 0, 0, 0)) 0 0 / cover no-repeat;\n          background: #f0f radial-gradient(#fff, rgba(0, 0, 0, 0)) 0 0 / cover no-repeat;\n        }\n\n        .background-advanced {\n          background: url(\"path/to/image.jpg\") 50% / cover;\n          background: -webkit-radial-gradient(at 5px 15px, rgba(214, 168, 18, .7) 0%, rgba(255, 21, 177, .7) 50%, rgba(210, 7, 148, .7) 95%), -webkit-radial-gradient(#fff, rgba(0, 0, 0, 0)), url(\"path/to/image.jpg\") 50% / cover;\n          background: -o-radial-gradient(at 5px 15px, rgba(214, 168, 18, .7) 0%, rgba(255, 21, 177, .7) 50%, rgba(210, 7, 148, .7) 95%), -o-radial-gradient(#fff, rgba(0, 0, 0, 0)), url(\"path/to/image.jpg\") 50% / cover;\n          background: radial-gradient(at 5px 15px, rgba(214, 168, 18, .7) 0%, rgba(255, 21, 177, .7) 50%, rgba(210, 7, 148, .7) 95%), radial-gradient(#fff, rgba(0, 0, 0, 0)), url(\"path/to/image.jpg\") 50% / cover;\n        }\n\n        .multiradial {\n          -webkit-mask-image: -webkit-radial-gradient(circle closest-corner at 100%, #000, rgba(0, 0, 0, 0));\n          -webkit-mask-image: -o-radial-gradient(circle closest-corner at 100%, #000, rgba(0, 0, 0, 0));\n          mask-image: -o-radial-gradient(circle closest-corner at 100%, #000, rgba(0, 0, 0, 0));\n          -webkit-mask-image: radial-gradient(circle closest-corner at 100%, #000, rgba(0, 0, 0, 0));\n          mask-image: radial-gradient(circle closest-corner at 100%, #000, rgba(0, 0, 0, 0));\n        }\n\n        .broken {\n          -webkit-mask-image: -webkit-radial-gradient(#fff, #000);\n          -webkit-mask-image: -o-radial-gradient(#fff, #000);\n          mask-image: -o-radial-gradient(#fff, #000);\n          -webkit-mask-image: radial-gradient(#fff, #000);\n          mask-image: radial-gradient(#fff, #000);\n        }\n\n        .loop {\n          background-image: url(\"https://test.com/lol(test.png\");\n          background-image: url(\"https://test.com/lol(test.png\"), -webkit-radial-gradient(#ff0, #000, #ff0);\n          background-image: url(\"https://test.com/lol(test.png\"), -o-radial-gradient(#ff0, #000, #ff0);\n          background-image: url(\"https://test.com/lol(test.png\"), radial-gradient(#ff0, #000, #ff0);\n        }\n\n        .unitless-zero {\n          background-image: -webkit-gradient(linear, 0 100%, 0 0, from(green), to(#00f));\n          background-image: -webkit-linear-gradient(90deg, green, #00f);\n          background-image: -o-linear-gradient(90deg, green, #00f);\n          background-image: linear-gradient(0deg, green, #00f);\n          background: repeating-linear-gradient(0deg, #00f, red 33.3%);\n        }\n\n        .zero-grad {\n          background: -webkit-gradient(linear, 0 100%, 0 0, from(green), to(#00f));\n          background: -webkit-linear-gradient(90deg, green, #00f);\n          background: -o-linear-gradient(90deg, green, #00f);\n          background: linear-gradient(0grad, green, #00f);\n          background-image: repeating-linear-gradient(0grad, #00f, red 33.3%);\n        }\n\n        .zero-rad, .zero-turn {\n          background: -webkit-gradient(linear, 0 100%, 0 0, from(green), to(#00f));\n          background: -webkit-linear-gradient(90deg, green, #00f);\n          background: -o-linear-gradient(90deg, green, #00f);\n          background: linear-gradient(0deg, green, #00f);\n        }\n      \"#},\n      Browsers {\n        chrome: Some(25 << 16),\n        opera: Some(12 << 16),\n        android: Some(2 << 16 | 3 << 8),\n        ..Browsers::default()\n      },\n    );\n  }\n\n  #[test]\n  fn test_font_face() {\n    minify_test(\n      r#\"@font-face {\n      src: url(\"test.woff\");\n      font-family: \"Helvetica\";\n      font-weight: bold;\n      font-style: italic;\n    }\"#,\n      \"@font-face{src:url(test.woff);font-family:Helvetica;font-weight:700;font-style:italic}\",\n    );\n    minify_test(\"@font-face {src: url(test.woff);}\", \"@font-face{src:url(test.woff)}\");\n    minify_test(\"@font-face {src: local(\\\"Test\\\");}\", \"@font-face{src:local(Test)}\");\n    minify_test(\n      \"@font-face {src: local(\\\"Foo Bar\\\");}\",\n      \"@font-face{src:local(Foo Bar)}\",\n    );\n    minify_test(\"@font-face {src: local(Test);}\", \"@font-face{src:local(Test)}\");\n    minify_test(\"@font-face {src: local(Foo Bar);}\", \"@font-face{src:local(Foo Bar)}\");\n\n    minify_test(\n      \"@font-face {src: url(\\\"test.woff\\\") format(woff);}\",\n      \"@font-face{src:url(test.woff)format(\\\"woff\\\")}\",\n    );\n    minify_test(\n      \"@font-face {src: url(\\\"test.ttc\\\") format(collection), url(test.ttf) format(truetype);}\",\n      \"@font-face{src:url(test.ttc)format(\\\"collection\\\"),url(test.ttf)format(\\\"truetype\\\")}\",\n    );\n    minify_test(\n      \"@font-face {src: url(\\\"test.otf\\\") format(opentype) tech(features-aat);}\",\n      \"@font-face{src:url(test.otf)format(\\\"opentype\\\")tech(features-aat)}\",\n    );\n    minify_test(\n      \"@font-face {src: url(\\\"test.woff\\\") format(woff) tech(color-colrv1);}\",\n      \"@font-face{src:url(test.woff)format(\\\"woff\\\")tech(color-colrv1)}\",\n    );\n    minify_test(\n      \"@font-face {src: url(\\\"test.woff2\\\") format(woff2) tech(variations);}\",\n      \"@font-face{src:url(test.woff2)format(\\\"woff2\\\")tech(variations)}\",\n    );\n    minify_test(\n      \"@font-face {src: url(\\\"test.woff\\\") format(woff) tech(palettes);}\",\n      \"@font-face{src:url(test.woff)format(\\\"woff\\\")tech(palettes)}\",\n    );\n    // multiple tech\n    minify_test(\n      \"@font-face {src: url(\\\"test.woff\\\") format(woff) tech(features-opentype, color-sbix);}\",\n      \"@font-face{src:url(test.woff)format(\\\"woff\\\")tech(features-opentype,color-sbix)}\",\n    );\n    minify_test(\n      \"@font-face {src: url(\\\"test.woff\\\")   format(woff)    tech(incremental, color-svg, features-graphite, features-aat);}\",\n      \"@font-face{src:url(test.woff)format(\\\"woff\\\")tech(incremental,color-svg,features-graphite,features-aat)}\",\n    );\n    // format() function must precede tech() if both are present\n    minify_test(\n      \"@font-face {src: url(\\\"foo.ttf\\\") format(opentype) tech(color-colrv1);}\",\n      \"@font-face{src:url(foo.ttf)format(\\\"opentype\\\")tech(color-colrv1)}\",\n    );\n    // only have tech is valid\n    minify_test(\n      \"@font-face {src: url(\\\"foo.ttf\\\") tech(color-SVG);}\",\n      \"@font-face{src:url(foo.ttf)tech(color-svg)}\",\n    );\n    // CGQAQ: if tech and format both presence, order is matter, tech before format is invalid\n    // but now just return raw token, we don't have strict mode yet.\n    // ref: https://github.com/parcel-bundler/lightningcss/pull/255#issuecomment-1219049998\n    minify_test(\n      \"@font-face {src: url(\\\"foo.ttf\\\") tech(palettes  color-colrv0  variations) format(opentype);}\",\n      \"@font-face{src:url(foo.ttf) tech(palettes color-colrv0 variations) format(opentype)}\",\n    );\n    // TODO(CGQAQ): make this test pass when we have strict mode\n    // ref: https://github.com/web-platform-tests/wpt/blob/9f8a6ccc41aa725e8f51f4f096f686313bb88d8d/css/css-fonts/parsing/font-face-src-tech.html#L45\n    // error_test(\n    //   \"@font-face {src: url(\\\"foo.ttf\\\") tech(features-opentype) format(opentype);}\",\n    //   ParserError::AtRuleBodyInvalid,\n    // );\n    // error_test(\n    //   \"@font-face {src: url(\\\"foo.ttf\\\") tech();}\",\n    //   ParserError::AtRuleBodyInvalid,\n    // );\n    // error_test(\n    //   \"@font-face {src: url(\\\"foo.ttf\\\") tech(\\\"features-opentype\\\");}\",\n    //   ParserError::AtRuleBodyInvalid,\n    // );\n    // error_test(\n    //   \"@font-face {src: url(\\\"foo.ttf\\\") tech(\\\"color-colrv0\\\");}\",\n    //   ParserError::AtRuleBodyInvalid,\n    // );\n    minify_test(\n      \"@font-face {src: local(\\\"\\\") url(\\\"test.woff\\\");}\",\n      \"@font-face{src:local(\\\"\\\") url(test.woff)}\",\n    );\n    minify_test(\"@font-face {font-weight: 200 400}\", \"@font-face{font-weight:200 400}\");\n    minify_test(\"@font-face {font-weight: 400 400}\", \"@font-face{font-weight:400}\");\n    minify_test(\n      \"@font-face {font-stretch: 50% 200%}\",\n      \"@font-face{font-stretch:50% 200%}\",\n    );\n    minify_test(\"@font-face {font-stretch: 50% 50%}\", \"@font-face{font-stretch:50%}\");\n    minify_test(\"@font-face {unicode-range: U+26;}\", \"@font-face{unicode-range:U+26}\");\n    minify_test(\"@font-face {unicode-range: u+26;}\", \"@font-face{unicode-range:U+26}\");\n    minify_test(\n      \"@font-face {unicode-range: U+0-7F;}\",\n      \"@font-face{unicode-range:U+0-7F}\",\n    );\n    minify_test(\n      \"@font-face {unicode-range: U+0025-00FF;}\",\n      \"@font-face{unicode-range:U+25-FF}\",\n    );\n    minify_test(\"@font-face {unicode-range: U+4??;}\", \"@font-face{unicode-range:U+4??}\");\n    minify_test(\n      \"@font-face {unicode-range: U+400-4FF;}\",\n      \"@font-face{unicode-range:U+4??}\",\n    );\n    minify_test(\n      \"@font-face {unicode-range: U+0025-00FF, U+4??;}\",\n      \"@font-face{unicode-range:U+25-FF,U+4??}\",\n    );\n    minify_test(\n      \"@font-face {unicode-range: U+A5, U+4E00-9FFF, U+30??, U+FF00-FF9F;}\",\n      \"@font-face{unicode-range:U+A5,U+4E00-9FFF,U+30??,U+FF00-FF9F}\",\n    );\n    minify_test(\n      \"@font-face {unicode-range: U+????;}\",\n      \"@font-face{unicode-range:U+????}\",\n    );\n    minify_test(\n      \"@font-face {unicode-range: U+0000-FFFF;}\",\n      \"@font-face{unicode-range:U+????}\",\n    );\n    minify_test(\n      \"@font-face {unicode-range: U+10????;}\",\n      \"@font-face{unicode-range:U+10????}\",\n    );\n    minify_test(\n      \"@font-face {unicode-range: U+100000-10FFFF;}\",\n      \"@font-face{unicode-range:U+10????}\",\n    );\n    minify_test(\n      \"@font-face {unicode-range: U+1e1e?;}\",\n      \"@font-face{unicode-range:U+1E1E?}\",\n    );\n    minify_test(\n      \"@font-face {unicode-range: u+????, U+1????, U+10????;}\",\n      \"@font-face{unicode-range:U+????,U+1????,U+10????}\",\n    );\n    minify_test(r#\"\n      @font-face {\n        font-family: Inter;\n        font-style: oblique 0deg 10deg;\n        font-weight: 100 900;\n        src: url(\"../fonts/Inter.var.woff2?v=3.19\") format(\"woff2\");\n        font-display: swap;\n      }\n    \"#, \"@font-face{font-family:Inter;font-style:oblique 0deg 10deg;font-weight:100 900;src:url(../fonts/Inter.var.woff2?v=3.19)format(\\\"woff2\\\");font-display:swap}\");\n    minify_test(r#\"\n    @font-face {\n      font-family: Inter;\n      font-style: oblique 14deg 14deg;\n      font-weight: 100 900;\n      src: url(\"../fonts/Inter.var.woff2?v=3.19\") format(\"woff2\");\n      font-display: swap;\n    }\n  \"#, \"@font-face{font-family:Inter;font-style:oblique;font-weight:100 900;src:url(../fonts/Inter.var.woff2?v=3.19)format(\\\"woff2\\\");font-display:swap}\");\n  }\n\n  #[test]\n  fn test_font_palette_values() {\n    minify_test(\n      r#\"@font-palette-values --Cooler {\n      font-family: Bixa;\n      base-palette: 1;\n      override-colors: 1 #7EB7E4;\n    }\"#,\n      \"@font-palette-values --Cooler{font-family:Bixa;base-palette:1;override-colors:1 #7eb7e4}\",\n    );\n    minify_test(\n      r#\"@font-palette-values --Cooler {\n      font-family: Handover Sans;\n      base-palette: 3;\n      override-colors: 1 rgb(43, 12, 9), 3 lime;\n    }\"#,\n      \"@font-palette-values --Cooler{font-family:Handover Sans;base-palette:3;override-colors:1 #2b0c09,3 #0f0}\",\n    );\n    minify_test(r#\"@font-palette-values --Cooler {\n      font-family: Handover Sans;\n      base-palette: 3;\n      override-colors: 1 rgb(43, 12, 9), 3 var(--highlight);\n    }\"#, \"@font-palette-values --Cooler{font-family:Handover Sans;base-palette:3;override-colors:1 #2b0c09, 3 var(--highlight)}\");\n    prefix_test(\n      r#\"@font-palette-values --Cooler {\n      font-family: Handover Sans;\n      base-palette: 3;\n      override-colors: 1 rgb(43, 12, 9), 3 lch(50.998% 135.363 338);\n    }\"#,\n      indoc! {r#\"@font-palette-values --Cooler {\n      font-family: Handover Sans;\n      base-palette: 3;\n      override-colors: 1 #2b0c09, 3 #ee00be;\n      override-colors: 1 #2b0c09, 3 lch(50.998% 135.363 338);\n    }\n    \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"@font-palette-values --Cooler {\n      font-family: Handover Sans;\n      base-palette: 3;\n      override-colors: 1 var(--foo), 3 lch(50.998% 135.363 338);\n    }\"#,\n      indoc! {r#\"@font-palette-values --Cooler {\n      font-family: Handover Sans;\n      base-palette: 3;\n      override-colors: 1 var(--foo), 3 #ee00be;\n    }\n\n    @supports (color: lab(0% 0 0)) {\n      @font-palette-values --Cooler {\n        font-family: Handover Sans;\n        base-palette: 3;\n        override-colors: 1 var(--foo), 3 lab(50.998% 125.506 -50.7078);\n      }\n    }\n    \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"@supports (color: lab(0% 0 0)) {\n      @font-palette-values --Cooler {\n        font-family: Handover Sans;\n        base-palette: 3;\n        override-colors: 1 var(--foo), 3 lab(50.998% 125.506 -50.7078);\n      }\n    }\"#,\n      indoc! {r#\"@supports (color: lab(0% 0 0)) {\n      @font-palette-values --Cooler {\n        font-family: Handover Sans;\n        base-palette: 3;\n        override-colors: 1 var(--foo), 3 lab(50.998% 125.506 -50.7078);\n      }\n    }\n    \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n    minify_test(\".foo { font-palette: --Custom; }\", \".foo{font-palette:--Custom}\");\n  }\n\n  #[test]\n  fn test_font_feature_values() {\n    // https://github.com/clagnut/TODS/blob/e693d52ad411507b960cf01a9734265e3efab102/tods.css#L116-L142\n    minify_test(\n      r#\"\n@font-feature-values \"Fancy Font Name\" {\n  @styleset { cursive: 1; swoopy: 7 16; }\n  @character-variant { ampersand: 1; capital-q: 2; }\n  @stylistic { two-story-g: 1; straight-y: 2; }\n  @swash { swishy: 1; flowing: 2; }\n  @ornaments { clover: 1; fleuron: 2; }\n  @annotation { circled: 1; boxed: 2; }\n}\n    \"#,\n      r#\"@font-feature-values Fancy Font Name{@styleset{cursive:1;swoopy:7 16}@character-variant{ampersand:1;capital-q:2}@stylistic{two-story-g:1;straight-y:2}@swash{swishy:1;flowing:2}@ornaments{clover:1;fleuron:2}@annotation{circled:1;boxed:2}}\"#,\n    );\n\n    // https://github.com/Sorixelle/srxl.me/blob/4eb4f4a15cb2d21356df24c096d6a819cfdc1a99/public/fonts/inter/inter.css#L201-L222\n    minify_test(\n      r#\"\n@font-feature-values \"Inter\", \"Inter var\", \"Inter var experimental\" {\n  @styleset {\n    open-digits: 1;\n    disambiguation: 2;\n    curved-r: 3;\n    disambiguation-without-zero: 4;\n  }\n\n  @character-variant {\n    alt-one: 1;\n    open-four: 2;\n    open-six: 3;\n    open-nine: 4;\n    lower-l-with-tail: 5;\n    curved-lower-r: 6;\n    german-double-s: 7;\n    upper-i-with-serif: 8;\n    flat-top-three: 9;\n    upper-g-with-spur: 10;\n    single-storey-a: 11;\n  }\n}\n      \"#,\n      r#\"@font-feature-values Inter,Inter var,Inter var experimental{@styleset{open-digits:1;disambiguation:2;curved-r:3;disambiguation-without-zero:4}@character-variant{alt-one:1;open-four:2;open-six:3;open-nine:4;lower-l-with-tail:5;curved-lower-r:6;german-double-s:7;upper-i-with-serif:8;flat-top-three:9;upper-g-with-spur:10;single-storey-a:11}}\"#,\n    );\n\n    // https://github.com/MihailJP/Inconsolata-LGC/blob/7c53cf455787096c93d82d9a51018f12ec39a6e9/Inconsolata-LGC.css#L65-L91\n    minify_test(\n      r#\"\n@font-feature-values \"Inconsolata LGC\" {\n\t@styleset {\n\t\talternative-umlaut: 1;\n\t}\n\t@character-variant {\n\t\tzero-plain: 1 1;\n\t\tzero-dotted: 1 2;\n\t\tzero-longslash: 1 3;\n\t\tr-with-serif: 2 1;\n\t\teng-descender: 3 1;\n\t\teng-uppercase: 3 2;\n\t\tdollar-open: 4 1;\n\t\tdollar-oldstyle: 4 2;\n\t\tdollar-cifrao: 4 2;\n\t\tezh-no-descender: 5 1;\n\t\tezh-reversed-sigma: 5 2;\n\t\ttriangle-text-form: 6 1;\n\t\tel-with-hook-old: 7 1;\n\t\tqa-enlarged-lowercase: 8 1;\n\t\tqa-reversed-p: 8 2;\n\t\tche-with-hook: 9 1;\n\t\tche-with-hook-alt: 9 2;\n\t\tge-with-hook: 10 1;\n\t\tge-with-hook-alt: 10 2;\n\t\tge-with-stroke-and-descender: 11 1;\n\t}\n}\n    \"#,\n      r#\"@font-feature-values Inconsolata LGC{@styleset{alternative-umlaut:1}@character-variant{zero-plain:1 1;zero-dotted:1 2;zero-longslash:1 3;r-with-serif:2 1;eng-descender:3 1;eng-uppercase:3 2;dollar-open:4 1;dollar-oldstyle:4 2;dollar-cifrao:4 2;ezh-no-descender:5 1;ezh-reversed-sigma:5 2;triangle-text-form:6 1;el-with-hook-old:7 1;qa-enlarged-lowercase:8 1;qa-reversed-p:8 2;che-with-hook:9 1;che-with-hook-alt:9 2;ge-with-hook:10 1;ge-with-hook-alt:10 2;ge-with-stroke-and-descender:11 1}}\"#,\n    );\n\n    minify_test(\n      r#\"\n      @font-feature-values \"Fancy Font Name\" {\n        @styleset { cursive: 1; swoopy: 7 16; }\n        @character-variant { ampersand: 1; capital-q: 2; }\n      }\n      \"#,\n      r#\"@font-feature-values Fancy Font Name{@styleset{cursive:1;swoopy:7 16}@character-variant{ampersand:1;capital-q:2}}\"#,\n    );\n    minify_test(\n      r#\"\n      @font-feature-values foo {\n          @swash { pretty: 0; pretty: 1; cool: 2; }\n      }\n      \"#,\n      \"@font-feature-values foo{@swash{pretty:1;cool:2}}\",\n    );\n    minify_test(\n      r#\"\n      @font-feature-values foo {\n          @swash { pretty: 1; }\n          @swash { cool: 2; }\n      }\n      \"#,\n      \"@font-feature-values foo{@swash{pretty:1;cool:2}}\",\n    );\n    minify_test(\n      r#\"\n      @font-feature-values foo {\n          @swash { pretty: 1; }\n      }\n      @font-feature-values foo {\n          @swash { cool: 2; }\n      }\n      \"#,\n      \"@font-feature-values foo{@swash{pretty:1;cool:2}}\",\n    );\n  }\n\n  #[test]\n  fn test_page_rule() {\n    minify_test(\"@page {margin: 0.5cm}\", \"@page{margin:.5cm}\");\n    minify_test(\"@page :left {margin: 0.5cm}\", \"@page:left{margin:.5cm}\");\n    minify_test(\"@page :right {margin: 0.5cm}\", \"@page:right{margin:.5cm}\");\n    minify_test(\n      \"@page LandscapeTable {margin: 0.5cm}\",\n      \"@page LandscapeTable{margin:.5cm}\",\n    );\n    minify_test(\n      \"@page CompanyLetterHead:first {margin: 0.5cm}\",\n      \"@page CompanyLetterHead:first{margin:.5cm}\",\n    );\n    minify_test(\"@page:first {margin: 0.5cm}\", \"@page:first{margin:.5cm}\");\n    minify_test(\"@page :blank:first {margin: 0.5cm}\", \"@page:blank:first{margin:.5cm}\");\n    minify_test(\"@page toc, index {margin: 0.5cm}\", \"@page toc,index{margin:.5cm}\");\n    minify_test(\n      r#\"\n    @page :right {\n      @bottom-left {\n        margin: 10pt;\n      }\n    }\n    \"#,\n      \"@page:right{@bottom-left{margin:10pt}}\",\n    );\n    minify_test(\n      r#\"\n    @page :right {\n      margin: 1in;\n\n      @bottom-left {\n        margin: 10pt;\n      }\n    }\n    \"#,\n      \"@page:right{margin:1in;@bottom-left{margin:10pt}}\",\n    );\n\n    test(\n      r#\"\n    @page :right {\n      @bottom-left {\n        margin: 10pt;\n      }\n    }\n    \"#,\n      indoc! {r#\"\n      @page :right {\n        @bottom-left {\n          margin: 10pt;\n        }\n      }\n      \"#},\n    );\n\n    test(\n      r#\"\n    @page :right {\n      margin: 1in;\n\n      @bottom-left-corner { content: \"Foo\"; }\n      @bottom-right-corner { content: \"Bar\"; }\n    }\n    \"#,\n      indoc! {r#\"\n      @page :right {\n        margin: 1in;\n\n        @bottom-left-corner {\n          content: \"Foo\";\n        }\n\n        @bottom-right-corner {\n          content: \"Bar\";\n        }\n      }\n      \"#},\n    );\n\n    error_test(\n      r#\"\n      @page {\n        @foo {\n          margin: 1in;\n        }\n      }\n      \"#,\n      ParserError::AtRuleInvalid(\"foo\".into()),\n    );\n\n    error_test(\n      r#\"\n      @page {\n        @top-left-corner {\n          @bottom-left {\n            margin: 1in;\n          }\n        }\n      }\n      \"#,\n      ParserError::AtRuleInvalid(\"bottom-left\".into()),\n    );\n  }\n\n  #[test]\n  fn test_supports_rule() {\n    test(\n      r#\"\n      @supports (foo: bar) {\n        .test {\n          foo: bar;\n        }\n      }\n    \"#,\n      indoc! { r#\"\n      @supports (foo: bar) {\n        .test {\n          foo: bar;\n        }\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      @supports not (foo: bar) {\n        .test {\n          foo: bar;\n        }\n      }\n    \"#,\n      indoc! { r#\"\n      @supports not (foo: bar) {\n        .test {\n          foo: bar;\n        }\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      @supports (foo: bar) or (bar: baz) {\n        .test {\n          foo: bar;\n        }\n      }\n    \"#,\n      indoc! { r#\"\n      @supports (foo: bar) or (bar: baz) {\n        .test {\n          foo: bar;\n        }\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      @supports (((foo: bar) or (bar: baz))) {\n        .test {\n          foo: bar;\n        }\n      }\n    \"#,\n      indoc! { r#\"\n      @supports (foo: bar) or (bar: baz) {\n        .test {\n          foo: bar;\n        }\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      @supports (foo: bar) and (bar: baz) {\n        .test {\n          foo: bar;\n        }\n      }\n    \"#,\n      indoc! { r#\"\n      @supports (foo: bar) and (bar: baz) {\n        .test {\n          foo: bar;\n        }\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      @supports (((foo: bar) and (bar: baz))) {\n        .test {\n          foo: bar;\n        }\n      }\n    \"#,\n      indoc! { r#\"\n      @supports (foo: bar) and (bar: baz) {\n        .test {\n          foo: bar;\n        }\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      @supports (foo: bar) and (((bar: baz) or (test: foo))) {\n        .test {\n          foo: bar;\n        }\n      }\n    \"#,\n      indoc! { r#\"\n      @supports (foo: bar) and ((bar: baz) or (test: foo)) {\n        .test {\n          foo: bar;\n        }\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      @supports not (((foo: bar) and (bar: baz))) {\n        .test {\n          foo: bar;\n        }\n      }\n    \"#,\n      indoc! { r#\"\n      @supports not ((foo: bar) and (bar: baz)) {\n        .test {\n          foo: bar;\n        }\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      @supports selector(a > b) {\n        .test {\n          foo: bar;\n        }\n      }\n    \"#,\n      indoc! { r#\"\n      @supports selector(a > b) {\n        .test {\n          foo: bar;\n        }\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      @supports unknown(test) {\n        .test {\n          foo: bar;\n        }\n      }\n    \"#,\n      indoc! { r#\"\n      @supports unknown(test) {\n        .test {\n          foo: bar;\n        }\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      @supports (unknown) {\n        .test {\n          foo: bar;\n        }\n      }\n    \"#,\n      indoc! { r#\"\n      @supports (unknown) {\n        .test {\n          foo: bar;\n        }\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      @supports (display: grid) and (not (display: inline-grid)) {\n        .test {\n          foo: bar;\n        }\n      }\n    \"#,\n      indoc! { r#\"\n      @supports (display: grid) and (not (display: inline-grid)) {\n        .test {\n          foo: bar;\n        }\n      }\n    \"#},\n    );\n    prefix_test(\n      r#\"\n      @supports (backdrop-filter: blur(10px)) {\n        div {\n          backdrop-filter: blur(10px);\n        }\n      }\n    \"#,\n      indoc! { r#\"\n      @supports ((-webkit-backdrop-filter: blur(10px)) or (backdrop-filter: blur(10px))) {\n        div {\n          -webkit-backdrop-filter: blur(10px);\n          backdrop-filter: blur(10px);\n        }\n      }\n    \"#},\n      Browsers {\n        safari: Some(14 << 16),\n        ..Default::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      @supports ((-webkit-backdrop-filter: blur(10px)) or (backdrop-filter: blur(10px))) {\n        div {\n          backdrop-filter: blur(10px);\n        }\n      }\n    \"#,\n      indoc! { r#\"\n      @supports ((-webkit-backdrop-filter: blur(10px)) or (backdrop-filter: blur(10px))) {\n        div {\n          -webkit-backdrop-filter: blur(10px);\n          backdrop-filter: blur(10px);\n        }\n      }\n    \"#},\n      Browsers {\n        safari: Some(14 << 16),\n        ..Default::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      @supports ((-webkit-backdrop-filter: blur(20px)) or (backdrop-filter: blur(10px))) {\n        div {\n          backdrop-filter: blur(10px);\n        }\n      }\n    \"#,\n      indoc! { r#\"\n      @supports ((-webkit-backdrop-filter: blur(20px))) or ((-webkit-backdrop-filter: blur(10px)) or (backdrop-filter: blur(10px))) {\n        div {\n          -webkit-backdrop-filter: blur(10px);\n          backdrop-filter: blur(10px);\n        }\n      }\n    \"#},\n      Browsers {\n        safari: Some(14 << 16),\n        ..Default::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      @supports ((-webkit-backdrop-filter: blur(10px)) or (backdrop-filter: blur(10px))) {\n        div {\n          backdrop-filter: blur(10px);\n        }\n      }\n    \"#,\n      indoc! { r#\"\n      @supports (backdrop-filter: blur(10px)) {\n        div {\n          backdrop-filter: blur(10px);\n        }\n      }\n    \"#},\n      Browsers {\n        chrome: Some(80 << 16),\n        ..Default::default()\n      },\n    );\n    minify_test(\n      r#\"\n      @supports (width: calc(10px * 2)) {\n        .test {\n          width: calc(10px * 2);\n        }\n      }\n    \"#,\n      \"@supports (width:calc(10px * 2)){.test{width:20px}}\",\n    );\n    minify_test(\n      r#\"\n      @supports (color: hsl(0deg, 0%, 0%)) {\n        .test {\n          color: hsl(0deg, 0%, 0%);\n        }\n      }\n    \"#,\n      \"@supports (color:hsl(0deg, 0%, 0%)){.test{color:#000}}\",\n    );\n  }\n\n  #[test]\n  fn test_counter_style() {\n    test(\n      r#\"\n      @counter-style circled-alpha {\n        system: fixed;\n        symbols: Ⓐ Ⓑ Ⓒ;\n        suffix: \" \";\n      }\n    \"#,\n      indoc! { r#\"\n      @counter-style circled-alpha {\n        system: fixed;\n        symbols: Ⓐ Ⓑ Ⓒ;\n        suffix: \" \";\n      }\n    \"#},\n    );\n  }\n\n  #[test]\n  fn test_namespace() {\n    minify_test(\n      \"@namespace url(http://toto.example.org);\",\n      \"@namespace \\\"http://toto.example.org\\\";\",\n    );\n    minify_test(\n      \"@namespace \\\"http://toto.example.org\\\";\",\n      \"@namespace \\\"http://toto.example.org\\\";\",\n    );\n    minify_test(\n      \"@namespace toto \\\"http://toto.example.org\\\";\",\n      \"@namespace toto \\\"http://toto.example.org\\\";\",\n    );\n    minify_test(\n      \"@namespace toto url(http://toto.example.org);\",\n      \"@namespace toto \\\"http://toto.example.org\\\";\",\n    );\n\n    test(\n      r#\"\n      @namespace \"http://example.com/foo\";\n\n      x {\n        color: red;\n      }\n    \"#,\n      indoc! {r#\"\n      @namespace \"http://example.com/foo\";\n\n      x {\n        color: red;\n      }\n    \"#},\n    );\n\n    test(\n      r#\"\n      @namespace toto \"http://toto.example.org\";\n\n      toto|x {\n        color: red;\n      }\n\n      [toto|att=val] {\n        color: blue\n      }\n    \"#,\n      indoc! {r#\"\n      @namespace toto \"http://toto.example.org\";\n\n      toto|x {\n        color: red;\n      }\n\n      [toto|att=\"val\"] {\n        color: #00f;\n      }\n    \"#},\n    );\n\n    test(\n      r#\"\n      @namespace \"http://example.com/foo\";\n\n      |x {\n        color: red;\n      }\n\n      [|att=val] {\n        color: blue\n      }\n    \"#,\n      indoc! {r#\"\n      @namespace \"http://example.com/foo\";\n\n      |x {\n        color: red;\n      }\n\n      [att=\"val\"] {\n        color: #00f;\n      }\n    \"#},\n    );\n\n    test(\n      r#\"\n      @namespace \"http://example.com/foo\";\n\n      *|x {\n        color: red;\n      }\n\n      [*|att=val] {\n        color: blue\n      }\n    \"#,\n      indoc! {r#\"\n      @namespace \"http://example.com/foo\";\n\n      *|x {\n        color: red;\n      }\n\n      [*|att=\"val\"] {\n        color: #00f;\n      }\n    \"#},\n    );\n\n    error_test(\n      \".foo { color: red } @namespace \\\"http://example.com/foo\\\";\",\n      ParserError::UnexpectedNamespaceRule,\n    );\n  }\n\n  #[test]\n  fn test_import() {\n    minify_test(\"@import url(foo.css);\", \"@import \\\"foo.css\\\";\");\n    minify_test(\"@import \\\"foo.css\\\";\", \"@import \\\"foo.css\\\";\");\n    minify_test(\"@import url(foo.css) print;\", \"@import \\\"foo.css\\\" print;\");\n    minify_test(\"@import \\\"foo.css\\\" print;\", \"@import \\\"foo.css\\\" print;\");\n    minify_test(\n      \"@import \\\"foo.css\\\" screen and (orientation: landscape);\",\n      \"@import \\\"foo.css\\\" screen and (orientation:landscape);\",\n    );\n    minify_test(\n      \"@import url(foo.css) supports(display: flex);\",\n      \"@import \\\"foo.css\\\" supports(display:flex);\",\n    );\n    minify_test(\n      \"@import url(foo.css) supports(display: flex) print;\",\n      \"@import \\\"foo.css\\\" supports(display:flex) print;\",\n    );\n    minify_test(\n      \"@import url(foo.css) supports(not (display: flex));\",\n      \"@import \\\"foo.css\\\" supports(not (display:flex));\",\n    );\n    minify_test(\n      \"@import url(foo.css) supports((display: flex));\",\n      \"@import \\\"foo.css\\\" supports(display:flex);\",\n    );\n    minify_test(\"@charset \\\"UTF-8\\\"; @import url(foo.css);\", \"@import \\\"foo.css\\\";\");\n    minify_test(\"@layer foo; @import url(foo.css);\", \"@layer foo;@import \\\"foo.css\\\";\");\n    error_test(\n      \".foo { color: red } @import url(bar.css);\",\n      ParserError::UnexpectedImportRule,\n    );\n    error_test(\n      \"@namespace \\\"http://example.com/foo\\\"; @import url(bar.css);\",\n      ParserError::UnexpectedImportRule,\n    );\n    error_test(\n      \"@media print { .foo { color: red }} @import url(bar.css);\",\n      ParserError::UnexpectedImportRule,\n    );\n    error_test(\n      \"@layer foo; @import url(foo.css); @layer bar; @import url(bar.css)\",\n      ParserError::UnexpectedImportRule,\n    );\n    let warnings = error_recovery_test(\"@import './actual-styles.css';\");\n    assert_eq!(warnings, vec![]);\n  }\n\n  #[test]\n  fn test_prefixes() {\n    prefix_test(\n      r#\"\n      .foo {\n        -webkit-transition: opacity 200ms;\n        -moz-transition: opacity 200ms;\n        transition: opacity 200ms;\n      }\n      \"#,\n      indoc! {r#\"\n      .foo {\n        transition: opacity .2s;\n      }\n      \"#},\n      Browsers {\n        chrome: Some(95 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo{transition:opacity 200ms}\",\n      indoc! {r#\"\n      .foo {\n        -webkit-transition: opacity .2s;\n        -moz-transition: opacity .2s;\n        transition: opacity .2s;\n      }\n      \"#},\n      Browsers {\n        safari: Some(5 << 16),\n        firefox: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n  }\n\n  #[test]\n  fn test_display() {\n    minify_test(\".foo { display: block }\", \".foo{display:block}\");\n    minify_test(\".foo { display: block flow }\", \".foo{display:block}\");\n    minify_test(\".foo { display: flow-root }\", \".foo{display:flow-root}\");\n    minify_test(\".foo { display: block flow-root }\", \".foo{display:flow-root}\");\n    minify_test(\".foo { display: inline }\", \".foo{display:inline}\");\n    minify_test(\".foo { display: inline flow }\", \".foo{display:inline}\");\n    minify_test(\".foo { display: inline-block }\", \".foo{display:inline-block}\");\n    minify_test(\".foo { display: inline flow-root }\", \".foo{display:inline-block}\");\n    minify_test(\".foo { display: run-in }\", \".foo{display:run-in}\");\n    minify_test(\".foo { display: run-in flow }\", \".foo{display:run-in}\");\n    minify_test(\".foo { display: list-item }\", \".foo{display:list-item}\");\n    minify_test(\".foo { display: block flow list-item }\", \".foo{display:list-item}\");\n    minify_test(\".foo { display: inline list-item }\", \".foo{display:inline list-item}\");\n    minify_test(\n      \".foo { display: inline flow list-item }\",\n      \".foo{display:inline list-item}\",\n    );\n    minify_test(\".foo { display: flex }\", \".foo{display:flex}\");\n    minify_test(\".foo { display: block flex }\", \".foo{display:flex}\");\n    minify_test(\".foo { display: inline-flex }\", \".foo{display:inline-flex}\");\n    minify_test(\".foo { display: inline flex }\", \".foo{display:inline-flex}\");\n    minify_test(\".foo { display: grid }\", \".foo{display:grid}\");\n    minify_test(\".foo { display: block grid }\", \".foo{display:grid}\");\n    minify_test(\".foo { display: inline-grid }\", \".foo{display:inline-grid}\");\n    minify_test(\".foo { display: inline grid }\", \".foo{display:inline-grid}\");\n    minify_test(\".foo { display: ruby }\", \".foo{display:ruby}\");\n    minify_test(\".foo { display: inline ruby }\", \".foo{display:ruby}\");\n    minify_test(\".foo { display: block ruby }\", \".foo{display:block ruby}\");\n    minify_test(\".foo { display: table }\", \".foo{display:table}\");\n    minify_test(\".foo { display: block table }\", \".foo{display:table}\");\n    minify_test(\".foo { display: inline-table }\", \".foo{display:inline-table}\");\n    minify_test(\".foo { display: inline table }\", \".foo{display:inline-table}\");\n    minify_test(\".foo { display: table-row-group }\", \".foo{display:table-row-group}\");\n    minify_test(\".foo { display: contents }\", \".foo{display:contents}\");\n    minify_test(\".foo { display: none }\", \".foo{display:none}\");\n    minify_test(\".foo { display: -webkit-flex }\", \".foo{display:-webkit-flex}\");\n    minify_test(\".foo { display: -ms-flexbox }\", \".foo{display:-ms-flexbox}\");\n    minify_test(\".foo { display: -webkit-box }\", \".foo{display:-webkit-box}\");\n    minify_test(\".foo { display: -moz-box }\", \".foo{display:-moz-box}\");\n    minify_test(\n      \".foo { display: -webkit-flex; display: -moz-box; display: flex }\",\n      \".foo{display:-webkit-flex;display:-moz-box;display:flex}\",\n    );\n    minify_test(\n      \".foo { display: -webkit-flex; display: flex; display: -moz-box }\",\n      \".foo{display:-webkit-flex;display:flex;display:-moz-box}\",\n    );\n    minify_test(\".foo { display: flex; display: grid }\", \".foo{display:grid}\");\n    minify_test(\n      \".foo { display: -webkit-inline-flex; display: -moz-inline-box; display: inline-flex }\",\n      \".foo{display:-webkit-inline-flex;display:-moz-inline-box;display:inline-flex}\",\n    );\n    minify_test(\n      \".foo { display: flex; display: var(--grid); }\",\n      \".foo{display:flex;display:var(--grid)}\",\n    );\n    prefix_test(\n      \".foo{ display: flex }\",\n      indoc! {r#\"\n      .foo {\n        display: -webkit-box;\n        display: -moz-box;\n        display: -webkit-flex;\n        display: -ms-flexbox;\n        display: flex;\n      }\n      \"#},\n      Browsers {\n        safari: Some(4 << 16),\n        firefox: Some(14 << 16),\n        ie: Some(10 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      \".foo{ display: flex; display: -webkit-box; }\",\n      indoc! {r#\"\n      .foo {\n        display: -webkit-box;\n      }\n      \"#},\n      Browsers {\n        safari: Some(4 << 16),\n        firefox: Some(14 << 16),\n        ie: Some(10 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      \".foo{ display: -webkit-box; display: flex; }\",\n      indoc! {r#\"\n      .foo {\n        display: -webkit-box;\n        display: -moz-box;\n        display: -webkit-flex;\n        display: -ms-flexbox;\n        display: flex;\n      }\n      \"#},\n      Browsers {\n        safari: Some(4 << 16),\n        firefox: Some(14 << 16),\n        ie: Some(10 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        display: -webkit-box;\n        display: -moz-box;\n        display: -webkit-flex;\n        display: -ms-flexbox;\n        display: flex;\n      }\n      \"#,\n      indoc! {r#\"\n      .foo {\n        display: flex;\n      }\n      \"#},\n      Browsers {\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        display: -webkit-box;\n        display: flex;\n        display: -moz-box;\n        display: -webkit-flex;\n        display: -ms-flexbox;\n      }\n      \"#,\n      indoc! {r#\"\n      .foo {\n        display: -moz-box;\n        display: -webkit-flex;\n        display: -ms-flexbox;\n      }\n      \"#},\n      Browsers {\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      \".foo{ display: inline-flex }\",\n      indoc! {r#\"\n      .foo {\n        display: -webkit-inline-box;\n        display: -moz-inline-box;\n        display: -webkit-inline-flex;\n        display: -ms-inline-flexbox;\n        display: inline-flex;\n      }\n      \"#},\n      Browsers {\n        safari: Some(4 << 16),\n        firefox: Some(14 << 16),\n        ie: Some(10 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        display: -webkit-inline-box;\n        display: -moz-inline-box;\n        display: -webkit-inline-flex;\n        display: -ms-inline-flexbox;\n        display: inline-flex;\n      }\n      \"#,\n      indoc! {r#\"\n      .foo {\n        display: inline-flex;\n      }\n      \"#},\n      Browsers {\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n  }\n\n  #[test]\n  fn test_visibility() {\n    minify_test(\".foo { visibility: visible }\", \".foo{visibility:visible}\");\n    minify_test(\".foo { visibility: hidden }\", \".foo{visibility:hidden}\");\n    minify_test(\".foo { visibility: collapse }\", \".foo{visibility:collapse}\");\n    minify_test(\".foo { visibility: Visible }\", \".foo{visibility:visible}\");\n  }\n\n  #[test]\n  fn test_text_transform() {\n    minify_test(\".foo { text-transform: uppercase }\", \".foo{text-transform:uppercase}\");\n    minify_test(\".foo { text-transform: lowercase }\", \".foo{text-transform:lowercase}\");\n    minify_test(\".foo { text-transform: capitalize }\", \".foo{text-transform:capitalize}\");\n    minify_test(\".foo { text-transform: none }\", \".foo{text-transform:none}\");\n    minify_test(\".foo { text-transform: full-width }\", \".foo{text-transform:full-width}\");\n    minify_test(\n      \".foo { text-transform: full-size-kana }\",\n      \".foo{text-transform:full-size-kana}\",\n    );\n    minify_test(\n      \".foo { text-transform: uppercase full-width }\",\n      \".foo{text-transform:uppercase full-width}\",\n    );\n    minify_test(\n      \".foo { text-transform: full-width uppercase }\",\n      \".foo{text-transform:uppercase full-width}\",\n    );\n    minify_test(\n      \".foo { text-transform: uppercase full-width full-size-kana }\",\n      \".foo{text-transform:uppercase full-width full-size-kana}\",\n    );\n    minify_test(\n      \".foo { text-transform: full-width uppercase full-size-kana }\",\n      \".foo{text-transform:uppercase full-width full-size-kana}\",\n    );\n  }\n\n  #[test]\n  fn test_whitespace() {\n    minify_test(\".foo { white-space: normal }\", \".foo{white-space:normal}\");\n    minify_test(\".foo { white-space: pre }\", \".foo{white-space:pre}\");\n    minify_test(\".foo { white-space: nowrap }\", \".foo{white-space:nowrap}\");\n    minify_test(\".foo { white-space: pre-wrap }\", \".foo{white-space:pre-wrap}\");\n    minify_test(\".foo { white-space: break-spaces }\", \".foo{white-space:break-spaces}\");\n    minify_test(\".foo { white-space: pre-line }\", \".foo{white-space:pre-line}\");\n    minify_test(\".foo { white-space: NoWrAp }\", \".foo{white-space:nowrap}\");\n  }\n\n  #[test]\n  fn test_tab_size() {\n    minify_test(\".foo { tab-size: 8 }\", \".foo{tab-size:8}\");\n    minify_test(\".foo { tab-size: 4px }\", \".foo{tab-size:4px}\");\n    minify_test(\".foo { -moz-tab-size: 4px }\", \".foo{-moz-tab-size:4px}\");\n    minify_test(\".foo { -o-tab-size: 4px }\", \".foo{-o-tab-size:4px}\");\n    prefix_test(\n      \".foo{ tab-size: 4 }\",\n      indoc! {r#\"\n      .foo {\n        -moz-tab-size: 4;\n        -o-tab-size: 4;\n        tab-size: 4;\n      }\n      \"#},\n      Browsers {\n        safari: Some(8 << 16),\n        firefox: Some(50 << 16),\n        opera: Some(12 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        -moz-tab-size: 4;\n        -o-tab-size: 4;\n        tab-size: 4;\n      }\n      \"#,\n      indoc! {r#\"\n      .foo {\n        tab-size: 4;\n      }\n      \"#},\n      Browsers {\n        safari: Some(8 << 16),\n        firefox: Some(94 << 16),\n        opera: Some(30 << 16),\n        ..Browsers::default()\n      },\n    );\n  }\n\n  #[test]\n  fn test_word_break() {\n    minify_test(\".foo { word-break: normal }\", \".foo{word-break:normal}\");\n    minify_test(\".foo { word-break: keep-all }\", \".foo{word-break:keep-all}\");\n    minify_test(\".foo { word-break: break-all }\", \".foo{word-break:break-all}\");\n    minify_test(\".foo { word-break: break-word }\", \".foo{word-break:break-word}\");\n  }\n\n  #[test]\n  fn test_line_break() {\n    minify_test(\".foo { line-break: auto }\", \".foo{line-break:auto}\");\n    minify_test(\".foo { line-break: Loose }\", \".foo{line-break:loose}\");\n    minify_test(\".foo { line-break: anywhere }\", \".foo{line-break:anywhere}\");\n  }\n\n  #[test]\n  fn test_wrap() {\n    minify_test(\".foo { overflow-wrap: nOrmal }\", \".foo{overflow-wrap:normal}\");\n    minify_test(\".foo { overflow-wrap: break-Word }\", \".foo{overflow-wrap:break-word}\");\n    minify_test(\".foo { overflow-wrap: Anywhere }\", \".foo{overflow-wrap:anywhere}\");\n    minify_test(\".foo { word-wrap: Normal }\", \".foo{word-wrap:normal}\");\n    minify_test(\".foo { word-wrap: Break-wOrd }\", \".foo{word-wrap:break-word}\");\n    minify_test(\".foo { word-wrap: Anywhere }\", \".foo{word-wrap:anywhere}\");\n  }\n\n  #[test]\n  fn test_hyphens() {\n    minify_test(\".foo { hyphens: manual }\", \".foo{hyphens:manual}\");\n    minify_test(\".foo { hyphens: auto }\", \".foo{hyphens:auto}\");\n    minify_test(\".foo { hyphens: none }\", \".foo{hyphens:none}\");\n    minify_test(\".foo { -webkit-hyphens: manual }\", \".foo{-webkit-hyphens:manual}\");\n    minify_test(\".foo { -moz-hyphens: manual }\", \".foo{-moz-hyphens:manual}\");\n    minify_test(\".foo { -ms-hyphens: manual }\", \".foo{-ms-hyphens:manual}\");\n    prefix_test(\n      \".foo{ hyphens: manual }\",\n      indoc! {r#\"\n      .foo {\n        -webkit-hyphens: manual;\n        -moz-hyphens: manual;\n        -ms-hyphens: manual;\n        hyphens: manual;\n      }\n      \"#},\n      Browsers {\n        safari: Some(14 << 16),\n        firefox: Some(40 << 16),\n        ie: Some(10 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        -webkit-hyphens: manual;\n        -moz-hyphens: manual;\n        -ms-hyphens: manual;\n        hyphens: manual;\n      }\n      \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-hyphens: manual;\n        hyphens: manual;\n      }\n      \"#},\n      Browsers {\n        safari: Some(14 << 16),\n        chrome: Some(88 << 16),\n        firefox: Some(88 << 16),\n        edge: Some(79 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        -webkit-hyphens: manual;\n        -moz-hyphens: manual;\n        -ms-hyphens: manual;\n        hyphens: manual;\n      }\n      \"#,\n      indoc! {r#\"\n      .foo {\n        hyphens: manual;\n      }\n      \"#},\n      Browsers {\n        chrome: Some(88 << 16),\n        firefox: Some(88 << 16),\n        edge: Some(79 << 16),\n        ..Browsers::default()\n      },\n    );\n  }\n\n  #[test]\n  fn test_text_align() {\n    minify_test(\".foo { text-align: left }\", \".foo{text-align:left}\");\n    minify_test(\".foo { text-align: Left }\", \".foo{text-align:left}\");\n    minify_test(\".foo { text-align: END }\", \".foo{text-align:end}\");\n    minify_test(\".foo { text-align: left }\", \".foo{text-align:left}\");\n\n    prefix_test(\n      r#\"\n      .foo {\n        text-align: start;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo:not(:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        text-align: left;\n      }\n\n      .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        text-align: right;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(2 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        text-align: end;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo:not(:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        text-align: right;\n      }\n\n      .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        text-align: left;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(2 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        text-align: start;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        text-align: start;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo > .bar {\n        text-align: start;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo > .bar:not(:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        text-align: left;\n      }\n\n      .foo > .bar:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        text-align: right;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(2 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo:after {\n        text-align: start;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo:not(:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))):after {\n        text-align: left;\n      }\n\n      .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)):after {\n        text-align: right;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(2 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo:hover {\n        text-align: start;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo:hover:not(:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        text-align: left;\n      }\n\n      .foo:hover:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        text-align: right;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(2 << 16),\n        ..Browsers::default()\n      },\n    );\n  }\n\n  #[test]\n  fn test_text_align_last() {\n    minify_test(\".foo { text-align-last: left }\", \".foo{text-align-last:left}\");\n    minify_test(\".foo { text-align-last: justify }\", \".foo{text-align-last:justify}\");\n    prefix_test(\n      \".foo{ text-align-last: left }\",\n      indoc! {r#\"\n      .foo {\n        -moz-text-align-last: left;\n        text-align-last: left;\n      }\n      \"#},\n      Browsers {\n        firefox: Some(40 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        -moz-text-align-last: left;\n        text-align-last: left;\n      }\n      \"#,\n      indoc! {r#\"\n      .foo {\n        text-align-last: left;\n      }\n      \"#},\n      Browsers {\n        firefox: Some(88 << 16),\n        ..Browsers::default()\n      },\n    );\n  }\n\n  #[test]\n  fn test_text_justify() {\n    minify_test(\".foo { text-justify: auto }\", \".foo{text-justify:auto}\");\n    minify_test(\".foo { text-justify: inter-word }\", \".foo{text-justify:inter-word}\");\n  }\n\n  #[test]\n  fn test_word_spacing() {\n    minify_test(\".foo { word-spacing: normal }\", \".foo{word-spacing:normal}\");\n    minify_test(\".foo { word-spacing: 3px }\", \".foo{word-spacing:3px}\");\n  }\n\n  #[test]\n  fn test_letter_spacing() {\n    minify_test(\".foo { letter-spacing: normal }\", \".foo{letter-spacing:normal}\");\n    minify_test(\".foo { letter-spacing: 3px }\", \".foo{letter-spacing:3px}\");\n  }\n\n  #[test]\n  fn test_text_indent() {\n    minify_test(\".foo { text-indent: 20px }\", \".foo{text-indent:20px}\");\n    minify_test(\".foo { text-indent: 10% }\", \".foo{text-indent:10%}\");\n    minify_test(\".foo { text-indent: 3em hanging }\", \".foo{text-indent:3em hanging}\");\n    minify_test(\".foo { text-indent: 3em each-line }\", \".foo{text-indent:3em each-line}\");\n    minify_test(\n      \".foo { text-indent: 3em hanging each-line }\",\n      \".foo{text-indent:3em hanging each-line}\",\n    );\n    minify_test(\n      \".foo { text-indent: 3em each-line hanging }\",\n      \".foo{text-indent:3em hanging each-line}\",\n    );\n    minify_test(\n      \".foo { text-indent: each-line 3em hanging }\",\n      \".foo{text-indent:3em hanging each-line}\",\n    );\n    minify_test(\n      \".foo { text-indent: each-line hanging 3em }\",\n      \".foo{text-indent:3em hanging each-line}\",\n    );\n  }\n\n  #[test]\n  fn test_text_size_adjust() {\n    minify_test(\".foo { text-size-adjust: none }\", \".foo{text-size-adjust:none}\");\n    minify_test(\".foo { text-size-adjust: auto }\", \".foo{text-size-adjust:auto}\");\n    minify_test(\".foo { text-size-adjust: 80% }\", \".foo{text-size-adjust:80%}\");\n    prefix_test(\n      r#\"\n      .foo {\n        text-size-adjust: none;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-text-size-adjust: none;\n        -moz-text-size-adjust: none;\n        -ms-text-size-adjust: none;\n        text-size-adjust: none;\n      }\n    \"#},\n      Browsers {\n        ios_saf: Some(16 << 16),\n        edge: Some(15 << 16),\n        firefox: Some(20 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        -webkit-text-size-adjust: none;\n        -moz-text-size-adjust: none;\n        -ms-text-size-adjust: none;\n        text-size-adjust: none;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        text-size-adjust: none;\n      }\n    \"#},\n      Browsers {\n        chrome: Some(110 << 16),\n        ..Browsers::default()\n      },\n    );\n  }\n\n  #[test]\n  fn test_text_decoration() {\n    minify_test(\".foo { text-decoration-line: none }\", \".foo{text-decoration-line:none}\");\n    minify_test(\n      \".foo { text-decoration-line: underline }\",\n      \".foo{text-decoration-line:underline}\",\n    );\n    minify_test(\n      \".foo { text-decoration-line: overline }\",\n      \".foo{text-decoration-line:overline}\",\n    );\n    minify_test(\n      \".foo { text-decoration-line: line-through }\",\n      \".foo{text-decoration-line:line-through}\",\n    );\n    minify_test(\n      \".foo { text-decoration-line: blink }\",\n      \".foo{text-decoration-line:blink}\",\n    );\n    minify_test(\n      \".foo { text-decoration-line: underline overline }\",\n      \".foo{text-decoration-line:underline overline}\",\n    );\n    minify_test(\n      \".foo { text-decoration-line: overline underline }\",\n      \".foo{text-decoration-line:underline overline}\",\n    );\n    minify_test(\n      \".foo { text-decoration-line: overline line-through underline }\",\n      \".foo{text-decoration-line:underline overline line-through}\",\n    );\n    minify_test(\n      \".foo { text-decoration-line: spelling-error }\",\n      \".foo{text-decoration-line:spelling-error}\",\n    );\n    minify_test(\n      \".foo { text-decoration-line: grammar-error }\",\n      \".foo{text-decoration-line:grammar-error}\",\n    );\n    minify_test(\n      \".foo { -webkit-text-decoration-line: overline underline }\",\n      \".foo{-webkit-text-decoration-line:underline overline}\",\n    );\n    minify_test(\n      \".foo { -moz-text-decoration-line: overline underline }\",\n      \".foo{-moz-text-decoration-line:underline overline}\",\n    );\n\n    minify_test(\n      \".foo { text-decoration-style: solid }\",\n      \".foo{text-decoration-style:solid}\",\n    );\n    minify_test(\n      \".foo { text-decoration-style: dotted }\",\n      \".foo{text-decoration-style:dotted}\",\n    );\n    minify_test(\n      \".foo { -webkit-text-decoration-style: solid }\",\n      \".foo{-webkit-text-decoration-style:solid}\",\n    );\n\n    minify_test(\n      \".foo { text-decoration-color: yellow }\",\n      \".foo{text-decoration-color:#ff0}\",\n    );\n    minify_test(\n      \".foo { -webkit-text-decoration-color: yellow }\",\n      \".foo{-webkit-text-decoration-color:#ff0}\",\n    );\n\n    minify_test(\".foo { text-decoration: none }\", \".foo{text-decoration:none}\");\n    minify_test(\n      \".foo { text-decoration: underline dotted }\",\n      \".foo{text-decoration:underline dotted}\",\n    );\n    minify_test(\n      \".foo { text-decoration: underline dotted yellow }\",\n      \".foo{text-decoration:underline dotted #ff0}\",\n    );\n    minify_test(\n      \".foo { text-decoration: yellow dotted underline }\",\n      \".foo{text-decoration:underline dotted #ff0}\",\n    );\n    minify_test(\n      \".foo { text-decoration: underline overline dotted yellow }\",\n      \".foo{text-decoration:underline overline dotted #ff0}\",\n    );\n    minify_test(\n      \".foo { -webkit-text-decoration: yellow dotted underline }\",\n      \".foo{-webkit-text-decoration:underline dotted #ff0}\",\n    );\n    minify_test(\n      \".foo { -moz-text-decoration: yellow dotted underline }\",\n      \".foo{-moz-text-decoration:underline dotted #ff0}\",\n    );\n\n    test(\n      r#\"\n      .foo {\n        text-decoration-line: underline;\n        text-decoration-style: dotted;\n        text-decoration-color: yellow;\n        text-decoration-thickness: 2px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        text-decoration: underline 2px dotted #ff0;\n      }\n    \"#},\n    );\n\n    test(\n      r#\"\n      .foo {\n        text-decoration: underline;\n        text-decoration-style: dotted;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        text-decoration: underline dotted;\n      }\n    \"#},\n    );\n\n    test(\n      r#\"\n      .foo {\n        text-decoration: underline;\n        text-decoration-style: var(--style);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        text-decoration: underline;\n        text-decoration-style: var(--style);\n      }\n    \"#},\n    );\n\n    test(\n      r#\"\n      .foo {\n        -webkit-text-decoration: underline;\n        -webkit-text-decoration-style: dotted;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-text-decoration: underline dotted;\n      }\n    \"#},\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        text-decoration: underline dotted;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-text-decoration: underline dotted;\n        text-decoration: underline dotted;\n      }\n    \"#},\n      Browsers {\n        safari: Some(8 << 16),\n        firefox: Some(30 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        text-decoration-line: underline;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-text-decoration-line: underline;\n        -moz-text-decoration-line: underline;\n        text-decoration-line: underline;\n      }\n    \"#},\n      Browsers {\n        safari: Some(8 << 16),\n        firefox: Some(30 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        text-decoration-style: dotted;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-text-decoration-style: dotted;\n        -moz-text-decoration-style: dotted;\n        text-decoration-style: dotted;\n      }\n    \"#},\n      Browsers {\n        safari: Some(8 << 16),\n        firefox: Some(30 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        text-decoration-color: yellow;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-text-decoration-color: #ff0;\n        -moz-text-decoration-color: #ff0;\n        text-decoration-color: #ff0;\n      }\n    \"#},\n      Browsers {\n        safari: Some(8 << 16),\n        firefox: Some(30 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        text-decoration: underline;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        text-decoration: underline;\n      }\n    \"#},\n      Browsers {\n        safari: Some(8 << 16),\n        firefox: Some(30 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        -webkit-text-decoration: underline dotted;\n        text-decoration: underline dotted;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-text-decoration: underline dotted;\n        text-decoration: underline dotted;\n      }\n    \"#},\n      Browsers {\n        safari: Some(14 << 16),\n        firefox: Some(45 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        text-decoration: double underline;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-text-decoration: underline double;\n        text-decoration: underline double;\n      }\n    \"#},\n      Browsers {\n        safari: Some(16 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        text-decoration: underline;\n        text-decoration-style: double;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-text-decoration: underline double;\n        text-decoration: underline double;\n      }\n    \"#},\n      Browsers {\n        safari: Some(16 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        text-decoration: underline;\n        text-decoration-color: red;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-text-decoration: underline red;\n        text-decoration: underline red;\n      }\n    \"#},\n      Browsers {\n        safari: Some(16 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        text-decoration: var(--test);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-text-decoration: var(--test);\n        text-decoration: var(--test);\n      }\n    \"#},\n      Browsers {\n        safari: Some(8 << 16),\n        firefox: Some(30 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    minify_test(\n      \".foo { text-decoration-skip-ink: all }\",\n      \".foo{text-decoration-skip-ink:all}\",\n    );\n    minify_test(\n      \".foo { -webkit-text-decoration-skip-ink: all }\",\n      \".foo{-webkit-text-decoration-skip-ink:all}\",\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        text-decoration: lch(50.998% 135.363 338) underline;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-text-decoration: underline #ee00be;\n        text-decoration: underline #ee00be;\n        -webkit-text-decoration: underline lch(50.998% 135.363 338);\n        text-decoration: underline lch(50.998% 135.363 338);\n      }\n    \"#},\n      Browsers {\n        safari: Some(8 << 16),\n        firefox: Some(30 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        text-decoration-color: lch(50.998% 135.363 338);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-text-decoration-color: #ee00be;\n        -moz-text-decoration-color: #ee00be;\n        text-decoration-color: #ee00be;\n        -webkit-text-decoration-color: lch(50.998% 135.363 338);\n        -moz-text-decoration-color: lch(50.998% 135.363 338);\n        text-decoration-color: lch(50.998% 135.363 338);\n      }\n    \"#},\n      Browsers {\n        safari: Some(8 << 16),\n        firefox: Some(30 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        text-decoration: lch(50.998% 135.363 338) var(--style);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        text-decoration: #ee00be var(--style);\n      }\n\n      @supports (color: lab(0% 0 0)) {\n        .foo {\n          text-decoration: lab(50.998% 125.506 -50.7078) var(--style);\n        }\n      }\n    \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      @supports (color: lab(0% 0 0)) {\n        .foo {\n          text-decoration: lab(50.998% 125.506 -50.7078) var(--style);\n        }\n      }\n    \"#,\n      indoc! {r#\"\n      @supports (color: lab(0% 0 0)) {\n        .foo {\n          text-decoration: lab(50.998% 125.506 -50.7078) var(--style);\n        }\n      }\n    \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        text-decoration: underline 10px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        text-decoration: underline;\n        text-decoration-thickness: 10px;\n      }\n    \"#},\n      Browsers {\n        safari: Some(15 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        text-decoration: underline 10px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        text-decoration: underline 10px;\n      }\n    \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        text-decoration: underline 10%;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        text-decoration: underline;\n        text-decoration-thickness: calc(1em / 10);\n      }\n    \"#},\n      Browsers {\n        safari: Some(12 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        text-decoration: underline 10%;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        text-decoration: underline 10%;\n      }\n    \"#},\n      Browsers {\n        firefox: Some(89 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        text-decoration-thickness: 10%;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        text-decoration-thickness: calc(1em / 10);\n      }\n    \"#},\n      Browsers {\n        safari: Some(12 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        text-decoration-thickness: 10%;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        text-decoration-thickness: 10%;\n      }\n    \"#},\n      Browsers {\n        firefox: Some(89 << 16),\n        ..Browsers::default()\n      },\n    );\n  }\n\n  #[test]\n  fn test_text_emphasis() {\n    minify_test(\".foo { text-emphasis-style: none }\", \".foo{text-emphasis-style:none}\");\n    minify_test(\n      \".foo { text-emphasis-style: filled }\",\n      \".foo{text-emphasis-style:filled}\",\n    );\n    minify_test(\".foo { text-emphasis-style: open }\", \".foo{text-emphasis-style:open}\");\n    minify_test(\".foo { text-emphasis-style: dot }\", \".foo{text-emphasis-style:dot}\");\n    minify_test(\n      \".foo { text-emphasis-style: filled dot }\",\n      \".foo{text-emphasis-style:dot}\",\n    );\n    minify_test(\n      \".foo { text-emphasis-style: dot filled }\",\n      \".foo{text-emphasis-style:dot}\",\n    );\n    minify_test(\n      \".foo { text-emphasis-style: open dot }\",\n      \".foo{text-emphasis-style:open dot}\",\n    );\n    minify_test(\n      \".foo { text-emphasis-style: dot open }\",\n      \".foo{text-emphasis-style:open dot}\",\n    );\n    minify_test(\".foo { text-emphasis-style: \\\"x\\\" }\", \".foo{text-emphasis-style:\\\"x\\\"}\");\n\n    minify_test(\".foo { text-emphasis-color: yellow }\", \".foo{text-emphasis-color:#ff0}\");\n\n    minify_test(\".foo { text-emphasis: none }\", \".foo{text-emphasis:none}\");\n    minify_test(\".foo { text-emphasis: filled }\", \".foo{text-emphasis:filled}\");\n    minify_test(\n      \".foo { text-emphasis: filled yellow }\",\n      \".foo{text-emphasis:filled #ff0}\",\n    );\n    minify_test(\n      \".foo { text-emphasis: dot filled yellow }\",\n      \".foo{text-emphasis:dot #ff0}\",\n    );\n\n    test(\n      r#\"\n      .foo {\n        text-emphasis-style: filled;\n        text-emphasis-color: yellow;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        text-emphasis: filled #ff0;\n      }\n    \"#},\n    );\n\n    test(\n      r#\"\n      .foo {\n        text-emphasis: filled red;\n        text-emphasis-color: yellow;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        text-emphasis: filled #ff0;\n      }\n    \"#},\n    );\n\n    test(\n      r#\"\n      .foo {\n        text-emphasis: filled yellow;\n        text-emphasis-color: var(--color);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        text-emphasis: filled #ff0;\n        text-emphasis-color: var(--color);\n      }\n    \"#},\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        text-emphasis-style: filled;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-text-emphasis-style: filled;\n        text-emphasis-style: filled;\n      }\n    \"#},\n      Browsers {\n        safari: Some(10 << 16),\n        chrome: Some(30 << 16),\n        firefox: Some(45 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        -webkit-text-emphasis-style: filled;\n        text-emphasis-style: filled;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        text-emphasis-style: filled;\n      }\n    \"#},\n      Browsers {\n        safari: Some(10 << 16),\n        firefox: Some(45 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    minify_test(\n      \".foo { text-emphasis-position: over }\",\n      \".foo{text-emphasis-position:over}\",\n    );\n    minify_test(\n      \".foo { text-emphasis-position: under }\",\n      \".foo{text-emphasis-position:under}\",\n    );\n    minify_test(\n      \".foo { text-emphasis-position: over right }\",\n      \".foo{text-emphasis-position:over}\",\n    );\n    minify_test(\n      \".foo { text-emphasis-position: over left }\",\n      \".foo{text-emphasis-position:over left}\",\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        text-emphasis-position: over;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-text-emphasis-position: over;\n        text-emphasis-position: over;\n      }\n    \"#},\n      Browsers {\n        safari: Some(10 << 16),\n        chrome: Some(30 << 16),\n        firefox: Some(45 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        text-emphasis-position: over left;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        text-emphasis-position: over left;\n      }\n    \"#},\n      Browsers {\n        safari: Some(10 << 16),\n        chrome: Some(30 << 16),\n        firefox: Some(45 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        text-emphasis-position: var(--test);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-text-emphasis-position: var(--test);\n        text-emphasis-position: var(--test);\n      }\n    \"#},\n      Browsers {\n        safari: Some(10 << 16),\n        chrome: Some(30 << 16),\n        firefox: Some(45 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        text-emphasis: filled lch(50.998% 135.363 338);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-text-emphasis: filled #ee00be;\n        text-emphasis: filled #ee00be;\n        -webkit-text-emphasis: filled lch(50.998% 135.363 338);\n        text-emphasis: filled lch(50.998% 135.363 338);\n      }\n    \"#},\n      Browsers {\n        chrome: Some(25 << 16),\n        firefox: Some(48 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        text-emphasis-color: lch(50.998% 135.363 338);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-text-emphasis-color: #ee00be;\n        text-emphasis-color: #ee00be;\n        -webkit-text-emphasis-color: lch(50.998% 135.363 338);\n        text-emphasis-color: lch(50.998% 135.363 338);\n      }\n    \"#},\n      Browsers {\n        chrome: Some(25 << 16),\n        firefox: Some(48 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        text-emphasis: lch(50.998% 135.363 338) var(--style);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        text-emphasis: #ee00be var(--style);\n      }\n\n      @supports (color: lab(0% 0 0)) {\n        .foo {\n          text-emphasis: lab(50.998% 125.506 -50.7078) var(--style);\n        }\n      }\n    \"#},\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      @supports (color: lab(0% 0 0)) {\n        .foo {\n          text-emphasis: lab(50.998% 125.506 -50.7078) var(--style);\n        }\n      }\n    \"#,\n      indoc! {r#\"\n      @supports (color: lab(0% 0 0)) {\n        .foo {\n          text-emphasis: lab(50.998% 125.506 -50.7078) var(--style);\n        }\n      }\n    \"#},\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n  }\n\n  #[test]\n  fn test_text_shadow() {\n    minify_test(\n      \".foo { text-shadow: 1px 1px 2px yellow; }\",\n      \".foo{text-shadow:1px 1px 2px #ff0}\",\n    );\n    minify_test(\n      \".foo { text-shadow: 1px 1px 2px 3px yellow; }\",\n      \".foo{text-shadow:1px 1px 2px 3px #ff0}\",\n    );\n    minify_test(\n      \".foo { text-shadow: 1px 1px 0 yellow; }\",\n      \".foo{text-shadow:1px 1px #ff0}\",\n    );\n    minify_test(\n      \".foo { text-shadow: 1px 1px yellow; }\",\n      \".foo{text-shadow:1px 1px #ff0}\",\n    );\n    minify_test(\n      \".foo { text-shadow: 1px 1px yellow, 2px 3px red; }\",\n      \".foo{text-shadow:1px 1px #ff0,2px 3px red}\",\n    );\n\n    prefix_test(\n      \".foo { text-shadow: 12px 12px lab(40% 56.6 39) }\",\n      indoc! { r#\"\n        .foo {\n          text-shadow: 12px 12px #b32323;\n          text-shadow: 12px 12px lab(40% 56.6 39);\n        }\n      \"#},\n      Browsers {\n        chrome: Some(4 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { text-shadow: 12px 12px lab(40% 56.6 39) }\",\n      indoc! { r#\"\n        .foo {\n          text-shadow: 12px 12px #b32323;\n          text-shadow: 12px 12px color(display-p3 .643308 .192455 .167712);\n          text-shadow: 12px 12px lab(40% 56.6 39);\n        }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { text-shadow: 12px 12px lab(40% 56.6 39), 12px 12px yellow }\",\n      indoc! { r#\"\n        .foo {\n          text-shadow: 12px 12px #b32323, 12px 12px #ff0;\n          text-shadow: 12px 12px lab(40% 56.6 39), 12px 12px #ff0;\n        }\n      \"#},\n      Browsers {\n        chrome: Some(4 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { text-shadow: var(--foo) 12px lab(40% 56.6 39) }\",\n      indoc! { r#\"\n        .foo {\n          text-shadow: var(--foo) 12px #b32323;\n        }\n\n        @supports (color: lab(0% 0 0)) {\n          .foo {\n            text-shadow: var(--foo) 12px lab(40% 56.6 39);\n          }\n        }\n      \"#},\n      Browsers {\n        chrome: Some(4 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n        @supports (color: lab(0% 0 0)) {\n          .foo {\n            text-shadow: var(--foo) 12px lab(40% 56.6 39);\n          }\n        }\n      \"#,\n      indoc! { r#\"\n        @supports (color: lab(0% 0 0)) {\n          .foo {\n            text-shadow: var(--foo) 12px lab(40% 56.6 39);\n          }\n        }\n      \"#},\n      Browsers {\n        chrome: Some(4 << 16),\n        ..Browsers::default()\n      },\n    );\n  }\n\n  #[test]\n  fn test_break() {\n    prefix_test(\n      r#\"\n      .foo {\n        box-decoration-break: clone;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-box-decoration-break: clone;\n        box-decoration-break: clone;\n      }\n    \"#},\n      Browsers {\n        safari: Some(15 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        box-decoration-break: clone;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        box-decoration-break: clone;\n      }\n    \"#},\n      Browsers {\n        firefox: Some(95 << 16),\n        ..Browsers::default()\n      },\n    );\n  }\n\n  #[test]\n  fn test_position() {\n    test(\n      r#\"\n      .foo {\n        position: relative;\n        position: absolute;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        position: absolute;\n      }\n    \"#},\n    );\n\n    test(\n      r#\"\n      .foo {\n        position: -webkit-sticky;\n        position: sticky;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        position: -webkit-sticky;\n        position: sticky;\n      }\n    \"#},\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        position: sticky;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        position: -webkit-sticky;\n        position: sticky;\n      }\n    \"#},\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        position: -webkit-sticky;\n        position: sticky;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        position: sticky;\n      }\n    \"#},\n      Browsers {\n        safari: Some(13 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        top: 0;\n        left: 0;\n        bottom: 0;\n        right: 0;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        inset: 0;\n      }\n    \"#},\n    );\n\n    test(\n      r#\"\n      .foo {\n        top: 2px;\n        left: 4px;\n        bottom: 2px;\n        right: 4px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        inset: 2px 4px;\n      }\n    \"#},\n    );\n\n    test(\n      r#\"\n      .foo {\n        top: 1px;\n        left: 2px;\n        bottom: 3px;\n        right: 4px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        inset: 1px 4px 3px 2px;\n      }\n    \"#},\n    );\n\n    test(\n      r#\"\n      .foo {\n        inset-block-start: 2px;\n        inset-block-end: 2px;\n        inset-inline-start: 4px;\n        inset-inline-end: 4px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        inset-block: 2px;\n        inset-inline: 4px;\n      }\n    \"#},\n    );\n\n    test(\n      r#\"\n      .foo {\n        inset-block-start: 2px;\n        inset-block-end: 3px;\n        inset-inline-start: 4px;\n        inset-inline-end: 5px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        inset-block: 2px 3px;\n        inset-inline: 4px 5px;\n      }\n    \"#},\n    );\n\n    test(\n      r#\"\n      .foo {\n        inset-block-start: 2px;\n        inset-block-end: 3px;\n        inset: 4px;\n        inset-inline-start: 4px;\n        inset-inline-end: 5px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        inset: 4px;\n        inset-inline: 4px 5px;\n      }\n    \"#},\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        inset-inline-start: 2px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo:not(:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        left: 2px;\n      }\n\n      .foo:not(:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        left: 2px;\n      }\n\n      .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        right: 2px;\n      }\n\n      .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        right: 2px;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        inset-inline-start: 2px;\n        inset-inline-end: 4px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo:not(:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        left: 2px;\n        right: 4px;\n      }\n\n      .foo:not(:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {\n        left: 2px;\n        right: 4px;\n      }\n\n      .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        left: 4px;\n        right: 2px;\n      }\n\n      .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {\n        left: 4px;\n        right: 2px;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        inset-inline: 2px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        left: 2px;\n        right: 2px;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        inset-block-start: 2px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        top: 2px;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        inset-block-end: 2px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        bottom: 2px;\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        top: 1px;\n        left: 2px;\n        bottom: 3px;\n        right: 4px;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        top: 1px;\n        bottom: 3px;\n        left: 2px;\n        right: 4px;\n      }\n    \"#},\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n  }\n\n  #[test]\n  fn test_overflow() {\n    minify_test(\".foo { overflow: hidden }\", \".foo{overflow:hidden}\");\n    minify_test(\".foo { overflow: hidden hidden }\", \".foo{overflow:hidden}\");\n    minify_test(\".foo { overflow: hidden auto }\", \".foo{overflow:hidden auto}\");\n\n    test(\n      r#\"\n      .foo {\n        overflow-x: hidden;\n        overflow-y: auto;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        overflow: hidden auto;\n      }\n    \"#},\n    );\n\n    test(\n      r#\"\n      .foo {\n        overflow: hidden;\n        overflow-y: auto;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        overflow: hidden auto;\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      .foo {\n        overflow: hidden;\n        overflow-y: var(--y);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        overflow: hidden;\n        overflow-y: var(--y);\n      }\n    \"#},\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        overflow: hidden auto;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        overflow-x: hidden;\n        overflow-y: auto;\n      }\n    \"#},\n      Browsers {\n        chrome: Some(67 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        overflow: hidden hidden;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        overflow: hidden;\n      }\n    \"#},\n      Browsers {\n        chrome: Some(67 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        overflow: hidden auto;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        overflow: hidden auto;\n      }\n    \"#},\n      Browsers {\n        chrome: Some(68 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    minify_test(\".foo { text-overflow: ellipsis }\", \".foo{text-overflow:ellipsis}\");\n    prefix_test(\n      r#\"\n      .foo {\n        text-overflow: ellipsis;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -o-text-overflow: ellipsis;\n        text-overflow: ellipsis;\n      }\n    \"#},\n      Browsers {\n        safari: Some(4 << 16),\n        opera: Some(10 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        -o-text-overflow: ellipsis;\n        text-overflow: ellipsis;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        text-overflow: ellipsis;\n      }\n    \"#},\n      Browsers {\n        safari: Some(4 << 16),\n        opera: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n  }\n\n  #[test]\n  fn test_ui() {\n    minify_test(\".foo { resize: both }\", \".foo{resize:both}\");\n    minify_test(\".foo { resize: Horizontal }\", \".foo{resize:horizontal}\");\n    minify_test(\".foo { cursor: ew-resize }\", \".foo{cursor:ew-resize}\");\n    minify_test(\n      \".foo { cursor: url(\\\"test.cur\\\"), ew-resize }\",\n      \".foo{cursor:url(test.cur),ew-resize}\",\n    );\n    minify_test(\n      \".foo { cursor: url(\\\"test.cur\\\"), url(\\\"foo.cur\\\"), ew-resize }\",\n      \".foo{cursor:url(test.cur),url(foo.cur),ew-resize}\",\n    );\n    minify_test(\".foo { caret-color: auto }\", \".foo{caret-color:auto}\");\n    minify_test(\".foo { caret-color: yellow }\", \".foo{caret-color:#ff0}\");\n    minify_test(\".foo { caret-shape: block }\", \".foo{caret-shape:block}\");\n    minify_test(\".foo { caret: yellow block }\", \".foo{caret:#ff0 block}\");\n    minify_test(\".foo { caret: block yellow }\", \".foo{caret:#ff0 block}\");\n    minify_test(\".foo { caret: block }\", \".foo{caret:block}\");\n    minify_test(\".foo { caret: yellow }\", \".foo{caret:#ff0}\");\n    minify_test(\".foo { caret: auto auto }\", \".foo{caret:auto}\");\n    minify_test(\".foo { caret: auto }\", \".foo{caret:auto}\");\n    minify_test(\".foo { caret: yellow auto }\", \".foo{caret:#ff0}\");\n    minify_test(\".foo { caret: auto block }\", \".foo{caret:block}\");\n    minify_test(\".foo { user-select: none }\", \".foo{user-select:none}\");\n    minify_test(\".foo { -webkit-user-select: none }\", \".foo{-webkit-user-select:none}\");\n    minify_test(\".foo { accent-color: auto }\", \".foo{accent-color:auto}\");\n    minify_test(\".foo { accent-color: yellow }\", \".foo{accent-color:#ff0}\");\n    minify_test(\".foo { appearance: None }\", \".foo{appearance:none}\");\n    minify_test(\n      \".foo { -webkit-appearance: textfield }\",\n      \".foo{-webkit-appearance:textfield}\",\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        user-select: none;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-user-select: none;\n        -moz-user-select: none;\n        -ms-user-select: none;\n        user-select: none;\n      }\n    \"#},\n      Browsers {\n        safari: Some(8 << 16),\n        opera: Some(5 << 16),\n        firefox: Some(10 << 16),\n        ie: Some(10 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        -webkit-user-select: none;\n        -moz-user-select: none;\n        -ms-user-select: none;\n        user-select: none;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-user-select: none;\n        user-select: none;\n      }\n    \"#},\n      Browsers {\n        safari: Some(8 << 16),\n        opera: Some(80 << 16),\n        firefox: Some(80 << 16),\n        edge: Some(80 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        -webkit-user-select: none;\n        -moz-user-select: none;\n        -ms-user-select: none;\n        user-select: none;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        user-select: none;\n      }\n    \"#},\n      Browsers {\n        opera: Some(80 << 16),\n        firefox: Some(80 << 16),\n        edge: Some(80 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        appearance: none;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-appearance: none;\n        -moz-appearance: none;\n        -ms-appearance: none;\n        appearance: none;\n      }\n    \"#},\n      Browsers {\n        safari: Some(8 << 16),\n        chrome: Some(80 << 16),\n        firefox: Some(10 << 16),\n        ie: Some(11 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        -webkit-appearance: none;\n        -moz-appearance: none;\n        -ms-appearance: none;\n        appearance: none;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-appearance: none;\n        appearance: none;\n      }\n    \"#},\n      Browsers {\n        safari: Some(15 << 16),\n        chrome: Some(85 << 16),\n        firefox: Some(80 << 16),\n        edge: Some(85 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        -webkit-appearance: none;\n        -moz-appearance: none;\n        -ms-appearance: none;\n        appearance: none;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        appearance: none;\n      }\n    \"#},\n      Browsers {\n        chrome: Some(85 << 16),\n        firefox: Some(80 << 16),\n        edge: Some(85 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { caret-color: lch(50.998% 135.363 338) }\",\n      indoc! { r#\"\n        .foo {\n          caret-color: #ee00be;\n          caret-color: color(display-p3 .972962 -.362078 .804206);\n          caret-color: lch(50.998% 135.363 338);\n        }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { caret: lch(50.998% 135.363 338) block }\",\n      indoc! { r#\"\n        .foo {\n          caret: #ee00be block;\n          caret: color(display-p3 .972962 -.362078 .804206) block;\n          caret: lch(50.998% 135.363 338) block;\n        }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { caret: lch(50.998% 135.363 338) var(--foo) }\",\n      indoc! { r#\"\n        .foo {\n          caret: #ee00be var(--foo);\n        }\n\n        @supports (color: lab(0% 0 0)) {\n          .foo {\n            caret: lab(50.998% 125.506 -50.7078) var(--foo);\n          }\n        }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n        @supports (color: lab(0% 0 0)) {\n          .foo {\n            caret: lab(50.998% 125.506 -50.7078) var(--foo);\n          }\n        }\n      \"#,\n      indoc! { r#\"\n        @supports (color: lab(0% 0 0)) {\n          .foo {\n            caret: lab(50.998% 125.506 -50.7078) var(--foo);\n          }\n        }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n  }\n\n  #[test]\n  fn test_list() {\n    minify_test(\".foo { list-style-type: disc; }\", \".foo{list-style-type:disc}\");\n    minify_test(\".foo { list-style-type: \\\"★\\\"; }\", \".foo{list-style-type:\\\"★\\\"}\");\n    minify_test(\n      \".foo { list-style-type: symbols(cyclic '○' '●'); }\",\n      \".foo{list-style-type:symbols(cyclic \\\"○\\\" \\\"●\\\")}\",\n    );\n    minify_test(\n      \".foo { list-style-type: symbols('○' '●'); }\",\n      \".foo{list-style-type:symbols(\\\"○\\\" \\\"●\\\")}\",\n    );\n    minify_test(\n      \".foo { list-style-type: symbols(symbolic '○' '●'); }\",\n      \".foo{list-style-type:symbols(\\\"○\\\" \\\"●\\\")}\",\n    );\n    minify_test(\n      \".foo { list-style-type: symbols(symbolic url('ellipse.png')); }\",\n      \".foo{list-style-type:symbols(url(ellipse.png))}\",\n    );\n    minify_test(\n      \".foo { list-style-image: url('ellipse.png'); }\",\n      \".foo{list-style-image:url(ellipse.png)}\",\n    );\n    minify_test(\n      \".foo { list-style-position: outside; }\",\n      \".foo{list-style-position:outside}\",\n    );\n    minify_test(\n      \".foo { list-style: \\\"★\\\" url(ellipse.png) outside; }\",\n      \".foo{list-style:url(ellipse.png) \\\"★\\\"}\",\n    );\n    minify_test(\".foo { list-style: none; }\", \".foo{list-style:none}\");\n    minify_test(\".foo { list-style: none none outside; }\", \".foo{list-style:none}\");\n    minify_test(\".foo { list-style: none none inside; }\", \".foo{list-style:inside none}\");\n    minify_test(\".foo { list-style: none inside; }\", \".foo{list-style:inside none}\");\n    minify_test(\".foo { list-style: none disc; }\", \".foo{list-style:outside}\");\n    minify_test(\".foo { list-style: none inside disc; }\", \".foo{list-style:inside}\");\n    minify_test(\".foo { list-style: none \\\"★\\\"; }\", \".foo{list-style:\\\"★\\\"}\");\n    minify_test(\n      \".foo { list-style: none url(foo.png); }\",\n      \".foo{list-style:url(foo.png) none}\",\n    );\n\n    test(\n      r#\"\n      .foo {\n        list-style-type: disc;\n        list-style-image: url(ellipse.png);\n        list-style-position: outside;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        list-style: url(\"ellipse.png\");\n      }\n    \"#},\n    );\n\n    test(\n      r#\"\n      .foo {\n        list-style: \\\"★\\\" url(ellipse.png) outside;\n        list-style-image: none;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        list-style: \\\"★\\\";\n      }\n    \"#},\n    );\n\n    test(\n      r#\"\n      .foo {\n        list-style: \\\"★\\\" url(ellipse.png) outside;\n        list-style-image: var(--img);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        list-style: url(\"ellipse.png\") \\\"★\\\";\n        list-style-image: var(--img);\n      }\n    \"#},\n    );\n\n    prefix_test(\n      \".foo { list-style-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) }\",\n      indoc! { r#\"\n        .foo {\n          list-style-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ff0f0e), to(#7773ff));\n          list-style-image: -webkit-linear-gradient(top, #ff0f0e, #7773ff);\n          list-style-image: linear-gradient(#ff0f0e, #7773ff);\n          list-style-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364));\n        }\n      \"#},\n      Browsers {\n        chrome: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { list-style: \\\"★\\\" linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) }\",\n      indoc! { r#\"\n        .foo {\n          list-style: linear-gradient(#ff0f0e, #7773ff) \"★\";\n          list-style: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) \"★\";\n        }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { list-style: var(--foo) linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) }\",\n      indoc! { r#\"\n        .foo {\n          list-style: var(--foo) linear-gradient(#ff0f0e, #7773ff);\n        }\n\n        @supports (color: lab(0% 0 0)) {\n          .foo {\n            list-style: var(--foo) linear-gradient(lab(56.208% 94.4644 98.8928), lab(51% 70.4544 -115.586));\n          }\n        }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n        @supports (color: lab(0% 0 0)) {\n          .foo {\n            list-style: var(--foo) linear-gradient(lab(56.208% 94.4644 98.8928), lab(51% 70.4544 -115.586));\n          }\n        }\n      \"#,\n      indoc! { r#\"\n        @supports (color: lab(0% 0 0)) {\n          .foo {\n            list-style: var(--foo) linear-gradient(lab(56.208% 94.4644 98.8928), lab(51% 70.4544 -115.586));\n          }\n        }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        list-style: inside;\n        list-style-type: disc;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        list-style: inside;\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      .foo {\n        list-style: inside;\n        list-style-type: decimal;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        list-style: inside decimal;\n      }\n    \"#},\n    );\n  }\n\n  #[test]\n  fn test_image_set() {\n    // Spec: https://drafts.csswg.org/css-images-4/#image-set-notation\n    // WPT: https://github.com/web-platform-tests/wpt/blob/master/css/css-images/image-set/image-set-parsing.html\n    // test image-set(<string>)\n    minify_test(\n      \".foo { background: image-set(\\\"foo.png\\\" 2x, url(bar.png) 1x) }\",\n      \".foo{background:image-set(\\\"foo.png\\\" 2x,\\\"bar.png\\\" 1x)}\",\n    );\n\n    // test image-set(type(<string>))\n    minify_test(\n      \".foo { background: image-set('foo.webp' type('webp'), url(foo.jpg)) }\",\n      \".foo{background:image-set(\\\"foo.webp\\\" 1x type(\\\"webp\\\"),\\\"foo.jpg\\\" 1x)}\",\n    );\n    minify_test(\n      \".foo { background: image-set('foo.avif' 2x type('image/avif'), url(foo.png)) }\",\n      \".foo{background:image-set(\\\"foo.avif\\\" 2x type(\\\"image/avif\\\"),\\\"foo.png\\\" 1x)}\",\n    );\n    minify_test(\n      \".foo { background: image-set(url('example.png') 3x type('image/png')) }\",\n      \".foo{background:image-set(\\\"example.png\\\" 3x type(\\\"image/png\\\"))}\",\n    );\n\n    minify_test(\n      \".foo { background: image-set(url(example.png) type('image/png') 1x) }\",\n      \".foo{background:image-set(\\\"example.png\\\" 1x type(\\\"image/png\\\"))}\",\n    );\n\n    minify_test(\n      \".foo { background: -webkit-image-set(url(\\\"foo.png\\\") 2x, url(bar.png) 1x) }\",\n      \".foo{background:-webkit-image-set(url(foo.png) 2x,url(bar.png) 1x)}\",\n    );\n\n    test(\n      r#\"\n      .foo {\n        background: -webkit-image-set(url(\"foo.png\") 2x, url(bar.png) 1x);\n        background: image-set(url(\"foo.png\") 2x, url(bar.png) 1x);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        background: -webkit-image-set(url(\"foo.png\") 2x, url(\"bar.png\") 1x);\n        background: image-set(\"foo.png\" 2x, \"bar.png\" 1x);\n      }\n    \"#},\n    );\n\n    // test image-set(<gradient>)\n    test(\n      r#\"\n      .foo {\n        background: image-set(linear-gradient(cornflowerblue, white) 1x, url(\"detailed-gradient.png\") 3x);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        background: image-set(linear-gradient(#6495ed, #fff) 1x, \"detailed-gradient.png\" 3x);\n      }\n    \"#},\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        background: image-set(url(\"foo.png\") 2x, url(bar.png) 1x);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        background: -webkit-image-set(url(\"foo.png\") 2x, url(\"bar.png\") 1x);\n        background: image-set(\"foo.png\" 2x, \"bar.png\" 1x);\n      }\n    \"#},\n      Browsers {\n        chrome: Some(85 << 16),\n        firefox: Some(80 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        background: -webkit-image-set(url(\"foo.png\") 2x, url(bar.png) 1x);\n        background: image-set(url(\"foo.png\") 2x, url(bar.png) 1x);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        background: -webkit-image-set(url(\"foo.png\") 2x, url(\"bar.png\") 1x);\n        background: image-set(\"foo.png\" 2x, \"bar.png\" 1x);\n      }\n    \"#},\n      Browsers {\n        firefox: Some(80 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        background: -webkit-image-set(url(\"foo.png\") 2x, url(bar.png) 1x);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        background: -webkit-image-set(url(\"foo.png\") 2x, url(\"bar.png\") 1x);\n      }\n    \"#},\n      Browsers {\n        chrome: Some(95 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    for property in &[\n      \"background\",\n      \"background-image\",\n      \"border-image-source\",\n      \"border-image\",\n      \"border-image-source\",\n      \"-webkit-mask-image\",\n      \"-webkit-mask\",\n      \"list-style-image\",\n      \"list-style\",\n    ] {\n      prefix_test(\n        &format!(\n          r#\"\n        .foo {{\n          {}: url(foo.png);\n          {}: image-set(url(\"foo.png\") 2x, url(bar.png) 1x);\n        }}\n      \"#,\n          property, property\n        ),\n        &format!(\n          indoc! {r#\"\n        .foo {{\n          {}: url(\"foo.png\");\n          {}: image-set(\"foo.png\" 2x, \"bar.png\" 1x);\n        }}\n      \"#},\n          property, property\n        ),\n        Browsers {\n          ie: Some(11 << 16),\n          chrome: Some(95 << 16),\n          ..Browsers::default()\n        },\n      );\n\n      prefix_test(\n        &format!(\n          r#\"\n        .foo {{\n          {}: url(foo.png);\n          {}: image-set(url(\"foo.png\") 2x, url(bar.png) 1x);\n        }}\n      \"#,\n          property, property\n        ),\n        &format!(\n          indoc! {r#\"\n        .foo {{\n          {}: -webkit-image-set(url(\"foo.png\") 2x, url(\"bar.png\") 1x);\n          {}: image-set(\"foo.png\" 2x, \"bar.png\" 1x);\n        }}\n      \"#},\n          property, property\n        ),\n        Browsers {\n          chrome: Some(95 << 16),\n          ..Browsers::default()\n        },\n      );\n    }\n  }\n\n  #[test]\n  fn test_color() {\n    minify_test(\".foo { color: yellow }\", \".foo{color:#ff0}\");\n    minify_test(\".foo { color: rgb(255, 255, 0) }\", \".foo{color:#ff0}\");\n    minify_test(\".foo { color: rgba(255, 255, 0, 1) }\", \".foo{color:#ff0}\");\n    minify_test(\".foo { color: rgba(255, 255, 0, 0.8) }\", \".foo{color:#ff0c}\");\n    minify_test(\".foo { color: rgb(128, 128, 128) }\", \".foo{color:gray}\");\n    minify_test(\".foo { color: rgb(123, 255, 255) }\", \".foo{color:#7bffff}\");\n    minify_test(\".foo { color: rgba(123, 255, 255, 0.5) }\", \".foo{color:#7bffff80}\");\n    minify_test(\".foo { color: rgb(123 255 255) }\", \".foo{color:#7bffff}\");\n    minify_test(\".foo { color: rgb(123 255 255 / .5) }\", \".foo{color:#7bffff80}\");\n    minify_test(\".foo { color: rgb(123 255 255 / 50%) }\", \".foo{color:#7bffff80}\");\n    minify_test(\".foo { color: rgb(48% 100% 100% / 50%) }\", \".foo{color:#7affff80}\");\n    minify_test(\".foo { color: hsl(100deg, 100%, 50%) }\", \".foo{color:#5f0}\");\n    minify_test(\".foo { color: hsl(100, 100%, 50%) }\", \".foo{color:#5f0}\");\n    minify_test(\".foo { color: hsl(100 100% 50%) }\", \".foo{color:#5f0}\");\n    minify_test(\".foo { color: hsl(100 100 50) }\", \".foo{color:#5f0}\");\n    minify_test(\".foo { color: hsl(100, 100%, 50%, .8) }\", \".foo{color:#5f0c}\");\n    minify_test(\".foo { color: hsl(100 100% 50% / .8) }\", \".foo{color:#5f0c}\");\n    minify_test(\".foo { color: hsla(100, 100%, 50%, .8) }\", \".foo{color:#5f0c}\");\n    minify_test(\".foo { color: hsla(100 100% 50% / .8) }\", \".foo{color:#5f0c}\");\n    minify_test(\".foo { color: transparent }\", \".foo{color:#0000}\");\n    minify_test(\".foo { color: currentColor }\", \".foo{color:currentColor}\");\n    minify_test(\".foo { color: ButtonBorder }\", \".foo{color:buttonborder}\");\n    minify_test(\".foo { color: hwb(194 0% 0%) }\", \".foo{color:#00c4ff}\");\n    minify_test(\".foo { color: hwb(194 0% 0% / 50%) }\", \".foo{color:#00c4ff80}\");\n    minify_test(\".foo { color: hwb(194 0% 50%) }\", \".foo{color:#006280}\");\n    minify_test(\".foo { color: hwb(194 50% 0%) }\", \".foo{color:#80e1ff}\");\n    minify_test(\".foo { color: hwb(194 50 0) }\", \".foo{color:#80e1ff}\");\n    minify_test(\".foo { color: hwb(194 50% 50%) }\", \".foo{color:gray}\");\n    // minify_test(\".foo { color: ActiveText }\", \".foo{color:ActiveTet}\");\n    minify_test(\n      \".foo { color: lab(29.2345% 39.3825 20.0664); }\",\n      \".foo{color:lab(29.2345% 39.3825 20.0664)}\",\n    );\n    minify_test(\n      \".foo { color: lab(29.2345 39.3825 20.0664); }\",\n      \".foo{color:lab(29.2345% 39.3825 20.0664)}\",\n    );\n    minify_test(\n      \".foo { color: lab(29.2345% 39.3825% 20.0664%); }\",\n      \".foo{color:lab(29.2345% 49.2281 25.083)}\",\n    );\n    minify_test(\n      \".foo { color: lab(29.2345% 39.3825 20.0664 / 100%); }\",\n      \".foo{color:lab(29.2345% 39.3825 20.0664)}\",\n    );\n    minify_test(\n      \".foo { color: lab(29.2345% 39.3825 20.0664 / 50%); }\",\n      \".foo{color:lab(29.2345% 39.3825 20.0664/.5)}\",\n    );\n    minify_test(\n      \".foo { color: lch(29.2345% 44.2 27); }\",\n      \".foo{color:lch(29.2345% 44.2 27)}\",\n    );\n    minify_test(\n      \".foo { color: lch(29.2345 44.2 27); }\",\n      \".foo{color:lch(29.2345% 44.2 27)}\",\n    );\n    minify_test(\n      \".foo { color: lch(29.2345% 44.2% 27deg); }\",\n      \".foo{color:lch(29.2345% 66.3 27)}\",\n    );\n    minify_test(\n      \".foo { color: lch(29.2345% 44.2 45deg); }\",\n      \".foo{color:lch(29.2345% 44.2 45)}\",\n    );\n    minify_test(\n      \".foo { color: lch(29.2345% 44.2 .5turn); }\",\n      \".foo{color:lch(29.2345% 44.2 180)}\",\n    );\n    minify_test(\n      \".foo { color: lch(29.2345% 44.2 27 / 100%); }\",\n      \".foo{color:lch(29.2345% 44.2 27)}\",\n    );\n    minify_test(\n      \".foo { color: lch(29.2345% 44.2 27 / 50%); }\",\n      \".foo{color:lch(29.2345% 44.2 27/.5)}\",\n    );\n    minify_test(\n      \".foo { color: oklab(40.101% 0.1147 0.0453); }\",\n      \".foo{color:oklab(40.101% .1147 .0453)}\",\n    );\n    minify_test(\n      \".foo { color: oklab(.40101 0.1147 0.0453); }\",\n      \".foo{color:oklab(40.101% .1147 .0453)}\",\n    );\n    minify_test(\n      \".foo { color: oklab(40.101% 0.1147% 0.0453%); }\",\n      \".foo{color:oklab(40.101% .0004588 .0001812)}\",\n    );\n    minify_test(\n      \".foo { color: oklch(40.101% 0.12332 21.555); }\",\n      \".foo{color:oklch(40.101% .12332 21.555)}\",\n    );\n    minify_test(\n      \".foo { color: oklch(.40101 0.12332 21.555); }\",\n      \".foo{color:oklch(40.101% .12332 21.555)}\",\n    );\n    minify_test(\n      \".foo { color: oklch(40.101% 0.12332% 21.555); }\",\n      \".foo{color:oklch(40.101% .00049328 21.555)}\",\n    );\n    minify_test(\n      \".foo { color: oklch(40.101% 0.12332 .5turn); }\",\n      \".foo{color:oklch(40.101% .12332 180)}\",\n    );\n    minify_test(\n      \".foo { color: color(display-p3 1 0.5 0); }\",\n      \".foo{color:color(display-p3 1 .5 0)}\",\n    );\n    minify_test(\n      \".foo { color: color(display-p3 100% 50% 0%); }\",\n      \".foo{color:color(display-p3 1 .5 0)}\",\n    );\n    minify_test(\n      \".foo { color: color(xyz-d50 0.2005 0.14089 0.4472); }\",\n      \".foo{color:color(xyz-d50 .2005 .14089 .4472)}\",\n    );\n    minify_test(\n      \".foo { color: color(xyz-d50 20.05% 14.089% 44.72%); }\",\n      \".foo{color:color(xyz-d50 .2005 .14089 .4472)}\",\n    );\n    minify_test(\n      \".foo { color: color(xyz-d65 0.2005 0.14089 0.4472); }\",\n      \".foo{color:color(xyz .2005 .14089 .4472)}\",\n    );\n    minify_test(\n      \".foo { color: color(xyz-d65 20.05% 14.089% 44.72%); }\",\n      \".foo{color:color(xyz .2005 .14089 .4472)}\",\n    );\n    minify_test(\n      \".foo { color: color(xyz 0.2005 0.14089 0.4472); }\",\n      \".foo{color:color(xyz .2005 .14089 .4472)}\",\n    );\n    minify_test(\n      \".foo { color: color(xyz 20.05% 14.089% 44.72%); }\",\n      \".foo{color:color(xyz .2005 .14089 .4472)}\",\n    );\n    minify_test(\n      \".foo { color: color(xyz 0.2005 0 0); }\",\n      \".foo{color:color(xyz .2005 0 0)}\",\n    );\n    minify_test(\".foo { color: color(xyz 0 0 0); }\", \".foo{color:color(xyz 0 0 0)}\");\n    minify_test(\".foo { color: color(xyz 0 1 0); }\", \".foo{color:color(xyz 0 1 0)}\");\n    minify_test(\n      \".foo { color: color(xyz 0 1 0 / 20%); }\",\n      \".foo{color:color(xyz 0 1 0/.2)}\",\n    );\n    minify_test(\n      \".foo { color: color(xyz 0 0 0 / 20%); }\",\n      \".foo{color:color(xyz 0 0 0/.2)}\",\n    );\n    minify_test(\n      \".foo { color: color(display-p3 100% 50% 0 / 20%); }\",\n      \".foo{color:color(display-p3 1 .5 0/.2)}\",\n    );\n    minify_test(\n      \".foo { color: color(display-p3 100% 0 0 / 20%); }\",\n      \".foo{color:color(display-p3 1 0 0/.2)}\",\n    );\n    minify_test(\".foo { color: hsl(none none none) }\", \".foo{color:#000}\");\n    minify_test(\".foo { color: hwb(none none none) }\", \".foo{color:red}\");\n    minify_test(\".foo { color: rgb(none none none) }\", \".foo{color:#000}\");\n\n    // If the browser doesn't support `#rrggbbaa` color syntax, it is converted to `transparent`.\n    attr_test(\n      \"color: rgba(0, 0, 0, 0)\",\n      \"color:transparent\",\n      true,\n      Some(Browsers {\n        chrome: Some(61 << 16), // Chrome >= 62 supports `#rrggbbaa` color.\n        ..Browsers::default()\n      }),\n    );\n\n    attr_test(\n      \"color: #0000\",\n      \"color:transparent\",\n      true,\n      Some(Browsers {\n        chrome: Some(61 << 16), // Chrome >= 62 supports `#rrggbbaa` color.\n        ..Browsers::default()\n      }),\n    );\n\n    attr_test(\n      \"color: transparent\",\n      \"color:transparent\",\n      true,\n      Some(Browsers {\n        chrome: Some(61 << 16),\n        ..Browsers::default()\n      }),\n    );\n\n    attr_test(\n      \"color: rgba(0, 0, 0, 0)\",\n      \"color: rgba(0, 0, 0, 0)\",\n      false,\n      Some(Browsers {\n        chrome: Some(61 << 16),\n        ..Browsers::default()\n      }),\n    );\n\n    attr_test(\n      \"color: rgba(255, 0, 0, 0)\",\n      \"color:rgba(255,0,0,0)\",\n      true,\n      Some(Browsers {\n        chrome: Some(61 << 16),\n        ..Browsers::default()\n      }),\n    );\n\n    attr_test(\n      \"color: rgba(255, 0, 0, 0)\",\n      \"color:#f000\",\n      true,\n      Some(Browsers {\n        chrome: Some(62 << 16),\n        ..Browsers::default()\n      }),\n    );\n\n    prefix_test(\n      \".foo { color: rgba(123, 456, 789, 0.5) }\",\n      indoc! { r#\"\n        .foo {\n          color: #7bffff80;\n        }\n      \"#},\n      Browsers {\n        chrome: Some(95 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { color: rgba(123, 255, 255, 0.5) }\",\n      indoc! { r#\"\n        .foo {\n          color: rgba(123, 255, 255, .5);\n        }\n      \"#},\n      Browsers {\n        ie: Some(11 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { color: #7bffff80 }\",\n      indoc! { r#\"\n        .foo {\n          color: rgba(123, 255, 255, .5);\n        }\n      \"#},\n      Browsers {\n        ie: Some(11 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { color: rgba(123, 456, 789, 0.5) }\",\n      indoc! { r#\"\n        .foo {\n          color: rgba(123, 255, 255, .5);\n        }\n      \"#},\n      Browsers {\n        firefox: Some(48 << 16),\n        safari: Some(10 << 16),\n        ios_saf: Some(9 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { color: rgba(123, 456, 789, 0.5) }\",\n      indoc! { r#\"\n        .foo {\n          color: #7bffff80;\n        }\n      \"#},\n      Browsers {\n        firefox: Some(49 << 16),\n        safari: Some(10 << 16),\n        ios_saf: Some(10 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { background-color: lab(40% 56.6 39) }\",\n      indoc! { r#\"\n        .foo {\n          background-color: #b32323;\n          background-color: lab(40% 56.6 39);\n        }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { background-color: lch(40% 68.735435 34.568626) }\",\n      indoc! { r#\"\n        .foo {\n          background-color: #b32323;\n          background-color: lch(40% 68.7354 34.5686);\n        }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { background-color: oklab(59.686% 0.1009 0.1192); }\",\n      indoc! { r#\"\n        .foo {\n          background-color: #c65d07;\n          background-color: lab(52.2319% 40.1449 59.9171);\n        }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { background-color: oklch(40% 0.1268735435 34.568626) }\",\n      indoc! { r#\"\n        .foo {\n          background-color: #7e250f;\n          background-color: lab(29.2661% 38.2437 35.3889);\n        }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { background-color: lab(40% 56.6 39) }\",\n      indoc! { r#\"\n        .foo {\n          background-color: lab(40% 56.6 39);\n        }\n      \"#},\n      Browsers {\n        safari: Some(15 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { background-color: oklab(59.686% 0.1009 0.1192); }\",\n      indoc! { r#\"\n        .foo {\n          background-color: #c65d07;\n          background-color: lab(52.2319% 40.1449 59.9171);\n        }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        safari: Some(15 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { background-color: oklab(59.686% 0.1009 0.1192); }\",\n      indoc! { r#\"\n        .foo {\n          background-color: #c65d07;\n          background-color: color(display-p3 .724144 .386777 .148795);\n          background-color: lab(52.2319% 40.1449 59.9171);\n        }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { background-color: lab(40% 56.6 39) }\",\n      indoc! { r#\"\n        .foo {\n          background-color: #b32323;\n          background-color: color(display-p3 .643308 .192455 .167712);\n          background-color: lab(40% 56.6 39);\n        }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { background-color: oklch(59.686% 0.15619 49.7694); }\",\n      indoc! { r#\"\n        .foo {\n          background-color: #c65d06;\n          background-color: lab(52.2321% 40.1417 59.9527);\n        }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        safari: Some(15 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { background-color: color(sRGB 0.41587 0.503670 0.36664); }\",\n      indoc! { r#\"\n        .foo {\n          background-color: #6a805d;\n          background-color: color(srgb .41587 .50367 .36664);\n        }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { background-color: color(display-p3 0.43313 0.50108 0.37950); }\",\n      indoc! { r#\"\n        .foo {\n          background-color: #6a805d;\n          background-color: color(display-p3 .43313 .50108 .3795);\n        }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { background-color: color(display-p3 0.43313 0.50108 0.37950); }\",\n      indoc! { r#\"\n        .foo {\n          background-color: #6a805d;\n          background-color: color(display-p3 .43313 .50108 .3795);\n        }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { background-color: color(display-p3 0.43313 0.50108 0.37950); }\",\n      indoc! { r#\"\n        .foo {\n          background-color: color(display-p3 .43313 .50108 .3795);\n        }\n      \"#},\n      Browsers {\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { background-color: color(display-p3 0.43313 0.50108 0.37950); }\",\n      indoc! { r#\"\n        .foo {\n          background-color: #6a805d;\n          background-color: color(display-p3 .43313 .50108 .3795);\n        }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        safari: Some(15 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { background-color: color(display-p3 0.43313 0.50108 0.37950); }\",\n      indoc! { r#\"\n        .foo {\n          background-color: #6a805d;\n          background-color: color(display-p3 .43313 .50108 .3795);\n        }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { background-color: color(a98-rgb 0.44091 0.49971 0.37408); }\",\n      indoc! { r#\"\n        .foo {\n          background-color: #6a805d;\n          background-color: color(a98-rgb .44091 .49971 .37408);\n        }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { background-color: color(a98-rgb 0.44091 0.49971 0.37408); }\",\n      indoc! { r#\"\n        .foo {\n          background-color: color(a98-rgb .44091 .49971 .37408);\n        }\n      \"#},\n      Browsers {\n        safari: Some(15 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { background-color: color(prophoto-rgb 0.36589 0.41717 0.31333); }\",\n      indoc! { r#\"\n        .foo {\n          background-color: #6a805d;\n          background-color: color(prophoto-rgb .36589 .41717 .31333);\n        }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { background-color: color(rec2020 0.42210 0.47580 0.35605); }\",\n      indoc! { r#\"\n        .foo {\n          background-color: #728765;\n          background-color: color(rec2020 .4221 .4758 .35605);\n        }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { background-color: color(xyz-d50 0.2005 0.14089 0.4472); }\",\n      indoc! { r#\"\n        .foo {\n          background-color: #7654cd;\n          background-color: color(xyz-d50 .2005 .14089 .4472);\n        }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { background-color: color(xyz-d65 0.21661 0.14602 0.59452); }\",\n      indoc! { r#\"\n        .foo {\n          background-color: #7654cd;\n          background-color: color(xyz .21661 .14602 .59452);\n        }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { background-color: lch(50.998% 135.363 338) }\",\n      indoc! { r#\"\n        .foo {\n          background-color: #ee00be;\n          background-color: color(display-p3 .972962 -.362078 .804206);\n          background-color: lch(50.998% 135.363 338);\n        }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { color: lch(50.998% 135.363 338) }\",\n      indoc! { r#\"\n        .foo {\n          color: #ee00be;\n          color: color(display-p3 .972962 -.362078 .804206);\n          color: lch(50.998% 135.363 338);\n        }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { background: var(--image) lch(40% 68.735435 34.568626) }\",\n      indoc! { r#\"\n        .foo {\n          background: var(--image) #b32323;\n        }\n\n        @supports (color: lab(0% 0 0)) {\n          .foo {\n            background: var(--image) lab(40% 56.6 39);\n          }\n        }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n        @supports (color: lab(0% 0 0)) {\n          .foo {\n            background: var(--image) lab(40% 56.6 39);\n          }\n        }\n      \"#,\n      indoc! { r#\"\n        @supports (color: lab(0% 0 0)) {\n          .foo {\n            background: var(--image) lab(40% 56.6 39);\n          }\n        }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        color: red;\n        color: lab(40% 56.6 39);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        color: red;\n        color: lab(40% 56.6 39);\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        color: red;\n        color: lab(40% 56.6 39);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        color: lab(40% 56.6 39);\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(16 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        color: var(--fallback);\n        color: lab(40% 56.6 39);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        color: var(--fallback);\n        color: lab(40% 56.6 39);\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        color: var(--fallback);\n        color: lab(40% 56.6 39);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        color: lab(40% 56.6 39);\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(16 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        color: red;\n        color: var(--foo, lab(40% 56.6 39));\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        color: var(--foo, color(display-p3 .643308 .192455 .167712));\n      }\n\n      @supports (color: lab(0% 0 0)) {\n        .foo {\n          color: var(--foo, lab(40% 56.6 39));\n        }\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      @supports (color: lab(0% 0 0)) {\n        .foo {\n          color: var(--foo, lab(40% 56.6 39));\n        }\n      }\n    \"#,\n      indoc! {r#\"\n      @supports (color: lab(0% 0 0)) {\n        .foo {\n          color: var(--foo, lab(40% 56.6 39));\n        }\n      }\n    \"#\n      },\n      Browsers {\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        --a: rgb(0 0 0 / var(--alpha));\n        --b: rgb(50% 50% 50% / var(--alpha));\n        --c: rgb(var(--x) 0 0);\n        --d: rgb(0 var(--x) 0);\n        --e: rgb(0 0 var(--x));\n        --f: rgb(var(--x) 0 0 / var(--alpha));\n        --g: rgb(0 var(--x) 0 / var(--alpha));\n        --h: rgb(0 0 var(--x) / var(--alpha));\n        --i: rgb(none 0 0 / var(--alpha));\n        --j: rgb(from yellow r g b / var(--alpha));\n      }\n      \"#,\n      indoc! { r#\"\n        .foo {\n          --a: rgba(0, 0, 0, var(--alpha));\n          --b: rgba(128, 128, 128, var(--alpha));\n          --c: rgb(var(--x) 0 0);\n          --d: rgb(0 var(--x) 0);\n          --e: rgb(0 0 var(--x));\n          --f: rgb(var(--x) 0 0 / var(--alpha));\n          --g: rgb(0 var(--x) 0 / var(--alpha));\n          --h: rgb(0 0 var(--x) / var(--alpha));\n          --i: rgb(none 0 0 / var(--alpha));\n          --j: rgba(255, 255, 0, var(--alpha));\n        }\n      \"#},\n      Browsers {\n        safari: Some(11 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        --a: rgb(0 0 0 / var(--alpha));\n        --b: rgb(50% 50% 50% / var(--alpha));\n        --c: rgb(var(--x) 0 0);\n        --d: rgb(0 var(--x) 0);\n        --e: rgb(0 0 var(--x));\n        --f: rgb(var(--x) 0 0 / var(--alpha));\n        --g: rgb(0 var(--x) 0 / var(--alpha));\n        --h: rgb(0 0 var(--x) / var(--alpha));\n        --i: rgb(none 0 0 / var(--alpha));\n        --j: rgb(from yellow r g b / var(--alpha));\n      }\n      \"#,\n      indoc! { r#\"\n        .foo {\n          --a: rgb(0 0 0 / var(--alpha));\n          --b: rgb(128 128 128 / var(--alpha));\n          --c: rgb(var(--x) 0 0);\n          --d: rgb(0 var(--x) 0);\n          --e: rgb(0 0 var(--x));\n          --f: rgb(var(--x) 0 0 / var(--alpha));\n          --g: rgb(0 var(--x) 0 / var(--alpha));\n          --h: rgb(0 0 var(--x) / var(--alpha));\n          --i: rgb(none 0 0 / var(--alpha));\n          --j: rgb(255 255 0 / var(--alpha));\n        }\n      \"#},\n      Browsers {\n        safari: Some(13 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        --a: hsl(270 100% 50% / var(--alpha));\n        --b: hsl(var(--x) 0 0);\n        --c: hsl(0 var(--x) 0);\n        --d: hsl(0 0 var(--x));\n        --e: hsl(var(--x) 0 0 / var(--alpha));\n        --f: hsl(0 var(--x) 0 / var(--alpha));\n        --g: hsl(0 0 var(--x) / var(--alpha));\n        --h: hsl(270 100% 50% / calc(var(--alpha) / 2));\n        --i: hsl(none 100% 50% / var(--alpha));\n        --j: hsl(from yellow h s l / var(--alpha));\n      }\n      \"#,\n      indoc! { r#\"\n        .foo {\n          --a: hsla(270, 100%, 50%, var(--alpha));\n          --b: hsl(var(--x) 0 0);\n          --c: hsl(0 var(--x) 0);\n          --d: hsl(0 0 var(--x));\n          --e: hsl(var(--x) 0 0 / var(--alpha));\n          --f: hsl(0 var(--x) 0 / var(--alpha));\n          --g: hsl(0 0 var(--x) / var(--alpha));\n          --h: hsla(270, 100%, 50%, calc(var(--alpha) / 2));\n          --i: hsl(none 100% 50% / var(--alpha));\n          --j: hsla(60, 100%, 50%, var(--alpha));\n        }\n      \"#},\n      Browsers {\n        safari: Some(11 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        --a: hsl(270 100% 50% / var(--alpha));\n        --b: hsl(var(--x) 0 0);\n        --c: hsl(0 var(--x) 0);\n        --d: hsl(0 0 var(--x));\n        --e: hsl(var(--x) 0 0 / var(--alpha));\n        --f: hsl(0 var(--x) 0 / var(--alpha));\n        --g: hsl(0 0 var(--x) / var(--alpha));\n        --h: hsl(270 100% 50% / calc(var(--alpha) / 2));\n        --i: hsl(none 100% 50% / var(--alpha));\n      }\n      \"#,\n      indoc! { r#\"\n        .foo {\n          --a: hsl(270 100% 50% / var(--alpha));\n          --b: hsl(var(--x) 0 0);\n          --c: hsl(0 var(--x) 0);\n          --d: hsl(0 0 var(--x));\n          --e: hsl(var(--x) 0 0 / var(--alpha));\n          --f: hsl(0 var(--x) 0 / var(--alpha));\n          --g: hsl(0 0 var(--x) / var(--alpha));\n          --h: hsl(270 100% 50% / calc(var(--alpha) / 2));\n          --i: hsl(none 100% 50% / var(--alpha));\n        }\n      \"#},\n      Browsers {\n        safari: Some(13 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    test(\n      r#\"\n      .foo {\n        --a: rgb(50% 50% 50% / calc(100% / 2));\n        --b: hsl(calc(360deg / 2) 50% 50%);\n        --c: oklab(40.101% calc(0.1 + 0.2) 0.0453);\n        --d: color(display-p3 0.43313 0.50108 calc(0.1 + 0.2));\n        --e: rgb(calc(255 / 2), calc(255 / 2), calc(255 / 2));\n      }\n      \"#,\n      indoc! { r#\"\n        .foo {\n          --a: #80808080;\n          --b: #40bfbf;\n          --c: oklab(40.101% .3 .0453);\n          --d: color(display-p3 .43313 .50108 .3);\n          --e: gray;\n        }\n      \"#},\n    );\n  }\n\n  #[test]\n  fn test_relative_color() {\n    fn test(input: &str, output: &str) {\n      let output = CssColor::parse_string(output)\n        .unwrap()\n        .to_css_string(PrinterOptions {\n          minify: true,\n          ..PrinterOptions::default()\n        })\n        .unwrap();\n      minify_test(\n        &format!(\".foo {{ color: {} }}\", input),\n        &format!(\".foo{{color:{}}}\", output),\n      );\n    }\n\n    test(\"lab(from indianred calc(l * .8) a b)\", \"lab(43.1402% 45.7516 23.1557)\");\n    test(\"lch(from indianred calc(l + 10) c h)\", \"lch(63.9252% 51.2776 26.8448)\");\n    test(\"lch(from indianred l calc(c - 50) h)\", \"lch(53.9252% 1.27763 26.8448)\");\n    test(\n      \"lch(from indianred l c calc(h + 180deg))\",\n      \"lch(53.9252% 51.2776 206.845)\",\n    );\n    test(\"lch(from orchid l 30 h)\", \"lch(62.7526% 30 326.969)\");\n    test(\"lch(from orchid l 30 h)\", \"lch(62.7526% 30 326.969)\");\n    test(\"lch(from peru calc(l * 0.8) c h)\", \"lch(49.8022% 54.0117 63.6804)\");\n    test(\"rgb(from indianred 255 g b)\", \"rgb(255, 92, 92)\");\n    test(\"rgb(from indianred r g b / .5)\", \"rgba(205, 92, 92, .5)\");\n    test(\n      \"rgb(from rgba(205, 92, 92, .5) r g b / calc(alpha + .2))\",\n      \"rgba(205, 92, 92, .7)\",\n    );\n    test(\n      \"rgb(from rgba(205, 92, 92, .5) r g b / calc(alpha + .2))\",\n      \"rgba(205, 92, 92, .7)\",\n    );\n    test(\"lch(from indianred l sin(c) h)\", \"lch(53.9252% .84797 26.8448)\");\n    test(\"lch(from indianred l sqrt(c) h)\", \"lch(53.9252% 7.16084 26.8448)\");\n    test(\"lch(from indianred l c sin(h))\", \"lch(53.9252% 51.2776 .451575)\");\n    test(\"lch(from indianred calc(10% + 20%) c h)\", \"lch(30% 51.2776 26.8448)\");\n    test(\"lch(from indianred calc(10 + 20) c h)\", \"lch(30% 51.2776 26.8448)\");\n    test(\"lch(from indianred l c calc(10 + 20))\", \"lch(53.9252% 51.2776 30)\");\n    test(\n      \"lch(from indianred l c calc(10deg + 20deg))\",\n      \"lch(53.9252% 51.2776 30)\",\n    );\n    test(\n      \"lch(from indianred l c calc(10deg + 0.35rad))\",\n      \"lch(53.9252% 51.2776 30.0535)\",\n    );\n    minify_test(\n      \".foo{color:lch(from currentColor l c sin(h))}\",\n      \".foo{color:lch(from currentColor l c sin(h))}\",\n    );\n\n    // The following tests were converted from WPT: https://github.com/web-platform-tests/wpt/blob/master/css/css-color/parsing/relative-color-valid.html\n    // Find: test_valid_value\\(`color`, `(.*?)`,\\s*`(.*?)`\\)\n    // Replace: test(\"$1\", \"$2\")\n\n    // Testing no modifications.\n    test(\"rgb(from rebeccapurple r g b)\", \"#639\");\n    test(\"rgb(from rebeccapurple r g b / alpha)\", \"#639\");\n    test(\"rgb(from rgb(20%, 40%, 60%, 80%) r g b / alpha)\", \"#369c\");\n    test(\"rgb(from hsl(120deg 20% 50% / .5) r g b / alpha)\", \"#66996680\");\n\n    // Test nesting relative colors.\n    test(\"rgb(from rgb(from rebeccapurple r g b) r g b)\", \"#639\");\n\n    // Testing non-sRGB origin colors to see gamut mapping.\n    test(\"rgb(from color(display-p3 0 1 0) r g b / alpha)\", \"#00f942\"); // Naive clip based mapping would give rgb(0, 255, 0).\n    test(\"rgb(from lab(100% 104.3 -50.9) r g b)\", \"#fff\"); // Naive clip based mapping would give rgb(255, 150, 255).\n    test(\"rgb(from lab(0% 104.3 -50.9) r g b)\", \"#2a0022\"); // Naive clip based mapping would give rgb(90, 0, 76). NOTE: 0% lightness in Lab/LCH does not automatically correspond with sRGB black.\n    test(\"rgb(from lch(100% 116 334) r g b)\", \"#fff\"); // Naive clip based mapping would give rgb(255, 150, 255).\n    test(\"rgb(from lch(0% 116 334) r g b)\", \"#2a0022\"); // Naive clip based mapping would give rgb(90, 0, 76). NOTE: 0% lightness in Lab/LCH does not automatically correspond with sRGB black.\n    test(\"rgb(from oklab(100% 0.365 -0.16) r g b)\", \"#fff\"); // Naive clip based mapping would give rgb(255, 92, 255).\n    test(\"rgb(from oklab(0% 0.365 -0.16) r g b)\", \"#000\"); // Naive clip based mapping would give rgb(19, 0, 24).\n    test(\"rgb(from oklch(100% 0.399 336.3) r g b)\", \"#fff\"); // Naive clip based mapping would give rgb(255, 91, 255).\n    test(\"rgb(from oklch(0% 0.399 336.3) r g b)\", \"#000\"); // Naive clip based mapping would give rgb(20, 0, 24).\n\n    // Testing replacement with 0.\n    test(\"rgb(from rebeccapurple 0 0 0)\", \"rgb(0, 0, 0)\");\n    test(\"rgb(from rebeccapurple 0 0 0 / 0)\", \"rgba(0, 0, 0, 0)\");\n    test(\"rgb(from rebeccapurple 0 g b / alpha)\", \"rgb(0, 51, 153)\");\n    test(\"rgb(from rebeccapurple r 0 b / alpha)\", \"rgb(102, 0, 153)\");\n    test(\"rgb(from rebeccapurple r g 0 / alpha)\", \"rgb(102, 51, 0)\");\n    test(\"rgb(from rebeccapurple r g b / 0)\", \"rgba(102, 51, 153, 0)\");\n    test(\n      \"rgb(from rgb(20%, 40%, 60%, 80%) 0 g b / alpha)\",\n      \"rgba(0, 102, 153, 0.8)\",\n    );\n    test(\n      \"rgb(from rgb(20%, 40%, 60%, 80%) r 0 b / alpha)\",\n      \"rgba(51, 0, 153, 0.8)\",\n    );\n    test(\n      \"rgb(from rgb(20%, 40%, 60%, 80%) r g 0 / alpha)\",\n      \"rgba(51, 102, 0, 0.8)\",\n    );\n    test(\"rgb(from rgb(20%, 40%, 60%, 80%) r g b / 0)\", \"rgba(51, 102, 153, 0)\");\n\n    // Testing replacement with a number.\n    test(\"rgb(from rebeccapurple 25 g b / alpha)\", \"rgb(25, 51, 153)\");\n    test(\"rgb(from rebeccapurple r 25 b / alpha)\", \"rgb(102, 25, 153)\");\n    test(\"rgb(from rebeccapurple r g 25 / alpha)\", \"rgb(102, 51, 25)\");\n    test(\"rgb(from rebeccapurple r g b / .25)\", \"rgba(102, 51, 153, 0.25)\");\n    test(\n      \"rgb(from rgb(20%, 40%, 60%, 80%) 25 g b / alpha)\",\n      \"rgba(25, 102, 153, 0.8)\",\n    );\n    test(\n      \"rgb(from rgb(20%, 40%, 60%, 80%) r 25 b / alpha)\",\n      \"rgba(51, 25, 153, 0.8)\",\n    );\n    test(\n      \"rgb(from rgb(20%, 40%, 60%, 80%) r g 25 / alpha)\",\n      \"rgba(51, 102, 25, 0.8)\",\n    );\n    test(\n      \"rgb(from rgb(20%, 40%, 60%, 80%) r g b / .20)\",\n      \"rgba(51, 102, 153, 0.2)\",\n    );\n\n    // Testing replacement with a percentage.\n    test(\"rgb(from rebeccapurple 20% g b / alpha)\", \"rgb(51, 51, 153)\");\n    test(\"rgb(from rebeccapurple r 20% b / alpha)\", \"rgb(102, 51, 153)\");\n    test(\"rgb(from rebeccapurple r g 20% / alpha)\", \"rgb(102, 51, 51)\");\n    test(\"rgb(from rebeccapurple r g b / 20%)\", \"rgba(102, 51, 153, 0.2)\");\n    test(\n      \"rgb(from rgb(20%, 40%, 60%, 80%) 20% g b / alpha)\",\n      \"rgba(51, 102, 153, 0.8)\",\n    );\n    test(\n      \"rgb(from rgb(20%, 40%, 60%, 80%) r 20% b / alpha)\",\n      \"rgba(51, 51, 153, 0.8)\",\n    );\n    test(\n      \"rgb(from rgb(20%, 40%, 60%, 80%) r g 20% / alpha)\",\n      \"rgba(51, 102, 51, 0.8)\",\n    );\n    test(\n      \"rgb(from rgb(20%, 40%, 60%, 80%) r g b / 20%)\",\n      \"rgba(51, 102, 153, 0.2)\",\n    );\n\n    // Testing replacement with a number for r, g, b but percent for alpha.\n    test(\"rgb(from rebeccapurple 25 g b / 25%)\", \"rgba(25, 51, 153, 0.25)\");\n    test(\"rgb(from rebeccapurple r 25 b / 25%)\", \"rgba(102, 25, 153, 0.25)\");\n    test(\"rgb(from rebeccapurple r g 25 / 25%)\", \"rgba(102, 51, 25, 0.25)\");\n    test(\n      \"rgb(from rgb(20%, 40%, 60%, 80%) 25 g b / 25%)\",\n      \"rgba(25, 102, 153, 0.25)\",\n    );\n    test(\n      \"rgb(from rgb(20%, 40%, 60%, 80%) r 25 b / 25%)\",\n      \"rgba(51, 25, 153, 0.25)\",\n    );\n    test(\n      \"rgb(from rgb(20%, 40%, 60%, 80%) r g 25 / 25%)\",\n      \"rgba(51, 102, 25, 0.25)\",\n    );\n\n    // Testing permutation.\n    test(\"rgb(from rebeccapurple g b r)\", \"rgb(51, 153, 102)\");\n    test(\"rgb(from rebeccapurple b alpha r / g)\", \"rgba(153, 1, 102, 1)\");\n    test(\"rgb(from rebeccapurple r r r / r)\", \"rgba(102, 102, 102, 1)\");\n    test(\"rgb(from rebeccapurple alpha alpha alpha / alpha)\", \"rgb(1, 1, 1)\");\n    test(\"rgb(from rgb(20%, 40%, 60%, 80%) g b r)\", \"rgb(102, 153, 51)\");\n    test(\"rgb(from rgb(20%, 40%, 60%, 80%) b alpha r / g)\", \"rgba(153, 1, 51, 1)\");\n    test(\"rgb(from rgb(20%, 40%, 60%, 80%) r r r / r)\", \"rgba(51, 51, 51, 1)\");\n    test(\n      \"rgb(from rgb(20%, 40%, 60%, 80%) alpha alpha alpha / alpha)\",\n      \"rgba(1, 1, 1, 0.8)\",\n    );\n\n    // Testing mixes of number and percentage. (These would not be allowed in the non-relative syntax).\n    test(\"rgb(from rebeccapurple r 20% 10)\", \"rgb(102, 51, 10)\");\n    test(\"rgb(from rebeccapurple r 10 20%)\", \"rgb(102, 10, 51)\");\n    test(\"rgb(from rebeccapurple 0% 10 10)\", \"rgb(0, 10, 10)\");\n    test(\"rgb(from rgb(20%, 40%, 60%, 80%) r 20% 10)\", \"rgb(51, 51, 10)\");\n    test(\"rgb(from rgb(20%, 40%, 60%, 80%) r 10 20%)\", \"rgb(51, 10, 51)\");\n    test(\"rgb(from rgb(20%, 40%, 60%, 80%) 0% 10 10)\", \"rgb(0, 10, 10)\");\n\n    // Testing with calc().\n    test(\"rgb(from rebeccapurple calc(r) calc(g) calc(b))\", \"rgb(102, 51, 153)\");\n    test(\"rgb(from rebeccapurple r calc(g * 2) 10)\", \"rgb(102, 102, 10)\");\n    test(\"rgb(from rebeccapurple b calc(r * .5) 10)\", \"rgb(153, 51, 10)\");\n    test(\"rgb(from rebeccapurple r calc(g * .5 + g * .5) 10)\", \"rgb(102, 51, 10)\");\n    test(\"rgb(from rebeccapurple r calc(b * .5 - g * .5) 10)\", \"rgb(102, 51, 10)\");\n    test(\n      \"rgb(from rgb(20%, 40%, 60%, 80%) calc(r) calc(g) calc(b) / calc(alpha))\",\n      \"rgba(51, 102, 153, 0.8)\",\n    );\n\n    // Testing with 'none'.\n    test(\"rgb(from rebeccapurple none none none)\", \"rgb(0, 0, 0)\");\n    test(\"rgb(from rebeccapurple none none none / none)\", \"rgba(0, 0, 0, 0)\");\n    test(\"rgb(from rebeccapurple r g none)\", \"rgb(102, 51, 0)\");\n    test(\"rgb(from rebeccapurple r g none / alpha)\", \"rgb(102, 51, 0)\");\n    test(\"rgb(from rebeccapurple r g b / none)\", \"rgba(102, 51, 153, 0)\");\n    test(\n      \"rgb(from rgb(20% 40% 60% / 80%) r g none / alpha)\",\n      \"rgba(51, 102, 0, 0.8)\",\n    );\n    test(\"rgb(from rgb(20% 40% 60% / 80%) r g b / none)\", \"rgba(51, 102, 153, 0)\");\n    // FIXME: Clarify with spec editors if 'none' should pass through to the constants.\n    test(\"rgb(from rgb(none none none) r g b)\", \"rgb(0, 0, 0)\");\n    test(\"rgb(from rgb(none none none / none) r g b / alpha)\", \"rgba(0, 0, 0, 0)\");\n    test(\"rgb(from rgb(20% none 60%) r g b)\", \"rgb(51, 0, 153)\");\n    test(\n      \"rgb(from rgb(20% 40% 60% / none) r g b / alpha)\",\n      \"rgba(51, 102, 153, 0)\",\n    );\n\n    // hsl(from ...)\n\n    // Testing no modifications.\n    test(\"hsl(from rebeccapurple h s l)\", \"rgb(102, 51, 153)\");\n    test(\"hsl(from rebeccapurple h s l / alpha)\", \"rgb(102, 51, 153)\");\n    test(\n      \"hsl(from rgb(20%, 40%, 60%, 80%) h s l / alpha)\",\n      \"rgba(51, 102, 153, 0.8)\",\n    );\n    test(\n      \"hsl(from hsl(120deg 20% 50% / .5) h s l / alpha)\",\n      \"rgba(102, 153, 102, 0.5)\",\n    );\n\n    // Test nesting relative colors.\n    test(\"hsl(from hsl(from rebeccapurple h s l) h s l)\", \"rgb(102, 51, 153)\");\n\n    // Testing non-sRGB origin colors to see gamut mapping.\n    test(\"hsl(from color(display-p3 0 1 0) h s l / alpha)\", \"rgb(0, 249, 66)\"); // Naive clip based mapping would give rgb(0, 255, 0).\n    test(\"hsl(from lab(100% 104.3 -50.9) h s l)\", \"rgb(255, 255, 255)\"); // Naive clip based mapping would give rgb(255, 150, 255).\n    test(\"hsl(from lab(0% 104.3 -50.9) h s l)\", \"rgb(42, 0, 34)\"); // Naive clip based mapping would give rgb(90, 0, 76). NOTE: 0% lightness in Lab/LCH does not automatically correspond with sRGB black,\n    test(\"hsl(from lch(100% 116 334) h s l)\", \"rgb(255, 255, 255)\"); // Naive clip based mapping would give rgb(255, 150, 255).\n    test(\"hsl(from lch(0% 116 334) h s l)\", \"rgb(42, 0, 34)\"); // Naive clip based mapping would give rgb(90, 0, 76). NOTE: 0% lightness in Lab/LCH does not automatically correspond with sRGB black,\n    test(\"hsl(from oklab(100% 0.365 -0.16) h s l)\", \"rgb(255, 255, 255)\"); // Naive clip based mapping would give rgb(255, 92, 255).\n    test(\"hsl(from oklab(0% 0.365 -0.16) h s l)\", \"rgb(0, 0, 0)\"); // Naive clip based mapping would give rgb(19, 0, 24).\n    test(\"hsl(from oklch(100% 0.399 336.3) h s l)\", \"rgb(255, 255, 255)\"); // Naive clip based mapping would give rgb(255, 91, 255).\n    test(\"hsl(from oklch(0% 0.399 336.3) h s l)\", \"rgb(0, 0, 0)\"); // Naive clip based mapping would give rgb(20, 0, 24).\n\n    // Testing replacement with 0.\n    test(\"hsl(from rebeccapurple 0 0% 0%)\", \"rgb(0, 0, 0)\");\n    test(\"hsl(from rebeccapurple 0deg 0% 0%)\", \"rgb(0, 0, 0)\");\n    test(\"hsl(from rebeccapurple 0 0% 0% / 0)\", \"rgba(0, 0, 0, 0)\");\n    test(\"hsl(from rebeccapurple 0deg 0% 0% / 0)\", \"rgba(0, 0, 0, 0)\");\n    test(\"hsl(from rebeccapurple 0 s l / alpha)\", \"rgb(153, 51, 51)\");\n    test(\"hsl(from rebeccapurple 0deg s l / alpha)\", \"rgb(153, 51, 51)\");\n    test(\"hsl(from rebeccapurple h 0% l / alpha)\", \"rgb(102, 102, 102)\");\n    test(\"hsl(from rebeccapurple h s 0% / alpha)\", \"rgb(0, 0, 0)\");\n    test(\"hsl(from rebeccapurple h s l / 0)\", \"rgba(102, 51, 153, 0)\");\n    test(\n      \"hsl(from rgb(20%, 40%, 60%, 80%) 0 s l / alpha)\",\n      \"rgba(153, 51, 51, 0.8)\",\n    );\n    test(\n      \"hsl(from rgb(20%, 40%, 60%, 80%) 0deg s l / alpha)\",\n      \"rgba(153, 51, 51, 0.8)\",\n    );\n    test(\n      \"hsl(from rgb(20%, 40%, 60%, 80%) h 0% l / alpha)\",\n      \"rgba(102, 102, 102, 0.8)\",\n    );\n    test(\"hsl(from rgb(20%, 40%, 60%, 80%) h s 0% / alpha)\", \"rgba(0, 0, 0, 0.8)\");\n    test(\"hsl(from rgb(20%, 40%, 60%, 80%) h s l / 0)\", \"rgba(51, 102, 153, 0)\");\n\n    // Testing replacement with a constant.\n    test(\"hsl(from rebeccapurple 25 s l / alpha)\", \"rgb(153, 94, 51)\");\n    test(\"hsl(from rebeccapurple 25deg s l / alpha)\", \"rgb(153, 94, 51)\");\n    test(\"hsl(from rebeccapurple h 20% l / alpha)\", \"rgb(102, 82, 122)\");\n    test(\"hsl(from rebeccapurple h s 20% / alpha)\", \"rgb(51, 25, 77)\");\n    test(\"hsl(from rebeccapurple h s l / .25)\", \"rgba(102, 51, 153, 0.25)\");\n    test(\n      \"hsl(from rgb(20%, 40%, 60%, 80%) 25 s l / alpha)\",\n      \"rgba(153, 94, 51, 0.8)\",\n    );\n    test(\n      \"hsl(from rgb(20%, 40%, 60%, 80%) 25deg s l / alpha)\",\n      \"rgba(153, 94, 51, 0.8)\",\n    );\n    test(\n      \"hsl(from rgb(20%, 40%, 60%, 80%) h 20% l / alpha)\",\n      \"rgba(82, 102, 122, 0.8)\",\n    );\n    test(\n      \"hsl(from rgb(20%, 40%, 60%, 80%) h s 20% / alpha)\",\n      \"rgba(25, 51, 77, 0.8)\",\n    );\n    test(\n      \"hsl(from rgb(20%, 40%, 60%, 80%) h s l / .2)\",\n      \"rgba(51, 102, 153, 0.2)\",\n    );\n\n    // Testing valid permutation (types match).\n    test(\"hsl(from rebeccapurple h l s)\", \"rgb(128, 77, 179)\");\n    test(\n      \"hsl(from rebeccapurple h calc(alpha * 100) l / calc(s / 100))\",\n      \"rgba(102, 0, 204, 0.5)\",\n    );\n    test(\n      \"hsl(from rebeccapurple h l l / calc(l / 100))\",\n      \"rgba(102, 61, 143, 0.4)\",\n    );\n    test(\n      \"hsl(from rebeccapurple h calc(alpha * 100) calc(alpha * 100) / calc(alpha * 100))\",\n      \"rgb(255, 255, 255)\",\n    );\n    test(\"hsl(from rgb(20%, 40%, 60%, 80%) h l s)\", \"rgb(77, 128, 179)\");\n    test(\n      \"hsl(from rgb(20%, 40%, 60%, 80%) h calc(alpha * 100) l / calc(s / 100))\",\n      \"rgba(20, 102, 184, 0.5)\",\n    );\n    test(\n      \"hsl(from rgb(20%, 40%, 60%, 80%) h l l / calc(l / 100))\",\n      \"rgba(61, 102, 143, 0.4)\",\n    );\n    test(\n      \"hsl(from rgb(20%, 40%, 60%, 80%) h calc(alpha * 100) calc(alpha * 100) / alpha)\",\n      \"rgba(163, 204, 245, 0.8)\",\n    );\n\n    // Testing with calc().\n    test(\"hsl(from rebeccapurple calc(h) calc(s) calc(l))\", \"rgb(102, 51, 153)\");\n    test(\n      \"hsl(from rgb(20%, 40%, 60%, 80%) calc(h) calc(s) calc(l) / calc(alpha))\",\n      \"rgba(51, 102, 153, 0.8)\",\n    );\n\n    // Testing with 'none'.\n    test(\"hsl(from rebeccapurple none none none)\", \"rgb(0, 0, 0)\");\n    test(\"hsl(from rebeccapurple none none none / none)\", \"rgba(0, 0, 0, 0)\");\n    test(\"hsl(from rebeccapurple h s none)\", \"rgb(0, 0, 0)\");\n    test(\"hsl(from rebeccapurple h s none / alpha)\", \"rgb(0, 0, 0)\");\n    test(\"hsl(from rebeccapurple h s l / none)\", \"rgba(102, 51, 153, 0)\");\n    test(\"hsl(from rebeccapurple none s l / alpha)\", \"rgb(153, 51, 51)\");\n    test(\n      \"hsl(from hsl(120deg 20% 50% / .5) h s none / alpha)\",\n      \"rgba(0, 0, 0, 0.5)\",\n    );\n    test(\n      \"hsl(from hsl(120deg 20% 50% / .5) h s l / none)\",\n      \"rgba(102, 153, 102, 0)\",\n    );\n    test(\n      \"hsl(from hsl(120deg 20% 50% / .5) none s l / alpha)\",\n      \"rgba(153, 102, 102, 0.5)\",\n    );\n    // FIXME: Clarify with spec editors if 'none' should pass through to the constants.\n    test(\"hsl(from hsl(none none none) h s l)\", \"rgb(0, 0, 0)\");\n    test(\"hsl(from hsl(none none none / none) h s l / alpha)\", \"rgba(0, 0, 0, 0)\");\n    test(\"hsl(from hsl(120deg none 50% / .5) h s l)\", \"rgb(128, 128, 128)\");\n    test(\n      \"hsl(from hsl(120deg 20% 50% / none) h s l / alpha)\",\n      \"rgba(102, 153, 102, 0)\",\n    );\n    test(\n      \"hsl(from hsl(none 20% 50% / .5) h s l / alpha)\",\n      \"rgba(153, 102, 102, 0.5)\",\n    );\n\n    // hwb(from ...)\n\n    // Testing no modifications.\n    test(\"hwb(from rebeccapurple h w b)\", \"rgb(102, 51, 153)\");\n    test(\"hwb(from rebeccapurple h w b / alpha)\", \"rgb(102, 51, 153)\");\n    test(\n      \"hwb(from rgb(20%, 40%, 60%, 80%) h w b / alpha)\",\n      \"rgba(51, 102, 153, 0.8)\",\n    );\n    test(\n      \"hwb(from hsl(120deg 20% 50% / .5) h w b / alpha)\",\n      \"rgba(102, 153, 102, 0.5)\",\n    );\n\n    // Test nesting relative colors.\n    test(\"hwb(from hwb(from rebeccapurple h w b) h w b)\", \"rgb(102, 51, 153)\");\n\n    // Testing non-sRGB origin colors to see gamut mapping.\n    test(\"hwb(from color(display-p3 0 1 0) h w b / alpha)\", \"rgb(0, 249, 66)\"); // Naive clip based mapping would give rgb(0, 255, 0).\n    test(\"hwb(from lab(100% 104.3 -50.9) h w b)\", \"rgb(255, 255, 255)\"); // Naive clip based mapping would give rgb(255, 150, 255).\n    test(\"hwb(from lab(0% 104.3 -50.9) h w b)\", \"rgb(42, 0, 34)\"); // Naive clip based mapping would give rgb(90, 0, 76). NOTE: 0% lightness in Lab/LCH does not automatically correspond with sRGB black,\n    test(\"hwb(from lch(100% 116 334) h w b)\", \"rgb(255, 255, 255)\"); // Naive clip based mapping would give rgb(255, 150, 255).\n    test(\"hwb(from lch(0% 116 334) h w b)\", \"rgb(42, 0, 34)\"); // Naive clip based mapping would give rgb(90, 0, 76). NOTE: 0% lightness in Lab/LCH does not automatically correspond with sRGB black,\n    test(\"hwb(from oklab(100% 0.365 -0.16) h w b)\", \"rgb(255, 255, 255)\"); // Naive clip based mapping would give rgb(255, 92, 255).\n    test(\"hwb(from oklab(0% 0.365 -0.16) h w b)\", \"rgb(0, 0, 0)\"); // Naive clip based mapping would give rgb(19, 0, 24).\n    test(\"hwb(from oklch(100% 0.399 336.3) h w b)\", \"rgb(255, 255, 255)\"); // Naive clip based mapping would give rgb(255, 91, 255).\n    test(\"hwb(from oklch(0% 0.399 336.3) h w b)\", \"rgb(0, 0, 0)\"); // Naive clip based mapping would give rgb(20, 0, 24).\n\n    // Testing replacement with 0.\n    test(\"hwb(from rebeccapurple 0 0% 0%)\", \"rgb(255, 0, 0)\");\n    test(\"hwb(from rebeccapurple 0deg 0% 0%)\", \"rgb(255, 0, 0)\");\n    test(\"hwb(from rebeccapurple 0 0% 0% / 0)\", \"rgba(255, 0, 0, 0)\");\n    test(\"hwb(from rebeccapurple 0deg 0% 0% / 0)\", \"rgba(255, 0, 0, 0)\");\n    test(\"hwb(from rebeccapurple 0 w b / alpha)\", \"rgb(153, 51, 51)\");\n    test(\"hwb(from rebeccapurple 0deg w b / alpha)\", \"rgb(153, 51, 51)\");\n    test(\"hwb(from rebeccapurple h 0% b / alpha)\", \"rgb(77, 0, 153)\");\n    test(\"hwb(from rebeccapurple h w 0% / alpha)\", \"rgb(153, 51, 255)\");\n    test(\"hwb(from rebeccapurple h w b / 0)\", \"rgba(102, 51, 153, 0)\");\n    test(\n      \"hwb(from rgb(20%, 40%, 60%, 80%) 0 w b / alpha)\",\n      \"rgba(153, 51, 51, 0.8)\",\n    );\n    test(\n      \"hwb(from rgb(20%, 40%, 60%, 80%) 0deg w b / alpha)\",\n      \"rgba(153, 51, 51, 0.8)\",\n    );\n    test(\n      \"hwb(from rgb(20%, 40%, 60%, 80%) h 0% b / alpha)\",\n      \"rgba(0, 77, 153, 0.8)\",\n    );\n    test(\n      \"hwb(from rgb(20%, 40%, 60%, 80%) h w 0% / alpha)\",\n      \"rgba(51, 153, 255, 0.8)\",\n    );\n    test(\"hwb(from rgb(20%, 40%, 60%, 80%) h w b / 0)\", \"rgba(51, 102, 153, 0)\");\n\n    // Testing replacement with a constant.\n    test(\"hwb(from rebeccapurple 25 w b / alpha)\", \"rgb(153, 94, 51)\");\n    test(\"hwb(from rebeccapurple 25deg w b / alpha)\", \"rgb(153, 94, 51)\");\n    test(\"hwb(from rebeccapurple h 20% b / alpha)\", \"rgb(102, 51, 153)\");\n    test(\"hwb(from rebeccapurple h w 20% / alpha)\", \"rgb(128, 51, 204)\");\n    test(\"hwb(from rebeccapurple h w b / .2)\", \"rgba(102, 51, 153, 0.2)\");\n    test(\n      \"hwb(from rgb(20%, 40%, 60%, 80%) 25 w b / alpha)\",\n      \"rgba(153, 94, 51, 0.8)\",\n    );\n    test(\n      \"hwb(from rgb(20%, 40%, 60%, 80%) 25deg w b / alpha)\",\n      \"rgba(153, 94, 51, 0.8)\",\n    );\n    test(\n      \"hwb(from rgb(20%, 40%, 60%, 80%) h 20% b / alpha)\",\n      \"rgba(51, 102, 153, 0.8)\",\n    );\n    test(\n      \"hwb(from rgb(20%, 40%, 60%, 80%) h w 20% / alpha)\",\n      \"rgba(51, 128, 204, 0.8)\",\n    );\n    test(\n      \"hwb(from rgb(20%, 40%, 60%, 80%) h w b / .2)\",\n      \"rgba(51, 102, 153, 0.2)\",\n    );\n\n    // Testing valid permutation (types match).\n    test(\"hwb(from rebeccapurple h b w)\", \"rgb(153, 102, 204)\");\n    test(\n      \"hwb(from rebeccapurple h calc(alpha * 100) w / calc(b / 100))\",\n      \"rgba(213, 213, 213, 0.4)\",\n    );\n    test(\n      \"hwb(from rebeccapurple h w w / calc(w / 100))\",\n      \"rgba(128, 51, 204, 0.2)\",\n    );\n    test(\n      \"hwb(from rebeccapurple h calc(alpha * 100) calc(alpha * 100) / alpha)\",\n      \"rgb(128, 128, 128)\",\n    );\n    test(\"hwb(from rgb(20%, 40%, 60%, 80%) h b w)\", \"rgb(102, 153, 204)\");\n    test(\n      \"hwb(from rgb(20%, 40%, 60%, 80%) h calc(alpha * 100) w / calc(b / 100))\",\n      \"rgba(204, 204, 204, 0.4)\",\n    );\n    test(\n      \"hwb(from rgb(20%, 40%, 60%, 80%) h w w / calc(w / 100))\",\n      \"rgba(51, 128, 204, 0.2)\",\n    );\n    test(\n      \"hwb(from rgb(20%, 40%, 60%, 80%) h calc(alpha * 100) calc(alpha * 100) / alpha)\",\n      \"rgba(128, 128, 128, 0.8)\",\n    );\n\n    // Testing with calc().\n    test(\"hwb(from rebeccapurple calc(h) calc(w) calc(b))\", \"rgb(102, 51, 153)\");\n    test(\n      \"hwb(from rgb(20%, 40%, 60%, 80%) calc(h) calc(w) calc(b) / calc(alpha))\",\n      \"rgba(51, 102, 153, 0.8)\",\n    );\n\n    // Testing with 'none'.\n    test(\"hwb(from rebeccapurple none none none)\", \"rgb(255, 0, 0)\");\n    test(\"hwb(from rebeccapurple none none none / none)\", \"rgba(255, 0, 0, 0)\");\n    test(\"hwb(from rebeccapurple h w none)\", \"rgb(153, 51, 255)\");\n    test(\"hwb(from rebeccapurple h w none / alpha)\", \"rgb(153, 51, 255)\");\n    test(\"hwb(from rebeccapurple h w b / none)\", \"rgba(102, 51, 153, 0)\");\n    test(\"hwb(from rebeccapurple none w b / alpha)\", \"rgb(153, 51, 51)\");\n    test(\n      \"hwb(from hwb(120deg 20% 50% / .5) h w none / alpha)\",\n      \"rgba(51, 255, 51, 0.5)\",\n    );\n    test(\n      \"hwb(from hwb(120deg 20% 50% / .5) h w b / none)\",\n      \"rgba(51, 128, 51, 0)\",\n    );\n    test(\n      \"hwb(from hwb(120deg 20% 50% / .5) none w b / alpha)\",\n      \"rgba(128, 51, 51, 0.5)\",\n    );\n    // FIXME: Clarify with spec editors if 'none' should pass through to the constants.\n    test(\"hwb(from hwb(none none none) h w b)\", \"rgb(255, 0, 0)\");\n    test(\n      \"hwb(from hwb(none none none / none) h w b / alpha)\",\n      \"rgba(255, 0, 0, 0)\",\n    );\n    test(\"hwb(from hwb(120deg none 50% / .5) h w b)\", \"rgb(0, 128, 0)\");\n    test(\n      \"hwb(from hwb(120deg 20% 50% / none) h w b / alpha)\",\n      \"rgba(51, 128, 51, 0)\",\n    );\n    test(\n      \"hwb(from hwb(none 20% 50% / .5) h w b / alpha)\",\n      \"rgba(128, 51, 51, 0.5)\",\n    );\n\n    for color_space in &[\"lab\", \"oklab\"] {\n      // Testing no modifications.\n      test(\n        &format!(\"{}(from {}(25% 20 50) l a b)\", color_space, color_space),\n        &format!(\"{}(25% 20 50)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(25% 20 50) l a b / alpha)\", color_space, color_space),\n        &format!(\"{}(25% 20 50)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(25% 20 50 / 40%) l a b / alpha)\", color_space, color_space),\n        &format!(\"{}(25% 20 50 / 0.4)\", color_space),\n      );\n      test(\n        &format!(\n          \"{}(from {}(200% 300 400 / 500%) l a b / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"{}(200% 300 400)\", color_space),\n      );\n      test(\n        &format!(\n          \"{}(from {}(-200% -300 -400 / -500%) l a b / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"{}(0% -300 -400 / 0)\", color_space),\n      );\n\n      // Test nesting relative colors.\n      test(\n        &format!(\n          \"{}(from {}(from {}(25% 20 50) l a b) l a b)\",\n          color_space, color_space, color_space\n        ),\n        &format!(\"{}(25% 20 50)\", color_space),\n      );\n\n      // Testing non-${colorSpace} origin to see conversion.\n      test(\n        &format!(\"{}(from color(display-p3 0 0 0) l a b / alpha)\", color_space),\n        &format!(\"{}(0% 0 0)\", color_space),\n      );\n\n      // Testing replacement with 0.\n      test(\n        &format!(\"{}(from {}(25% 20 50) 0% 0 0)\", color_space, color_space),\n        &format!(\"{}(0% 0 0)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(25% 20 50) 0% 0 0 / 0)\", color_space, color_space),\n        &format!(\"{}(0% 0 0 / 0)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(25% 20 50) 0% a b / alpha)\", color_space, color_space),\n        &format!(\"{}(0% 20 50)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(25% 20 50) l 0 b / alpha)\", color_space, color_space),\n        &format!(\"{}(25% 0 50)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(25% 20 50) l a 0 / alpha)\", color_space, color_space),\n        &format!(\"{}(25% 20 0)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(25% 20 50) l a b / 0)\", color_space, color_space),\n        &format!(\"{}(25% 20 50 / 0)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(25% 20 50 / 40%) 0% a b / alpha)\", color_space, color_space),\n        &format!(\"{}(0% 20 50 / 0.4)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(25% 20 50 / 40%) l 0 b / alpha)\", color_space, color_space),\n        &format!(\"{}(25% 0 50 / 0.4)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(25% 20 50 / 40%) l a 0 / alpha)\", color_space, color_space),\n        &format!(\"{}(25% 20 0 / 0.4)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(25% 20 50 / 40%) l a b / 0)\", color_space, color_space),\n        &format!(\"{}(25% 20 50 / 0)\", color_space),\n      );\n\n      // Testing replacement with a constant.\n      test(\n        &format!(\"{}(from {}(25% 20 50) 35% a b / alpha)\", color_space, color_space),\n        &format!(\"{}(35% 20 50)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(25% 20 50) l 35 b / alpha)\", color_space, color_space),\n        &format!(\"{}(25% 35 50)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(25% 20 50) l a 35 / alpha)\", color_space, color_space),\n        &format!(\"{}(25% 20 35)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(25% 20 50) l a b / .35)\", color_space, color_space),\n        &format!(\"{}(25% 20 50 / 0.35)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(25% 20 50 / 40%) 35% a b / alpha)\", color_space, color_space),\n        &format!(\"{}(35% 20 50 / 0.4)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(25% 20 50 / 40%) l 35 b / alpha)\", color_space, color_space),\n        &format!(\"{}(25% 35 50 / 0.4)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(25% 20 50 / 40%) l a 35 / alpha)\", color_space, color_space),\n        &format!(\"{}(25% 20 35 / 0.4)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(25% 20 50 / 40%) l a b / .35)\", color_space, color_space),\n        &format!(\"{}(25% 20 50 / 0.35)\", color_space),\n      );\n      test(\n        &format!(\n          \"{}(from {}(70% 45 30 / 40%) 200% 300 400 / 500)\",\n          color_space, color_space\n        ),\n        &format!(\"{}(200% 300 400)\", color_space),\n      );\n      test(\n        &format!(\n          \"{}(from {}(70% 45 30 / 40%) -200% -300 -400 / -500)\",\n          color_space, color_space\n        ),\n        &format!(\"{}(0% -300 -400 / 0)\", color_space),\n      );\n\n      // Testing valid permutation (types match).\n      test(\n        &format!(\"{}(from {}(25% 20 50) l b a)\", color_space, color_space),\n        &format!(\"{}(25% 50 20)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(25% 20 50) l a a / a)\", color_space, color_space),\n        &format!(\"{}(25% 20 20)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(25% 20 50 / 40%) l b a)\", color_space, color_space),\n        &format!(\"{}(25% 50 20)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(25% 20 50 / 40%) l a a / a)\", color_space, color_space),\n        &format!(\"{}(25% 20 20)\", color_space),\n      );\n\n      // Testing with calc().\n      test(\n        &format!(\n          \"{}(from {}(25% 20 50) calc(l) calc(a) calc(b))\",\n          color_space, color_space\n        ),\n        &format!(\"{}(25% 20 50)\", color_space),\n      );\n      test(\n        &format!(\n          \"{}(from {}(25% 20 50 / 40%) calc(l) calc(a) calc(b) / calc(alpha))\",\n          color_space, color_space\n        ),\n        &format!(\"{}(25% 20 50 / 0.4)\", color_space),\n      );\n\n      // Testing with 'none'.\n      test(\n        &format!(\"{}(from {}(25% 20 50) none none none)\", color_space, color_space),\n        &format!(\"{}(none none none)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(25% 20 50) none none none / none)\", color_space, color_space),\n        &format!(\"{}(none none none / none)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(25% 20 50) l a none)\", color_space, color_space),\n        &format!(\"{}(25% 20 none)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(25% 20 50) l a none / alpha)\", color_space, color_space),\n        &format!(\"{}(25% 20 none)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(25% 20 50) l a b / none)\", color_space, color_space),\n        &format!(\"{}(25% 20 50 / none)\", color_space),\n      );\n      test(\n        &format!(\n          \"{}(from {}(25% 20 50 / 40%) l a none / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"{}(25% 20 none / 0.4)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(25% 20 50 / 40%) l a b / none)\", color_space, color_space),\n        &format!(\"{}(25% 20 50 / none)\", color_space),\n      );\n      // FIXME: Clarify with spec editors if 'none' should pass through to the constants.\n      test(\n        &format!(\"{}(from {}(none none none) l a b)\", color_space, color_space),\n        &format!(\"{}(0% 0 0)\", color_space),\n      );\n      test(\n        &format!(\n          \"{}(from {}(none none none / none) l a b / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"{}(0% 0 0 / 0)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(25% none 50) l a b)\", color_space, color_space),\n        &format!(\"{}(25% 0 50)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(25% 20 50 / none) l a b / alpha)\", color_space, color_space),\n        &format!(\"{}(25% 20 50 / 0)\", color_space),\n      );\n    }\n\n    // test_valid_value\\(`color`, `\\$\\{colorSpace\\}\\(from \\$\\{colorSpace\\}\\((.*?)`,\\s*`\\$\\{colorSpace\\}(.*?)`\\)\n    // test(&format!(\"{}(from {}($1\", color_space, color_space), &format!(\"{}$2\", color_space))\n\n    for color_space in &[\"lch\", \"oklch\"] {\n      // Testing no modifications.\n      test(\n        &format!(\"{}(from {}(70% 45 30) l c h)\", color_space, color_space),\n        &format!(\"{}(70% 45 30)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(70% 45 30) l c h / alpha)\", color_space, color_space),\n        &format!(\"{}(70% 45 30)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(70% 45 30 / 40%) l c h / alpha)\", color_space, color_space),\n        &format!(\"{}(70% 45 30 / 0.4)\", color_space),\n      );\n      test(\n        &format!(\n          \"{}(from {}(200% 300 400 / 500%) l c h / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"{}(200% 300 40)\", color_space),\n      );\n      test(\n        &format!(\n          \"{}(from {}(-200% -300 -400 / -500%) l c h / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"{}(0% 0 320 / 0)\", color_space),\n      );\n\n      // Test nesting relative colors.\n      test(\n        &format!(\n          \"{}(from {}(from {}(70% 45 30) l c h) l c h)\",\n          color_space, color_space, color_space\n        ),\n        &format!(\"{}(70% 45 30)\", color_space),\n      );\n\n      // Testing non-sRGB origin colors (no gamut mapping will happen since the destination is not a bounded RGB color space).\n      test(\n        &format!(\"{}(from color(display-p3 0 0 0) l c h / alpha)\", color_space),\n        &format!(\"{}(0% 0 0)\", color_space),\n      );\n\n      // Testing replacement with 0.\n      test(\n        &format!(\"{}(from {}(70% 45 30) 0% 0 0)\", color_space, color_space),\n        &format!(\"{}(0% 0 0)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(70% 45 30) 0% 0 0deg)\", color_space, color_space),\n        &format!(\"{}(0% 0 0)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(70% 45 30) 0% 0 0 / 0)\", color_space, color_space),\n        &format!(\"{}(0% 0 0 / 0)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(70% 45 30) 0% 0 0deg / 0)\", color_space, color_space),\n        &format!(\"{}(0% 0 0 / 0)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(70% 45 30) 0% c h / alpha)\", color_space, color_space),\n        &format!(\"{}(0% 45 30)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(70% 45 30) l 0 h / alpha)\", color_space, color_space),\n        &format!(\"{}(70% 0 30)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(70% 45 30) l c 0 / alpha)\", color_space, color_space),\n        &format!(\"{}(70% 45 0)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(70% 45 30) l c 0deg / alpha)\", color_space, color_space),\n        &format!(\"{}(70% 45 0)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(70% 45 30) l c h / 0)\", color_space, color_space),\n        &format!(\"{}(70% 45 30 / 0)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(70% 45 30 / 40%) 0% c h / alpha)\", color_space, color_space),\n        &format!(\"{}(0% 45 30 / 0.4)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(70% 45 30 / 40%) l 0 h / alpha)\", color_space, color_space),\n        &format!(\"{}(70% 0 30 / 0.4)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(70% 45 30 / 40%) l c 0 / alpha)\", color_space, color_space),\n        &format!(\"{}(70% 45 0 / 0.4)\", color_space),\n      );\n      test(\n        &format!(\n          \"{}(from {}(70% 45 30 / 40%) l c 0deg / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"{}(70% 45 0 / 0.4)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(70% 45 30 / 40%) l c h / 0)\", color_space, color_space),\n        &format!(\"{}(70% 45 30 / 0)\", color_space),\n      );\n\n      // Testing replacement with a constant.\n      test(\n        &format!(\"{}(from {}(70% 45 30) 25% c h / alpha)\", color_space, color_space),\n        &format!(\"{}(25% 45 30)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(70% 45 30) l 25 h / alpha)\", color_space, color_space),\n        &format!(\"{}(70% 25 30)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(70% 45 30) l c 25 / alpha)\", color_space, color_space),\n        &format!(\"{}(70% 45 25)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(70% 45 30) l c 25deg / alpha)\", color_space, color_space),\n        &format!(\"{}(70% 45 25)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(70% 45 30) l c h / .25)\", color_space, color_space),\n        &format!(\"{}(70% 45 30 / 0.25)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(70% 45 30 / 40%) 25% c h / alpha)\", color_space, color_space),\n        &format!(\"{}(25% 45 30 / 0.4)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(70% 45 30 / 40%) l 25 h / alpha)\", color_space, color_space),\n        &format!(\"{}(70% 25 30 / 0.4)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(70% 45 30 / 40%) l c 25 / alpha)\", color_space, color_space),\n        &format!(\"{}(70% 45 25 / 0.4)\", color_space),\n      );\n      test(\n        &format!(\n          \"{}(from {}(70% 45 30 / 40%) l c 25deg / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"{}(70% 45 25 / 0.4)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(70% 45 30 / 40%) l c h / .25)\", color_space, color_space),\n        &format!(\"{}(70% 45 30 / 0.25)\", color_space),\n      );\n      test(\n        &format!(\n          \"{}(from {}(70% 45 30 / 40%) 200% 300 400 / 500)\",\n          color_space, color_space\n        ),\n        &format!(\"{}(200% 300 400)\", color_space),\n      );\n      test(\n        &format!(\n          \"{}(from {}(70% 45 30 / 40%) -200% -300 -400 / -500)\",\n          color_space, color_space\n        ),\n        &format!(\"{}(0% 0 -400 / 0)\", color_space),\n      );\n      test(\n        &format!(\n          \"{}(from {}(70% 45 30 / 40%) 50% 120 400deg / 500)\",\n          color_space, color_space\n        ),\n        &format!(\"{}(50% 120 400)\", color_space),\n      );\n      test(\n        &format!(\n          \"{}(from {}(70% 45 30 / 40%) 50% 120 -400deg / -500)\",\n          color_space, color_space\n        ),\n        &format!(\"{}(50% 120 -400 / 0)\", color_space),\n      );\n\n      // Testing valid permutation (types match).\n      // NOTE: 'c' is a valid hue, as hue is <angle>|<number>.\n      test(\n        &format!(\"{}(from {}(70% 45 30) alpha c h / l)\", color_space, color_space),\n        &format!(\n          \"{}(1 45 30 / {})\",\n          color_space,\n          if *color_space == \"lch\" { \"1\" } else { \".7\" }\n        ),\n      );\n      test(\n        &format!(\"{}(from {}(70% 45 30) l c c / alpha)\", color_space, color_space),\n        &format!(\"{}(70% 45 45)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(70% 45 30) alpha c h / alpha)\", color_space, color_space),\n        &format!(\"{}(1 45 30)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(70% 45 30) alpha c c / alpha)\", color_space, color_space),\n        &format!(\"{}(1 45 45)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(70% 45 30 / 40%) alpha c h / l)\", color_space, color_space),\n        &format!(\n          \"{}(.4 45 30 / {})\",\n          color_space,\n          if *color_space == \"lch\" { \"1\" } else { \".7\" }\n        ),\n      );\n      test(\n        &format!(\"{}(from {}(70% 45 30 / 40%) l c c / alpha)\", color_space, color_space),\n        &format!(\"{}(70% 45 45 / 0.4)\", color_space),\n      );\n      test(\n        &format!(\n          \"{}(from {}(70% 45 30 / 40%) alpha c h / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"{}(.4 45 30 / 0.4)\", color_space),\n      );\n      test(\n        &format!(\n          \"{}(from {}(70% 45 30 / 40%) alpha c c / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"{}(.4 45 45 / 0.4)\", color_space),\n      );\n\n      // Testing with calc().\n      test(\n        &format!(\n          \"{}(from {}(70% 45 30) calc(l) calc(c) calc(h))\",\n          color_space, color_space\n        ),\n        &format!(\"{}(70% 45 30)\", color_space),\n      );\n      test(\n        &format!(\n          \"{}(from {}(70% 45 30 / 40%) calc(l) calc(c) calc(h) / calc(alpha))\",\n          color_space, color_space\n        ),\n        &format!(\"{}(70% 45 30 / 0.4)\", color_space),\n      );\n\n      // Testing with 'none'.\n      test(\n        &format!(\"{}(from {}(70% 45 30) none none none)\", color_space, color_space),\n        &format!(\"{}(none none none)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(70% 45 30) none none none / none)\", color_space, color_space),\n        &format!(\"{}(none none none / none)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(70% 45 30) l c none)\", color_space, color_space),\n        &format!(\"{}(70% 45 none)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(70% 45 30) l c none / alpha)\", color_space, color_space),\n        &format!(\"{}(70% 45 none)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(70% 45 30) l c h / none)\", color_space, color_space),\n        &format!(\"{}(70% 45 30 / none)\", color_space),\n      );\n      test(\n        &format!(\n          \"{}(from {}(70% 45 30 / 40%) l c none / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"{}(70% 45 none / 0.4)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(70% 45 30 / 40%) l c h / none)\", color_space, color_space),\n        &format!(\"{}(70% 45 30 / none)\", color_space),\n      );\n      // FIXME: Clarify with spec editors if 'none' should pass through to the constants.\n      test(\n        &format!(\"{}(from {}(none none none) l c h)\", color_space, color_space),\n        &format!(\"{}(0% 0 0)\", color_space),\n      );\n      test(\n        &format!(\n          \"{}(from {}(none none none / none) l c h / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"{}(0% 0 0 / 0)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(70% none 30) l c h)\", color_space, color_space),\n        &format!(\"{}(70% 0 30)\", color_space),\n      );\n      test(\n        &format!(\"{}(from {}(70% 45 30 / none) l c h / alpha)\", color_space, color_space),\n        &format!(\"{}(70% 45 30 / 0)\", color_space),\n      );\n    }\n\n    // test_valid_value\\(`color`, `color\\(from color\\(\\$\\{colorSpace\\}(.*?) \\$\\{colorSpace\\}(.*?)`,\\s*`color\\(\\$\\{colorSpace\\}(.*?)`\\)\n    // test(&format!(\"color(from color({}$1 {}$2\", color_space, color_space), &format!(\"color({}$3\", color_space))\n\n    for color_space in &[\"srgb\", \"srgb-linear\", \"a98-rgb\", \"rec2020\", \"prophoto-rgb\"] {\n      // Testing no modifications.\n      test(\n        &format!(\"color(from color({} 0.7 0.5 0.3) {} r g b)\", color_space, color_space),\n        &format!(\"color({} 0.7 0.5 0.3)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 0.7 0.5 0.3) {} r g b / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 0.7 0.5 0.3)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 0.7 0.5 0.3 / 40%) {} r g b)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 0.7 0.5 0.3)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 0.7 0.5 0.3 / 40%) {} r g b / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 0.7 0.5 0.3 / 0.4)\", color_space),\n      );\n\n      // Test nesting relative colors.\n      test(\n        &format!(\n          \"color(from color(from color({} 0.7 0.5 0.3) {} r g b) {} r g b)\",\n          color_space, color_space, color_space\n        ),\n        &format!(\"color({} 0.7 0.5 0.3)\", color_space),\n      );\n\n      // Testing replacement with 0.\n      test(\n        &format!(\"color(from color({} 0.7 0.5 0.3) {} 0 0 0)\", color_space, color_space),\n        &format!(\"color({} 0 0 0)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 0.7 0.5 0.3) {} 0 0 0 / 0)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 0 0 0 / 0)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 0.7 0.5 0.3) {} 0 g b / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 0 0.5 0.3)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 0.7 0.5 0.3) {} r 0 b / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 0.7 0 0.3)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 0.7 0.5 0.3) {} r g 0 / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 0.7 0.5 0)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 0.7 0.5 0.3) {} r g b / 0)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 0.7 0.5 0.3 / 0)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 0.7 0.5 0.3 / 40%) {} 0 g b / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 0 0.5 0.3 / 0.4)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 0.7 0.5 0.3 / 40%) {} r 0 b / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 0.7 0 0.3 / 0.4)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 0.7 0.5 0.3 / 40%) {} r g 0 / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 0.7 0.5 0 / 0.4)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 0.7 0.5 0.3 / 40%) {} r g b / 0)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 0.7 0.5 0.3 / 0)\", color_space),\n      );\n\n      // Testing replacement with a constant.\n      test(\n        &format!(\n          \"color(from color({} 0.7 0.5 0.3) {} 0.2 g b / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 0.2 0.5 0.3)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 0.7 0.5 0.3) {} 20% g b / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 0.2 0.5 0.3)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 0.7 0.5 0.3) {} r 0.2 b / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 0.7 0.2 0.3)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 0.7 0.5 0.3) {} r 20% b / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 0.7 0.2 0.3)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 0.7 0.5 0.3) {} r g 0.2 / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 0.7 0.5 0.2)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 0.7 0.5 0.3) {} r g 20% / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 0.7 0.5 0.2)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 0.7 0.5 0.3) {} r g b / 0.2)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 0.7 0.5 0.3 / 0.2)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 0.7 0.5 0.3) {} r g b / 20%)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 0.7 0.5 0.3 / 0.2)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 0.7 0.5 0.3 / 40%) {} 0.2 g b / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 0.2 0.5 0.3 / 0.4)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 0.7 0.5 0.3 / 40%) {} 20% g b / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 0.2 0.5 0.3 / 0.4)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 0.7 0.5 0.3 / 40%) {} r 0.2 b / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 0.7 0.2 0.3 / 0.4)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 0.7 0.5 0.3 / 40%) {} r 20% b / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 0.7 0.2 0.3 / 0.4)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 0.7 0.5 0.3 / 40%) {} r g 0.2 / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 0.7 0.5 0.2 / 0.4)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 0.7 0.5 0.3 / 40%) {} r g 20% / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 0.7 0.5 0.2 / 0.4)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 0.7 0.5 0.3 / 40%) {} r g b / 0.2)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 0.7 0.5 0.3 / 0.2)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 0.7 0.5 0.3 / 40%) {} r g b / 20%)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 0.7 0.5 0.3 / 0.2)\", color_space),\n      );\n      test(\n        &format!(\"color(from color({} 0.7 0.5 0.3) {} 2 3 4)\", color_space, color_space),\n        &format!(\"color({} 2 3 4)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 0.7 0.5 0.3) {} 2 3 4 / 5)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 2 3 4)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 0.7 0.5 0.3) {} -2 -3 -4)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} -2 -3 -4)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 0.7 0.5 0.3) {} -2 -3 -4 / -5)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} -2 -3 -4 / 0)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 0.7 0.5 0.3) {} 200% 300% 400%)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 2 3 4)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 0.7 0.5 0.3) {} 200% 300% 400% / 500%)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 2 3 4)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 0.7 0.5 0.3) {} -200% -300% -400%)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} -2 -3 -4)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 0.7 0.5 0.3) {} -200% -300% -400% / -500%)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} -2 -3 -4 / 0)\", color_space),\n      );\n\n      // Testing valid permutation (types match).\n      test(\n        &format!(\"color(from color({} 0.7 0.5 0.3) {} g b r)\", color_space, color_space),\n        &format!(\"color({} 0.5 0.3 0.7)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 0.7 0.5 0.3) {} b alpha r / g)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 0.3 1 0.7 / 0.5)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 0.7 0.5 0.3) {} r r r / r)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 0.7 0.7 0.7 / 0.7)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 0.7 0.5 0.3) {} alpha alpha alpha / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 1 1 1)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 0.7 0.5 0.3 / 40%) {} g b r)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 0.5 0.3 0.7)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 0.7 0.5 0.3 / 40%) {} b alpha r / g)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 0.3 0.4 0.7 / 0.5)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 0.7 0.5 0.3 / 40%) {} r r r / r)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 0.7 0.7 0.7 / 0.7)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 0.7 0.5 0.3 / 40%) {} alpha alpha alpha / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 0.4 0.4 0.4 / 0.4)\", color_space),\n      );\n\n      // Testing out of gamut components.\n      test(\n        &format!(\"color(from color({} 1.7 1.5 1.3) {} r g b)\", color_space, color_space),\n        &format!(\"color({} 1.7 1.5 1.3)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 1.7 1.5 1.3) {} r g b / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 1.7 1.5 1.3)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 1.7 1.5 1.3 / 140%) {} r g b)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 1.7 1.5 1.3)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 1.7 1.5 1.3 / 140%) {} r g b / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 1.7 1.5 1.3)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} -0.7 -0.5 -0.3) {} r g b)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} -0.7 -0.5 -0.3)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} -0.7 -0.5 -0.3) {} r g b / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} -0.7 -0.5 -0.3)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} -0.7 -0.5 -0.3 / -40%) {} r g b)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} -0.7 -0.5 -0.3)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} -0.7 -0.5 -0.3 / -40%) {} r g b / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} -0.7 -0.5 -0.3 / 0)\", color_space),\n      );\n\n      // Testing with calc().\n      test(\n        &format!(\n          \"color(from color({} 0.7 0.5 0.3) {} calc(r) calc(g) calc(b))\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 0.7 0.5 0.3)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 0.7 0.5 0.3 / 40%) {} calc(r) calc(g) calc(b) / calc(alpha))\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 0.7 0.5 0.3 / 0.4)\", color_space),\n      );\n\n      // Testing with 'none'.\n      test(\n        &format!(\n          \"color(from color({} 0.7 0.5 0.3) {} none none none)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} none none none)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 0.7 0.5 0.3) {} none none none / none)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} none none none / none)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 0.7 0.5 0.3) {} r g none)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 0.7 0.5 none)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 0.7 0.5 0.3) {} r g none / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 0.7 0.5 none)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 0.7 0.5 0.3) {} r g b / none)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 0.7 0.5 0.3 / none)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 0.7 0.5 0.3 / 40%) {} r g none / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 0.7 0.5 none / 0.4)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 0.7 0.5 0.3 / 40%) {} r g b / none)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 0.7 0.5 0.3 / none)\", color_space),\n      );\n      // FIXME: Clarify with spec editors if 'none' should pass through to the constants.\n      test(\n        &format!(\n          \"color(from color({} none none none) {} r g b)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 0 0 0)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} none none none / none) {} r g b / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 0 0 0 / 0)\", color_space),\n      );\n      test(\n        &format!(\"color(from color({} 0.7 none 0.3) {} r g b)\", color_space, color_space),\n        &format!(\"color({} 0.7 0 0.3)\", color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 0.7 0.5 0.3 / none) {} r g b / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 0.7 0.5 0.3 / 0)\", color_space),\n      );\n    }\n\n    // test_valid_value\\(`color`, `color\\(from color\\(\\$\\{colorSpace\\}(.*?) \\$\\{colorSpace\\}(.*?)`,\\s*`color\\(\\$\\{resultColorSpace\\}(.*?)`\\)\n    // test(&format!(\"color(from color({}$1 {}$2\", color_space, color_space), &format!(\"color({}$3\", result_color_space))\n\n    for color_space in &[\"xyz\", \"xyz-d50\", \"xyz-d65\"] {\n      let result_color_space = if *color_space == \"xyz\" { \"xyz-d65\" } else { color_space };\n\n      // Testing no modifications.\n      test(\n        &format!(\"color(from color({} 7 -20.5 100) {} x y z)\", color_space, color_space),\n        &format!(\"color({} 7 -20.5 100)\", result_color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 7 -20.5 100) {} x y z / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 7 -20.5 100)\", result_color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 7 -20.5 100 / 40%) {} x y z)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 7 -20.5 100)\", result_color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 7 -20.5 100 / 40%) {} x y z / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 7 -20.5 100 / 0.4)\", result_color_space),\n      );\n\n      // Test nesting relative colors.\n      test(\n        &format!(\n          \"color(from color(from color({} 7 -20.5 100) {} x y z) {} x y z)\",\n          color_space, color_space, color_space\n        ),\n        &format!(\"color({} 7 -20.5 100)\", result_color_space),\n      );\n\n      // Testing replacement with 0.\n      test(\n        &format!(\"color(from color({} 7 -20.5 100) {} 0 0 0)\", color_space, color_space),\n        &format!(\"color({} 0 0 0)\", result_color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 7 -20.5 100) {} 0 0 0 / 0)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 0 0 0 / 0)\", result_color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 7 -20.5 100) {} 0 y z / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 0 -20.5 100)\", result_color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 7 -20.5 100) {} x 0 z / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 7 0 100)\", result_color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 7 -20.5 100) {} x y 0 / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 7 -20.5 0)\", result_color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 7 -20.5 100) {} x y z / 0)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 7 -20.5 100 / 0)\", result_color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 7 -20.5 100 / 40%) {} 0 y z / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 0 -20.5 100 / 0.4)\", result_color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 7 -20.5 100 / 40%) {} x 0 z / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 7 0 100 / 0.4)\", result_color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 7 -20.5 100 / 40%) {} x y 0 / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 7 -20.5 0 / 0.4)\", result_color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 7 -20.5 100 / 40%) {} x y z / 0)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 7 -20.5 100 / 0)\", result_color_space),\n      );\n\n      // Testing replacement with a constant.\n      test(\n        &format!(\n          \"color(from color({} 7 -20.5 100) {} 0.2 y z / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 0.2 -20.5 100)\", result_color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 7 -20.5 100) {} x 0.2 z / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 7 0.2 100)\", result_color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 7 -20.5 100) {} x y 0.2 / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 7 -20.5 0.2)\", result_color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 7 -20.5 100) {} x y z / 0.2)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 7 -20.5 100 / 0.2)\", result_color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 7 -20.5 100) {} x y z / 20%)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 7 -20.5 100 / 0.2)\", result_color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 7 -20.5 100 / 40%) {} 0.2 y z / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 0.2 -20.5 100 / 0.4)\", result_color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 7 -20.5 100 / 40%) {} x 0.2 z / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 7 0.2 100 / 0.4)\", result_color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 7 -20.5 100 / 40%) {} x y 0.2 / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 7 -20.5 0.2 / 0.4)\", result_color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 7 -20.5 100 / 40%) {} x y z / 0.2)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 7 -20.5 100 / 0.2)\", result_color_space),\n      );\n\n      // Testing valid permutation (types match).\n      test(\n        &format!(\"color(from color({} 7 -20.5 100) {} y z x)\", color_space, color_space),\n        &format!(\"color({} -20.5 100 7)\", result_color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 7 -20.5 100) {} x x x / x)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 7 7 7)\", result_color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 7 -20.5 100 / 40%) {} y z x)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} -20.5 100 7)\", result_color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 7 -20.5 100 / 40%) {} x x x / x)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 7 7 7)\", result_color_space),\n      );\n\n      // Testing with calc().\n      test(\n        &format!(\n          \"color(from color({} 7 -20.5 100) {} calc(x) calc(y) calc(z))\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 7 -20.5 100)\", result_color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 7 -20.5 100 / 40%) {} calc(x) calc(y) calc(z) / calc(alpha))\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 7 -20.5 100 / 0.4)\", result_color_space),\n      );\n\n      // Testing with 'none'.\n      test(\n        &format!(\n          \"color(from color({} 7 -20.5 100) {} none none none)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} none none none)\", result_color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 7 -20.5 100) {} none none none / none)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} none none none / none)\", result_color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 7 -20.5 100) {} x y none)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 7 -20.5 none)\", result_color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 7 -20.5 100) {} x y none / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 7 -20.5 none)\", result_color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 7 -20.5 100) {} x y z / none)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 7 -20.5 100 / none)\", result_color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 7 -20.5 100 / 40%) {} x y none / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 7 -20.5 none / 0.4)\", result_color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 7 -20.5 100 / 40%) {} x y z / none)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 7 -20.5 100 / none)\", result_color_space),\n      );\n      // FIXME: Clarify with spec editors if 'none' should pass through to the constants.\n      test(\n        &format!(\n          \"color(from color({} none none none) {} x y z)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 0 0 0)\", result_color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} none none none / none) {} x y z / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 0 0 0 / 0)\", result_color_space),\n      );\n      test(\n        &format!(\"color(from color({} 7 none 100) {} x y z)\", color_space, color_space),\n        &format!(\"color({} 7 0 100)\", result_color_space),\n      );\n      test(\n        &format!(\n          \"color(from color({} 7 -20.5 100 / none) {} x y z / alpha)\",\n          color_space, color_space\n        ),\n        &format!(\"color({} 7 -20.5 100 / 0)\", result_color_space),\n      );\n\n      // https://github.com/web-platform-tests/wpt/blob/master/css/css-color/parsing/relative-color-invalid.html\n      minify_test(\n        \".foo{color:rgb(from rebeccapurple r 10deg 10)}\",\n        \".foo{color:rgb(from rebeccapurple r 10deg 10)}\",\n      );\n      minify_test(\n        \".foo{color:rgb(from rebeccapurple l g b)}\",\n        \".foo{color:rgb(from rebeccapurple l g b)}\",\n      );\n      minify_test(\n        \".foo{color:hsl(from rebeccapurple s h l)}\",\n        \".foo{color:hsl(from rebeccapurple s h l)}\",\n      );\n      minify_test(\".foo{color:hsl(from rebeccapurple s s s / s)}\", \".foo{color:#bfaa40}\");\n      minify_test(\n        \".foo{color:hsl(from rebeccapurple calc(alpha * 100) calc(alpha * 100) calc(alpha * 100) / alpha)}\",\n        \".foo{color:#fff}\",\n      );\n    }\n  }\n\n  #[test]\n  fn test_color_mix() {\n    minify_test(\n      \".foo { color: color-mix(in lab, purple 50%, plum 50%); }\",\n      \".foo{color:lab(51.5117% 43.3777 -29.0443)}\",\n    );\n    minify_test(\n      \".foo { color: color-mix(in lch, peru 40%, palegoldenrod); }\",\n      \".foo{color:lch(79.7255% 40.4542 84.7634)}\",\n    );\n    minify_test(\n      \".foo { color: color-mix(in lch, teal 65%, olive); }\",\n      \".foo{color:lch(49.4431% 40.4806 162.546)}\",\n    );\n    minify_test(\n      \".foo { color: color-mix(in lch, white, black); }\",\n      \".foo{color:lch(50% 0 none)}\",\n    );\n    minify_test(\n      \".foo { color: color-mix(in xyz, rgb(82.02% 30.21% 35.02%) 75.23%, rgb(5.64% 55.94% 85.31%)); }\",\n      \".foo{color:color(xyz .287458 .208776 .260566)}\",\n    );\n    minify_test(\n      \".foo { color: color-mix(in lch, white, blue); }\",\n      \".foo{color:lch(64.7842% 65.6007 301.364)}\",\n    );\n    minify_test(\n      \".foo { color: color-mix(in oklch, white, blue); }\",\n      \".foo{color:oklch(72.6007% .156607 264.052)}\",\n    );\n    minify_test(\n      \".foo { color: color-mix(in srgb, white, blue); }\",\n      \".foo{color:#8080ff}\",\n    );\n    minify_test(\n      \".foo { color: color-mix(in lch, blue, white); }\",\n      \".foo{color:lch(64.7842% 65.6007 301.364)}\",\n    );\n    minify_test(\n      \".foo { color: color-mix(in oklch, blue, white); }\",\n      \".foo{color:oklch(72.6007% .156607 264.052)}\",\n    );\n    minify_test(\n      \".foo { color: color-mix(in srgb, blue, white); }\",\n      \".foo{color:#8080ff}\",\n    );\n    // minify_test(\".foo { color: color-mix(in hsl, color(display-p3 0 1 0) 80%, yellow); }\", \".foo{color:hsl(108 100% 49.9184%) }\");\n    minify_test(\n      \".foo { color: color-mix(in hsl, hsl(120 100% 49.898%) 80%, yellow); }\",\n      \".foo{color:#33fe00}\",\n    );\n    minify_test(\n      \".foo { color: color-mix(in srgb, rgb(100% 0% 0% / 0.7) 25%, rgb(0% 100% 0% / 0.2)); }\",\n      \".foo{color:#89760053}\",\n    );\n    minify_test(\n      \".foo { color: color-mix(in srgb, rgb(100% 0% 0% / 0.7) 20%, rgb(0% 100% 0% / 0.2) 60%); }\",\n      \".foo{color:#89760042}\",\n    );\n    minify_test(\n      \".foo { color: color-mix(in lch, color(display-p3 0 1 none), color(display-p3 0 0 1)); }\",\n      \".foo{color:lch(58.8143% 141.732 218.684)}\",\n    );\n    minify_test(\n      \".foo { color: color-mix(in srgb, rgb(128 128 none), rgb(none none 128)); }\",\n      \".foo{color:gray}\",\n    );\n    minify_test(\n      \".foo { color: color-mix(in srgb, rgb(50% 50% none), rgb(none none 50%)); }\",\n      \".foo{color:gray}\",\n    );\n    minify_test(\n      \".foo { color: color-mix(in srgb, rgb(none 50% none), rgb(50% none 50%)); }\",\n      \".foo{color:gray}\",\n    );\n    minify_test(\n      \".foo { --color: color-mix(in lch, teal 65%, olive); }\",\n      \".foo{--color:lch(49.4431% 40.4806 162.546)}\",\n    );\n    minify_test(\n      \".foo { color: color-mix(in xyz, transparent, green 65%); }\",\n      \".foo{color:color(xyz .0771883 .154377 .0257295/.65)}\",\n    );\n    prefix_test(\n      \".foo { color: color-mix(in xyz, transparent, green 65%); }\",\n      indoc! { r#\"\n      .foo {\n        color: #008000a6;\n        color: color(xyz .0771883 .154377 .0257295 / .65);\n      }\n      \"# },\n      Browsers {\n        chrome: Some(95 << 16),\n        ..Default::default()\n      },\n    );\n    minify_test(\n      \".foo { color: color-mix(in srgb, currentColor, blue); }\",\n      \".foo{color:color-mix(in srgb, currentColor, blue)}\",\n    );\n    minify_test(\n      \".foo { color: color-mix(in srgb, blue, currentColor); }\",\n      \".foo{color:color-mix(in srgb, blue, currentColor)}\",\n    );\n    minify_test(\n      \".foo { color: color-mix(in srgb, accentcolor, blue); }\",\n      \".foo{color:color-mix(in srgb, accentcolor, blue)}\",\n    );\n    minify_test(\n      \".foo { color: color-mix(in srgb, blue, accentcolor); }\",\n      \".foo{color:color-mix(in srgb, blue, accentcolor)}\",\n    );\n\n    // regex for converting web platform tests:\n    // test_computed_value\\(.*?, `(.*?)`, `(.*?)`\\);\n    // minify_test(\".foo { color: $1 }\", \".foo{color:$2}\");\n\n    // https://github.com/web-platform-tests/wpt/blob/f8c76b11cff66a7adc87264a18e39353cb5a60c9/css/css-color/parsing/color-mix-computed.html\n    minify_test(\n      \".foo { color: color-mix(in hsl, hsl(120deg 10% 20%), hsl(30deg 30% 40%)) }\",\n      \".foo{color:#545c3d}\",\n    );\n    minify_test(\n      \".foo { color: color-mix(in hsl, hsl(120deg 10% 20%) 25%, hsl(30deg 30% 40%)) }\",\n      \".foo{color:#706a43}\",\n    );\n    minify_test(\n      \".foo { color: color-mix(in hsl, 25% hsl(120deg 10% 20%), hsl(30deg 30% 40%)) }\",\n      \".foo{color:#706a43}\",\n    );\n    minify_test(\n      \".foo { color: color-mix(in hsl, hsl(120deg 10% 20%), 25% hsl(30deg 30% 40%)) }\",\n      \".foo{color:#3d4936}\",\n    );\n    minify_test(\n      \".foo { color: color-mix(in hsl, hsl(120deg 10% 20%), hsl(30deg 30% 40%) 25%) }\",\n      \".foo{color:#3d4936}\",\n    );\n    minify_test(\n      \".foo { color: color-mix(in hsl, hsl(120deg 10% 20%) 25%, hsl(30deg 30% 40%) 75%) }\",\n      \".foo{color:#706a43}\",\n    );\n    minify_test(\n      \".foo { color: color-mix(in hsl, hsl(120deg 10% 20%) 30%, hsl(30deg 30% 40%) 90%) }\",\n      \".foo{color:#706a43}\",\n    ); // Scale down > 100% sum.\n    minify_test(\n      \".foo { color: color-mix(in hsl, hsl(120deg 10% 20%) 12.5%, hsl(30deg 30% 40%) 37.5%) }\",\n      \".foo{color:#706a4380}\",\n    ); // Scale up < 100% sum, causes alpha multiplication.\n    minify_test(\n      \".foo { color: color-mix(in hsl, hsl(120deg 10% 20%) 0%, hsl(30deg 30% 40%)) }\",\n      \".foo{color:#856647}\",\n    );\n\n    minify_test(\n      \".foo { color: color-mix(in hsl, hsl(120deg 10% 20% / .4), hsl(30deg 30% 40% / .8)) }\",\n      \".foo{color:#5f694199}\",\n    );\n    minify_test(\n      \".foo { color: color-mix(in hsl, hsl(120deg 10% 20%) 25%, hsl(30deg 30% 40% / .8)) }\",\n      \".foo{color:#6c6742d9}\",\n    );\n    minify_test(\n      \".foo { color: color-mix(in hsl, 25% hsl(120deg 10% 20% / .4), hsl(30deg 30% 40% / .8)) }\",\n      \".foo{color:#797245b3}\",\n    );\n    minify_test(\n      \".foo { color: color-mix(in hsl, hsl(120deg 10% 20% / .4), 25% hsl(30deg 30% 40% / .8)) }\",\n      \".foo{color:#44543b80}\",\n    );\n    minify_test(\n      \".foo { color: color-mix(in hsl, hsl(120deg 10% 20% / .4), hsl(30deg 30% 40% / .8) 25%) }\",\n      \".foo{color:#44543b80}\",\n    );\n    minify_test(\n      \".foo { color: color-mix(in hsl, hsl(120deg 10% 20% / .4) 25%, hsl(30deg 30% 40% / .8) 75%) }\",\n      \".foo{color:#797245b3}\",\n    );\n    minify_test(\n      \".foo { color: color-mix(in hsl, hsl(120deg 10% 20% / .4) 30%, hsl(30deg 30% 40% / .8) 90%) }\",\n      \".foo{color:#797245b3}\",\n    ); // Scale down > 100% sum.\n    minify_test(\n      \".foo { color: color-mix(in hsl, hsl(120deg 10% 20% / .4) 12.5%, hsl(30deg 30% 40% / .8) 37.5%) }\",\n      \".foo{color:#79724559}\",\n    ); // Scale up < 100% sum, causes alpha multiplication.\n    minify_test(\n      \".foo { color: color-mix(in hsl, hsl(120deg 10% 20% / .4) 0%, hsl(30deg 30% 40% / .8)) }\",\n      \".foo{color:#856647cc}\",\n    );\n\n    fn canonicalize(s: &str) -> String {\n      use crate::traits::{Parse, ToCss};\n      use crate::values::color::CssColor;\n      use cssparser::{Parser, ParserInput};\n\n      let mut input = ParserInput::new(s);\n      let mut parser = Parser::new(&mut input);\n      let v = CssColor::parse(&mut parser).unwrap().to_rgb().unwrap();\n      format!(\".foo{{color:{}}}\", v.to_css_string(PrinterOptions::default()).unwrap())\n    }\n\n    // regex for converting web platform tests:\n    // test_computed_value\\(.*?, `(.*?)`, canonicalize\\(`(.*?)`\\)\\);\n    // minify_test(\".foo { color: $1 }\", &canonicalize(\"$2\"));\n\n    minify_test(\n      \".foo { color: color-mix(in hsl, hsl(40deg 50% 50%), hsl(60deg 50% 50%)) }\",\n      &canonicalize(\"hsl(50deg 50% 50%)\"),\n    );\n    minify_test(\n      \".foo { color: color-mix(in hsl, hsl(60deg 50% 50%), hsl(40deg 50% 50%)) }\",\n      &canonicalize(\"hsl(50deg 50% 50%)\"),\n    );\n    minify_test(\n      \".foo { color: color-mix(in hsl, hsl(50deg 50% 50%), hsl(330deg 50% 50%)) }\",\n      &canonicalize(\"hsl(10deg 50% 50%)\"),\n    );\n    minify_test(\n      \".foo { color: color-mix(in hsl, hsl(330deg 50% 50%), hsl(50deg 50% 50%)) }\",\n      &canonicalize(\"hsl(10deg 50% 50%)\"),\n    );\n    minify_test(\n      \".foo { color: color-mix(in hsl, hsl(20deg 50% 50%), hsl(320deg 50% 50%)) }\",\n      &canonicalize(\"hsl(350deg 50% 50%)\"),\n    );\n    minify_test(\n      \".foo { color: color-mix(in hsl, hsl(320deg 50% 50%), hsl(20deg 50% 50%)) }\",\n      &canonicalize(\"hsl(350deg 50% 50%)\"),\n    );\n\n    minify_test(\n      \".foo { color: color-mix(in hsl shorter hue, hsl(40deg 50% 50%), hsl(60deg 50% 50%)) }\",\n      &canonicalize(\"hsl(50deg 50% 50%)\"),\n    );\n    minify_test(\n      \".foo { color: color-mix(in hsl shorter hue, hsl(60deg 50% 50%), hsl(40deg 50% 50%)) }\",\n      &canonicalize(\"hsl(50deg 50% 50%)\"),\n    );\n    minify_test(\n      \".foo { color: color-mix(in hsl shorter hue, hsl(50deg 50% 50%), hsl(330deg 50% 50%)) }\",\n      &canonicalize(\"hsl(10deg 50% 50%)\"),\n    );\n    minify_test(\n      \".foo { color: color-mix(in hsl shorter hue, hsl(330deg 50% 50%), hsl(50deg 50% 50%)) }\",\n      &canonicalize(\"hsl(10deg 50% 50%)\"),\n    );\n    minify_test(\n      \".foo { color: color-mix(in hsl shorter hue, hsl(20deg 50% 50%), hsl(320deg 50% 50%)) }\",\n      &canonicalize(\"hsl(350deg 50% 50%)\"),\n    );\n    minify_test(\n      \".foo { color: color-mix(in hsl shorter hue, hsl(320deg 50% 50%), hsl(20deg 50% 50%)) }\",\n      &canonicalize(\"hsl(350deg 50% 50%)\"),\n    );\n\n    minify_test(\n      \".foo { color: color-mix(in hsl longer hue, hsl(40deg 50% 50%), hsl(60deg 50% 50%)) }\",\n      &canonicalize(\"hsl(230deg 50% 50%)\"),\n    );\n    minify_test(\n      \".foo { color: color-mix(in hsl longer hue, hsl(60deg 50% 50%), hsl(40deg 50% 50%)) }\",\n      &canonicalize(\"hsl(230deg 50% 50%)\"),\n    );\n    minify_test(\n      \".foo { color: color-mix(in hsl longer hue, hsl(50deg 50% 50%), hsl(330deg 50% 50%)) }\",\n      &canonicalize(\"hsl(190deg 50% 50%)\"),\n    );\n    minify_test(\n      \".foo { color: color-mix(in hsl longer hue, hsl(330deg 50% 50%), hsl(50deg 50% 50%)) }\",\n      &canonicalize(\"hsl(190deg 50% 50%)\"),\n    );\n    // minify_test(\".foo { color: color-mix(in hsl longer hue, hsl(20deg 50% 50%), hsl(320deg 50% 50%)) }\", &canonicalize(\"hsl(170deg 50% 50%)\"));\n    // minify_test(\".foo { color: color-mix(in hsl longer hue, hsl(320deg 50% 50%), hsl(20deg 50% 50%)) }\", &canonicalize(\"hsl(170deg 50% 50%)\"));\n\n    minify_test(\n      \".foo { color: color-mix(in hsl increasing hue, hsl(40deg 50% 50%), hsl(60deg 50% 50%)) }\",\n      &canonicalize(\"hsl(50deg 50% 50%)\"),\n    );\n    minify_test(\n      \".foo { color: color-mix(in hsl increasing hue, hsl(60deg 50% 50%), hsl(40deg 50% 50%)) }\",\n      &canonicalize(\"hsl(230deg 50% 50%)\"),\n    );\n    minify_test(\n      \".foo { color: color-mix(in hsl increasing hue, hsl(50deg 50% 50%), hsl(330deg 50% 50%)) }\",\n      &canonicalize(\"hsl(190deg 50% 50%)\"),\n    );\n    minify_test(\n      \".foo { color: color-mix(in hsl increasing hue, hsl(330deg 50% 50%), hsl(50deg 50% 50%)) }\",\n      &canonicalize(\"hsl(10deg 50% 50%)\"),\n    );\n    // minify_test(\".foo { color: color-mix(in hsl increasing hue, hsl(20deg 50% 50%), hsl(320deg 50% 50%)) }\", &canonicalize(\"hsl(170deg 50% 50%)\"));\n    // minify_test(\".foo { color: color-mix(in hsl increasing hue, hsl(320deg 50% 50%), hsl(20deg 50% 50%)) }\", &canonicalize(\"hsl(350deg 50% 50%)\"));\n\n    minify_test(\n      \".foo { color: color-mix(in hsl decreasing hue, hsl(40deg 50% 50%), hsl(60deg 50% 50%)) }\",\n      &canonicalize(\"hsl(230deg 50% 50%)\"),\n    );\n    minify_test(\n      \".foo { color: color-mix(in hsl decreasing hue, hsl(60deg 50% 50%), hsl(40deg 50% 50%)) }\",\n      &canonicalize(\"hsl(50deg 50% 50%)\"),\n    );\n    minify_test(\n      \".foo { color: color-mix(in hsl decreasing hue, hsl(50deg 50% 50%), hsl(330deg 50% 50%)) }\",\n      &canonicalize(\"hsl(10deg 50% 50%)\"),\n    );\n    minify_test(\n      \".foo { color: color-mix(in hsl decreasing hue, hsl(330deg 50% 50%), hsl(50deg 50% 50%)) }\",\n      &canonicalize(\"hsl(190deg 50% 50%)\"),\n    );\n    minify_test(\n      \".foo { color: color-mix(in hsl decreasing hue, hsl(20deg 50% 50%), hsl(320deg 50% 50%)) }\",\n      &canonicalize(\"hsl(350deg 50% 50%)\"),\n    );\n    // minify_test(\".foo { color: color-mix(in hsl decreasing hue, hsl(320deg 50% 50%), hsl(20deg 50% 50%)) }\", &canonicalize(\"hsl(170deg 50% 50%)\"));\n\n    minify_test(\n      \".foo { color: color-mix(in hsl specified hue, hsl(40deg 50% 50%), hsl(60deg 50% 50%)) }\",\n      &canonicalize(\"hsl(50deg 50% 50%)\"),\n    );\n    minify_test(\n      \".foo { color: color-mix(in hsl specified hue, hsl(60deg 50% 50%), hsl(40deg 50% 50%)) }\",\n      &canonicalize(\"hsl(50deg 50% 50%)\"),\n    );\n    minify_test(\n      \".foo { color: color-mix(in hsl specified hue, hsl(50deg 50% 50%), hsl(330deg 50% 50%)) }\",\n      &canonicalize(\"hsl(190deg 50% 50%)\"),\n    );\n    minify_test(\n      \".foo { color: color-mix(in hsl specified hue, hsl(330deg 50% 50%), hsl(50deg 50% 50%)) }\",\n      &canonicalize(\"hsl(190deg 50% 50%)\"),\n    );\n    // minify_test(\".foo { color: color-mix(in hsl specified hue, hsl(20deg 50% 50%), hsl(320deg 50% 50%)) }\", &canonicalize(\"hsl(170deg 50% 50%)\"));\n    // minify_test(\".foo { color: color-mix(in hsl specified hue, hsl(320deg 50% 50%), hsl(20deg 50% 50%)) }\", &canonicalize(\"hsl(170deg 50% 50%)\"));\n\n    minify_test(\n      \".foo { color: color-mix(in hsl, hsl(none none none), hsl(none none none)) }\",\n      &canonicalize(\"hsl(none none none)\"),\n    );\n    minify_test(\n      \".foo { color: color-mix(in hsl, hsl(none none none), hsl(30deg 40% 80%)) }\",\n      &canonicalize(\"hsl(30deg 40% 80%)\"),\n    );\n    minify_test(\n      \".foo { color: color-mix(in hsl, hsl(120deg 20% 40%), hsl(none none none)) }\",\n      &canonicalize(\"hsl(120deg 20% 40%)\"),\n    );\n    minify_test(\n      \".foo { color: color-mix(in hsl, hsl(120deg 20% none), hsl(30deg 40% 60%)) }\",\n      &canonicalize(\"hsl(75deg 30% 60%)\"),\n    );\n    minify_test(\n      \".foo { color: color-mix(in hsl, hsl(120deg 20% 40%), hsl(30deg 20% none)) }\",\n      &canonicalize(\"hsl(75deg 20% 40%)\"),\n    );\n    minify_test(\n      \".foo { color: color-mix(in hsl, hsl(none 20% 40%), hsl(30deg none 80%)) }\",\n      &canonicalize(\"hsl(30deg 20% 60%)\"),\n    );\n\n    minify_test(\n      \".foo { color: color-mix(in hsl, color(display-p3 0 1 0) 100%, rgb(0, 0, 0) 0%) }\",\n      &canonicalize(\"rgb(0, 249, 66)\"),\n    ); // Naive clip based mapping would give rgb(0, 255, 0).\n    minify_test(\n      \".foo { color: color-mix(in hsl, lab(100% 104.3 -50.9) 100%, rgb(0, 0, 0) 0%) }\",\n      &canonicalize(\"rgb(255, 255, 255)\"),\n    ); // Naive clip based mapping would give rgb(255, 150, 255).\n    minify_test(\n      \".foo { color: color-mix(in hsl, lab(0% 104.3 -50.9) 100%, rgb(0, 0, 0) 0%) }\",\n      &canonicalize(\"rgb(42, 0, 34)\"),\n    ); // Naive clip based mapping would give rgb(90, 0, 76). NOTE: 0% lightness in Lab/LCH does not automatically correspond with sRGB black,\n    minify_test(\n      \".foo { color: color-mix(in hsl, lch(100% 116 334) 100%, rgb(0, 0, 0) 0%) }\",\n      &canonicalize(\"rgb(255, 255, 255)\"),\n    ); // Naive clip based mapping would give rgb(255, 150, 255).\n    minify_test(\n      \".foo { color: color-mix(in hsl, lch(0% 116 334) 100%, rgb(0, 0, 0) 0%) }\",\n      &canonicalize(\"rgb(42, 0, 34)\"),\n    ); // Naive clip based mapping would give rgb(90, 0, 76). NOTE: 0% lightness in Lab/LCH does not automatically correspond with sRGB black,\n    minify_test(\n      \".foo { color: color-mix(in hsl, oklab(100% 0.365 -0.16) 100%, rgb(0, 0, 0) 0%) }\",\n      &canonicalize(\"rgb(255, 255, 255)\"),\n    ); // Naive clip based mapping would give rgb(255, 92, 255).\n    minify_test(\n      \".foo { color: color-mix(in hsl, oklab(0% 0.365 -0.16) 100%, rgb(0, 0, 0) 0%) }\",\n      &canonicalize(\"rgb(0, 0, 0)\"),\n    ); // Naive clip based mapping would give rgb(19, 0, 24).\n    minify_test(\n      \".foo { color: color-mix(in hsl, oklch(100% 0.399 336.3) 100%, rgb(0, 0, 0) 0%) }\",\n      &canonicalize(\"rgb(255, 255, 255)\"),\n    ); // Naive clip based mapping would give rgb(255, 91, 255).\n    minify_test(\n      \".foo { color: color-mix(in hsl, oklch(0% 0.399 336.3) 100%, rgb(0, 0, 0) 0%) }\",\n      &canonicalize(\"rgb(0, 0, 0)\"),\n    ); // Naive clip based mapping would give rgb(20, 0, 24).\n\n    minify_test(\n      \".foo { color: color-mix(in hwb, hwb(120deg 10% 20%), hwb(30deg 30% 40%)) }\",\n      &canonicalize(\"rgb(147, 179, 52)\"),\n    );\n    minify_test(\n      \".foo { color: color-mix(in hwb, hwb(120deg 10% 20%) 25%, hwb(30deg 30% 40%)) }\",\n      &canonicalize(\"rgb(166, 153, 64)\"),\n    );\n    minify_test(\n      \".foo { color: color-mix(in hwb, 25% hwb(120deg 10% 20%), hwb(30deg 30% 40%)) }\",\n      &canonicalize(\"rgb(166, 153, 64)\"),\n    );\n    minify_test(\n      \".foo { color: color-mix(in hwb, hwb(120deg 10% 20%), 25% hwb(30deg 30% 40%)) }\",\n      &canonicalize(\"rgb(96, 191, 39)\"),\n    );\n    minify_test(\n      \".foo { color: color-mix(in hwb, hwb(120deg 10% 20%), hwb(30deg 30% 40%) 25%) }\",\n      &canonicalize(\"rgb(96, 191, 39)\"),\n    );\n    minify_test(\n      \".foo { color: color-mix(in hwb, hwb(120deg 10% 20%) 25%, hwb(30deg 30% 40%) 75%) }\",\n      &canonicalize(\"rgb(166, 153, 64)\"),\n    );\n    minify_test(\n      \".foo { color: color-mix(in hwb, hwb(120deg 10% 20%) 30%, hwb(30deg 30% 40%) 90%) }\",\n      &canonicalize(\"rgb(166, 153, 64)\"),\n    ); // Scale down > 100% sum.\n    minify_test(\n      \".foo { color: color-mix(in hwb, hwb(120deg 10% 20%) 12.5%, hwb(30deg 30% 40%) 37.5%) }\",\n      &canonicalize(\"rgba(166, 153, 64, 0.5)\"),\n    ); // Scale up < 100% sum, causes alpha multiplication.\n    minify_test(\n      \".foo { color: color-mix(in hwb, hwb(120deg 10% 20%) 0%, hwb(30deg 30% 40%)) }\",\n      &canonicalize(\"rgb(153, 115, 77)\"),\n    );\n\n    minify_test(\n      \".foo { color: color-mix(in hwb, hwb(120deg 10% 20% / .4), hwb(30deg 30% 40% / .8)) }\",\n      &canonicalize(\"rgba(143, 170, 60, 0.6)\"),\n    );\n    minify_test(\n      \".foo { color: color-mix(in hwb, hwb(120deg 10% 20% / .4) 25%, hwb(30deg 30% 40% / .8)) }\",\n      &canonicalize(\"rgba(160, 149, 70, 0.7)\"),\n    );\n    minify_test(\n      \".foo { color: color-mix(in hwb, 25% hwb(120deg 10% 20% / .4), hwb(30deg 30% 40% / .8)) }\",\n      &canonicalize(\"rgba(160, 149, 70, 0.7)\"),\n    );\n    minify_test(\n      \".foo { color: color-mix(in hwb, hwb(120deg 10% 20%), 25% hwb(30deg 30% 40% / .8)) }\",\n      &canonicalize(\"rgba(95, 193, 37, 0.95)\"),\n    );\n    minify_test(\n      \".foo { color: color-mix(in hwb, hwb(120deg 10% 20% / .4), hwb(30deg 30% 40% / .8) 25%) }\",\n      &canonicalize(\"rgba(98, 184, 46, 0.5)\"),\n    );\n    minify_test(\n      \".foo { color: color-mix(in hwb, hwb(120deg 10% 20% / .4) 25%, hwb(30deg 30% 40% / .8) 75%) }\",\n      &canonicalize(\"rgba(160, 149, 70, 0.7)\"),\n    );\n    minify_test(\n      \".foo { color: color-mix(in hwb, hwb(120deg 10% 20% / .4) 30%, hwb(30deg 30% 40% / .8) 90%) }\",\n      &canonicalize(\"rgba(160, 149, 70, 0.7)\"),\n    ); // Scale down > 100% sum.\n    minify_test(\n      \".foo { color: color-mix(in hwb, hwb(120deg 10% 20% / .4) 12.5%, hwb(30deg 30% 40% / .8) 37.5%) }\",\n      &canonicalize(\"rgba(160, 149, 70, 0.35)\"),\n    ); // Scale up < 100% sum, causes alpha multiplication.\n    minify_test(\n      \".foo { color: color-mix(in hwb, hwb(120deg 10% 20% / .4) 0%, hwb(30deg 30% 40% / .8)) }\",\n      &canonicalize(\"rgba(153, 115, 77, 0.8)\"),\n    );\n\n    //  minify_test(\".foo { color: color-mix(in hwb, hwb(40deg 30% 40%), hwb(60deg 30% 40%)) }\", &canonicalize(\"hwb(50deg 30% 40%)\"));\n    //  minify_test(\".foo { color: color-mix(in hwb, hwb(60deg 30% 40%), hwb(40deg 30% 40%)) }\", &canonicalize(\"hwb(50deg 30% 40%)\"));\n    minify_test(\n      \".foo { color: color-mix(in hwb, hwb(50deg 30% 40%), hwb(330deg 30% 40%)) }\",\n      &canonicalize(\"hwb(10deg 30% 40%)\"),\n    );\n    minify_test(\n      \".foo { color: color-mix(in hwb, hwb(330deg 30% 40%), hwb(50deg 30% 40%)) }\",\n      &canonicalize(\"hwb(10deg 30% 40%)\"),\n    );\n    minify_test(\n      \".foo { color: color-mix(in hwb, hwb(20deg 30% 40%), hwb(320deg 30% 40%)) }\",\n      &canonicalize(\"hwb(350deg 30% 40%)\"),\n    );\n    minify_test(\n      \".foo { color: color-mix(in hwb, hwb(320deg 30% 40%), hwb(20deg 30% 40%)) }\",\n      &canonicalize(\"hwb(350deg 30% 40%)\"),\n    );\n\n    //  minify_test(\".foo { color: color-mix(in hwb shorter hue, hwb(40deg 30% 40%), hwb(60deg 30% 40%)) }\", &canonicalize(\"hwb(50deg 30% 40%)\"));\n    //  minify_test(\".foo { color: color-mix(in hwb shorter hue, hwb(60deg 30% 40%), hwb(40deg 30% 40%)) }\", &canonicalize(\"hwb(50deg 30% 40%)\"));\n    minify_test(\n      \".foo { color: color-mix(in hwb shorter hue, hwb(50deg 30% 40%), hwb(330deg 30% 40%)) }\",\n      &canonicalize(\"hwb(10deg 30% 40%)\"),\n    );\n    minify_test(\n      \".foo { color: color-mix(in hwb shorter hue, hwb(330deg 30% 40%), hwb(50deg 30% 40%)) }\",\n      &canonicalize(\"hwb(10deg 30% 40%)\"),\n    );\n    minify_test(\n      \".foo { color: color-mix(in hwb shorter hue, hwb(20deg 30% 40%), hwb(320deg 30% 40%)) }\",\n      &canonicalize(\"hwb(350deg 30% 40%)\"),\n    );\n    minify_test(\n      \".foo { color: color-mix(in hwb shorter hue, hwb(320deg 30% 40%), hwb(20deg 30% 40%)) }\",\n      &canonicalize(\"hwb(350deg 30% 40%)\"),\n    );\n\n    minify_test(\n      \".foo { color: color-mix(in hwb longer hue, hwb(40deg 30% 40%), hwb(60deg 30% 40%)) }\",\n      &canonicalize(\"hwb(230deg 30% 40%)\"),\n    );\n    minify_test(\n      \".foo { color: color-mix(in hwb longer hue, hwb(60deg 30% 40%), hwb(40deg 30% 40%)) }\",\n      &canonicalize(\"hwb(230deg 30% 40%)\"),\n    );\n    //  minify_test(\".foo { color: color-mix(in hwb longer hue, hwb(50deg 30% 40%), hwb(330deg 30% 40%)) }\", &canonicalize(\"hwb(190deg 30% 40%)\"));\n    //  minify_test(\".foo { color: color-mix(in hwb longer hue, hwb(330deg 30% 40%), hwb(50deg 30% 40%)) }\", &canonicalize(\"hwb(190deg 30% 40%)\"));\n    //  minify_test(\".foo { color: color-mix(in hwb longer hue, hwb(20deg 30% 40%), hwb(320deg 30% 40%)) }\", &canonicalize(\"hwb(170deg 30% 40%)\"));\n    //  minify_test(\".foo { color: color-mix(in hwb longer hue, hwb(320deg 30% 40%), hwb(20deg 30% 40%)) }\", &canonicalize(\"hwb(170deg 30% 40%)\"));\n\n    // minify_test(\".foo { color: color-mix(in hwb increasing hue, hwb(40deg 30% 40%), hwb(60deg 30% 40%)) }\", &canonicalize(\"hwb(50deg 30% 40%)\"));\n    minify_test(\n      \".foo { color: color-mix(in hwb increasing hue, hwb(60deg 30% 40%), hwb(40deg 30% 40%)) }\",\n      &canonicalize(\"hwb(230deg 30% 40%)\"),\n    );\n    // minify_test(\".foo { color: color-mix(in hwb increasing hue, hwb(50deg 30% 40%), hwb(330deg 30% 40%)) }\", &canonicalize(\"hwb(190deg 30% 40%)\"));\n    minify_test(\n      \".foo { color: color-mix(in hwb increasing hue, hwb(330deg 30% 40%), hwb(50deg 30% 40%)) }\",\n      &canonicalize(\"hwb(10deg 30% 40%)\"),\n    );\n    // minify_test(\".foo { color: color-mix(in hwb increasing hue, hwb(20deg 30% 40%), hwb(320deg 30% 40%)) }\", &canonicalize(\"hwb(170deg 30% 40%)\"));\n    minify_test(\n      \".foo { color: color-mix(in hwb increasing hue, hwb(320deg 30% 40%), hwb(20deg 30% 40%)) }\",\n      &canonicalize(\"hwb(350deg 30% 40%)\"),\n    );\n\n    minify_test(\n      \".foo { color: color-mix(in hwb decreasing hue, hwb(40deg 30% 40%), hwb(60deg 30% 40%)) }\",\n      &canonicalize(\"hwb(230deg 30% 40%)\"),\n    );\n    // minify_test(\".foo { color: color-mix(in hwb decreasing hue, hwb(60deg 30% 40%), hwb(40deg 30% 40%)) }\", &canonicalize(\"hwb(50deg 30% 40%)\"));\n    minify_test(\n      \".foo { color: color-mix(in hwb decreasing hue, hwb(50deg 30% 40%), hwb(330deg 30% 40%)) }\",\n      &canonicalize(\"hwb(10deg 30% 40%)\"),\n    );\n    // minify_test(\".foo { color: color-mix(in hwb decreasing hue, hwb(330deg 30% 40%), hwb(50deg 30% 40%)) }\", &canonicalize(\"hwb(190deg 30% 40%)\"));\n    minify_test(\n      \".foo { color: color-mix(in hwb decreasing hue, hwb(20deg 30% 40%), hwb(320deg 30% 40%)) }\",\n      &canonicalize(\"hwb(350deg 30% 40%)\"),\n    );\n    // minify_test(\".foo { color: color-mix(in hwb decreasing hue, hwb(320deg 30% 40%), hwb(20deg 30% 40%)) }\", &canonicalize(\"hwb(170deg 30% 40%)\"));\n\n    // minify_test(\".foo { color: color-mix(in hwb specified hue, hwb(40deg 30% 40%), hwb(60deg 30% 40%)) }\", &canonicalize(\"hwb(50deg 30% 40%)\"));\n    // minify_test(\".foo { color: color-mix(in hwb specified hue, hwb(60deg 30% 40%), hwb(40deg 30% 40%)) }\", &canonicalize(\"hwb(50deg 30% 40%)\"));\n    // minify_test(\".foo { color: color-mix(in hwb specified hue, hwb(50deg 30% 40%), hwb(330deg 30% 40%)) }\", &canonicalize(\"hwb(190deg 30% 40%)\"));\n    // minify_test(\".foo { color: color-mix(in hwb specified hue, hwb(330deg 30% 40%), hwb(50deg 30% 40%)) }\", &canonicalize(\"hwb(190deg 30% 40%)\"));\n    // minify_test(\".foo { color: color-mix(in hwb specified hue, hwb(20deg 30% 40%), hwb(320deg 30% 40%)) }\", &canonicalize(\"hwb(170deg 30% 40%)\"));\n    // minify_test(\".foo { color: color-mix(in hwb specified hue, hwb(320deg 30% 40%), hwb(20deg 30% 40%)) }\", &canonicalize(\"hwb(170deg 30% 40%)\"));\n\n    minify_test(\n      \".foo { color: color-mix(in hwb, color(display-p3 0 1 0) 100%, rgb(0, 0, 0) 0%) }\",\n      &canonicalize(\"rgb(0, 249, 66)\"),\n    ); // Naive clip based mapping would give rgb(0, 255, 0).\n    minify_test(\n      \".foo { color: color-mix(in hwb, lab(100% 104.3 -50.9) 100%, rgb(0, 0, 0) 0%) }\",\n      &canonicalize(\"rgb(255, 255, 255)\"),\n    ); // Naive clip based mapping would give rgb(255, 150, 255).\n    minify_test(\n      \".foo { color: color-mix(in hwb, lab(0% 104.3 -50.9) 100%, rgb(0, 0, 0) 0%) }\",\n      &canonicalize(\"rgb(42, 0, 34)\"),\n    ); // Naive clip based mapping would give rgb(90, 0, 76). NOTE: 0% lightness in Lab/LCH does not automatically correspond with sRGB black,\n    minify_test(\n      \".foo { color: color-mix(in hwb, lch(100% 116 334) 100%, rgb(0, 0, 0) 0%) }\",\n      &canonicalize(\"rgb(255, 255, 255)\"),\n    ); // Naive clip based mapping would give rgb(255, 150, 255).\n    minify_test(\n      \".foo { color: color-mix(in hwb, lch(0% 116 334) 100%, rgb(0, 0, 0) 0%) }\",\n      &canonicalize(\"rgb(42, 0, 34)\"),\n    ); // Naive clip based mapping would give rgb(90, 0, 76). NOTE: 0% lightness in Lab/LCH does not automatically correspond with sRGB black,\n    minify_test(\n      \".foo { color: color-mix(in hwb, oklab(100% 0.365 -0.16) 100%, rgb(0, 0, 0) 0%) }\",\n      &canonicalize(\"rgb(255, 255, 255)\"),\n    ); // Naive clip based mapping would give rgb(255, 92, 255).\n    minify_test(\n      \".foo { color: color-mix(in hwb, oklab(0% 0.365 -0.16) 100%, rgb(0, 0, 0) 0%) }\",\n      &canonicalize(\"rgb(0, 0, 0)\"),\n    ); // Naive clip based mapping would give rgb(19, 0, 24).\n    minify_test(\n      \".foo { color: color-mix(in hwb, oklch(100% 0.399 336.3) 100%, rgb(0, 0, 0) 0%) }\",\n      &canonicalize(\"rgb(255, 255, 255)\"),\n    ); // Naive clip based mapping would give rgb(255, 91, 255).\n    minify_test(\n      \".foo { color: color-mix(in hwb, oklch(0% 0.399 336.3) 100%, rgb(0, 0, 0) 0%) }\",\n      &canonicalize(\"rgb(0, 0, 0)\"),\n    ); // Naive clip based mapping would give rgb(20, 0, 24).\n\n    for color_space in &[\"lch\", \"oklch\"] {\n      // regex for converting web platform tests:\n      // test_computed_value\\(.*?, `color-mix\\(in \\$\\{colorSpace\\}(.*?), (.*?)\\$\\{colorSpace\\}(.*?) \\$\\{colorSpace\\}(.*?)`, `\\$\\{colorSpace\\}(.*?)`\\);\n      // minify_test(&format!(\".foo {{ color: color-mix(in {0}$1, $2{0}$3 {0}$4 }}\", color_space), &format!(\".foo{{color:{}$5}}\", color_space));\n\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg), {0}(50% 60 70deg)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(30% 40 50)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg) 25%, {0}(50% 60 70deg)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(40% 50 60)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, 25% {0}(10% 20 30deg), {0}(50% 60 70deg)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(40% 50 60)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg), 25% {0}(50% 60 70deg)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(20% 30 40)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg), {0}(50% 60 70deg) 25%) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(20% 30 40)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg) 25%, {0}(50% 60 70deg) 75%) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(40% 50 60)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg) 30%, {0}(50% 60 70deg) 90%) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(40% 50 60)}}\", color_space),\n      ); // Scale down > 100% sum.\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg) 12.5%, {0}(50% 60 70deg) 37.5%) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(40% 50 60/.5)}}\", color_space),\n      ); // Scale up < 100% sum, causes alpha multiplication.\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg) 0%, {0}(50% 60 70deg)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(50% 60 70)}}\", color_space),\n      );\n\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg / .4), {0}(50% 60 70deg / .8)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(36.6667% 46.6667 50/.6)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg / .4) 25%, {0}(50% 60 70deg / .8)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(44.2857% 54.2857 60/.7)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, 25% {0}(10% 20 30deg / .4), {0}(50% 60 70deg / .8)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(44.2857% 54.2857 60/.7)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg / .4), 25% {0}(50% 60 70deg / .8)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(26% 36 40/.5)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg / .4), {0}(50% 60 70deg / .8) 25%) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(26% 36 40/.5)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg / .4) 25%, {0}(50% 60 70deg / .8) 75%) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(44.2857% 54.2857 60/.7)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg / .4) 30%, {0}(50% 60 70deg / .8) 90%) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(44.2857% 54.2857 60/.7)}}\", color_space),\n      ); // Scale down > 100% sum.\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg / .4) 12.5%, {0}(50% 60 70deg / .8) 37.5%) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(44.2857% 54.2857 60/.35)}}\", color_space),\n      ); // Scale up < 100% sum, causes alpha multiplication.\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg / .4) 0%, {0}(50% 60 70deg / .8)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(50% 60 70/.8)}}\", color_space),\n      );\n\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(100% 0 40deg), {0}(100% 0 60deg)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(100% 0 50)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(100% 0 60deg), {0}(100% 0 40deg)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(100% 0 50)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(100% 0 50deg), {0}(100% 0 330deg)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(100% 0 10)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(100% 0 330deg), {0}(100% 0 50deg)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(100% 0 10)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(100% 0 20deg), {0}(100% 0 320deg)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(100% 0 350)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(100% 0 320deg), {0}(100% 0 20deg)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(100% 0 350)}}\", color_space),\n      );\n\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0} shorter hue, {0}(100% 0 40deg), {0}(100% 0 60deg)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(100% 0 50)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0} shorter hue, {0}(100% 0 60deg), {0}(100% 0 40deg)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(100% 0 50)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0} shorter hue, {0}(100% 0 50deg), {0}(100% 0 330deg)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(100% 0 10)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0} shorter hue, {0}(100% 0 330deg), {0}(100% 0 50deg)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(100% 0 10)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0} shorter hue, {0}(100% 0 20deg), {0}(100% 0 320deg)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(100% 0 350)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0} shorter hue, {0}(100% 0 320deg), {0}(100% 0 20deg)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(100% 0 350)}}\", color_space),\n      );\n\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0} longer hue, {0}(100% 0 40deg), {0}(100% 0 60deg)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(100% 0 230)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0} longer hue, {0}(100% 0 60deg), {0}(100% 0 40deg)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(100% 0 230)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0} longer hue, {0}(100% 0 50deg), {0}(100% 0 330deg)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(100% 0 190)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0} longer hue, {0}(100% 0 330deg), {0}(100% 0 50deg)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(100% 0 190)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0} longer hue, {0}(100% 0 20deg), {0}(100% 0 320deg)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(100% 0 170)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0} longer hue, {0}(100% 0 320deg), {0}(100% 0 20deg)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(100% 0 170)}}\", color_space),\n      );\n\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0} increasing hue, {0}(100% 0 40deg), {0}(100% 0 60deg)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(100% 0 50)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0} increasing hue, {0}(100% 0 60deg), {0}(100% 0 40deg)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(100% 0 230)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0} increasing hue, {0}(100% 0 50deg), {0}(100% 0 330deg)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(100% 0 190)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0} increasing hue, {0}(100% 0 330deg), {0}(100% 0 50deg)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(100% 0 10)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0} increasing hue, {0}(100% 0 20deg), {0}(100% 0 320deg)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(100% 0 170)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0} increasing hue, {0}(100% 0 320deg), {0}(100% 0 20deg)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(100% 0 350)}}\", color_space),\n      );\n\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0} decreasing hue, {0}(100% 0 40deg), {0}(100% 0 60deg)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(100% 0 230)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0} decreasing hue, {0}(100% 0 60deg), {0}(100% 0 40deg)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(100% 0 50)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0} decreasing hue, {0}(100% 0 50deg), {0}(100% 0 330deg)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(100% 0 10)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0} decreasing hue, {0}(100% 0 330deg), {0}(100% 0 50deg)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(100% 0 190)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0} decreasing hue, {0}(100% 0 20deg), {0}(100% 0 320deg)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(100% 0 350)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0} decreasing hue, {0}(100% 0 320deg), {0}(100% 0 20deg)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(100% 0 170)}}\", color_space),\n      );\n\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0} specified hue, {0}(100% 0 40deg), {0}(100% 0 60deg)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(100% 0 50)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0} specified hue, {0}(100% 0 60deg), {0}(100% 0 40deg)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(100% 0 50)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0} specified hue, {0}(100% 0 50deg), {0}(100% 0 330deg)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(100% 0 190)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0} specified hue, {0}(100% 0 330deg), {0}(100% 0 50deg)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(100% 0 190)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0} specified hue, {0}(100% 0 20deg), {0}(100% 0 320deg)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(100% 0 170)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0} specified hue, {0}(100% 0 320deg), {0}(100% 0 20deg)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(100% 0 170)}}\", color_space),\n      );\n\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(none none none), {0}(none none none)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(none none none)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(none none none), {0}(50% 60 70deg)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(50% 60 70)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg), {0}(none none none)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(10% 20 30)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(10% 20 none), {0}(50% 60 70deg)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(30% 40 70)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg), {0}(50% 60 none)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(30% 40 30)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(none 20 30deg), {0}(50% none 70deg)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(50% 20 50)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg / none), {0}(50% 60 70deg)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(30% 40 50)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg / none), {0}(50% 60 70deg / 0.5)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(30% 40 50/.5)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg / none), {0}(50% 60 70deg / none)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(30% 40 50/none)}}\", color_space),\n      );\n    }\n\n    for color_space in [\"lab\", \"oklab\"] {\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(10% 20 30), {0}(50% 60 70)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(30% 40 50)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(10% 20 30) 25%, {0}(50% 60 70)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(40% 50 60)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, 25% {0}(10% 20 30), {0}(50% 60 70)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(40% 50 60)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(10% 20 30), 25% {0}(50% 60 70)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(20% 30 40)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(10% 20 30), {0}(50% 60 70) 25%) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(20% 30 40)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(10% 20 30) 25%, {0}(50% 60 70) 75%) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(40% 50 60)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(10% 20 30) 30%, {0}(50% 60 70) 90%) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(40% 50 60)}}\", color_space),\n      ); // Scale down > 100% sum.\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(10% 20 30) 12.5%, {0}(50% 60 70) 37.5%) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(40% 50 60/.5)}}\", color_space),\n      ); // Scale up < 100% sum, causes alpha multiplication.\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(10% 20 30) 0%, {0}(50% 60 70)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(50% 60 70)}}\", color_space),\n      );\n\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(10% 20 30 / .4), {0}(50% 60 70 / .8)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(36.6667% 46.6667 56.6667/.6)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(10% 20 30 / .4) 25%, {0}(50% 60 70 / .8)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(44.2857% 54.2857 64.2857/.7)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, 25% {0}(10% 20 30 / .4), {0}(50% 60 70 / .8)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(44.2857% 54.2857 64.2857/.7)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(10% 20 30 / .4), 25% {0}(50% 60 70 / .8)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(26% 36 46/.5)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(10% 20 30 / .4), {0}(50% 60 70 / .8) 25%) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(26% 36 46/.5)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(10% 20 30 / .4) 25%, {0}(50% 60 70 / .8) 75%) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(44.2857% 54.2857 64.2857/.7)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(10% 20 30 / .4) 30%, {0}(50% 60 70 / .8) 90%) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(44.2857% 54.2857 64.2857/.7)}}\", color_space),\n      ); // Scale down > 100% sum.\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(10% 20 30 / .4) 12.5%, {0}(50% 60 70 / .8) 37.5%) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(44.2857% 54.2857 64.2857/.35)}}\", color_space),\n      ); // Scale up < 100% sum, causes alpha multiplication.\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(10% 20 30 / .4) 0%, {0}(50% 60 70 / .8)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(50% 60 70/.8)}}\", color_space),\n      );\n\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(none none none), {0}(none none none)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(none none none)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(none none none), {0}(50% 60 70)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(50% 60 70)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(10% 20 30), {0}(none none none)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(10% 20 30)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(10% 20 none), {0}(50% 60 70)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(30% 40 70)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(10% 20 30), {0}(50% 60 none)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(30% 40 30)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(none 20 30), {0}(50% none 70)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(50% 20 50)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(10% 20 30 / none), {0}(50% 60 70)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(30% 40 50)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(10% 20 30 / none), {0}(50% 60 70 / 0.5)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(30% 40 50/.5)}}\", color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, {0}(10% 20 30 / none), {0}(50% 60 70 / none)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:{}(30% 40 50/none)}}\", color_space),\n      );\n    }\n\n    for color_space in [/*\"srgb\", */ \"srgb-linear\", \"xyz\", \"xyz-d50\", \"xyz-d65\"] {\n      // regex for converting web platform tests:\n      // test_computed_value\\(.*?, `color-mix\\(in \\$\\{colorSpace\\}(.*?), (.*?)color\\(\\$\\{colorSpace\\}(.*?) color\\(\\$\\{colorSpace\\}(.*?)`, `color\\(\\$\\{resultColorSpace\\}(.*?)`\\);\n      // minify_test(&format!(\".foo {{ color: color-mix(in {0}$1, $2color({0}$3 color({0}$4 }}\", color_space), &format!(\".foo{{color:color({}$5}}\", result_color_space));\n\n      let result_color_space = if color_space == \"xyz-d65\" { \"xyz\" } else { color_space };\n\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3), color({0} .5 .6 .7)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:color({} .3 .4 .5)}}\", result_color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3) 25%, color({0} .5 .6 .7)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:color({} .4 .5 .6)}}\", result_color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, 25% color({0} .1 .2 .3), color({0} .5 .6 .7)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:color({} .4 .5 .6)}}\", result_color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3), color({0} .5 .6 .7) 25%) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:color({} .2 .3 .4)}}\", result_color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3), 25% color({0} .5 .6 .7)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:color({} .2 .3 .4)}}\", result_color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3) 25%, color({0} .5 .6 .7) 75%) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:color({} .4 .5 .6)}}\", result_color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3) 30%, color({0} .5 .6 .7) 90%) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:color({} .4 .5 .6)}}\", result_color_space),\n      ); // Scale down > 100% sum.\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3) 12.5%, color({0} .5 .6 .7) 37.5%) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:color({} .4 .5 .6/.5)}}\", result_color_space),\n      ); // Scale up < 100% sum, causes alpha multiplication.\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3) 0%, color({0} .5 .6 .7)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:color({} .5 .6 .7)}}\", result_color_space),\n      );\n\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3 / .5), color({0} .5 .6 .7 / .8)) }}\",\n          color_space\n        ),\n        &format!(\n          \".foo{{color:color({} .346154 .446154 .546154/.65)}}\",\n          result_color_space\n        ),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3 / .4) 25%, color({0} .5 .6 .7 / .8)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:color({} .442857 .542857 .642857/.7)}}\", result_color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, 25% color({0} .1 .2 .3 / .4), color({0} .5 .6 .7 / .8)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:color({} .442857 .542857 .642857/.7)}}\", result_color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3 / .4), color({0} .5 .6 .7 / .8) 25%) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:color({} .26 .36 .46/.5)}}\", result_color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3 / .4), 25% color({0} .5 .6 .7 / .8)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:color({} .26 .36 .46/.5)}}\", result_color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3 / .4) 25%, color({0} .5 .6 .7 / .8) 75%) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:color({} .442857 .542857 .642857/.7)}}\", result_color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3 / .4) 30%, color({0} .5 .6 .7 / .8) 90%) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:color({} .442857 .542857 .642857/.7)}}\", result_color_space),\n      ); // Scale down > 100% sum.\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3 / .4) 12.5%, color({0} .5 .6 .7 / .8) 37.5%) }}\",\n          color_space\n        ),\n        &format!(\n          \".foo{{color:color({} .442857 .542857 .642857/.35)}}\",\n          result_color_space\n        ),\n      ); // Scale up < 100% sum, causes alpha multiplication.\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3 / .4) 0%, color({0} .5 .6 .7 / .8)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:color({} .5 .6 .7/.8)}}\", result_color_space),\n      );\n\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, color({0} 2 3 4 / 5), color({0} 4 6 8 / 10)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:color({} 3 4.5 6)}}\", result_color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, color({0} -2 -3 -4), color({0} -4 -6 -8)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:color({} -3 -4.5 -6)}}\", result_color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, color({0} -2 -3 -4 / -5), color({0} -4 -6 -8 / -10)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:color({} 0 0 0/0)}}\", result_color_space),\n      );\n\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, color({0} none none none), color({0} none none none)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:color({} none none none)}}\", result_color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, color({0} none none none), color({0} .5 .6 .7)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:color({} .5 .6 .7)}}\", result_color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3), color({0} none none none)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:color({} .1 .2 .3)}}\", result_color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, color({0} .1 .2 none), color({0} .5 .6 .7)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:color({} .3 .4 .7)}}\", result_color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3), color({0} .5 .6 none)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:color({} .3 .4 .3)}}\", result_color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, color({0} none .2 .3), color({0} .5 none .7)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:color({} .5 .2 .5)}}\", result_color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3 / none), color({0} .5 .6 .7)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:color({} .3 .4 .5)}}\", result_color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3 / none), color({0} .5 .6 .7 / 0.5)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:color({} .3 .4 .5/.5)}}\", result_color_space),\n      );\n      minify_test(\n        &format!(\n          \".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3 / none), color({0} .5 .6 .7 / none)) }}\",\n          color_space\n        ),\n        &format!(\".foo{{color:color({} .3 .4 .5/none)}}\", result_color_space),\n      );\n    }\n  }\n\n  #[test]\n  fn test_grid() {\n    minify_test(\n      \".foo { grid-template-columns: [first nav-start]  150px [main-start] 1fr [last]; }\",\n      \".foo{grid-template-columns:[first nav-start]150px[main-start]1fr[last]}\",\n    );\n    minify_test(\n      \".foo { grid-template-columns: 150px 1fr; }\",\n      \".foo{grid-template-columns:150px 1fr}\",\n    );\n    minify_test(\n      \".foo { grid-template-columns: repeat(4, 1fr); }\",\n      \".foo{grid-template-columns:repeat(4,1fr)}\",\n    );\n    minify_test(\n      \".foo { grid-template-columns: repeat(2, [e] 40px); }\",\n      \".foo{grid-template-columns:repeat(2,[e]40px)}\",\n    );\n    minify_test(\n      \".foo { grid-template-columns: repeat(4, [col-start] 250px [col-end]); }\",\n      \".foo{grid-template-columns:repeat(4,[col-start]250px[col-end])}\",\n    );\n    minify_test(\n      \".foo { grid-template-columns: repeat(4, [col-start] 60% [col-end]); }\",\n      \".foo{grid-template-columns:repeat(4,[col-start]60%[col-end])}\",\n    );\n    minify_test(\n      \".foo { grid-template-columns: repeat(4, [col-start] 1fr [col-end]); }\",\n      \".foo{grid-template-columns:repeat(4,[col-start]1fr[col-end])}\",\n    );\n    minify_test(\n      \".foo { grid-template-columns: repeat(4, [col-start] min-content [col-end]); }\",\n      \".foo{grid-template-columns:repeat(4,[col-start]min-content[col-end])}\",\n    );\n    minify_test(\n      \".foo { grid-template-columns: repeat(4, [col-start] max-content [col-end]); }\",\n      \".foo{grid-template-columns:repeat(4,[col-start]max-content[col-end])}\",\n    );\n    minify_test(\n      \".foo { grid-template-columns: repeat(4, [col-start] auto [col-end]); }\",\n      \".foo{grid-template-columns:repeat(4,[col-start]auto[col-end])}\",\n    );\n    minify_test(\n      \".foo { grid-template-columns: repeat(4, [col-start] minmax(100px, 1fr) [col-end]); }\",\n      \".foo{grid-template-columns:repeat(4,[col-start]minmax(100px,1fr)[col-end])}\",\n    );\n    minify_test(\n      \".foo { grid-template-columns: repeat(4, [col-start] fit-content(200px) [col-end]); }\",\n      \".foo{grid-template-columns:repeat(4,[col-start]fit-content(200px)[col-end])}\",\n    );\n    minify_test(\n      \".foo { grid-template-columns: repeat(4, 10px [col-start] 30% [col-middle] auto [col-end]); }\",\n      \".foo{grid-template-columns:repeat(4,10px[col-start]30%[col-middle]auto[col-end])}\",\n    );\n    minify_test(\n      \".foo { grid-template-columns: repeat(5, auto); }\",\n      \".foo{grid-template-columns:repeat(5,auto)}\",\n    );\n    minify_test(\n      \".foo { grid-template-columns: repeat(auto-fill, 250px); }\",\n      \".foo{grid-template-columns:repeat(auto-fill,250px)}\",\n    );\n    minify_test(\n      \".foo { grid-template-columns: repeat(auto-fit, 250px); }\",\n      \".foo{grid-template-columns:repeat(auto-fit,250px)}\",\n    );\n    minify_test(\n      \".foo { grid-template-columns: repeat(auto-fill, [col-start] 250px [col-end]); }\",\n      \".foo{grid-template-columns:repeat(auto-fill,[col-start]250px[col-end])}\",\n    );\n    minify_test(\n      \".foo { grid-template-columns: repeat(auto-fill, [col-start] minmax(100px, 1fr) [col-end]); }\",\n      \".foo{grid-template-columns:repeat(auto-fill,[col-start]minmax(100px,1fr)[col-end])}\",\n    );\n    minify_test(\n      \".foo { grid-template-columns: minmax(min-content, 1fr); }\",\n      \".foo{grid-template-columns:minmax(min-content,1fr)}\",\n    );\n    minify_test(\n      \".foo { grid-template-columns: 200px repeat(auto-fill, 100px) 300px; }\",\n      \".foo{grid-template-columns:200px repeat(auto-fill,100px) 300px}\",\n    );\n    minify_test(\".foo { grid-template-columns: [linename1 linename2] 100px repeat(auto-fit, [linename1] 300px) [linename3]; }\", \".foo{grid-template-columns:[linename1 linename2]100px repeat(auto-fit,[linename1]300px)[linename3]}\");\n    minify_test(\n      \".foo { grid-template-rows: [linename1 linename2] 100px repeat(auto-fit, [linename1] 300px) [linename3]; }\",\n      \".foo{grid-template-rows:[linename1 linename2]100px repeat(auto-fit,[linename1]300px)[linename3]}\",\n    );\n\n    minify_test(\".foo { grid-auto-rows: auto; }\", \".foo{grid-auto-rows:auto}\");\n    minify_test(\".foo { grid-auto-rows: 1fr; }\", \".foo{grid-auto-rows:1fr}\");\n    minify_test(\".foo { grid-auto-rows: 100px; }\", \".foo{grid-auto-rows:100px}\");\n    minify_test(\n      \".foo { grid-auto-rows: min-content; }\",\n      \".foo{grid-auto-rows:min-content}\",\n    );\n    minify_test(\n      \".foo { grid-auto-rows: max-content; }\",\n      \".foo{grid-auto-rows:max-content}\",\n    );\n    minify_test(\n      \".foo { grid-auto-rows: minmax(100px,auto); }\",\n      \".foo{grid-auto-rows:minmax(100px,auto)}\",\n    );\n    minify_test(\n      \".foo { grid-auto-rows: fit-content(20%); }\",\n      \".foo{grid-auto-rows:fit-content(20%)}\",\n    );\n    minify_test(\n      \".foo { grid-auto-rows: 100px minmax(100px, auto) 10% 0.5fr fit-content(400px); }\",\n      \".foo{grid-auto-rows:100px minmax(100px,auto) 10% .5fr fit-content(400px)}\",\n    );\n    minify_test(\n      \".foo { grid-auto-columns: 100px minmax(100px, auto) 10% 0.5fr fit-content(400px); }\",\n      \".foo{grid-auto-columns:100px minmax(100px,auto) 10% .5fr fit-content(400px)}\",\n    );\n\n    minify_test(\n      r#\"\n      .foo {\n        grid-template-areas: \"head head\"\n                             \"nav  main\"\n                             \"foot ....\";\n      }\n    \"#,\n      \".foo{grid-template-areas:\\\"head head\\\"\\\"nav main\\\"\\\"foot.\\\"}\",\n    );\n    minify_test(\n      r#\"\n      .foo {\n        grid-template-areas: \"head head\"\n                             \"nav  main\"\n                             \".... foot\";\n      }\n    \"#,\n      \".foo{grid-template-areas:\\\"head head\\\"\\\"nav main\\\"\\\".foot\\\"}\",\n    );\n    minify_test(\n      r#\"\n      .foo {\n        grid-template-areas: \"head head\"\n                             \"nav  main\"\n                             \".... ....\";\n      }\n    \"#,\n      \".foo{grid-template-areas:\\\"head head\\\"\\\"nav main\\\"\\\". .\\\"}\",\n    );\n\n    // to grid-* shorthand\n    minify_test(\n      r#\"\n      .test-miss-areas {\n        grid-template-columns: 1fr 90px;\n        grid-template-rows: auto 80px;\n        grid-template-areas: \"one\";\n      }\n    \"#,\n      \".test-miss-areas{grid-template:\\\"one\\\"\\\".\\\"80px/1fr 90px}\",\n    );\n    test(\n      r#\"\n      .test-miss-areas-2 {\n        grid-template-columns: 1fr 1fr 1fr;      \n        grid-template-rows: 30px 60px 100px;\n        grid-template-areas: \"a a a\" \"b c c\";\n      }\n    \"#,\n      indoc! { r#\"\n      .test-miss-areas-2 {\n        grid-template: \"a a a\" 30px\n                       \"b c c\" 60px\n                       \". . .\" 100px\n                       / 1fr 1fr 1fr;\n      }\n    \"#},\n    );\n\n    test(\n      r#\"\n      .test-miss-areas-3 {\n        grid-template: 30px 60px 100px / 1fr 1fr 1fr;\n        grid-template-areas: \"a a a\" \"b c c\";\n      }\n    \"#,\n      indoc! { r#\"\n      .test-miss-areas-3 {\n        grid-template: \"a a a\" 30px\n                       \"b c c\" 60px\n                       \". . .\" 100px\n                       / 1fr 1fr 1fr;\n      }\n    \"#},\n    );\n\n    test(\n      r#\"\n      .test-miss-areas-4 {\n        grid: 30px 60px 100px / 1fr 1fr 1fr;\n        grid-template-areas: \"a a a\" \"b c c\";\n      }\n    \"#,\n      indoc! { r#\"\n      .test-miss-areas-4 {\n        grid: \"a a a\" 30px\n              \"b c c\" 60px\n              \". . .\" 100px\n              / 1fr 1fr 1fr;\n      }\n    \"#},\n    );\n\n    // test no unreachable error\n    minify_test(\n      r#\"\n    .grid-shorthand-areas {\n      grid: auto / 1fr 3fr;\n      grid-template-areas: \". content .\";\n    }\n  \"#,\n      \".grid-shorthand-areas{grid:\\\".content.\\\"/1fr 3fr}\",\n    );\n    minify_test(\n      r#\"\n    .grid-shorthand-areas-rows {\n      grid: auto / 1fr 3fr;\n      grid-template-rows: 20px;\n      grid-template-areas: \". content .\";\n    }\n  \"#,\n      \".grid-shorthand-areas-rows{grid:\\\".content.\\\"20px/1fr 3fr}\",\n    );\n\n    // test grid-auto-flow: row in grid shorthand\n    test(\n      r#\"\n      .test-auto-flow-row-1 {\n        grid: auto-flow   /  1fr 2fr 1fr;\n        grid-template-areas: \"  .   one  .  \";\n      }\n    \"#,\n      indoc! { r#\"\n      .test-auto-flow-row-1 {\n        grid: auto-flow / 1fr 2fr 1fr;\n        grid-template-areas: \". one .\";\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      .test-auto-flow-row-2 {\n        grid: auto-flow   auto   /  100px   100px;\n        grid-template-areas: \" one  two \";\n      }\n    \"#,\n      indoc! { r#\"\n      .test-auto-flow-row-2 {\n        grid: auto-flow / 100px 100px;\n        grid-template-areas: \"one two\";\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      .test-auto-flow-dense {\n        grid: dense auto-flow  /  1fr 2fr;\n        grid-template-areas: \"  .   content  .  \";\n      }\n    \"#,\n      indoc! { r#\"\n      .test-auto-flow-dense {\n        grid: auto-flow dense / 1fr 2fr;\n        grid-template-areas: \". content .\";\n      }\n    \"#},\n    );\n    minify_test(\n      r#\"\n      .grid-auto-flow-row-auto-rows {\n        grid: auto-flow 40px / 1fr 90px;\n        grid-template-areas: \"a\";\n      }\n    \"#,\n      \".grid-auto-flow-row-auto-rows{grid:auto-flow 40px/1fr 90px;grid-template-areas:\\\"a\\\"}\",\n    );\n    minify_test(\n      r#\"\n      .grid-auto-flow-row-auto-rows-multiple {\n        grid: auto-flow 40px max-content / 1fr;\n        grid-template-areas: \". a\";\n      }\n    \"#,\n      \".grid-auto-flow-row-auto-rows-multiple{grid:auto-flow 40px max-content/1fr;grid-template-areas:\\\".a\\\"}\",\n    );\n\n    // test grid-auto-flow: column in grid shorthand\n    test(\n      r#\"\n      .test-auto-flow-column-1 {\n        grid: 300px / auto-flow;\n        grid-template-areas: \"  .   one  .  \";\n      }\n    \"#,\n      indoc! { r#\"\n      .test-auto-flow-column-1 {\n        grid: 300px / auto-flow;\n        grid-template-areas: \". one .\";\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      .test-auto-flow-column-2 {\n        grid: 200px 1fr / auto-flow auto;\n        grid-template-areas: \"  .   one  .  \";\n      }\n    \"#,\n      indoc! { r#\"\n      .test-auto-flow-column-2 {\n        grid: 200px 1fr / auto-flow;\n        grid-template-areas: \". one .\";\n      }\n    \"#},\n    );\n    test(\n      r#\"\n      .test-auto-flow-column-dense {\n        grid: 1fr 2fr / dense auto-flow;\n        grid-template-areas: \"  .   content  .  \";\n      }\n    \"#,\n      indoc! { r#\"\n      .test-auto-flow-column-dense {\n        grid: 1fr 2fr / auto-flow dense;\n        grid-template-areas: \". content .\";\n      }\n    \"#},\n    );\n    minify_test(\n      r#\"\n      .grid-auto-flow-column-auto-rows {\n        grid: 1fr 3fr / auto-flow 40px;\n        grid-template-areas: \"a\";\n      }\n    \"#,\n      \".grid-auto-flow-column-auto-rows{grid:1fr 3fr/auto-flow 40px;grid-template-areas:\\\"a\\\"}\",\n    );\n    minify_test(\n      r#\"\n      .grid-auto-flow-column-auto-rows-multiple {\n        grid: 1fr / auto-flow 40px max-content ;\n        grid-template-areas: \". a\";\n      }\n    \"#,\n      \".grid-auto-flow-column-auto-rows-multiple{grid:1fr/auto-flow 40px max-content;grid-template-areas:\\\".a\\\"}\",\n    );\n\n    test(\n      r#\"\n      .foo {\n        grid-template-areas: \"head head\" \"nav  main\" \"foot ....\";\n      }\n    \"#,\n      indoc! { r#\"\n      .foo {\n        grid-template-areas: \"head head\"\n                             \"nav main\"\n                             \"foot .\";\n      }\n    \"#},\n    );\n\n    minify_test(\n      r#\"\n      .foo {\n        grid-template: [header-top] \"a   a   a\"     [header-bottom]\n                       [main-top] \"b   b   b\" 1fr [main-bottom];\n      }\n    \"#,\n      \".foo{grid-template:[header-top]\\\"a a a\\\"[header-bottom main-top]\\\"b b b\\\"1fr[main-bottom]}\",\n    );\n    minify_test(\n      r#\"\n      .foo {\n        grid-template: \"head head\"\n                       \"nav  main\" 1fr\n                       \"foot ....\";\n      }\n    \"#,\n      \".foo{grid-template:\\\"head head\\\"\\\"nav main\\\"1fr\\\"foot.\\\"}\",\n    );\n    minify_test(\n      r#\"\n      .foo {\n        grid-template: [header-top] \"a   a   a\"     [header-bottom]\n                         [main-top] \"b   b   b\" 1fr [main-bottom]\n                                  / auto 1fr auto;\n      }\n    \"#,\n      \".foo{grid-template:[header-top]\\\"a a a\\\"[header-bottom main-top]\\\"b b b\\\"1fr[main-bottom]/auto 1fr auto}\",\n    );\n\n    minify_test(\n      \".foo { grid-template: auto 1fr / auto 1fr auto; }\",\n      \".foo{grid-template:auto 1fr/auto 1fr auto}\",\n    );\n    minify_test(\n      \".foo { grid-template: [linename1 linename2] 100px repeat(auto-fit, [linename1] 300px) [linename3] / [linename1 linename2] 100px repeat(auto-fit, [linename1] 300px) [linename3]; }\",\n      \".foo{grid-template:[linename1 linename2]100px repeat(auto-fit,[linename1]300px)[linename3]/[linename1 linename2]100px repeat(auto-fit,[linename1]300px)[linename3]}\"\n    );\n\n    test(\n      \".foo{grid-template:[header-top]\\\"a a a\\\"[header-bottom main-top]\\\"b b b\\\"1fr[main-bottom]/auto 1fr auto}\",\n      indoc! {r#\"\n        .foo {\n          grid-template: [header-top] \"a a a\" [header-bottom]\n                         [main-top] \"b b b\" 1fr [main-bottom]\n                         / auto 1fr auto;\n        }\n      \"#},\n    );\n    test(\n      \".foo{grid-template:[header-top]\\\"a a a\\\"[main-top]\\\"b b b\\\"1fr/auto 1fr auto}\",\n      indoc! {r#\"\n        .foo {\n          grid-template: [header-top] \"a a a\"\n                         [main-top] \"b b b\" 1fr\n                         / auto 1fr auto;\n        }\n      \"#},\n    );\n\n    minify_test(\".foo { grid-auto-flow: row }\", \".foo{grid-auto-flow:row}\");\n    minify_test(\".foo { grid-auto-flow: column }\", \".foo{grid-auto-flow:column}\");\n    minify_test(\".foo { grid-auto-flow: row dense }\", \".foo{grid-auto-flow:dense}\");\n    minify_test(\".foo { grid-auto-flow: dense row }\", \".foo{grid-auto-flow:dense}\");\n    minify_test(\n      \".foo { grid-auto-flow: column dense }\",\n      \".foo{grid-auto-flow:column dense}\",\n    );\n    minify_test(\n      \".foo { grid-auto-flow: dense column }\",\n      \".foo{grid-auto-flow:column dense}\",\n    );\n\n    minify_test(\".foo { grid: none }\", \".foo{grid:none}\");\n    minify_test(\".foo { grid: \\\"a\\\" 100px \\\"b\\\" 1fr }\", \".foo{grid:\\\"a\\\"100px\\\"b\\\"1fr}\");\n    minify_test(\n      \".foo { grid: [linename1] \\\"a\\\" 100px [linename2] }\",\n      \".foo{grid:[linename1]\\\"a\\\"100px[linename2]}\",\n    );\n    minify_test(\n      \".foo { grid: \\\"a\\\" 200px \\\"b\\\" min-content }\",\n      \".foo{grid:\\\"a\\\"200px\\\"b\\\"min-content}\",\n    );\n    minify_test(\n      \".foo { grid: \\\"a\\\" minmax(100px, max-content) \\\"b\\\" 20% }\",\n      \".foo{grid:\\\"a\\\"minmax(100px,max-content)\\\"b\\\"20%}\",\n    );\n    minify_test(\".foo { grid: 100px / 200px }\", \".foo{grid:100px/200px}\");\n    minify_test(\n      \".foo { grid: minmax(400px, min-content) / repeat(auto-fill, 50px) }\",\n      \".foo{grid:minmax(400px,min-content)/repeat(auto-fill,50px)}\",\n    );\n\n    minify_test(\".foo { grid: 200px / auto-flow }\", \".foo{grid:200px/auto-flow}\");\n    minify_test(\".foo { grid: 30% / auto-flow dense }\", \".foo{grid:30%/auto-flow dense}\");\n    minify_test(\".foo { grid: 30% / dense auto-flow }\", \".foo{grid:30%/auto-flow dense}\");\n    minify_test(\n      \".foo { grid: repeat(3, [line1 line2 line3] 200px) / auto-flow 300px }\",\n      \".foo{grid:repeat(3,[line1 line2 line3]200px)/auto-flow 300px}\",\n    );\n    minify_test(\n      \".foo { grid: [line1] minmax(20em, max-content) / auto-flow dense 40% }\",\n      \".foo{grid:[line1]minmax(20em,max-content)/auto-flow dense 40%}\",\n    );\n    minify_test(\".foo { grid: none / auto-flow 1fr }\", \".foo{grid:none/auto-flow 1fr}\");\n\n    minify_test(\".foo { grid: auto-flow / 200px }\", \".foo{grid:none/200px}\");\n    minify_test(\".foo { grid: auto-flow dense / 30% }\", \".foo{grid:auto-flow dense/30%}\");\n    minify_test(\".foo { grid: dense auto-flow / 30% }\", \".foo{grid:auto-flow dense/30%}\");\n    minify_test(\n      \".foo { grid: auto-flow 300px / repeat(3, [line1 line2 line3] 200px) }\",\n      \".foo{grid:auto-flow 300px/repeat(3,[line1 line2 line3]200px)}\",\n    );\n    minify_test(\n      \".foo { grid: auto-flow dense 40% / [line1] minmax(20em, max-content) }\",\n      \".foo{grid:auto-flow dense 40%/[line1]minmax(20em,max-content)}\",\n    );\n\n    minify_test(\".foo { grid-row-start: auto }\", \".foo{grid-row-start:auto}\");\n    minify_test(\".foo { grid-row-start: some-area }\", \".foo{grid-row-start:some-area}\");\n    minify_test(\".foo { grid-row-start: 2 }\", \".foo{grid-row-start:2}\");\n    minify_test(\n      \".foo { grid-row-start: 2 some-line }\",\n      \".foo{grid-row-start:2 some-line}\",\n    );\n    minify_test(\n      \".foo { grid-row-start: some-line 2 }\",\n      \".foo{grid-row-start:2 some-line}\",\n    );\n    minify_test(\".foo { grid-row-start: span 3 }\", \".foo{grid-row-start:span 3}\");\n    minify_test(\n      \".foo { grid-row-start: span some-line }\",\n      \".foo{grid-row-start:span some-line}\",\n    );\n    minify_test(\n      \".foo { grid-row-start: span some-line 1 }\",\n      \".foo{grid-row-start:span some-line}\",\n    );\n    minify_test(\n      \".foo { grid-row-start: span 1 some-line }\",\n      \".foo{grid-row-start:span some-line}\",\n    );\n    minify_test(\n      \".foo { grid-row-start: span 5 some-line }\",\n      \".foo{grid-row-start:span 5 some-line}\",\n    );\n    minify_test(\n      \".foo { grid-row-start: span some-line 5 }\",\n      \".foo{grid-row-start:span 5 some-line}\",\n    );\n\n    minify_test(\n      \".foo { grid-row-end: span 1 some-line }\",\n      \".foo{grid-row-end:span some-line}\",\n    );\n    minify_test(\n      \".foo { grid-column-start: span 1 some-line }\",\n      \".foo{grid-column-start:span some-line}\",\n    );\n    minify_test(\n      \".foo { grid-column-end: span 1 some-line }\",\n      \".foo{grid-column-end:span some-line}\",\n    );\n\n    minify_test(\".foo { grid-row: 1 }\", \".foo{grid-row:1}\");\n    minify_test(\".foo { grid-row: 1 / auto }\", \".foo{grid-row:1}\");\n    minify_test(\".foo { grid-row: 1 / 1 }\", \".foo{grid-row:1/1}\");\n    minify_test(\".foo { grid-row: 1 / 3 }\", \".foo{grid-row:1/3}\");\n    minify_test(\".foo { grid-row: 1 / span 2 }\", \".foo{grid-row:1/span 2}\");\n    minify_test(\".foo { grid-row: main-start }\", \".foo{grid-row:main-start}\");\n    minify_test(\n      \".foo { grid-row: main-start / main-end }\",\n      \".foo{grid-row:main-start/main-end}\",\n    );\n    minify_test(\n      \".foo { grid-row: main-start / main-start }\",\n      \".foo{grid-row:main-start}\",\n    );\n    minify_test(\".foo { grid-column: 1 / auto }\", \".foo{grid-column:1}\");\n\n    minify_test(\".foo { grid-area: a }\", \".foo{grid-area:a}\");\n    minify_test(\".foo { grid-area: a / a / a / a }\", \".foo{grid-area:a}\");\n    minify_test(\".foo { grid-area: a / b / a / b }\", \".foo{grid-area:a/b}\");\n    minify_test(\".foo { grid-area: a / b / c / b }\", \".foo{grid-area:a/b/c}\");\n    minify_test(\".foo { grid-area: a / b / c / d }\", \".foo{grid-area:a/b/c/d}\");\n\n    minify_test(\".foo { grid-area: auto / auto / auto / auto }\", \".foo{grid-area:auto}\");\n    minify_test(\".foo { grid-area: 1 / auto }\", \".foo{grid-area:1}\");\n    minify_test(\".foo { grid-area: 1 / 2 / 3 / 4 }\", \".foo{grid-area:1/2/3/4}\");\n    minify_test(\".foo { grid-area: 1 / 1 / 1 / 1 }\", \".foo{grid-area:1/1/1/1}\");\n\n    test(\n      r#\"\n        .foo{\n          grid-template-rows: auto 1fr;\n          grid-template-columns: auto 1fr auto;\n          grid-template-areas: none;\n        }\n      \"#,\n      indoc! {r#\"\n        .foo {\n          grid-template: auto 1fr / auto 1fr auto;\n        }\n      \"#},\n    );\n\n    test(\n      r#\"\n        .foo{\n          grid-template-areas: \"a a a\"\n                               \"b b b\";\n          grid-template-rows: [header-top] auto [header-bottom main-top] 1fr [main-bottom];\n          grid-template-columns: auto 1fr auto;\n        }\n      \"#,\n      indoc! {r#\"\n        .foo {\n          grid-template: [header-top] \"a a a\" [header-bottom]\n                         [main-top] \"b b b\" 1fr [main-bottom]\n                         / auto 1fr auto;\n        }\n      \"#},\n    );\n\n    test(\n      r#\"\n        .foo{\n          grid-template-areas: \"a a a\"\n                               \"b b b\";\n          grid-template-columns: repeat(3, 1fr);\n          grid-template-rows: auto 1fr;\n        }\n      \"#,\n      indoc! {r#\"\n        .foo {\n          grid-template-rows: auto 1fr;\n          grid-template-columns: repeat(3, 1fr);\n          grid-template-areas: \"a a a\"\n                               \"b b b\";\n        }\n      \"#},\n    );\n\n    test(\n      r#\"\n        .foo{\n          grid-template-areas: \"a a a\"\n                               \"b b b\";\n          grid-template-columns: auto 1fr auto;\n          grid-template-rows: repeat(2, 1fr);\n        }\n      \"#,\n      indoc! {r#\"\n        .foo {\n          grid-template-rows: repeat(2, 1fr);\n          grid-template-columns: auto 1fr auto;\n          grid-template-areas: \"a a a\"\n                               \"b b b\";\n        }\n      \"#},\n    );\n\n    test(\n      r#\"\n        .foo{\n          grid-template-areas: \". a a .\"\n                               \". b b .\";\n          grid-template-rows: auto 1fr;\n          grid-template-columns: 10px 1fr 1fr 10px;\n        }\n      \"#,\n      indoc! {r#\"\n        .foo {\n          grid-template: \". a a .\"\n                         \". b b .\" 1fr\n                         / 10px 1fr 1fr 10px;\n        }\n      \"#},\n    );\n\n    test(\n      r#\"\n        .foo{\n          grid-template-areas: none;\n          grid-template-columns: auto 1fr auto;\n          grid-template-rows: repeat(2, 1fr);\n        }\n      \"#,\n      indoc! {r#\"\n        .foo {\n          grid-template: repeat(2, 1fr) / auto 1fr auto;\n        }\n      \"#},\n    );\n\n    test(\n      r#\"\n        .foo{\n          grid-template-areas: none;\n          grid-template-columns: none;\n          grid-template-rows: none;\n        }\n      \"#,\n      indoc! {r#\"\n        .foo {\n          grid-template: none;\n        }\n      \"#},\n    );\n\n    test(\n      r#\"\n        .foo{\n          grid-template-areas: \"a a a\"\n                               \"b b b\";\n          grid-template-rows: [header-top] auto [header-bottom main-top] 1fr [main-bottom];\n          grid-template-columns: auto 1fr auto;\n          grid-auto-flow: row;\n          grid-auto-rows: auto;\n          grid-auto-columns: auto;\n        }\n      \"#,\n      indoc! {r#\"\n        .foo {\n          grid: [header-top] \"a a a\" [header-bottom]\n                [main-top] \"b b b\" 1fr [main-bottom]\n                / auto 1fr auto;\n        }\n      \"#},\n    );\n\n    test(\n      r#\"\n        .foo{\n          grid-template-areas: none;\n          grid-template-columns: auto 1fr auto;\n          grid-template-rows: repeat(2, 1fr);\n          grid-auto-flow: row;\n          grid-auto-rows: auto;\n          grid-auto-columns: auto;\n        }\n      \"#,\n      indoc! {r#\"\n        .foo {\n          grid: repeat(2, 1fr) / auto 1fr auto;\n        }\n      \"#},\n    );\n\n    test(\n      r#\"\n        .foo{\n          grid-template-areas: none;\n          grid-template-columns: none;\n          grid-template-rows: none;\n          grid-auto-flow: row;\n          grid-auto-rows: auto;\n          grid-auto-columns: auto;\n        }\n      \"#,\n      indoc! {r#\"\n        .foo {\n          grid: none;\n        }\n      \"#},\n    );\n\n    test(\n      r#\"\n        .foo{\n          grid-template-areas: \"a a a\"\n                               \"b b b\";\n          grid-template-rows: [header-top] auto [header-bottom main-top] 1fr [main-bottom];\n          grid-template-columns: auto 1fr auto;\n          grid-auto-flow: column;\n          grid-auto-rows: 1fr;\n          grid-auto-columns: 1fr;\n        }\n      \"#,\n      indoc! {r#\"\n        .foo {\n          grid-template: [header-top] \"a a a\" [header-bottom]\n                         [main-top] \"b b b\" 1fr [main-bottom]\n                         / auto 1fr auto;\n          grid-auto-rows: 1fr;\n          grid-auto-columns: 1fr;\n          grid-auto-flow: column;\n        }\n      \"#},\n    );\n\n    test(\n      r#\"\n        .foo{\n          grid-template-rows: auto 1fr;\n          grid-template-columns: auto 1fr auto;\n          grid-template-areas: none;\n          grid-auto-flow: row;\n          grid-auto-rows: auto;\n          grid-auto-columns: auto;\n        }\n      \"#,\n      indoc! {r#\"\n        .foo {\n          grid: auto 1fr / auto 1fr auto;\n        }\n      \"#},\n    );\n\n    test(\n      r#\"\n        .foo{\n          grid-template-rows: auto 1fr;\n          grid-template-columns: auto 1fr auto;\n          grid-template-areas: none;\n          grid-auto-flow: column;\n          grid-auto-rows: 1fr;\n          grid-auto-columns: 1fr;\n        }\n      \"#,\n      indoc! {r#\"\n        .foo {\n          grid-template: auto 1fr / auto 1fr auto;\n          grid-auto-rows: 1fr;\n          grid-auto-columns: 1fr;\n          grid-auto-flow: column;\n        }\n      \"#},\n    );\n\n    test(\n      r#\"\n        .foo{\n          grid-template-rows: none;\n          grid-template-columns: auto 1fr auto;\n          grid-template-areas: none;\n          grid-auto-flow: column;\n          grid-auto-rows: 1fr;\n          grid-auto-columns: 1fr;\n        }\n      \"#,\n      indoc! {r#\"\n        .foo {\n          grid-template: none / auto 1fr auto;\n          grid-auto-rows: 1fr;\n          grid-auto-columns: 1fr;\n          grid-auto-flow: column;\n        }\n      \"#},\n    );\n\n    test(\n      r#\"\n        .foo{\n          grid-template-rows: none;\n          grid-template-columns: auto 1fr auto;\n          grid-template-areas: none;\n          grid-auto-flow: row;\n          grid-auto-rows: 1fr;\n          grid-auto-columns: auto;\n        }\n      \"#,\n      indoc! {r#\"\n        .foo {\n          grid: auto-flow 1fr / auto 1fr auto;\n        }\n      \"#},\n    );\n\n    test(\n      r#\"\n        .foo{\n          grid-template-rows: none;\n          grid-template-columns: auto 1fr auto;\n          grid-template-areas: none;\n          grid-auto-flow: row dense;\n          grid-auto-rows: 1fr;\n          grid-auto-columns: auto;\n        }\n      \"#,\n      indoc! {r#\"\n        .foo {\n          grid: auto-flow dense 1fr / auto 1fr auto;\n        }\n      \"#},\n    );\n\n    test(\n      r#\"\n        .foo{\n          grid-template-rows: auto 1fr auto;\n          grid-template-columns: none;\n          grid-template-areas: none;\n          grid-auto-flow: column;\n          grid-auto-rows: auto;\n          grid-auto-columns: 1fr;\n        }\n      \"#,\n      indoc! {r#\"\n        .foo {\n          grid: auto 1fr auto / auto-flow 1fr;\n        }\n      \"#},\n    );\n\n    test(\n      r#\"\n        .foo{\n          grid-template-rows: auto 1fr auto;\n          grid-template-columns: none;\n          grid-template-areas: none;\n          grid-auto-flow: column dense;\n          grid-auto-rows: auto;\n          grid-auto-columns: 1fr;\n        }\n      \"#,\n      indoc! {r#\"\n        .foo {\n          grid: auto 1fr auto / auto-flow dense 1fr;\n        }\n      \"#},\n    );\n\n    test(\n      r#\"\n        .foo{\n          grid-template-rows: auto 1fr auto;\n          grid-template-columns: none;\n          grid-template-areas: none;\n          grid-auto-flow: var(--auto-flow);\n          grid-auto-rows: auto;\n          grid-auto-columns: 1fr;\n        }\n      \"#,\n      indoc! {r#\"\n        .foo {\n          grid-template: auto 1fr auto / none;\n          grid-auto-flow: var(--auto-flow);\n          grid-auto-rows: auto;\n          grid-auto-columns: 1fr;\n        }\n      \"#},\n    );\n\n    test(\n      r#\"\n        .foo{\n          grid: auto 1fr auto / auto-flow dense 1fr;\n          grid-template-rows: 1fr 1fr 1fr;\n        }\n      \"#,\n      indoc! {r#\"\n        .foo {\n          grid: 1fr 1fr 1fr / auto-flow dense 1fr;\n        }\n      \"#},\n    );\n\n    test(\n      r#\"\n        .foo{\n          grid-row-start: a;\n          grid-row-end: a;\n          grid-column-start: a;\n          grid-column-end: a;\n        }\n      \"#,\n      indoc! {r#\"\n        .foo {\n          grid-area: a;\n        }\n      \"#},\n    );\n\n    test(\n      r#\"\n        .foo{\n          grid-row-start: 1;\n          grid-row-end: 2;\n          grid-column-start: 3;\n          grid-column-end: 4;\n        }\n      \"#,\n      indoc! {r#\"\n        .foo {\n          grid-area: 1 / 3 / 2 / 4;\n        }\n      \"#},\n    );\n\n    test(\n      r#\"\n        .foo{\n          grid-row-start: a;\n          grid-row-end: a;\n        }\n      \"#,\n      indoc! {r#\"\n        .foo {\n          grid-row: a;\n        }\n      \"#},\n    );\n\n    test(\n      r#\"\n        .foo{\n          grid-column-start: a;\n          grid-column-end: a;\n        }\n      \"#,\n      indoc! {r#\"\n        .foo {\n          grid-column: a;\n        }\n      \"#},\n    );\n  }\n\n  #[test]\n  fn test_moz_document() {\n    minify_test(\n      r#\"\n      @-moz-document url-prefix() {\n        h1 {\n          color: yellow;\n        }\n      }\n    \"#,\n      \"@-moz-document url-prefix(){h1{color:#ff0}}\",\n    );\n    minify_test(\n      r#\"\n      @-moz-document url-prefix(\"\") {\n        h1 {\n          color: yellow;\n        }\n      }\n    \"#,\n      \"@-moz-document url-prefix(){h1{color:#ff0}}\",\n    );\n    error_test(\n      \"@-moz-document url-prefix(foo) {}\",\n      ParserError::UnexpectedToken(crate::properties::custom::Token::Ident(\"foo\".into())),\n    );\n    error_test(\n      \"@-moz-document url-prefix(\\\"foo\\\") {}\",\n      ParserError::UnexpectedToken(crate::properties::custom::Token::String(\"foo\".into())),\n    );\n  }\n\n  #[test]\n  fn test_custom_properties() {\n    minify_test(\".foo { --test: ; }\", \".foo{--test: }\");\n    minify_test(\".foo { --test:  ; }\", \".foo{--test: }\");\n    minify_test(\".foo { --test: foo; }\", \".foo{--test:foo}\");\n    minify_test(\".foo { --test:  foo; }\", \".foo{--test:foo}\");\n    minify_test(\".foo { --test: foo ; }\", \".foo{--test:foo}\");\n    minify_test(\".foo { --test: foo  ; }\", \".foo{--test:foo}\");\n    minify_test(\".foo { --test:foo; }\", \".foo{--test:foo}\");\n    minify_test(\".foo { --test:foo ; }\", \".foo{--test:foo}\");\n    minify_test(\".foo { --test: var(--foo, 20px); }\", \".foo{--test:var(--foo,20px)}\");\n    minify_test(\n      \".foo { transition: var(--foo, 20px),\\nvar(--bar, 40px); }\",\n      \".foo{transition:var(--foo,20px), var(--bar,40px)}\",\n    );\n    minify_test(\n      \".foo { background: var(--color) var(--image); }\",\n      \".foo{background:var(--color) var(--image)}\",\n    );\n    minify_test(\n      \".foo { height: calc(var(--spectrum-global-dimension-size-300) / 2);\",\n      \".foo{height:calc(var(--spectrum-global-dimension-size-300) / 2)}\",\n    );\n    minify_test(\n      \".foo { color: var(--color, rgb(255, 255, 0)); }\",\n      \".foo{color:var(--color,#ff0)}\",\n    );\n    minify_test(\n      \".foo { color: var(--color, #ffff00); }\",\n      \".foo{color:var(--color,#ff0)}\",\n    );\n    minify_test(\n      \".foo { color: var(--color, rgb(var(--red), var(--green), 0)); }\",\n      \".foo{color:var(--color,rgb(var(--red), var(--green), 0))}\",\n    );\n    minify_test(\".foo { --test: .5s; }\", \".foo{--test:.5s}\");\n    minify_test(\".foo { --theme-sizes-1\\\\/12: 2 }\", \".foo{--theme-sizes-1\\\\/12:2}\");\n    minify_test(\".foo { --test: 0px; }\", \".foo{--test:0px}\");\n    test(\n      \".foo { transform: var(--bar, ) }\",\n      indoc! {r#\"\n      .foo {\n        transform: var(--bar, );\n      }\n      \"#},\n    );\n    test(\n      \".foo { transform: env(--bar, ) }\",\n      indoc! {r#\"\n      .foo {\n        transform: env(--bar, );\n      }\n      \"#},\n    );\n\n    // Test attr() function with type() syntax - minified\n    minify_test(\n      \".foo { background-color: attr(data-color type(<color>)); }\",\n      \".foo{background-color:attr(data-color type(<color>))}\",\n    );\n    minify_test(\n      \".foo { width: attr(data-width type(<length>), 100px); }\",\n      \".foo{width:attr(data-width type(<length>), 100px)}\",\n    );\n\n    minify_test(\".foo { width: attr( data-foo    % ); }\", \".foo{width:attr(data-foo %)}\");\n\n    // <attr-args> = attr( <declaration-value>, <declaration-value>? )\n    // Like var(), a bare comma can be used with nothing following it, indicating that the second <declaration-value> was passed, just as an empty sequence.\n    // Spec: https://drafts.csswg.org/css-values-5/#funcdef-attr\n    minify_test(\n      \".foo { width: attr( data-foo    %, ); }\",\n      \".foo{width:attr(data-foo %,)}\",\n    );\n\n    minify_test(\n      \".foo { width: attr( data-foo    px ); }\",\n      \".foo{width:attr(data-foo px)}\",\n    );\n\n    minify_test(\n      \".foo { width: attr(data-foo    number ); }\",\n      \".foo{width:attr(data-foo number)}\",\n    );\n\n    minify_test(\n      \".foo { width: attr(data-foo    raw-string); }\",\n      \".foo{width:attr(data-foo raw-string)}\",\n    );\n\n    // Test attr() function with type() syntax - non-minified (issue with extra spaces)\n    test(\n      \".foo { background-color: attr(data-color type(<color>)); }\",\n      \".foo {\\n  background-color: attr(data-color type(<color>));\\n}\\n\",\n    );\n    test(\n      \".foo { width: attr(data-width type(<length>), 100px); }\",\n      \".foo {\\n  width: attr(data-width type(<length>), 100px);\\n}\\n\",\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        --custom: lab(40% 56.6 39);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        --custom: #b32323;\n      }\n\n      @supports (color: lab(0% 0 0)) {\n        .foo {\n          --custom: lab(40% 56.6 39);\n        }\n      }\n    \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      @supports (color: lab(0% 0 0)) {\n        .foo {\n          --custom: lab(40% 56.6 39);\n        }\n      }\n    \"#,\n      indoc! {r#\"\n      @supports (color: lab(0% 0 0)) {\n        .foo {\n          --custom: lab(40% 56.6 39);\n        }\n      }\n    \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        --custom: lab(40% 56.6 39) !important;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        --custom: #b32323 !important;\n      }\n\n      @supports (color: lab(0% 0 0)) {\n        .foo {\n          --custom: lab(40% 56.6 39) !important;\n        }\n      }\n    \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      @supports (color: lab(0% 0 0)) {\n        .foo {\n          --custom: lab(40% 56.6 39) !important;\n        }\n      }\n    \"#,\n      indoc! {r#\"\n      @supports (color: lab(0% 0 0)) {\n        .foo {\n          --custom: lab(40% 56.6 39) !important;\n        }\n      }\n    \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        --custom: lab(40% 56.6 39);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        --custom: #b32323;\n      }\n\n      @supports (color: color(display-p3 0 0 0)) {\n        .foo {\n          --custom: color(display-p3 .643308 .192455 .167712);\n        }\n      }\n\n      @supports (color: lab(0% 0 0)) {\n        .foo {\n          --custom: lab(40% 56.6 39);\n        }\n      }\n    \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      @supports (color: color(display-p3 0 0 0)) {\n        .foo {\n          --custom: color(display-p3 .643308 .192455 .167712);\n        }\n      }\n\n      @supports (color: lab(0% 0 0)) {\n        .foo {\n          --custom: lab(40% 56.6 39);\n        }\n      }\n    \"#,\n      indoc! {r#\"\n      @supports (color: color(display-p3 0 0 0)) {\n        .foo {\n          --custom: color(display-p3 .643308 .192455 .167712);\n        }\n      }\n\n      @supports (color: lab(0% 0 0)) {\n        .foo {\n          --custom: lab(40% 56.6 39);\n        }\n      }\n    \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        --custom: lab(40% 56.6 39);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        --custom: color(display-p3 .643308 .192455 .167712);\n      }\n\n      @supports (color: lab(0% 0 0)) {\n        .foo {\n          --custom: lab(40% 56.6 39);\n        }\n      }\n    \"#},\n      Browsers {\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        --custom: lab(40% 56.6 39);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        --custom: lab(40% 56.6 39);\n      }\n    \"#},\n      Browsers {\n        safari: Some(15 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        --custom: oklab(59.686% 0.1009 0.1192);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        --custom: lab(52.2319% 40.1449 59.9171);\n      }\n    \"#},\n      Browsers {\n        safari: Some(15 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        --custom: oklab(59.686% 0.1009 0.1192);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        --custom: color(display-p3 .724144 .386777 .148795);\n      }\n\n      @supports (color: lab(0% 0 0)) {\n        .foo {\n          --custom: lab(52.2319% 40.1449 59.9171);\n        }\n      }\n    \"#},\n      Browsers {\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        --custom: oklab(59.686% 0.1009 0.1192);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        --custom: #c65d07;\n      }\n\n      @supports (color: color(display-p3 0 0 0)) {\n        .foo {\n          --custom: color(display-p3 .724144 .386777 .148795);\n        }\n      }\n\n      @supports (color: lab(0% 0 0)) {\n        .foo {\n          --custom: lab(52.2319% 40.1449 59.9171);\n        }\n      }\n    \"#},\n      Browsers {\n        safari: Some(14 << 16),\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        --foo: oklab(59.686% 0.1009 0.1192);\n        --bar: lab(40% 56.6 39);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        --foo: #c65d07;\n        --bar: #b32323;\n      }\n\n      @supports (color: color(display-p3 0 0 0)) {\n        .foo {\n          --foo: color(display-p3 .724144 .386777 .148795);\n          --bar: color(display-p3 .643308 .192455 .167712);\n        }\n      }\n\n      @supports (color: lab(0% 0 0)) {\n        .foo {\n          --foo: lab(52.2319% 40.1449 59.9171);\n          --bar: lab(40% 56.6 39);\n        }\n      }\n    \"#},\n      Browsers {\n        safari: Some(14 << 16),\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        --foo: color(display-p3 0 1 0);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        --foo: #00f942;\n      }\n\n      @supports (color: color(display-p3 0 0 0)) {\n        .foo {\n          --foo: color(display-p3 0 1 0);\n        }\n      }\n    \"#},\n      Browsers {\n        safari: Some(14 << 16),\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      @supports (color: color(display-p3 0 0 0)) {\n        .foo {\n          --foo: color(display-p3 0 1 0);\n        }\n      }\n    \"#,\n      indoc! {r#\"\n      @supports (color: color(display-p3 0 0 0)) {\n        .foo {\n          --foo: color(display-p3 0 1 0);\n        }\n      }\n    \"#},\n      Browsers {\n        safari: Some(14 << 16),\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        --foo: color(display-p3 0 1 0);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        --foo: color(display-p3 0 1 0);\n      }\n    \"#},\n      Browsers {\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        --foo: color(display-p3 0 1 0);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        --foo: #00f942;\n      }\n\n      @supports (color: color(display-p3 0 0 0)) {\n        .foo {\n          --foo: color(display-p3 0 1 0);\n        }\n      }\n    \"#},\n      Browsers {\n        safari: Some(15 << 16),\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        --foo: color(display-p3 0 1 0);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        --foo: #00f942;\n      }\n\n      @supports (color: color(display-p3 0 0 0)) {\n        .foo {\n          --foo: color(display-p3 0 1 0);\n        }\n      }\n    \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        text-decoration: underline;\n      }\n\n      .foo {\n        --custom: lab(40% 56.6 39);\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        --custom: #b32323;\n        text-decoration: underline;\n      }\n\n      @supports (color: lab(0% 0 0)) {\n        .foo {\n          --custom: lab(40% 56.6 39);\n        }\n      }\n    \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        --custom: lab(40% 56.6 39);\n      }\n\n      .foo {\n        text-decoration: underline;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        --custom: #b32323;\n      }\n\n      @supports (color: lab(0% 0 0)) {\n        .foo {\n          --custom: lab(40% 56.6 39);\n        }\n      }\n\n      .foo {\n        text-decoration: underline;\n      }\n    \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      @keyframes foo {\n        from {\n          --custom: lab(40% 56.6 39);\n        }\n\n        to {\n          --custom: lch(50.998% 135.363 338);\n        }\n      }\n    \"#,\n      indoc! {r#\"\n      @keyframes foo {\n        from {\n          --custom: #b32323;\n        }\n\n        to {\n          --custom: #ee00be;\n        }\n      }\n\n      @supports (color: lab(0% 0 0)) {\n        @keyframes foo {\n          from {\n            --custom: lab(40% 56.6 39);\n          }\n\n          to {\n            --custom: lab(50.998% 125.506 -50.7078);\n          }\n        }\n      }\n    \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      @supports (color: lab(0% 0 0)) {\n        @keyframes foo {\n          from {\n            --custom: lab(40% 56.6 39);\n          }\n\n          to {\n            --custom: lab(50.998% 125.506 -50.7078);\n          }\n        }\n      }\n    \"#,\n      indoc! {r#\"\n      @supports (color: lab(0% 0 0)) {\n        @keyframes foo {\n          from {\n            --custom: lab(40% 56.6 39);\n          }\n\n          to {\n            --custom: lab(50.998% 125.506 -50.7078);\n          }\n        }\n      }\n    \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      @keyframes foo {\n        from {\n          --custom: lab(40% 56.6 39);\n        }\n\n        to {\n          --custom: lch(50.998% 135.363 338);\n        }\n      }\n    \"#,\n      indoc! {r#\"\n      @keyframes foo {\n        from {\n          --custom: #b32323;\n        }\n\n        to {\n          --custom: #ee00be;\n        }\n      }\n\n      @supports (color: color(display-p3 0 0 0)) {\n        @keyframes foo {\n          from {\n            --custom: color(display-p3 .643308 .192455 .167712);\n          }\n\n          to {\n            --custom: color(display-p3 .972962 -.362078 .804206);\n          }\n        }\n      }\n\n      @supports (color: lab(0% 0 0)) {\n        @keyframes foo {\n          from {\n            --custom: lab(40% 56.6 39);\n          }\n\n          to {\n            --custom: lab(50.998% 125.506 -50.7078);\n          }\n        }\n      }\n    \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      @supports (color: color(display-p3 0 0 0)) {\n        @keyframes foo {\n          from {\n            --custom: color(display-p3 .643308 .192455 .167712);\n          }\n\n          to {\n            --custom: color(display-p3 .972962 -.362078 .804206);\n          }\n        }\n      }\n\n      @supports (color: lab(0% 0 0)) {\n        @keyframes foo {\n          from {\n            --custom: lab(40% 56.6 39);\n          }\n\n          to {\n            --custom: lab(50.998% 125.506 -50.7078);\n          }\n        }\n      }\n    \"#,\n      indoc! {r#\"\n      @supports (color: color(display-p3 0 0 0)) {\n        @keyframes foo {\n          from {\n            --custom: color(display-p3 .643308 .192455 .167712);\n          }\n\n          to {\n            --custom: color(display-p3 .972962 -.362078 .804206);\n          }\n        }\n      }\n\n      @supports (color: lab(0% 0 0)) {\n        @keyframes foo {\n          from {\n            --custom: lab(40% 56.6 39);\n          }\n\n          to {\n            --custom: lab(50.998% 125.506 -50.7078);\n          }\n        }\n      }\n    \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      @keyframes foo {\n        from {\n          --custom: #ff0;\n          opacity: 0;\n        }\n\n        to {\n          --custom: lch(50.998% 135.363 338);\n          opacity: 1;\n        }\n      }\n    \"#,\n      indoc! {r#\"\n      @keyframes foo {\n        from {\n          --custom: #ff0;\n          opacity: 0;\n        }\n\n        to {\n          --custom: #ee00be;\n          opacity: 1;\n        }\n      }\n\n      @supports (color: lab(0% 0 0)) {\n        @keyframes foo {\n          from {\n            --custom: #ff0;\n            opacity: 0;\n          }\n\n          to {\n            --custom: lab(50.998% 125.506 -50.7078);\n            opacity: 1;\n          }\n        }\n      }\n    \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      @keyframes foo {\n        from {\n          text-decoration: var(--foo) lab(29.2345% 39.3825 20.0664);\n        }\n      }\n    \"#,\n      indoc! {r#\"\n      @keyframes foo {\n        from {\n          text-decoration: var(--foo) #7d2329;\n        }\n      }\n\n      @supports (color: lab(0% 0 0)) {\n        @keyframes foo {\n          from {\n            text-decoration: var(--foo) lab(29.2345% 39.3825 20.0664);\n          }\n        }\n      }\n    \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n  }\n\n  #[test]\n  fn test_charset() {\n    test(\n      r#\"\n      @charset \"UTF-8\";\n\n      .foo {\n        color: red;\n      }\n\n      @charset \"UTF-8\";\n\n      .bar {\n        color: yellow;\n      }\n    \"#,\n      indoc! { r#\"\n      .foo {\n        color: red;\n      }\n\n      .bar {\n        color: #ff0;\n      }\n    \"#},\n    )\n  }\n\n  #[test]\n  fn test_style_attr() {\n    attr_test(\"color: yellow; flex: 1 1 auto\", \"color: #ff0; flex: auto\", false, None);\n    attr_test(\"color: yellow; flex: 1 1 auto\", \"color:#ff0;flex:auto\", true, None);\n    attr_test(\n      \"border-inline-start: 2px solid red\",\n      \"border-inline-start: 2px solid red\",\n      false,\n      Some(Browsers {\n        safari: Some(12 << 16),\n        ..Browsers::default()\n      }),\n    );\n    attr_test(\n      \"color: lab(40% 56.6 39);\",\n      \"color:#b32323;color:lab(40% 56.6 39)\",\n      true,\n      Some(Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      }),\n    );\n    attr_test(\n      \"--foo: lab(40% 56.6 39);\",\n      \"--foo:#b32323\",\n      true,\n      Some(Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      }),\n    );\n    attr_test(\n      \"text-decoration: var(--foo) lab(40% 56.6 39);\",\n      \"text-decoration:var(--foo) #b32323\",\n      true,\n      Some(Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      }),\n    );\n  }\n\n  #[test]\n  fn test_nesting() {\n    nesting_test(\n      r#\"\n        .foo {\n          color: blue;\n          & > .bar { color: red; }\n        }\n      \"#,\n      indoc! {r#\"\n        .foo {\n          color: #00f;\n        }\n\n        .foo > .bar {\n          color: red;\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        .foo {\n          color: blue;\n          &.bar { color: red; }\n        }\n      \"#,\n      indoc! {r#\"\n        .foo {\n          color: #00f;\n        }\n\n        .foo.bar {\n          color: red;\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        .foo, .bar {\n          color: blue;\n          & + .baz, &.qux { color: red; }\n        }\n      \"#,\n      indoc! {r#\"\n        .foo, .bar {\n          color: #00f;\n        }\n\n        :is(.foo, .bar) + .baz, :is(.foo, .bar).qux {\n          color: red;\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        .foo {\n          color: blue;\n          & .bar & .baz & .qux { color: red; }\n        }\n      \"#,\n      indoc! {r#\"\n        .foo {\n          color: #00f;\n        }\n\n        .foo .bar .foo .baz .foo .qux {\n          color: red;\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        .foo {\n          color: blue;\n          & { padding: 2ch; }\n        }\n      \"#,\n      indoc! {r#\"\n        .foo {\n          color: #00f;\n        }\n\n        .foo {\n          padding: 2ch;\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        .foo {\n          color: blue;\n          && { padding: 2ch; }\n        }\n      \"#,\n      indoc! {r#\"\n        .foo {\n          color: #00f;\n        }\n\n        .foo.foo {\n          padding: 2ch;\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        .error, .invalid {\n          &:hover > .baz { color: red; }\n        }\n      \"#,\n      indoc! {r#\"\n        :is(.error, .invalid):hover > .baz {\n          color: red;\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        .foo {\n          &:is(.bar, &.baz) { color: red; }\n        }\n      \"#,\n      indoc! {r#\"\n        .foo:is(.bar, .foo.baz) {\n          color: red;\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        figure {\n          margin: 0;\n\n          & > figcaption {\n            background: hsl(0 0% 0% / 50%);\n\n            & > p {\n              font-size: .9rem;\n            }\n          }\n        }\n      \"#,\n      indoc! {r#\"\n        figure {\n          margin: 0;\n        }\n\n        figure > figcaption {\n          background: #00000080;\n        }\n\n        figure > figcaption > p {\n          font-size: .9rem;\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        .foo {\n          display: grid;\n\n          @media (orientation: landscape) {\n            grid-auto-flow: column;\n          }\n        }\n      \"#,\n      indoc! {r#\"\n        .foo {\n          display: grid;\n        }\n\n        @media (orientation: landscape) {\n          .foo {\n            grid-auto-flow: column;\n          }\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        .foo {\n          display: grid;\n\n          @media (orientation: landscape) {\n            grid-auto-flow: column;\n\n            @media (width > 1024px) {\n              max-inline-size: 1024px;\n            }\n          }\n        }\n      \"#,\n      indoc! {r#\"\n        .foo {\n          display: grid;\n        }\n\n        @media (orientation: landscape) {\n          .foo {\n            grid-auto-flow: column;\n          }\n\n          @media not (max-width: 1024px) {\n            .foo {\n              max-inline-size: 1024px;\n            }\n          }\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        .foo {\n          @media (min-width: 640px) {\n            color: red !important;\n          }\n        }\n      \"#,\n      indoc! {r#\"\n        @media (min-width: 640px) {\n          .foo {\n            color: red !important;\n          }\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        .foo {\n          display: grid;\n\n          @supports (foo: bar) {\n            grid-auto-flow: column;\n          }\n        }\n      \"#,\n      indoc! {r#\"\n        .foo {\n          display: grid;\n        }\n\n        @supports (foo: bar) {\n          .foo {\n            grid-auto-flow: column;\n          }\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        .foo {\n          display: grid;\n\n          @container (min-width: 100px) {\n            grid-auto-flow: column;\n          }\n        }\n      \"#,\n      indoc! {r#\"\n        .foo {\n          display: grid;\n        }\n\n        @container (width >= 100px) {\n          .foo {\n            grid-auto-flow: column;\n          }\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        .foo {\n          display: grid;\n\n          @layer test {\n            grid-auto-flow: column;\n          }\n        }\n      \"#,\n      indoc! {r#\"\n        .foo {\n          display: grid;\n        }\n\n        @layer test {\n          .foo {\n            grid-auto-flow: column;\n          }\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        .foo {\n          display: grid;\n\n          @layer {\n            grid-auto-flow: column;\n          }\n        }\n      \"#,\n      indoc! {r#\"\n        .foo {\n          display: grid;\n        }\n\n        @layer {\n          .foo {\n            grid-auto-flow: column;\n          }\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        @namespace \"http://example.com/foo\";\n        @namespace toto \"http://toto.example.org\";\n\n        .foo {\n          &div {\n            color: red;\n          }\n\n          &* {\n            color: green;\n          }\n\n          &|x {\n            color: red;\n          }\n\n          &*|x {\n            color: green;\n          }\n\n          &toto|x {\n            color: red;\n          }\n        }\n      \"#,\n      indoc! {r#\"\n        @namespace \"http://example.com/foo\";\n        @namespace toto \"http://toto.example.org\";\n\n        div.foo {\n          color: red;\n        }\n\n        *.foo {\n          color: green;\n        }\n\n        |x.foo {\n          color: red;\n        }\n\n        *|x.foo {\n          color: green;\n        }\n\n        toto|x.foo {\n          color: red;\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        .foo {\n          &article > figure {\n            color: red;\n          }\n        }\n      \"#,\n      indoc! {r#\"\n        article.foo > figure {\n          color: red;\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        div {\n          &.bar {\n            background: green;\n          }\n        }\n      \"#,\n      indoc! {r#\"\n        div.bar {\n          background: green;\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        div > .foo {\n          &span {\n            background: green;\n          }\n        }\n      \"#,\n      indoc! {r#\"\n        span:is(div > .foo) {\n          background: green;\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        .foo {\n          & h1 {\n            background: green;\n          }\n        }\n      \"#,\n      indoc! {r#\"\n        .foo h1 {\n          background: green;\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        .foo .bar {\n          &h1 {\n            background: green;\n          }\n        }\n      \"#,\n      indoc! {r#\"\n        h1:is(.foo .bar) {\n          background: green;\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        .foo.bar {\n          &h1 {\n            background: green;\n          }\n        }\n      \"#,\n      indoc! {r#\"\n        h1.foo.bar {\n          background: green;\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        .foo .bar {\n          &h1 .baz {\n            background: green;\n          }\n        }\n      \"#,\n      indoc! {r#\"\n        h1:is(.foo .bar) .baz {\n          background: green;\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        .foo .bar {\n          &.baz {\n            background: green;\n          }\n        }\n      \"#,\n      indoc! {r#\"\n        .foo .bar.baz {\n          background: green;\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        .foo {\n          color: red;\n          @nest .parent & {\n            color: blue;\n          }\n        }\n      \"#,\n      indoc! {r#\"\n        .foo {\n          color: red;\n        }\n\n        .parent .foo {\n          color: #00f;\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        .foo {\n          color: red;\n          @nest :not(&) {\n            color: blue;\n          }\n        }\n      \"#,\n      indoc! {r#\"\n        .foo {\n          color: red;\n        }\n\n        :not(.foo) {\n          color: #00f;\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        .foo {\n          color: blue;\n          @nest .bar & {\n            color: red;\n            &.baz {\n              color: green;\n            }\n          }\n        }\n      \"#,\n      indoc! {r#\"\n        .foo {\n          color: #00f;\n        }\n\n        .bar .foo {\n          color: red;\n        }\n\n        .bar .foo.baz {\n          color: green;\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        .foo {\n          @nest :not(&) {\n            color: red;\n          }\n\n          & h1 {\n            background: green;\n          }\n        }\n      \"#,\n      indoc! {r#\"\n        :not(.foo) {\n          color: red;\n        }\n\n        .foo h1 {\n          background: green;\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        .foo {\n          & h1 {\n            background: green;\n          }\n\n          @nest :not(&) {\n            color: red;\n          }\n        }\n      \"#,\n      indoc! {r#\"\n        .foo h1 {\n          background: green;\n        }\n\n        :not(.foo) {\n          color: red;\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        .foo .bar {\n          @nest h1& {\n            background: green;\n          }\n        }\n      \"#,\n      indoc! {r#\"\n        h1:is(.foo .bar) {\n          background: green;\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        @namespace \"http://example.com/foo\";\n        @namespace toto \"http://toto.example.org\";\n\n        div {\n          @nest .foo& {\n            color: red;\n          }\n        }\n\n        * {\n          @nest .foo& {\n            color: red;\n          }\n        }\n\n        |x {\n          @nest .foo& {\n            color: red;\n          }\n        }\n\n        *|x {\n          @nest .foo& {\n            color: red;\n          }\n        }\n\n        toto|x {\n          @nest .foo& {\n            color: red;\n          }\n        }\n      \"#,\n      indoc! {r#\"\n        @namespace \"http://example.com/foo\";\n        @namespace toto \"http://toto.example.org\";\n\n        .foo:is(div) {\n          color: red;\n        }\n\n        .foo:is(*) {\n          color: red;\n        }\n\n        .foo:is(|x) {\n          color: red;\n        }\n\n        .foo:is(*|x) {\n          color: red;\n        }\n\n        .foo:is(toto|x) {\n          color: red;\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        .foo .bar {\n          @nest h1 .baz& {\n            background: green;\n          }\n        }\n      \"#,\n      indoc! {r#\"\n        h1 .baz:is(.foo .bar) {\n          background: green;\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        .foo .bar {\n          @nest .baz& {\n            background: green;\n          }\n        }\n      \"#,\n      indoc! {r#\"\n        .baz:is(.foo .bar) {\n          background: green;\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        .foo .bar {\n          @nest .baz & {\n            background: green;\n          }\n        }\n      \"#,\n      indoc! {r#\"\n        .baz :is(.foo .bar) {\n          background: green;\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        .foo {\n          color: red;\n          @nest & > .bar {\n            color: blue;\n          }\n        }\n      \"#,\n      indoc! {r#\"\n        .foo {\n          color: red;\n        }\n\n        .foo > .bar {\n          color: #00f;\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n      .foo {\n        color: red;\n        .bar {\n          color: blue;\n        }\n      }\n      \"#,\n      indoc! {r#\"\n      .foo {\n        color: red;\n      }\n\n      .foo .bar {\n        color: #00f;\n      }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n      .foo {\n        color: red;\n        .bar & {\n          color: blue;\n        }\n      }\n      \"#,\n      indoc! {r#\"\n      .foo {\n        color: red;\n      }\n\n      .bar .foo {\n        color: #00f;\n      }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n      .foo {\n        color: red;\n        + .bar + & { color: blue; }\n      }\n      \"#,\n      indoc! {r#\"\n      .foo {\n        color: red;\n      }\n\n      .foo + .bar + .foo {\n        color: #00f;\n      }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n      .foo {\n        color: red;\n        .bar & {\n          color: blue;\n        }\n      }\n      \"#,\n      indoc! {r#\"\n      .foo {\n        color: red;\n      }\n\n      .bar .foo {\n        color: #00f;\n      }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        .foo {\n          color: red;\n          .parent & {\n            color: blue;\n          }\n        }\n      \"#,\n      indoc! {r#\"\n        .foo {\n          color: red;\n        }\n\n        .parent .foo {\n          color: #00f;\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        .foo {\n          color: red;\n          :not(&) {\n            color: blue;\n          }\n        }\n      \"#,\n      indoc! {r#\"\n        .foo {\n          color: red;\n        }\n\n        :not(.foo) {\n          color: #00f;\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        .foo {\n          color: blue;\n          .bar & {\n            color: red;\n            &.baz {\n              color: green;\n            }\n          }\n        }\n      \"#,\n      indoc! {r#\"\n        .foo {\n          color: #00f;\n        }\n\n        .bar .foo {\n          color: red;\n        }\n\n        .bar .foo.baz {\n          color: green;\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        .foo {\n          :not(&) {\n            color: red;\n          }\n\n          & h1 {\n            background: green;\n          }\n        }\n      \"#,\n      indoc! {r#\"\n        :not(.foo) {\n          color: red;\n        }\n\n        .foo h1 {\n          background: green;\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        .foo {\n          & h1 {\n            background: green;\n          }\n\n          :not(&) {\n            color: red;\n          }\n        }\n      \"#,\n      indoc! {r#\"\n        .foo h1 {\n          background: green;\n        }\n\n        :not(.foo) {\n          color: red;\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        .foo .bar {\n          :is(h1)& {\n            background: green;\n          }\n        }\n      \"#,\n      indoc! {r#\"\n        :is(h1):is(.foo .bar) {\n          background: green;\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        @namespace \"http://example.com/foo\";\n        @namespace toto \"http://toto.example.org\";\n\n        div {\n          .foo& {\n            color: red;\n          }\n        }\n\n        * {\n          .foo& {\n            color: red;\n          }\n        }\n\n        |x {\n          .foo& {\n            color: red;\n          }\n        }\n\n        *|x {\n          .foo& {\n            color: red;\n          }\n        }\n\n        toto|x {\n          .foo& {\n            color: red;\n          }\n        }\n      \"#,\n      indoc! {r#\"\n        @namespace \"http://example.com/foo\";\n        @namespace toto \"http://toto.example.org\";\n\n        .foo:is(div) {\n          color: red;\n        }\n\n        .foo:is(*) {\n          color: red;\n        }\n\n        .foo:is(|x) {\n          color: red;\n        }\n\n        .foo:is(*|x) {\n          color: red;\n        }\n\n        .foo:is(toto|x) {\n          color: red;\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        .foo .bar {\n          :is(h1) .baz& {\n            background: green;\n          }\n        }\n      \"#,\n      indoc! {r#\"\n        :is(h1) .baz:is(.foo .bar) {\n          background: green;\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        .foo .bar {\n          .baz& {\n            background: green;\n          }\n        }\n      \"#,\n      indoc! {r#\"\n        .baz:is(.foo .bar) {\n          background: green;\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        .foo .bar {\n          .baz & {\n            background: green;\n          }\n        }\n      \"#,\n      indoc! {r#\"\n        .baz :is(.foo .bar) {\n          background: green;\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        .foo {\n          .bar {\n            color: blue;\n          }\n          color: red;\n        }\n      \"#,\n      indoc! {r#\"\n        .foo .bar {\n          color: #00f;\n        }\n\n        .foo {\n          color: red;\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        article {\n          color: green;\n          & { color: blue; }\n          color: red;\n        }\n      \"#,\n      indoc! {r#\"\n        article {\n          color: green;\n        }\n\n        article {\n          color: #00f;\n        }\n\n        article {\n          color: red;\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        & .foo {\n          color: red;\n        }\n      \"#,\n      indoc! {r#\"\n        :scope .foo {\n          color: red;\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        &.foo {\n          color: red;\n        }\n      \"#,\n      indoc! {r#\"\n        :scope.foo {\n          color: red;\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        .foo& {\n          color: red;\n        }\n      \"#,\n      indoc! {r#\"\n        .foo:scope {\n          color: red;\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        &html {\n          color: red;\n        }\n      \"#,\n      indoc! {r#\"\n        html:scope {\n          color: red;\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        .foo {\n          color: blue;\n          div {\n            color: red;\n          }\n        }\n      \"#,\n      indoc! {r#\"\n        .foo {\n          color: #00f;\n        }\n\n        .foo div {\n          color: red;\n        }\n      \"#},\n    );\n\n    nesting_test(\n      r#\"\n        div {\n          color: blue;\n\n          button:focus {\n            color: red;\n          }\n        }\n      \"#,\n      indoc! {r#\"\n        div {\n          color: #00f;\n        }\n\n        div button:focus {\n          color: red;\n        }\n      \"#},\n    );\n    nesting_test(\n      r#\"\n        div {\n          color: blue;\n\n          --button:focus {\n            color: red;\n          }\n        }\n      \"#,\n      indoc! {r#\"\n        div {\n          color: #00f;\n          --button: focus {\n                    color: red;\n                  };\n        }\n      \"#},\n    );\n    nesting_test(\n      r#\"\n      .foo {\n        &::before, &::after {\n          background: blue;\n          @media screen {\n            background: orange;\n          }\n        }\n      }\n      \"#,\n      indoc! {r#\"\n      .foo:before, .foo:after {\n        background: #00f;\n      }\n\n      @media screen {\n        .foo:before, .foo:after {\n          background: orange;\n        }\n      }\n      \"#},\n    );\n\n    nesting_test_no_targets(\n      r#\"\n        .foo {\n          color: blue;\n          @nest .bar & {\n            color: red;\n            &.baz {\n              color: green;\n            }\n          }\n        }\n      \"#,\n      indoc! {r#\"\n        .foo {\n          color: #00f;\n\n          @nest .bar & {\n            color: red;\n\n            &.baz {\n              color: green;\n            }\n          }\n        }\n      \"#},\n    );\n\n    nesting_test_no_targets(\n      r#\"\n        .foo {\n          color: blue;\n          &div {\n            color: red;\n          }\n\n          &span {\n            color: purple;\n          }\n        }\n      \"#,\n      indoc! {r#\"\n        .foo {\n          color: #00f;\n\n          &div {\n            color: red;\n          }\n\n          &span {\n            color: purple;\n          }\n        }\n      \"#},\n    );\n\n    nesting_test_no_targets(\n      r#\"\n        .error, .invalid {\n          &:hover > .baz { color: red; }\n        }\n      \"#,\n      indoc! {r#\"\n        .error, .invalid {\n          &:hover > .baz {\n            color: red;\n          }\n        }\n      \"#},\n    );\n\n    nesting_test_with_targets(\n      r#\"\n        .foo {\n          color: blue;\n          & > .bar { color: red; }\n        }\n      \"#,\n      indoc! {r#\"\n        .foo {\n          color: #00f;\n        }\n\n        .foo > .bar {\n          color: red;\n        }\n      \"#},\n      Targets {\n        browsers: Some(Browsers {\n          chrome: Some(112 << 16),\n          ..Browsers::default()\n        }),\n        include: Features::Nesting,\n        exclude: Features::empty(),\n      },\n    );\n    nesting_test_with_targets(\n      r#\"\n        .foo {\n          color: blue;\n          & > .bar { color: red; }\n        }\n      \"#,\n      indoc! {r#\"\n        .foo {\n          color: #00f;\n\n          & > .bar {\n            color: red;\n          }\n        }\n      \"#},\n      Targets {\n        browsers: Some(Browsers {\n          chrome: Some(50 << 16),\n          ..Browsers::default()\n        }),\n        include: Features::empty(),\n        exclude: Features::Nesting,\n      },\n    );\n\n    let mut stylesheet = StyleSheet::parse(\n      r#\"\n      .foo {\n        color: blue;\n        .bar {\n          color: red;\n        }\n      }\n      \"#,\n      ParserOptions::default(),\n    )\n    .unwrap();\n    stylesheet.minify(MinifyOptions::default()).unwrap();\n    let res = stylesheet\n      .to_css(PrinterOptions {\n        minify: true,\n        ..PrinterOptions::default()\n      })\n      .unwrap();\n    assert_eq!(res.code, \".foo{color:#00f;& .bar{color:red}}\");\n\n    nesting_test_with_targets(\n      r#\"\n        .a {\n          &.b,\n          &.c {\n            &.d {\n              color: red;\n            }\n          }\n        }\n      \"#,\n      indoc! {r#\"\n        .a.b.d {\n          color: red;\n        }\n\n        .a.c.d {\n          color: red;\n        }\n      \"#},\n      Targets {\n        browsers: Some(Browsers {\n          safari: Some(13 << 16),\n          ..Browsers::default()\n        }),\n        include: Features::Nesting,\n        exclude: Features::empty(),\n      },\n    );\n\n    minify_test(\n      r#\"\n    .foo {\n      color: red;\n      .bar {\n        color: green;\n      }\n      color: blue;\n      .baz {\n        color: pink;\n      }\n    }\"#,\n      \".foo{color:red;& .bar{color:green}color:#00f;& .baz{color:pink}}\",\n    );\n  }\n\n  #[test]\n  fn test_nesting_error_recovery() {\n    error_recovery_test(\n      \"\n    .container {\n      padding: 3rem;\n      @media (max-width: --styled-jsx-placeholder-0__) {\n        .responsive {\n          color: purple;\n        }\n      }\n    }\n    \",\n    );\n  }\n\n  #[test]\n  fn test_css_variable_error_recovery() {\n    error_recovery_test(\"\n    .container {\n      --local-var: --styled-jsx-placeholder-0__;\n      color: var(--text-color);\n      background: linear-gradient(to right, --styled-jsx-placeholder-1__, --styled-jsx-placeholder-2__);\n\n      .item {\n        transform: translate(calc(var(--x) + --styled-jsx-placeholder-3__px), calc(var(--y) + --styled-jsx-placeholder-4__px));\n      }\n\n      div {\n        margin: calc(10px + --styled-jsx-placeholder-5__px);\n      }\n    }\n  \");\n  }\n\n  #[test]\n  fn test_css_modules() {\n    css_modules_test(\n      r#\"\n      .foo {\n        color: red;\n      }\n\n      #id {\n        animation: 2s test;\n      }\n\n      @keyframes test {\n        from { color: red }\n        to { color: yellow }\n      }\n\n      @counter-style circles {\n        symbols: Ⓐ Ⓑ Ⓒ;\n      }\n\n      ul {\n        list-style: circles;\n      }\n\n      ol {\n        list-style-type: none;\n      }\n\n      li {\n        list-style-type: disc;\n      }\n\n      @keyframes fade {\n        from { opacity: 0 }\n        to { opacity: 1 }\n      }\n    \"#,\n      indoc! {r#\"\n      .EgL3uq_foo {\n        color: red;\n      }\n\n      #EgL3uq_id {\n        animation: 2s EgL3uq_test;\n      }\n\n      @keyframes EgL3uq_test {\n        from {\n          color: red;\n        }\n\n        to {\n          color: #ff0;\n        }\n      }\n\n      @counter-style EgL3uq_circles {\n        symbols: Ⓐ Ⓑ Ⓒ;\n      }\n\n      ul {\n        list-style: EgL3uq_circles;\n      }\n\n      ol {\n        list-style-type: none;\n      }\n\n      li {\n        list-style-type: disc;\n      }\n\n      @keyframes EgL3uq_fade {\n        from {\n          opacity: 0;\n        }\n\n        to {\n          opacity: 1;\n        }\n      }\n    \"#},\n      map! {\n        \"foo\" => \"EgL3uq_foo\",\n        \"id\" => \"EgL3uq_id\",\n        \"test\" => \"EgL3uq_test\" referenced: true,\n        \"circles\" => \"EgL3uq_circles\" referenced: true,\n        \"fade\" => \"EgL3uq_fade\"\n      },\n      HashMap::new(),\n      Default::default(),\n      false,\n    );\n\n    css_modules_test(\n      r#\"\n      .foo {\n        color: red;\n      }\n\n      #id {\n        animation: 2s test;\n      }\n\n      @keyframes test {\n        from { color: red }\n        to { color: yellow }\n      }\n    \"#,\n      indoc! {r#\"\n      .EgL3uq_foo {\n        color: red;\n      }\n\n      #EgL3uq_id {\n        animation: 2s test;\n      }\n\n      @keyframes test {\n        from {\n          color: red;\n        }\n\n        to {\n          color: #ff0;\n        }\n      }\n    \"#},\n      map! {\n        \"foo\" => \"EgL3uq_foo\",\n        \"id\" => \"EgL3uq_id\"\n      },\n      HashMap::new(),\n      crate::css_modules::Config {\n        animation: false,\n        // custom_idents: false,\n        ..Default::default()\n      },\n      false,\n    );\n\n    css_modules_test(\n      r#\"\n      @counter-style circles {\n        symbols: Ⓐ Ⓑ Ⓒ;\n      }\n\n      ul {\n        list-style: circles;\n      }\n\n      ol {\n        list-style-type: none;\n      }\n\n      li {\n        list-style-type: disc;\n      }\n    \"#,\n      indoc! {r#\"\n      @counter-style circles {\n        symbols: Ⓐ Ⓑ Ⓒ;\n      }\n\n      ul {\n        list-style: circles;\n      }\n\n      ol {\n        list-style-type: none;\n      }\n\n      li {\n        list-style-type: disc;\n      }\n    \"#},\n      map! {\n        \"circles\" => \"EgL3uq_circles\" referenced: true\n      },\n      HashMap::new(),\n      crate::css_modules::Config {\n        custom_idents: false,\n        ..Default::default()\n      },\n      false,\n    );\n\n    css_modules_test(\n      r#\"\n      body {\n        grid: [header-top] \"a a a\" [header-bottom]\n              [main-top] \"b b b\" 1fr [main-bottom]\n              / auto 1fr auto;\n      }\n\n      header {\n        grid-area: a;\n      }\n\n      main {\n        grid-row: main-top / main-bottom;\n      }\n    \"#,\n      indoc! {r#\"\n      body {\n        grid: [EgL3uq_header-top] \"EgL3uq_a EgL3uq_a EgL3uq_a\" [EgL3uq_header-bottom]\n              [EgL3uq_main-top] \"EgL3uq_b EgL3uq_b EgL3uq_b\" 1fr [EgL3uq_main-bottom]\n              / auto 1fr auto;\n      }\n\n      header {\n        grid-area: EgL3uq_a;\n      }\n\n      main {\n        grid-row: EgL3uq_main-top / EgL3uq_main-bottom;\n      }\n    \"#},\n      map! {\n        \"header-top\" => \"EgL3uq_header-top\",\n        \"header-bottom\" => \"EgL3uq_header-bottom\",\n        \"main-top\" => \"EgL3uq_main-top\",\n        \"main-bottom\" => \"EgL3uq_main-bottom\",\n        \"a\" => \"EgL3uq_a\",\n        \"b\" => \"EgL3uq_b\"\n      },\n      HashMap::new(),\n      Default::default(),\n      false,\n    );\n\n    css_modules_test(\n      r#\"\n        .grid {\n          grid-template-areas: \"foo\";\n        }\n\n        .foo {\n          grid-area: foo;\n        }\n\n        .bar {\n          grid-column-start: foo-start;\n        }\n      \"#,\n      indoc! {r#\"\n        .EgL3uq_grid {\n          grid-template-areas: \"EgL3uq_foo\";\n        }\n\n        .EgL3uq_foo {\n          grid-area: EgL3uq_foo;\n        }\n\n        .EgL3uq_bar {\n          grid-column-start: EgL3uq_foo-start;\n        }\n      \"#},\n      map! {\n        \"foo\" => \"EgL3uq_foo\",\n        \"foo-start\" => \"EgL3uq_foo-start\",\n        \"grid\" => \"EgL3uq_grid\",\n        \"bar\" => \"EgL3uq_bar\"\n      },\n      HashMap::new(),\n      Default::default(),\n      false,\n    );\n\n    css_modules_test(\n      r#\"\n        .grid {\n          grid-template-areas: \"foo\";\n        }\n\n        .foo {\n          grid-area: foo;\n        }\n\n        .bar {\n          grid-column-start: foo-start;\n        }\n      \"#,\n      indoc! {r#\"\n        .EgL3uq_grid {\n          grid-template-areas: \"foo\";\n        }\n\n        .EgL3uq_foo {\n          grid-area: foo;\n        }\n\n        .EgL3uq_bar {\n          grid-column-start: foo-start;\n        }\n      \"#},\n      map! {\n        \"foo\" => \"EgL3uq_foo\",\n        \"grid\" => \"EgL3uq_grid\",\n        \"bar\" => \"EgL3uq_bar\"\n      },\n      HashMap::new(),\n      crate::css_modules::Config {\n        grid: false,\n        ..Default::default()\n      },\n      false,\n    );\n\n    css_modules_test(\n      r#\"\n      test {\n        transition-property: opacity;\n      }\n    \"#,\n      indoc! {r#\"\n      test {\n        transition-property: opacity;\n      }\n    \"#},\n      map! {},\n      HashMap::new(),\n      Default::default(),\n      false,\n    );\n\n    css_modules_test(\n      r#\"\n      :global(.foo) {\n        color: red;\n      }\n\n      :local(.bar) {\n        color: yellow;\n      }\n\n      .bar :global(.baz) {\n        color: purple;\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        color: red;\n      }\n\n      .EgL3uq_bar {\n        color: #ff0;\n      }\n\n      .EgL3uq_bar .baz {\n        color: purple;\n      }\n    \"#},\n      map! {\n        \"bar\" => \"EgL3uq_bar\"\n      },\n      HashMap::new(),\n      Default::default(),\n      false,\n    );\n\n    // :global(:local(.hi)) {\n    //   color: green;\n    // }\n\n    css_modules_test(\n      r#\"\n      .test {\n        composes: foo;\n        background: white;\n      }\n\n      .foo {\n        color: red;\n      }\n    \"#,\n      indoc! {r#\"\n      .EgL3uq_test {\n        background: #fff;\n      }\n\n      .EgL3uq_foo {\n        color: red;\n      }\n    \"#},\n      map! {\n        \"test\" => \"EgL3uq_test\" \"EgL3uq_foo\",\n        \"foo\" => \"EgL3uq_foo\"\n      },\n      HashMap::new(),\n      Default::default(),\n      false,\n    );\n\n    css_modules_test(\n      r#\"\n      .a, .b {\n        composes: foo;\n        background: white;\n      }\n\n      .foo {\n        color: red;\n      }\n    \"#,\n      indoc! {r#\"\n      .EgL3uq_a, .EgL3uq_b {\n        background: #fff;\n      }\n\n      .EgL3uq_foo {\n        color: red;\n      }\n    \"#},\n      map! {\n        \"a\" => \"EgL3uq_a\" \"EgL3uq_foo\",\n        \"b\" => \"EgL3uq_b\" \"EgL3uq_foo\",\n        \"foo\" => \"EgL3uq_foo\"\n      },\n      HashMap::new(),\n      Default::default(),\n      false,\n    );\n\n    css_modules_test(\n      r#\"\n      .test {\n        composes: foo bar;\n        background: white;\n      }\n\n      .foo {\n        color: red;\n      }\n\n      .bar {\n        color: yellow;\n      }\n    \"#,\n      indoc! {r#\"\n      .EgL3uq_test {\n        background: #fff;\n      }\n\n      .EgL3uq_foo {\n        color: red;\n      }\n\n      .EgL3uq_bar {\n        color: #ff0;\n      }\n    \"#},\n      map! {\n        \"test\" => \"EgL3uq_test\" \"EgL3uq_foo\" \"EgL3uq_bar\",\n        \"foo\" => \"EgL3uq_foo\",\n        \"bar\" => \"EgL3uq_bar\"\n      },\n      HashMap::new(),\n      Default::default(),\n      false,\n    );\n\n    css_modules_test(\n      r#\"\n      .test {\n        composes: foo from global;\n        background: white;\n      }\n    \"#,\n      indoc! {r#\"\n      .EgL3uq_test {\n        background: #fff;\n      }\n    \"#},\n      map! {\n        \"test\" => \"EgL3uq_test\" \"foo\" global: true\n      },\n      HashMap::new(),\n      Default::default(),\n      false,\n    );\n\n    css_modules_test(\n      r#\"\n      .test {\n        composes: foo bar from global;\n        background: white;\n      }\n    \"#,\n      indoc! {r#\"\n      .EgL3uq_test {\n        background: #fff;\n      }\n    \"#},\n      map! {\n        \"test\" => \"EgL3uq_test\" \"foo\" global: true \"bar\" global: true\n      },\n      HashMap::new(),\n      Default::default(),\n      false,\n    );\n\n    css_modules_test(\n      r#\"\n      .test {\n        composes: foo from \"foo.css\";\n        background: white;\n      }\n    \"#,\n      indoc! {r#\"\n      .EgL3uq_test {\n        background: #fff;\n      }\n    \"#},\n      map! {\n        \"test\" => \"EgL3uq_test\" \"foo\" from \"foo.css\"\n      },\n      HashMap::new(),\n      Default::default(),\n      false,\n    );\n\n    css_modules_test(\n      r#\"\n      .test {\n        composes: foo bar from \"foo.css\";\n        background: white;\n      }\n    \"#,\n      indoc! {r#\"\n      .EgL3uq_test {\n        background: #fff;\n      }\n    \"#},\n      map! {\n        \"test\" => \"EgL3uq_test\" \"foo\" from \"foo.css\" \"bar\" from \"foo.css\"\n      },\n      HashMap::new(),\n      Default::default(),\n      false,\n    );\n\n    css_modules_test(\n      r#\"\n      .test {\n        composes: foo;\n        composes: foo from \"foo.css\";\n        composes: bar from \"bar.css\";\n        background: white;\n      }\n\n      .foo {\n        color: red;\n      }\n    \"#,\n      indoc! {r#\"\n      .EgL3uq_test {\n        background: #fff;\n      }\n\n      .EgL3uq_foo {\n        color: red;\n      }\n    \"#},\n      map! {\n        \"test\" => \"EgL3uq_test\" \"EgL3uq_foo\" \"foo\" from \"foo.css\" \"bar\" from \"bar.css\",\n        \"foo\" => \"EgL3uq_foo\"\n      },\n      HashMap::new(),\n      Default::default(),\n      false,\n    );\n\n    css_modules_test(\n      r#\"\n      .foo {\n        color: red;\n      }\n    \"#,\n      indoc! {r#\"\n      .test-EgL3uq-foo {\n        color: red;\n      }\n    \"#},\n      map! {\n        \"foo\" => \"test-EgL3uq-foo\"\n      },\n      HashMap::new(),\n      crate::css_modules::Config {\n        pattern: crate::css_modules::Pattern::parse(\"test-[hash]-[local]\").unwrap(),\n        ..Default::default()\n      },\n      false,\n    );\n\n    let stylesheet = StyleSheet::parse(\n      r#\"\n        .grid {\n          grid-template-areas: \"foo\";\n        }\n\n        .foo {\n          grid-area: foo;\n        }\n\n        .bar {\n          grid-column-start: foo-start;\n        }\n      \"#,\n      ParserOptions {\n        css_modules: Some(crate::css_modules::Config {\n          pattern: crate::css_modules::Pattern::parse(\"test-[local]-[hash]\").unwrap(),\n          ..Default::default()\n        }),\n        ..ParserOptions::default()\n      },\n    )\n    .unwrap();\n    if let Err(err) = stylesheet.to_css(PrinterOptions::default()) {\n      assert_eq!(err.kind, PrinterErrorKind::InvalidCssModulesPatternInGrid);\n    } else {\n      unreachable!()\n    }\n\n    css_modules_test(\n      r#\"\n      @property --foo {\n        syntax: '<color>';\n        inherits: false;\n        initial-value: yellow;\n      }\n\n      .foo {\n        --foo: red;\n        color: var(--foo);\n      }\n    \"#,\n      indoc! {r#\"\n      @property --foo {\n        syntax: \"<color>\";\n        inherits: false;\n        initial-value: #ff0;\n      }\n\n      .EgL3uq_foo {\n        --foo: red;\n        color: var(--foo);\n      }\n    \"#},\n      map! {\n        \"foo\" => \"EgL3uq_foo\"\n      },\n      HashMap::new(),\n      Default::default(),\n      false,\n    );\n\n    css_modules_test(\n      r#\"\n      @property --foo {\n        syntax: '<color>';\n        inherits: false;\n        initial-value: yellow;\n      }\n\n      @font-palette-values --Cooler {\n        font-family: Bixa;\n        base-palette: 1;\n        override-colors: 1 #7EB7E4;\n      }\n\n      .foo {\n        --foo: red;\n        --bar: green;\n        color: var(--foo);\n        font-palette: --Cooler;\n      }\n\n      .bar {\n        color: var(--color from \"./b.css\");\n      }\n    \"#,\n      indoc! {r#\"\n      @property --EgL3uq_foo {\n        syntax: \"<color>\";\n        inherits: false;\n        initial-value: #ff0;\n      }\n\n      @font-palette-values --EgL3uq_Cooler {\n        font-family: Bixa;\n        base-palette: 1;\n        override-colors: 1 #7eb7e4;\n      }\n\n      .EgL3uq_foo {\n        --EgL3uq_foo: red;\n        --EgL3uq_bar: green;\n        color: var(--EgL3uq_foo);\n        font-palette: --EgL3uq_Cooler;\n      }\n\n      .EgL3uq_bar {\n        color: var(--ma1CsG);\n      }\n    \"#},\n      map! {\n        \"foo\" => \"EgL3uq_foo\",\n        \"--foo\" => \"--EgL3uq_foo\" referenced: true,\n        \"--bar\" => \"--EgL3uq_bar\",\n        \"bar\" => \"EgL3uq_bar\",\n        \"--Cooler\" => \"--EgL3uq_Cooler\" referenced: true\n      },\n      HashMap::from([(\n        \"--ma1CsG\".into(),\n        CssModuleReference::Dependency {\n          name: \"--color\".into(),\n          specifier: \"./b.css\".into(),\n        },\n      )]),\n      crate::css_modules::Config {\n        dashed_idents: true,\n        ..Default::default()\n      },\n      false,\n    );\n\n    css_modules_test(\n      r#\"\n      .test {\n        animation: rotate var(--duration) linear infinite;\n      }\n    \"#,\n      indoc! {r#\"\n      .EgL3uq_test {\n        animation: EgL3uq_rotate var(--duration) linear infinite;\n      }\n    \"#},\n      map! {\n        \"test\" => \"EgL3uq_test\",\n        \"rotate\" => \"EgL3uq_rotate\" referenced: true\n      },\n      HashMap::new(),\n      Default::default(),\n      false,\n    );\n    css_modules_test(\n      r#\"\n      .test {\n        animation: none var(--duration);\n      }\n    \"#,\n      indoc! {r#\"\n      .EgL3uq_test {\n        animation: none var(--duration);\n      }\n    \"#},\n      map! {\n        \"test\" => \"EgL3uq_test\"\n      },\n      HashMap::new(),\n      Default::default(),\n      false,\n    );\n    css_modules_test(\n      r#\"\n      .test {\n        animation: var(--animation);\n      }\n    \"#,\n      indoc! {r#\"\n      .EgL3uq_test {\n        animation: var(--animation);\n      }\n    \"#},\n      map! {\n        \"test\" => \"EgL3uq_test\"\n      },\n      HashMap::new(),\n      Default::default(),\n      false,\n    );\n    css_modules_test(\n      r#\"\n      .test {\n        animation: rotate var(--duration);\n      }\n    \"#,\n      indoc! {r#\"\n      .EgL3uq_test {\n        animation: rotate var(--duration);\n      }\n    \"#},\n      map! {\n        \"test\" => \"EgL3uq_test\"\n      },\n      HashMap::new(),\n      crate::css_modules::Config {\n        animation: false,\n        ..Default::default()\n      },\n      false,\n    );\n    css_modules_test(\n      r#\"\n      .test {\n        animation: \"rotate\" var(--duration);\n      }\n    \"#,\n      indoc! {r#\"\n      .EgL3uq_test {\n        animation: EgL3uq_rotate var(--duration);\n      }\n    \"#},\n      map! {\n        \"test\" => \"EgL3uq_test\",\n        \"rotate\" => \"EgL3uq_rotate\" referenced: true\n      },\n      HashMap::new(),\n      crate::css_modules::Config { ..Default::default() },\n      false,\n    );\n\n    css_modules_test(\n      r#\"\n      .test {\n        composes: foo bar from \"foo.css\";\n        background: white;\n      }\n    \"#,\n      indoc! {r#\"\n      ._5h2kwG-test {\n        background: #fff;\n      }\n    \"#},\n      map! {\n        \"test\" => \"_5h2kwG-test\" \"foo\" from \"foo.css\" \"bar\" from \"foo.css\"\n      },\n      HashMap::new(),\n      crate::css_modules::Config {\n        pattern: crate::css_modules::Pattern::parse(\"[content-hash]-[local]\").unwrap(),\n        ..Default::default()\n      },\n      false,\n    );\n\n    css_modules_test(\n      r#\"\n      .box2 {\n        @container main (width >= 0) {\n          background-color: #90ee90;\n        }\n      }\n    \"#,\n      indoc! {r#\"\n      .EgL3uq_box2 {\n        @container EgL3uq_main (width >= 0) {\n          background-color: #90ee90;\n        }\n      }\n    \"#},\n      map! {\n        \"main\" => \"EgL3uq_main\",\n        \"box2\" => \"EgL3uq_box2\"\n      },\n      HashMap::new(),\n      crate::css_modules::Config { ..Default::default() },\n      false,\n    );\n\n    css_modules_test(\n      r#\"\n      .box2 {\n        @container main (width >= 0) {\n          background-color: #90ee90;\n        }\n      }\n    \"#,\n      indoc! {r#\"\n      .EgL3uq_box2 {\n        @container main (width >= 0) {\n          background-color: #90ee90;\n        }\n      }\n    \"#},\n      map! {\n        \"box2\" => \"EgL3uq_box2\"\n      },\n      HashMap::new(),\n      crate::css_modules::Config {\n        container: false,\n        ..Default::default()\n      },\n      false,\n    );\n\n    css_modules_test(\n      \".foo { view-transition-name: bar }\",\n      \".EgL3uq_foo{view-transition-name:EgL3uq_bar}\",\n      map! {\n        \"foo\" => \"EgL3uq_foo\",\n        \"bar\" => \"EgL3uq_bar\"\n      },\n      HashMap::new(),\n      Default::default(),\n      true,\n    );\n    css_modules_test(\n      \".foo { view-transition-name: none }\",\n      \".EgL3uq_foo{view-transition-name:none}\",\n      map! {\n        \"foo\" => \"EgL3uq_foo\"\n      },\n      HashMap::new(),\n      Default::default(),\n      true,\n    );\n    css_modules_test(\n      \".foo { view-transition-name: auto }\",\n      \".EgL3uq_foo{view-transition-name:auto}\",\n      map! {\n        \"foo\" => \"EgL3uq_foo\"\n      },\n      HashMap::new(),\n      Default::default(),\n      true,\n    );\n\n    css_modules_test(\n      \".foo { view-transition-class: bar baz qux }\",\n      \".EgL3uq_foo{view-transition-class:EgL3uq_bar EgL3uq_baz EgL3uq_qux}\",\n      map! {\n        \"foo\" => \"EgL3uq_foo\",\n        \"bar\" => \"EgL3uq_bar\",\n        \"baz\" => \"EgL3uq_baz\",\n        \"qux\" => \"EgL3uq_qux\"\n      },\n      HashMap::new(),\n      Default::default(),\n      true,\n    );\n\n    css_modules_test(\n      \".foo { view-transition-group: contain }\",\n      \".EgL3uq_foo{view-transition-group:contain}\",\n      map! {\n        \"foo\" => \"EgL3uq_foo\"\n      },\n      HashMap::new(),\n      Default::default(),\n      true,\n    );\n    css_modules_test(\n      \".foo { view-transition-group: bar }\",\n      \".EgL3uq_foo{view-transition-group:EgL3uq_bar}\",\n      map! {\n        \"foo\" => \"EgL3uq_foo\",\n        \"bar\" => \"EgL3uq_bar\"\n      },\n      HashMap::new(),\n      Default::default(),\n      true,\n    );\n\n    css_modules_test(\n      \"@view-transition { types: foo bar baz }\",\n      \"@view-transition{types:EgL3uq_foo EgL3uq_bar EgL3uq_baz}\",\n      map! {\n        \"foo\" => \"EgL3uq_foo\",\n        \"bar\" => \"EgL3uq_bar\",\n        \"baz\" => \"EgL3uq_baz\"\n      },\n      HashMap::new(),\n      Default::default(),\n      true,\n    );\n\n    css_modules_test(\n      \":root:active-view-transition-type(foo, bar) { color: red }\",\n      \":root:active-view-transition-type(EgL3uq_foo,EgL3uq_bar){color:red}\",\n      map! {\n        \"foo\" => \"EgL3uq_foo\",\n        \"bar\" => \"EgL3uq_bar\"\n      },\n      HashMap::new(),\n      Default::default(),\n      true,\n    );\n\n    for name in &[\n      \"view-transition-group\",\n      \"view-transition-image-pair\",\n      \"view-transition-new\",\n      \"view-transition-old\",\n    ] {\n      css_modules_test(\n        &format!(\":root::{}(foo) {{position: fixed}}\", name),\n        &format!(\":root::{}(EgL3uq_foo){{position:fixed}}\", name),\n        map! {\n          \"foo\" => \"EgL3uq_foo\"\n        },\n        HashMap::new(),\n        Default::default(),\n        true,\n      );\n      css_modules_test(\n        &format!(\":root::{}(.bar) {{position: fixed}}\", name),\n        &format!(\":root::{}(.EgL3uq_bar){{position:fixed}}\", name),\n        map! {\n          \"bar\" => \"EgL3uq_bar\"\n        },\n        HashMap::new(),\n        Default::default(),\n        true,\n      );\n      css_modules_test(\n        &format!(\":root::{}(foo.bar.baz) {{position: fixed}}\", name),\n        &format!(\":root::{}(EgL3uq_foo.EgL3uq_bar.EgL3uq_baz){{position:fixed}}\", name),\n        map! {\n          \"foo\" => \"EgL3uq_foo\",\n          \"bar\" => \"EgL3uq_bar\",\n          \"baz\" => \"EgL3uq_baz\"\n        },\n        HashMap::new(),\n        Default::default(),\n        true,\n      );\n\n      css_modules_test(\n        \":nth-child(1 of .foo) {width: 20px}\",\n        \":nth-child(1 of .EgL3uq_foo){width:20px}\",\n        map! {\n          \"foo\" => \"EgL3uq_foo\"\n        },\n        HashMap::new(),\n        Default::default(),\n        true,\n      );\n      css_modules_test(\n        \":nth-last-child(1 of .foo) {width: 20px}\",\n        \":nth-last-child(1 of .EgL3uq_foo){width:20px}\",\n        map! {\n          \"foo\" => \"EgL3uq_foo\"\n        },\n        HashMap::new(),\n        Default::default(),\n        true,\n      );\n    }\n\n    // Stable hashes between project roots.\n    fn test_project_root(project_root: &str, filename: &str, hash: &str) {\n      let stylesheet = StyleSheet::parse(\n        r#\"\n        .foo {\n          background: red;\n        }\n        \"#,\n        ParserOptions {\n          filename: filename.into(),\n          css_modules: Some(Default::default()),\n          ..ParserOptions::default()\n        },\n      )\n      .unwrap();\n      let res = stylesheet\n        .to_css(PrinterOptions {\n          project_root: Some(project_root),\n          ..PrinterOptions::default()\n        })\n        .unwrap();\n      assert_eq!(\n        res.code,\n        format!(\n          indoc! {r#\"\n      .{}_foo {{\n        background: red;\n      }}\n      \"#},\n          hash\n        )\n      );\n    }\n\n    test_project_root(\"/foo/bar\", \"/foo/bar/test.css\", \"EgL3uq\");\n    test_project_root(\"/foo\", \"/foo/test.css\", \"EgL3uq\");\n    test_project_root(\"/foo/bar\", \"/foo/bar/baz/test.css\", \"xLEkNW\");\n    test_project_root(\"/foo\", \"/foo/baz/test.css\", \"xLEkNW\");\n\n    let mut stylesheet = StyleSheet::parse(\n      r#\"\n      .foo {\n        color: red;\n        .bar {\n          color: green;\n        }\n        composes: test from \"foo.css\";\n      }\n      \"#,\n      ParserOptions {\n        filename: \"test.css\".into(),\n        css_modules: Some(Default::default()),\n        ..ParserOptions::default()\n      },\n    )\n    .unwrap();\n    stylesheet.minify(MinifyOptions::default()).unwrap();\n    let res = stylesheet\n      .to_css(PrinterOptions {\n        targets: Browsers {\n          chrome: Some(95 << 16),\n          ..Browsers::default()\n        }\n        .into(),\n        ..Default::default()\n      })\n      .unwrap();\n    assert_eq!(\n      res.code,\n      indoc! {r#\"\n    .EgL3uq_foo {\n      color: red;\n    }\n\n    .EgL3uq_foo .EgL3uq_bar {\n      color: green;\n    }\n\n\n    \"#}\n    );\n    assert_eq!(\n      res.exports.unwrap(),\n      map! {\n        \"foo\" => \"EgL3uq_foo\" \"test\" from \"foo.css\",\n        \"bar\" => \"EgL3uq_bar\"\n      }\n    );\n  }\n\n  #[test]\n  fn test_pseudo_replacement() {\n    let source = r#\"\n      .foo:hover {\n        color: red;\n      }\n\n      .foo:active {\n        color: yellow;\n      }\n\n      .foo:focus-visible {\n        color: purple;\n      }\n    \"#;\n\n    let expected = indoc! { r#\"\n      .foo.is-hovered {\n        color: red;\n      }\n\n      .foo.is-active {\n        color: #ff0;\n      }\n\n      .foo.focus-visible {\n        color: purple;\n      }\n    \"#};\n\n    let stylesheet = StyleSheet::parse(&source, ParserOptions::default()).unwrap();\n    let res = stylesheet\n      .to_css(PrinterOptions {\n        pseudo_classes: Some(PseudoClasses {\n          hover: Some(\"is-hovered\"),\n          active: Some(\"is-active\"),\n          focus_visible: Some(\"focus-visible\"),\n          ..PseudoClasses::default()\n        }),\n        ..PrinterOptions::default()\n      })\n      .unwrap();\n    assert_eq!(res.code, expected);\n\n    let source = r#\"\n      .foo:hover {\n        color: red;\n      }\n    \"#;\n\n    let expected = indoc! { r#\"\n      .EgL3uq_foo.EgL3uq_is-hovered {\n        color: red;\n      }\n    \"#};\n\n    let stylesheet = StyleSheet::parse(\n      &source,\n      ParserOptions {\n        filename: \"test.css\".into(),\n        css_modules: Some(Default::default()),\n        ..ParserOptions::default()\n      },\n    )\n    .unwrap();\n    let res = stylesheet\n      .to_css(PrinterOptions {\n        pseudo_classes: Some(PseudoClasses {\n          hover: Some(\"is-hovered\"),\n          ..PseudoClasses::default()\n        }),\n        ..PrinterOptions::default()\n      })\n      .unwrap();\n    assert_eq!(res.code, expected);\n  }\n\n  #[test]\n  fn test_unused_symbols() {\n    let source = r#\"\n      .foo {\n        color: red;\n      }\n\n      .bar {\n        color: green;\n      }\n\n      .bar:hover {\n        color: purple;\n      }\n\n      .bar .baz {\n        background: red;\n      }\n\n      .baz:is(.bar) {\n        background: green;\n      }\n\n      #id {\n        animation: 2s test;\n      }\n\n      #other_id {\n        color: red;\n      }\n\n      @keyframes test {\n        from { color: red }\n        to { color: yellow }\n      }\n\n      @counter-style circles {\n        symbols: Ⓐ Ⓑ Ⓒ;\n      }\n\n      @keyframes fade {\n        from { opacity: 0 }\n        to { opacity: 1 }\n      }\n    \"#;\n\n    let expected = indoc! {r#\"\n      .foo {\n        color: red;\n      }\n\n      #id {\n        animation: 2s test;\n      }\n\n      @keyframes test {\n        from {\n          color: red;\n        }\n\n        to {\n          color: #ff0;\n        }\n      }\n    \"#};\n\n    let mut stylesheet = StyleSheet::parse(&source, ParserOptions::default()).unwrap();\n    stylesheet\n      .minify(MinifyOptions {\n        unused_symbols: vec![\"bar\", \"other_id\", \"fade\", \"circles\"]\n          .iter()\n          .map(|s| String::from(*s))\n          .collect(),\n        ..MinifyOptions::default()\n      })\n      .unwrap();\n    let res = stylesheet.to_css(PrinterOptions::default()).unwrap();\n    assert_eq!(res.code, expected);\n\n    let source = r#\"\n      .foo {\n        color: red;\n\n        &.bar {\n          color: green;\n        }\n      }\n    \"#;\n\n    let expected = indoc! {r#\"\n      .foo {\n        color: red;\n      }\n    \"#};\n\n    let mut stylesheet = StyleSheet::parse(&source, ParserOptions::default()).unwrap();\n    stylesheet\n      .minify(MinifyOptions {\n        unused_symbols: vec![\"bar\"].iter().map(|s| String::from(*s)).collect(),\n        ..MinifyOptions::default()\n      })\n      .unwrap();\n    let res = stylesheet.to_css(PrinterOptions::default()).unwrap();\n    assert_eq!(res.code, expected);\n\n    let source = r#\"\n      .foo {\n        color: red;\n\n        &.bar {\n          color: purple;\n        }\n\n        @nest &.bar {\n          color: orange;\n        }\n\n        @nest :not(&) {\n          color: green;\n        }\n\n        @media (orientation: portrait) {\n          color: brown;\n        }\n      }\n\n      .x {\n        color: purple;\n\n        &.y {\n          color: green;\n        }\n      }\n    \"#;\n\n    let expected = indoc! {r#\"\n      :not(.foo) {\n        color: green;\n      }\n    \"#};\n\n    let mut stylesheet = StyleSheet::parse(&source, ParserOptions::default()).unwrap();\n    stylesheet\n      .minify(MinifyOptions {\n        unused_symbols: vec![\"foo\", \"x\"].iter().map(|s| String::from(*s)).collect(),\n        ..MinifyOptions::default()\n      })\n      .unwrap();\n    let res = stylesheet\n      .to_css(PrinterOptions {\n        targets: Browsers {\n          chrome: Some(95 << 16),\n          ..Browsers::default()\n        }\n        .into(),\n        ..PrinterOptions::default()\n      })\n      .unwrap();\n    assert_eq!(res.code, expected);\n\n    let source = r#\"\n      @property --EgL3uq_foo {\n        syntax: \"<color>\";\n        inherits: false;\n        initial-value: #ff0;\n      }\n\n      @font-palette-values --EgL3uq_Cooler {\n        font-family: Bixa;\n        base-palette: 1;\n        override-colors: 1 #7EB7E4;\n      }\n\n      .EgL3uq_foo {\n        --EgL3uq_foo: red;\n      }\n\n      .EgL3uq_bar {\n        color: green;\n      }\n    \"#;\n\n    let expected = indoc! {r#\"\n      .EgL3uq_bar {\n        color: green;\n      }\n    \"#};\n\n    let mut stylesheet = StyleSheet::parse(&source, ParserOptions::default()).unwrap();\n    stylesheet\n      .minify(MinifyOptions {\n        unused_symbols: vec![\"--EgL3uq_foo\", \"--EgL3uq_Cooler\"]\n          .iter()\n          .map(|s| String::from(*s))\n          .collect(),\n        ..MinifyOptions::default()\n      })\n      .unwrap();\n    let res = stylesheet.to_css(PrinterOptions::default()).unwrap();\n    assert_eq!(res.code, expected);\n  }\n\n  #[test]\n  fn test_svg() {\n    use crate::properties::svg;\n\n    minify_test(\".foo { fill: yellow; }\", \".foo{fill:#ff0}\");\n    minify_test(\".foo { fill: url(#foo); }\", \".foo{fill:url(#foo)}\");\n    minify_test(\".foo { fill: url(#foo) none; }\", \".foo{fill:url(#foo) none}\");\n    minify_test(\".foo { fill: url(#foo) yellow; }\", \".foo{fill:url(#foo) #ff0}\");\n    minify_test(\".foo { fill: none; }\", \".foo{fill:none}\");\n    minify_test(\".foo { fill: context-fill; }\", \".foo{fill:context-fill}\");\n    minify_test(\".foo { fill: context-stroke; }\", \".foo{fill:context-stroke}\");\n\n    minify_test(\".foo { stroke: yellow; }\", \".foo{stroke:#ff0}\");\n    minify_test(\".foo { stroke: url(#foo); }\", \".foo{stroke:url(#foo)}\");\n    minify_test(\".foo { stroke: url(#foo) none; }\", \".foo{stroke:url(#foo) none}\");\n    minify_test(\".foo { stroke: url(#foo) yellow; }\", \".foo{stroke:url(#foo) #ff0}\");\n    minify_test(\".foo { stroke: none; }\", \".foo{stroke:none}\");\n    minify_test(\".foo { stroke: context-fill; }\", \".foo{stroke:context-fill}\");\n    minify_test(\".foo { stroke: context-stroke; }\", \".foo{stroke:context-stroke}\");\n\n    minify_test(\".foo { marker-start: url(#foo); }\", \".foo{marker-start:url(#foo)}\");\n\n    minify_test(\".foo { stroke-dasharray: 4 1 2; }\", \".foo{stroke-dasharray:4 1 2}\");\n    minify_test(\".foo { stroke-dasharray: 4,1,2; }\", \".foo{stroke-dasharray:4 1 2}\");\n    minify_test(\".foo { stroke-dasharray: 4, 1, 2; }\", \".foo{stroke-dasharray:4 1 2}\");\n    minify_test(\n      \".foo { stroke-dasharray: 4px, 1px, 2px; }\",\n      \".foo{stroke-dasharray:4 1 2}\",\n    );\n\n    minify_test(\".foo { mask: url('foo.svg'); }\", \".foo{mask:url(foo.svg)}\");\n    minify_test(\n      \".foo { mask: url(masks.svg#star) luminance }\",\n      \".foo{mask:url(masks.svg#star) luminance}\",\n    );\n    minify_test(\n      \".foo { mask: url(masks.svg#star) 40px 20px }\",\n      \".foo{mask:url(masks.svg#star) 40px 20px}\",\n    );\n    minify_test(\n      \".foo { mask: url(masks.svg#star) 0 0 / 50px 50px }\",\n      \".foo{mask:url(masks.svg#star) 0 0/50px 50px}\",\n    );\n    minify_test(\n      \".foo { mask: url(masks.svg#star) repeat-x }\",\n      \".foo{mask:url(masks.svg#star) repeat-x}\",\n    );\n    minify_test(\n      \".foo { mask: url(masks.svg#star) stroke-box }\",\n      \".foo{mask:url(masks.svg#star) stroke-box}\",\n    );\n    minify_test(\n      \".foo { mask: url(masks.svg#star) stroke-box stroke-box }\",\n      \".foo{mask:url(masks.svg#star) stroke-box}\",\n    );\n    minify_test(\n      \".foo { mask: url(masks.svg#star) border-box }\",\n      \".foo{mask:url(masks.svg#star)}\",\n    );\n    minify_test(\n      \".foo { mask: url(masks.svg#star) left / 16px repeat-y, url(masks.svg#circle) right / 16px repeat-y }\",\n      \".foo{mask:url(masks.svg#star) 0/16px repeat-y,url(masks.svg#circle) 100%/16px repeat-y}\",\n    );\n\n    minify_test(\n      \".foo { mask-border: url('border-mask.png') 25; }\",\n      \".foo{mask-border:url(border-mask.png) 25}\",\n    );\n    minify_test(\n      \".foo { mask-border: url('border-mask.png') 25 / 35px / 12px space alpha; }\",\n      \".foo{mask-border:url(border-mask.png) 25/35px/12px space}\",\n    );\n    minify_test(\n      \".foo { mask-border: url('border-mask.png') 25 / 35px / 12px space luminance; }\",\n      \".foo{mask-border:url(border-mask.png) 25/35px/12px space luminance}\",\n    );\n    minify_test(\n      \".foo { mask-border: url('border-mask.png') luminance 25 / 35px / 12px space; }\",\n      \".foo{mask-border:url(border-mask.png) 25/35px/12px space luminance}\",\n    );\n\n    minify_test(\n      \".foo { clip-path: url('clip.svg#star'); }\",\n      \".foo{clip-path:url(clip.svg#star)}\",\n    );\n    minify_test(\".foo { clip-path: margin-box; }\", \".foo{clip-path:margin-box}\");\n    minify_test(\n      \".foo { clip-path: inset(100px 50px); }\",\n      \".foo{clip-path:inset(100px 50px)}\",\n    );\n    minify_test(\n      \".foo { clip-path: inset(100px 50px round 5px); }\",\n      \".foo{clip-path:inset(100px 50px round 5px)}\",\n    );\n    minify_test(\n      \".foo { clip-path: inset(100px 50px round 5px 5px 5px 5px); }\",\n      \".foo{clip-path:inset(100px 50px round 5px)}\",\n    );\n    minify_test(\".foo { clip-path: circle(50px); }\", \".foo{clip-path:circle(50px)}\");\n    minify_test(\n      \".foo { clip-path: circle(50px at center center); }\",\n      \".foo{clip-path:circle(50px)}\",\n    );\n    minify_test(\n      \".foo { clip-path: circle(50px at 50% 50%); }\",\n      \".foo{clip-path:circle(50px)}\",\n    );\n    minify_test(\n      \".foo { clip-path: circle(50px at 0 100px); }\",\n      \".foo{clip-path:circle(50px at 0 100px)}\",\n    );\n    minify_test(\n      \".foo { clip-path: circle(closest-side at 0 100px); }\",\n      \".foo{clip-path:circle(at 0 100px)}\",\n    );\n    minify_test(\n      \".foo { clip-path: circle(farthest-side at 0 100px); }\",\n      \".foo{clip-path:circle(farthest-side at 0 100px)}\",\n    );\n    minify_test(\n      \".foo { clip-path: circle(closest-side at 50% 50%); }\",\n      \".foo{clip-path:circle()}\",\n    );\n    minify_test(\n      \".foo { clip-path: ellipse(50px 60px at 0 10% 20%); }\",\n      \".foo{clip-path:ellipse(50px 60px at 0 10% 20%)}\",\n    );\n    minify_test(\n      \".foo { clip-path: ellipse(50px 60px at center center); }\",\n      \".foo{clip-path:ellipse(50px 60px)}\",\n    );\n    minify_test(\n      \".foo { clip-path: ellipse(closest-side closest-side at 50% 50%); }\",\n      \".foo{clip-path:ellipse()}\",\n    );\n    minify_test(\n      \".foo { clip-path: ellipse(closest-side closest-side at 10% 20%); }\",\n      \".foo{clip-path:ellipse(at 10% 20%)}\",\n    );\n    minify_test(\n      \".foo { clip-path: ellipse(farthest-side closest-side at 10% 20%); }\",\n      \".foo{clip-path:ellipse(farthest-side closest-side at 10% 20%)}\",\n    );\n    minify_test(\n      \".foo { clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%); }\",\n      \".foo{clip-path:polygon(50% 0%,100% 50%,50% 100%,0% 50%)}\",\n    );\n    minify_test(\n      \".foo { clip-path: polygon(nonzero, 50% 0%, 100% 50%, 50% 100%, 0% 50%); }\",\n      \".foo{clip-path:polygon(50% 0%,100% 50%,50% 100%,0% 50%)}\",\n    );\n    minify_test(\n      \".foo { clip-path: polygon(evenodd, 50% 0%, 100% 50%, 50% 100%, 0% 50%); }\",\n      \".foo{clip-path:polygon(evenodd,50% 0%,100% 50%,50% 100%,0% 50%)}\",\n    );\n    minify_test(\n      \".foo { clip-path: padding-box circle(50px at 0 100px); }\",\n      \".foo{clip-path:circle(50px at 0 100px) padding-box}\",\n    );\n    minify_test(\n      \".foo { clip-path: circle(50px at 0 100px) padding-box; }\",\n      \".foo{clip-path:circle(50px at 0 100px) padding-box}\",\n    );\n    minify_test(\n      \".foo { clip-path: circle(50px at 0 100px) border-box; }\",\n      \".foo{clip-path:circle(50px at 0 100px)}\",\n    );\n\n    prefix_test(\n      \".foo { clip-path: circle(50px); }\",\n      indoc! { r#\"\n        .foo {\n          -webkit-clip-path: circle(50px);\n          clip-path: circle(50px);\n        }\n      \"#},\n      Browsers {\n        chrome: Some(30 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { clip-path: circle(50px); }\",\n      indoc! { r#\"\n        .foo {\n          clip-path: circle(50px);\n        }\n      \"#},\n      Browsers {\n        chrome: Some(80 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { clip-path: circle(50px); }\",\n      indoc! { r#\"\n        .foo {\n          -webkit-clip-path: circle(50px);\n          clip-path: circle(50px);\n        }\n      \"#},\n      Browsers {\n        safari: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { clip-path: circle(50px); }\",\n      indoc! { r#\"\n        .foo {\n          clip-path: circle(50px);\n        }\n      \"#},\n      Browsers {\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { fill: lch(50.998% 135.363 338) }\",\n      indoc! { r#\"\n        .foo {\n          fill: #ee00be;\n          fill: color(display-p3 .972962 -.362078 .804206);\n          fill: lch(50.998% 135.363 338);\n        }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { stroke: lch(50.998% 135.363 338) }\",\n      indoc! { r#\"\n        .foo {\n          stroke: #ee00be;\n          stroke: color(display-p3 .972962 -.362078 .804206);\n          stroke: lch(50.998% 135.363 338);\n        }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { fill: url(#foo) lch(50.998% 135.363 338) }\",\n      indoc! { r##\"\n        .foo {\n          fill: url(\"#foo\") #ee00be;\n          fill: url(\"#foo\") color(display-p3 .972962 -.362078 .804206);\n          fill: url(\"#foo\") lch(50.998% 135.363 338);\n        }\n      \"##},\n      Browsers {\n        chrome: Some(90 << 16),\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { fill: var(--url) lch(50.998% 135.363 338) }\",\n      indoc! { r#\"\n        .foo {\n          fill: var(--url) #ee00be;\n        }\n\n        @supports (color: lab(0% 0 0)) {\n          .foo {\n            fill: var(--url) lab(50.998% 125.506 -50.7078);\n          }\n        }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n        @supports (color: lab(0% 0 0)) {\n          .foo {\n            fill: var(--url) lab(50.998% 125.506 -50.7078);\n          }\n        }\n      \"#,\n      indoc! { r#\"\n        @supports (color: lab(0% 0 0)) {\n          .foo {\n            fill: var(--url) lab(50.998% 125.506 -50.7078);\n          }\n        }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { mask-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) }\",\n      indoc! { r#\"\n        .foo {\n          -webkit-mask-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ff0f0e), to(#7773ff));\n          -webkit-mask-image: -webkit-linear-gradient(top, #ff0f0e, #7773ff);\n          -webkit-mask-image: linear-gradient(#ff0f0e, #7773ff);\n          mask-image: linear-gradient(#ff0f0e, #7773ff);\n          -webkit-mask-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364));\n          mask-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364));\n        }\n      \"#},\n      Browsers {\n        chrome: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { mask-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) }\",\n      indoc! { r#\"\n        .foo {\n          -webkit-mask-image: linear-gradient(#ff0f0e, #7773ff);\n          mask-image: linear-gradient(#ff0f0e, #7773ff);\n          -webkit-mask-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364));\n          mask-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364));\n        }\n      \"#},\n      Browsers {\n        chrome: Some(95 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { mask-image: linear-gradient(red, green) }\",\n      indoc! { r#\"\n        .foo {\n          -webkit-mask-image: linear-gradient(red, green);\n          mask-image: linear-gradient(red, green);\n        }\n      \"#},\n      Browsers {\n        chrome: Some(95 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { -webkit-mask-image: url(x.svg); mask-image: url(x.svg); }\",\n      indoc! { r#\"\n        .foo {\n          -webkit-mask-image: url(\"x.svg\");\n          mask-image: url(\"x.svg\");\n        }\n      \"#},\n      Browsers {\n        chrome: Some(95 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { mask: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) 40px 20px }\",\n      indoc! { r#\"\n        .foo {\n          -webkit-mask: -webkit-gradient(linear, 0 0, 0 100%, from(#ff0f0e), to(#7773ff)) 40px 20px;\n          -webkit-mask: -webkit-linear-gradient(top, #ff0f0e, #7773ff) 40px 20px;\n          -webkit-mask: linear-gradient(#ff0f0e, #7773ff) 40px 20px;\n          mask: linear-gradient(#ff0f0e, #7773ff) 40px 20px;\n          -webkit-mask: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) 40px 20px;\n          mask: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) 40px 20px;\n        }\n      \"#},\n      Browsers {\n        chrome: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { mask: -webkit-linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) 40px 20px }\",\n      indoc! { r#\"\n        .foo {\n          -webkit-mask: -webkit-gradient(linear, 0 0, 0 100%, from(#ff0f0e), to(#7773ff)) 40px 20px;\n          -webkit-mask: -webkit-linear-gradient(#ff0f0e, #7773ff) 40px 20px;\n        }\n      \"#},\n      Browsers {\n        chrome: Some(8 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { mask: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) 40px var(--foo) }\",\n      indoc! { r#\"\n        .foo {\n          -webkit-mask: linear-gradient(#ff0f0e, #7773ff) 40px var(--foo);\n          mask: linear-gradient(#ff0f0e, #7773ff) 40px var(--foo);\n        }\n\n        @supports (color: lab(0% 0 0)) {\n          .foo {\n            -webkit-mask: linear-gradient(lab(56.208% 94.4644 98.8928), lab(51% 70.4544 -115.586)) 40px var(--foo);\n            mask: linear-gradient(lab(56.208% 94.4644 98.8928), lab(51% 70.4544 -115.586)) 40px var(--foo);\n          }\n        }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n        @supports (color: lab(0% 0 0)) {\n          .foo {\n            mask: linear-gradient(lab(56.208% 94.4644 98.8928), lab(51% 70.4544 -115.586)) 40px var(--foo);\n          }\n        }\n      \"#,\n      indoc! { r#\"\n        @supports (color: lab(0% 0 0)) {\n          .foo {\n            -webkit-mask: linear-gradient(lab(56.208% 94.4644 98.8928), lab(51% 70.4544 -115.586)) 40px var(--foo);\n            mask: linear-gradient(lab(56.208% 94.4644 98.8928), lab(51% 70.4544 -115.586)) 40px var(--foo);\n          }\n        }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { mask: url(masks.svg#star) luminance }\",\n      indoc! { r#\"\n        .foo {\n          -webkit-mask: url(\"masks.svg#star\");\n          -webkit-mask-source-type: luminance;\n          mask: url(\"masks.svg#star\") luminance;\n        }\n    \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { mask-image: url(masks.svg#star) }\",\n      indoc! { r#\"\n        .foo {\n          -webkit-mask-image: url(\"masks.svg#star\");\n          mask-image: url(\"masks.svg#star\");\n        }\n    \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n        .foo {\n          mask-image: url(masks.svg#star);\n          mask-position: 25% 75%;\n          mask-size: cover;\n          mask-repeat: no-repeat;\n          mask-clip: padding-box;\n          mask-origin: content-box;\n          mask-composite: subtract;\n          mask-mode: luminance;\n        }\n      \"#,\n      indoc! { r#\"\n        .foo {\n          -webkit-mask: url(\"masks.svg#star\") 25% 75% / cover no-repeat content-box padding-box;\n          -webkit-mask-composite: source-out;\n          -webkit-mask-source-type: luminance;\n          mask: url(\"masks.svg#star\") 25% 75% / cover no-repeat content-box padding-box subtract luminance;\n        }\n    \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n        .foo {\n          mask-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364));\n          mask-position: 25% 75%;\n          mask-size: cover;\n          mask-repeat: no-repeat;\n          mask-clip: padding-box;\n          mask-origin: content-box;\n          mask-composite: subtract;\n          mask-mode: luminance;\n        }\n      \"#,\n      indoc! { r#\"\n        .foo {\n          -webkit-mask: linear-gradient(#ff0f0e, #7773ff) 25% 75% / cover no-repeat content-box padding-box;\n          -webkit-mask-composite: source-out;\n          -webkit-mask-source-type: luminance;\n          mask: linear-gradient(#ff0f0e, #7773ff) 25% 75% / cover no-repeat content-box padding-box subtract luminance;\n          -webkit-mask: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) 25% 75% / cover no-repeat content-box padding-box;\n          -webkit-mask-composite: source-out;\n          -webkit-mask-source-type: luminance;\n          mask: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) 25% 75% / cover no-repeat content-box padding-box subtract luminance;\n        }\n    \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    test(\n      r#\"\n        .foo {\n          mask: none center / 100% no-repeat;\n          mask-image: var(--svg);\n        }\n      \"#,\n      indoc! { r#\"\n        .foo {\n          mask: none center / 100% no-repeat;\n          mask-image: var(--svg);\n        }\n      \"#},\n    );\n\n    prefix_test(\n      r#\"\n        .foo {\n          mask-composite: subtract;\n        }\n      \"#,\n      indoc! { r#\"\n        .foo {\n          -webkit-mask-composite: source-out;\n          mask-composite: subtract;\n        }\n    \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n        .foo {\n          mask-mode: luminance;\n        }\n      \"#,\n      indoc! { r#\"\n        .foo {\n          -webkit-mask-source-type: luminance;\n          mask-mode: luminance;\n        }\n    \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n        .foo {\n          mask-border: url('border-mask.png') 25 / 35px / 12px space luminance;\n        }\n      \"#,\n      indoc! { r#\"\n        .foo {\n          -webkit-mask-box-image: url(\"border-mask.png\") 25 / 35px / 12px space;\n          mask-border: url(\"border-mask.png\") 25 / 35px / 12px space luminance;\n        }\n    \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n        .foo {\n          mask-border: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) 25 / 35px / 12px space luminance;\n        }\n      \"#,\n      indoc! { r#\"\n        .foo {\n          -webkit-mask-box-image: linear-gradient(#ff0f0e, #7773ff) 25 / 35px / 12px space;\n          mask-border: linear-gradient(#ff0f0e, #7773ff) 25 / 35px / 12px space luminance;\n          -webkit-mask-box-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) 25 / 35px / 12px space;\n          mask-border: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) 25 / 35px / 12px space luminance;\n        }\n    \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n        .foo {\n          mask-border-source: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364));\n        }\n      \"#,\n      indoc! { r#\"\n        .foo {\n          -webkit-mask-box-image-source: linear-gradient(#ff0f0e, #7773ff);\n          mask-border-source: linear-gradient(#ff0f0e, #7773ff);\n          -webkit-mask-box-image-source: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364));\n          mask-border-source: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364));\n        }\n    \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n        .foo {\n          mask-border-source: url(foo.png);\n          mask-border-slice: 10 40 10 40;\n          mask-border-width: 10px;\n          mask-border-outset: 0;\n          mask-border-repeat: round round;\n          mask-border-mode: luminance;\n        }\n      \"#,\n      indoc! { r#\"\n        .foo {\n          -webkit-mask-box-image: url(\"foo.png\") 10 40 / 10px round;\n          mask-border: url(\"foo.png\") 10 40 / 10px round luminance;\n        }\n    \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n        .foo {\n          -webkit-mask-box-image-source: url(foo.png);\n          -webkit-mask-box-image-slice: 10 40 10 40;\n          -webkit-mask-box-image-width: 10px;\n          -webkit-mask-box-image-outset: 0;\n          -webkit-mask-box-image-repeat: round round;\n        }\n      \"#,\n      indoc! { r#\"\n        .foo {\n          -webkit-mask-box-image: url(\"foo.png\") 10 40 / 10px round;\n        }\n    \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n        .foo {\n          mask-border-slice: 10 40 10 40;\n        }\n      \"#,\n      indoc! { r#\"\n        .foo {\n          -webkit-mask-box-image-slice: 10 40;\n          mask-border-slice: 10 40;\n        }\n    \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n        .foo {\n          mask-border-slice: var(--foo);\n        }\n      \"#,\n      indoc! { r#\"\n        .foo {\n          -webkit-mask-box-image-slice: var(--foo);\n          mask-border-slice: var(--foo);\n        }\n    \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n        .foo {\n          mask-border: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) var(--foo);\n        }\n      \"#,\n      indoc! { r#\"\n        .foo {\n          -webkit-mask-box-image: linear-gradient(#ff0f0e, #7773ff) var(--foo);\n          mask-border: linear-gradient(#ff0f0e, #7773ff) var(--foo);\n        }\n\n        @supports (color: lab(0% 0 0)) {\n          .foo {\n            -webkit-mask-box-image: linear-gradient(lab(56.208% 94.4644 98.8928), lab(51% 70.4544 -115.586)) var(--foo);\n            mask-border: linear-gradient(lab(56.208% 94.4644 98.8928), lab(51% 70.4544 -115.586)) var(--foo);\n          }\n        }\n    \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n        .foo {\n          transition: mask 200ms;\n        }\n      \"#,\n      indoc! { r#\"\n        .foo {\n          transition: -webkit-mask .2s, mask .2s;\n        }\n    \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n        .foo {\n          transition: mask-border 200ms;\n        }\n      \"#,\n      indoc! { r#\"\n        .foo {\n          transition: -webkit-mask-box-image .2s, mask-border .2s;\n        }\n    \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n        .foo {\n          transition-property: mask;\n        }\n      \"#,\n      indoc! { r#\"\n        .foo {\n          transition-property: -webkit-mask, mask;\n        }\n    \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n        .foo {\n          transition-property: mask-border;\n        }\n      \"#,\n      indoc! { r#\"\n        .foo {\n          transition-property: -webkit-mask-box-image, mask-border;\n        }\n    \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n        .foo {\n          transition-property: mask-composite, mask-mode;\n        }\n      \"#,\n      indoc! { r#\"\n        .foo {\n          transition-property: -webkit-mask-composite, mask-composite, -webkit-mask-source-type, mask-mode;\n        }\n    \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    let property =\n      Property::parse_string(\"text-rendering\".into(), \"geometricPrecision\", ParserOptions::default()).unwrap();\n    assert_eq!(\n      property,\n      Property::TextRendering(svg::TextRendering::GeometricPrecision)\n    );\n    let property =\n      Property::parse_string(\"shape-rendering\".into(), \"geometricPrecision\", ParserOptions::default()).unwrap();\n    assert_eq!(\n      property,\n      Property::ShapeRendering(svg::ShapeRendering::GeometricPrecision)\n    );\n    let property = Property::parse_string(\"color-interpolation\".into(), \"sRGB\", ParserOptions::default()).unwrap();\n    assert_eq!(property, Property::ColorInterpolation(svg::ColorInterpolation::SRGB));\n  }\n\n  #[test]\n  fn test_filter() {\n    minify_test(\n      \".foo { filter: url('filters.svg#filter-id'); }\",\n      \".foo{filter:url(filters.svg#filter-id)}\",\n    );\n    minify_test(\".foo { filter: blur(5px); }\", \".foo{filter:blur(5px)}\");\n    minify_test(\".foo { filter: blur(0px); }\", \".foo{filter:blur()}\");\n    minify_test(\".foo { filter: brightness(10%); }\", \".foo{filter:brightness(10%)}\");\n    minify_test(\".foo { filter: brightness(100%); }\", \".foo{filter:brightness()}\");\n    minify_test(\n      \".foo { filter: drop-shadow(16px 16px 20px yellow); }\",\n      \".foo{filter:drop-shadow(16px 16px 20px #ff0)}\",\n    );\n    minify_test(\n      \".foo { filter: contrast(175%) brightness(3%); }\",\n      \".foo{filter:contrast(175%)brightness(3%)}\",\n    );\n    minify_test(\".foo { filter: hue-rotate(0) }\", \".foo{filter:hue-rotate()}\");\n\n    prefix_test(\n      \".foo { filter: blur(5px) }\",\n      indoc! { r#\"\n        .foo {\n          -webkit-filter: blur(5px);\n          filter: blur(5px);\n        }\n      \"#},\n      Browsers {\n        chrome: Some(20 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { filter: blur(5px) }\",\n      indoc! { r#\"\n        .foo {\n          filter: blur(5px);\n        }\n      \"#},\n      Browsers {\n        chrome: Some(80 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { backdrop-filter: blur(5px) }\",\n      indoc! { r#\"\n        .foo {\n          backdrop-filter: blur(5px);\n        }\n      \"#},\n      Browsers {\n        chrome: Some(80 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { backdrop-filter: blur(5px) }\",\n      indoc! { r#\"\n        .foo {\n          -webkit-backdrop-filter: blur(5px);\n          backdrop-filter: blur(5px);\n        }\n      \"#},\n      Browsers {\n        safari: Some(15 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        -webkit-backdrop-filter: blur(8px);\n        backdrop-filter: blur(8px);\n      }\n      \"#,\n      indoc! {r#\"\n      .foo {\n        -webkit-backdrop-filter: blur(8px);\n        backdrop-filter: blur(8px);\n      }\n      \"#},\n      Browsers {\n        safari: Some(16 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { filter: var(--foo) }\",\n      indoc! { r#\"\n        .foo {\n          -webkit-filter: var(--foo);\n          filter: var(--foo);\n        }\n      \"#},\n      Browsers {\n        chrome: Some(20 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { filter: drop-shadow(16px 16px 20px lab(40% 56.6 39)) }\",\n      indoc! { r#\"\n        .foo {\n          -webkit-filter: drop-shadow(16px 16px 20px #b32323);\n          filter: drop-shadow(16px 16px 20px #b32323);\n          filter: drop-shadow(16px 16px 20px lab(40% 56.6 39));\n        }\n      \"#},\n      Browsers {\n        chrome: Some(20 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { filter: contrast(175%) drop-shadow(16px 16px 20px lab(40% 56.6 39)) }\",\n      indoc! { r#\"\n        .foo {\n          filter: contrast(175%) drop-shadow(16px 16px 20px #b32323);\n          filter: contrast(175%) drop-shadow(16px 16px 20px lab(40% 56.6 39));\n        }\n      \"#},\n      Browsers {\n        chrome: Some(4 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { filter: drop-shadow(16px 16px 20px lab(40% 56.6 39)) drop-shadow(16px 16px 20px yellow) }\",\n      indoc! { r#\"\n        .foo {\n          filter: drop-shadow(16px 16px 20px #b32323) drop-shadow(16px 16px 20px #ff0);\n          filter: drop-shadow(16px 16px 20px lab(40% 56.6 39)) drop-shadow(16px 16px 20px #ff0);\n        }\n      \"#},\n      Browsers {\n        chrome: Some(4 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { filter: var(--foo) drop-shadow(16px 16px 20px lab(40% 56.6 39)) }\",\n      indoc! { r#\"\n        .foo {\n          filter: var(--foo) drop-shadow(16px 16px 20px #b32323);\n        }\n\n        @supports (color: lab(0% 0 0)) {\n          .foo {\n            filter: var(--foo) drop-shadow(16px 16px 20px lab(40% 56.6 39));\n          }\n        }\n      \"#},\n      Browsers {\n        chrome: Some(4 << 16),\n        ..Browsers::default()\n      },\n    );\n  }\n\n  #[test]\n  fn test_mix_blend_mode() {\n    minify_test(\n      \".foo { mix-blend-mode: normal }\",\n      \".foo{mix-blend-mode:normal}\",\n    );\n    minify_test(\n      \".foo { mix-blend-mode: multiply }\",\n      \".foo{mix-blend-mode:multiply}\",\n    );\n    minify_test(\n      \".foo { mix-blend-mode: screen }\",\n      \".foo{mix-blend-mode:screen}\",\n    );\n    minify_test(\n      \".foo { mix-blend-mode: overlay }\",\n      \".foo{mix-blend-mode:overlay}\",\n    );\n    minify_test(\n      \".foo { mix-blend-mode: darken }\",\n      \".foo{mix-blend-mode:darken}\",\n    );\n    minify_test(\n      \".foo { mix-blend-mode: lighten }\",\n      \".foo{mix-blend-mode:lighten}\",\n    );\n    minify_test(\n      \".foo { mix-blend-mode: color-dodge }\",\n      \".foo{mix-blend-mode:color-dodge}\",\n    );\n    minify_test(\n      \".foo { mix-blend-mode: color-burn }\",\n      \".foo{mix-blend-mode:color-burn}\",\n    );\n    minify_test(\n      \".foo { mix-blend-mode: hard-light }\",\n      \".foo{mix-blend-mode:hard-light}\",\n    );\n    minify_test(\n      \".foo { mix-blend-mode: soft-light }\",\n      \".foo{mix-blend-mode:soft-light}\",\n    );\n    minify_test(\n      \".foo { mix-blend-mode: difference }\",\n      \".foo{mix-blend-mode:difference}\",\n    );\n    minify_test(\n      \".foo { mix-blend-mode: exclusion }\",\n      \".foo{mix-blend-mode:exclusion}\",\n    );\n    minify_test(\n      \".foo { mix-blend-mode: hue }\",\n      \".foo{mix-blend-mode:hue}\",\n    );\n    minify_test(\n      \".foo { mix-blend-mode: saturation }\",\n      \".foo{mix-blend-mode:saturation}\",\n    );\n    minify_test(\n      \".foo { mix-blend-mode: color }\",\n      \".foo{mix-blend-mode:color}\",\n    );\n    minify_test(\n      \".foo { mix-blend-mode: luminosity }\",\n      \".foo{mix-blend-mode:luminosity}\",\n    );\n    minify_test(\n      \".foo { mix-blend-mode: plus-darker }\",\n      \".foo{mix-blend-mode:plus-darker}\",\n    );\n    minify_test(\n      \".foo { mix-blend-mode: plus-lighter }\",\n      \".foo{mix-blend-mode:plus-lighter}\",\n    );\n  }\n\n  #[test]\n  fn test_viewport() {\n    minify_test(\n      r#\"\n    @viewport {\n      width: 100vw;\n    }\"#,\n      \"@viewport{width:100vw}\",\n    );\n    minify_test(\n      r#\"\n    @-ms-viewport {\n      width: device-width;\n    }\"#,\n      \"@-ms-viewport{width:device-width}\",\n    );\n  }\n\n  #[test]\n  fn test_at_scope() {\n    minify_test(\n      r#\"\n      @scope {\n        .foo {\n          display: flex;\n        }\n      }\n      \"#,\n      \"@scope{.foo{display:flex}}\",\n    );\n    minify_test(\n      r#\"\n      @scope {\n        :scope {\n          display: flex;\n          color: lightblue;\n        }\n      }\"#,\n      \"@scope{:scope{color:#add8e6;display:flex}}\",\n    );\n    minify_test(\n      r#\"\n      @scope (.light-scheme) {\n        a { color: yellow; }\n      }\n      \"#,\n      \"@scope(.light-scheme){a{color:#ff0}}\",\n    );\n    minify_test(\n      r#\"\n      @scope (.media-object) to (.content > *) {\n        a { color: yellow; }\n      }\n      \"#,\n      \"@scope(.media-object) to (.content>*){a{color:#ff0}}\",\n    );\n    minify_test(\n      r#\"\n      @scope to (.content > *) {\n        a { color: yellow; }\n      }\n      \"#,\n      \"@scope to (.content>*){a{color:#ff0}}\",\n    );\n    minify_test(\n      r#\"\n      @scope (#my-component) {\n        & { color: yellow; }\n      }\n      \"#,\n      \"@scope(#my-component){&{color:#ff0}}\",\n    );\n    minify_test(\n      r#\"\n      @scope (.parent-scope) {\n        @scope (:scope > .child-scope) to (:scope .limit) {\n          .content { color: yellow; }\n        }\n      }\n      \"#,\n      \"@scope(.parent-scope){@scope(:scope>.child-scope) to (:scope .limit){.content{color:#ff0}}}\",\n    );\n    minify_test(\n      r#\"\n      .foo {\n        @scope (.bar) {\n          color: yellow;\n        }\n      }\n      \"#,\n      \".foo{@scope(.bar){color:#ff0}}\",\n    );\n    nesting_test(\n      r#\"\n      .foo {\n        @scope (.bar) {\n          color: yellow;\n        }\n      }\n      \"#,\n      indoc! {r#\"\n        @scope (.bar) {\n          color: #ff0;\n        }\n      \"#},\n    );\n    nesting_test(\n      r#\"\n      .parent {\n        color: blue;\n\n        @scope (& > .scope) to (& .limit) {\n          & .content {\n            color: yellow;\n          }\n        }\n      }\n      \"#,\n      indoc! {r#\"\n        .parent {\n          color: #00f;\n        }\n\n        @scope (.parent > .scope) to (.parent > .scope .limit) {\n          :scope .content {\n            color: #ff0;\n          }\n        }\n      \"#},\n    );\n  }\n\n  #[test]\n  fn test_custom_media() {\n    custom_media_test(\n      r#\"\n      @custom-media --modern (color), (hover);\n\n      @media (--modern) and (width > 1024px) {\n        .a {\n          color: green;\n        }\n      }\n      \"#,\n      indoc! {r#\"\n      @media ((color) or (hover)) and (width > 1024px) {\n        .a {\n          color: green;\n        }\n      }\n      \"#},\n    );\n\n    custom_media_test(\n      r#\"\n      @custom-media --color (color);\n\n      @media (--color) and (width > 1024px) {\n        .a {\n          color: green;\n        }\n      }\n      \"#,\n      indoc! {r#\"\n      @media (color) and (width > 1024px) {\n        .a {\n          color: green;\n        }\n      }\n      \"#},\n    );\n\n    custom_media_test(\n      r#\"\n      @custom-media --a (color);\n      @custom-media --b (--a);\n\n      @media (--b) and (width > 1024px) {\n        .a {\n          color: green;\n        }\n      }\n      \"#,\n      indoc! {r#\"\n      @media (color) and (width > 1024px) {\n        .a {\n          color: green;\n        }\n      }\n      \"#},\n    );\n\n    custom_media_test(\n      r#\"\n      @custom-media --not-color not (color);\n\n      @media not (--not-color) {\n        .a {\n          color: green;\n        }\n      }\n      \"#,\n      indoc! {r#\"\n      @media (color) {\n        .a {\n          color: green;\n        }\n      }\n      \"#},\n    );\n\n    custom_media_test(\n      r#\"\n      @custom-media --color-print print and (color);\n\n      @media (--color-print) {\n        .a {\n          color: green;\n        }\n      }\n      \"#,\n      indoc! {r#\"\n      @media print and (color) {\n        .a {\n          color: green;\n        }\n      }\n      \"#},\n    );\n\n    custom_media_test(\n      r#\"\n      @custom-media --color-print print and (color);\n\n      @media print and (--color-print) {\n        .a {\n          color: green;\n        }\n      }\n      \"#,\n      indoc! {r#\"\n      @media print and (color) {\n        .a {\n          color: green;\n        }\n      }\n      \"#},\n    );\n\n    custom_media_test(\n      r#\"\n      @custom-media --not-color-print not print and (color);\n\n      @media not print and (--not-color-print) {\n        .a {\n          color: green;\n        }\n      }\n      \"#,\n      indoc! {r#\"\n      @media not print and (color) {\n        .a {\n          color: green;\n        }\n      }\n      \"#},\n    );\n\n    custom_media_test(\n      r#\"\n      @custom-media --print print;\n\n      @media (--print) {\n        .a {\n          color: green;\n        }\n      }\n      \"#,\n      indoc! {r#\"\n      @media print {\n        .a {\n          color: green;\n        }\n      }\n      \"#},\n    );\n\n    custom_media_test(\n      r#\"\n      @custom-media --print print;\n\n      @media not (--print) {\n        .a {\n          color: green;\n        }\n      }\n      \"#,\n      indoc! {r#\"\n      @media not print {\n        .a {\n          color: green;\n        }\n      }\n      \"#},\n    );\n\n    custom_media_test(\n      r#\"\n      @custom-media --print not print;\n\n      @media not (--print) {\n        .a {\n          color: green;\n        }\n      }\n      \"#,\n      indoc! {r#\"\n      @media print {\n        .a {\n          color: green;\n        }\n      }\n      \"#},\n    );\n\n    custom_media_test(\n      r#\"\n      @custom-media --print print;\n\n      @media ((--print)) {\n        .a {\n          color: green;\n        }\n      }\n      \"#,\n      indoc! {r#\"\n      @media print {\n        .a {\n          color: green;\n        }\n      }\n      \"#},\n    );\n\n    custom_media_test(\n      r#\"\n      @custom-media --color (color);\n      @custom-media --print print;\n\n      @media (--print) and (--color) {\n        .a {\n          color: green;\n        }\n      }\n      \"#,\n      indoc! {r#\"\n      @media print and (color) {\n        .a {\n          color: green;\n        }\n      }\n      \"#},\n    );\n\n    custom_media_test(\n      r#\"\n      @custom-media --color (color);\n      @custom-media --not-print not print;\n\n      @media (--not-print) and (--color) {\n        .a {\n          color: green;\n        }\n      }\n      \"#,\n      indoc! {r#\"\n      @media not print and (color) {\n        .a {\n          color: green;\n        }\n      }\n      \"#},\n    );\n\n    custom_media_test(\n      r#\"\n      @custom-media --color (color);\n      @custom-media --screen screen;\n      @custom-media --print print;\n\n      @media (--print) and (--color), (--screen) and (--color) {\n        .a {\n          color: green;\n        }\n      }\n      \"#,\n      indoc! {r#\"\n      @media print and (color), screen and (color) {\n        .a {\n          color: green;\n        }\n      }\n      \"#},\n    );\n\n    custom_media_test(\n      r#\"\n      @custom-media --color print and (color), print and (script);\n\n      @media (--color) {\n        .a {\n          color: green;\n        }\n      }\n      \"#,\n      indoc! {r#\"\n      @media print and ((color) or (script)) {\n        .a {\n          color: green;\n        }\n      }\n      \"#},\n    );\n\n    custom_media_test(\n      r#\"\n      @custom-media --color (color);\n      @custom-media --not-color not all and (--color);\n\n      @media (--not-color) {\n        .a {\n          color: green;\n        }\n      }\n      \"#,\n      indoc! {r#\"\n        @media not all and (color) {\n          .a {\n            color: green;\n          }\n        }\n      \"#},\n    );\n\n    custom_media_test(\n      r#\"\n      @custom-media --color (color);\n\n      @media not all and (--color) {\n        .a {\n          color: green;\n        }\n      }\n      \"#,\n      indoc! {r#\"\n        @media not all and (color) {\n          .a {\n            color: green;\n          }\n        }\n      \"#},\n    );\n\n    custom_media_test(\n      r#\"\n      @media (--print) {\n        .a {\n          color: green;\n        }\n      }\n\n      @custom-media --print print;\n      \"#,\n      indoc! {r#\"\n      @media print {\n        .a {\n          color: green;\n        }\n      }\n      \"#},\n    );\n\n    custom_media_test(\n      r#\"\n      @custom-media --not-width not (min-width: 300px);\n      @media screen and ((prefers-color-scheme: dark) or (--not-width)) {\n        .foo {\n          order: 6;\n        }\n      }\n      \"#,\n      indoc! {r#\"\n      @media screen and ((prefers-color-scheme: dark) or ((width < 300px))) {\n        .foo {\n          order: 6;\n        }\n      }\n      \"#},\n    );\n\n    fn custom_media_error_test(source: &str, err: Error<MinifyErrorKind>) {\n      let mut stylesheet = StyleSheet::parse(\n        &source,\n        ParserOptions {\n          filename: \"test.css\".into(),\n          flags: ParserFlags::CUSTOM_MEDIA,\n          ..ParserOptions::default()\n        },\n      )\n      .unwrap();\n      let res = stylesheet.minify(MinifyOptions {\n        targets: Browsers {\n          chrome: Some(95 << 16),\n          ..Browsers::default()\n        }\n        .into(),\n        ..MinifyOptions::default()\n      });\n      assert_eq!(res, Err(err))\n    }\n\n    custom_media_error_test(\n      r#\"\n      @custom-media --color-print print and (color);\n\n      @media screen and (--color-print) {\n        .a {\n          color: green;\n        }\n      }\n      \"#,\n      Error {\n        kind: MinifyErrorKind::UnsupportedCustomMediaBooleanLogic {\n          custom_media_loc: Location {\n            source_index: 0,\n            line: 1,\n            column: 7,\n          },\n        },\n        loc: Some(ErrorLocation {\n          filename: \"test.css\".into(),\n          line: 3,\n          column: 7,\n        }),\n      },\n    );\n\n    custom_media_error_test(\n      r#\"\n      @custom-media --color-print print and (color);\n\n      @media not print and (--color-print) {\n        .a {\n          color: green;\n        }\n      }\n      \"#,\n      Error {\n        kind: MinifyErrorKind::UnsupportedCustomMediaBooleanLogic {\n          custom_media_loc: Location {\n            source_index: 0,\n            line: 1,\n            column: 7,\n          },\n        },\n        loc: Some(ErrorLocation {\n          filename: \"test.css\".into(),\n          line: 3,\n          column: 7,\n        }),\n      },\n    );\n\n    custom_media_error_test(\n      r#\"\n      @custom-media --color-print print and (color);\n      @custom-media --color-screen screen and (color);\n\n      @media (--color-print) or (--color-screen) {}\n      \"#,\n      Error {\n        kind: MinifyErrorKind::UnsupportedCustomMediaBooleanLogic {\n          custom_media_loc: Location {\n            source_index: 0,\n            line: 2,\n            column: 7,\n          },\n        },\n        loc: Some(ErrorLocation {\n          filename: \"test.css\".into(),\n          line: 4,\n          column: 7,\n        }),\n      },\n    );\n\n    custom_media_error_test(\n      r#\"\n      @custom-media --color-print print and (color);\n      @custom-media --color-screen screen and (color);\n\n      @media (--color-print) and (--color-screen) {}\n      \"#,\n      Error {\n        kind: MinifyErrorKind::UnsupportedCustomMediaBooleanLogic {\n          custom_media_loc: Location {\n            source_index: 0,\n            line: 2,\n            column: 7,\n          },\n        },\n        loc: Some(ErrorLocation {\n          filename: \"test.css\".into(),\n          line: 4,\n          column: 7,\n        }),\n      },\n    );\n\n    custom_media_error_test(\n      r#\"\n      @custom-media --screen screen;\n      @custom-media --print print;\n\n      @media (--print) and (--screen) {}\n      \"#,\n      Error {\n        kind: MinifyErrorKind::UnsupportedCustomMediaBooleanLogic {\n          custom_media_loc: Location {\n            source_index: 0,\n            line: 1,\n            column: 7,\n          },\n        },\n        loc: Some(ErrorLocation {\n          filename: \"test.css\".into(),\n          line: 4,\n          column: 7,\n        }),\n      },\n    );\n\n    custom_media_error_test(\n      r#\"\n      @custom-media --not-print not print and (color);\n      @custom-media --not-screen not screen and (color);\n\n      @media ((script) or ((--not-print) and (--not-screen))) {\n        .a {\n          color: green;\n        }\n      }\n      \"#,\n      Error {\n        kind: MinifyErrorKind::UnsupportedCustomMediaBooleanLogic {\n          custom_media_loc: Location {\n            source_index: 0,\n            line: 2,\n            column: 7,\n          },\n        },\n        loc: Some(ErrorLocation {\n          filename: \"test.css\".into(),\n          line: 4,\n          column: 7,\n        }),\n      },\n    );\n\n    custom_media_error_test(\n      r#\"\n      @custom-media --color screen and (color), print and (color);\n\n      @media (--color) {\n        .a {\n          color: green;\n        }\n      }\n      \"#,\n      Error {\n        kind: MinifyErrorKind::UnsupportedCustomMediaBooleanLogic {\n          custom_media_loc: Location {\n            source_index: 0,\n            line: 1,\n            column: 7,\n          },\n        },\n        loc: Some(ErrorLocation {\n          filename: \"test.css\".into(),\n          line: 3,\n          column: 7,\n        }),\n      },\n    );\n\n    custom_media_error_test(\n      r#\"\n      @media (--not-defined) {\n        .a {\n          color: green;\n        }\n      }\n      \"#,\n      Error {\n        kind: MinifyErrorKind::CustomMediaNotDefined {\n          name: \"--not-defined\".into(),\n        },\n        loc: Some(ErrorLocation {\n          filename: \"test.css\".into(),\n          line: 1,\n          column: 7,\n        }),\n      },\n    );\n\n    custom_media_error_test(\n      r#\"\n      @custom-media --circular-mq-a (--circular-mq-b);\n      @custom-media --circular-mq-b (--circular-mq-a);\n\n      @media (--circular-mq-a) {\n        body {\n          order: 3;\n        }\n      }\n      \"#,\n      Error {\n        kind: MinifyErrorKind::CircularCustomMedia {\n          name: \"--circular-mq-a\".into(),\n        },\n        loc: Some(ErrorLocation {\n          filename: \"test.css\".into(),\n          line: 4,\n          column: 7,\n        }),\n      },\n    );\n  }\n\n  #[test]\n  fn test_dependencies() {\n    fn dep_test(source: &str, expected: &str, deps: Vec<(&str, &str)>) {\n      let mut stylesheet = StyleSheet::parse(\n        &source,\n        ParserOptions {\n          filename: \"test.css\".into(),\n          ..ParserOptions::default()\n        },\n      )\n      .unwrap();\n      stylesheet.minify(MinifyOptions::default()).unwrap();\n      let res = stylesheet\n        .to_css(PrinterOptions {\n          analyze_dependencies: Some(Default::default()),\n          minify: true,\n          ..PrinterOptions::default()\n        })\n        .unwrap();\n      assert_eq!(res.code, expected);\n      let dependencies = res.dependencies.unwrap();\n      assert_eq!(dependencies.len(), deps.len());\n      for (i, (url, placeholder)) in deps.into_iter().enumerate() {\n        match &dependencies[i] {\n          Dependency::Url(dep) => {\n            assert_eq!(dep.url, url);\n            assert_eq!(dep.placeholder, placeholder);\n          }\n          Dependency::Import(dep) => {\n            assert_eq!(dep.url, url);\n            assert_eq!(dep.placeholder, placeholder);\n          }\n        }\n      }\n    }\n\n    fn dep_error_test(source: &str, error: PrinterErrorKind) {\n      let stylesheet = StyleSheet::parse(&source, ParserOptions::default()).unwrap();\n      let res = stylesheet.to_css(PrinterOptions {\n        analyze_dependencies: Some(Default::default()),\n        ..PrinterOptions::default()\n      });\n      match res {\n        Err(e) => assert_eq!(e.kind, error),\n        _ => unreachable!(),\n      }\n    }\n\n    dep_test(\n      \".foo { background: image-set('./img12x.png', './img21x.png' 2x)}\",\n      \".foo{background:image-set(\\\"hXFI8W\\\" 1x,\\\"5TkpBa\\\" 2x)}\",\n      vec![(\"./img12x.png\", \"hXFI8W\"), (\"./img21x.png\", \"5TkpBa\")],\n    );\n\n    dep_test(\n      \".foo { background: image-set(url(./img12x.png), url('./img21x.png') 2x)}\",\n      \".foo{background:image-set(\\\"hXFI8W\\\" 1x,\\\"5TkpBa\\\" 2x)}\",\n      vec![(\"./img12x.png\", \"hXFI8W\"), (\"./img21x.png\", \"5TkpBa\")],\n    );\n\n    dep_test(\n      \".foo { --test: url(/foo.png) }\",\n      \".foo{--test:url(\\\"lDnnrG\\\")}\",\n      vec![(\"/foo.png\", \"lDnnrG\")],\n    );\n\n    dep_test(\n      \".foo { --test: url(\\\"/foo.png\\\") }\",\n      \".foo{--test:url(\\\"lDnnrG\\\")}\",\n      vec![(\"/foo.png\", \"lDnnrG\")],\n    );\n\n    dep_test(\n      \".foo { --test: url(\\\"http://example.com/foo.png\\\") }\",\n      \".foo{--test:url(\\\"3X1zSW\\\")}\",\n      vec![(\"http://example.com/foo.png\", \"3X1zSW\")],\n    );\n\n    dep_test(\n      \".foo { --test: url(\\\"data:image/svg+xml;utf8,<svg></svg>\\\") }\",\n      \".foo{--test:url(\\\"-vl-rG\\\")}\",\n      vec![(\"data:image/svg+xml;utf8,<svg></svg>\", \"-vl-rG\")],\n    );\n\n    dep_test(\n      \".foo { background: url(\\\"foo.png\\\") var(--test) }\",\n      \".foo{background:url(\\\"Vwkwkq\\\") var(--test)}\",\n      vec![(\"foo.png\", \"Vwkwkq\")],\n    );\n\n    dep_error_test(\n      \".foo { --test: url(\\\"foo.png\\\") }\",\n      PrinterErrorKind::AmbiguousUrlInCustomProperty { url: \"foo.png\".into() },\n    );\n\n    dep_error_test(\n      \".foo { --test: url(foo.png) }\",\n      PrinterErrorKind::AmbiguousUrlInCustomProperty { url: \"foo.png\".into() },\n    );\n\n    dep_error_test(\n      \".foo { --test: url(./foo.png) }\",\n      PrinterErrorKind::AmbiguousUrlInCustomProperty {\n        url: \"./foo.png\".into(),\n      },\n    );\n\n    dep_test(\n      \".foo { behavior: url(#foo) }\",\n      \".foo{behavior:url(\\\"Zn9-2q\\\")}\",\n      vec![(\"#foo\", \"Zn9-2q\")],\n    );\n\n    dep_test(\n      \".foo { --foo: url(#foo) }\",\n      \".foo{--foo:url(\\\"Zn9-2q\\\")}\",\n      vec![(\"#foo\", \"Zn9-2q\")],\n    );\n\n    dep_test(\n      \"@import \\\"test.css\\\"; .foo { color: red }\",\n      \"@import \\\"hHsogW\\\";.foo{color:red}\",\n      vec![(\"test.css\", \"hHsogW\")],\n    );\n  }\n\n  #[test]\n  fn test_api() {\n    let stylesheet = StyleSheet::parse(\".foo:hover { color: red }\", ParserOptions::default()).unwrap();\n    match &stylesheet.rules.0[0] {\n      CssRule::Style(s) => {\n        assert_eq!(&s.selectors.to_string(), \".foo:hover\");\n      }\n      _ => unreachable!(),\n    }\n\n    let color = CssColor::parse_string(\"#f0f\").unwrap();\n    assert_eq!(color.to_css_string(PrinterOptions::default()).unwrap(), \"#f0f\");\n\n    let rule = CssRule::parse_string(\".foo { color: red }\", ParserOptions::default()).unwrap();\n    assert_eq!(\n      rule.to_css_string(PrinterOptions::default()).unwrap(),\n      indoc! {r#\"\n    .foo {\n      color: red;\n    }\"#}\n    );\n\n    let property = Property::parse_string(\"color\".into(), \"#f0f\", ParserOptions::default()).unwrap();\n    assert_eq!(\n      property.to_css_string(false, PrinterOptions::default()).unwrap(),\n      \"color: #f0f\"\n    );\n    assert_eq!(\n      property.to_css_string(true, PrinterOptions::default()).unwrap(),\n      \"color: #f0f !important\"\n    );\n\n    let code = indoc! { r#\"\n      .foo {\n        color: green;\n      }\n\n      .bar {\n        color: red;\n        background: pink;\n      }\n\n      @media print {\n        .baz {\n          color: green;\n        }\n      }\n    \"#};\n    let stylesheet = StyleSheet::parse(code, ParserOptions::default()).unwrap();\n    if let CssRule::Style(style) = &stylesheet.rules.0[1] {\n      let (key, val) = style.property_location(code, 0).unwrap();\n      assert_eq!(\n        key,\n        SourceLocation { line: 5, column: 3 }..SourceLocation { line: 5, column: 8 }\n      );\n      assert_eq!(\n        val,\n        SourceLocation { line: 5, column: 10 }..SourceLocation { line: 5, column: 13 }\n      );\n    }\n\n    if let CssRule::Style(style) = &stylesheet.rules.0[1] {\n      let (key, val) = style.property_location(code, 1).unwrap();\n      assert_eq!(\n        key,\n        SourceLocation { line: 6, column: 3 }..SourceLocation { line: 6, column: 13 }\n      );\n      assert_eq!(\n        val,\n        SourceLocation { line: 6, column: 15 }..SourceLocation { line: 6, column: 19 }\n      );\n    }\n    if let CssRule::Media(media) = &stylesheet.rules.0[2] {\n      if let CssRule::Style(style) = &media.rules.0[0] {\n        let (key, val) = style.property_location(code, 0).unwrap();\n        assert_eq!(\n          key,\n          SourceLocation { line: 11, column: 5 }..SourceLocation { line: 11, column: 10 }\n        );\n        assert_eq!(\n          val,\n          SourceLocation { line: 11, column: 12 }..SourceLocation { line: 11, column: 17 }\n        );\n      }\n    }\n\n    let mut property = Property::Transform(Default::default(), VendorPrefix::WebKit);\n    property.set_prefix(VendorPrefix::None);\n    assert_eq!(property, Property::Transform(Default::default(), VendorPrefix::None));\n    property.set_prefix(VendorPrefix::Moz);\n    assert_eq!(property, Property::Transform(Default::default(), VendorPrefix::Moz));\n    property.set_prefix(VendorPrefix::WebKit | VendorPrefix::Moz);\n    assert_eq!(\n      property,\n      Property::Transform(Default::default(), VendorPrefix::WebKit | VendorPrefix::Moz)\n    );\n\n    let mut property = Property::TextDecorationLine(Default::default(), VendorPrefix::None);\n    property.set_prefix(VendorPrefix::Ms);\n    assert_eq!(\n      property,\n      Property::TextDecorationLine(Default::default(), VendorPrefix::None)\n    );\n    property.set_prefix(VendorPrefix::WebKit | VendorPrefix::Moz | VendorPrefix::Ms);\n    assert_eq!(\n      property,\n      Property::TextDecorationLine(Default::default(), VendorPrefix::WebKit | VendorPrefix::Moz)\n    );\n\n    let mut property = Property::AccentColor(Default::default());\n    property.set_prefix(VendorPrefix::WebKit);\n    assert_eq!(property, Property::AccentColor(Default::default()));\n  }\n\n  #[cfg(feature = \"substitute_variables\")]\n  #[test]\n  fn test_substitute_vars() {\n    use crate::properties::custom::TokenList;\n    use crate::traits::ParseWithOptions;\n\n    fn test(property: Property, vars: HashMap<&str, &str>, expected: &str) {\n      if let Property::Unparsed(unparsed) = property {\n        let vars = vars\n          .into_iter()\n          .map(|(k, v)| {\n            (\n              k,\n              TokenList::parse_string_with_options(v, ParserOptions::default()).unwrap(),\n            )\n          })\n          .collect();\n        let substituted = unparsed.substitute_variables(&vars).unwrap();\n        assert_eq!(\n          substituted.to_css_string(false, PrinterOptions::default()).unwrap(),\n          expected\n        );\n      } else {\n        panic!(\"Not an unparsed property\");\n      }\n    }\n\n    let property = Property::parse_string(\"color\".into(), \"var(--test)\", ParserOptions::default()).unwrap();\n    test(property, HashMap::from([(\"--test\", \"yellow\")]), \"color: #ff0\");\n\n    let property =\n      Property::parse_string(\"color\".into(), \"var(--test, var(--foo))\", ParserOptions::default()).unwrap();\n    test(property, HashMap::from([(\"--foo\", \"yellow\")]), \"color: #ff0\");\n    let property = Property::parse_string(\n      \"color\".into(),\n      \"var(--test, var(--foo, yellow))\",\n      ParserOptions::default(),\n    )\n    .unwrap();\n    test(property, HashMap::new(), \"color: #ff0\");\n\n    let property =\n      Property::parse_string(\"width\".into(), \"calc(var(--a) + var(--b))\", ParserOptions::default()).unwrap();\n    test(property, HashMap::from([(\"--a\", \"2px\"), (\"--b\", \"4px\")]), \"width: 6px\");\n\n    let property = Property::parse_string(\"color\".into(), \"var(--a)\", ParserOptions::default()).unwrap();\n    test(\n      property,\n      HashMap::from([(\"--a\", \"var(--b)\"), (\"--b\", \"yellow\")]),\n      \"color: #ff0\",\n    );\n\n    let property = Property::parse_string(\"color\".into(), \"var(--a)\", ParserOptions::default()).unwrap();\n    test(\n      property,\n      HashMap::from([(\"--a\", \"var(--b)\"), (\"--b\", \"var(--c)\"), (\"--c\", \"var(--a)\")]),\n      \"color: var(--a)\",\n    );\n  }\n\n  #[test]\n  fn test_layer() {\n    minify_test(\"@layer foo;\", \"@layer foo;\");\n    minify_test(\"@layer foo, bar;\", \"@layer foo,bar;\");\n    minify_test(\"@layer foo.bar;\", \"@layer foo.bar;\");\n    minify_test(\"@layer foo.bar, baz;\", \"@layer foo.bar,baz;\");\n\n    minify_test(\n      r#\"\n      @layer foo {\n        .bar {\n          color: red;\n        }\n      }\n    \"#,\n      \"@layer foo{.bar{color:red}}\",\n    );\n    minify_test(\n      r#\"\n      @layer foo.bar {\n        .bar {\n          color: red;\n        }\n      }\n    \"#,\n      \"@layer foo.bar{.bar{color:red}}\",\n    );\n    minify_test(r#\"\n      @layer base {\n        p { max-width: 70ch; }\n      }\n\n      @layer framework {\n        @layer base {\n          p { margin-block: 0.75em; }\n        }\n\n        @layer theme {\n          p { color: #222; }\n        }\n      }\n    \"#, \"@layer base{p{max-width:70ch}}@layer framework{@layer base{p{margin-block:.75em}}@layer theme{p{color:#222}}}\");\n    minify_test(\n      r#\"\n      @layer {\n        .bar {\n          color: red;\n        }\n      }\n    \"#,\n      \"@layer{.bar{color:red}}\",\n    );\n    minify_test(\n      r#\"\n      @layer foo\\20 bar, baz;\n    \"#,\n      \"@layer foo\\\\ bar,baz;\",\n    );\n    minify_test(\n      r#\"\n      @layer one.two\\20 three\\#four\\.five {\n        .bar {\n          color: red;\n        }\n      }\n    \"#,\n      \"@layer one.two\\\\ three\\\\#four\\\\.five{.bar{color:red}}\",\n    );\n\n    error_test(\"@layer;\", ParserError::UnexpectedToken(Token::Semicolon));\n    error_test(\"@layer foo, bar {};\", ParserError::AtRuleBodyInvalid);\n    minify_test(\"@import 'test.css' layer;\", \"@import \\\"test.css\\\" layer;\");\n    minify_test(\"@import 'test.css' layer(foo);\", \"@import \\\"test.css\\\" layer(foo);\");\n    minify_test(\n      \"@import 'test.css' layer(foo.bar);\",\n      \"@import \\\"test.css\\\" layer(foo.bar);\",\n    );\n    minify_test(\n      \"@import 'test.css' layer(foo\\\\20 bar);\",\n      \"@import \\\"test.css\\\" layer(foo\\\\ bar);\",\n    );\n    error_test(\n      \"@import 'test.css' layer(foo, bar) {};\",\n      ParserError::UnexpectedToken(Token::Comma),\n    );\n    minify_test(\n      r#\"\n      @layer one {\n        body {\n          background: red;\n        }\n      }\n\n      body {\n        background: red;\n      }\n\n      @layer two {\n        body {\n          background: green;\n        }\n      }\n\n      @layer one {\n        body {\n          background: yellow;\n        }\n      }\n      \"#,\n      \"@layer one{body{background:#ff0}}body{background:red}@layer two{body{background:green}}\",\n    );\n  }\n\n  #[test]\n  fn test_property() {\n    minify_test(\n      r#\"\n      @property --property-name {\n        syntax: '<color>';\n        inherits: false;\n        initial-value: yellow;\n      }\n    \"#,\n      \"@property --property-name{syntax:\\\"<color>\\\";inherits:false;initial-value:#ff0}\",\n    );\n\n    test(\n      r#\"\n      @property --property-name {\n        syntax: '*';\n        inherits: false;\n        initial-value: ;\n      }\n    \"#,\n      indoc! {r#\"\n      @property --property-name {\n        syntax: \"*\";\n        inherits: false;\n        initial-value: ;\n      }\n    \"#},\n    );\n\n    minify_test(\n      r#\"\n      @property --property-name {\n        syntax: '*';\n        inherits: false;\n        initial-value: ;\n      }\n    \"#,\n      \"@property --property-name{syntax:\\\"*\\\";inherits:false;initial-value:}\",\n    );\n\n    test(\n      r#\"\n      @property --property-name {\n        syntax: '*';\n        inherits: false;\n        initial-value:;\n      }\n    \"#,\n      indoc! {r#\"\n      @property --property-name {\n        syntax: \"*\";\n        inherits: false;\n        initial-value: ;\n      }\n    \"#},\n    );\n\n    minify_test(\n      r#\"\n      @property --property-name {\n        syntax: '*';\n        inherits: false;\n        initial-value:;\n      }\n    \"#,\n      \"@property --property-name{syntax:\\\"*\\\";inherits:false;initial-value:}\",\n    );\n    minify_test(\n      r#\"\n      @property --property-name {\n        syntax: '*';\n        inherits: false;\n        initial-value: foo bar;\n      }\n    \"#,\n      \"@property --property-name{syntax:\\\"*\\\";inherits:false;initial-value:foo bar}\",\n    );\n\n    minify_test(\n      r#\"\n      @property --property-name {\n        syntax: '<length>';\n        inherits: true;\n        initial-value: 25px;\n      }\n    \"#,\n      \"@property --property-name{syntax:\\\"<length>\\\";inherits:true;initial-value:25px}\",\n    );\n\n    minify_test(\n      r#\"\n      @property --property-name {\n        syntax: '<string>';\n        inherits: true;\n        initial-value: \"hi\";\n      }\n    \"#,\n      \"@property --property-name{syntax:\\\"<string>\\\";inherits:true;initial-value:\\\"hi\\\"}\",\n    );\n\n    error_test(\n      r#\"\n      @property --property-name {\n        syntax: '<color>';\n        inherits: false;\n        initial-value: 25px;\n      }\n    \"#,\n      ParserError::UnexpectedToken(crate::properties::custom::Token::Dimension {\n        has_sign: false,\n        value: 25.0,\n        int_value: Some(25),\n        unit: \"px\".into(),\n      }),\n    );\n\n    error_test(\n      r#\"\n      @property --property-name {\n        syntax: '<length>';\n        inherits: false;\n        initial-value: var(--some-value);\n      }\n    \"#,\n      ParserError::UnexpectedToken(crate::properties::custom::Token::Function(\"var\".into())),\n    );\n\n    error_test(\n      r#\"\n      @property --property-name {\n        syntax: '<color>';\n        inherits: false;\n      }\n    \"#,\n      ParserError::AtRuleBodyInvalid,\n    );\n\n    minify_test(\n      r#\"\n      @property --property-name {\n        syntax: '*';\n        inherits: false;\n      }\n    \"#,\n      \"@property --property-name{syntax:\\\"*\\\";inherits:false}\",\n    );\n\n    error_test(\n      r#\"\n      @property --property-name {\n        syntax: '*';\n      }\n    \"#,\n      ParserError::AtRuleBodyInvalid,\n    );\n\n    error_test(\n      r#\"\n      @property --property-name {\n        inherits: false;\n      }\n    \"#,\n      ParserError::AtRuleBodyInvalid,\n    );\n\n    error_test(\n      r#\"\n      @property property-name {\n        syntax: '*';\n        inherits: false;\n      }\n    \"#,\n      ParserError::UnexpectedToken(crate::properties::custom::Token::Ident(\"property-name\".into())),\n    );\n\n    minify_test(\n      r#\"\n      @property --property-name {\n        syntax: 'custom | <color>';\n        inherits: false;\n        initial-value: yellow;\n      }\n    \"#,\n      \"@property --property-name{syntax:\\\"custom|<color>\\\";inherits:false;initial-value:#ff0}\",\n    );\n\n    // TODO: Re-enable with a better solution\n    //       See: https://github.com/parcel-bundler/lightningcss/issues/288\n    // minify_test(r#\"\n    //   @property --property-name {\n    //     syntax: '<transform-list>';\n    //     inherits: false;\n    //     initial-value: translate(200px,300px) translate(100px,200px) scale(2);\n    //   }\n    // \"#, \"@property --property-name{syntax:\\\"<transform-list>\\\";inherits:false;initial-value:matrix(2,0,0,2,300,500)}\");\n\n    minify_test(\n      r#\"\n      @property --property-name {\n        syntax: '<time>';\n        inherits: false;\n        initial-value: 1000ms;\n      }\n    \"#,\n      \"@property --property-name{syntax:\\\"<time>\\\";inherits:false;initial-value:1s}\",\n    );\n\n    minify_test(\n      r#\"\n      @property --property-name {\n        syntax: '<url>';\n        inherits: false;\n        initial-value: url(\"foo.png\");\n      }\n    \"#,\n      \"@property --property-name{syntax:\\\"<url>\\\";inherits:false;initial-value:url(foo.png)}\",\n    );\n\n    minify_test(\n      r#\"\n      @property --property-name {\n        syntax: '<image>';\n        inherits: false;\n        initial-value: linear-gradient(yellow, blue);\n      }\n    \"#,\n      \"@property --property-name{syntax:\\\"<image>\\\";inherits:false;initial-value:linear-gradient(#ff0,#00f)}\",\n    );\n\n    minify_test(\n      r#\"\n      @property --property-name {\n        initial-value: linear-gradient(yellow, blue);\n        inherits: false;\n        syntax: '<image>';\n      }\n    \"#,\n      \"@property --property-name{syntax:\\\"<image>\\\";inherits:false;initial-value:linear-gradient(#ff0,#00f)}\",\n    );\n\n    test(\n      r#\"\n      @property --property-name {\n        syntax: '<length>|none';\n        inherits: false;\n        initial-value: none;\n      }\n    \"#,\n      indoc! {r#\"\n      @property --property-name {\n        syntax: \"<length> | none\";\n        inherits: false;\n        initial-value: none;\n      }\n    \"#},\n    );\n\n    minify_test(\n      r#\"\n      @property --property-name {\n        syntax: '<color>#';\n        inherits: false;\n        initial-value: yellow, blue;\n      }\n    \"#,\n      \"@property --property-name{syntax:\\\"<color>#\\\";inherits:false;initial-value:#ff0,#00f}\",\n    );\n    minify_test(\n      r#\"\n      @property --property-name {\n        syntax: '<color>+';\n        inherits: false;\n        initial-value: yellow blue;\n      }\n    \"#,\n      \"@property --property-name{syntax:\\\"<color>+\\\";inherits:false;initial-value:#ff0 #00f}\",\n    );\n    minify_test(\n      r#\"\n      @property --property-name {\n        syntax: '<color>';\n        inherits: false;\n        initial-value: yellow;\n      }\n      .foo {\n        color: var(--property-name)\n      }\n      @property --property-name {\n        syntax: '<color>';\n        inherits: true;\n        initial-value: blue;\n      }\n    \"#,\n      \"@property --property-name{syntax:\\\"<color>\\\";inherits:true;initial-value:#00f}.foo{color:var(--property-name)}\",\n    );\n\n    test(\n      r#\"\n      @media (width < 800px) {\n        @property --property-name {\n          syntax: '*';\n          inherits: false;\n        }\n      }\n    \"#,\n      indoc! {r#\"\n        @media (width < 800px) {\n          @property --property-name {\n            syntax: \"*\";\n            inherits: false\n          }\n        }\n    \"#},\n    );\n\n    test(\n      r#\"\n      @layer foo {\n        @property --property-name {\n          syntax: '*';\n          inherits: false;\n        }\n      }\n    \"#,\n      indoc! {r#\"\n        @layer foo {\n          @property --property-name {\n            syntax: \"*\";\n            inherits: false\n          }\n        }\n    \"#},\n    );\n  }\n\n  #[test]\n  fn test_quoting_unquoting_urls() {\n    // Quotes remain double quotes when not minifying\n    test(\n      r#\".foo {\n      background-image: url(\"0123abcd\");\n    }\"#,\n      r#\".foo {\n  background-image: url(\"0123abcd\");\n}\n\"#,\n    );\n\n    // Quotes removed when minifying\n    minify_test(\n      r#\".foo {\n      background-image: url(\"0123abcd\");\n    }\"#,\n      r#\".foo{background-image:url(0123abcd)}\"#,\n    );\n\n    // Doubles quotes added if not present when not minifying\n    test(\n      r#\".foo {\n      background-image: url(0123abcd);\n    }\"#,\n      r#\".foo {\n  background-image: url(\"0123abcd\");\n}\n\"#,\n    );\n\n    // No quotes changed if not present when not minifying\n    minify_test(\n      r#\".foo {\n      background-image: url(0123abcd);\n    }\"#,\n      r#\".foo{background-image:url(0123abcd)}\"#,\n    );\n  }\n\n  #[test]\n  fn test_zindex() {\n    minify_test(\".foo { z-index: 2 }\", \".foo{z-index:2}\");\n    minify_test(\".foo { z-index: -2 }\", \".foo{z-index:-2}\");\n    minify_test(\".foo { z-index: 999999 }\", \".foo{z-index:999999}\");\n    minify_test(\".foo { z-index: 9999999 }\", \".foo{z-index:9999999}\");\n    minify_test(\".foo { z-index: -9999999 }\", \".foo{z-index:-9999999}\");\n  }\n\n  #[test]\n  #[cfg(feature = \"sourcemap\")]\n  fn test_input_source_map() {\n    let source = r#\".imported {\n      content: \"yay, file support!\";\n    }\n\n    .selector {\n      margin: 1em;\n      background-color: #f60;\n    }\n\n    .selector .nested {\n      margin: 0.5em;\n    }\n\n    /*# sourceMappingURL=data:application/json;base64,ewoJInZlcnNpb24iOiAzLAoJInNvdXJjZVJvb3QiOiAicm9vdCIsCgkiZmlsZSI6ICJzdGRvdXQiLAoJInNvdXJjZXMiOiBbCgkJInN0ZGluIiwKCQkic2Fzcy9fdmFyaWFibGVzLnNjc3MiLAoJCSJzYXNzL19kZW1vLnNjc3MiCgldLAoJInNvdXJjZXNDb250ZW50IjogWwoJCSJAaW1wb3J0IFwiX3ZhcmlhYmxlc1wiO1xuQGltcG9ydCBcIl9kZW1vXCI7XG5cbi5zZWxlY3RvciB7XG4gIG1hcmdpbjogJHNpemU7XG4gIGJhY2tncm91bmQtY29sb3I6ICRicmFuZENvbG9yO1xuXG4gIC5uZXN0ZWQge1xuICAgIG1hcmdpbjogJHNpemUgLyAyO1xuICB9XG59IiwKCQkiJGJyYW5kQ29sb3I6ICNmNjA7XG4kc2l6ZTogMWVtOyIsCgkJIi5pbXBvcnRlZCB7XG4gIGNvbnRlbnQ6IFwieWF5LCBmaWxlIHN1cHBvcnQhXCI7XG59IgoJXSwKCSJtYXBwaW5ncyI6ICJBRUFBLFNBQVMsQ0FBQztFQUNSLE9BQU8sRUFBRSxvQkFBcUI7Q0FDL0I7O0FGQ0QsU0FBUyxDQUFDO0VBQ1IsTUFBTSxFQ0hELEdBQUc7RURJUixnQkFBZ0IsRUNMTCxJQUFJO0NEVWhCOztBQVBELFNBQVMsQ0FJUCxPQUFPLENBQUM7RUFDTixNQUFNLEVDUEgsS0FBRztDRFFQIiwKCSJuYW1lcyI6IFtdCn0= */\"#;\n\n    let mut stylesheet = StyleSheet::parse(&source, ParserOptions::default()).unwrap();\n    stylesheet.minify(MinifyOptions::default()).unwrap();\n    let mut sm = parcel_sourcemap::SourceMap::new(\"/\");\n    stylesheet\n      .to_css(PrinterOptions {\n        source_map: Some(&mut sm),\n        minify: true,\n        ..PrinterOptions::default()\n      })\n      .unwrap();\n    let map = sm.to_json(None).unwrap();\n    assert_eq!(\n      map,\n      r#\"{\"version\":3,\"sourceRoot\":null,\"mappings\":\"AAAA,uCCGA,2CAAA\",\"sources\":[\"sass/_demo.scss\",\"stdin\"],\"sourcesContent\":[\".imported {\\n  content: \\\"yay, file support!\\\";\\n}\",\"@import \\\"_variables\\\";\\n@import \\\"_demo\\\";\\n\\n.selector {\\n  margin: $size;\\n  background-color: $brandColor;\\n\\n  .nested {\\n    margin: $size / 2;\\n  }\\n}\"],\"names\":[]}\"#\n    );\n  }\n\n  #[test]\n  #[cfg(feature = \"sourcemap\")]\n  fn test_source_maps_with_license_comments() {\n    let source = r#\"/*! a single line comment */\n    /*!\n      a comment\n      containing\n      multiple\n      lines\n    */\n    .a {\n      display: flex;\n    }\n\n    .b {\n      display: hidden;\n    }\n    \"#;\n\n    let mut sm = parcel_sourcemap::SourceMap::new(\"/\");\n    let source_index = sm.add_source(\"input.css\");\n    sm.set_source_content(source_index as usize, source).unwrap();\n\n    let mut stylesheet = StyleSheet::parse(\n      &source,\n      ParserOptions {\n        source_index,\n        ..Default::default()\n      },\n    )\n    .unwrap();\n    stylesheet.minify(MinifyOptions::default()).unwrap();\n    stylesheet\n      .to_css(PrinterOptions {\n        source_map: Some(&mut sm),\n        minify: true,\n        ..PrinterOptions::default()\n      })\n      .unwrap();\n    let map = sm.to_json(None).unwrap();\n    assert_eq!(\n      map,\n      r#\"{\"version\":3,\"sourceRoot\":null,\"mappings\":\";;;;;;;AAOI,gBAIA\",\"sources\":[\"input.css\"],\"sourcesContent\":[\"/*! a single line comment */\\n    /*!\\n      a comment\\n      containing\\n      multiple\\n      lines\\n    */\\n    .a {\\n      display: flex;\\n    }\\n\\n    .b {\\n      display: hidden;\\n    }\\n    \"],\"names\":[]}\"#\n    );\n  }\n\n  #[test]\n  fn test_error_recovery() {\n    use std::sync::{Arc, RwLock};\n    let warnings = Some(Arc::new(RwLock::new(Vec::new())));\n    test_with_options(\n      r#\"\n      h1(>h1) {\n        color: red;\n      }\n\n      .foo {\n        color: red;\n      }\n\n      .clearfix {\n        *zoom: 1;\n        background: red;\n      }\n\n      @media (hover) {\n        h1(>h1) {\n          color: red;\n        }\n\n        .bar {\n          color: red;\n        }\n      }\n\n      input:placeholder {\n        color: red;\n      }\n\n      input::hover {\n        color: red;\n      }\n    \"#,\n      indoc! { r#\"\n      .foo {\n        color: red;\n      }\n\n      .clearfix {\n        background: red;\n      }\n\n      @media (hover) {\n        .bar {\n          color: red;\n        }\n      }\n\n      input:placeholder {\n        color: red;\n      }\n\n      input::hover {\n        color: red;\n      }\n    \"#},\n      ParserOptions {\n        filename: \"test.css\".into(),\n        error_recovery: true,\n        warnings: warnings.clone(),\n        ..ParserOptions::default()\n      },\n    );\n    let w = warnings.unwrap();\n    let warnings = w.read().unwrap();\n    assert_eq!(\n      *warnings,\n      vec![\n        Error {\n          kind: ParserError::SelectorError(SelectorError::EmptySelector),\n          loc: Some(ErrorLocation {\n            filename: \"test.css\".into(),\n            line: 1,\n            column: 7\n          })\n        },\n        Error {\n          kind: ParserError::UnexpectedToken(Token::Semicolon),\n          loc: Some(ErrorLocation {\n            filename: \"test.css\".into(),\n            line: 10,\n            column: 17\n          })\n        },\n        Error {\n          kind: ParserError::SelectorError(SelectorError::EmptySelector),\n          loc: Some(ErrorLocation {\n            filename: \"test.css\".into(),\n            line: 15,\n            column: 9\n          })\n        },\n        Error {\n          kind: ParserError::SelectorError(SelectorError::UnsupportedPseudoClass(\"placeholder\".into())),\n          loc: Some(ErrorLocation {\n            filename: \"test.css\".into(),\n            line: 24,\n            column: 13,\n          }),\n        },\n        Error {\n          kind: ParserError::SelectorError(SelectorError::UnsupportedPseudoElement(\"hover\".into())),\n          loc: Some(ErrorLocation {\n            filename: \"test.css\".into(),\n            line: 28,\n            column: 13,\n          }),\n        },\n      ]\n    )\n  }\n\n  #[test]\n  fn test_invalid() {\n    error_test(\n      \".a{color: hsla(120, 62.32%;}\",\n      ParserError::UnexpectedToken(Token::CloseCurlyBracket),\n    );\n    error_test(\n      \".a{--foo: url(foo\\\\) b\\\\)ar)}\",\n      ParserError::UnexpectedToken(Token::BadUrl(\"foo\\\\) b\\\\)ar\".into())),\n    );\n  }\n\n  #[test]\n  fn test_container_queries() {\n    // name only (no condition) - new syntax\n    minify_test(\n      r#\"\n      @container foo {\n        .inner {\n          background: green;\n        }\n      }\n    \"#,\n      \"@container foo{.inner{background:green}}\",\n    );\n\n    // with name\n    minify_test(\n      r#\"\n      @container my-layout (inline-size > 45em) {\n        .foo {\n          color: red;\n        }\n      }\n    \"#,\n      \"@container my-layout (inline-size>45em){.foo{color:red}}\",\n    );\n\n    minify_test(\n      r#\"\n      @container my-layout ( not (width > 500px) ) {\n        .foo {\n          color: red;\n        }\n      }\n    \"#,\n      \"@container my-layout not (width>500px){.foo{color:red}}\",\n    );\n\n    minify_test(\n      r#\"\n      @container my-layout not (width > 500px) {\n        .foo {\n          color: red;\n        }\n      }\n    \"#,\n      \"@container my-layout not (width>500px){.foo{color:red}}\",\n    );\n\n    minify_test(\n      r#\"\n      @container not (width > 500px) {\n        .foo {\n          color: red;\n        }\n      }\n    \"#,\n      \"@container not (width>500px){.foo{color:red}}\",\n    );\n\n    minify_test(\n      r#\"\n      @container my-layout ((width: 100px) and (not (height: 100px))) {\n        .foo {\n          color: red;\n        }\n      }\n    \"#,\n      \"@container my-layout (width:100px) and (not (height:100px)){.foo{color:red}}\",\n    );\n\n    minify_test(\n      r#\"\n      @container my-layout (width = max(10px, 10em)) {\n        .foo {\n          color: red;\n        }\n      }\n    \"#,\n      \"@container my-layout (width=max(10px,10em)){.foo{color:red}}\",\n    );\n\n    // without name\n    minify_test(\n      r#\"\n      @container (inline-size > 45em) {\n        .foo {\n          color: red;\n        }\n      }\n    \"#,\n      \"@container (inline-size>45em){.foo{color:red}}\",\n    );\n\n    minify_test(\n      r#\"\n      @container (inline-size > 45em) and (inline-size < 100em) {\n        .foo {\n          color: red;\n        }\n      }\n    \"#,\n      \"@container (inline-size>45em) and (inline-size<100em){.foo{color:red}}\",\n    );\n\n    // calc()\n    minify_test(\n      r#\"\n      @container (width > calc(100vw - 50px)) {\n        .foo {\n          color: red;\n        }\n      }\n    \"#,\n      \"@container (width>calc(100vw - 50px)){.foo{color:red}}\",\n    );\n\n    minify_test(\n      r#\"\n      @container (calc(100vh - 50px) <= height ) {\n        .foo {\n          color: red;\n        }\n      }\n    \"#,\n      \"@container (height>=calc(100vh - 50px)){.foo{color:red}}\",\n    );\n\n    // merge adjacent\n    minify_test(\n      r#\"\n      @container my-layout (inline-size > 45em) {\n        .foo {\n          color: red;\n        }\n      }\n\n      @container my-layout (inline-size > 45em) {\n        .foo {\n          background: yellow;\n        }\n\n        .bar {\n          color: white;\n        }\n      }\n    \"#,\n      \"@container my-layout (inline-size>45em){.foo{color:red;background:#ff0}.bar{color:#fff}}\",\n    );\n\n    minify_test(\n      r#\"\n    .foo {\n      container-name: foo bar;\n      container-type: size;\n    }\n    \"#,\n      \".foo{container:foo bar/size}\",\n    );\n    minify_test(\n      r#\"\n    .foo {\n      container-name: foo bar;\n      container-type: normal;\n    }\n    \"#,\n      \".foo{container:foo bar}\",\n    );\n    minify_test(\n      \".foo{ container-type: inline-size }\",\n      \".foo{container-type:inline-size}\",\n    );\n    minify_test(\".foo{ container-name: none; }\", \".foo{container-name:none}\");\n    minify_test(\".foo{ container-name: foo; }\", \".foo{container-name:foo}\");\n    minify_test(\".foo{ container: foo / normal; }\", \".foo{container:foo}\");\n    minify_test(\n      \".foo{ container: foo / inline-size; }\",\n      \".foo{container:foo/inline-size}\",\n    );\n    minify_test(\".foo { width: calc(1cqw + 2cqw) }\", \".foo{width:3cqw}\");\n    minify_test(\".foo { width: calc(1cqh + 2cqh) }\", \".foo{width:3cqh}\");\n    minify_test(\".foo { width: calc(1cqi + 2cqi) }\", \".foo{width:3cqi}\");\n    minify_test(\".foo { width: calc(1cqb + 2cqb) }\", \".foo{width:3cqb}\");\n    minify_test(\".foo { width: calc(1cqmin + 2cqmin) }\", \".foo{width:3cqmin}\");\n    minify_test(\".foo { width: calc(1cqmax + 2cqmax) }\", \".foo{width:3cqmax}\");\n\n    // Unlike in @media, there is no need to convert the range syntax in @container,\n    // because browsers all support this syntax.\n    prefix_test(\n      r#\"\n      @container (width > 100px) {\n        .foo { padding: 5px; }\n      }\n      \"#,\n      indoc! { r#\"\n        @container (width > 100px) {\n          .foo {\n            padding: 5px;\n          }\n        }\n      \"#},\n      Browsers {\n        chrome: Some(105 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      @container (min-width: 100px) {\n        .foo { padding: 5px; }\n      }\n      \"#,\n      indoc! { r#\"\n        @container (width >= 100px) {\n          .foo {\n            padding: 5px;\n          }\n        }\n      \"#},\n      Browsers {\n        chrome: Some(105 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    minify_test(\n      r#\"\n      @container style(--responsive: true) {\n        .foo {\n          color: red;\n        }\n      }\n    \"#,\n      \"@container style(--responsive:true){.foo{color:red}}\",\n    );\n    minify_test(\n      r#\"\n      @container style(--responsive: true) and style(color: yellow) {\n        .foo {\n          color: red;\n        }\n      }\n    \"#,\n      \"@container style(--responsive:true) and style(color:#ff0){.foo{color:red}}\",\n    );\n    minify_test(\n      r#\"\n      @container not style(--responsive: true) {\n        .foo {\n          color: red;\n        }\n      }\n    \"#,\n      \"@container not style(--responsive:true){.foo{color:red}}\",\n    );\n    minify_test(\n      r#\"\n      @container (inline-size > 45em) and style(--responsive: true) {\n        .foo {\n          color: red;\n        }\n      }\n    \"#,\n      \"@container (inline-size>45em) and style(--responsive:true){.foo{color:red}}\",\n    );\n    minify_test(\n      r#\"\n      @container style((accent-color: yellow) or (--bar: 10px)) {\n        .foo {\n          color: red;\n        }\n      }\n    \"#,\n      \"@container style((accent-color:#ff0) or (--bar:10px)){.foo{color:red}}\",\n    );\n    minify_test(\n      r#\"\n      @container style(not ((width: calc(10px + 20px)) and ((--bar: url(x))))) {\n        .foo {\n          color: red;\n        }\n      }\n    \"#,\n      \"@container style(not ((width:30px) and (--bar:url(x)))){.foo{color:red}}\",\n    );\n    minify_test(\n      r#\"\n      @container style(color: yellow !important) {\n        .foo {\n          color: red;\n        }\n      }\n    \"#,\n      \"@container style(color:yellow){.foo{color:red}}\",\n    );\n    minify_test(\n      r#\"\n      @container style(--foo:) {\n        .foo {\n          color: red;\n        }\n      }\n    \"#,\n      \"@container style(--foo:){.foo{color:red}}\",\n    );\n    minify_test(\n      r#\"\n      @container style(--foo: ) {\n        .foo {\n          color: red;\n        }\n      }\n    \"#,\n      \"@container style(--foo:){.foo{color:red}}\",\n    );\n    minify_test(\n      r#\"\n      @container style(--my-prop: foo - bar ()) {\n        .foo {\n          color: red;\n        }\n      }\n    \"#,\n      \"@container style(--my-prop:foo - bar ()){.foo{color:red}}\",\n    );\n    minify_test(\n      r#\"\n      @container style(--test) {\n        .foo {\n          color: red;\n        }\n      }\n    \"#,\n      \"@container style(--test){.foo{color:red}}\",\n    );\n    minify_test(\n      r#\"\n      @container style(width) {\n        .foo {\n          color: red;\n        }\n      }\n    \"#,\n      \"@container style(width){.foo{color:red}}\",\n    );\n    minify_test(\n      r#\"\n      @container scroll-state(scrollable: top) {\n        .foo {\n          color: red;\n        }\n      }\n    \"#,\n      \"@container scroll-state(scrollable:top){.foo{color:red}}\",\n    );\n    minify_test(\n      r#\"\n      @container scroll-state((stuck: top) and (stuck: left)) {\n        .foo {\n          color: red;\n        }\n      }\n    \"#,\n      \"@container scroll-state((stuck:top) and (stuck:left)){.foo{color:red}}\",\n    );\n    minify_test(\n      r#\"\n      @container scroll-state(not ((scrollable: bottom) and (scrollable: right))) {\n        .foo {\n          color: red;\n        }\n      }\n    \"#,\n      \"@container scroll-state(not ((scrollable:bottom) and (scrollable:right))){.foo{color:red}}\",\n    );\n    minify_test(\n      r#\"\n      @container (scroll-state(scrollable: inline-end)) {\n        .foo {\n          color: red;\n        }\n      }\n    \"#,\n      \"@container scroll-state(scrollable:inline-end){.foo{color:red}}\",\n    );\n    minify_test(\n      r#\"\n      @container not scroll-state(scrollable: top) {\n        .foo {\n          color: red;\n        }\n      }\n    \"#,\n      \"@container not scroll-state(scrollable:top){.foo{color:red}}\",\n    );\n\n    // Disallow 'none', 'not', 'and', 'or' as a `<container-name>`\n    // https://github.com/w3c/csswg-drafts/issues/7203#issuecomment-1144257312\n    // https://chromium-review.googlesource.com/c/chromium/src/+/3698402\n    error_test(\n      \"@container none (width < 100vw) {}\",\n      ParserError::UnexpectedToken(crate::properties::custom::Token::Ident(\"none\".into())),\n    );\n\n    error_test(\n      \"@container and (width < 100vw) {}\",\n      ParserError::UnexpectedToken(crate::properties::custom::Token::Ident(\"and\".into())),\n    );\n\n    error_test(\n      \"@container or (width < 100vw) {}\",\n      ParserError::UnexpectedToken(crate::properties::custom::Token::Ident(\"or\".into())),\n    );\n\n    // Disallow CSS wide keywords as a `<container-name>`\n    error_test(\n      \"@container revert-layer (width < 100vw) {}\",\n      ParserError::UnexpectedToken(crate::properties::custom::Token::Ident(\"revert-layer\".into())),\n    );\n\n    error_test(\n      \"@container initial (width < 100vw) {}\",\n      ParserError::UnexpectedToken(crate::properties::custom::Token::Ident(\"initial\".into())),\n    );\n\n    // <ident> contains spaces\n    // https://github.com/web-platform-tests/wpt/blob/39f0da08fbbe33d0582a35749b6dbf8bd067a52d/css/css-contain/container-queries/at-container-parsing.html#L160-L178\n    error_test(\n      \"@container foo bar (width < 100vw) {}\",\n      ParserError::UnexpectedToken(crate::properties::custom::Token::Ident(\"bar\".into())),\n    );\n\n    error_test(\"@container (inline-size <= foo) {}\", ParserError::InvalidMediaQuery);\n    error_test(\"@container (orientation <= 10px) {}\", ParserError::InvalidMediaQuery);\n\n    error_test(\n      \"@container style(style(--foo: bar)) {}\",\n      ParserError::UnexpectedToken(crate::properties::custom::Token::Function(\"style\".into())),\n    );\n    error_test(\n      \"@container scroll-state(scroll-state(scrollable: top)) {}\",\n      ParserError::InvalidMediaQuery,\n    );\n    error_test(\n      \"@container unknown(foo) {}\",\n      ParserError::UnexpectedToken(crate::properties::custom::Token::Function(\"unknown\".into())),\n    );\n\n    // empty container (no name and no condition) should error\n    error_test(\"@container {}\", ParserError::EndOfInput);\n\n    // empty brackets should return a clearer error message\n    error_test(\"@container () {}\", ParserError::EmptyBracketInCondition);\n\n    // invalid condition after a name should error\n    error_test(\"@container foo () {}\", ParserError::EmptyBracketInCondition);\n    error_test(\n      \"@container foo bar {}\",\n      ParserError::UnexpectedToken(crate::properties::custom::Token::Ident(\"bar\".into())),\n    );\n\n    error_recovery_test(\"@container unknown(foo) {}\");\n  }\n\n  #[test]\n  fn test_css_modules_value_rule() {\n    css_modules_error_test(\n      \"@value compact: (max-width: 37.4375em);\",\n      ParserError::DeprecatedCssModulesValueRule,\n    );\n  }\n\n  #[test]\n  fn test_unknown_at_rules() {\n    minify_test(\"@foo;\", \"@foo;\");\n    minify_test(\"@foo bar;\", \"@foo bar;\");\n    minify_test(\"@foo (bar: baz);\", \"@foo (bar: baz);\");\n    test(\n      r#\"@foo test {\n      div {\n        color: red;\n      }\n    }\"#,\n      indoc! { r#\"@foo test {\n      div {\n            color: red;\n          }\n    }\n    \"#},\n    );\n    minify_test(\n      r#\"@foo test {\n      div {\n        color: red;\n      }\n    }\"#,\n      \"@foo test{div { color: red; }}\",\n    );\n    minify_test(\n      r#\"@foo test {\n        foo: bar;\n      }\"#,\n      \"@foo test{foo: bar;}\",\n    );\n    test(\n      r#\"@foo {\n        foo: bar;\n      }\"#,\n      indoc! {r#\"\n      @foo {\n        foo: bar;\n      }\n      \"#},\n    );\n    minify_test(\n      r#\"@foo {\n        foo: bar;\n      }\"#,\n      \"@foo{foo: bar;}\",\n    );\n  }\n\n  #[test]\n  fn test_resolution() {\n    prefix_test(\n      r#\"\n      @media (resolution: 1dppx) {\n        body {\n          background: red;\n        }\n      }\n      \"#,\n      indoc! { r#\"\n      @media (resolution: 1dppx) {\n        body {\n          background: red;\n        }\n      }\n      \"#},\n      Browsers {\n        chrome: Some(50 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      @media (resolution: 1dppx) {\n        body {\n          background: red;\n        }\n      }\n      \"#,\n      indoc! { r#\"\n      @media (resolution: 1x) {\n        body {\n          background: red;\n        }\n      }\n      \"#},\n      Browsers {\n        chrome: Some(95 << 16),\n        ..Browsers::default()\n      },\n    );\n  }\n\n  #[test]\n  fn test_environment() {\n    minify_test(\n      r#\"\n      @media (max-width: env(--branding-small)) {\n        body {\n          padding: env(--branding-padding);\n        }\n      }\n    \"#,\n      \"@media (width<=env(--branding-small)){body{padding:env(--branding-padding)}}\",\n    );\n\n    minify_test(\n      r#\"\n      @media (max-width: env(--branding-small 1)) {\n        body {\n          padding: env(--branding-padding 2);\n        }\n      }\n    \"#,\n      \"@media (width<=env(--branding-small 1)){body{padding:env(--branding-padding 2)}}\",\n    );\n\n    minify_test(\n      r#\"\n      @media (max-width: env(--branding-small 1, 20px)) {\n        body {\n          padding: env(--branding-padding 2, 20px);\n        }\n      }\n    \"#,\n      \"@media (width<=env(--branding-small 1,20px)){body{padding:env(--branding-padding 2,20px)}}\",\n    );\n\n    minify_test(\n      r#\"\n      @media (max-width: env(safe-area-inset-top)) {\n        body {\n          padding: env(safe-area-inset-top);\n        }\n      }\n    \"#,\n      \"@media (width<=env(safe-area-inset-top)){body{padding:env(safe-area-inset-top)}}\",\n    );\n\n    minify_test(\n      r#\"\n      @media (max-width: env(unknown)) {\n        body {\n          padding: env(unknown);\n        }\n      }\n    \"#,\n      \"@media (width<=env(unknown)){body{padding:env(unknown)}}\",\n    );\n\n    prefix_test(\n      r#\"\n      .foo {\n        color: env(--brand-color, color(display-p3 0 1 0));\n      }\n    \"#,\n      indoc! {r#\"\n      .foo {\n        color: env(--brand-color, #00f942);\n      }\n\n      @supports (color: color(display-p3 0 0 0)) {\n        .foo {\n          color: env(--brand-color, color(display-p3 0 1 0));\n        }\n      }\n    \"#},\n      Browsers {\n        safari: Some(15 << 16),\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      @supports (color: color(display-p3 0 0 0)) {\n        .foo {\n          color: env(--brand-color, color(display-p3 0 1 0));\n        }\n      }\n    \"#,\n      indoc! {r#\"\n      @supports (color: color(display-p3 0 0 0)) {\n        .foo {\n          color: env(--brand-color, color(display-p3 0 1 0));\n        }\n      }\n    \"#},\n      Browsers {\n        safari: Some(15 << 16),\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    css_modules_test(\n      r#\"\n      @media (max-width: env(--branding-small)) {\n        .foo {\n          color: env(--brand-color);\n        }\n      }\n    \"#,\n      indoc! {r#\"\n      @media (width <= env(--EgL3uq_branding-small)) {\n        .EgL3uq_foo {\n          color: env(--EgL3uq_brand-color);\n        }\n      }\n    \"#},\n      map! {\n        \"foo\" => \"EgL3uq_foo\",\n        \"--brand-color\" => \"--EgL3uq_brand-color\" referenced: true,\n        \"--branding-small\" => \"--EgL3uq_branding-small\" referenced: true\n      },\n      HashMap::new(),\n      crate::css_modules::Config {\n        dashed_idents: true,\n        ..Default::default()\n      },\n      false,\n    );\n  }\n\n  #[test]\n  fn test_license_comments() {\n    minify_test(\n      r#\"\n      /*! Copyright 2023 Someone awesome */\n      /* Some other comment */\n      .foo {\n        color: red;\n      }\n    \"#,\n      indoc! {r#\"\n      /*! Copyright 2023 Someone awesome */\n      .foo{color:red}\"#},\n    );\n\n    minify_test(\n      r#\"\n      /*! Copyright 2023 Someone awesome */\n      /*! Copyright 2023 Someone else */\n      .foo {\n        color: red;\n      }\n    \"#,\n      indoc! {r#\"\n      /*! Copyright 2023 Someone awesome */\n      /*! Copyright 2023 Someone else */\n      .foo{color:red}\"#},\n    );\n  }\n\n  #[test]\n  fn test_starting_style() {\n    minify_test(\n      r#\"\n      @starting-style {\n        h1 {\n          background: yellow;\n        }\n      }\n      \"#,\n      \"@starting-style{h1{background:#ff0}}\",\n    );\n    minify_test(\"@starting-style {}\", \"\");\n\n    nesting_test(\n      r#\"\n      h1 {\n        background: red;\n        @starting-style {\n          background: yellow;\n        }\n      }\n      \"#,\n      indoc! {r#\"\n      h1 {\n        background: red;\n      }\n\n      @starting-style {\n        h1 {\n          background: #ff0;\n        }\n      }\n      \"#},\n    );\n  }\n\n  #[test]\n  fn test_color_scheme() {\n    minify_test(\".foo { color-scheme: normal; }\", \".foo{color-scheme:normal}\");\n    minify_test(\".foo { color-scheme: light; }\", \".foo{color-scheme:light}\");\n    minify_test(\".foo { color-scheme: dark; }\", \".foo{color-scheme:dark}\");\n    minify_test(\".foo { color-scheme: light dark; }\", \".foo{color-scheme:light dark}\");\n    minify_test(\".foo { color-scheme: dark light; }\", \".foo{color-scheme:light dark}\");\n    minify_test(\".foo { color-scheme: only light; }\", \".foo{color-scheme:light only}\");\n    minify_test(\".foo { color-scheme: only dark; }\", \".foo{color-scheme:dark only}\");\n    minify_test(\".foo { color-scheme: inherit; }\", \".foo{color-scheme:inherit}\");\n    minify_test(\":root { color-scheme: unset; }\", \":root{color-scheme:unset}\");\n    minify_test(\".foo { color-scheme: unknow; }\", \".foo{color-scheme:unknow}\");\n    minify_test(\".foo { color-scheme: only; }\", \".foo{color-scheme:only}\");\n    minify_test(\".foo { color-scheme: dark foo; }\", \".foo{color-scheme:dark foo}\");\n    minify_test(\".foo { color-scheme: normal dark; }\", \".foo{color-scheme:normal dark}\");\n    minify_test(\n      \".foo { color-scheme: dark light only; }\",\n      \".foo{color-scheme:light dark only}\",\n    );\n    minify_test(\".foo { color-scheme: foo bar light; }\", \".foo{color-scheme:foo bar light}\");\n    minify_test(\n      \".foo { color-scheme: only foo dark bar; }\",\n      \".foo{color-scheme:only foo dark bar}\",\n    );\n    prefix_test(\n      \".foo { color-scheme: dark; }\",\n      indoc! { r#\"\n      .foo {\n        --lightningcss-light: ;\n        --lightningcss-dark: initial;\n        color-scheme: dark;\n      }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      \".foo { color-scheme: light; }\",\n      indoc! { r#\"\n      .foo {\n        --lightningcss-light: initial;\n        --lightningcss-dark: ;\n        color-scheme: light;\n      }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      \".foo { color-scheme: light dark; }\",\n      indoc! { r#\"\n      .foo {\n        --lightningcss-light: initial;\n        --lightningcss-dark: ;\n        color-scheme: light dark;\n      }\n\n      @media (prefers-color-scheme: dark) {\n        .foo {\n          --lightningcss-light: ;\n          --lightningcss-dark: initial;\n        }\n      }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      \".foo { color-scheme: light dark; }\",\n      indoc! { r#\"\n      .foo {\n        color-scheme: light dark;\n      }\n      \"#},\n      Browsers {\n        firefox: Some(120 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    minify_test(\n      \".foo { color: light-dark(yellow, red); }\",\n      \".foo{color:light-dark(#ff0,red)}\",\n    );\n    minify_test(\n      \".foo { color: light-dark(light-dark(yellow, red), light-dark(yellow, red)); }\",\n      \".foo{color:light-dark(#ff0,red)}\",\n    );\n    minify_test(\n      \".foo { color: light-dark(rgb(0, 0, 255), hsl(120deg, 50%, 50%)); }\",\n      \".foo{color:light-dark(#00f,#40bf40)}\",\n    );\n    prefix_test(\n      \".foo { color: light-dark(oklch(40% 0.1268735435 34.568626), oklab(59.686% 0.1009 0.1192)); }\",\n      indoc! { r#\"\n      .foo {\n        color: var(--lightningcss-light, #7e250f) var(--lightningcss-dark, #c65d07);\n        color: var(--lightningcss-light, lab(29.2661% 38.2437 35.3889)) var(--lightningcss-dark, lab(52.2319% 40.1449 59.9171));\n      }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      \".foo { color: light-dark(oklch(40% 0.1268735435 34.568626), oklab(59.686% 0.1009 0.1192)); }\",\n      indoc! { r#\"\n      .foo {\n        color: light-dark(oklch(40% .126874 34.5686), oklab(59.686% .1009 .1192));\n      }\n      \"#},\n      Browsers {\n        firefox: Some(120 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        box-shadow:\n            oklch(100% 0 0deg / 50%) 0 0.63rem 0.94rem -0.19rem,\n            currentColor 0 0.44rem 0.8rem -0.58rem;\n      }\n    \"#,\n      indoc! { r#\"\n      .foo {\n        box-shadow: 0 .63rem .94rem -.19rem #ffffff80, 0 .44rem .8rem -.58rem;\n        box-shadow: 0 .63rem .94rem -.19rem lab(100% 0 0 / .5), 0 .44rem .8rem -.58rem;\n      }\n      \"#},\n      Browsers {\n        chrome: Some(95 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      r#\"\n      .foo {\n        box-shadow:\n            oklch(100% 0 0deg / 50%) 0 0.63rem 0.94rem -0.19rem,\n            currentColor 0 0.44rem 0.8rem -0.58rem;\n      }\n    \"#,\n      indoc! { r#\"\n      .foo {\n        box-shadow: 0 .63rem .94rem -.19rem color(display-p3 1 1 1 / .5), 0 .44rem .8rem -.58rem;\n        box-shadow: 0 .63rem .94rem -.19rem lab(100% 0 0 / .5), 0 .44rem .8rem -.58rem;\n      }\n      \"#},\n      Browsers {\n        safari: Some(14 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      \".foo { color: light-dark(var(--light), var(--dark)); }\",\n      indoc! { r#\"\n      .foo {\n        color: var(--lightningcss-light, var(--light)) var(--lightningcss-dark, var(--dark));\n      }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      \".foo { color: rgb(from light-dark(yellow, red) r g b / 10%); }\",\n      indoc! { r#\"\n      .foo {\n        color: var(--lightningcss-light, #ffff001a) var(--lightningcss-dark, #ff00001a);\n      }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      \".foo { color: rgb(from light-dark(yellow, red) r g b / var(--alpha)); }\",\n      indoc! { r#\"\n      .foo {\n        color: var(--lightningcss-light, rgb(255 255 0 / var(--alpha))) var(--lightningcss-dark, rgb(255 0 0 / var(--alpha)));\n      }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      \".foo { color: color(from light-dark(yellow, red) srgb r g b / 10%); }\",\n      indoc! { r#\"\n      .foo {\n        color: var(--lightningcss-light, #ffff001a) var(--lightningcss-dark, #ff00001a);\n        color: var(--lightningcss-light, color(srgb 1 1 0 / .1)) var(--lightningcss-dark, color(srgb 1 0 0 / .1));\n      }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      \".foo { color: color-mix(in srgb, light-dark(yellow, red), light-dark(red, pink)); }\",\n      indoc! { r#\"\n      .foo {\n        color: var(--lightningcss-light, #ff8000) var(--lightningcss-dark, #ff6066);\n      }\n      \"#},\n      Browsers {\n        chrome: Some(90 << 16),\n        ..Browsers::default()\n      },\n    );\n    nesting_test_with_targets(\n      r#\"\n        .foo { color-scheme: light; }\n        .bar { color: light-dark(red, green); }\n      \"#,\n      indoc! {r#\"\n        .foo {\n          color-scheme: light;\n        }\n\n        .bar {\n          color: light-dark(red, green);\n        }\n      \"#},\n      Targets {\n        browsers: Some(Browsers {\n          safari: Some(13 << 16),\n          ..Browsers::default()\n        }),\n        include: Features::empty(),\n        exclude: Features::LightDark,\n      },\n    );\n  }\n\n  #[test]\n  fn test_print_color_adjust() {\n    prefix_test(\n      \".foo { print-color-adjust: exact; }\",\n      indoc! { r#\"\n      .foo {\n        -webkit-print-color-adjust: exact;\n        print-color-adjust: exact;\n      }\n      \"#},\n      Browsers {\n        chrome: Some(135 << 16),\n        ..Browsers::default()\n      },\n    );\n    prefix_test(\n      \".foo { print-color-adjust: exact; }\",\n      indoc! { r#\"\n      .foo {\n        print-color-adjust: exact;\n      }\n      \"#},\n      Browsers {\n        chrome: Some(137 << 16),\n        ..Browsers::default()\n      },\n    );\n  }\n\n  #[test]\n  fn test_all() {\n    minify_test(\".foo { all: initial; all: initial }\", \".foo{all:initial}\");\n    minify_test(\".foo { all: initial; all: revert }\", \".foo{all:revert}\");\n    minify_test(\".foo { background: red; all: revert-layer }\", \".foo{all:revert-layer}\");\n    minify_test(\n      \".foo { background: red; all: revert-layer; background: green }\",\n      \".foo{all:revert-layer;background:green}\",\n    );\n    minify_test(\n      \".foo { --test: red; all: revert-layer }\",\n      \".foo{--test:red;all:revert-layer}\",\n    );\n    minify_test(\n      \".foo { unicode-bidi: embed; all: revert-layer }\",\n      \".foo{all:revert-layer;unicode-bidi:embed}\",\n    );\n    minify_test(\n      \".foo { direction: rtl; all: revert-layer }\",\n      \".foo{all:revert-layer;direction:rtl}\",\n    );\n    minify_test(\n      \".foo { direction: rtl; all: revert-layer; direction: ltr }\",\n      \".foo{all:revert-layer;direction:ltr}\",\n    );\n    minify_test(\".foo { background: var(--foo); all: unset; }\", \".foo{all:unset}\");\n    minify_test(\n      \".foo { all: unset; background: var(--foo); }\",\n      \".foo{all:unset;background:var(--foo)}\",\n    );\n    minify_test(\n      \".foo {--bar:currentcolor; --foo:1.1em; all:unset}\",\n      \".foo{--bar:currentcolor;--foo:1.1em;all:unset}\",\n    );\n  }\n\n  #[test]\n  fn test_view_transition() {\n    minify_test(\n      \"@view-transition { navigation: auto }\",\n      \"@view-transition{navigation:auto}\",\n    );\n    minify_test(\n      \"@view-transition { navigation: auto; types: none; }\",\n      \"@view-transition{navigation:auto;types:none}\",\n    );\n    minify_test(\n      \"@view-transition { navigation: auto; types: foo bar; }\",\n      \"@view-transition{navigation:auto;types:foo bar}\",\n    );\n    minify_test(\n      \"@layer { @view-transition { navigation: auto; types: foo bar; } }\",\n      \"@layer{@view-transition{navigation:auto;types:foo bar}}\",\n    );\n  }\n\n  #[test]\n  fn test_skip_generating_unnecessary_fallbacks() {\n    prefix_test(\n      r#\"\n      @supports (color: lab(0% 0 0)) and (color: color(display-p3 0 0 0)) {\n        .foo {\n          color: lab(40% 56.6 39);\n        }\n\n        .bar {\n          color: color(display-p3 .643308 .192455 .167712);\n        }\n      }\n      \"#,\n      indoc! {r#\"\n      @supports (color: lab(0% 0 0)) and (color: color(display-p3 0 0 0)) {\n        .foo {\n          color: lab(40% 56.6 39);\n        }\n\n        .bar {\n          color: color(display-p3 .643308 .192455 .167712);\n        }\n      }\n      \"#},\n      Browsers {\n        chrome: Some(4 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      @supports (color: lab(40% 56.6 39)) {\n        .foo {\n          color: lab(40% 56.6 39);\n        }\n      }\n      \"#,\n      indoc! {r#\"\n      @supports (color: lab(40% 56.6 39)) {\n        .foo {\n          color: lab(40% 56.6 39);\n        }\n      }\n      \"#},\n      Browsers {\n        chrome: Some(4 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      @supports (background-color: lab(40% 56.6 39)) {\n        .foo {\n          background-color: lab(40% 56.6 39);\n        }\n      }\n      \"#,\n      indoc! {r#\"\n      @supports (background-color: lab(40% 56.6 39)) {\n        .foo {\n          background-color: lab(40% 56.6 39);\n        }\n      }\n      \"#},\n      Browsers {\n        chrome: Some(4 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      @supports (color: light-dark(#f00, #00f)) {\n        .foo {\n          color: light-dark(#ff0, #0ff);\n        }\n      }\n      \"#,\n      indoc! {r#\"\n      @supports (color: light-dark(#f00, #00f)) {\n        .foo {\n          color: light-dark(#ff0, #0ff);\n        }\n      }\n      \"#},\n      Browsers {\n        chrome: Some(4 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    // NOTE: fallback for lab is not necessary\n    prefix_test(\n      r#\"\n      @supports (color: lab(0% 0 0)) and (not (color: color(display-p3 0 0 0))) {\n        .foo {\n          color: lab(40% 56.6 39);\n        }\n\n        .bar {\n          color: color(display-p3 .643308 .192455 .167712);\n        }\n      }\n      \"#,\n      indoc! {r#\"\n      @supports (color: lab(0% 0 0)) and (not (color: color(display-p3 0 0 0))) {\n        .foo {\n          color: #b32323;\n          color: lab(40% 56.6 39);\n        }\n\n        .bar {\n          color: #b32323;\n          color: color(display-p3 .643308 .192455 .167712);\n        }\n      }\n      \"#},\n      Browsers {\n        chrome: Some(4 << 16),\n        ..Browsers::default()\n      },\n    );\n\n    prefix_test(\n      r#\"\n      @supports (color: lab(0% 0 0)) or (color: color(display-p3 0 0 0)) {\n        .foo {\n          color: lab(40% 56.6 39);\n        }\n\n        .bar {\n          color: color(display-p3 .643308 .192455 .167712);\n        }\n      }\n      \"#,\n      indoc! {r#\"\n      @supports (color: lab(0% 0 0)) or (color: color(display-p3 0 0 0)) {\n        .foo {\n          color: #b32323;\n          color: lab(40% 56.6 39);\n        }\n\n        .bar {\n          color: #b32323;\n          color: color(display-p3 .643308 .192455 .167712);\n        }\n      }\n      \"#},\n      Browsers {\n        chrome: Some(4 << 16),\n        ..Browsers::default()\n      },\n    );\n  }\n}\n"
  },
  {
    "path": "src/logical.rs",
    "content": "#[derive(Debug, PartialEq)]\npub enum PropertyCategory {\n  Logical,\n  Physical,\n}\n\nimpl Default for PropertyCategory {\n  fn default() -> PropertyCategory {\n    PropertyCategory::Physical\n  }\n}\n\n#[derive(PartialEq)]\npub enum LogicalGroup {\n  BorderColor,\n  BorderStyle,\n  BorderWidth,\n  BorderRadius,\n  Margin,\n  ScrollMargin,\n  Padding,\n  ScrollPadding,\n  Inset,\n  Size,\n  MinSize,\n  MaxSize,\n}\n"
  },
  {
    "path": "src/macros.rs",
    "content": "macro_rules! enum_property {\n  (\n    $(#[$outer:meta])*\n    $vis:vis enum $name:ident {\n      $(\n        $(#[$meta: meta])*\n        $x: ident,\n      )+\n    }\n  ) => {\n    #[derive(Debug, Clone, Copy, PartialEq, Parse, ToCss)]\n    #[cfg_attr(feature = \"visitor\", derive(Visit))]\n    #[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize), serde(rename_all = \"kebab-case\"))]\n    #[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n    #[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n    $(#[$outer])*\n    $vis enum $name {\n      $(\n        $(#[$meta])*\n        $x,\n      )+\n    }\n\n    impl $name {\n      /// Returns a string representation of the value.\n      pub fn as_str(&self) -> &str {\n        use $name::*;\n        match self {\n          $(\n            $x => const_str::convert_ascii_case!(kebab, stringify!($x)),\n          )+\n        }\n      }\n    }\n  };\n  (\n    $(#[$outer:meta])*\n    $vis:vis enum $name:ident {\n      $(\n        $(#[$meta: meta])*\n        $str: literal: $id: ident,\n      )+\n    }\n  ) => {\n    $(#[$outer])*\n    #[derive(Debug, Clone, Copy, PartialEq)] #[cfg_attr(feature = \"visitor\", derive(Visit))]\n    #[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n    #[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n    #[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n    $vis enum $name {\n      $(\n        $(#[$meta])*\n        #[cfg_attr(feature = \"serde\", serde(rename = $str))]\n        $id,\n      )+\n    }\n\n    impl<'i> Parse<'i> for $name {\n      fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n        let location = input.current_source_location();\n        let ident = input.expect_ident()?;\n        cssparser::match_ignore_ascii_case! { &*ident,\n          $(\n            $str => Ok($name::$id),\n          )+\n          _ => Err(location.new_unexpected_token_error(\n            cssparser::Token::Ident(ident.clone())\n          )),\n        }\n      }\n    }\n\n    impl $name {\n      /// Returns a string representation of the value.\n      pub fn as_str(&self) -> &str {\n        use $name::*;\n        match self {\n          $(\n            $id => $str,\n          )+\n        }\n      }\n    }\n\n    impl ToCss for $name {\n      fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError> where W: std::fmt::Write {\n        dest.write_str(self.as_str())\n      }\n    }\n  };\n}\n\npub(crate) use enum_property;\n\nmacro_rules! shorthand_property {\n  (\n    $(#[$outer:meta])*\n    $vis:vis struct $name: ident$(<$l: lifetime>)? {\n      $(#[$first_meta: meta])*\n      $first_key: ident: $first_prop: ident($first_type: ty $(, $first_vp: ty)?),\n      $(\n        $(#[$meta: meta])*\n        $key: ident: $prop: ident($type: ty $(, $vp: ty)?),\n      )*\n    }\n  ) => {\n    define_shorthand! {\n      $(#[$outer])*\n      pub struct $name$(<$l>)? {\n        $(#[$first_meta])*\n        $first_key: $first_prop($first_type $($first_vp)?),\n        $(\n          $(#[$meta])*\n          $key: $prop($type $($vp)?),\n        )*\n      }\n    }\n\n    impl<'i> Parse<'i> for $name$(<$l>)? {\n      fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n        let mut $first_key = None;\n        $(\n          let mut $key = None;\n        )*\n\n        macro_rules! parse_one {\n          ($k: ident, $t: ty) => {\n            if $k.is_none() {\n              if let Ok(val) = input.try_parse(<$t>::parse) {\n                $k = Some(val);\n                continue\n              }\n            }\n          };\n        }\n\n        loop {\n          parse_one!($first_key, $first_type);\n          $(\n            parse_one!($key, $type);\n          )*\n          break\n        }\n\n        Ok($name {\n          $first_key: $first_key.unwrap_or_default(),\n          $(\n            $key: $key.unwrap_or_default(),\n          )*\n        })\n      }\n    }\n\n    impl$(<$l>)? ToCss for $name$(<$l>)? {\n      fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError> where W: std::fmt::Write {\n        let mut needs_space = false;\n        macro_rules! print_one {\n          ($k: ident, $t: ty) => {\n            if self.$k != <$t>::default() {\n              if needs_space {\n                dest.write_char(' ')?;\n              }\n              self.$k.to_css(dest)?;\n              needs_space = true;\n            }\n          };\n        }\n\n        print_one!($first_key, $first_type);\n        $(\n          print_one!($key, $type);\n        )*\n        if !needs_space {\n          self.$first_key.to_css(dest)?;\n        }\n        Ok(())\n      }\n    }\n  };\n}\n\npub(crate) use shorthand_property;\n\nmacro_rules! shorthand_property_bitflags {\n  ($name:ident, $first:ident, $($rest:ident),*) => {\n    crate::macros::shorthand_property_bitflags!($name, [$first,$($rest),+] $($rest),+ ; 0; $first = 0);\n  };\n  ($name:ident, [$($all:ident),*] $cur:ident, $($rest:ident),* ; $last_index: expr ; $($var:ident = $index:expr)+) => {\n    crate::macros::shorthand_property_bitflags!($name, [$($all),*] $($rest),* ; $last_index + 1; $($var = $index)* $cur = $last_index + 1);\n  };\n  ($name:ident, [$($all:ident),*] $cur:ident; $last_index:expr ; $($var:ident = $index:expr)+) => {\n    pastey::paste! {\n      crate::macros::property_bitflags! {\n        #[derive(Default, Debug)]\n        struct [<$name Property>]: u8 {\n          $(const $var = 1 << $index);*;\n          const $cur = 1 << ($last_index + 1);\n          const $name = $(Self::$all.bits())|*;\n        }\n      }\n    }\n  };\n}\n\npub(crate) use shorthand_property_bitflags;\n\nmacro_rules! shorthand_handler {\n  (\n    $name: ident -> $shorthand: ident$(<$l: lifetime>)? $(fallbacks: $shorthand_fallback: literal)?\n    { $( $key: ident: $prop: ident($type: ty $(, fallback: $fallback: literal)? $(, image: $image: literal)?), )+ }\n  ) => {\n    crate::macros::shorthand_property_bitflags!($shorthand, $($prop),*);\n\n    #[derive(Default)]\n    pub(crate) struct $name$(<$l>)? {\n      $(\n        pub $key: Option<$type>,\n      )*\n      flushed_properties: pastey::paste!([<$shorthand Property>]),\n      has_any: bool\n    }\n\n    impl<'i> PropertyHandler<'i> for $name$(<$l>)? {\n      fn handle_property(&mut self, property: &Property<'i>, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) -> bool {\n        use crate::traits::IsCompatible;\n\n        match property {\n          $(\n            Property::$prop(val) => {\n              if self.$key.is_some() && matches!(context.targets.browsers, Some(targets) if !val.is_compatible(targets)) {\n                self.flush(dest, context);\n              }\n              self.$key = Some(val.clone());\n              self.has_any = true;\n            },\n          )+\n          Property::$shorthand(val) => {\n            $(\n              if self.$key.is_some() && matches!(context.targets.browsers, Some(targets) if !val.$key.is_compatible(targets)) {\n                self.flush(dest, context);\n              }\n            )+\n            $(\n              self.$key = Some(val.$key.clone());\n            )+\n            self.has_any = true;\n          }\n          Property::Unparsed(val) if matches!(val.property_id, $( PropertyId::$prop | )+ PropertyId::$shorthand) => {\n            self.flush(dest, context);\n\n            let mut unparsed = val.clone();\n            context.add_unparsed_fallbacks(&mut unparsed);\n            pastey::paste! {\n              self.flushed_properties.insert([<$shorthand Property>]::try_from(&unparsed.property_id).unwrap());\n            };\n            dest.push(Property::Unparsed(unparsed));\n          }\n          _ => return false\n        }\n\n        true\n      }\n\n      fn finalize(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {\n        self.flush(dest, context);\n        self.flushed_properties = pastey::paste!([<$shorthand Property>]::empty());\n      }\n    }\n\n    impl<'i> $name$(<$l>)? {\n      #[allow(unused_variables)]\n      fn flush(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {\n        if !self.has_any {\n          return\n        }\n\n        self.has_any = false;\n\n        $(\n          let $key = std::mem::take(&mut self.$key);\n        )+\n\n        if $( $key.is_some() && )* true {\n          #[allow(unused_mut)]\n          let mut shorthand = $shorthand {\n            $(\n              $key: $key.unwrap(),\n            )+\n          };\n\n          $(\n            if $shorthand_fallback && !self.flushed_properties.intersects(pastey::paste!([<$shorthand Property>]::$shorthand)) {\n              let fallbacks = shorthand.get_fallbacks(context.targets);\n              for fallback in fallbacks {\n                dest.push(Property::$shorthand(fallback));\n              }\n            }\n          )?\n\n          dest.push(Property::$shorthand(shorthand));\n          pastey::paste! {\n            self.flushed_properties.insert([<$shorthand Property>]::$shorthand);\n          };\n        } else {\n          $(\n            #[allow(unused_mut)]\n            if let Some(mut val) = $key {\n              $(\n                if $fallback && !self.flushed_properties.intersects(pastey::paste!([<$shorthand Property>]::$prop)) {\n                  let fallbacks = val.get_fallbacks(context.targets);\n                  for fallback in fallbacks {\n                    dest.push(Property::$prop(fallback));\n                  }\n                }\n              )?\n\n              dest.push(Property::$prop(val));\n              pastey::paste! {\n                self.flushed_properties.insert([<$shorthand Property>]::$prop);\n              };\n            }\n          )+\n        }\n      }\n    }\n  };\n}\n\npub(crate) use shorthand_handler;\n\nmacro_rules! define_shorthand {\n  (\n    $(#[$outer:meta])*\n    $vis:vis struct $name: ident$(<$l: lifetime>)?$(($prefix: ty))? {\n      $(\n        $(#[$meta: meta])*\n        $key: ident: $prop: ident($type: ty $(, $vp: ty)?),\n      )+\n    }\n  ) => {\n    $(#[$outer])*\n    #[derive(Debug, Clone, PartialEq)]\n    #[cfg_attr(feature = \"visitor\", derive(Visit))]\n    #[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize), serde(rename_all = \"camelCase\"))]\n    #[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n    #[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n    pub struct $name$(<$l>)? {\n      $(\n        $(#[$meta])*\n        pub $key: $type,\n      )+\n    }\n\n    crate::macros::impl_shorthand! {\n      $name($name$(<$l>)? $(, $prefix)?) {\n        $(\n          $key: [ $prop$(($vp))?, ],\n        )+\n      }\n    }\n  };\n}\n\npub(crate) use define_shorthand;\n\nmacro_rules! impl_shorthand {\n  (\n    $name: ident($t: ty $(, $prefix: ty)?) {\n      $(\n        $key: ident: [ $( $prop: ident$(($vp: ty))? $(,)?)+ ],\n      )+\n    }\n\n    $(\n      fn is_valid($v: ident) {\n        $($body: tt)+\n      }\n    )?\n  ) => {\n    #[allow(unused_macros)]\n    macro_rules! vp_name {\n      ($x: ty, $n: ident) => {\n        $n\n      };\n      ($x: ty, $n: expr) => {\n        $n\n      };\n    }\n\n    impl<'i> Shorthand<'i> for $t {\n      #[allow(unused_variables)]\n      fn from_longhands(decls: &DeclarationBlock<'i>, vendor_prefix: crate::vendor_prefix::VendorPrefix) -> Option<(Self, bool)> {\n        use pastey::paste;\n\n        $(\n          $(\n            paste! {\n              let mut [<$prop:snake _value>] = None;\n            }\n          )+\n        )+\n\n        let mut count = 0;\n        let mut important_count = 0;\n        for (property, important) in decls.iter() {\n          match property {\n            $(\n              $(\n                Property::$prop(val $(, vp_name!($vp, p))?) => {\n                  $(\n                    if *vp_name!($vp, p) != vendor_prefix {\n                      return None\n                    }\n                  )?\n\n                  paste! {\n                    [<$prop:snake _value>] = Some(val.clone());\n                  }\n                  count += 1;\n                  if important {\n                    important_count += 1;\n                  }\n                }\n              )+\n            )+\n            Property::$name(val $(, vp_name!($prefix, p))?) => {\n              $(\n                if *vp_name!($prefix, p) != vendor_prefix {\n                  return None\n                }\n              )?\n\n              $(\n                $(\n                  paste! {\n                    [<$prop:snake _value>] = Some(val.$key.clone());\n                  }\n                  count += 1;\n                  if important {\n                    important_count += 1;\n                  }\n                )+\n              )+\n            }\n            _ => {\n              $(\n                $(\n                  if let Some(Property::$prop(longhand $(, vp_name!($vp, _p))?)) = property.longhand(&PropertyId::$prop$((vp_name!($vp, vendor_prefix)))?) {\n                    paste! {\n                      [<$prop:snake _value>] = Some(longhand);\n                    }\n                    count += 1;\n                    if important {\n                      important_count += 1;\n                    }\n                  }\n                )+\n              )+\n            }\n          }\n        }\n\n        // !important flags must match to produce a shorthand.\n        if important_count > 0 && important_count != count {\n          return None\n        }\n\n        if $($(paste! { [<$prop:snake _value>].is_some() } &&)+)+ true {\n          // All properties in the group must have a matching value to produce a shorthand.\n          $(\n            let mut $key = None;\n            $(\n              if $key == None {\n                paste! {\n                  $key = [<$prop:snake _value>];\n                }\n              } else if paste! { $key != [<$prop:snake _value>] } {\n                return None\n              }\n            )+\n          )+\n\n          let value = $name {\n            $(\n              $key: $key.unwrap(),\n            )+\n          };\n\n          $(\n            #[inline]\n            fn is_valid($v: &$name) -> bool {\n              $($body)+\n            }\n\n            if !is_valid(&value) {\n              return None\n            }\n          )?\n\n          return Some((value, important_count > 0));\n        }\n\n        None\n      }\n\n      #[allow(unused_variables)]\n      fn longhands(vendor_prefix: crate::vendor_prefix::VendorPrefix) -> Vec<PropertyId<'static>> {\n        vec![$($(PropertyId::$prop$((vp_name!($vp, vendor_prefix)))?, )+)+]\n      }\n\n      fn longhand(&self, property_id: &PropertyId) -> Option<Property<'i>> {\n        match property_id {\n          $(\n            $(\n              PropertyId::$prop$((vp_name!($vp, p)))? => {\n                Some(Property::$prop(self.$key.clone() $(, *vp_name!($vp, p))?))\n              }\n            )+\n          )+\n          _ => None\n        }\n      }\n\n      fn set_longhand(&mut self, property: &Property<'i>) -> Result<(), ()> {\n        macro_rules! count {\n          ($p: ident) => {\n            1\n          }\n        }\n\n        $(\n          #[allow(non_upper_case_globals)]\n          const $key: u8 = 0 $( + count!($prop))+;\n        )+\n\n        match property {\n          $(\n            $(\n              Property::$prop(val $(, vp_name!($vp, _p))?) => {\n                // If more than one longhand maps to this key, bail.\n                if $key > 1 {\n                  return Err(())\n                }\n                self.$key = val.clone();\n                return Ok(())\n              }\n            )+\n          )+\n          _ => {}\n        }\n        Err(())\n      }\n    }\n  }\n}\n\npub(crate) use impl_shorthand;\n\nmacro_rules! define_list_shorthand {\n  (\n    $(#[$outer:meta])*\n    $vis:vis struct $name: ident$(<$l: lifetime>)?$(($prefix: ty))? {\n      $(\n        $(#[$meta: meta])*\n        $key: ident: $prop: ident($type: ty $(, $vp: ty)?),\n      )+\n    }\n  ) => {\n    $(#[$outer])*\n    #[derive(Debug, Clone, PartialEq)]\n    #[cfg_attr(feature = \"visitor\", derive(Visit))]\n    #[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize), serde(rename_all = \"camelCase\"))]\n    #[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n    #[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n    pub struct $name$(<$l>)? {\n      $(\n        $(#[$meta])*\n        pub $key: $type,\n      )+\n    }\n\n    #[allow(unused_macros)]\n    macro_rules! vp_name {\n      ($x: ty, $n: ident) => {\n        $n\n      };\n      ($x: ty, $n: expr) => {\n        $n\n      };\n    }\n\n    impl<'i> Shorthand<'i> for SmallVec<[$name$(<$l>)?; 1]> {\n      #[allow(unused_variables)]\n      fn from_longhands(decls: &DeclarationBlock<'i>, vendor_prefix: crate::vendor_prefix::VendorPrefix) -> Option<(Self, bool)> {\n        $(\n          let mut $key = None;\n        )+\n\n        let mut count = 0;\n        let mut important_count = 0;\n        let mut length = None;\n        for (property, important) in decls.iter() {\n          let mut len = 0;\n          match property {\n            $(\n              Property::$prop(val $(, vp_name!($vp, p))?) => {\n                $(\n                  if *vp_name!($vp, p) != vendor_prefix {\n                    return None\n                  }\n                )?\n\n                $key = Some(val.clone());\n                len = val.len();\n                count += 1;\n                if important {\n                  important_count += 1;\n                }\n              }\n            )+\n            Property::$name(val $(, vp_name!($prefix, p))?) => {\n              $(\n                if *vp_name!($prefix, p) != vendor_prefix {\n                  return None\n                }\n              )?\n              $(\n                $key = Some(val.iter().map(|b| b.$key.clone()).collect());\n              )+\n              len = val.len();\n              count += 1;\n              if important {\n                important_count += 1;\n              }\n            }\n            _ => {\n              $(\n                if let Some(Property::$prop(longhand $(, vp_name!($vp, _p))?)) = property.longhand(&PropertyId::$prop$((vp_name!($vp, vendor_prefix)))?) {\n                  len = longhand.len();\n                  $key = Some(longhand);\n                  count += 1;\n                  if important {\n                    important_count += 1;\n                  }\n                }\n              )+\n            }\n          }\n\n          // Lengths must be equal.\n          if length.is_none() {\n            length = Some(len);\n          } else if length.unwrap() != len {\n            return None\n          }\n        }\n\n        // !important flags must match to produce a shorthand.\n        if important_count > 0 && important_count != count {\n          return None\n        }\n\n        if $($key.is_some() &&)+ true {\n          let values = izip!(\n            $(\n              $key.unwrap().drain(..),\n            )+\n          ).map(|($($key,)+)| {\n            $name {\n              $(\n                $key,\n              )+\n            }\n          }).collect();\n          return Some((values, important_count > 0))\n        }\n\n        None\n      }\n\n      #[allow(unused_variables)]\n      fn longhands(vendor_prefix: crate::vendor_prefix::VendorPrefix) -> Vec<PropertyId<'static>> {\n        vec![$(PropertyId::$prop$((vp_name!($vp, vendor_prefix)))?, )+]\n      }\n\n      fn longhand(&self, property_id: &PropertyId) -> Option<Property<'i>> {\n        match property_id {\n          $(\n            PropertyId::$prop$((vp_name!($vp, p)))? => {\n              Some(Property::$prop(self.iter().map(|v| v.$key.clone()).collect() $(, *vp_name!($vp, p))?))\n            }\n          )+\n          _ => None\n        }\n      }\n\n      fn set_longhand(&mut self, property: &Property<'i>) -> Result<(), ()> {\n        match property {\n          $(\n            Property::$prop(val $(, vp_name!($vp, _p))?) => {\n              if val.len() != self.len() {\n                return Err(())\n              }\n\n              for (i, item) in self.iter_mut().enumerate() {\n                item.$key = val[i].clone();\n              }\n              return Ok(())\n            }\n          )+\n          _ => {}\n        }\n        Err(())\n      }\n    }\n  };\n}\n\npub(crate) use define_list_shorthand;\n\nmacro_rules! rect_shorthand {\n  (\n    $(#[$meta: meta])*\n    $vis:vis struct $name: ident<$t: ty> {\n      $top: ident,\n      $right: ident,\n      $bottom: ident,\n      $left: ident\n    }\n  ) => {\n    define_shorthand! {\n      $(#[$meta])*\n      pub struct $name {\n        /// The top value.\n        top: $top($t),\n        /// The right value.\n        right: $right($t),\n        /// The bottom value.\n        bottom: $bottom($t),\n        /// The left value.\n        left: $left($t),\n      }\n    }\n\n    impl<'i> Parse<'i> for $name {\n      fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n        let rect = Rect::parse(input)?;\n        Ok(Self {\n          top: rect.0,\n          right: rect.1,\n          bottom: rect.2,\n          left: rect.3,\n        })\n      }\n    }\n\n    impl ToCss for $name {\n      fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n      where\n        W: std::fmt::Write,\n      {\n        Rect::new(&self.top, &self.right, &self.bottom, &self.left).to_css(dest)\n      }\n    }\n  };\n}\n\npub(crate) use rect_shorthand;\n\nmacro_rules! size_shorthand {\n  (\n    $(#[$outer:meta])*\n    $vis:vis struct $name: ident<$t: ty> {\n      $(#[$a_meta: meta])*\n      $a_key: ident: $a_prop: ident,\n      $(#[$b_meta: meta])*\n      $b_key: ident: $b_prop: ident,\n    }\n  ) => {\n    define_shorthand! {\n      $(#[$outer])*\n      $vis struct $name {\n        $(#[$a_meta])*\n        $a_key: $a_prop($t),\n        $(#[$b_meta])*\n        $b_key: $b_prop($t),\n      }\n    }\n\n    impl<'i> Parse<'i> for $name {\n      fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n        let size = Size2D::parse(input)?;\n        Ok(Self {\n          $a_key: size.0,\n          $b_key: size.1,\n        })\n      }\n    }\n\n    impl ToCss for $name {\n      fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n      where\n        W: std::fmt::Write,\n      {\n        Size2D(&self.$a_key, &self.$b_key).to_css(dest)\n      }\n    }\n  };\n}\n\npub(crate) use size_shorthand;\n\nmacro_rules! property_bitflags {\n  (\n    $(#[$outer:meta])*\n    $vis:vis struct $BitFlags:ident: $T:ty {\n      $(\n        $(#[$inner:ident $($args:tt)*])*\n        const $Flag:ident $(($vp:ident))? = $value:expr;\n      )*\n    }\n  ) => {\n    bitflags::bitflags! {\n      $(#[$outer])*\n      $vis struct $BitFlags: $T {\n        $(\n          $(#[$inner $($args)*])*\n            const $Flag = $value;\n        )*\n      }\n    }\n\n    impl<'i> TryFrom<&PropertyId<'i>> for $BitFlags {\n      type Error = ();\n\n      fn try_from(value: &PropertyId<'i>) -> Result<$BitFlags, Self::Error> {\n        match value {\n          $(\n            PropertyId::$Flag $(($vp))? => Ok($BitFlags::$Flag),\n          )*\n          _ => Err(())\n        }\n      }\n    }\n  };\n}\n\npub(crate) use property_bitflags;\n"
  },
  {
    "path": "src/main.rs",
    "content": "use atty::Stream;\nuse clap::{ArgGroup, Parser};\nuse lightningcss::bundler::{Bundler, FileProvider};\nuse lightningcss::stylesheet::{MinifyOptions, ParserFlags, ParserOptions, PrinterOptions, StyleSheet};\nuse lightningcss::targets::Browsers;\nuse parcel_sourcemap::SourceMap;\nuse serde::Serialize;\nuse std::borrow::Cow;\nuse std::sync::{Arc, RwLock};\nuse std::{ffi, fs, io, path::Path};\n\n#[cfg(target_os = \"macos\")]\n#[global_allocator]\nstatic GLOBAL: jemallocator::Jemalloc = jemallocator::Jemalloc;\n\n#[derive(Parser, Debug)]\n#[clap(author, version, about, long_about = None)]\n#[clap(group(\n  ArgGroup::new(\"targets-resolution\")\n      .args(&[\"targets\", \"browserslist\"]),\n))]\nstruct CliArgs {\n  /// Target CSS file (default: stdin)\n  #[clap(value_parser)]\n  input_file: Vec<String>,\n  /// Destination file for the output\n  #[clap(short, long, group = \"output_file\", value_parser)]\n  output_file: Option<String>,\n  /// Destination directory to output into.\n  #[clap(short = 'd', long, group = \"output_file\", value_parser)]\n  output_dir: Option<String>,\n  /// Minify the output\n  #[clap(short, long, value_parser)]\n  minify: bool,\n  /// Enable parsing CSS nesting\n  // Now on by default, but left for backward compatibility.\n  #[clap(long, value_parser, hide = true)]\n  nesting: bool,\n  /// Enable parsing custom media queries\n  #[clap(long, value_parser)]\n  custom_media: bool,\n  /// Enable CSS modules in output.\n  /// If no filename is provided, <output_file>.json will be used.\n  /// If no --output-file is specified, code and exports will be printed to stdout as JSON.\n  #[clap(long, group = \"css_modules\", value_parser)]\n  css_modules: Option<Option<String>>,\n  #[clap(long, requires = \"css_modules\", value_parser)]\n  css_modules_pattern: Option<String>,\n  #[clap(long, requires = \"css_modules\", value_parser)]\n  css_modules_dashed_idents: bool,\n  /// Enable sourcemap, at <output_file>.map\n  #[clap(long, requires = \"output_file\", value_parser)]\n  sourcemap: bool,\n  #[clap(long, value_parser)]\n  bundle: bool,\n  #[clap(short, long, value_parser)]\n  targets: Vec<String>,\n  #[clap(long, value_parser)]\n  browserslist: bool,\n  #[clap(long, value_parser)]\n  error_recovery: bool,\n}\n\n#[derive(Serialize)]\n#[serde(rename_all = \"camelCase\")]\nstruct SourceMapJson<'a> {\n  version: u8,\n  mappings: String,\n  sources: &'a Vec<String>,\n  sources_content: &'a Vec<String>,\n  names: &'a Vec<String>,\n}\n\npub fn main() -> Result<(), std::io::Error> {\n  let cli_args = CliArgs::parse();\n  let project_root = std::env::current_dir()?;\n\n  // If we're given an input file, read from it and adjust its name.\n  //\n  // If we're not given an input file and stdin was redirected, read\n  // from it and create a fake name. Return an error if stdin was not\n  // redirected (otherwise the program will hang waiting for input).\n  //\n  let inputs = if !cli_args.input_file.is_empty() {\n    if cli_args.input_file.len() > 1 && cli_args.output_file.is_some() {\n      eprintln!(\"Cannot use the --output-file option with multiple inputs. Use --output-dir instead.\");\n      std::process::exit(1);\n    }\n\n    if cli_args.input_file.len() > 1 && cli_args.output_file.is_none() && cli_args.output_dir.is_none() {\n      eprintln!(\"Cannot output to stdout with multiple inputs. Use --output-dir instead.\");\n      std::process::exit(1);\n    }\n\n    cli_args\n      .input_file\n      .into_iter()\n      .map(|ref f| -> Result<_, std::io::Error> {\n        let absolute_path = fs::canonicalize(f)?;\n        let filename = pathdiff::diff_paths(absolute_path, &project_root).unwrap();\n        let filename = filename.to_string_lossy().into_owned();\n        let contents = fs::read_to_string(f)?;\n        Ok((filename, contents))\n      })\n      .collect::<Result<_, _>>()?\n  } else {\n    // Don't silently wait for input if stdin was not redirected.\n    if atty::is(Stream::Stdin) {\n      return Err(io::Error::new(\n        io::ErrorKind::Other,\n        \"Not reading from stdin as it was not redirected\",\n      ));\n    }\n    let filename = format!(\"stdin-{}\", std::process::id());\n    let contents = io::read_to_string(io::stdin())?;\n    vec![(filename, contents)]\n  };\n\n  let css_modules = if let Some(_) = cli_args.css_modules {\n    let pattern = if let Some(pattern) = cli_args.css_modules_pattern.as_ref() {\n      match lightningcss::css_modules::Pattern::parse(pattern) {\n        Ok(p) => p,\n        Err(e) => {\n          eprintln!(\"{}\", e);\n          std::process::exit(1);\n        }\n      }\n    } else {\n      Default::default()\n    };\n\n    Some(lightningcss::css_modules::Config {\n      pattern,\n      dashed_idents: cli_args.css_modules_dashed_idents,\n      ..Default::default()\n    })\n  } else {\n    cli_args.css_modules.as_ref().map(|_| Default::default())\n  };\n\n  let fs = FileProvider::new();\n\n  for (filename, source) in inputs {\n    let warnings = if cli_args.error_recovery {\n      Some(Arc::new(RwLock::new(Vec::new())))\n    } else {\n      None\n    };\n\n    let mut source_map = if cli_args.sourcemap {\n      Some(SourceMap::new(&project_root.to_string_lossy()))\n    } else {\n      None\n    };\n\n    let output_file = if let Some(output_file) = &cli_args.output_file {\n      Some(Cow::Borrowed(Path::new(output_file)))\n    } else if let Some(dir) = &cli_args.output_dir {\n      Some(Cow::Owned(\n        Path::new(dir).join(Path::new(&filename).file_name().unwrap()),\n      ))\n    } else {\n      None\n    };\n\n    let res = {\n      let mut flags = ParserFlags::empty();\n      flags.set(ParserFlags::CUSTOM_MEDIA, cli_args.custom_media);\n\n      let mut options = ParserOptions {\n        flags,\n        css_modules: css_modules.clone(),\n        error_recovery: cli_args.error_recovery,\n        warnings: warnings.clone(),\n        ..ParserOptions::default()\n      };\n\n      let mut stylesheet = if cli_args.bundle {\n        let mut bundler = Bundler::new(&fs, source_map.as_mut(), options);\n        bundler.bundle(Path::new(&filename)).unwrap()\n      } else {\n        if let Some(sm) = &mut source_map {\n          sm.add_source(&filename);\n          let _ = sm.set_source_content(0, &source);\n        }\n        options.filename = filename;\n        StyleSheet::parse(&source, options).unwrap()\n      };\n\n      let targets = if !cli_args.targets.is_empty() {\n        Browsers::from_browserslist(&cli_args.targets).unwrap()\n      } else if cli_args.browserslist {\n        Browsers::load_browserslist().unwrap()\n      } else {\n        None\n      }\n      .into();\n\n      stylesheet\n        .minify(MinifyOptions {\n          targets,\n          ..MinifyOptions::default()\n        })\n        .unwrap();\n\n      stylesheet\n        .to_css(PrinterOptions {\n          minify: cli_args.minify,\n          source_map: source_map.as_mut(),\n          project_root: Some(&project_root.to_string_lossy()),\n          targets,\n          ..PrinterOptions::default()\n        })\n        .unwrap()\n    };\n\n    let map = if let Some(ref mut source_map) = source_map {\n      let mut vlq_output: Vec<u8> = Vec::new();\n      source_map\n        .write_vlq(&mut vlq_output)\n        .map_err(|_| io::Error::new(io::ErrorKind::Other, \"Error writing sourcemap vlq\"))?;\n\n      let sm = SourceMapJson {\n        version: 3,\n        mappings: unsafe { String::from_utf8_unchecked(vlq_output) },\n        sources: source_map.get_sources(),\n        sources_content: source_map.get_sources_content(),\n        names: source_map.get_names(),\n      };\n\n      serde_json::to_vec(&sm).ok()\n    } else {\n      None\n    };\n\n    if let Some(warnings) = warnings {\n      let warnings = Arc::try_unwrap(warnings).unwrap().into_inner().unwrap();\n      for warning in warnings {\n        eprintln!(\"{}\", warning);\n      }\n    }\n\n    if let Some(output_file) = &output_file {\n      let mut code = res.code;\n      if cli_args.sourcemap {\n        if let Some(map_buf) = map {\n          let map_filename = output_file.to_string_lossy() + \".map\";\n          code += &format!(\"\\n/*# sourceMappingURL={} */\\n\", map_filename);\n          fs::write(map_filename.as_ref(), map_buf)?;\n        }\n      }\n\n      if let Some(p) = output_file.parent() {\n        fs::create_dir_all(p)?\n      };\n      fs::write(output_file, code.as_bytes())?;\n\n      if let Some(css_modules) = &cli_args.css_modules {\n        let css_modules_filename = if let Some(name) = css_modules {\n          Cow::Borrowed(name)\n        } else {\n          Cow::Owned(infer_css_modules_filename(output_file.as_ref())?)\n        };\n        if let Some(exports) = res.exports {\n          let css_modules_json = serde_json::to_string(&exports)?;\n          fs::write(css_modules_filename.as_ref(), css_modules_json)?;\n        }\n      }\n    } else {\n      if let Some(exports) = res.exports {\n        println!(\n          \"{}\",\n          serde_json::json!({\n            \"code\": res.code,\n            \"exports\": exports\n          })\n        );\n      } else {\n        println!(\"{}\", res.code);\n      }\n    }\n  }\n\n  Ok(())\n}\n\nfn infer_css_modules_filename(path: &Path) -> Result<String, std::io::Error> {\n  if path.extension() == Some(ffi::OsStr::new(\"json\")) {\n    Err(io::Error::new(\n      io::ErrorKind::Other,\n      \"Cannot infer a css modules json filename, since the output file extension is '.json'\",\n    ))\n  } else {\n    // unwrap: the filename option is a String from clap, so is valid utf-8\n    Ok(path.with_extension(\"json\").to_str().unwrap().into())\n  }\n}\n"
  },
  {
    "path": "src/media_query.rs",
    "content": "//! Media queries.\nuse crate::error::{ErrorWithLocation, MinifyError, MinifyErrorKind, ParserError, PrinterError};\nuse crate::macros::enum_property;\nuse crate::parser::starts_with_ignore_ascii_case;\nuse crate::printer::Printer;\nuse crate::properties::custom::{EnvironmentVariable, TokenList};\n#[cfg(feature = \"visitor\")]\nuse crate::rules::container::{ContainerSizeFeatureId, ScrollStateFeatureId};\nuse crate::rules::custom_media::CustomMediaRule;\nuse crate::rules::Location;\nuse crate::stylesheet::ParserOptions;\nuse crate::targets::{should_compile, Targets};\nuse crate::traits::{Parse, ParseWithOptions, ToCss};\nuse crate::values::ident::{DashedIdent, Ident};\nuse crate::values::number::{CSSInteger, CSSNumber};\nuse crate::values::string::CowArcStr;\nuse crate::values::{length::Length, ratio::Ratio, resolution::Resolution};\nuse crate::vendor_prefix::VendorPrefix;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse bitflags::bitflags;\nuse cssparser::*;\n#[cfg(feature = \"into_owned\")]\nuse static_self::IntoOwned;\nuse std::borrow::Cow;\nuse std::collections::{HashMap, HashSet};\n\n#[cfg(feature = \"serde\")]\nuse crate::serialization::ValueWrapper;\n\n/// A [media query list](https://drafts.csswg.org/mediaqueries/#mq-list).\n#[derive(Clone, Debug, PartialEq, Default)]\n#[cfg_attr(feature = \"visitor\", derive(Visit), visit(visit_media_list, MEDIA_QUERIES))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"camelCase\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct MediaList<'i> {\n  /// The list of media queries.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  pub media_queries: Vec<MediaQuery<'i>>,\n}\n\nimpl<'i> MediaList<'i> {\n  /// Creates an empty media query list.\n  pub fn new() -> Self {\n    MediaList { media_queries: vec![] }\n  }\n\n  /// Parse a media query list from CSS.\n  pub fn parse<'t>(\n    input: &mut Parser<'i, 't>,\n    options: &ParserOptions<'_, 'i>,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let mut media_queries = vec![];\n    if input.is_exhausted() {\n      return Ok(MediaList { media_queries });\n    }\n\n    loop {\n      match input.parse_until_before(Delimiter::Comma, |i| MediaQuery::parse_with_options(i, options)) {\n        Ok(mq) => {\n          media_queries.push(mq);\n        }\n        Err(err) => match err.kind {\n          ParseErrorKind::Basic(BasicParseErrorKind::EndOfInput) => break,\n          _ => return Err(err),\n        },\n      }\n\n      match input.next() {\n        Ok(&Token::Comma) => {}\n        Ok(_) => unreachable!(),\n        Err(_) => break,\n      }\n    }\n\n    Ok(MediaList { media_queries })\n  }\n\n  pub(crate) fn transform_custom_media(\n    &mut self,\n    loc: Location,\n    custom_media: &HashMap<CowArcStr<'i>, CustomMediaRule<'i>>,\n  ) -> Result<(), MinifyError> {\n    for query in self.media_queries.iter_mut() {\n      query.transform_custom_media(loc, custom_media)?;\n    }\n    Ok(())\n  }\n\n  pub(crate) fn transform_resolution(&mut self, targets: Targets) {\n    let mut i = 0;\n    while i < self.media_queries.len() {\n      let query = &self.media_queries[i];\n      let mut prefixes = query.get_necessary_prefixes(targets);\n      prefixes.remove(VendorPrefix::None);\n      if !prefixes.is_empty() {\n        let query = query.clone();\n        for prefix in prefixes {\n          let mut transformed = query.clone();\n          transformed.transform_resolution(prefix);\n          if !self.media_queries.contains(&transformed) {\n            self.media_queries.insert(i, transformed);\n          }\n          i += 1;\n        }\n      }\n\n      i += 1;\n    }\n  }\n\n  /// Returns whether the media query list always matches.\n  pub fn always_matches(&self) -> bool {\n    // If the media list is empty, it always matches.\n    self.media_queries.is_empty() || self.media_queries.iter().all(|mq| mq.always_matches())\n  }\n\n  /// Returns whether the media query list never matches.\n  pub fn never_matches(&self) -> bool {\n    !self.media_queries.is_empty() && self.media_queries.iter().all(|mq| mq.never_matches())\n  }\n\n  /// Attempts to combine the given media query list into this one. The resulting media query\n  /// list matches if both the original media query lists would have matched.\n  ///\n  /// Returns an error if the boolean logic is not possible.\n  pub fn and(&mut self, b: &MediaList<'i>) -> Result<(), ()> {\n    if self.media_queries.is_empty() {\n      self.media_queries.extend(b.media_queries.iter().cloned());\n      return Ok(());\n    }\n\n    for b in &b.media_queries {\n      if self.media_queries.contains(&b) {\n        continue;\n      }\n\n      for a in &mut self.media_queries {\n        a.and(&b)?;\n      }\n    }\n\n    Ok(())\n  }\n\n  /// Combines the given media query list into this one. The resulting media query list\n  /// matches if either of the original media query lists would have matched.\n  pub fn or(&mut self, b: &MediaList<'i>) {\n    for mq in &b.media_queries {\n      if !self.media_queries.contains(&mq) {\n        self.media_queries.push(mq.clone())\n      }\n    }\n  }\n}\n\nimpl<'i> ToCss for MediaList<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    if self.media_queries.is_empty() {\n      dest.write_str(\"not all\")?;\n      return Ok(());\n    }\n\n    let mut first = true;\n    for query in &self.media_queries {\n      if !first {\n        dest.delim(',', false)?;\n      }\n      first = false;\n      query.to_css(dest)?;\n    }\n    Ok(())\n  }\n}\n\nenum_property! {\n  /// A [media query qualifier](https://drafts.csswg.org/mediaqueries/#mq-prefix).\n  pub enum Qualifier {\n    /// Prevents older browsers from matching the media query.\n    Only,\n    /// Negates a media query.\n    Not,\n  }\n}\n\n/// A [media type](https://drafts.csswg.org/mediaqueries/#media-types) within a media query.\n#[derive(Clone, Debug, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"kebab-case\", into = \"CowArcStr\", from = \"CowArcStr\")\n)]\npub enum MediaType<'i> {\n  /// Matches all devices.\n  All,\n  /// Matches printers, and devices intended to reproduce a printed\n  /// display, such as a web browser showing a document in “Print Preview”.\n  Print,\n  /// Matches all devices that aren’t matched by print.\n  Screen,\n  /// An unknown media type.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  Custom(CowArcStr<'i>),\n}\n\nimpl<'i> From<CowArcStr<'i>> for MediaType<'i> {\n  fn from(name: CowArcStr<'i>) -> Self {\n    match_ignore_ascii_case! { &*name,\n      \"all\" => MediaType::All,\n      \"print\" => MediaType::Print,\n      \"screen\" => MediaType::Screen,\n      _ => MediaType::Custom(name)\n    }\n  }\n}\n\nimpl<'i> Into<CowArcStr<'i>> for MediaType<'i> {\n  fn into(self) -> CowArcStr<'i> {\n    match self {\n      MediaType::All => \"all\".into(),\n      MediaType::Print => \"print\".into(),\n      MediaType::Screen => \"screen\".into(),\n      MediaType::Custom(desc) => desc,\n    }\n  }\n}\n\nimpl<'i> Parse<'i> for MediaType<'i> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let name: CowArcStr = input.expect_ident()?.into();\n    Ok(Self::from(name))\n  }\n}\n\n#[cfg(feature = \"jsonschema\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"jsonschema\")))]\nimpl<'a> schemars::JsonSchema for MediaType<'a> {\n  fn is_referenceable() -> bool {\n    true\n  }\n\n  fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {\n    str::json_schema(gen)\n  }\n\n  fn schema_name() -> String {\n    \"MediaType\".into()\n  }\n}\n\n/// A [media query](https://drafts.csswg.org/mediaqueries/#media).\n#[derive(Clone, Debug, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(feature = \"visitor\", visit(visit_media_query, MEDIA_QUERIES))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize), serde(rename_all = \"camelCase\"))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct MediaQuery<'i> {\n  /// The qualifier for this query.\n  pub qualifier: Option<Qualifier>,\n  /// The media type for this query, that can be known, unknown, or \"all\".\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  pub media_type: MediaType<'i>,\n  /// The condition that this media query contains. This cannot have `or`\n  /// in the first level.\n  pub condition: Option<MediaCondition<'i>>,\n}\n\nimpl<'i> ParseWithOptions<'i> for MediaQuery<'i> {\n  fn parse_with_options<'t>(\n    input: &mut Parser<'i, 't>,\n    options: &ParserOptions<'_, 'i>,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let (qualifier, explicit_media_type) = input\n      .try_parse(|input| -> Result<_, ParseError<'i, ParserError<'i>>> {\n        let qualifier = input.try_parse(Qualifier::parse).ok();\n        let media_type = MediaType::parse(input)?;\n        Ok((qualifier, Some(media_type)))\n      })\n      .unwrap_or_default();\n\n    let condition = if explicit_media_type.is_none() {\n      Some(MediaCondition::parse_with_flags(\n        input,\n        QueryConditionFlags::ALLOW_OR,\n        options,\n      )?)\n    } else if input.try_parse(|i| i.expect_ident_matching(\"and\")).is_ok() {\n      Some(MediaCondition::parse_with_flags(\n        input,\n        QueryConditionFlags::empty(),\n        options,\n      )?)\n    } else {\n      None\n    };\n\n    let media_type = explicit_media_type.unwrap_or(MediaType::All);\n    Ok(Self {\n      qualifier,\n      media_type,\n      condition,\n    })\n  }\n}\n\nimpl<'i> MediaQuery<'i> {\n  fn transform_custom_media(\n    &mut self,\n    loc: Location,\n    custom_media: &HashMap<CowArcStr<'i>, CustomMediaRule<'i>>,\n  ) -> Result<(), MinifyError> {\n    if let Some(condition) = &mut self.condition {\n      let used = process_condition(\n        loc,\n        custom_media,\n        &mut self.media_type,\n        &mut self.qualifier,\n        condition,\n        &mut HashSet::new(),\n      )?;\n      if !used {\n        self.condition = None;\n      }\n    }\n    Ok(())\n  }\n\n  fn get_necessary_prefixes(&self, targets: Targets) -> VendorPrefix {\n    if let Some(condition) = &self.condition {\n      condition.get_necessary_prefixes(targets)\n    } else {\n      VendorPrefix::empty()\n    }\n  }\n\n  fn transform_resolution(&mut self, prefix: VendorPrefix) {\n    if let Some(condition) = &mut self.condition {\n      condition.transform_resolution(prefix)\n    }\n  }\n\n  /// Returns whether the media query is guaranteed to always match.\n  pub fn always_matches(&self) -> bool {\n    self.qualifier == None && self.media_type == MediaType::All && self.condition == None\n  }\n\n  /// Returns whether the media query is guaranteed to never match.\n  pub fn never_matches(&self) -> bool {\n    self.qualifier == Some(Qualifier::Not) && self.media_type == MediaType::All && self.condition == None\n  }\n\n  /// Attempts to combine the given media query into this one. The resulting media query\n  /// matches if both of the original media queries would have matched.\n  ///\n  /// Returns an error if the boolean logic is not possible.\n  pub fn and<'a>(&mut self, b: &MediaQuery<'i>) -> Result<(), ()> {\n    let at = (&self.qualifier, &self.media_type);\n    let bt = (&b.qualifier, &b.media_type);\n    let (qualifier, media_type) = match (at, bt) {\n      // `not all and screen` => not all\n      // `screen and not all` => not all\n      ((&Some(Qualifier::Not), &MediaType::All), _) |\n      (_, (&Some(Qualifier::Not), &MediaType::All)) => (Some(Qualifier::Not), MediaType::All),\n      // `not screen and not print` => ERROR\n      // `not screen and not screen` => not screen\n      ((&Some(Qualifier::Not), a), (&Some(Qualifier::Not), b)) => {\n        if a == b {\n          (Some(Qualifier::Not), a.clone())\n        } else {\n          return Err(())\n        }\n      },\n      // `all and print` => print\n      // `print and all` => print\n      // `all and not print` => not print\n      ((_, MediaType::All), (q, t)) |\n      ((q, t), (_, MediaType::All)) |\n      // `not screen and print` => print\n      // `print and not screen` => print\n      ((&Some(Qualifier::Not), _), (q, t)) |\n      ((q, t), (&Some(Qualifier::Not), _)) => (q.clone(), t.clone()),\n      // `print and screen` => not all\n      ((_, a), (_, b)) if a != b => (Some(Qualifier::Not), MediaType::All),\n      ((_, a), _) => (None, a.clone())\n    };\n\n    self.qualifier = qualifier;\n    self.media_type = media_type;\n\n    if let Some(cond) = &b.condition {\n      self.condition = if let Some(condition) = &self.condition {\n        if condition != cond {\n          Some(MediaCondition::Operation {\n            conditions: vec![condition.clone(), cond.clone()],\n            operator: Operator::And,\n          })\n        } else {\n          Some(condition.clone())\n        }\n      } else {\n        Some(cond.clone())\n      }\n    }\n\n    Ok(())\n  }\n}\n\nimpl<'i> ToCss for MediaQuery<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    if let Some(qual) = self.qualifier {\n      qual.to_css(dest)?;\n      dest.write_char(' ')?;\n    }\n\n    match self.media_type {\n      MediaType::All => {\n        // We need to print \"all\" if there's a qualifier, or there's\n        // just an empty list of expressions.\n        //\n        // Otherwise, we'd serialize media queries like \"(min-width:\n        // 40px)\" in \"all (min-width: 40px)\", which is unexpected.\n        if self.qualifier.is_some() || self.condition.is_none() {\n          dest.write_str(\"all\")?;\n        }\n      }\n      MediaType::Print => dest.write_str(\"print\")?,\n      MediaType::Screen => dest.write_str(\"screen\")?,\n      MediaType::Custom(ref desc) => dest.write_str(desc)?,\n    }\n\n    let condition = match self.condition {\n      Some(ref c) => c,\n      None => return Ok(()),\n    };\n\n    let needs_parens = if self.media_type != MediaType::All || self.qualifier.is_some() {\n      dest.write_str(\" and \")?;\n      matches!(condition, MediaCondition::Operation { operator, .. } if *operator != Operator::And)\n    } else {\n      false\n    };\n\n    to_css_with_parens_if_needed(condition, dest, needs_parens)\n  }\n}\n\n#[cfg(feature = \"serde\")]\n#[derive(serde::Deserialize)]\n#[serde(untagged)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\nenum MediaQueryOrRaw<'i> {\n  #[cfg_attr(feature = \"serde\", serde(rename_all = \"camelCase\"))]\n  MediaQuery {\n    qualifier: Option<Qualifier>,\n    #[cfg_attr(feature = \"serde\", serde(borrow))]\n    media_type: MediaType<'i>,\n    condition: Option<MediaCondition<'i>>,\n  },\n  Raw {\n    raw: CowArcStr<'i>,\n  },\n}\n\n#[cfg(feature = \"serde\")]\nimpl<'i, 'de: 'i> serde::Deserialize<'de> for MediaQuery<'i> {\n  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n  where\n    D: serde::Deserializer<'de>,\n  {\n    let mq = MediaQueryOrRaw::deserialize(deserializer)?;\n    match mq {\n      MediaQueryOrRaw::MediaQuery {\n        qualifier,\n        media_type,\n        condition,\n      } => Ok(MediaQuery {\n        qualifier,\n        media_type,\n        condition,\n      }),\n      MediaQueryOrRaw::Raw { raw } => {\n        let res = MediaQuery::parse_string_with_options(raw.as_ref(), ParserOptions::default())\n          .map_err(|_| serde::de::Error::custom(\"Could not parse value\"))?;\n        Ok(res.into_owned())\n      }\n    }\n  }\n}\n\nenum_property! {\n  /// A binary `and` or `or` operator.\n  pub enum Operator {\n    /// The `and` operator.\n    And,\n    /// The `or` operator.\n    Or,\n  }\n}\n\n/// Represents a media condition.\n#[derive(Clone, Debug, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub enum MediaCondition<'i> {\n  /// A media feature, implicitly parenthesized.\n  #[cfg_attr(feature = \"serde\", serde(borrow, with = \"ValueWrapper::<MediaFeature>\"))]\n  Feature(MediaFeature<'i>),\n  /// A negation of a condition.\n  #[cfg_attr(feature = \"visitor\", skip_type)]\n  #[cfg_attr(feature = \"serde\", serde(with = \"ValueWrapper::<Box<MediaCondition>>\"))]\n  Not(Box<MediaCondition<'i>>),\n  /// A set of joint operations.\n  #[cfg_attr(feature = \"visitor\", skip_type)]\n  Operation {\n    /// The operator for the conditions.\n    operator: Operator,\n    /// The conditions for the operator.\n    conditions: Vec<MediaCondition<'i>>,\n  },\n  /// Unknown tokens.\n  #[cfg_attr(feature = \"serde\", serde(borrow, with = \"ValueWrapper::<TokenList>\"))]\n  Unknown(TokenList<'i>),\n}\n\n/// A trait for conditions such as media queries and container queries.\npub(crate) trait QueryCondition<'i>: Sized {\n  fn parse_feature<'t>(\n    input: &mut Parser<'i, 't>,\n    options: &ParserOptions<'_, 'i>,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>>;\n  fn create_negation(condition: Box<Self>) -> Self;\n  fn create_operation(operator: Operator, conditions: Vec<Self>) -> Self;\n  fn parse_style_query<'t>(\n    input: &mut Parser<'i, 't>,\n    _options: &ParserOptions<'_, 'i>,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    Err(input.new_error_for_next_token())\n  }\n\n  fn parse_scroll_state_query<'t>(\n    input: &mut Parser<'i, 't>,\n    _options: &ParserOptions<'_, 'i>,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    Err(input.new_error_for_next_token())\n  }\n\n  fn needs_parens(&self, parent_operator: Option<Operator>, targets: &Targets) -> bool;\n}\n\nimpl<'i> QueryCondition<'i> for MediaCondition<'i> {\n  #[inline]\n  fn parse_feature<'t>(\n    input: &mut Parser<'i, 't>,\n    options: &ParserOptions<'_, 'i>,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let feature = MediaFeature::parse_with_options(input, options)?;\n    Ok(Self::Feature(feature))\n  }\n\n  #[inline]\n  fn create_negation(condition: Box<MediaCondition<'i>>) -> Self {\n    Self::Not(condition)\n  }\n\n  #[inline]\n  fn create_operation(operator: Operator, conditions: Vec<MediaCondition<'i>>) -> Self {\n    Self::Operation { operator, conditions }\n  }\n\n  fn needs_parens(&self, parent_operator: Option<Operator>, targets: &Targets) -> bool {\n    match self {\n      MediaCondition::Not(_) => true,\n      MediaCondition::Operation { operator, .. } => Some(*operator) != parent_operator,\n      MediaCondition::Feature(f) => f.needs_parens(parent_operator, targets),\n      MediaCondition::Unknown(_) => false,\n    }\n  }\n}\n\nbitflags! {\n  /// Flags for `parse_query_condition`.\n  #[derive(PartialEq, Eq, Clone, Copy)]\n  pub(crate) struct QueryConditionFlags: u8 {\n    /// Whether to allow top-level \"or\" boolean logic.\n    const ALLOW_OR = 1 << 0;\n    /// Whether to allow style container queries.\n    const ALLOW_STYLE = 1 << 1;\n    /// Whether to allow scroll state container queries.\n    const ALLOW_SCROLL_STATE = 1 << 2;\n  }\n}\n\nimpl<'i> MediaCondition<'i> {\n  /// Parse a single media condition.\n  fn parse_with_flags<'t>(\n    input: &mut Parser<'i, 't>,\n    flags: QueryConditionFlags,\n    options: &ParserOptions<'_, 'i>,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    input\n      .try_parse(|input| parse_query_condition(input, flags, options))\n      .or_else(|e| {\n        if options.error_recovery {\n          options.warn(e);\n          Ok(MediaCondition::Unknown(TokenList::parse(input, options, 0)?))\n        } else {\n          Err(e)\n        }\n      })\n  }\n\n  fn get_necessary_prefixes(&self, targets: Targets) -> VendorPrefix {\n    match self {\n      MediaCondition::Feature(MediaFeature::Range {\n        name: MediaFeatureName::Standard(MediaFeatureId::Resolution),\n        ..\n      }) => targets.prefixes(VendorPrefix::None, crate::prefixes::Feature::AtResolution),\n      MediaCondition::Not(not) => not.get_necessary_prefixes(targets),\n      MediaCondition::Operation { conditions, .. } => {\n        let mut prefixes = VendorPrefix::empty();\n        for condition in conditions {\n          prefixes |= condition.get_necessary_prefixes(targets);\n        }\n        prefixes\n      }\n      _ => VendorPrefix::empty(),\n    }\n  }\n\n  fn transform_resolution(&mut self, prefix: VendorPrefix) {\n    match self {\n      MediaCondition::Feature(MediaFeature::Range {\n        name: MediaFeatureName::Standard(MediaFeatureId::Resolution),\n        operator,\n        value: MediaFeatureValue::Resolution(value),\n      }) => match prefix {\n        VendorPrefix::WebKit | VendorPrefix::Moz => {\n          *self = MediaCondition::Feature(MediaFeature::Range {\n            name: MediaFeatureName::Standard(match prefix {\n              VendorPrefix::WebKit => MediaFeatureId::WebKitDevicePixelRatio,\n              VendorPrefix::Moz => MediaFeatureId::MozDevicePixelRatio,\n              _ => unreachable!(),\n            }),\n            operator: *operator,\n            value: MediaFeatureValue::Number(match value {\n              Resolution::Dpi(dpi) => *dpi / 96.0,\n              Resolution::Dpcm(dpcm) => *dpcm * 2.54 / 96.0,\n              Resolution::Dppx(dppx) => *dppx,\n            }),\n          });\n        }\n        _ => {}\n      },\n      MediaCondition::Not(not) => not.transform_resolution(prefix),\n      MediaCondition::Operation { conditions, .. } => {\n        for condition in conditions {\n          condition.transform_resolution(prefix);\n        }\n      }\n      _ => {}\n    }\n  }\n}\n\nimpl<'i> ParseWithOptions<'i> for MediaCondition<'i> {\n  fn parse_with_options<'t>(\n    input: &mut Parser<'i, 't>,\n    options: &ParserOptions<'_, 'i>,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    Self::parse_with_flags(input, QueryConditionFlags::ALLOW_OR, options)\n  }\n}\n\n/// Parse a single query condition.\npub(crate) fn parse_query_condition<'t, 'i, P: QueryCondition<'i>>(\n  input: &mut Parser<'i, 't>,\n  flags: QueryConditionFlags,\n  options: &ParserOptions<'_, 'i>,\n) -> Result<P, ParseError<'i, ParserError<'i>>> {\n  let location = input.current_source_location();\n  enum QueryFunction {\n    None,\n    Style,\n    ScrollState,\n  }\n\n  let (is_negation, function) = match *input.next()? {\n    Token::ParenthesisBlock => (false, QueryFunction::None),\n    Token::Ident(ref ident) if ident.eq_ignore_ascii_case(\"not\") => (true, QueryFunction::None),\n    Token::Function(ref f)\n      if flags.contains(QueryConditionFlags::ALLOW_STYLE) && f.eq_ignore_ascii_case(\"style\") =>\n    {\n      (false, QueryFunction::Style)\n    }\n    Token::Function(ref f)\n      if flags.contains(QueryConditionFlags::ALLOW_SCROLL_STATE) && f.eq_ignore_ascii_case(\"scroll-state\") =>\n    {\n      (false, QueryFunction::ScrollState)\n    }\n    ref t => return Err(location.new_unexpected_token_error(t.clone())),\n  };\n\n  let first_condition = match (is_negation, function) {\n    (true, QueryFunction::None) => {\n      let inner_condition = parse_parens_or_function(input, flags, options)?;\n      return Ok(P::create_negation(Box::new(inner_condition)));\n    }\n    (true, QueryFunction::Style) => {\n      let inner_condition = P::parse_style_query(input, options)?;\n      return Ok(P::create_negation(Box::new(inner_condition)));\n    }\n    (true, QueryFunction::ScrollState) => {\n      let inner_condition = P::parse_scroll_state_query(input, options)?;\n      return Ok(P::create_negation(Box::new(inner_condition)));\n    }\n    (false, QueryFunction::None) => parse_paren_block(input, flags, options)?,\n    (false, QueryFunction::Style) => P::parse_style_query(input, options)?,\n    (false, QueryFunction::ScrollState) => P::parse_scroll_state_query(input, options)?,\n  };\n\n  let operator = match input.try_parse(Operator::parse) {\n    Ok(op) => op,\n    Err(..) => return Ok(first_condition),\n  };\n\n  if !flags.contains(QueryConditionFlags::ALLOW_OR) && operator == Operator::Or {\n    return Err(location.new_unexpected_token_error(Token::Ident(\"or\".into())));\n  }\n\n  let mut conditions = vec![];\n  conditions.push(first_condition);\n  conditions.push(parse_parens_or_function(input, flags, options)?);\n\n  let delim = match operator {\n    Operator::And => \"and\",\n    Operator::Or => \"or\",\n  };\n\n  loop {\n    if input.try_parse(|i| i.expect_ident_matching(delim)).is_err() {\n      return Ok(P::create_operation(operator, conditions));\n    }\n\n    conditions.push(parse_parens_or_function(input, flags, options)?);\n  }\n}\n\n/// Parse a media condition in parentheses, or a style() function.\nfn parse_parens_or_function<'t, 'i, P: QueryCondition<'i>>(\n  input: &mut Parser<'i, 't>,\n  flags: QueryConditionFlags,\n  options: &ParserOptions<'_, 'i>,\n) -> Result<P, ParseError<'i, ParserError<'i>>> {\n  let location = input.current_source_location();\n  match *input.next()? {\n    Token::ParenthesisBlock => parse_paren_block(input, flags, options),\n    Token::Function(ref f)\n      if flags.contains(QueryConditionFlags::ALLOW_STYLE) && f.eq_ignore_ascii_case(\"style\") =>\n    {\n      P::parse_style_query(input, options)\n    }\n    Token::Function(ref f)\n      if flags.contains(QueryConditionFlags::ALLOW_SCROLL_STATE) && f.eq_ignore_ascii_case(\"scroll-state\") =>\n    {\n      P::parse_scroll_state_query(input, options)\n    }\n    ref t => return Err(location.new_unexpected_token_error(t.clone())),\n  }\n}\n\nfn parse_paren_block<'t, 'i, P: QueryCondition<'i>>(\n  input: &mut Parser<'i, 't>,\n  flags: QueryConditionFlags,\n  options: &ParserOptions<'_, 'i>,\n) -> Result<P, ParseError<'i, ParserError<'i>>> {\n  input.parse_nested_block(|input| {\n    // Detect empty brackets and provide a clearer error message.\n    if input.is_exhausted() {\n      return Err(input.new_custom_error(ParserError::EmptyBracketInCondition));\n    }\n\n    if let Ok(inner) =\n      input.try_parse(|i| parse_query_condition(i, flags | QueryConditionFlags::ALLOW_OR, options))\n    {\n      return Ok(inner);\n    }\n\n    P::parse_feature(input, options)\n  })\n}\n\npub(crate) fn to_css_with_parens_if_needed<V: ToCss, W>(\n  value: V,\n  dest: &mut Printer<W>,\n  needs_parens: bool,\n) -> Result<(), PrinterError>\nwhere\n  W: std::fmt::Write,\n{\n  if needs_parens {\n    dest.write_char('(')?;\n  }\n  value.to_css(dest)?;\n  if needs_parens {\n    dest.write_char(')')?;\n  }\n  Ok(())\n}\n\npub(crate) fn operation_to_css<'i, V: ToCss + QueryCondition<'i>, W>(\n  operator: Operator,\n  conditions: &Vec<V>,\n  dest: &mut Printer<W>,\n) -> Result<(), PrinterError>\nwhere\n  W: std::fmt::Write,\n{\n  let mut iter = conditions.iter();\n  let first = iter.next().unwrap();\n  to_css_with_parens_if_needed(first, dest, first.needs_parens(Some(operator), &dest.targets.current))?;\n  for item in iter {\n    dest.write_char(' ')?;\n    operator.to_css(dest)?;\n    dest.write_char(' ')?;\n    to_css_with_parens_if_needed(item, dest, item.needs_parens(Some(operator), &dest.targets.current))?;\n  }\n\n  Ok(())\n}\n\nimpl<'i> MediaCondition<'i> {\n  fn negate(&self) -> Option<MediaCondition<'i>> {\n    match self {\n      MediaCondition::Not(not) => Some((**not).clone()),\n      MediaCondition::Feature(f) => f.negate().map(MediaCondition::Feature),\n      _ => None,\n    }\n  }\n}\n\nimpl<'i> ToCss for MediaCondition<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match *self {\n      MediaCondition::Feature(ref f) => f.to_css(dest),\n      MediaCondition::Not(ref c) => {\n        if let Some(negated) = c.negate() {\n          negated.to_css(dest)\n        } else {\n          dest.write_str(\"not \")?;\n          to_css_with_parens_if_needed(&**c, dest, c.needs_parens(None, &dest.targets.current))\n        }\n      }\n      MediaCondition::Operation {\n        ref conditions,\n        operator,\n      } => operation_to_css(operator, conditions, dest),\n      MediaCondition::Unknown(ref tokens) => tokens.to_css(dest, false),\n    }\n  }\n}\n\n/// A [comparator](https://drafts.csswg.org/mediaqueries/#typedef-mf-comparison) within a media query.\n#[derive(Clone, Copy, Debug, Eq, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum MediaFeatureComparison {\n  /// `=`\n  Equal,\n  /// `>`\n  GreaterThan,\n  /// `>=`\n  GreaterThanEqual,\n  /// `<`\n  LessThan,\n  /// `<=`\n  LessThanEqual,\n}\n\nimpl ToCss for MediaFeatureComparison {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    use MediaFeatureComparison::*;\n    match self {\n      Equal => dest.delim('=', true),\n      GreaterThan => dest.delim('>', true),\n      GreaterThanEqual => {\n        dest.whitespace()?;\n        dest.write_str(\">=\")?;\n        dest.whitespace()\n      }\n      LessThan => dest.delim('<', true),\n      LessThanEqual => {\n        dest.whitespace()?;\n        dest.write_str(\"<=\")?;\n        dest.whitespace()\n      }\n    }\n  }\n}\n\nimpl MediaFeatureComparison {\n  fn opposite(&self) -> MediaFeatureComparison {\n    match self {\n      MediaFeatureComparison::GreaterThan => MediaFeatureComparison::LessThan,\n      MediaFeatureComparison::GreaterThanEqual => MediaFeatureComparison::LessThanEqual,\n      MediaFeatureComparison::LessThan => MediaFeatureComparison::GreaterThan,\n      MediaFeatureComparison::LessThanEqual => MediaFeatureComparison::GreaterThanEqual,\n      MediaFeatureComparison::Equal => MediaFeatureComparison::Equal,\n    }\n  }\n\n  fn negate(&self) -> MediaFeatureComparison {\n    match self {\n      MediaFeatureComparison::GreaterThan => MediaFeatureComparison::LessThanEqual,\n      MediaFeatureComparison::GreaterThanEqual => MediaFeatureComparison::LessThan,\n      MediaFeatureComparison::LessThan => MediaFeatureComparison::GreaterThanEqual,\n      MediaFeatureComparison::LessThanEqual => MediaFeatureComparison::GreaterThan,\n      MediaFeatureComparison::Equal => MediaFeatureComparison::Equal,\n    }\n  }\n}\n\n/// A generic media feature or container feature.\n#[derive(Clone, Debug, PartialEq)]\n#[cfg_attr(\n  feature = \"visitor\",\n  derive(Visit),\n  visit(visit_media_feature, MEDIA_QUERIES, <'i, MediaFeatureId>),\n  visit(<'i, ContainerSizeFeatureId>),\n  visit(<'i, ScrollStateFeatureId>)\n)]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub enum QueryFeature<'i, FeatureId> {\n  /// A plain media feature, e.g. `(min-width: 240px)`.\n  Plain {\n    /// The name of the feature.\n    #[cfg_attr(feature = \"serde\", serde(borrow))]\n    name: MediaFeatureName<'i, FeatureId>,\n    /// The feature value.\n    value: MediaFeatureValue<'i>,\n  },\n  /// A boolean feature, e.g. `(hover)`.\n  Boolean {\n    /// The name of the feature.\n    name: MediaFeatureName<'i, FeatureId>,\n  },\n  /// A range, e.g. `(width > 240px)`.\n  Range {\n    /// The name of the feature.\n    name: MediaFeatureName<'i, FeatureId>,\n    /// A comparator.\n    operator: MediaFeatureComparison,\n    /// The feature value.\n    value: MediaFeatureValue<'i>,\n  },\n  /// An interval, e.g. `(120px < width < 240px)`.\n  #[cfg_attr(feature = \"serde\", serde(rename_all = \"camelCase\"))]\n  Interval {\n    /// The name of the feature.\n    name: MediaFeatureName<'i, FeatureId>,\n    /// A start value.\n    start: MediaFeatureValue<'i>,\n    /// A comparator for the start value.\n    start_operator: MediaFeatureComparison,\n    /// The end value.\n    end: MediaFeatureValue<'i>,\n    /// A comparator for the end value.\n    end_operator: MediaFeatureComparison,\n  },\n}\n\n/// A [media feature](https://drafts.csswg.org/mediaqueries/#typedef-media-feature)\npub type MediaFeature<'i> = QueryFeature<'i, MediaFeatureId>;\n\nimpl<'i, FeatureId> ParseWithOptions<'i> for QueryFeature<'i, FeatureId>\nwhere\n  FeatureId: for<'x> Parse<'x> + std::fmt::Debug + PartialEq + ValueType + Clone,\n{\n  fn parse_with_options<'t>(\n    input: &mut Parser<'i, 't>,\n    options: &ParserOptions<'_, 'i>,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    match input.try_parse(|input| Self::parse_name_first(input, options)) {\n      Ok(res) => Ok(res),\n      Err(\n        err @ ParseError {\n          kind: ParseErrorKind::Custom(ParserError::InvalidMediaQuery),\n          ..\n        },\n      ) => Err(err),\n      _ => Self::parse_value_first(input),\n    }\n  }\n}\n\nimpl<'i, FeatureId> QueryFeature<'i, FeatureId>\nwhere\n  FeatureId: for<'x> Parse<'x> + std::fmt::Debug + PartialEq + ValueType + Clone,\n{\n  fn parse_name_first<'t>(\n    input: &mut Parser<'i, 't>,\n    options: &ParserOptions<'_, 'i>,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let (name, legacy_op) = MediaFeatureName::parse(input)?;\n\n    let operator = input.try_parse(|input| consume_operation_or_colon(input, true));\n    let operator = match operator {\n      Err(..) => return Ok(QueryFeature::Boolean { name }),\n      Ok(operator) => operator,\n    };\n\n    if operator.is_some() && legacy_op.is_some() {\n      return Err(input.new_custom_error(ParserError::InvalidMediaQuery));\n    }\n\n    let value = MediaFeatureValue::parse(input, name.value_type())?;\n    if !value.check_type(name.value_type()) {\n      if options.error_recovery {\n        options.warn(ParseError {\n          kind: ParseErrorKind::Custom(ParserError::InvalidMediaQuery),\n          location: input.current_source_location(),\n        });\n      } else {\n        return Err(input.new_custom_error(ParserError::InvalidMediaQuery));\n      }\n    }\n\n    if let Some(operator) = operator.or(legacy_op) {\n      if !name.value_type().allows_ranges() {\n        return Err(input.new_custom_error(ParserError::InvalidMediaQuery));\n      }\n\n      Ok(QueryFeature::Range { name, operator, value })\n    } else {\n      Ok(QueryFeature::Plain { name, value })\n    }\n  }\n\n  fn parse_value_first<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    // We need to find the feature name first so we know the type.\n    let start = input.state();\n    let name = loop {\n      if let Ok((name, legacy_op)) = MediaFeatureName::parse(input) {\n        if legacy_op.is_some() {\n          return Err(input.new_custom_error(ParserError::InvalidMediaQuery));\n        }\n        break name;\n      }\n      if input.is_exhausted() {\n        return Err(input.new_custom_error(ParserError::InvalidMediaQuery));\n      }\n    };\n\n    input.reset(&start);\n\n    // Now we can parse the first value.\n    let value = MediaFeatureValue::parse(input, name.value_type())?;\n    let operator = consume_operation_or_colon(input, false)?;\n\n    // Skip over the feature name again.\n    {\n      let (feature_name, _) = MediaFeatureName::parse(input)?;\n      debug_assert_eq!(name, feature_name);\n    }\n\n    if !name.value_type().allows_ranges() || !value.check_type(name.value_type()) {\n      return Err(input.new_custom_error(ParserError::InvalidMediaQuery));\n    }\n\n    if let Ok(end_operator) = input.try_parse(|input| consume_operation_or_colon(input, false)) {\n      let start_operator = operator.unwrap();\n      let end_operator = end_operator.unwrap();\n      // Start and end operators must be matching.\n      match (start_operator, end_operator) {\n        (MediaFeatureComparison::GreaterThan, MediaFeatureComparison::GreaterThan)\n        | (MediaFeatureComparison::GreaterThan, MediaFeatureComparison::GreaterThanEqual)\n        | (MediaFeatureComparison::GreaterThanEqual, MediaFeatureComparison::GreaterThanEqual)\n        | (MediaFeatureComparison::GreaterThanEqual, MediaFeatureComparison::GreaterThan)\n        | (MediaFeatureComparison::LessThan, MediaFeatureComparison::LessThan)\n        | (MediaFeatureComparison::LessThan, MediaFeatureComparison::LessThanEqual)\n        | (MediaFeatureComparison::LessThanEqual, MediaFeatureComparison::LessThanEqual)\n        | (MediaFeatureComparison::LessThanEqual, MediaFeatureComparison::LessThan) => {}\n        _ => return Err(input.new_custom_error(ParserError::InvalidMediaQuery)),\n      };\n\n      let end_value = MediaFeatureValue::parse(input, name.value_type())?;\n      if !end_value.check_type(name.value_type()) {\n        return Err(input.new_custom_error(ParserError::InvalidMediaQuery));\n      }\n\n      Ok(QueryFeature::Interval {\n        name,\n        start: value,\n        start_operator,\n        end: end_value,\n        end_operator,\n      })\n    } else {\n      let operator = operator.unwrap().opposite();\n      Ok(QueryFeature::Range { name, operator, value })\n    }\n  }\n\n  pub(crate) fn needs_parens(&self, parent_operator: Option<Operator>, targets: &Targets) -> bool {\n    match self {\n      QueryFeature::Interval { .. } => {\n        should_compile!(targets, MediaIntervalSyntax) && parent_operator != Some(Operator::And)\n      }\n      QueryFeature::Range { operator, .. } => {\n        should_compile!(targets, MediaRangeSyntax)\n          && matches!(\n            operator,\n            MediaFeatureComparison::GreaterThan | MediaFeatureComparison::LessThan\n          )\n      }\n      _ => false,\n    }\n  }\n\n  fn negate(&self) -> Option<QueryFeature<'i, FeatureId>> {\n    match self {\n      QueryFeature::Range { name, operator, value } => Some(QueryFeature::Range {\n        name: (*name).clone(),\n        operator: operator.negate(),\n        value: value.clone(),\n      }),\n      _ => None,\n    }\n  }\n}\n\nimpl<'i, FeatureId: FeatureToCss> ToCss for QueryFeature<'i, FeatureId> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      QueryFeature::Boolean { name } => {\n        dest.write_char('(')?;\n        name.to_css(dest)?;\n      }\n      QueryFeature::Plain { name, value } => {\n        dest.write_char('(')?;\n        name.to_css(dest)?;\n        dest.delim(':', false)?;\n        value.to_css(dest)?;\n      }\n      QueryFeature::Range { name, operator, value } => {\n        // If range syntax is unsupported, use min/max prefix if possible.\n        if should_compile!(dest.targets.current, MediaRangeSyntax) {\n          return write_min_max(operator, name, value, dest, false);\n        }\n\n        dest.write_char('(')?;\n        name.to_css(dest)?;\n        operator.to_css(dest)?;\n        value.to_css(dest)?;\n      }\n      QueryFeature::Interval {\n        name,\n        start,\n        start_operator,\n        end,\n        end_operator,\n      } => {\n        if should_compile!(dest.targets.current, MediaIntervalSyntax) {\n          write_min_max(&start_operator.opposite(), name, start, dest, true)?;\n          dest.write_str(\" and \")?;\n          return write_min_max(end_operator, name, end, dest, true);\n        }\n\n        dest.write_char('(')?;\n        start.to_css(dest)?;\n        start_operator.to_css(dest)?;\n        name.to_css(dest)?;\n        end_operator.to_css(dest)?;\n        end.to_css(dest)?;\n      }\n    }\n\n    dest.write_char(')')\n  }\n}\n\n/// A media feature name.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize), serde(untagged))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub enum MediaFeatureName<'i, FeatureId> {\n  /// A standard media query feature identifier.\n  Standard(FeatureId),\n  /// A custom author-defined environment variable.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  Custom(DashedIdent<'i>),\n  /// An unknown environment variable.\n  Unknown(Ident<'i>),\n}\n\nimpl<'i, FeatureId: for<'x> Parse<'x>> MediaFeatureName<'i, FeatureId> {\n  /// Parses a media feature name.\n  pub fn parse<'t>(\n    input: &mut Parser<'i, 't>,\n  ) -> Result<(Self, Option<MediaFeatureComparison>), ParseError<'i, ParserError<'i>>> {\n    let ident = input.expect_ident()?;\n\n    if ident.starts_with(\"--\") {\n      return Ok((MediaFeatureName::Custom(DashedIdent(ident.into())), None));\n    }\n\n    let mut name = ident.as_ref();\n\n    // Webkit places its prefixes before \"min\" and \"max\". Remove it first, and\n    // re-add after removing min/max.\n    let is_webkit = starts_with_ignore_ascii_case(&name, \"-webkit-\");\n    if is_webkit {\n      name = &name[8..];\n    }\n\n    let comparator = if starts_with_ignore_ascii_case(&name, \"min-\") {\n      name = &name[4..];\n      Some(MediaFeatureComparison::GreaterThanEqual)\n    } else if starts_with_ignore_ascii_case(&name, \"max-\") {\n      name = &name[4..];\n      Some(MediaFeatureComparison::LessThanEqual)\n    } else {\n      None\n    };\n\n    let name = if is_webkit {\n      Cow::Owned(format!(\"-webkit-{}\", name))\n    } else {\n      Cow::Borrowed(name)\n    };\n\n    if let Ok(standard) = FeatureId::parse_string(&name) {\n      return Ok((MediaFeatureName::Standard(standard), comparator));\n    }\n\n    Ok((MediaFeatureName::Unknown(Ident(ident.into())), None))\n  }\n}\n\nmod private {\n  use super::*;\n\n  /// A trait for feature ids which can get a value type.\n  pub trait ValueType {\n    /// Returns the value type for this feature id.\n    fn value_type(&self) -> MediaFeatureType;\n  }\n}\n\npub(crate) use private::ValueType;\n\nimpl<'i, FeatureId: ValueType> ValueType for MediaFeatureName<'i, FeatureId> {\n  fn value_type(&self) -> MediaFeatureType {\n    match self {\n      Self::Standard(standard) => standard.value_type(),\n      _ => MediaFeatureType::Unknown,\n    }\n  }\n}\n\nimpl<'i, FeatureId: FeatureToCss> ToCss for MediaFeatureName<'i, FeatureId> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      Self::Standard(v) => v.to_css(dest),\n      Self::Custom(v) => v.to_css(dest),\n      Self::Unknown(v) => v.to_css(dest),\n    }\n  }\n}\n\nimpl<'i, FeatureId: FeatureToCss> FeatureToCss for MediaFeatureName<'i, FeatureId> {\n  fn to_css_with_prefix<W>(&self, prefix: &str, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      Self::Standard(v) => v.to_css_with_prefix(prefix, dest),\n      Self::Custom(v) => {\n        dest.write_str(prefix)?;\n        v.to_css(dest)\n      }\n      Self::Unknown(v) => {\n        dest.write_str(prefix)?;\n        v.to_css(dest)\n      }\n    }\n  }\n}\n\n/// The type of a media feature.\n#[derive(PartialEq)]\npub enum MediaFeatureType {\n  /// A length value.\n  Length,\n  /// A number value.\n  Number,\n  /// An integer value.\n  Integer,\n  /// A boolean value, either 0 or 1.\n  Boolean,\n  /// A resolution.\n  Resolution,\n  /// A ratio.\n  Ratio,\n  /// An identifier.\n  Ident,\n  /// An unknown type.\n  Unknown,\n}\n\nimpl MediaFeatureType {\n  fn allows_ranges(&self) -> bool {\n    use MediaFeatureType::*;\n    match self {\n      Length => true,\n      Number => true,\n      Integer => true,\n      Boolean => false,\n      Resolution => true,\n      Ratio => true,\n      Ident => false,\n      Unknown => true,\n    }\n  }\n}\n\nmacro_rules! define_query_features {\n  (\n    $(#[$outer:meta])*\n    $vis:vis enum $name:ident {\n      $(\n        $(#[$meta: meta])*\n        $str: literal: $id: ident = $ty: ident,\n      )+\n    }\n  ) => {\n    crate::macros::enum_property! {\n      $(#[$outer])*\n      $vis enum $name {\n        $(\n          $(#[$meta])*\n          $str: $id,\n        )+\n      }\n    }\n\n    impl ValueType for $name {\n      fn value_type(&self) -> MediaFeatureType {\n        match self {\n          $(\n            Self::$id => MediaFeatureType::$ty,\n          )+\n        }\n      }\n    }\n  }\n}\n\npub(crate) use define_query_features;\n\ndefine_query_features! {\n  /// A media query feature identifier.\n  pub enum MediaFeatureId {\n    /// The [width](https://w3c.github.io/csswg-drafts/mediaqueries-5/#width) media feature.\n    \"width\": Width = Length,\n    /// The [height](https://w3c.github.io/csswg-drafts/mediaqueries-5/#height) media feature.\n    \"height\": Height = Length,\n    /// The [aspect-ratio](https://w3c.github.io/csswg-drafts/mediaqueries-5/#aspect-ratio) media feature.\n    \"aspect-ratio\": AspectRatio = Ratio,\n    /// The [orientation](https://w3c.github.io/csswg-drafts/mediaqueries-5/#orientation) media feature.\n    \"orientation\": Orientation = Ident,\n    /// The [overflow-block](https://w3c.github.io/csswg-drafts/mediaqueries-5/#overflow-block) media feature.\n    \"overflow-block\": OverflowBlock = Ident,\n    /// The [overflow-inline](https://w3c.github.io/csswg-drafts/mediaqueries-5/#overflow-inline) media feature.\n    \"overflow-inline\": OverflowInline = Ident,\n    /// The [horizontal-viewport-segments](https://w3c.github.io/csswg-drafts/mediaqueries-5/#horizontal-viewport-segments) media feature.\n    \"horizontal-viewport-segments\": HorizontalViewportSegments = Integer,\n    /// The [vertical-viewport-segments](https://w3c.github.io/csswg-drafts/mediaqueries-5/#vertical-viewport-segments) media feature.\n    \"vertical-viewport-segments\": VerticalViewportSegments = Integer,\n    /// The [display-mode](https://w3c.github.io/csswg-drafts/mediaqueries-5/#display-mode) media feature.\n    \"display-mode\": DisplayMode = Ident,\n    /// The [resolution](https://w3c.github.io/csswg-drafts/mediaqueries-5/#resolution) media feature.\n    \"resolution\": Resolution = Resolution, // | infinite??\n    /// The [scan](https://w3c.github.io/csswg-drafts/mediaqueries-5/#scan) media feature.\n    \"scan\": Scan = Ident,\n    /// The [grid](https://w3c.github.io/csswg-drafts/mediaqueries-5/#grid) media feature.\n    \"grid\": Grid = Boolean,\n    /// The [update](https://w3c.github.io/csswg-drafts/mediaqueries-5/#update) media feature.\n    \"update\": Update = Ident,\n    /// The [environment-blending](https://w3c.github.io/csswg-drafts/mediaqueries-5/#environment-blending) media feature.\n    \"environment-blending\": EnvironmentBlending = Ident,\n    /// The [color](https://w3c.github.io/csswg-drafts/mediaqueries-5/#color) media feature.\n    \"color\": Color = Integer,\n    /// The [color-index](https://w3c.github.io/csswg-drafts/mediaqueries-5/#color-index) media feature.\n    \"color-index\": ColorIndex = Integer,\n    /// The [monochrome](https://w3c.github.io/csswg-drafts/mediaqueries-5/#monochrome) media feature.\n    \"monochrome\": Monochrome = Integer,\n    /// The [color-gamut](https://w3c.github.io/csswg-drafts/mediaqueries-5/#color-gamut) media feature.\n    \"color-gamut\": ColorGamut = Ident,\n    /// The [dynamic-range](https://w3c.github.io/csswg-drafts/mediaqueries-5/#dynamic-range) media feature.\n    \"dynamic-range\": DynamicRange = Ident,\n    /// The [inverted-colors](https://w3c.github.io/csswg-drafts/mediaqueries-5/#inverted-colors) media feature.\n    \"inverted-colors\": InvertedColors = Ident,\n    /// The [pointer](https://w3c.github.io/csswg-drafts/mediaqueries-5/#pointer) media feature.\n    \"pointer\": Pointer = Ident,\n    /// The [hover](https://w3c.github.io/csswg-drafts/mediaqueries-5/#hover) media feature.\n    \"hover\": Hover = Ident,\n    /// The [any-pointer](https://w3c.github.io/csswg-drafts/mediaqueries-5/#any-pointer) media feature.\n    \"any-pointer\": AnyPointer = Ident,\n    /// The [any-hover](https://w3c.github.io/csswg-drafts/mediaqueries-5/#any-hover) media feature.\n    \"any-hover\": AnyHover = Ident,\n    /// The [nav-controls](https://w3c.github.io/csswg-drafts/mediaqueries-5/#nav-controls) media feature.\n    \"nav-controls\": NavControls = Ident,\n    /// The [video-color-gamut](https://w3c.github.io/csswg-drafts/mediaqueries-5/#video-color-gamut) media feature.\n    \"video-color-gamut\": VideoColorGamut = Ident,\n    /// The [video-dynamic-range](https://w3c.github.io/csswg-drafts/mediaqueries-5/#video-dynamic-range) media feature.\n    \"video-dynamic-range\": VideoDynamicRange = Ident,\n    /// The [scripting](https://w3c.github.io/csswg-drafts/mediaqueries-5/#scripting) media feature.\n    \"scripting\": Scripting = Ident,\n    /// The [prefers-reduced-motion](https://w3c.github.io/csswg-drafts/mediaqueries-5/#prefers-reduced-motion) media feature.\n    \"prefers-reduced-motion\": PrefersReducedMotion = Ident,\n    /// The [prefers-reduced-transparency](https://w3c.github.io/csswg-drafts/mediaqueries-5/#prefers-reduced-transparency) media feature.\n    \"prefers-reduced-transparency\": PrefersReducedTransparency = Ident,\n    /// The [prefers-contrast](https://w3c.github.io/csswg-drafts/mediaqueries-5/#prefers-contrast) media feature.\n    \"prefers-contrast\": PrefersContrast = Ident,\n    /// The [forced-colors](https://w3c.github.io/csswg-drafts/mediaqueries-5/#forced-colors) media feature.\n    \"forced-colors\": ForcedColors = Ident,\n    /// The [prefers-color-scheme](https://w3c.github.io/csswg-drafts/mediaqueries-5/#prefers-color-scheme) media feature.\n    \"prefers-color-scheme\": PrefersColorScheme = Ident,\n    /// The [prefers-reduced-data](https://w3c.github.io/csswg-drafts/mediaqueries-5/#prefers-reduced-data) media feature.\n    \"prefers-reduced-data\": PrefersReducedData = Ident,\n    /// The [device-width](https://w3c.github.io/csswg-drafts/mediaqueries-5/#device-width) media feature.\n    \"device-width\": DeviceWidth = Length,\n    /// The [device-height](https://w3c.github.io/csswg-drafts/mediaqueries-5/#device-height) media feature.\n    \"device-height\": DeviceHeight = Length,\n    /// The [device-aspect-ratio](https://w3c.github.io/csswg-drafts/mediaqueries-5/#device-aspect-ratio) media feature.\n    \"device-aspect-ratio\": DeviceAspectRatio = Ratio,\n\n    /// The non-standard -webkit-device-pixel-ratio media feature.\n    \"-webkit-device-pixel-ratio\": WebKitDevicePixelRatio = Number,\n    /// The non-standard -moz-device-pixel-ratio media feature.\n    \"-moz-device-pixel-ratio\": MozDevicePixelRatio = Number,\n\n    // TODO: parse non-standard media queries?\n    // -moz-device-orientation\n    // -webkit-transform-3d\n  }\n}\n\npub(crate) trait FeatureToCss: ToCss {\n  fn to_css_with_prefix<W>(&self, prefix: &str, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write;\n}\n\nimpl FeatureToCss for MediaFeatureId {\n  fn to_css_with_prefix<W>(&self, prefix: &str, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      MediaFeatureId::WebKitDevicePixelRatio => {\n        dest.write_str(\"-webkit-\")?;\n        dest.write_str(prefix)?;\n        dest.write_str(\"device-pixel-ratio\")\n      }\n      _ => {\n        dest.write_str(prefix)?;\n        self.to_css(dest)\n      }\n    }\n  }\n}\n\n#[inline]\nfn write_min_max<W, FeatureId: FeatureToCss>(\n  operator: &MediaFeatureComparison,\n  name: &MediaFeatureName<FeatureId>,\n  value: &MediaFeatureValue,\n  dest: &mut Printer<W>,\n  is_range: bool,\n) -> Result<(), PrinterError>\nwhere\n  W: std::fmt::Write,\n{\n  let prefix = match operator {\n    MediaFeatureComparison::GreaterThan => {\n      if is_range {\n        dest.write_char('(')?;\n      }\n      dest.write_str(\"not \")?;\n      Some(\"max-\")\n    }\n    MediaFeatureComparison::GreaterThanEqual => Some(\"min-\"),\n    MediaFeatureComparison::LessThan => {\n      if is_range {\n        dest.write_char('(')?;\n      }\n      dest.write_str(\"not \")?;\n      Some(\"min-\")\n    }\n    MediaFeatureComparison::LessThanEqual => Some(\"max-\"),\n    MediaFeatureComparison::Equal => None,\n  };\n\n  dest.write_char('(')?;\n  if let Some(prefix) = prefix {\n    name.to_css_with_prefix(prefix, dest)?;\n  } else {\n    name.to_css(dest)?;\n  }\n\n  dest.delim(':', false)?;\n  value.to_css(dest)?;\n\n  if is_range\n    && matches!(\n      operator,\n      MediaFeatureComparison::GreaterThan | MediaFeatureComparison::LessThan\n    )\n  {\n    dest.write_char(')')?;\n  }\n\n  dest.write_char(')')?;\n  Ok(())\n}\n\n/// [media feature value](https://drafts.csswg.org/mediaqueries/#typedef-mf-value) within a media query.\n///\n/// See [MediaFeature](MediaFeature).\n#[derive(Clone, Debug, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit), visit(visit_media_feature_value, MEDIA_QUERIES))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub enum MediaFeatureValue<'i> {\n  /// A length value.\n  Length(Length),\n  /// A number value.\n  Number(CSSNumber),\n  /// An integer value.\n  Integer(CSSInteger),\n  /// A boolean value.\n  Boolean(bool),\n  /// A resolution.\n  Resolution(Resolution),\n  /// A ratio.\n  Ratio(Ratio),\n  /// An identifier.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  Ident(Ident<'i>),\n  /// An environment variable reference.\n  Env(EnvironmentVariable<'i>),\n}\n\nimpl<'i> MediaFeatureValue<'i> {\n  fn value_type(&self) -> MediaFeatureType {\n    use MediaFeatureValue::*;\n    match self {\n      Length(..) => MediaFeatureType::Length,\n      Number(..) => MediaFeatureType::Number,\n      Integer(..) => MediaFeatureType::Integer,\n      Boolean(..) => MediaFeatureType::Boolean,\n      Resolution(..) => MediaFeatureType::Resolution,\n      Ratio(..) => MediaFeatureType::Ratio,\n      Ident(..) => MediaFeatureType::Ident,\n      Env(..) => MediaFeatureType::Unknown,\n    }\n  }\n\n  fn check_type(&self, expected_type: MediaFeatureType) -> bool {\n    match (expected_type, self.value_type()) {\n      (_, MediaFeatureType::Unknown) | (MediaFeatureType::Unknown, _) => true,\n      (a, b) => a == b,\n    }\n  }\n}\n\nimpl<'i> MediaFeatureValue<'i> {\n  /// Parses a single media query feature value, with an expected type.\n  /// If the type is unknown, pass MediaFeatureType::Unknown instead.\n  pub fn parse<'t>(\n    input: &mut Parser<'i, 't>,\n    expected_type: MediaFeatureType,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    if let Ok(value) = input.try_parse(|input| Self::parse_known(input, expected_type)) {\n      return Ok(value);\n    }\n\n    Self::parse_unknown(input)\n  }\n\n  fn parse_known<'t>(\n    input: &mut Parser<'i, 't>,\n    expected_type: MediaFeatureType,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    match expected_type {\n      MediaFeatureType::Boolean => {\n        let value = CSSInteger::parse(input)?;\n        if value != 0 && value != 1 {\n          return Err(input.new_custom_error(ParserError::InvalidValue));\n        }\n        Ok(MediaFeatureValue::Boolean(value == 1))\n      }\n      MediaFeatureType::Number => Ok(MediaFeatureValue::Number(CSSNumber::parse(input)?)),\n      MediaFeatureType::Integer => Ok(MediaFeatureValue::Integer(CSSInteger::parse(input)?)),\n      MediaFeatureType::Length => Ok(MediaFeatureValue::Length(Length::parse(input)?)),\n      MediaFeatureType::Resolution => Ok(MediaFeatureValue::Resolution(Resolution::parse(input)?)),\n      MediaFeatureType::Ratio => Ok(MediaFeatureValue::Ratio(Ratio::parse(input)?)),\n      MediaFeatureType::Ident => Ok(MediaFeatureValue::Ident(Ident::parse(input)?)),\n      MediaFeatureType::Unknown => Err(input.new_custom_error(ParserError::InvalidValue)),\n    }\n  }\n\n  fn parse_unknown<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    // Ratios are ambiguous with numbers because the second param is optional (e.g. 2/1 == 2).\n    // We require the / delimiter when parsing ratios so that 2/1 ends up as a ratio and 2 is\n    // parsed as a number.\n    if let Ok(ratio) = input.try_parse(Ratio::parse_required) {\n      return Ok(MediaFeatureValue::Ratio(ratio));\n    }\n\n    // Parse number next so that unitless values are not parsed as lengths.\n    if let Ok(num) = input.try_parse(CSSNumber::parse) {\n      return Ok(MediaFeatureValue::Number(num));\n    }\n\n    if let Ok(length) = input.try_parse(Length::parse) {\n      return Ok(MediaFeatureValue::Length(length));\n    }\n\n    if let Ok(res) = input.try_parse(Resolution::parse) {\n      return Ok(MediaFeatureValue::Resolution(res));\n    }\n\n    if let Ok(env) = input.try_parse(|input| EnvironmentVariable::parse(input, &ParserOptions::default(), 0)) {\n      return Ok(MediaFeatureValue::Env(env));\n    }\n\n    let ident = Ident::parse(input)?;\n    Ok(MediaFeatureValue::Ident(ident))\n  }\n}\n\nimpl<'i> ToCss for MediaFeatureValue<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      MediaFeatureValue::Length(len) => len.to_css(dest),\n      MediaFeatureValue::Number(num) => num.to_css(dest),\n      MediaFeatureValue::Integer(num) => num.to_css(dest),\n      MediaFeatureValue::Boolean(b) => {\n        if *b {\n          dest.write_char('1')\n        } else {\n          dest.write_char('0')\n        }\n      }\n      MediaFeatureValue::Resolution(res) => res.to_css(dest),\n      MediaFeatureValue::Ratio(ratio) => ratio.to_css(dest),\n      MediaFeatureValue::Ident(id) => {\n        id.to_css(dest)?;\n        Ok(())\n      }\n      MediaFeatureValue::Env(env) => env.to_css(dest, false),\n    }\n  }\n}\n\n/// Consumes an operation or a colon, or returns an error.\nfn consume_operation_or_colon<'i, 't>(\n  input: &mut Parser<'i, 't>,\n  allow_colon: bool,\n) -> Result<Option<MediaFeatureComparison>, ParseError<'i, ParserError<'i>>> {\n  let location = input.current_source_location();\n  let first_delim = {\n    let location = input.current_source_location();\n    let next_token = input.next()?;\n    match next_token {\n      Token::Colon if allow_colon => return Ok(None),\n      Token::Delim(oper) => oper,\n      t => return Err(location.new_unexpected_token_error(t.clone())),\n    }\n  };\n  Ok(Some(match first_delim {\n    '=' => MediaFeatureComparison::Equal,\n    '>' => {\n      if input.try_parse(|i| i.expect_delim('=')).is_ok() {\n        MediaFeatureComparison::GreaterThanEqual\n      } else {\n        MediaFeatureComparison::GreaterThan\n      }\n    }\n    '<' => {\n      if input.try_parse(|i| i.expect_delim('=')).is_ok() {\n        MediaFeatureComparison::LessThanEqual\n      } else {\n        MediaFeatureComparison::LessThan\n      }\n    }\n    d => return Err(location.new_unexpected_token_error(Token::Delim(*d))),\n  }))\n}\n\nfn process_condition<'i>(\n  loc: Location,\n  custom_media: &HashMap<CowArcStr<'i>, CustomMediaRule<'i>>,\n  media_type: &mut MediaType<'i>,\n  qualifier: &mut Option<Qualifier>,\n  condition: &mut MediaCondition<'i>,\n  seen: &mut HashSet<DashedIdent<'i>>,\n) -> Result<bool, MinifyError> {\n  match condition {\n    MediaCondition::Not(cond) => {\n      let used = process_condition(loc, custom_media, media_type, qualifier, &mut *cond, seen)?;\n      if !used {\n        // If unused, only a media type remains so apply a not qualifier.\n        // If it is already not, then it cancels out.\n        *qualifier = if *qualifier == Some(Qualifier::Not) {\n          None\n        } else {\n          Some(Qualifier::Not)\n        };\n        return Ok(false);\n      }\n\n      // Unwrap nested nots\n      match &**cond {\n        MediaCondition::Not(cond) => {\n          *condition = (**cond).clone();\n        }\n        _ => {}\n      }\n    }\n    MediaCondition::Operation { conditions, .. } => {\n      let mut res = Ok(true);\n      conditions.retain_mut(|condition| {\n        let r = process_condition(loc, custom_media, media_type, qualifier, condition, seen);\n        if let Ok(used) = r {\n          used\n        } else {\n          res = r;\n          false\n        }\n      });\n      return res;\n    }\n    MediaCondition::Feature(QueryFeature::Boolean { name }) => {\n      let name = match name {\n        MediaFeatureName::Custom(name) => name,\n        _ => return Ok(true),\n      };\n\n      if seen.contains(name) {\n        return Err(ErrorWithLocation {\n          kind: MinifyErrorKind::CircularCustomMedia { name: name.to_string() },\n          loc,\n        });\n      }\n\n      let rule = custom_media.get(&name.0).ok_or_else(|| ErrorWithLocation {\n        kind: MinifyErrorKind::CustomMediaNotDefined { name: name.to_string() },\n        loc,\n      })?;\n\n      seen.insert(name.clone());\n\n      let mut res = Ok(true);\n      let mut conditions: Vec<MediaCondition> = rule\n        .query\n        .media_queries\n        .iter()\n        .filter_map(|query| {\n          if query.media_type != MediaType::All || query.qualifier != None {\n            if *media_type == MediaType::All {\n              // `not all` will never match.\n              if *qualifier == Some(Qualifier::Not) {\n                res = Ok(false);\n                return None;\n              }\n\n              // Propagate media type and qualifier to @media rule.\n              *media_type = query.media_type.clone();\n              *qualifier = query.qualifier.clone();\n            } else if query.media_type != *media_type || query.qualifier != *qualifier {\n              // Boolean logic with media types is hard to emulate, so we error for now.\n              res = Err(ErrorWithLocation {\n                kind: MinifyErrorKind::UnsupportedCustomMediaBooleanLogic {\n                  custom_media_loc: rule.loc,\n                },\n                loc,\n              });\n              return None;\n            }\n          }\n\n          if let Some(condition) = &query.condition {\n            let mut condition = condition.clone();\n            let r = process_condition(loc, custom_media, media_type, qualifier, &mut condition, seen);\n            if r.is_err() {\n              res = r;\n            }\n            // Parentheses are required around the condition unless there is a single media feature.\n            match condition {\n              MediaCondition::Feature(..) => Some(condition),\n              _ => Some(condition),\n            }\n          } else {\n            None\n          }\n        })\n        .collect();\n\n      seen.remove(name);\n\n      if res.is_err() {\n        return res;\n      }\n\n      if conditions.is_empty() {\n        return Ok(false);\n      }\n\n      if conditions.len() == 1 {\n        *condition = conditions.pop().unwrap();\n      } else {\n        *condition = MediaCondition::Operation {\n          conditions,\n          operator: Operator::Or,\n        };\n      }\n    }\n    _ => {}\n  }\n\n  Ok(true)\n}\n\n#[cfg(test)]\nmod tests {\n  use super::*;\n  use crate::{\n    stylesheet::PrinterOptions,\n    targets::{Browsers, Targets},\n  };\n\n  fn parse(s: &str) -> MediaQuery<'_> {\n    let mut input = ParserInput::new(&s);\n    let mut parser = Parser::new(&mut input);\n    MediaQuery::parse_with_options(&mut parser, &ParserOptions::default()).unwrap()\n  }\n\n  fn and(a: &str, b: &str) -> String {\n    let mut a = parse(a);\n    let b = parse(b);\n    a.and(&b).unwrap();\n    a.to_css_string(PrinterOptions::default()).unwrap()\n  }\n\n  #[test]\n  fn test_and() {\n    assert_eq!(and(\"(min-width: 250px)\", \"(color)\"), \"(width >= 250px) and (color)\");\n    assert_eq!(\n      and(\"(min-width: 250px) or (color)\", \"(orientation: landscape)\"),\n      \"((width >= 250px) or (color)) and (orientation: landscape)\"\n    );\n    assert_eq!(\n      and(\"(min-width: 250px) and (color)\", \"(orientation: landscape)\"),\n      \"(width >= 250px) and (color) and (orientation: landscape)\"\n    );\n    assert_eq!(and(\"all\", \"print\"), \"print\");\n    assert_eq!(and(\"print\", \"all\"), \"print\");\n    assert_eq!(and(\"all\", \"not print\"), \"not print\");\n    assert_eq!(and(\"not print\", \"all\"), \"not print\");\n    assert_eq!(and(\"not all\", \"print\"), \"not all\");\n    assert_eq!(and(\"print\", \"not all\"), \"not all\");\n    assert_eq!(and(\"print\", \"screen\"), \"not all\");\n    assert_eq!(and(\"not print\", \"screen\"), \"screen\");\n    assert_eq!(and(\"print\", \"not screen\"), \"print\");\n    assert_eq!(and(\"not screen\", \"print\"), \"print\");\n    assert_eq!(and(\"not screen\", \"not all\"), \"not all\");\n    assert_eq!(and(\"print\", \"(min-width: 250px)\"), \"print and (width >= 250px)\");\n    assert_eq!(and(\"(min-width: 250px)\", \"print\"), \"print and (width >= 250px)\");\n    assert_eq!(\n      and(\"print and (min-width: 250px)\", \"(color)\"),\n      \"print and (width >= 250px) and (color)\"\n    );\n    assert_eq!(and(\"all\", \"only screen\"), \"only screen\");\n    assert_eq!(and(\"only screen\", \"all\"), \"only screen\");\n    assert_eq!(and(\"print\", \"print\"), \"print\");\n  }\n\n  #[test]\n  fn test_negated_interval_parens() {\n    let media_query = parse(\"screen and not (200px <= width < 500px)\");\n    let printer_options = PrinterOptions {\n      targets: Targets {\n        browsers: Some(Browsers {\n          chrome: Some(95 << 16),\n          ..Default::default()\n        }),\n        ..Default::default()\n      },\n      ..Default::default()\n    };\n    assert_eq!(\n      media_query.to_css_string(printer_options).unwrap(),\n      \"screen and not ((min-width: 200px) and (not (min-width: 500px)))\"\n    );\n  }\n}\n"
  },
  {
    "path": "src/parser.rs",
    "content": "use crate::declaration::{parse_declaration, DeclarationBlock, DeclarationList};\nuse crate::error::{Error, ParserError, PrinterError};\nuse crate::media_query::*;\nuse crate::printer::Printer;\nuse crate::properties::custom::TokenList;\nuse crate::rules::container::{ContainerCondition, ContainerName, ContainerRule};\nuse crate::rules::font_feature_values::FontFeatureValuesRule;\nuse crate::rules::font_palette_values::FontPaletteValuesRule;\nuse crate::rules::layer::{LayerBlockRule, LayerStatementRule};\nuse crate::rules::nesting::NestedDeclarationsRule;\nuse crate::rules::property::PropertyRule;\nuse crate::rules::scope::ScopeRule;\nuse crate::rules::starting_style::StartingStyleRule;\nuse crate::rules::view_transition::ViewTransitionRule;\nuse crate::rules::viewport::ViewportRule;\n\nuse crate::properties::font::FamilyName;\nuse crate::rules::{\n  counter_style::CounterStyleRule,\n  custom_media::CustomMediaRule,\n  document::MozDocumentRule,\n  font_face::{FontFaceDeclarationParser, FontFaceRule},\n  import::ImportRule,\n  keyframes::{KeyframeListParser, KeyframesName, KeyframesRule},\n  layer::LayerName,\n  media::MediaRule,\n  namespace::NamespaceRule,\n  nesting::NestingRule,\n  page::{PageRule, PageSelector},\n  style::StyleRule,\n  supports::{SupportsCondition, SupportsRule},\n  unknown::UnknownAtRule,\n  CssRule, CssRuleList, Location,\n};\nuse crate::selector::{SelectorList, SelectorParser};\nuse crate::traits::{Parse, ParseWithOptions};\nuse crate::values::ident::{CustomIdent, DashedIdent};\nuse crate::values::string::CowArcStr;\nuse crate::vendor_prefix::VendorPrefix;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::{Visit, VisitTypes, Visitor};\nuse bitflags::bitflags;\nuse cssparser::*;\nuse parcel_selectors::parser::{NestingRequirement, ParseErrorRecovery};\nuse std::sync::{Arc, RwLock};\n\nbitflags! {\n  /// Parser feature flags to enable.\n  #[derive(Clone, Debug, Default)]\n  pub struct ParserFlags: u8 {\n    /// Whether the enable the [CSS nesting](https://www.w3.org/TR/css-nesting-1/) draft syntax.\n    const NESTING = 1 << 0;\n    /// Whether to enable the [custom media](https://drafts.csswg.org/mediaqueries-5/#custom-mq) draft syntax.\n    const CUSTOM_MEDIA = 1 << 1;\n    /// Whether to enable the non-standard >>> and /deep/ selector combinators used by Vue and Angular.\n    const DEEP_SELECTOR_COMBINATOR = 1 << 2;\n  }\n}\n\n/// CSS parsing options.\n#[derive(Clone, Debug, Default)]\npub struct ParserOptions<'o, 'i> {\n  /// Filename to use in error messages.\n  pub filename: String,\n  /// Whether the enable [CSS modules](https://github.com/css-modules/css-modules).\n  pub css_modules: Option<crate::css_modules::Config<'o>>,\n  /// The source index to assign to all parsed rules. Impacts the source map when\n  /// the style sheet is serialized.\n  pub source_index: u32,\n  /// Whether to ignore invalid rules and declarations rather than erroring.\n  pub error_recovery: bool,\n  /// A list that will be appended to when a warning occurs.\n  pub warnings: Option<Arc<RwLock<Vec<Error<ParserError<'i>>>>>>,\n  /// Feature flags to enable.\n  pub flags: ParserFlags,\n}\n\nimpl<'o, 'i> ParserOptions<'o, 'i> {\n  #[inline]\n  pub(crate) fn warn(&self, warning: ParseError<'i, ParserError<'i>>) {\n    if let Some(warnings) = &self.warnings {\n      if let Ok(mut warnings) = warnings.write() {\n        warnings.push(Error::from(warning, self.filename.clone()));\n      }\n    }\n  }\n}\n\n#[derive(Clone, Default)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct DefaultAtRuleParser;\nimpl<'i> crate::traits::AtRuleParser<'i> for DefaultAtRuleParser {\n  type AtRule = DefaultAtRule;\n  type Error = ();\n  type Prelude = ();\n}\n\n#[derive(PartialEq, Clone, Debug)]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct DefaultAtRule;\nimpl crate::traits::ToCss for DefaultAtRule {\n  fn to_css<W: std::fmt::Write>(&self, _: &mut Printer<W>) -> Result<(), PrinterError> {\n    Err(PrinterError {\n      kind: crate::error::PrinterErrorKind::FmtError,\n      loc: None,\n    })\n  }\n}\n\n#[cfg(feature = \"into_owned\")]\nimpl<'any> static_self::IntoOwned<'any> for DefaultAtRule {\n  type Owned = Self;\n  fn into_owned(self) -> Self {\n    self\n  }\n}\n\n#[cfg(feature = \"visitor\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"visitor\")))]\nimpl<'i, V: Visitor<'i, DefaultAtRule>> Visit<'i, DefaultAtRule, V> for DefaultAtRule {\n  const CHILD_TYPES: VisitTypes = VisitTypes::empty();\n  fn visit_children(&mut self, _: &mut V) -> Result<(), V::Error> {\n    Ok(())\n  }\n}\n\n#[derive(PartialEq, PartialOrd)]\nenum State {\n  Start = 1,\n  Layers = 2,\n  Imports = 3,\n  Namespaces = 4,\n  Body = 5,\n}\n\n/// The parser for the top-level rules in a stylesheet.\npub struct TopLevelRuleParser<'a, 'o, 'i, T: crate::traits::AtRuleParser<'i>> {\n  pub options: &'a ParserOptions<'o, 'i>,\n  state: State,\n  at_rule_parser: &'a mut T,\n  rules: &'a mut CssRuleList<'i, T::AtRule>,\n}\n\nimpl<'a, 'o, 'b, 'i, T: crate::traits::AtRuleParser<'i>> TopLevelRuleParser<'a, 'o, 'i, T> {\n  pub fn new(\n    options: &'a ParserOptions<'o, 'i>,\n    at_rule_parser: &'a mut T,\n    rules: &'a mut CssRuleList<'i, T::AtRule>,\n  ) -> Self {\n    TopLevelRuleParser {\n      options,\n      state: State::Start,\n      at_rule_parser,\n      rules,\n    }\n  }\n\n  pub fn nested<'x: 'b>(&'x mut self) -> NestedRuleParser<'x, 'o, 'i, T> {\n    NestedRuleParser {\n      options: &self.options,\n      at_rule_parser: self.at_rule_parser,\n      declarations: DeclarationList::new(),\n      important_declarations: DeclarationList::new(),\n      rules: &mut self.rules,\n      is_in_style_rule: false,\n      allow_declarations: false,\n    }\n  }\n}\n\n/// A rule prelude for at-rule with block.\n#[derive(Debug)]\n#[allow(dead_code)]\npub enum AtRulePrelude<'i, T> {\n  /// A @font-face rule prelude.\n  FontFace,\n  /// A @font-feature-values rule prelude, with its FamilyName list.\n  FontFeatureValues(Vec<FamilyName<'i>>),\n  /// A @font-palette-values rule prelude, with its name.\n  FontPaletteValues(DashedIdent<'i>),\n  /// A @counter-style rule prelude, with its counter style name.\n  CounterStyle(CustomIdent<'i>),\n  /// A @media rule prelude, with its media queries.\n  Media(MediaList<'i>),\n  /// A @custom-media rule prelude.\n  CustomMedia(DashedIdent<'i>, MediaList<'i>),\n  /// An @supports rule, with its conditional\n  Supports(SupportsCondition<'i>),\n  /// A @viewport rule prelude.\n  Viewport(VendorPrefix),\n  /// A @keyframes rule, with its animation name and vendor prefix if exists.\n  Keyframes(KeyframesName<'i>, VendorPrefix),\n  /// A @page rule prelude.\n  Page(Vec<PageSelector<'i>>),\n  /// A @-moz-document rule.\n  MozDocument,\n  /// A @import rule prelude.\n  Import(\n    CowRcStr<'i>,\n    MediaList<'i>,\n    Option<SupportsCondition<'i>>,\n    Option<Option<LayerName<'i>>>,\n  ),\n  /// A @namespace rule prelude.\n  Namespace(Option<CowRcStr<'i>>, CowRcStr<'i>),\n  /// A @charset rule prelude.\n  Charset,\n  /// A @nest prelude.\n  Nest(SelectorList<'i>),\n  /// An @layer prelude.\n  Layer(Vec<LayerName<'i>>),\n  /// An @property prelude.\n  Property(DashedIdent<'i>),\n  /// A @container prelude.\n  /// Spec: https://drafts.csswg.org/css-conditional-5/#container-rule\n  /// @container [ <container-name>? <container-query>? ]!\n  Container(Option<ContainerName<'i>>, Option<ContainerCondition<'i>>),\n  /// A @starting-style prelude.\n  StartingStyle,\n  /// A @scope rule prelude.\n  Scope(Option<SelectorList<'i>>, Option<SelectorList<'i>>),\n  /// A @view-transition rule prelude.\n  ViewTransition,\n  /// An unknown prelude.\n  Unknown(CowArcStr<'i>, TokenList<'i>),\n  /// A custom prelude.\n  Custom(T),\n}\n\nimpl<'i, T> AtRulePrelude<'i, T> {\n  // https://drafts.csswg.org/css-nesting/#conditionals\n  //     In addition to nested style rules, this specification allows nested group rules inside\n  //     of style rules: any at-rule whose body contains style rules can be nested inside of a\n  //     style rule as well.\n  fn allowed_in_style_rule(&self) -> bool {\n    match *self {\n      Self::Media(..)\n      | Self::Supports(..)\n      | Self::Container(..)\n      | Self::MozDocument\n      | Self::Layer(..)\n      | Self::StartingStyle\n      | Self::Scope(..)\n      | Self::Nest(..)\n      | Self::Unknown(..)\n      | Self::Custom(..) => true,\n\n      Self::Namespace(..)\n      | Self::FontFace\n      | Self::FontFeatureValues(..)\n      | Self::FontPaletteValues(..)\n      | Self::CounterStyle(..)\n      | Self::Keyframes(..)\n      | Self::Page(..)\n      | Self::Property(..)\n      | Self::Import(..)\n      | Self::CustomMedia(..)\n      | Self::Viewport(..)\n      | Self::Charset\n      | Self::ViewTransition => false,\n    }\n  }\n}\n\nimpl<'a, 'o, 'i, T: crate::traits::AtRuleParser<'i>> AtRuleParser<'i> for TopLevelRuleParser<'a, 'o, 'i, T> {\n  type Prelude = AtRulePrelude<'i, T::Prelude>;\n  type AtRule = ();\n  type Error = ParserError<'i>;\n\n  fn parse_prelude<'t>(\n    &mut self,\n    name: CowRcStr<'i>,\n    input: &mut Parser<'i, 't>,\n  ) -> Result<Self::Prelude, ParseError<'i, Self::Error>> {\n    match_ignore_ascii_case! { &*name,\n      \"import\" => {\n        if self.state > State::Imports {\n          return Err(input.new_custom_error(ParserError::UnexpectedImportRule))\n        }\n\n        let url_string = input.expect_url_or_string()?.clone();\n\n        let layer = if input.try_parse(|input| input.expect_ident_matching(\"layer\")).is_ok() {\n          Some(None)\n        } else if input.try_parse(|input| input.expect_function_matching(\"layer\")).is_ok() {\n          let name = input.parse_nested_block(LayerName::parse).map(|name| Some(name))?;\n          Some(name)\n        } else {\n          None\n        };\n\n        let supports = if input.try_parse(|input| input.expect_function_matching(\"supports\")).is_ok() {\n          Some(input.parse_nested_block(|input| {\n            input.try_parse(SupportsCondition::parse).or_else(|_| SupportsCondition::parse_declaration(input))\n          })?)\n        } else {\n          None\n        };\n        let media = MediaList::parse(input, &self.options)?;\n        return Ok(AtRulePrelude::Import(url_string, media, supports, layer));\n      },\n      \"namespace\" => {\n        if self.state > State::Namespaces {\n          return Err(input.new_custom_error(ParserError::UnexpectedNamespaceRule))\n        }\n\n        let prefix = input.try_parse(|input| input.expect_ident_cloned()).ok();\n        let namespace = input.expect_url_or_string()?;\n        let prelude = AtRulePrelude::Namespace(prefix, namespace);\n        return Ok(prelude);\n      },\n      \"charset\" => {\n        // @charset is removed by rust-cssparser if it’s the first rule in the stylesheet.\n        // Anything left is technically invalid, however, users often concatenate CSS files\n        // together, so we are more lenient and simply ignore @charset rules in the middle of a file.\n        input.expect_string()?;\n        return Ok(AtRulePrelude::Charset)\n      },\n      \"custom-media\" if self.options.flags.contains(ParserFlags::CUSTOM_MEDIA) => {\n        let name = DashedIdent::parse(input)?;\n        let media = MediaList::parse(input, &self.options)?;\n        return Ok(AtRulePrelude::CustomMedia(name, media))\n      },\n      _ => {}\n    }\n\n    AtRuleParser::parse_prelude(&mut self.nested(), name, input)\n  }\n\n  #[inline]\n  fn parse_block<'t>(\n    &mut self,\n    prelude: Self::Prelude,\n    start: &ParserState,\n    input: &mut Parser<'i, 't>,\n  ) -> Result<Self::AtRule, ParseError<'i, Self::Error>> {\n    self.state = State::Body;\n    AtRuleParser::parse_block(&mut self.nested(), prelude, start, input)\n  }\n\n  #[inline]\n  fn rule_without_block(\n    &mut self,\n    prelude: AtRulePrelude<'i, T::Prelude>,\n    start: &ParserState,\n  ) -> Result<Self::AtRule, ()> {\n    let loc = start.source_location();\n    let loc = Location {\n      source_index: self.options.source_index,\n      line: loc.line,\n      column: loc.column,\n    };\n\n    match prelude {\n      AtRulePrelude::Import(url, media, supports, layer) => {\n        self.state = State::Imports;\n        self.rules.0.push(CssRule::Import(ImportRule {\n          url: url.into(),\n          layer,\n          supports,\n          media,\n          loc,\n        }));\n        Ok(())\n      }\n      AtRulePrelude::Namespace(prefix, url) => {\n        self.state = State::Namespaces;\n\n        self.rules.0.push(CssRule::Namespace(NamespaceRule {\n          prefix: prefix.map(|x| x.into()),\n          url: url.into(),\n          loc,\n        }));\n        Ok(())\n      }\n      AtRulePrelude::CustomMedia(name, query) => {\n        self.state = State::Body;\n        self.rules.0.push(CssRule::CustomMedia(CustomMediaRule { name, query, loc }));\n        Ok(())\n      }\n      AtRulePrelude::Layer(_) => {\n        // @layer statements are allowed before @import rules, but cannot be interleaved.\n        if self.state <= State::Layers {\n          self.state = State::Layers;\n        } else {\n          self.state = State::Body;\n        }\n        AtRuleParser::rule_without_block(&mut self.nested(), prelude, start)\n      }\n      AtRulePrelude::Charset => Ok(()),\n      AtRulePrelude::Unknown(name, prelude) => {\n        self.rules.0.push(CssRule::Unknown(UnknownAtRule {\n          name,\n          prelude,\n          block: None,\n          loc,\n        }));\n        Ok(())\n      }\n      AtRulePrelude::Custom(_) => {\n        self.state = State::Body;\n        AtRuleParser::rule_without_block(&mut self.nested(), prelude, start)\n      }\n      _ => Err(()),\n    }\n  }\n}\n\nimpl<'a, 'o, 'i, T: crate::traits::AtRuleParser<'i>> QualifiedRuleParser<'i>\n  for TopLevelRuleParser<'a, 'o, 'i, T>\n{\n  type Prelude = SelectorList<'i>;\n  type QualifiedRule = ();\n  type Error = ParserError<'i>;\n\n  #[inline]\n  fn parse_prelude<'t>(\n    &mut self,\n    input: &mut Parser<'i, 't>,\n  ) -> Result<Self::Prelude, ParseError<'i, Self::Error>> {\n    self.state = State::Body;\n    QualifiedRuleParser::parse_prelude(&mut self.nested(), input)\n  }\n\n  #[inline]\n  fn parse_block<'t>(\n    &mut self,\n    prelude: Self::Prelude,\n    start: &ParserState,\n    input: &mut Parser<'i, 't>,\n  ) -> Result<Self::QualifiedRule, ParseError<'i, Self::Error>> {\n    QualifiedRuleParser::parse_block(&mut self.nested(), prelude, start, input)\n  }\n}\n\npub struct NestedRuleParser<'a, 'o, 'i, T: crate::traits::AtRuleParser<'i>> {\n  pub options: &'a ParserOptions<'o, 'i>,\n  pub at_rule_parser: &'a mut T,\n  declarations: DeclarationList<'i>,\n  important_declarations: DeclarationList<'i>,\n  rules: &'a mut CssRuleList<'i, T::AtRule>,\n  is_in_style_rule: bool,\n  allow_declarations: bool,\n}\n\nimpl<'a, 'o, 'b, 'i, T: crate::traits::AtRuleParser<'i>> NestedRuleParser<'a, 'o, 'i, T> {\n  pub fn parse_nested<'t>(\n    &mut self,\n    input: &mut Parser<'i, 't>,\n    is_style_rule: bool,\n  ) -> Result<(DeclarationBlock<'i>, CssRuleList<'i, T::AtRule>), ParseError<'i, ParserError<'i>>> {\n    let mut rules = CssRuleList(vec![]);\n    let mut nested_parser = NestedRuleParser {\n      options: self.options,\n      at_rule_parser: self.at_rule_parser,\n      declarations: DeclarationList::new(),\n      important_declarations: DeclarationList::new(),\n      rules: &mut rules,\n      is_in_style_rule: self.is_in_style_rule || is_style_rule,\n      allow_declarations: self.allow_declarations || self.is_in_style_rule || is_style_rule,\n    };\n\n    let parse_declarations = nested_parser.parse_declarations();\n    let mut errors = Vec::new();\n    let mut iter = RuleBodyParser::new(input, &mut nested_parser);\n    while let Some(result) = iter.next() {\n      match result {\n        Ok(()) => {}\n        Err((e, _)) => {\n          if parse_declarations {\n            iter.parser.declarations.clear();\n            iter.parser.important_declarations.clear();\n            errors.push(e);\n          } else {\n            if iter.parser.options.error_recovery {\n              iter.parser.options.warn(e);\n              continue;\n            }\n            return Err(e);\n          }\n        }\n      }\n    }\n\n    if parse_declarations {\n      if !errors.is_empty() {\n        if self.options.error_recovery {\n          for err in errors {\n            self.options.warn(err);\n          }\n        } else {\n          return Err(errors.remove(0));\n        }\n      }\n    }\n\n    Ok((\n      DeclarationBlock {\n        declarations: nested_parser.declarations,\n        important_declarations: nested_parser.important_declarations,\n      },\n      rules,\n    ))\n  }\n\n  fn parse_style_block<'t>(\n    &mut self,\n    input: &mut Parser<'i, 't>,\n  ) -> Result<CssRuleList<'i, T::AtRule>, ParseError<'i, ParserError<'i>>> {\n    let loc = input.current_source_location();\n    let loc = Location {\n      source_index: self.options.source_index,\n      line: loc.line,\n      column: loc.column,\n    };\n\n    // Declarations can be immediately within @media and @supports blocks that are nested within a parent style rule.\n    // These are wrapped in an (invisible) NestedDeclarationsRule.\n    let (declarations, mut rules) = self.parse_nested(input, false)?;\n\n    if declarations.len() > 0 {\n      rules.0.insert(\n        0,\n        CssRule::NestedDeclarations(NestedDeclarationsRule { declarations, loc }),\n      )\n    }\n\n    Ok(rules)\n  }\n\n  fn loc(&self, start: &ParserState) -> Location {\n    let loc = start.source_location();\n    Location {\n      source_index: self.options.source_index,\n      line: loc.line,\n      column: loc.column,\n    }\n  }\n}\n\nimpl<'a, 'o, 'b, 'i, T: crate::traits::AtRuleParser<'i>> AtRuleParser<'i> for NestedRuleParser<'a, 'o, 'i, T> {\n  type Prelude = AtRulePrelude<'i, T::Prelude>;\n  type AtRule = ();\n  type Error = ParserError<'i>;\n\n  fn parse_prelude<'t>(\n    &mut self,\n    name: CowRcStr<'i>,\n    input: &mut Parser<'i, 't>,\n  ) -> Result<Self::Prelude, ParseError<'i, Self::Error>> {\n    let result = match_ignore_ascii_case! { &*name,\n      \"media\" => {\n        let media = MediaList::parse(input, &self.options)?;\n        AtRulePrelude::Media(media)\n      },\n      \"supports\" => {\n        let cond = SupportsCondition::parse(input, )?;\n        AtRulePrelude::Supports(cond)\n      },\n      \"font-face\" => {\n        AtRulePrelude::FontFace\n      },\n      // \"font-feature-values\" => {\n      //     if !cfg!(feature = \"gecko\") {\n      //         // Support for this rule is not fully implemented in Servo yet.\n      //         return Err(input.new_custom_error(StyleParseErrorKind::UnsupportedAtRule(name.clone())))\n      //     }\n      //     let family_names = parse_family_name_list(self.context, input)?;\n      //     Ok(AtRuleType::WithBlock(AtRuleBlockPrelude::FontFeatureValues(family_names)))\n      // },\n      \"font-feature-values\" => {\n        let names = match Vec::<FamilyName>::parse(input) {\n          Ok(names) => names,\n          Err(e) => return Err(e)\n        };\n\n        AtRulePrelude::FontFeatureValues(names)\n      },\n      \"font-palette-values\" => {\n        let name = DashedIdent::parse(input)?;\n        AtRulePrelude::FontPaletteValues(name)\n      },\n      \"counter-style\" => {\n        let name = CustomIdent::parse(input)?;\n        AtRulePrelude::CounterStyle(name)\n      },\n      \"viewport\" | \"-ms-viewport\" => {\n        let prefix = if starts_with_ignore_ascii_case(&*name, \"-ms\") {\n          VendorPrefix::Ms\n        } else {\n          VendorPrefix::None\n        };\n        AtRulePrelude::Viewport(prefix)\n      },\n      \"keyframes\" | \"-webkit-keyframes\" | \"-moz-keyframes\" | \"-o-keyframes\" | \"-ms-keyframes\" => {\n        let prefix = if starts_with_ignore_ascii_case(&*name, \"-webkit-\") {\n          VendorPrefix::WebKit\n        } else if starts_with_ignore_ascii_case(&*name, \"-moz-\") {\n          VendorPrefix::Moz\n        } else if starts_with_ignore_ascii_case(&*name, \"-o-\") {\n          VendorPrefix::O\n        } else if starts_with_ignore_ascii_case(&*name, \"-ms-\") {\n          VendorPrefix::Ms\n        } else {\n          VendorPrefix::None\n        };\n\n        let name = input.try_parse(KeyframesName::parse)?;\n        AtRulePrelude::Keyframes(name, prefix)\n      },\n      \"page\" => {\n        let selectors = input.try_parse(|input| input.parse_comma_separated(PageSelector::parse)).unwrap_or_default();\n        AtRulePrelude::Page(selectors)\n      },\n      \"-moz-document\" => {\n        // Firefox only supports the url-prefix() function with no arguments as a legacy CSS hack.\n        // See https://css-tricks.com/snippets/css/css-hacks-targeting-firefox/\n        input.expect_function_matching(\"url-prefix\")?;\n        input.parse_nested_block(|input| {\n          // Firefox also allows an empty string as an argument...\n          // https://github.com/mozilla/gecko-dev/blob/0077f2248712a1b45bf02f0f866449f663538164/servo/components/style/stylesheets/document_rule.rs#L303\n          let _ = input.try_parse(|input| -> Result<(), ParseError<'i, Self::Error>> {\n            let s = input.expect_string()?;\n            if !s.is_empty() {\n              return Err(input.new_custom_error(ParserError::InvalidValue))\n            }\n            Ok(())\n          });\n          input.expect_exhausted()?;\n          Ok(())\n        })?;\n\n        AtRulePrelude::MozDocument\n      },\n      \"layer\" => {\n        let names = match Vec::<LayerName>::parse(input) {\n          Ok(names) => names,\n          Err(ParseError { kind: ParseErrorKind::Basic(BasicParseErrorKind::EndOfInput), .. }) => Vec::new(),\n          Err(e) => return Err(e)\n        };\n        AtRulePrelude::Layer(names)\n      },\n      \"container\" => {\n        let name = input.try_parse(ContainerName::parse).ok();\n        match input.try_parse(|input| ContainerCondition::parse_with_options(input, &self.options)) {\n          Ok(condition) => AtRulePrelude::Container(name, Some(condition)),\n          Err(e) => {\n            if name.is_some() && input.is_exhausted() {\n              // name only, no condition - allowed by new syntax\n              AtRulePrelude::Container(name, None)\n            } else {\n              // condition parsing failed (e.g., empty brackets or invalid tokens)\n              return Err(e);\n            }\n          }\n        }\n      },\n      \"starting-style\" => {\n        AtRulePrelude::StartingStyle\n      },\n      \"scope\" => {\n        let selector_parser = SelectorParser {\n          is_nesting_allowed: true,\n          options: &self.options,\n        };\n\n        let scope_start = if input.try_parse(|input| input.expect_parenthesis_block()).is_ok() {\n          Some(input.parse_nested_block(|input| {\n            // https://drafts.csswg.org/css-cascade-6/#scoped-rules\n            // TODO: disallow pseudo elements?\n            SelectorList::parse_relative(&selector_parser, input, ParseErrorRecovery::IgnoreInvalidSelector, NestingRequirement::None)\n          })?)\n        } else {\n          None\n        };\n\n        let scope_end = if input.try_parse(|input| input.expect_ident_matching(\"to\")).is_ok() {\n          input.expect_parenthesis_block()?;\n          Some(input.parse_nested_block(|input| {\n            SelectorList::parse_relative(&selector_parser, input, ParseErrorRecovery::IgnoreInvalidSelector, NestingRequirement::None)\n          })?)\n        } else {\n          None\n        };\n\n        AtRulePrelude::Scope(scope_start, scope_end)\n      },\n      \"view-transition\" => {\n        AtRulePrelude::ViewTransition\n      },\n      \"nest\" if self.is_in_style_rule => {\n        self.options.warn(input.new_custom_error(ParserError::DeprecatedNestRule));\n        let selector_parser = SelectorParser {\n          is_nesting_allowed: true,\n          options: &self.options,\n        };\n        let selectors = SelectorList::parse(&selector_parser, input, ParseErrorRecovery::DiscardList, NestingRequirement::Contained)?;\n        AtRulePrelude::Nest(selectors)\n      },\n\n      \"value\" if self.options.css_modules.is_some() => {\n        return Err(input.new_custom_error(ParserError::DeprecatedCssModulesValueRule));\n      },\n\n      \"property\" => {\n        let name = DashedIdent::parse(input)?;\n        return Ok(AtRulePrelude::Property(name))\n      },\n\n      _ => parse_custom_at_rule_prelude(&name, input, self.options, self.at_rule_parser)?\n    };\n\n    if self.is_in_style_rule && !result.allowed_in_style_rule() {\n      return Err(input.new_error(BasicParseErrorKind::AtRuleInvalid(name.clone())));\n    }\n\n    Ok(result)\n  }\n\n  #[inline]\n  fn rule_without_block(\n    &mut self,\n    prelude: AtRulePrelude<'i, T::Prelude>,\n    start: &ParserState,\n  ) -> Result<Self::AtRule, ()> {\n    let loc = self.loc(start);\n    match prelude {\n      AtRulePrelude::Layer(names) => {\n        if self.is_in_style_rule || names.is_empty() {\n          return Err(());\n        }\n\n        self.rules.0.push(CssRule::LayerStatement(LayerStatementRule { names, loc }));\n        Ok(())\n      }\n      AtRulePrelude::Unknown(name, prelude) => {\n        self.rules.0.push(CssRule::Unknown(UnknownAtRule {\n          name,\n          prelude,\n          block: None,\n          loc,\n        }));\n        Ok(())\n      }\n      AtRulePrelude::Custom(prelude) => {\n        self.rules.0.push(parse_custom_at_rule_without_block(\n          prelude,\n          start,\n          self.options,\n          self.at_rule_parser,\n          self.is_in_style_rule,\n        )?);\n        Ok(())\n      }\n      _ => Err(()),\n    }\n  }\n\n  fn parse_block<'t>(\n    &mut self,\n    prelude: Self::Prelude,\n    start: &ParserState,\n    input: &mut Parser<'i, 't>,\n  ) -> Result<(), ParseError<'i, Self::Error>> {\n    let loc = self.loc(start);\n    match prelude {\n      AtRulePrelude::FontFace => {\n        let mut decl_parser = FontFaceDeclarationParser;\n        let mut parser = RuleBodyParser::new(input, &mut decl_parser);\n        let mut properties = vec![];\n        while let Some(decl) = parser.next() {\n          if let Ok(decl) = decl {\n            properties.push(decl);\n          }\n        }\n        self.rules.0.push(CssRule::FontFace(FontFaceRule { properties, loc }));\n        Ok(())\n      }\n      // AtRuleBlockPrelude::FontFeatureValues(family_names) => {\n      //     let context = ParserContext::new_with_rule_type(\n      //         self.context,\n      //         CssRuleType::FontFeatureValues,\n      //         self.namespaces,\n      //     );\n\n      //     Ok(CssRule::FontFeatureValues(Arc::new(self.shared_lock.wrap(\n      //         FontFeatureValuesRule::parse(\n      //             &context,\n      //             input,\n      //             family_names,\n      //             start.source_location(),\n      //         ),\n      //     ))))\n      // },\n      AtRulePrelude::FontPaletteValues(name) => {\n        let rule = FontPaletteValuesRule::parse(name, input, loc)?;\n        self.rules.0.push(CssRule::FontPaletteValues(rule));\n        Ok(())\n      }\n      AtRulePrelude::CounterStyle(name) => {\n        self.rules.0.push(CssRule::CounterStyle(CounterStyleRule {\n          name,\n          declarations: DeclarationBlock::parse(input, self.options)?,\n          loc,\n        }));\n        Ok(())\n      }\n      AtRulePrelude::Media(query) => {\n        let rules = self.parse_style_block(input)?;\n        self.rules.0.push(CssRule::Media(MediaRule { query, rules, loc }));\n        Ok(())\n      }\n      AtRulePrelude::Supports(condition) => {\n        let rules = self.parse_style_block(input)?;\n        self.rules.0.push(CssRule::Supports(SupportsRule { condition, rules, loc }));\n        Ok(())\n      }\n      AtRulePrelude::Container(name, condition) => {\n        let rules = self.parse_style_block(input)?;\n        self.rules.0.push(CssRule::Container(ContainerRule {\n          name,\n          condition,\n          rules,\n          loc,\n        }));\n        Ok(())\n      }\n      AtRulePrelude::Scope(scope_start, scope_end) => {\n        let rules = self.parse_style_block(input)?;\n        self.rules.0.push(CssRule::Scope(ScopeRule {\n          scope_start,\n          scope_end,\n          rules,\n          loc,\n        }));\n        Ok(())\n      }\n      AtRulePrelude::Viewport(vendor_prefix) => {\n        self.rules.0.push(CssRule::Viewport(ViewportRule {\n          vendor_prefix,\n          // TODO: parse viewport descriptors rather than properties\n          // https://drafts.csswg.org/css-device-adapt/#viewport-desc\n          declarations: DeclarationBlock::parse(input, self.options)?,\n          loc,\n        }));\n        Ok(())\n      }\n      AtRulePrelude::Keyframes(name, vendor_prefix) => {\n        let mut parser = KeyframeListParser;\n        let iter = RuleBodyParser::new(input, &mut parser);\n        self.rules.0.push(CssRule::Keyframes(KeyframesRule {\n          name,\n          keyframes: iter.filter_map(Result::ok).collect(),\n          vendor_prefix,\n          loc,\n        }));\n        Ok(())\n      }\n      AtRulePrelude::Page(selectors) => {\n        let rule = PageRule::parse(selectors, input, loc, self.options)?;\n        self.rules.0.push(CssRule::Page(rule));\n        Ok(())\n      }\n      AtRulePrelude::MozDocument => {\n        let rules = self.parse_style_block(input)?;\n        self.rules.0.push(CssRule::MozDocument(MozDocumentRule { rules, loc }));\n        Ok(())\n      }\n      AtRulePrelude::Layer(names) => {\n        let name = if names.is_empty() {\n          None\n        } else if names.len() == 1 {\n          names.into_iter().next()\n        } else {\n          return Err(input.new_error(BasicParseErrorKind::AtRuleBodyInvalid));\n        };\n\n        let rules = self.parse_style_block(input)?;\n        self.rules.0.push(CssRule::LayerBlock(LayerBlockRule { name, rules, loc }));\n        Ok(())\n      }\n      AtRulePrelude::Property(name) => {\n        self.rules.0.push(CssRule::Property(PropertyRule::parse(name, input, loc)?));\n        Ok(())\n      }\n      AtRulePrelude::Import(..)\n      | AtRulePrelude::Namespace(..)\n      | AtRulePrelude::CustomMedia(..)\n      | AtRulePrelude::Charset => {\n        // These rules don't have blocks.\n        Err(input.new_unexpected_token_error(Token::CurlyBracketBlock))\n      }\n      AtRulePrelude::StartingStyle => {\n        let rules = self.parse_style_block(input)?;\n        self.rules.0.push(CssRule::StartingStyle(StartingStyleRule { rules, loc }));\n        Ok(())\n      }\n      AtRulePrelude::ViewTransition => {\n        self\n          .rules\n          .0\n          .push(CssRule::ViewTransition(ViewTransitionRule::parse(input, loc)?));\n        Ok(())\n      }\n      AtRulePrelude::Nest(selectors) => {\n        let (declarations, rules) = self.parse_nested(input, true)?;\n        self.rules.0.push(CssRule::Nesting(NestingRule {\n          style: StyleRule {\n            selectors,\n            declarations,\n            vendor_prefix: VendorPrefix::empty(),\n            rules,\n            loc,\n          },\n          loc,\n        }));\n        Ok(())\n      }\n      AtRulePrelude::FontFeatureValues(family_names) => {\n        let rule = FontFeatureValuesRule::parse(family_names, input, loc, self.options)?;\n        self.rules.0.push(CssRule::FontFeatureValues(rule));\n        Ok(())\n      }\n      AtRulePrelude::Unknown(name, prelude) => {\n        self.rules.0.push(CssRule::Unknown(UnknownAtRule {\n          name,\n          prelude,\n          block: Some(TokenList::parse(input, &self.options, 0)?),\n          loc,\n        }));\n        Ok(())\n      }\n      AtRulePrelude::Custom(prelude) => {\n        self.rules.0.push(parse_custom_at_rule_body(\n          prelude,\n          input,\n          start,\n          self.options,\n          self.at_rule_parser,\n          self.is_in_style_rule,\n        )?);\n        Ok(())\n      }\n    }\n  }\n}\n\nimpl<'a, 'o, 'b, 'i, T: crate::traits::AtRuleParser<'i>> QualifiedRuleParser<'i>\n  for NestedRuleParser<'a, 'o, 'i, T>\n{\n  type Prelude = SelectorList<'i>;\n  type QualifiedRule = ();\n  type Error = ParserError<'i>;\n\n  fn parse_prelude<'t>(\n    &mut self,\n    input: &mut Parser<'i, 't>,\n  ) -> Result<Self::Prelude, ParseError<'i, Self::Error>> {\n    let selector_parser = SelectorParser {\n      is_nesting_allowed: true,\n      options: &self.options,\n    };\n    if self.is_in_style_rule {\n      SelectorList::parse_relative(\n        &selector_parser,\n        input,\n        ParseErrorRecovery::DiscardList,\n        NestingRequirement::Implicit,\n      )\n    } else {\n      SelectorList::parse(\n        &selector_parser,\n        input,\n        ParseErrorRecovery::DiscardList,\n        NestingRequirement::None,\n      )\n    }\n  }\n\n  fn parse_block<'t>(\n    &mut self,\n    selectors: Self::Prelude,\n    start: &ParserState,\n    input: &mut Parser<'i, 't>,\n  ) -> Result<(), ParseError<'i, Self::Error>> {\n    let loc = self.loc(start);\n    let (declarations, rules) = self.parse_nested(input, true)?;\n    self.rules.0.push(CssRule::Style(StyleRule {\n      selectors,\n      vendor_prefix: VendorPrefix::empty(),\n      declarations,\n      rules,\n      loc,\n    }));\n    Ok(())\n  }\n}\n\n/// Parse a declaration within {} block: `color: blue`\nimpl<'a, 'o, 'i, T: crate::traits::AtRuleParser<'i>> cssparser::DeclarationParser<'i>\n  for NestedRuleParser<'a, 'o, 'i, T>\n{\n  type Declaration = ();\n  type Error = ParserError<'i>;\n\n  fn parse_value<'t>(\n    &mut self,\n    name: CowRcStr<'i>,\n    input: &mut cssparser::Parser<'i, 't>,\n  ) -> Result<Self::Declaration, cssparser::ParseError<'i, Self::Error>> {\n    if self.rules.0.is_empty() {\n      parse_declaration(\n        name,\n        input,\n        &mut self.declarations,\n        &mut self.important_declarations,\n        &self.options,\n      )\n    } else if let Some(CssRule::NestedDeclarations(last)) = self.rules.0.last_mut() {\n      parse_declaration(\n        name,\n        input,\n        &mut last.declarations.declarations,\n        &mut last.declarations.important_declarations,\n        &self.options,\n      )\n    } else {\n      let loc = self.loc(&input.state());\n      let mut nested = NestedDeclarationsRule {\n        declarations: DeclarationBlock::new(),\n        loc,\n      };\n\n      parse_declaration(\n        name,\n        input,\n        &mut nested.declarations.declarations,\n        &mut nested.declarations.important_declarations,\n        &self.options,\n      )?;\n\n      self.rules.0.push(CssRule::NestedDeclarations(nested));\n      Ok(())\n    }\n  }\n}\n\nimpl<'a, 'o, 'b, 'i, T: crate::traits::AtRuleParser<'i>> RuleBodyItemParser<'i, (), ParserError<'i>>\n  for NestedRuleParser<'a, 'o, 'i, T>\n{\n  fn parse_qualified(&self) -> bool {\n    true\n  }\n\n  fn parse_declarations(&self) -> bool {\n    self.allow_declarations\n  }\n}\n\nfn parse_custom_at_rule_prelude<'i, 't, T: crate::traits::AtRuleParser<'i>>(\n  name: &CowRcStr<'i>,\n  input: &mut Parser<'i, 't>,\n  options: &ParserOptions<'_, 'i>,\n  at_rule_parser: &mut T,\n) -> Result<AtRulePrelude<'i, T::Prelude>, ParseError<'i, ParserError<'i>>> {\n  match at_rule_parser.parse_prelude(name.clone(), input, options) {\n    Ok(prelude) => return Ok(AtRulePrelude::Custom(prelude)),\n    Err(ParseError {\n      kind: ParseErrorKind::Basic(BasicParseErrorKind::AtRuleInvalid(..)),\n      ..\n    }) => {}\n    Err(err) => {\n      return Err(match &err.kind {\n        ParseErrorKind::Basic(kind) => ParseError {\n          kind: ParseErrorKind::Basic(kind.clone()),\n          location: err.location,\n        },\n        _ => input.new_custom_error(ParserError::AtRulePreludeInvalid),\n      })\n    }\n  }\n\n  options.warn(input.new_error(BasicParseErrorKind::AtRuleInvalid(name.clone())));\n  input.skip_whitespace();\n  let tokens = TokenList::parse(input, &options, 0)?;\n  Ok(AtRulePrelude::Unknown(name.into(), tokens))\n}\n\nfn parse_custom_at_rule_body<'i, 't, T: crate::traits::AtRuleParser<'i>>(\n  prelude: T::Prelude,\n  input: &mut Parser<'i, 't>,\n  start: &ParserState,\n  options: &ParserOptions<'_, 'i>,\n  at_rule_parser: &mut T,\n  is_nested: bool,\n) -> Result<CssRule<'i, T::AtRule>, ParseError<'i, ParserError<'i>>> {\n  at_rule_parser\n    .parse_block(prelude, start, input, options, is_nested)\n    .map(|prelude| CssRule::Custom(prelude))\n    .map_err(|err| match &err.kind {\n      ParseErrorKind::Basic(kind) => ParseError {\n        kind: ParseErrorKind::Basic(kind.clone()),\n        location: err.location,\n      },\n      _ => input.new_error(BasicParseErrorKind::AtRuleBodyInvalid),\n    })\n}\n\nfn parse_custom_at_rule_without_block<'i, 't, T: crate::traits::AtRuleParser<'i>>(\n  prelude: T::Prelude,\n  start: &ParserState,\n  options: &ParserOptions<'_, 'i>,\n  at_rule_parser: &mut T,\n  is_nested: bool,\n) -> Result<CssRule<'i, T::AtRule>, ()> {\n  at_rule_parser\n    .rule_without_block(prelude, start, options, is_nested)\n    .map(|prelude| CssRule::Custom(prelude))\n}\n\npub fn parse_rule_list<'a, 'o, 'i, 't, T: crate::traits::AtRuleParser<'i>>(\n  input: &mut Parser<'i, 't>,\n  options: &'a ParserOptions<'o, 'i>,\n  at_rule_parser: &mut T,\n) -> Result<CssRuleList<'i, T::AtRule>, ParseError<'i, ParserError<'i>>> {\n  let mut parser = NestedRuleParser {\n    options,\n    at_rule_parser,\n    declarations: DeclarationList::new(),\n    important_declarations: DeclarationList::new(),\n    rules: &mut CssRuleList(Vec::new()),\n    is_in_style_rule: false,\n    allow_declarations: false,\n  };\n\n  let (_, rules) = parser.parse_nested(input, false)?;\n  Ok(rules)\n}\n\npub fn parse_style_block<'a, 'o, 'i, 't, T: crate::traits::AtRuleParser<'i>>(\n  input: &mut Parser<'i, 't>,\n  options: &'a ParserOptions<'o, 'i>,\n  at_rule_parser: &mut T,\n  is_nested: bool,\n) -> Result<CssRuleList<'i, T::AtRule>, ParseError<'i, ParserError<'i>>> {\n  let mut parser = NestedRuleParser {\n    options,\n    at_rule_parser,\n    declarations: DeclarationList::new(),\n    important_declarations: DeclarationList::new(),\n    rules: &mut CssRuleList(Vec::new()),\n    is_in_style_rule: is_nested,\n    allow_declarations: true,\n  };\n\n  parser.parse_style_block(input)\n}\n\n#[inline]\npub fn starts_with_ignore_ascii_case(string: &str, prefix: &str) -> bool {\n  string.len() >= prefix.len() && string.as_bytes()[0..prefix.len()].eq_ignore_ascii_case(prefix.as_bytes())\n}\n"
  },
  {
    "path": "src/prefixes.rs",
    "content": "// This file is autogenerated by build-prefixes.js. DO NOT EDIT!\n\nuse crate::targets::Browsers;\nuse crate::vendor_prefix::VendorPrefix;\n\n#[allow(dead_code)]\npub enum Feature {\n  AlignContent,\n  AlignItems,\n  AlignSelf,\n  Animation,\n  AnimationDelay,\n  AnimationDirection,\n  AnimationDuration,\n  AnimationFillMode,\n  AnimationIterationCount,\n  AnimationName,\n  AnimationPlayState,\n  AnimationTimingFunction,\n  AnyPseudo,\n  Appearance,\n  AtKeyframes,\n  AtResolution,\n  AtViewport,\n  BackdropFilter,\n  BackfaceVisibility,\n  BackgroundClip,\n  BackgroundOrigin,\n  BackgroundSize,\n  BorderBlockEnd,\n  BorderBlockStart,\n  BorderBottomLeftRadius,\n  BorderBottomRightRadius,\n  BorderImage,\n  BorderInlineEnd,\n  BorderInlineStart,\n  BorderRadius,\n  BorderTopLeftRadius,\n  BorderTopRightRadius,\n  BoxDecorationBreak,\n  BoxShadow,\n  BoxSizing,\n  BreakAfter,\n  BreakBefore,\n  BreakInside,\n  Calc,\n  ClipPath,\n  ColorAdjust,\n  ColumnCount,\n  ColumnFill,\n  ColumnGap,\n  ColumnRule,\n  ColumnRuleColor,\n  ColumnRuleStyle,\n  ColumnRuleWidth,\n  ColumnSpan,\n  ColumnWidth,\n  Columns,\n  CrossFade,\n  DisplayFlex,\n  DisplayGrid,\n  Element,\n  Fill,\n  FillAvailable,\n  Filter,\n  FilterFunction,\n  FitContent,\n  Flex,\n  FlexBasis,\n  FlexDirection,\n  FlexFlow,\n  FlexGrow,\n  FlexShrink,\n  FlexWrap,\n  FlowFrom,\n  FlowInto,\n  FontFeatureSettings,\n  FontKerning,\n  FontLanguageOverride,\n  FontVariantLigatures,\n  Grab,\n  Grabbing,\n  GridArea,\n  GridColumn,\n  GridColumnAlign,\n  GridColumnEnd,\n  GridColumnStart,\n  GridRow,\n  GridRowAlign,\n  GridRowEnd,\n  GridRowStart,\n  GridTemplate,\n  GridTemplateAreas,\n  GridTemplateColumns,\n  GridTemplateRows,\n  Hyphens,\n  ImageRendering,\n  ImageSet,\n  InlineFlex,\n  InlineGrid,\n  Isolate,\n  IsolateOverride,\n  JustifyContent,\n  LinearGradient,\n  MarginBlockEnd,\n  MarginBlockStart,\n  MarginInlineEnd,\n  MarginInlineStart,\n  Mask,\n  MaskBorder,\n  MaskBorderOutset,\n  MaskBorderRepeat,\n  MaskBorderSlice,\n  MaskBorderSource,\n  MaskBorderWidth,\n  MaskClip,\n  MaskComposite,\n  MaskImage,\n  MaskOrigin,\n  MaskPosition,\n  MaskRepeat,\n  MaskSize,\n  MaxContent,\n  MinContent,\n  ObjectFit,\n  ObjectPosition,\n  Order,\n  OverscrollBehavior,\n  PaddingBlockEnd,\n  PaddingBlockStart,\n  PaddingInlineEnd,\n  PaddingInlineStart,\n  Perspective,\n  PerspectiveOrigin,\n  Pixelated,\n  PlaceSelf,\n  Plaintext,\n  PrintColorAdjust,\n  PseudoClassAnyLink,\n  PseudoClassAutofill,\n  PseudoClassFullscreen,\n  PseudoClassPlaceholderShown,\n  PseudoClassReadOnly,\n  PseudoClassReadWrite,\n  PseudoElementBackdrop,\n  PseudoElementFileSelectorButton,\n  PseudoElementPlaceholder,\n  PseudoElementSelection,\n  RadialGradient,\n  RegionFragment,\n  RepeatingLinearGradient,\n  RepeatingRadialGradient,\n  ScrollSnapCoordinate,\n  ScrollSnapDestination,\n  ScrollSnapPointsX,\n  ScrollSnapPointsY,\n  ScrollSnapType,\n  ShapeImageThreshold,\n  ShapeMargin,\n  ShapeOutside,\n  Sticky,\n  Stretch,\n  TabSize,\n  TextAlignLast,\n  TextDecoration,\n  TextDecorationColor,\n  TextDecorationLine,\n  TextDecorationSkip,\n  TextDecorationSkipInk,\n  TextDecorationStyle,\n  TextEmphasis,\n  TextEmphasisColor,\n  TextEmphasisPosition,\n  TextEmphasisStyle,\n  TextOrientation,\n  TextOverflow,\n  TextSizeAdjust,\n  TextSpacing,\n  TouchAction,\n  Transform,\n  TransformOrigin,\n  TransformStyle,\n  Transition,\n  TransitionDelay,\n  TransitionDuration,\n  TransitionProperty,\n  TransitionTimingFunction,\n  UserSelect,\n  WritingMode,\n  ZoomIn,\n  ZoomOut,\n}\n\nimpl Feature {\n  pub fn prefixes_for(&self, browsers: Browsers) -> VendorPrefix {\n    let mut prefixes = VendorPrefix::None;\n    match self {\n      Feature::BorderRadius\n      | Feature::BorderTopLeftRadius\n      | Feature::BorderTopRightRadius\n      | Feature::BorderBottomRightRadius\n      | Feature::BorderBottomLeftRadius => {\n        if let Some(version) = browsers.android {\n          if version <= 131328 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version <= 262144 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version >= 131072 && version <= 198144 {\n            prefixes |= VendorPrefix::Moz;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version <= 197120 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 196864 && version <= 262144 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::BoxShadow => {\n        if let Some(version) = browsers.android {\n          if version >= 131328 && version <= 196608 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version >= 262144 && version <= 589824 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version >= 197888 && version <= 198144 {\n            prefixes |= VendorPrefix::Moz;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version >= 197120 && version <= 262656 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 196864 && version <= 327680 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::Animation\n      | Feature::AnimationName\n      | Feature::AnimationDuration\n      | Feature::AnimationDelay\n      | Feature::AnimationDirection\n      | Feature::AnimationFillMode\n      | Feature::AnimationIterationCount\n      | Feature::AnimationPlayState\n      | Feature::AnimationTimingFunction\n      | Feature::AtKeyframes => {\n        if let Some(version) = browsers.android {\n          if version >= 131328 && version <= 263171 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version >= 262144 && version <= 2752512 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version >= 327680 && version <= 983040 {\n            prefixes |= VendorPrefix::Moz;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version >= 197120 && version <= 524544 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version == 786432 {\n            prefixes |= VendorPrefix::O;\n          }\n          if version >= 983040 && version <= 1900544 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 262144 && version <= 524288 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::Transition\n      | Feature::TransitionProperty\n      | Feature::TransitionDuration\n      | Feature::TransitionDelay\n      | Feature::TransitionTimingFunction => {\n        if let Some(version) = browsers.android {\n          if version >= 131328 && version <= 262656 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version >= 262144 && version <= 1638400 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version >= 262144 && version <= 983040 {\n            prefixes |= VendorPrefix::Moz;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version >= 197120 && version <= 393216 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version >= 655360 && version <= 786432 {\n            prefixes |= VendorPrefix::O;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 196864 && version <= 393216 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::Transform | Feature::TransformOrigin => {\n        if let Some(version) = browsers.android {\n          if version >= 131328 && version <= 263171 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version >= 262144 && version <= 2293760 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version >= 197888 && version <= 983040 {\n            prefixes |= VendorPrefix::Moz;\n          }\n        }\n        if let Some(version) = browsers.ie {\n          if version <= 589824 {\n            prefixes |= VendorPrefix::Ms;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version >= 197120 && version <= 524544 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version >= 656640 && version <= 786432 {\n            prefixes |= VendorPrefix::O;\n          }\n          if version >= 983040 && version <= 1441792 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 196864 && version <= 524288 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::Perspective | Feature::PerspectiveOrigin | Feature::TransformStyle => {\n        if let Some(version) = browsers.android {\n          if version >= 196608 && version <= 263171 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version >= 786432 && version <= 2293760 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version >= 655360 && version <= 983040 {\n            prefixes |= VendorPrefix::Moz;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version >= 197120 && version <= 524544 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version >= 983040 && version <= 1441792 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 262144 && version <= 524288 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::BackfaceVisibility => {\n        if let Some(version) = browsers.android {\n          if version >= 196608 && version <= 263171 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version >= 786432 && version <= 2293760 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version >= 655360 && version <= 983040 {\n            prefixes |= VendorPrefix::Moz;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version >= 197120 && version <= 983552 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version >= 983040 && version <= 1441792 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 262144 && version <= 983552 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::LinearGradient\n      | Feature::RepeatingLinearGradient\n      | Feature::RadialGradient\n      | Feature::RepeatingRadialGradient => {\n        if let Some(version) = browsers.android {\n          if version >= 131328 && version <= 262656 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version >= 262144 && version <= 1638400 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version >= 198144 && version <= 983040 {\n            prefixes |= VendorPrefix::Moz;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version >= 197120 && version <= 393216 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version >= 721152 && version <= 786432 {\n            prefixes |= VendorPrefix::O;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 262144 && version <= 393216 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::BoxSizing => {\n        if let Some(version) = browsers.android {\n          if version >= 131328 && version <= 196608 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version >= 262144 && version <= 589824 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version >= 131072 && version <= 1835008 {\n            prefixes |= VendorPrefix::Moz;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version >= 197120 && version <= 262656 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 196864 && version <= 327680 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::Filter => {\n        if let Some(version) = browsers.android {\n          if version >= 263168 && version <= 263171 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version >= 1179648 && version <= 3407872 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version >= 393216 && version <= 589824 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version >= 983040 && version <= 2555904 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 393216 && version <= 589824 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version >= 262144 && version <= 393728 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::FilterFunction => {\n        if let Some(version) = browsers.ios_saf {\n          if version >= 589824 && version <= 590592 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version <= 589824 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::BackdropFilter => {\n        if let Some(version) = browsers.edge {\n          if version >= 1114112 && version <= 1179648 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version >= 589824 && version <= 1115648 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 589824 && version <= 1115648 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::Element => {\n        if let Some(version) = browsers.firefox {\n          if version >= 131072 {\n            prefixes |= VendorPrefix::Moz;\n          }\n        }\n      }\n      Feature::Columns\n      | Feature::ColumnWidth\n      | Feature::ColumnGap\n      | Feature::ColumnRule\n      | Feature::ColumnRuleColor\n      | Feature::ColumnRuleWidth\n      | Feature::ColumnCount\n      | Feature::ColumnRuleStyle\n      | Feature::ColumnSpan\n      | Feature::ColumnFill => {\n        if let Some(version) = browsers.android {\n          if version >= 131328 && version <= 263171 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version >= 262144 && version <= 3211264 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version >= 131072 && version <= 3342336 {\n            prefixes |= VendorPrefix::Moz;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version >= 197120 && version <= 524544 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version >= 983040 && version <= 2359296 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 196864 && version <= 524288 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version <= 262144 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::BreakBefore | Feature::BreakAfter | Feature::BreakInside => {\n        if let Some(version) = browsers.android {\n          if version >= 131328 && version <= 263171 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version >= 262144 && version <= 3211264 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version >= 197120 && version <= 524544 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version >= 983040 && version <= 2359296 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 196864 && version <= 524288 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version <= 262144 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::UserSelect => {\n        if let Some(version) = browsers.android {\n          if version >= 131328 && version <= 263171 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version >= 262144 && version <= 3473408 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version >= 786432 && version <= 1179648 {\n            prefixes |= VendorPrefix::Ms;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version >= 131072 && version <= 4456448 {\n            prefixes |= VendorPrefix::Moz;\n          }\n        }\n        if let Some(version) = browsers.ie {\n          if version >= 655360 {\n            prefixes |= VendorPrefix::Ms;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version >= 197120 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version >= 983040 && version <= 2621440 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 196864 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version >= 262144 && version <= 327680 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::DisplayFlex\n      | Feature::InlineFlex\n      | Feature::Flex\n      | Feature::FlexGrow\n      | Feature::FlexShrink\n      | Feature::FlexBasis\n      | Feature::FlexDirection\n      | Feature::FlexWrap\n      | Feature::FlexFlow\n      | Feature::JustifyContent\n      | Feature::Order\n      | Feature::AlignItems\n      | Feature::AlignSelf\n      | Feature::AlignContent => {\n        if let Some(version) = browsers.android {\n          if version >= 131328 && version <= 262656 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version >= 262144 && version <= 1835008 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version >= 131072 && version <= 1376256 {\n            prefixes |= VendorPrefix::Moz;\n          }\n        }\n        if let Some(version) = browsers.ie {\n          if version == 655360 {\n            prefixes |= VendorPrefix::Ms;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version >= 197120 && version <= 524544 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version >= 983040 && version <= 1048576 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 196864 && version <= 524288 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::Calc => {\n        if let Some(version) = browsers.chrome {\n          if version >= 1245184 && version <= 1638400 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version >= 262144 && version <= 983040 {\n            prefixes |= VendorPrefix::Moz;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version <= 393216 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version <= 393216 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::BackgroundOrigin | Feature::BackgroundSize => {\n        if let Some(version) = browsers.android {\n          if version >= 131328 && version <= 131840 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version <= 198144 {\n            prefixes |= VendorPrefix::Moz;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version <= 655360 {\n            prefixes |= VendorPrefix::O;\n          }\n        }\n      }\n      Feature::BackgroundClip => {\n        if let Some(version) = browsers.android {\n          if version >= 262144 && version <= 263171 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version >= 262144 && version <= 7798784 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version >= 786432 && version <= 917504 {\n            prefixes |= VendorPrefix::Ms;\n          }\n          if version >= 5177344 && version <= 7798784 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version >= 983040 && version <= 6881280 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 197120 && version <= 851968 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version >= 262144 && version <= 1572864 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version >= 262144 && version <= 851968 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::FontFeatureSettings | Feature::FontVariantLigatures | Feature::FontLanguageOverride => {\n        if let Some(version) = browsers.android {\n          if version >= 263168 && version <= 263171 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version >= 1048576 && version <= 3080192 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version >= 262144 && version <= 2162688 {\n            prefixes |= VendorPrefix::Moz;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version >= 983040 && version <= 2228224 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version <= 262144 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::FontKerning => {\n        if let Some(version) = browsers.android {\n          if version <= 263168 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version >= 1900544 && version <= 2097152 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version >= 524288 && version <= 721664 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version >= 1048576 && version <= 1245184 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 458752 && version <= 589824 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::BorderImage => {\n        if let Some(version) = browsers.android {\n          if version >= 131328 && version <= 262656 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version >= 262144 && version <= 917504 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version >= 197888 && version <= 917504 {\n            prefixes |= VendorPrefix::Moz;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version >= 197120 && version <= 327680 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version >= 720896 && version <= 786688 {\n            prefixes |= VendorPrefix::O;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 196864 && version <= 327936 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::PseudoElementSelection => {\n        if let Some(version) = browsers.firefox {\n          if version >= 131072 && version <= 3997696 {\n            prefixes |= VendorPrefix::Moz;\n          }\n        }\n      }\n      Feature::PseudoElementPlaceholder => {\n        if let Some(version) = browsers.android {\n          if version >= 131328 && version <= 263171 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version >= 262144 && version <= 3670016 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version >= 786432 && version <= 1179648 {\n            prefixes |= VendorPrefix::Ms;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version >= 1179648 && version <= 3276800 {\n            prefixes |= VendorPrefix::Moz;\n          }\n        }\n        if let Some(version) = browsers.ie {\n          if version >= 655360 {\n            prefixes |= VendorPrefix::Ms;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version >= 262656 && version <= 655360 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version >= 983040 && version <= 2818048 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 327680 && version <= 655360 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version >= 262144 && version <= 393728 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::PseudoClassPlaceholderShown => {\n        if let Some(version) = browsers.firefox {\n          if version >= 262144 && version <= 3276800 {\n            prefixes |= VendorPrefix::Moz;\n          }\n        }\n        if let Some(version) = browsers.ie {\n          if version >= 655360 {\n            prefixes |= VendorPrefix::Ms;\n          }\n        }\n      }\n      Feature::Hyphens => {\n        if let Some(version) = browsers.edge {\n          if version >= 786432 && version <= 1179648 {\n            prefixes |= VendorPrefix::Ms;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version >= 393216 && version <= 2752512 {\n            prefixes |= VendorPrefix::Moz;\n          }\n        }\n        if let Some(version) = browsers.ie {\n          if version >= 655360 {\n            prefixes |= VendorPrefix::Ms;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version >= 262656 && version <= 1050112 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 327936 && version <= 1050112 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::PseudoClassFullscreen => {\n        if let Some(version) = browsers.chrome {\n          if version >= 983040 && version <= 4587520 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version >= 655360 && version <= 4128768 {\n            prefixes |= VendorPrefix::Moz;\n          }\n        }\n        if let Some(version) = browsers.ie {\n          if version >= 720896 {\n            prefixes |= VendorPrefix::Ms;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version >= 983040 && version <= 4128768 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 327936 && version <= 1049344 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version >= 262144 && version <= 590336 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::PseudoElementBackdrop => {\n        if let Some(version) = browsers.android {\n          if version >= 263168 && version <= 263171 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version >= 2097152 && version <= 2359296 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version >= 786432 && version <= 1179648 {\n            prefixes |= VendorPrefix::Ms;\n          }\n        }\n        if browsers.ie.is_some() {\n          prefixes |= VendorPrefix::Ms;\n        }\n        if let Some(version) = browsers.opera {\n          if version >= 1245184 && version <= 1507328 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::PseudoElementFileSelectorButton => {\n        if let Some(version) = browsers.android {\n          if version >= 263168 && version <= 263171 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version >= 262144 && version <= 5767168 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version >= 786432 && version <= 1179648 {\n            prefixes |= VendorPrefix::Ms;\n          }\n          if version >= 5177344 && version <= 5767168 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.ie {\n          if version >= 655360 {\n            prefixes |= VendorPrefix::Ms;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version >= 197120 && version <= 917504 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version >= 983040 && version <= 4849664 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 196864 && version <= 917504 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version >= 262144 && version <= 917504 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::PseudoClassAutofill => {\n        if let Some(version) = browsers.android {\n          if version >= 263168 && version <= 263171 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version >= 262144 && version <= 7143424 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version >= 5177344 && version <= 7143424 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version >= 197120 && version <= 918784 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version >= 983040 && version <= 6225920 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 196864 && version <= 917760 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version >= 262144 && version <= 1310720 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::TabSize => {\n        if let Some(version) = browsers.firefox {\n          if version >= 262144 && version <= 5898240 {\n            prefixes |= VendorPrefix::Moz;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version >= 656896 && version <= 786688 {\n            prefixes |= VendorPrefix::O;\n          }\n        }\n      }\n      Feature::MaxContent | Feature::MinContent => {\n        if let Some(version) = browsers.android {\n          if version >= 263168 && version <= 263171 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version >= 1441792 && version <= 2949120 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version >= 196608 && version <= 4259840 {\n            prefixes |= VendorPrefix::Moz;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version >= 458752 && version <= 852992 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version >= 983040 && version <= 2097152 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 393472 && version <= 655616 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version <= 262144 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::Fill | Feature::FillAvailable => {\n        if let Some(version) = browsers.chrome {\n          if version >= 1441792 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version >= 263168 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version >= 5177344 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version >= 196608 && version <= 4259840 {\n            prefixes |= VendorPrefix::Moz;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version >= 458752 && version <= 852992 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version >= 983040 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 393472 && version <= 655616 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version >= 262144 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::FitContent => {\n        if let Some(version) = browsers.android {\n          if version >= 263168 && version <= 263171 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version >= 1441792 && version <= 2949120 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version >= 196608 && version <= 6094848 {\n            prefixes |= VendorPrefix::Moz;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version >= 458752 && version <= 852992 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version >= 983040 && version <= 2097152 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 393472 && version <= 655616 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version <= 262144 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::Stretch => {\n        if let Some(version) = browsers.firefox {\n          if version >= 196608 {\n            prefixes |= VendorPrefix::Moz;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version >= 263168 && version <= 263171 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version >= 1441792 && version <= 8978432 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version >= 5177344 && version <= 8978432 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version >= 458752 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version >= 983040 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 393472 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version >= 262144 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::ZoomIn | Feature::ZoomOut => {\n        if let Some(version) = browsers.chrome {\n          if version >= 262144 && version <= 2359296 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version >= 131072 && version <= 1507328 {\n            prefixes |= VendorPrefix::Moz;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version >= 983040 && version <= 1507328 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 196864 && version <= 524288 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::Grab | Feature::Grabbing => {\n        if let Some(version) = browsers.chrome {\n          if version >= 262144 && version <= 4390912 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version >= 131072 && version <= 1703936 {\n            prefixes |= VendorPrefix::Moz;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version >= 983040 && version <= 3538944 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 196864 && version <= 655616 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::Sticky => {\n        if let Some(version) = browsers.ios_saf {\n          if version >= 393216 && version <= 786944 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 393472 && version <= 786688 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::TouchAction => {\n        if let Some(version) = browsers.ie {\n          if version == 655360 {\n            prefixes |= VendorPrefix::Ms;\n          }\n        }\n      }\n      Feature::TextDecorationSkip | Feature::TextDecorationSkipInk => {\n        if let Some(version) = browsers.ios_saf {\n          if version >= 524288 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 459008 && version <= 786432 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::TextDecoration => {\n        if let Some(version) = browsers.ios_saf {\n          if version >= 524288 && version <= 1704192 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 524288 && version <= 1704192 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::TextDecorationColor | Feature::TextDecorationLine | Feature::TextDecorationStyle => {\n        if let Some(version) = browsers.firefox {\n          if version >= 393216 && version <= 2293760 {\n            prefixes |= VendorPrefix::Moz;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version >= 524288 && version <= 786432 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 524288 && version <= 786432 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::TextSizeAdjust => {\n        if browsers.firefox.is_some() {\n          prefixes |= VendorPrefix::Moz;\n        }\n        if let Some(version) = browsers.edge {\n          if version >= 786432 && version <= 1179648 {\n            prefixes |= VendorPrefix::Ms;\n          }\n        }\n        if let Some(version) = browsers.ie {\n          if version >= 655360 {\n            prefixes |= VendorPrefix::Ms;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version >= 327680 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::MaskClip\n      | Feature::MaskComposite\n      | Feature::MaskImage\n      | Feature::MaskOrigin\n      | Feature::MaskRepeat\n      | Feature::MaskBorderRepeat\n      | Feature::MaskBorderSource\n      | Feature::Mask\n      | Feature::MaskPosition\n      | Feature::MaskSize\n      | Feature::MaskBorder\n      | Feature::MaskBorderOutset\n      | Feature::MaskBorderWidth\n      | Feature::MaskBorderSlice => {\n        if let Some(version) = browsers.android {\n          if version >= 131328 && version <= 263171 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version >= 262144 && version <= 7798784 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version >= 5177344 && version <= 7798784 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version >= 197120 && version <= 983552 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version >= 983040 && version <= 6881280 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 262144 && version <= 983552 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version >= 262144 && version <= 1572864 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::ClipPath => {\n        if let Some(version) = browsers.android {\n          if version >= 263168 && version <= 263171 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version >= 1572864 && version <= 3538944 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version >= 458752 && version <= 589824 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version >= 983040 && version <= 2686976 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 458752 && version <= 589824 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version >= 262144 && version <= 327680 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::BoxDecorationBreak => {\n        if let Some(version) = browsers.android {\n          if version >= 263168 && version <= 263171 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version >= 1441792 && version <= 8454144 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version >= 5177344 && version <= 8454144 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version >= 458752 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version >= 983040 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 393472 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version >= 262144 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::ObjectFit | Feature::ObjectPosition => {\n        if let Some(version) = browsers.opera {\n          if version >= 656896 && version <= 786688 {\n            prefixes |= VendorPrefix::O;\n          }\n        }\n      }\n      Feature::ShapeMargin | Feature::ShapeOutside | Feature::ShapeImageThreshold => {\n        if let Some(version) = browsers.ios_saf {\n          if version >= 524288 && version <= 655360 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 459008 && version <= 655360 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::TextOverflow => {\n        if let Some(version) = browsers.opera {\n          if version >= 589824 && version <= 786432 {\n            prefixes |= VendorPrefix::O;\n          }\n        }\n      }\n      Feature::AtViewport => {\n        if let Some(version) = browsers.edge {\n          if version >= 786432 && version <= 1179648 {\n            prefixes |= VendorPrefix::Ms;\n          }\n        }\n        if let Some(version) = browsers.ie {\n          if version >= 655360 {\n            prefixes |= VendorPrefix::Ms;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version >= 720896 && version <= 786688 {\n            prefixes |= VendorPrefix::O;\n          }\n        }\n      }\n      Feature::AtResolution => {\n        if let Some(version) = browsers.android {\n          if version >= 131840 && version <= 262656 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version >= 262144 && version <= 1835008 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version >= 197888 && version <= 983040 {\n            prefixes |= VendorPrefix::Moz;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version >= 262144 && version <= 984576 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version >= 591104 && version <= 786432 {\n            prefixes |= VendorPrefix::O;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 262144 && version <= 984576 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::TextAlignLast => {\n        if let Some(version) = browsers.firefox {\n          if version >= 786432 && version <= 3145728 {\n            prefixes |= VendorPrefix::Moz;\n          }\n        }\n      }\n      Feature::Pixelated => {\n        if let Some(version) = browsers.firefox {\n          if version >= 198144 && version <= 4194304 {\n            prefixes |= VendorPrefix::Moz;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version >= 327680 && version <= 393216 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version >= 722432 && version <= 786688 {\n            prefixes |= VendorPrefix::O;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version <= 393216 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::ImageRendering => {\n        if let Some(version) = browsers.ie {\n          if version >= 458752 {\n            prefixes |= VendorPrefix::Ms;\n          }\n        }\n      }\n      Feature::BorderInlineStart\n      | Feature::BorderInlineEnd\n      | Feature::MarginInlineStart\n      | Feature::MarginInlineEnd\n      | Feature::PaddingInlineStart\n      | Feature::PaddingInlineEnd => {\n        if let Some(version) = browsers.android {\n          if version >= 131328 && version <= 263171 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version >= 262144 && version <= 4456448 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version >= 196608 && version <= 2621440 {\n            prefixes |= VendorPrefix::Moz;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version >= 197120 && version <= 786432 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version >= 983040 && version <= 3604480 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 196864 && version <= 786432 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version >= 262144 && version <= 590336 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::BorderBlockStart\n      | Feature::BorderBlockEnd\n      | Feature::MarginBlockStart\n      | Feature::MarginBlockEnd\n      | Feature::PaddingBlockStart\n      | Feature::PaddingBlockEnd => {\n        if let Some(version) = browsers.android {\n          if version >= 131328 && version <= 263171 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version >= 262144 && version <= 4456448 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version >= 197120 && version <= 786432 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version >= 983040 && version <= 3604480 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 196864 && version <= 786432 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version >= 262144 && version <= 590336 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::Appearance => {\n        if let Some(version) = browsers.android {\n          if version >= 131328 && version <= 263171 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version >= 262144 && version <= 5439488 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version >= 786432 && version <= 1179648 {\n            prefixes |= VendorPrefix::Ms;\n          }\n          if version >= 5177344 && version <= 5439488 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version >= 131072 && version <= 5177344 {\n            prefixes |= VendorPrefix::Moz;\n          }\n        }\n        if browsers.ie.is_some() {\n          prefixes |= VendorPrefix::Ms;\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version >= 197120 && version <= 983552 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version >= 983040 && version <= 4718592 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 196864 && version <= 983552 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version >= 262144 && version <= 851968 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::ScrollSnapType\n      | Feature::ScrollSnapCoordinate\n      | Feature::ScrollSnapDestination\n      | Feature::ScrollSnapPointsX\n      | Feature::ScrollSnapPointsY => {\n        if let Some(version) = browsers.edge {\n          if version >= 786432 && version <= 1179648 {\n            prefixes |= VendorPrefix::Ms;\n          }\n        }\n        if let Some(version) = browsers.ie {\n          if version >= 655360 {\n            prefixes |= VendorPrefix::Ms;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version >= 589824 && version <= 656128 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 589824 && version <= 655616 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::FlowInto | Feature::FlowFrom | Feature::RegionFragment => {\n        if let Some(version) = browsers.chrome {\n          if version >= 983040 && version <= 1179648 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version >= 786432 && version <= 1179648 {\n            prefixes |= VendorPrefix::Ms;\n          }\n        }\n        if let Some(version) = browsers.ie {\n          if version >= 655360 {\n            prefixes |= VendorPrefix::Ms;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version >= 458752 && version <= 720896 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 393472 && version <= 720896 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::ImageSet => {\n        if let Some(version) = browsers.android {\n          if version >= 263168 && version <= 263171 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version >= 1376256 && version <= 7340032 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version >= 5177344 && version <= 7340032 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version >= 393216 && version <= 590592 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version >= 983040 && version <= 6422528 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 393216 && version <= 590080 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version >= 262144 && version <= 1441792 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::WritingMode => {\n        if let Some(version) = browsers.android {\n          if version >= 196608 && version <= 263171 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version >= 524288 && version <= 3080192 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.ie {\n          if version >= 328960 {\n            prefixes |= VendorPrefix::Ms;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version >= 327680 && version <= 656128 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version >= 983040 && version <= 2228224 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 327936 && version <= 655616 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version <= 262144 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::CrossFade => {\n        if let Some(version) = browsers.chrome {\n          if version >= 1114112 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version >= 263168 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version >= 5177344 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version >= 327680 && version <= 590592 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version >= 983040 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 327936 && version <= 590080 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version >= 262144 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::PseudoClassReadOnly | Feature::PseudoClassReadWrite => {\n        if let Some(version) = browsers.firefox {\n          if version >= 196608 && version <= 5046272 {\n            prefixes |= VendorPrefix::Moz;\n          }\n        }\n      }\n      Feature::TextEmphasis\n      | Feature::TextEmphasisPosition\n      | Feature::TextEmphasisStyle\n      | Feature::TextEmphasisColor => {\n        if let Some(version) = browsers.android {\n          if version >= 263168 && version <= 263171 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version >= 1638400 && version <= 6422528 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version >= 5177344 && version <= 6422528 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version >= 983040 && version <= 5570560 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 393472 && version <= 458752 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version >= 262144 && version <= 1114112 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::DisplayGrid\n      | Feature::InlineGrid\n      | Feature::GridTemplateColumns\n      | Feature::GridTemplateRows\n      | Feature::GridRowStart\n      | Feature::GridColumnStart\n      | Feature::GridRowEnd\n      | Feature::GridColumnEnd\n      | Feature::GridRow\n      | Feature::GridColumn\n      | Feature::GridArea\n      | Feature::GridTemplate\n      | Feature::GridTemplateAreas\n      | Feature::PlaceSelf\n      | Feature::GridColumnAlign\n      | Feature::GridRowAlign => {\n        if let Some(version) = browsers.edge {\n          if version >= 786432 && version <= 983040 {\n            prefixes |= VendorPrefix::Ms;\n          }\n        }\n        if let Some(version) = browsers.ie {\n          if version >= 655360 {\n            prefixes |= VendorPrefix::Ms;\n          }\n        }\n      }\n      Feature::TextSpacing => {\n        if let Some(version) = browsers.edge {\n          if version >= 786432 && version <= 1179648 {\n            prefixes |= VendorPrefix::Ms;\n          }\n        }\n        if let Some(version) = browsers.ie {\n          if version >= 524288 {\n            prefixes |= VendorPrefix::Ms;\n          }\n        }\n      }\n      Feature::PseudoClassAnyLink => {\n        if let Some(version) = browsers.android {\n          if version >= 263168 && version <= 263171 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version >= 983040 && version <= 4194304 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version >= 196608 && version <= 3211264 {\n            prefixes |= VendorPrefix::Moz;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version >= 393216 && version <= 524544 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version >= 983040 && version <= 3342336 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 393472 && version <= 524288 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version >= 327680 && version <= 524800 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::Isolate => {\n        if let Some(version) = browsers.chrome {\n          if version >= 1048576 && version <= 3080192 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version >= 655360 && version <= 3211264 {\n            prefixes |= VendorPrefix::Moz;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version >= 393216 && version <= 656128 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version >= 983040 && version <= 2228224 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 393216 && version <= 655616 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::Plaintext => {\n        if let Some(version) = browsers.firefox {\n          if version >= 655360 && version <= 3211264 {\n            prefixes |= VendorPrefix::Moz;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version >= 393216 && version <= 656128 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 393216 && version <= 655616 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::IsolateOverride => {\n        if let Some(version) = browsers.firefox {\n          if version >= 1114112 && version <= 3211264 {\n            prefixes |= VendorPrefix::Moz;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version >= 458752 && version <= 656128 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 458752 && version <= 655616 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::OverscrollBehavior => {\n        if let Some(version) = browsers.edge {\n          if version >= 786432 && version <= 1114112 {\n            prefixes |= VendorPrefix::Ms;\n          }\n        }\n        if let Some(version) = browsers.ie {\n          if version >= 655360 {\n            prefixes |= VendorPrefix::Ms;\n          }\n        }\n      }\n      Feature::TextOrientation => {\n        if let Some(version) = browsers.safari {\n          if version >= 655616 && version <= 852224 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::PrintColorAdjust | Feature::ColorAdjust => {\n        if let Some(version) = browsers.android {\n          if version >= 263168 && version <= 263171 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.chrome {\n          if version >= 1114112 && version <= 8847360 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version >= 5177344 && version <= 8847360 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version >= 3145728 && version <= 6291456 {\n            prefixes |= VendorPrefix::Moz;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version >= 393216 && version <= 983552 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version >= 983040 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 393216 && version <= 983552 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version >= 262144 && version <= 1835008 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n      Feature::AnyPseudo => {\n        if let Some(version) = browsers.chrome {\n          if version >= 786432 && version <= 5701632 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.edge {\n          if version >= 5177344 && version <= 5701632 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.firefox {\n          if version >= 262144 && version <= 5111808 {\n            prefixes |= VendorPrefix::Moz;\n          }\n        }\n        if let Some(version) = browsers.opera {\n          if version >= 917504 && version <= 4784128 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.safari {\n          if version >= 327680 && version <= 851968 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.ios_saf {\n          if version >= 327680 && version <= 851968 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.samsung {\n          if version >= 65536 && version <= 917504 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n        if let Some(version) = browsers.android {\n          if version >= 263168 && version <= 5701632 {\n            prefixes |= VendorPrefix::WebKit;\n          }\n        }\n      }\n    }\n    prefixes\n  }\n}\n\npub fn is_flex_2009(browsers: Browsers) -> bool {\n  if let Some(version) = browsers.android {\n    if version >= 131328 && version <= 262656 {\n      return true;\n    }\n  }\n  if let Some(version) = browsers.chrome {\n    if version >= 262144 && version <= 1310720 {\n      return true;\n    }\n  }\n  if let Some(version) = browsers.ios_saf {\n    if version >= 197120 && version <= 393216 {\n      return true;\n    }\n  }\n  if let Some(version) = browsers.safari {\n    if version >= 196864 && version <= 393216 {\n      return true;\n    }\n  }\n  false\n}\n\npub fn is_webkit_gradient(browsers: Browsers) -> bool {\n  if let Some(version) = browsers.android {\n    if version >= 131328 && version <= 196608 {\n      return true;\n    }\n  }\n  if let Some(version) = browsers.chrome {\n    if version >= 262144 && version <= 589824 {\n      return true;\n    }\n  }\n  if let Some(version) = browsers.ios_saf {\n    if version >= 197120 && version <= 393216 {\n      return true;\n    }\n  }\n  if let Some(version) = browsers.safari {\n    if version >= 262144 && version <= 393216 {\n      return true;\n    }\n  }\n  false\n}\n"
  },
  {
    "path": "src/printer.rs",
    "content": "//! CSS serialization and source map generation.\n\nuse crate::css_modules::CssModule;\nuse crate::dependencies::{Dependency, DependencyOptions};\nuse crate::error::{Error, ErrorLocation, PrinterError, PrinterErrorKind};\nuse crate::rules::{Location, StyleContext};\nuse crate::selector::SelectorList;\nuse crate::targets::{Targets, TargetsWithSupportsScope};\nuse crate::vendor_prefix::VendorPrefix;\nuse cssparser::{serialize_identifier, serialize_name};\n#[cfg(feature = \"sourcemap\")]\nuse parcel_sourcemap::{OriginalLocation, SourceMap};\n\n/// Options that control how CSS is serialized to a string.\n#[derive(Default)]\npub struct PrinterOptions<'a> {\n  /// Whether to minify the CSS, i.e. remove white space.\n  pub minify: bool,\n  /// An optional reference to a source map to write mappings into.\n  #[cfg(feature = \"sourcemap\")]\n  #[cfg_attr(docsrs, doc(cfg(feature = \"sourcemap\")))]\n  pub source_map: Option<&'a mut SourceMap>,\n  /// An optional project root path, used to generate relative paths for sources used in CSS module hashes.\n  pub project_root: Option<&'a str>,\n  /// Targets to output the CSS for.\n  pub targets: Targets,\n  /// Whether to analyze dependencies (i.e. `@import` and `url()`).\n  /// If true, the dependencies are returned as part of the\n  /// [ToCssResult](super::stylesheet::ToCssResult).\n  ///\n  /// When enabled, `@import` and `url()` dependencies\n  /// are replaced with hashed placeholders that can be replaced with the final\n  /// urls later (after bundling).\n  pub analyze_dependencies: Option<DependencyOptions>,\n  /// A mapping of pseudo classes to replace with class names that can be applied\n  /// from JavaScript. Useful for polyfills, for example.\n  pub pseudo_classes: Option<PseudoClasses<'a>>,\n}\n\n/// A mapping of user action pseudo classes to replace with class names.\n///\n/// See [PrinterOptions](PrinterOptions).\n#[derive(Default, Debug)]\npub struct PseudoClasses<'a> {\n  /// The class name to replace `:hover` with.\n  pub hover: Option<&'a str>,\n  /// The class name to replace `:active` with.\n  pub active: Option<&'a str>,\n  /// The class name to replace `:focus` with.\n  pub focus: Option<&'a str>,\n  /// The class name to replace `:focus-visible` with.\n  pub focus_visible: Option<&'a str>,\n  /// The class name to replace `:focus-within` with.\n  pub focus_within: Option<&'a str>,\n}\n\n/// A `Printer` represents a destination to output serialized CSS, as used in\n/// the [ToCss](super::traits::ToCss) trait. It can wrap any destination that\n/// implements [std::fmt::Write](std::fmt::Write), such as a [String](String).\n///\n/// A `Printer` keeps track of the current line and column position, and uses\n/// this to generate a source map if provided in the options.\n///\n/// `Printer` also includes helper functions that assist with writing output\n/// that respects options such as `minify`, and `css_modules`.\npub struct Printer<'a, 'b, 'c, W> {\n  pub(crate) sources: Option<&'c Vec<String>>,\n  dest: &'a mut W,\n  #[cfg(feature = \"sourcemap\")]\n  #[cfg_attr(docsrs, doc(cfg(feature = \"sourcemap\")))]\n  pub(crate) source_map: Option<&'a mut SourceMap>,\n  #[cfg(feature = \"sourcemap\")]\n  #[cfg_attr(docsrs, doc(cfg(feature = \"sourcemap\")))]\n  pub(crate) source_maps: Vec<Option<SourceMap>>,\n  pub(crate) loc: Location,\n  indent: u8,\n  line: u32,\n  col: u32,\n  pub(crate) minify: bool,\n  pub(crate) targets: TargetsWithSupportsScope,\n  /// Vendor prefix override. When non-empty, it overrides\n  /// the vendor prefix of whatever is being printed.\n  pub(crate) vendor_prefix: VendorPrefix,\n  pub(crate) in_calc: bool,\n  pub(crate) css_module: Option<CssModule<'a, 'b, 'c>>,\n  pub(crate) dependencies: Option<Vec<Dependency>>,\n  pub(crate) remove_imports: bool,\n  pub(crate) pseudo_classes: Option<PseudoClasses<'a>>,\n  context: Option<&'a StyleContext<'a, 'b>>,\n}\n\nimpl<'a, 'b, 'c, W: std::fmt::Write + Sized> Printer<'a, 'b, 'c, W> {\n  /// Create a new Printer wrapping the given destination.\n  pub fn new(dest: &'a mut W, options: PrinterOptions<'a>) -> Self {\n    Printer {\n      sources: None,\n      dest,\n      #[cfg(feature = \"sourcemap\")]\n      source_map: options.source_map,\n      #[cfg(feature = \"sourcemap\")]\n      source_maps: Vec::new(),\n      loc: Location {\n        source_index: 0,\n        line: 0,\n        column: 1,\n      },\n      indent: 0,\n      line: 0,\n      col: 0,\n      minify: options.minify,\n      targets: TargetsWithSupportsScope::new(options.targets),\n      vendor_prefix: VendorPrefix::empty(),\n      in_calc: false,\n      css_module: None,\n      dependencies: if options.analyze_dependencies.is_some() {\n        Some(Vec::new())\n      } else {\n        None\n      },\n      remove_imports: matches!(&options.analyze_dependencies, Some(d) if d.remove_imports),\n      pseudo_classes: options.pseudo_classes,\n      context: None,\n    }\n  }\n\n  /// Returns the current source filename that is being printed.\n  pub fn filename(&self) -> &'c str {\n    if let Some(sources) = self.sources {\n      if let Some(f) = sources.get(self.loc.source_index as usize) {\n        f\n      } else {\n        \"unknown.css\"\n      }\n    } else {\n      \"unknown.css\"\n    }\n  }\n\n  /// Writes a raw string to the underlying destination.\n  ///\n  /// NOTE: Is is assumed that the string does not contain any newline characters.\n  /// If such a string is written, it will break source maps.\n  pub fn write_str(&mut self, s: &str) -> Result<(), PrinterError> {\n    self.col += s.len() as u32;\n    self.dest.write_str(s)?;\n    Ok(())\n  }\n\n  /// Writes a raw string which may contain newlines to the underlying destination.\n  pub fn write_str_with_newlines(&mut self, s: &str) -> Result<(), PrinterError> {\n    let mut last_line_start: usize = 0;\n\n    for (idx, n) in s.char_indices() {\n      if n == '\\n' {\n        self.line += 1;\n        self.col = 0;\n\n        // Keep track of where the *next* line starts\n        last_line_start = idx + 1;\n      }\n    }\n\n    self.col += (s.len() - last_line_start) as u32;\n    self.dest.write_str(s)?;\n    Ok(())\n  }\n\n  /// Write a single character to the underlying destination.\n  pub fn write_char(&mut self, c: char) -> Result<(), PrinterError> {\n    if c == '\\n' {\n      self.line += 1;\n      self.col = 0;\n    } else {\n      self.col += 1;\n    }\n    self.dest.write_char(c)?;\n    Ok(())\n  }\n\n  /// Writes a single whitespace character, unless the `minify` option is enabled.\n  ///\n  /// Use `write_char` instead if you wish to force a space character to be written,\n  /// regardless of the `minify` option.\n  pub fn whitespace(&mut self) -> Result<(), PrinterError> {\n    if self.minify {\n      return Ok(());\n    }\n\n    self.write_char(' ')\n  }\n\n  /// Writes a delimiter character, followed by whitespace (depending on the `minify` option).\n  /// If `ws_before` is true, then whitespace is also written before the delimiter.\n  pub fn delim(&mut self, delim: char, ws_before: bool) -> Result<(), PrinterError> {\n    if ws_before {\n      self.whitespace()?;\n    }\n    self.write_char(delim)?;\n    self.whitespace()\n  }\n\n  /// Writes a newline character followed by indentation.\n  /// If the `minify` option is enabled, then nothing is printed.\n  pub fn newline(&mut self) -> Result<(), PrinterError> {\n    if self.minify {\n      return Ok(());\n    }\n\n    self.write_char('\\n')?;\n    if self.indent > 0 {\n      self.write_str(&\" \".repeat(self.indent as usize))?;\n    }\n\n    Ok(())\n  }\n\n  /// Increases the current indent level.\n  pub fn indent(&mut self) {\n    self.indent += 2;\n  }\n\n  /// Decreases the current indent level.\n  pub fn dedent(&mut self) {\n    self.indent -= 2;\n  }\n\n  /// Increases the current indent level by the given number of characters.\n  pub fn indent_by(&mut self, amt: u8) {\n    self.indent += amt;\n  }\n\n  /// Decreases the current indent level by the given number of characters.\n  pub fn dedent_by(&mut self, amt: u8) {\n    self.indent -= amt;\n  }\n\n  /// Returns whether the indent level is greater than one.\n  pub fn is_nested(&self) -> bool {\n    self.indent > 2\n  }\n\n  /// Adds a mapping to the source map, if any.\n  #[cfg(feature = \"sourcemap\")]\n  #[cfg_attr(docsrs, doc(cfg(feature = \"sourcemap\")))]\n  pub fn add_mapping(&mut self, loc: Location) {\n    self.loc = loc;\n\n    if let Some(map) = &mut self.source_map {\n      let mut original = OriginalLocation {\n        original_line: loc.line,\n        original_column: loc.column - 1,\n        source: loc.source_index,\n        name: None,\n      };\n\n      // Remap using input source map if possible.\n      if let Some(Some(sm)) = self.source_maps.get_mut(loc.source_index as usize) {\n        let mut found_mapping = false;\n        if let Some(mapping) = sm.find_closest_mapping(loc.line, loc.column - 1) {\n          if let Some(orig) = mapping.original {\n            let sources_len = map.get_sources().len();\n            let source_index = map.add_source(sm.get_source(orig.source).unwrap());\n            let name = orig.name.map(|name| map.add_name(sm.get_name(name).unwrap()));\n            original.original_line = orig.original_line;\n            original.original_column = orig.original_column;\n            original.source = source_index;\n            original.name = name;\n\n            if map.get_sources().len() > sources_len {\n              let content = sm.get_source_content(orig.source).unwrap().to_owned();\n              let _ = map.set_source_content(source_index as usize, &content);\n            }\n\n            found_mapping = true;\n          }\n        }\n\n        if !found_mapping {\n          return;\n        }\n      }\n\n      map.add_mapping(self.line, self.col, Some(original))\n    }\n  }\n\n  /// Writes a CSS identifier to the underlying destination, escaping it\n  /// as appropriate. If the `css_modules` option was enabled, then a hash\n  /// is added, and the mapping is added to the CSS module.\n  pub fn write_ident(&mut self, ident: &str, handle_css_module: bool) -> Result<(), PrinterError> {\n    if handle_css_module {\n      if let Some(css_module) = &mut self.css_module {\n        let dest = &mut self.dest;\n        let mut first = true;\n        css_module.config.pattern.write(\n          &css_module.hashes[self.loc.source_index as usize],\n          &css_module.sources[self.loc.source_index as usize],\n          ident,\n          if let Some(content_hashes) = &css_module.content_hashes {\n            &content_hashes[self.loc.source_index as usize]\n          } else {\n            \"\"\n          },\n          |s| {\n            self.col += s.len() as u32;\n            if first {\n              first = false;\n              serialize_identifier(s, dest)\n            } else {\n              serialize_name(s, dest)\n            }\n          },\n        )?;\n\n        css_module.add_local(&ident, &ident, self.loc.source_index);\n        return Ok(());\n      }\n    }\n\n    serialize_identifier(ident, self)?;\n    Ok(())\n  }\n\n  pub(crate) fn write_dashed_ident(&mut self, ident: &str, is_declaration: bool) -> Result<(), PrinterError> {\n    self.write_str(\"--\")?;\n\n    match &mut self.css_module {\n      Some(css_module) if css_module.config.dashed_idents => {\n        let dest = &mut self.dest;\n        css_module.config.pattern.write(\n          &css_module.hashes[self.loc.source_index as usize],\n          &css_module.sources[self.loc.source_index as usize],\n          &ident[2..],\n          if let Some(content_hashes) = &css_module.content_hashes {\n            &content_hashes[self.loc.source_index as usize]\n          } else {\n            \"\"\n          },\n          |s| {\n            self.col += s.len() as u32;\n            serialize_name(s, dest)\n          },\n        )?;\n\n        if is_declaration {\n          css_module.add_dashed(ident, self.loc.source_index);\n        }\n      }\n      _ => {\n        serialize_name(&ident[2..], self)?;\n      }\n    }\n\n    Ok(())\n  }\n\n  /// Returns an error of the given kind at the provided location in the current source file.\n  pub fn error(&self, kind: PrinterErrorKind, loc: crate::dependencies::Location) -> Error<PrinterErrorKind> {\n    Error {\n      kind,\n      loc: Some(ErrorLocation {\n        filename: self.filename().into(),\n        line: loc.line - 1,\n        column: loc.column,\n      }),\n    }\n  }\n\n  pub(crate) fn with_context<T, U, F: FnOnce(&mut Printer<'a, 'b, 'c, W>) -> Result<T, U>>(\n    &mut self,\n    selectors: &SelectorList,\n    f: F,\n  ) -> Result<T, U> {\n    let parent = std::mem::take(&mut self.context);\n    let ctx = StyleContext {\n      selectors: unsafe { std::mem::transmute(selectors) },\n      parent,\n    };\n\n    // I can't figure out what lifetime to use here to convince the compiler that\n    // the reference doesn't live beyond the function call.\n    self.context = Some(unsafe { std::mem::transmute(&ctx) });\n    let res = f(self);\n    self.context = parent;\n    res\n  }\n\n  pub(crate) fn with_cleared_context<T, U, F: FnOnce(&mut Printer<'a, 'b, 'c, W>) -> Result<T, U>>(\n    &mut self,\n    f: F,\n  ) -> Result<T, U> {\n    let parent = std::mem::take(&mut self.context);\n    let res = f(self);\n    self.context = parent;\n    res\n  }\n\n  pub(crate) fn with_parent_context<T, U, F: FnOnce(&mut Printer<'a, 'b, 'c, W>) -> Result<T, U>>(\n    &mut self,\n    f: F,\n  ) -> Result<T, U> {\n    let parent = std::mem::take(&mut self.context);\n    if let Some(parent) = parent {\n      self.context = parent.parent;\n    }\n    let res = f(self);\n    self.context = parent;\n    res\n  }\n\n  pub(crate) fn context(&self) -> Option<&'a StyleContext<'a, 'b>> {\n    self.context.clone()\n  }\n}\n\nimpl<'a, 'b, 'c, W: std::fmt::Write + Sized> std::fmt::Write for Printer<'a, 'b, 'c, W> {\n  fn write_str(&mut self, s: &str) -> std::fmt::Result {\n    self.col += s.len() as u32;\n    self.dest.write_str(s)\n  }\n}\n"
  },
  {
    "path": "src/properties/align.rs",
    "content": "//! CSS properties related to box alignment.\n\nuse super::flex::{BoxAlign, BoxPack, FlexAlign, FlexItemAlign, FlexLinePack, FlexPack};\nuse super::{Property, PropertyId};\nuse crate::compat;\nuse crate::context::PropertyHandlerContext;\nuse crate::declaration::{DeclarationBlock, DeclarationList};\nuse crate::error::{ParserError, PrinterError};\nuse crate::macros::*;\nuse crate::prefixes::{is_flex_2009, Feature};\nuse crate::printer::Printer;\nuse crate::traits::{FromStandard, Parse, PropertyHandler, Shorthand, ToCss};\nuse crate::values::length::LengthPercentage;\nuse crate::vendor_prefix::VendorPrefix;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse cssparser::*;\n\n#[cfg(feature = \"serde\")]\nuse crate::serialization::ValueWrapper;\n\n/// A [`<baseline-position>`](https://www.w3.org/TR/css-align-3/#typedef-baseline-position) value,\n/// as used in the alignment properties.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub enum BaselinePosition {\n  /// The first baseline.\n  First,\n  /// The last baseline.\n  Last,\n}\n\nimpl<'i> Parse<'i> for BaselinePosition {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let location = input.current_source_location();\n    let ident = input.expect_ident()?;\n    match_ignore_ascii_case! { &*ident,\n      \"baseline\" => Ok(BaselinePosition::First),\n      \"first\" => {\n        input.expect_ident_matching(\"baseline\")?;\n        Ok(BaselinePosition::First)\n      },\n      \"last\" => {\n        input.expect_ident_matching(\"baseline\")?;\n        Ok(BaselinePosition::Last)\n      },\n      _ => Err(location.new_unexpected_token_error(\n        cssparser::Token::Ident(ident.clone())\n      ))\n    }\n  }\n}\n\nimpl ToCss for BaselinePosition {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      BaselinePosition::First => dest.write_str(\"baseline\"),\n      BaselinePosition::Last => dest.write_str(\"last baseline\"),\n    }\n  }\n}\n\nenum_property! {\n  /// A [`<content-distribution>`](https://www.w3.org/TR/css-align-3/#typedef-content-distribution) value.\n  pub enum ContentDistribution {\n    /// Items are spaced evenly, with the first and last items against the edge of the container.\n    SpaceBetween,\n    /// Items are spaced evenly, with half-size spaces at the start and end.\n    SpaceAround,\n    /// Items are spaced evenly, with full-size spaces at the start and end.\n    SpaceEvenly,\n    /// Items are stretched evenly to fill free space.\n    Stretch,\n  }\n}\n\nenum_property! {\n  /// An [`<overflow-position>`](https://www.w3.org/TR/css-align-3/#typedef-overflow-position) value.\n  pub enum OverflowPosition {\n    /// If the size of the alignment subject overflows the alignment container,\n    /// the alignment subject is instead aligned as if the alignment mode were start.\n    Safe,\n    /// Regardless of the relative sizes of the alignment subject and alignment\n    /// container, the given alignment value is honored.\n    Unsafe,\n  }\n}\n\nenum_property! {\n  /// A [`<content-position>`](https://www.w3.org/TR/css-align-3/#typedef-content-position) value.\n  pub enum ContentPosition {\n    /// Content is centered within the container.\n    Center,\n    /// Content is aligned to the start of the container.\n    Start,\n    /// Content is aligned to the end of the container.\n    End,\n    /// Same as `start` when within a flexbox container.\n    FlexStart,\n    /// Same as `end` when within a flexbox container.\n    FlexEnd,\n  }\n}\n\n/// A value for the [align-content](https://www.w3.org/TR/css-align-3/#propdef-align-content) property.\n#[derive(Debug, Clone, PartialEq, Parse, ToCss)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum AlignContent {\n  /// Default alignment.\n  Normal,\n  /// A baseline position.\n  #[cfg_attr(feature = \"serde\", serde(with = \"ValueWrapper::<BaselinePosition>\"))]\n  BaselinePosition(BaselinePosition),\n  /// A content distribution keyword.\n  #[cfg_attr(feature = \"serde\", serde(with = \"ValueWrapper::<ContentDistribution>\"))]\n  ContentDistribution(ContentDistribution),\n  /// A content position keyword.\n  ContentPosition {\n    /// An overflow alignment mode.\n    overflow: Option<OverflowPosition>,\n    /// A content position keyword.\n    value: ContentPosition,\n  },\n}\n\n/// A value for the [justify-content](https://www.w3.org/TR/css-align-3/#propdef-justify-content) property.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum JustifyContent {\n  /// Default justification.\n  Normal,\n  /// A content distribution keyword.\n  #[cfg_attr(feature = \"serde\", serde(with = \"ValueWrapper::<ContentDistribution>\"))]\n  ContentDistribution(ContentDistribution),\n  /// A content position keyword.\n  ContentPosition {\n    /// A content position keyword.\n    value: ContentPosition,\n    /// An overflow alignment mode.\n    overflow: Option<OverflowPosition>,\n  },\n  /// Justify to the left.\n  Left {\n    /// An overflow alignment mode.\n    overflow: Option<OverflowPosition>,\n  },\n  /// Justify to the right.\n  Right {\n    /// An overflow alignment mode.\n    overflow: Option<OverflowPosition>,\n  },\n}\n\nimpl<'i> Parse<'i> for JustifyContent {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    if input.try_parse(|input| input.expect_ident_matching(\"normal\")).is_ok() {\n      return Ok(JustifyContent::Normal);\n    }\n\n    if let Ok(val) = input.try_parse(ContentDistribution::parse) {\n      return Ok(JustifyContent::ContentDistribution(val));\n    }\n\n    let overflow = input.try_parse(OverflowPosition::parse).ok();\n    if let Ok(content_position) = input.try_parse(ContentPosition::parse) {\n      return Ok(JustifyContent::ContentPosition {\n        overflow,\n        value: content_position,\n      });\n    }\n\n    let location = input.current_source_location();\n    let ident = input.expect_ident()?;\n    match_ignore_ascii_case! { &*ident,\n      \"left\" => Ok(JustifyContent::Left { overflow }),\n      \"right\" => Ok(JustifyContent::Right { overflow }),\n      _ => Err(location.new_unexpected_token_error(\n        cssparser::Token::Ident(ident.clone())\n      ))\n    }\n  }\n}\n\nimpl ToCss for JustifyContent {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      JustifyContent::Normal => dest.write_str(\"normal\"),\n      JustifyContent::ContentDistribution(value) => value.to_css(dest),\n      JustifyContent::ContentPosition { overflow, value } => {\n        if let Some(overflow) = overflow {\n          overflow.to_css(dest)?;\n          dest.write_str(\" \")?;\n        }\n\n        value.to_css(dest)\n      }\n      JustifyContent::Left { overflow } => {\n        if let Some(overflow) = overflow {\n          overflow.to_css(dest)?;\n          dest.write_str(\" \")?;\n        }\n\n        dest.write_str(\"left\")\n      }\n      JustifyContent::Right { overflow } => {\n        if let Some(overflow) = overflow {\n          overflow.to_css(dest)?;\n          dest.write_str(\" \")?;\n        }\n\n        dest.write_str(\"right\")\n      }\n    }\n  }\n}\n\ndefine_shorthand! {\n  /// A value for the [place-content](https://www.w3.org/TR/css-align-3/#place-content) shorthand property.\n  pub struct PlaceContent {\n    /// The content alignment.\n    align: AlignContent(AlignContent, VendorPrefix),\n    /// The content justification.\n    justify: JustifyContent(JustifyContent, VendorPrefix),\n  }\n}\n\nimpl<'i> Parse<'i> for PlaceContent {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let align = AlignContent::parse(input)?;\n    let justify = match input.try_parse(JustifyContent::parse) {\n      Ok(j) => j,\n      Err(_) => {\n        // The second value is assigned to justify-content; if omitted, it is copied\n        // from the first value, unless that value is a <baseline-position> in which\n        // case it is defaulted to start.\n        match align {\n          AlignContent::BaselinePosition(_) => JustifyContent::ContentPosition {\n            overflow: None,\n            value: ContentPosition::Start,\n          },\n          AlignContent::Normal => JustifyContent::Normal,\n          AlignContent::ContentDistribution(value) => JustifyContent::ContentDistribution(value.clone()),\n          AlignContent::ContentPosition { overflow, value } => JustifyContent::ContentPosition {\n            overflow: overflow.clone(),\n            value: value.clone(),\n          },\n        }\n      }\n    };\n\n    Ok(PlaceContent { align, justify })\n  }\n}\n\nimpl ToCss for PlaceContent {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    self.align.to_css(dest)?;\n    let is_equal = match self.justify {\n      JustifyContent::Normal if self.align == AlignContent::Normal => true,\n      JustifyContent::ContentDistribution(d) if matches!(self.align, AlignContent::ContentDistribution(d2) if d == d2) => {\n        true\n      }\n      JustifyContent::ContentPosition { overflow: o, value: c } if matches!(self.align, AlignContent::ContentPosition { overflow: o2, value: c2 } if o == o2 && c == c2) => {\n        true\n      }\n      _ => false,\n    };\n\n    if !is_equal {\n      dest.write_str(\" \")?;\n      self.justify.to_css(dest)?;\n    }\n\n    Ok(())\n  }\n}\n\nenum_property! {\n  /// A [`<self-position>`](https://www.w3.org/TR/css-align-3/#typedef-self-position) value.\n  pub enum SelfPosition {\n    /// Item is centered within the container.\n    Center,\n    /// Item is aligned to the start of the container.\n    Start,\n    /// Item is aligned to the end of the container.\n    End,\n    /// Item is aligned to the edge of the container corresponding to the start side of the item.\n    SelfStart,\n    /// Item is aligned to the edge of the container corresponding to the end side of the item.\n    SelfEnd,\n    /// Item  is aligned to the start of the container, within flexbox layouts.\n    FlexStart,\n    /// Item  is aligned to the end of the container, within flexbox layouts.\n    FlexEnd,\n  }\n}\n\n/// A value for the [align-self](https://www.w3.org/TR/css-align-3/#align-self-property) property.\n#[derive(Debug, Clone, PartialEq, Parse, ToCss)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum AlignSelf {\n  /// Automatic alignment.\n  Auto,\n  /// Default alignment.\n  Normal,\n  /// Item is stretched.\n  Stretch,\n  /// A baseline position keyword.\n  #[cfg_attr(feature = \"serde\", serde(with = \"ValueWrapper::<BaselinePosition>\"))]\n  BaselinePosition(BaselinePosition),\n  /// A self position keyword.\n  SelfPosition {\n    /// An overflow alignment mode.\n    overflow: Option<OverflowPosition>,\n    /// A self position keyword.\n    value: SelfPosition,\n  },\n}\n\n/// A value for the [justify-self](https://www.w3.org/TR/css-align-3/#justify-self-property) property.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum JustifySelf {\n  /// Automatic justification.\n  Auto,\n  /// Default justification.\n  Normal,\n  /// Item is stretched.\n  Stretch,\n  /// A baseline position keyword.\n  #[cfg_attr(feature = \"serde\", serde(with = \"ValueWrapper::<BaselinePosition>\"))]\n  BaselinePosition(BaselinePosition),\n  /// A self position keyword.\n  SelfPosition {\n    /// A self position keyword.\n    value: SelfPosition,\n    /// An overflow alignment mode.\n    overflow: Option<OverflowPosition>,\n  },\n  /// Item is justified to the left.\n  Left {\n    /// An overflow alignment mode.\n    overflow: Option<OverflowPosition>,\n  },\n  /// Item is justified to the right.\n  Right {\n    /// An overflow alignment mode.\n    overflow: Option<OverflowPosition>,\n  },\n}\n\nimpl<'i> Parse<'i> for JustifySelf {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    if input.try_parse(|input| input.expect_ident_matching(\"auto\")).is_ok() {\n      return Ok(JustifySelf::Auto);\n    }\n\n    if input.try_parse(|input| input.expect_ident_matching(\"normal\")).is_ok() {\n      return Ok(JustifySelf::Normal);\n    }\n\n    if input.try_parse(|input| input.expect_ident_matching(\"stretch\")).is_ok() {\n      return Ok(JustifySelf::Stretch);\n    }\n\n    if let Ok(val) = input.try_parse(BaselinePosition::parse) {\n      return Ok(JustifySelf::BaselinePosition(val));\n    }\n\n    let overflow = input.try_parse(OverflowPosition::parse).ok();\n    if let Ok(value) = input.try_parse(SelfPosition::parse) {\n      return Ok(JustifySelf::SelfPosition { overflow, value });\n    }\n\n    let location = input.current_source_location();\n    let ident = input.expect_ident()?;\n    match_ignore_ascii_case! { &*ident,\n      \"left\" => Ok(JustifySelf::Left { overflow }),\n      \"right\" => Ok(JustifySelf::Right { overflow }),\n      _ => Err(location.new_unexpected_token_error(\n        cssparser::Token::Ident(ident.clone())\n      ))\n    }\n  }\n}\n\nimpl ToCss for JustifySelf {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      JustifySelf::Auto => dest.write_str(\"auto\"),\n      JustifySelf::Normal => dest.write_str(\"normal\"),\n      JustifySelf::Stretch => dest.write_str(\"stretch\"),\n      JustifySelf::BaselinePosition(val) => val.to_css(dest),\n      JustifySelf::SelfPosition { overflow, value } => {\n        if let Some(overflow) = overflow {\n          overflow.to_css(dest)?;\n          dest.write_str(\" \")?;\n        }\n\n        value.to_css(dest)\n      }\n      JustifySelf::Left { overflow } => {\n        if let Some(overflow) = overflow {\n          overflow.to_css(dest)?;\n          dest.write_str(\" \")?;\n        }\n\n        dest.write_str(\"left\")\n      }\n      JustifySelf::Right { overflow } => {\n        if let Some(overflow) = overflow {\n          overflow.to_css(dest)?;\n          dest.write_str(\" \")?;\n        }\n\n        dest.write_str(\"right\")\n      }\n    }\n  }\n}\n\ndefine_shorthand! {\n  /// A value for the [place-self](https://www.w3.org/TR/css-align-3/#place-self-property) shorthand property.\n  pub struct PlaceSelf {\n    /// The item alignment.\n    align: AlignSelf(AlignSelf, VendorPrefix),\n    /// The item justification.\n    justify: JustifySelf(JustifySelf),\n  }\n}\n\nimpl<'i> Parse<'i> for PlaceSelf {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let align = AlignSelf::parse(input)?;\n    let justify = match input.try_parse(JustifySelf::parse) {\n      Ok(j) => j,\n      Err(_) => {\n        // The second value is assigned to justify-self; if omitted, it is copied from the first value.\n        match &align {\n          AlignSelf::Auto => JustifySelf::Auto,\n          AlignSelf::Normal => JustifySelf::Normal,\n          AlignSelf::Stretch => JustifySelf::Stretch,\n          AlignSelf::BaselinePosition(p) => JustifySelf::BaselinePosition(p.clone()),\n          AlignSelf::SelfPosition { overflow, value } => JustifySelf::SelfPosition {\n            overflow: overflow.clone(),\n            value: value.clone(),\n          },\n        }\n      }\n    };\n\n    Ok(PlaceSelf { align, justify })\n  }\n}\n\nimpl ToCss for PlaceSelf {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    self.align.to_css(dest)?;\n    let is_equal = match &self.justify {\n      JustifySelf::Auto => true,\n      JustifySelf::Normal => self.align == AlignSelf::Normal,\n      JustifySelf::Stretch => self.align == AlignSelf::Normal,\n      JustifySelf::BaselinePosition(p) if matches!(&self.align, AlignSelf::BaselinePosition(p2) if p == p2) => {\n        true\n      }\n      JustifySelf::SelfPosition { overflow: o, value: c } if matches!(&self.align, AlignSelf::SelfPosition  { overflow: o2, value: c2 } if o == o2 && c == c2) => {\n        true\n      }\n      _ => false,\n    };\n\n    if !is_equal {\n      dest.write_str(\" \")?;\n      self.justify.to_css(dest)?;\n    }\n\n    Ok(())\n  }\n}\n\n/// A value for the [align-items](https://www.w3.org/TR/css-align-3/#align-items-property) property.\n#[derive(Debug, Clone, PartialEq, Parse, ToCss)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum AlignItems {\n  /// Default alignment.\n  Normal,\n  /// Items are stretched.\n  Stretch,\n  /// A baseline position keyword.\n  #[cfg_attr(feature = \"serde\", serde(with = \"ValueWrapper::<BaselinePosition>\"))]\n  BaselinePosition(BaselinePosition),\n  /// A self position keyword.\n  SelfPosition {\n    /// An overflow alignment mode.\n    overflow: Option<OverflowPosition>,\n    /// A self position keyword.\n    value: SelfPosition,\n  },\n}\n\n/// A legacy justification keyword, as used in the `justify-items` property.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub enum LegacyJustify {\n  /// Left justify.\n  Left,\n  /// Right justify.\n  Right,\n  /// Centered.\n  Center,\n}\n\nimpl<'i> Parse<'i> for LegacyJustify {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let location = input.current_source_location();\n    let ident = input.expect_ident()?;\n    match_ignore_ascii_case! { &*ident,\n      \"legacy\" => {\n        let location = input.current_source_location();\n        let ident = input.expect_ident()?;\n        match_ignore_ascii_case! { &*ident,\n          \"left\" => Ok(LegacyJustify::Left),\n          \"right\" => Ok(LegacyJustify::Right),\n          \"center\" => Ok(LegacyJustify::Center),\n          _ => Err(location.new_unexpected_token_error(\n            cssparser::Token::Ident(ident.clone())\n          ))\n        }\n      },\n      \"left\" => {\n        input.expect_ident_matching(\"legacy\")?;\n        Ok(LegacyJustify::Left)\n      },\n      \"right\" => {\n        input.expect_ident_matching(\"legacy\")?;\n        Ok(LegacyJustify::Right)\n      },\n      \"center\" => {\n        input.expect_ident_matching(\"legacy\")?;\n        Ok(LegacyJustify::Center)\n      },\n      _ => Err(location.new_unexpected_token_error(\n        cssparser::Token::Ident(ident.clone())\n      ))\n    }\n  }\n}\n\nimpl ToCss for LegacyJustify {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    dest.write_str(\"legacy \")?;\n    match self {\n      LegacyJustify::Left => dest.write_str(\"left\"),\n      LegacyJustify::Right => dest.write_str(\"right\"),\n      LegacyJustify::Center => dest.write_str(\"center\"),\n    }\n  }\n}\n\n/// A value for the [justify-items](https://www.w3.org/TR/css-align-3/#justify-items-property) property.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum JustifyItems {\n  /// Default justification.\n  Normal,\n  /// Items are stretched.\n  Stretch,\n  /// A baseline position keyword.\n  #[cfg_attr(feature = \"serde\", serde(with = \"ValueWrapper::<BaselinePosition>\"))]\n  BaselinePosition(BaselinePosition),\n  /// A self position keyword, with optional overflow position.\n  SelfPosition {\n    /// A self position keyword.\n    value: SelfPosition,\n    /// An overflow alignment mode.\n    overflow: Option<OverflowPosition>,\n  },\n  /// Items are justified to the left, with an optional overflow position.\n  Left {\n    /// An overflow alignment mode.\n    overflow: Option<OverflowPosition>,\n  },\n  /// Items are justified to the right, with an optional overflow position.\n  Right {\n    /// An overflow alignment mode.\n    overflow: Option<OverflowPosition>,\n  },\n  /// A legacy justification keyword.\n  #[cfg_attr(feature = \"serde\", serde(with = \"ValueWrapper::<LegacyJustify>\"))]\n  Legacy(LegacyJustify),\n}\n\nimpl<'i> Parse<'i> for JustifyItems {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    if input.try_parse(|input| input.expect_ident_matching(\"normal\")).is_ok() {\n      return Ok(JustifyItems::Normal);\n    }\n\n    if input.try_parse(|input| input.expect_ident_matching(\"stretch\")).is_ok() {\n      return Ok(JustifyItems::Stretch);\n    }\n\n    if let Ok(val) = input.try_parse(BaselinePosition::parse) {\n      return Ok(JustifyItems::BaselinePosition(val));\n    }\n\n    if let Ok(val) = input.try_parse(LegacyJustify::parse) {\n      return Ok(JustifyItems::Legacy(val));\n    }\n\n    let overflow = input.try_parse(OverflowPosition::parse).ok();\n    if let Ok(value) = input.try_parse(SelfPosition::parse) {\n      return Ok(JustifyItems::SelfPosition { overflow, value });\n    }\n\n    let location = input.current_source_location();\n    let ident = input.expect_ident()?;\n    match_ignore_ascii_case! { &*ident,\n      \"left\" => Ok(JustifyItems::Left { overflow }),\n      \"right\" => Ok(JustifyItems::Right { overflow }),\n      _ => Err(location.new_unexpected_token_error(\n        cssparser::Token::Ident(ident.clone())\n      ))\n    }\n  }\n}\n\nimpl ToCss for JustifyItems {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      JustifyItems::Normal => dest.write_str(\"normal\"),\n      JustifyItems::Stretch => dest.write_str(\"stretch\"),\n      JustifyItems::BaselinePosition(val) => val.to_css(dest),\n      JustifyItems::Legacy(val) => val.to_css(dest),\n      JustifyItems::SelfPosition { overflow, value } => {\n        if let Some(overflow) = overflow {\n          overflow.to_css(dest)?;\n          dest.write_str(\" \")?;\n        }\n\n        value.to_css(dest)\n      }\n      JustifyItems::Left { overflow } => {\n        if let Some(overflow) = overflow {\n          overflow.to_css(dest)?;\n          dest.write_str(\" \")?;\n        }\n\n        dest.write_str(\"left\")\n      }\n      JustifyItems::Right { overflow } => {\n        if let Some(overflow) = overflow {\n          overflow.to_css(dest)?;\n          dest.write_str(\" \")?;\n        }\n\n        dest.write_str(\"right\")\n      }\n    }\n  }\n}\n\ndefine_shorthand! {\n  /// A value for the [place-items](https://www.w3.org/TR/css-align-3/#place-items-property) shorthand property.\n  pub struct PlaceItems {\n    /// The item alignment.\n    align: AlignItems(AlignItems, VendorPrefix),\n    /// The item justification.\n    justify: JustifyItems(JustifyItems),\n  }\n}\n\nimpl<'i> Parse<'i> for PlaceItems {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let align = AlignItems::parse(input)?;\n    let justify = match input.try_parse(JustifyItems::parse) {\n      Ok(j) => j,\n      Err(_) => {\n        // The second value is assigned to justify-items; if omitted, it is copied from the first value.\n        match &align {\n          AlignItems::Normal => JustifyItems::Normal,\n          AlignItems::Stretch => JustifyItems::Stretch,\n          AlignItems::BaselinePosition(p) => JustifyItems::BaselinePosition(p.clone()),\n          AlignItems::SelfPosition { overflow, value } => JustifyItems::SelfPosition {\n            overflow: overflow.clone(),\n            value: value.clone(),\n          },\n        }\n      }\n    };\n\n    Ok(PlaceItems { align, justify })\n  }\n}\n\nimpl ToCss for PlaceItems {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    self.align.to_css(dest)?;\n    let is_equal = match &self.justify {\n      JustifyItems::Normal => self.align == AlignItems::Normal,\n      JustifyItems::Stretch => self.align == AlignItems::Normal,\n      JustifyItems::BaselinePosition(p) if matches!(&self.align, AlignItems::BaselinePosition(p2) if p == p2) => {\n        true\n      }\n      JustifyItems::SelfPosition { overflow: o, value: c } if matches!(&self.align, AlignItems::SelfPosition { overflow: o2, value: c2 } if o == o2 && c == c2) => {\n        true\n      }\n      _ => false,\n    };\n\n    if !is_equal {\n      dest.write_str(\" \")?;\n      self.justify.to_css(dest)?;\n    }\n\n    Ok(())\n  }\n}\n\n/// A [gap](https://www.w3.org/TR/css-align-3/#column-row-gap) value, as used in the\n/// `column-gap` and `row-gap` properties.\n#[derive(Debug, Clone, PartialEq, Parse, ToCss)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum GapValue {\n  /// Equal to `1em` for multi-column containers, and zero otherwise.\n  Normal,\n  /// An explicit length.\n  LengthPercentage(LengthPercentage),\n}\n\ndefine_shorthand! {\n  /// A value for the [gap](https://www.w3.org/TR/css-align-3/#gap-shorthand) shorthand property.\n  pub struct Gap {\n    /// The row gap.\n    row: RowGap(GapValue),\n    /// The column gap.\n    column: ColumnGap(GapValue),\n  }\n}\n\nimpl<'i> Parse<'i> for Gap {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let row = GapValue::parse(input)?;\n    let column = input.try_parse(GapValue::parse).unwrap_or(row.clone());\n    Ok(Gap { row, column })\n  }\n}\n\nimpl ToCss for Gap {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    self.row.to_css(dest)?;\n    if self.column != self.row {\n      dest.write_str(\" \")?;\n      self.column.to_css(dest)?;\n    }\n    Ok(())\n  }\n}\n\n#[derive(Default, Debug)]\npub(crate) struct AlignHandler {\n  align_content: Option<(AlignContent, VendorPrefix)>,\n  flex_line_pack: Option<(FlexLinePack, VendorPrefix)>,\n  justify_content: Option<(JustifyContent, VendorPrefix)>,\n  box_pack: Option<(BoxPack, VendorPrefix)>,\n  flex_pack: Option<(FlexPack, VendorPrefix)>,\n  align_self: Option<(AlignSelf, VendorPrefix)>,\n  flex_item_align: Option<(FlexItemAlign, VendorPrefix)>,\n  justify_self: Option<JustifySelf>,\n  align_items: Option<(AlignItems, VendorPrefix)>,\n  box_align: Option<(BoxAlign, VendorPrefix)>,\n  flex_align: Option<(FlexAlign, VendorPrefix)>,\n  justify_items: Option<JustifyItems>,\n  row_gap: Option<GapValue>,\n  column_gap: Option<GapValue>,\n  has_any: bool,\n}\n\nimpl<'i> PropertyHandler<'i> for AlignHandler {\n  fn handle_property(\n    &mut self,\n    property: &Property<'i>,\n    dest: &mut DeclarationList<'i>,\n    context: &mut PropertyHandlerContext<'i, '_>,\n  ) -> bool {\n    use Property::*;\n\n    macro_rules! maybe_flush {\n      ($prop: ident, $val: expr, $vp: expr) => {{\n        // If two vendor prefixes for the same property have different\n        // values, we need to flush what we have immediately to preserve order.\n        if let Some((val, prefixes)) = &self.$prop {\n          if val != $val && !prefixes.contains(*$vp) {\n            self.flush(dest, context);\n          }\n        }\n      }};\n    }\n\n    macro_rules! property {\n      ($prop: ident, $val: expr, $vp: expr) => {{\n        maybe_flush!($prop, $val, $vp);\n\n        // Otherwise, update the value and add the prefix.\n        if let Some((val, prefixes)) = &mut self.$prop {\n          *val = $val.clone();\n          *prefixes |= *$vp;\n        } else {\n          self.$prop = Some(($val.clone(), *$vp));\n          self.has_any = true;\n        }\n      }};\n    }\n\n    match property {\n      AlignContent(val, vp) => {\n        self.flex_line_pack = None;\n        property!(align_content, val, vp);\n      }\n      FlexLinePack(val, vp) => property!(flex_line_pack, val, vp),\n      JustifyContent(val, vp) => {\n        self.box_pack = None;\n        self.flex_pack = None;\n        property!(justify_content, val, vp);\n      }\n      BoxPack(val, vp) => property!(box_pack, val, vp),\n      FlexPack(val, vp) => property!(flex_pack, val, vp),\n      PlaceContent(val) => {\n        self.flex_line_pack = None;\n        self.box_pack = None;\n        self.flex_pack = None;\n        maybe_flush!(align_content, &val.align, &VendorPrefix::None);\n        maybe_flush!(justify_content, &val.justify, &VendorPrefix::None);\n        property!(align_content, &val.align, &VendorPrefix::None);\n        property!(justify_content, &val.justify, &VendorPrefix::None);\n      }\n      AlignSelf(val, vp) => {\n        self.flex_item_align = None;\n        property!(align_self, val, vp);\n      }\n      FlexItemAlign(val, vp) => property!(flex_item_align, val, vp),\n      JustifySelf(val) => {\n        self.justify_self = Some(val.clone());\n        self.has_any = true;\n      }\n      PlaceSelf(val) => {\n        self.flex_item_align = None;\n        property!(align_self, &val.align, &VendorPrefix::None);\n        self.justify_self = Some(val.justify.clone());\n      }\n      AlignItems(val, vp) => {\n        self.box_align = None;\n        self.flex_align = None;\n        property!(align_items, val, vp);\n      }\n      BoxAlign(val, vp) => property!(box_align, val, vp),\n      FlexAlign(val, vp) => property!(flex_align, val, vp),\n      JustifyItems(val) => {\n        self.justify_items = Some(val.clone());\n        self.has_any = true;\n      }\n      PlaceItems(val) => {\n        self.box_align = None;\n        self.flex_align = None;\n        property!(align_items, &val.align, &VendorPrefix::None);\n        self.justify_items = Some(val.justify.clone());\n      }\n      RowGap(val) => {\n        self.row_gap = Some(val.clone());\n        self.has_any = true;\n      }\n      ColumnGap(val) => {\n        self.column_gap = Some(val.clone());\n        self.has_any = true;\n      }\n      Gap(val) => {\n        self.row_gap = Some(val.row.clone());\n        self.column_gap = Some(val.column.clone());\n        self.has_any = true;\n      }\n      Unparsed(val) if is_align_property(&val.property_id) => {\n        self.flush(dest, context);\n        dest.push(property.clone()) // TODO: prefix?\n      }\n      _ => return false,\n    }\n\n    true\n  }\n\n  fn finalize(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {\n    self.flush(dest, context);\n  }\n}\n\nimpl AlignHandler {\n  fn flush<'i>(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {\n    if !self.has_any {\n      return;\n    }\n\n    self.has_any = false;\n\n    let mut align_content = std::mem::take(&mut self.align_content);\n    let mut justify_content = std::mem::take(&mut self.justify_content);\n    let mut align_self = std::mem::take(&mut self.align_self);\n    let mut justify_self = std::mem::take(&mut self.justify_self);\n    let mut align_items = std::mem::take(&mut self.align_items);\n    let mut justify_items = std::mem::take(&mut self.justify_items);\n    let row_gap = std::mem::take(&mut self.row_gap);\n    let column_gap = std::mem::take(&mut self.column_gap);\n    let box_align = std::mem::take(&mut self.box_align);\n    let box_pack = std::mem::take(&mut self.box_pack);\n    let flex_line_pack = std::mem::take(&mut self.flex_line_pack);\n    let flex_pack = std::mem::take(&mut self.flex_pack);\n    let flex_align = std::mem::take(&mut self.flex_align);\n    let flex_item_align = std::mem::take(&mut self.flex_item_align);\n\n    // Gets prefixes for standard properties.\n    macro_rules! prefixes {\n      ($prop: ident) => {{\n        let mut prefix = context.targets.prefixes(VendorPrefix::None, Feature::$prop);\n        // Firefox only implemented the 2009 spec prefixed.\n        // Microsoft only implemented the 2012 spec prefixed.\n        prefix.remove(VendorPrefix::Moz | VendorPrefix::Ms);\n        prefix\n      }};\n    }\n\n    macro_rules! standard_property {\n      ($prop: ident, $key: ident) => {\n        if let Some((val, prefix)) = $key {\n          // If we have an unprefixed property, override necessary prefixes.\n          let prefix = if prefix.contains(VendorPrefix::None) {\n            prefixes!($prop)\n          } else {\n            prefix\n          };\n          dest.push(Property::$prop(val, prefix))\n        }\n      };\n    }\n\n    macro_rules! legacy_property {\n      ($prop: ident, $key: ident, $( $prop_2009: ident )?, $prop_2012: ident) => {\n        if let Some((val, prefix)) = &$key {\n          // If we have an unprefixed standard property, generate legacy prefixed versions.\n          let mut prefix = context.targets.prefixes(*prefix, Feature::$prop);\n\n          if prefix.contains(VendorPrefix::None) {\n            $(\n              // 2009 spec, implemented by webkit and firefox.\n              if let Some(targets) = context.targets.browsers {\n                let mut prefixes_2009 = VendorPrefix::empty();\n                if is_flex_2009(targets) {\n                  prefixes_2009 |= VendorPrefix::WebKit;\n                }\n                if prefix.contains(VendorPrefix::Moz) {\n                  prefixes_2009 |= VendorPrefix::Moz;\n                }\n                if !prefixes_2009.is_empty() {\n                  if let Some(v) = $prop_2009::from_standard(&val) {\n                    dest.push(Property::$prop_2009(v, prefixes_2009));\n                  }\n                }\n              }\n            )?\n          }\n\n          // 2012 spec, implemented by microsoft.\n          if prefix.contains(VendorPrefix::Ms) {\n            if let Some(v) = $prop_2012::from_standard(&val) {\n              dest.push(Property::$prop_2012(v, VendorPrefix::Ms));\n            }\n          }\n\n          // Remove Firefox and IE from standard prefixes.\n          prefix.remove(VendorPrefix::Moz | VendorPrefix::Ms);\n        }\n      };\n    }\n\n    macro_rules! prefixed_property {\n      ($prop: ident, $key: expr) => {\n        if let Some((val, prefix)) = $key {\n          dest.push(Property::$prop(val, prefix))\n        }\n      };\n    }\n\n    macro_rules! unprefixed_property {\n      ($prop: ident, $key: expr) => {\n        if let Some(val) = $key {\n          dest.push(Property::$prop(val))\n        }\n      };\n    }\n\n    macro_rules! shorthand {\n      ($prop: ident, $align_prop: ident, $align: ident, $justify: ident $(, $justify_prop: ident )?) => {\n        if let (Some((align, align_prefix)), Some(justify)) = (&mut $align, &mut $justify) {\n          let intersection = *align_prefix $( & {\n            // Hack for conditional compilation. Have to use a variable.\n            #[allow(non_snake_case)]\n            let $justify_prop = justify.1;\n            $justify_prop\n          })?;\n\n          // Only use shorthand if unprefixed.\n          if intersection.contains(VendorPrefix::None) {\n            // Add prefixed longhands if needed.\n            *align_prefix = prefixes!($align_prop);\n            align_prefix.remove(VendorPrefix::None);\n            if !align_prefix.is_empty() {\n              dest.push(Property::$align_prop(align.clone(), *align_prefix))\n            }\n\n            $(\n              let (justify, justify_prefix) = justify;\n              *justify_prefix = prefixes!($justify_prop);\n              justify_prefix.remove(VendorPrefix::None);\n\n              if !justify_prefix.is_empty() {\n                dest.push(Property::$justify_prop(justify.clone(), *justify_prefix))\n              }\n            )?\n\n            // Add shorthand.\n            dest.push(Property::$prop($prop {\n              align: align.clone(),\n              justify: justify.clone()\n            }));\n\n            $align = None;\n            $justify = None;\n          }\n        }\n      };\n    }\n\n    // 2009 properties\n    prefixed_property!(BoxAlign, box_align);\n    prefixed_property!(BoxPack, box_pack);\n\n    // 2012 properties\n    prefixed_property!(FlexPack, flex_pack);\n    prefixed_property!(FlexAlign, flex_align);\n    prefixed_property!(FlexItemAlign, flex_item_align);\n    prefixed_property!(FlexLinePack, flex_line_pack);\n\n    legacy_property!(AlignContent, align_content, , FlexLinePack);\n    legacy_property!(JustifyContent, justify_content, BoxPack, FlexPack);\n    if context.targets.is_compatible(compat::Feature::PlaceContent) {\n      shorthand!(\n        PlaceContent,\n        AlignContent,\n        align_content,\n        justify_content,\n        JustifyContent\n      );\n    }\n    standard_property!(AlignContent, align_content);\n    standard_property!(JustifyContent, justify_content);\n\n    legacy_property!(AlignSelf, align_self, , FlexItemAlign);\n    if context.targets.is_compatible(compat::Feature::PlaceSelf) {\n      shorthand!(PlaceSelf, AlignSelf, align_self, justify_self);\n    }\n    standard_property!(AlignSelf, align_self);\n    unprefixed_property!(JustifySelf, justify_self);\n\n    legacy_property!(AlignItems, align_items, BoxAlign, FlexAlign);\n    if context.targets.is_compatible(compat::Feature::PlaceItems) {\n      shorthand!(PlaceItems, AlignItems, align_items, justify_items);\n    }\n    standard_property!(AlignItems, align_items);\n    unprefixed_property!(JustifyItems, justify_items);\n\n    if row_gap.is_some() && column_gap.is_some() {\n      dest.push(Property::Gap(Gap {\n        row: row_gap.unwrap(),\n        column: column_gap.unwrap(),\n      }))\n    } else {\n      if let Some(gap) = row_gap {\n        dest.push(Property::RowGap(gap))\n      }\n\n      if let Some(gap) = column_gap {\n        dest.push(Property::ColumnGap(gap))\n      }\n    }\n  }\n}\n\n#[inline]\nfn is_align_property(property_id: &PropertyId) -> bool {\n  match property_id {\n    PropertyId::AlignContent(_)\n    | PropertyId::FlexLinePack(_)\n    | PropertyId::JustifyContent(_)\n    | PropertyId::BoxPack(_)\n    | PropertyId::FlexPack(_)\n    | PropertyId::PlaceContent\n    | PropertyId::AlignSelf(_)\n    | PropertyId::FlexItemAlign(_)\n    | PropertyId::JustifySelf\n    | PropertyId::PlaceSelf\n    | PropertyId::AlignItems(_)\n    | PropertyId::BoxAlign(_)\n    | PropertyId::FlexAlign(_)\n    | PropertyId::JustifyItems\n    | PropertyId::PlaceItems\n    | PropertyId::RowGap\n    | PropertyId::ColumnGap\n    | PropertyId::Gap => true,\n    _ => false,\n  }\n}\n"
  },
  {
    "path": "src/properties/animation.rs",
    "content": "//! CSS properties related to keyframe animations.\n\nuse std::borrow::Cow;\n\nuse crate::context::PropertyHandlerContext;\nuse crate::declaration::{DeclarationBlock, DeclarationList};\nuse crate::error::{ParserError, PrinterError};\nuse crate::macros::*;\nuse crate::prefixes::Feature;\nuse crate::printer::Printer;\nuse crate::properties::{Property, PropertyId, TokenOrValue, VendorPrefix};\nuse crate::traits::{Parse, PropertyHandler, Shorthand, ToCss, Zero};\nuse crate::values::ident::DashedIdent;\nuse crate::values::number::CSSNumber;\nuse crate::values::percentage::Percentage;\nuse crate::values::size::Size2D;\nuse crate::values::string::CSSString;\nuse crate::values::{easing::EasingFunction, ident::CustomIdent, time::Time};\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse cssparser::*;\nuse itertools::izip;\nuse smallvec::SmallVec;\n\nuse super::{LengthPercentage, LengthPercentageOrAuto};\n\n/// A value for the [animation-name](https://drafts.csswg.org/css-animations/#animation-name) property.\n#[derive(Debug, Clone, PartialEq, Parse)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub enum AnimationName<'i> {\n  /// The `none` keyword.\n  None,\n  /// An identifier of a `@keyframes` rule.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  Ident(CustomIdent<'i>),\n  /// A `<string>` name of a `@keyframes` rule.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  String(CSSString<'i>),\n}\n\nimpl<'i> ToCss for AnimationName<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    let css_module_animation_enabled =\n      dest.css_module.as_ref().map_or(false, |css_module| css_module.config.animation);\n\n    match self {\n      AnimationName::None => dest.write_str(\"none\"),\n      AnimationName::Ident(s) => {\n        if css_module_animation_enabled {\n          if let Some(css_module) = &mut dest.css_module {\n            css_module.reference(&s.0, dest.loc.source_index)\n          }\n        }\n        s.to_css_with_options(dest, css_module_animation_enabled)\n      }\n      AnimationName::String(s) => {\n        if css_module_animation_enabled {\n          if let Some(css_module) = &mut dest.css_module {\n            css_module.reference(&s, dest.loc.source_index)\n          }\n        }\n\n        // CSS-wide keywords and `none` cannot remove quotes.\n        match_ignore_ascii_case! { &*s,\n          \"none\" | \"initial\" | \"inherit\" | \"unset\" | \"default\" | \"revert\" | \"revert-layer\" => {\n            serialize_string(&s, dest)?;\n            Ok(())\n          },\n          _ => {\n            dest.write_ident(s.as_ref(), css_module_animation_enabled)\n          }\n        }\n      }\n    }\n  }\n}\n\n/// A list of animation names.\npub type AnimationNameList<'i> = SmallVec<[AnimationName<'i>; 1]>;\n\n/// A value for the [animation-iteration-count](https://drafts.csswg.org/css-animations/#animation-iteration-count) property.\n#[derive(Debug, Clone, PartialEq, Parse, ToCss)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum AnimationIterationCount {\n  /// The animation will repeat the specified number of times.\n  Number(CSSNumber),\n  /// The animation will repeat forever.\n  Infinite,\n}\n\nimpl Default for AnimationIterationCount {\n  fn default() -> Self {\n    AnimationIterationCount::Number(1.0)\n  }\n}\n\nenum_property! {\n  /// A value for the [animation-direction](https://drafts.csswg.org/css-animations/#animation-direction) property.\n  pub enum AnimationDirection {\n    /// The animation is played as specified\n    Normal,\n    /// The animation is played in reverse.\n    Reverse,\n    /// The animation iterations alternate between forward and reverse.\n    Alternate,\n    /// The animation iterations alternate between forward and reverse, with reverse occurring first.\n    AlternateReverse,\n  }\n}\n\nimpl Default for AnimationDirection {\n  fn default() -> Self {\n    AnimationDirection::Normal\n  }\n}\n\nenum_property! {\n  /// A value for the [animation-play-state](https://drafts.csswg.org/css-animations/#animation-play-state) property.\n  pub enum AnimationPlayState {\n    /// The animation is playing.\n    Running,\n    /// The animation is paused.\n    Paused,\n  }\n}\n\nimpl Default for AnimationPlayState {\n  fn default() -> Self {\n    AnimationPlayState::Running\n  }\n}\n\nenum_property! {\n  /// A value for the [animation-fill-mode](https://drafts.csswg.org/css-animations/#animation-fill-mode) property.\n  pub enum AnimationFillMode {\n    /// The animation has no effect while not playing.\n    None,\n    /// After the animation, the ending values are applied.\n    Forwards,\n    /// Before the animation, the starting values are applied.\n    Backwards,\n    /// Both forwards and backwards apply.\n    Both,\n  }\n}\n\nimpl Default for AnimationFillMode {\n  fn default() -> Self {\n    AnimationFillMode::None\n  }\n}\n\nenum_property! {\n  /// A value for the [animation-composition](https://drafts.csswg.org/css-animations-2/#animation-composition) property.\n  pub enum AnimationComposition {\n    /// The result of compositing the effect value with the underlying value is simply the effect value.\n    Replace,\n    /// The effect value is added to the underlying value.\n    Add,\n    /// The effect value is accumulated onto the underlying value.\n    Accumulate,\n  }\n}\n\n/// A value for the [animation-timeline](https://drafts.csswg.org/css-animations-2/#animation-timeline) property.\n#[derive(Debug, Clone, PartialEq, Parse, ToCss)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum AnimationTimeline<'i> {\n  /// The animation’s timeline is a DocumentTimeline, more specifically the default document timeline.\n  Auto,\n  /// The animation is not associated with a timeline.\n  None,\n  /// A timeline referenced by name.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  DashedIdent(DashedIdent<'i>),\n  /// The scroll() function.\n  Scroll(ScrollTimeline),\n  /// The view() function.\n  View(ViewTimeline),\n}\n\nimpl<'i> Default for AnimationTimeline<'i> {\n  fn default() -> Self {\n    AnimationTimeline::Auto\n  }\n}\n\n/// The [scroll()](https://drafts.csswg.org/scroll-animations-1/#scroll-notation) function.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub struct ScrollTimeline {\n  /// Specifies which element to use as the scroll container.\n  pub scroller: Scroller,\n  /// Specifies which axis of the scroll container to use as the progress for the timeline.\n  pub axis: ScrollAxis,\n}\n\nimpl<'i> Parse<'i> for ScrollTimeline {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    input.expect_function_matching(\"scroll\")?;\n    input.parse_nested_block(|input| {\n      let mut scroller = None;\n      let mut axis = None;\n      loop {\n        if scroller.is_none() {\n          scroller = input.try_parse(Scroller::parse).ok();\n        }\n\n        if axis.is_none() {\n          axis = input.try_parse(ScrollAxis::parse).ok();\n          if axis.is_some() {\n            continue;\n          }\n        }\n        break;\n      }\n\n      Ok(ScrollTimeline {\n        scroller: scroller.unwrap_or_default(),\n        axis: axis.unwrap_or_default(),\n      })\n    })\n  }\n}\n\nimpl ToCss for ScrollTimeline {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    dest.write_str(\"scroll(\")?;\n\n    let mut needs_space = false;\n    if self.scroller != Scroller::default() {\n      self.scroller.to_css(dest)?;\n      needs_space = true;\n    }\n\n    if self.axis != ScrollAxis::default() {\n      if needs_space {\n        dest.write_char(' ')?;\n      }\n      self.axis.to_css(dest)?;\n    }\n\n    dest.write_char(')')\n  }\n}\n\nenum_property! {\n  /// A scroller, used in the `scroll()` function.\n  pub enum Scroller {\n    /// Specifies to use the document viewport as the scroll container.\n    \"root\": Root,\n    /// Specifies to use the nearest ancestor scroll container.\n    \"nearest\": Nearest,\n    /// Specifies to use the element’s own principal box as the scroll container.\n    \"self\": SelfElement,\n  }\n}\n\nimpl Default for Scroller {\n  fn default() -> Self {\n    Scroller::Nearest\n  }\n}\n\nenum_property! {\n  /// A scroll axis, used in the `scroll()` function.\n  pub enum ScrollAxis {\n    /// Specifies to use the measure of progress along the block axis of the scroll container.\n    Block,\n    /// Specifies to use the measure of progress along the inline axis of the scroll container.\n    Inline,\n    /// Specifies to use the measure of progress along the horizontal axis of the scroll container.\n    X,\n    /// Specifies to use the measure of progress along the vertical axis of the scroll container.\n    Y,\n  }\n}\n\nimpl Default for ScrollAxis {\n  fn default() -> Self {\n    ScrollAxis::Block\n  }\n}\n\n/// The [view()](https://drafts.csswg.org/scroll-animations-1/#view-notation) function.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub struct ViewTimeline {\n  /// Specifies which axis of the scroll container to use as the progress for the timeline.\n  pub axis: ScrollAxis,\n  /// Provides an adjustment of the view progress visibility range.\n  pub inset: Size2D<LengthPercentageOrAuto>,\n}\n\nimpl<'i> Parse<'i> for ViewTimeline {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    input.expect_function_matching(\"view\")?;\n    input.parse_nested_block(|input| {\n      let mut axis = None;\n      let mut inset = None;\n      loop {\n        if axis.is_none() {\n          axis = input.try_parse(ScrollAxis::parse).ok();\n        }\n\n        if inset.is_none() {\n          inset = input.try_parse(Size2D::parse).ok();\n          if inset.is_some() {\n            continue;\n          }\n        }\n        break;\n      }\n\n      Ok(ViewTimeline {\n        axis: axis.unwrap_or_default(),\n        inset: inset.unwrap_or(Size2D(LengthPercentageOrAuto::Auto, LengthPercentageOrAuto::Auto)),\n      })\n    })\n  }\n}\n\nimpl ToCss for ViewTimeline {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    dest.write_str(\"view(\")?;\n    let mut needs_space = false;\n    if self.axis != ScrollAxis::default() {\n      self.axis.to_css(dest)?;\n      needs_space = true;\n    }\n\n    if self.inset.0 != LengthPercentageOrAuto::Auto || self.inset.1 != LengthPercentageOrAuto::Auto {\n      if needs_space {\n        dest.write_char(' ')?;\n      }\n      self.inset.to_css(dest)?;\n    }\n\n    dest.write_char(')')\n  }\n}\n\n/// A [view progress timeline range](https://drafts.csswg.org/scroll-animations/#view-timelines-ranges)\n#[derive(Debug, Clone, PartialEq, Parse, ToCss)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum TimelineRangeName {\n  /// Represents the full range of the view progress timeline.\n  Cover,\n  /// Represents the range during which the principal box is either fully contained by,\n  /// or fully covers, its view progress visibility range within the scrollport.\n  Contain,\n  /// Represents the range during which the principal box is entering the view progress visibility range.\n  Entry,\n  /// Represents the range during which the principal box is exiting the view progress visibility range.\n  Exit,\n  /// Represents the range during which the principal box crosses the end border edge.\n  EntryCrossing,\n  /// Represents the range during which the principal box crosses the start border edge.\n  ExitCrossing,\n}\n\n/// A value for the [animation-range-start](https://drafts.csswg.org/scroll-animations/#animation-range-start)\n/// or [animation-range-end](https://drafts.csswg.org/scroll-animations/#animation-range-end) property.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"lowercase\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum AnimationAttachmentRange {\n  /// The start of the animation’s attachment range is the start of its associated timeline.\n  Normal,\n  /// The animation attachment range starts at the specified point on the timeline measuring from the start of the timeline.\n  #[cfg_attr(feature = \"serde\", serde(untagged))]\n  LengthPercentage(LengthPercentage),\n  /// The animation attachment range starts at the specified point on the timeline measuring from the start of the specified named timeline range.\n  #[cfg_attr(feature = \"serde\", serde(untagged))]\n  TimelineRange {\n    /// The name of the timeline range.\n    name: TimelineRangeName,\n    /// The offset from the start of the named timeline range.\n    offset: LengthPercentage,\n  },\n}\n\nimpl<'i> AnimationAttachmentRange {\n  fn parse<'t>(input: &mut Parser<'i, 't>, default: f32) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    if input.try_parse(|input| input.expect_ident_matching(\"normal\")).is_ok() {\n      return Ok(AnimationAttachmentRange::Normal);\n    }\n\n    if let Ok(val) = input.try_parse(LengthPercentage::parse) {\n      return Ok(AnimationAttachmentRange::LengthPercentage(val));\n    }\n\n    let name = TimelineRangeName::parse(input)?;\n    let offset = input\n      .try_parse(LengthPercentage::parse)\n      .unwrap_or(LengthPercentage::Percentage(Percentage(default)));\n    Ok(AnimationAttachmentRange::TimelineRange { name, offset })\n  }\n\n  fn to_css<W>(&self, dest: &mut Printer<W>, default: f32) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      Self::Normal => dest.write_str(\"normal\"),\n      Self::LengthPercentage(val) => val.to_css(dest),\n      Self::TimelineRange { name, offset } => {\n        name.to_css(dest)?;\n        if *offset != LengthPercentage::Percentage(Percentage(default)) {\n          dest.write_char(' ')?;\n          offset.to_css(dest)?;\n        }\n        Ok(())\n      }\n    }\n  }\n}\n\nimpl Default for AnimationAttachmentRange {\n  fn default() -> Self {\n    AnimationAttachmentRange::Normal\n  }\n}\n\n/// A value for the [animation-range-start](https://drafts.csswg.org/scroll-animations/#animation-range-start) property.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub struct AnimationRangeStart(pub AnimationAttachmentRange);\n\nimpl<'i> Parse<'i> for AnimationRangeStart {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let range = AnimationAttachmentRange::parse(input, 0.0)?;\n    Ok(Self(range))\n  }\n}\n\nimpl ToCss for AnimationRangeStart {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    self.0.to_css(dest, 0.0)\n  }\n}\n\n/// A value for the [animation-range-end](https://drafts.csswg.org/scroll-animations/#animation-range-end) property.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub struct AnimationRangeEnd(pub AnimationAttachmentRange);\n\nimpl<'i> Parse<'i> for AnimationRangeEnd {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let range = AnimationAttachmentRange::parse(input, 1.0)?;\n    Ok(Self(range))\n  }\n}\n\nimpl ToCss for AnimationRangeEnd {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    self.0.to_css(dest, 1.0)\n  }\n}\n\n/// A value for the [animation-range](https://drafts.csswg.org/scroll-animations/#animation-range) shorthand property.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub struct AnimationRange {\n  /// The start of the animation's attachment range.\n  pub start: AnimationRangeStart,\n  /// The end of the animation's attachment range.\n  pub end: AnimationRangeEnd,\n}\n\nimpl<'i> Parse<'i> for AnimationRange {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let start = AnimationRangeStart::parse(input)?;\n    let end = input\n      .try_parse(AnimationRangeStart::parse)\n      .map(|r| AnimationRangeEnd(r.0))\n      .unwrap_or_else(|_| {\n        // If <'animation-range-end'> is omitted and <'animation-range-start'> includes a <timeline-range-name> component, then\n        // animation-range-end is set to that same <timeline-range-name> and 100%. Otherwise, any omitted longhand is set to its initial value.\n        match &start.0 {\n          AnimationAttachmentRange::TimelineRange { name, .. } => {\n            AnimationRangeEnd(AnimationAttachmentRange::TimelineRange {\n              name: name.clone(),\n              offset: LengthPercentage::Percentage(Percentage(1.0)),\n            })\n          }\n          _ => AnimationRangeEnd(AnimationAttachmentRange::default()),\n        }\n      });\n    Ok(AnimationRange { start, end })\n  }\n}\n\nimpl ToCss for AnimationRange {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    self.start.to_css(dest)?;\n\n    let omit_end = match (&self.start.0, &self.end.0) {\n      (\n        AnimationAttachmentRange::TimelineRange { name: start_name, .. },\n        AnimationAttachmentRange::TimelineRange {\n          name: end_name,\n          offset: end_offset,\n        },\n      ) => start_name == end_name && *end_offset == LengthPercentage::Percentage(Percentage(1.0)),\n      (_, end) => *end == AnimationAttachmentRange::default(),\n    };\n\n    if !omit_end {\n      dest.write_char(' ')?;\n      self.end.to_css(dest)?;\n    }\n    Ok(())\n  }\n}\n\ndefine_list_shorthand! {\n  /// A value for the [animation](https://drafts.csswg.org/css-animations/#animation) shorthand property.\n  pub struct Animation<'i>(VendorPrefix) {\n    /// The animation name.\n    #[cfg_attr(feature = \"serde\", serde(borrow))]\n    name: AnimationName(AnimationName<'i>, VendorPrefix),\n    /// The animation duration.\n    duration: AnimationDuration(Time, VendorPrefix),\n    /// The easing function for the animation.\n    timing_function: AnimationTimingFunction(EasingFunction, VendorPrefix),\n    /// The number of times the animation will run.\n    iteration_count: AnimationIterationCount(AnimationIterationCount, VendorPrefix),\n    /// The direction of the animation.\n    direction: AnimationDirection(AnimationDirection, VendorPrefix),\n    /// The current play state of the animation.\n    play_state: AnimationPlayState(AnimationPlayState, VendorPrefix),\n    /// The animation delay.\n    delay: AnimationDelay(Time, VendorPrefix),\n    /// The animation fill mode.\n    fill_mode: AnimationFillMode(AnimationFillMode, VendorPrefix),\n    /// The animation timeline.\n    timeline: AnimationTimeline(AnimationTimeline<'i>),\n  }\n}\n\nimpl<'i> Parse<'i> for Animation<'i> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let mut name = None;\n    let mut duration = None;\n    let mut timing_function = None;\n    let mut iteration_count = None;\n    let mut direction = None;\n    let mut play_state = None;\n    let mut delay = None;\n    let mut fill_mode = None;\n    let mut timeline = None;\n\n    macro_rules! parse_prop {\n      ($var: ident, $type: ident) => {\n        if $var.is_none() {\n          if let Ok(value) = input.try_parse($type::parse) {\n            $var = Some(value);\n            continue;\n          }\n        }\n      };\n    }\n\n    loop {\n      parse_prop!(duration, Time);\n      parse_prop!(timing_function, EasingFunction);\n      parse_prop!(delay, Time);\n      parse_prop!(iteration_count, AnimationIterationCount);\n      parse_prop!(direction, AnimationDirection);\n      parse_prop!(fill_mode, AnimationFillMode);\n      parse_prop!(play_state, AnimationPlayState);\n      parse_prop!(name, AnimationName);\n      parse_prop!(timeline, AnimationTimeline);\n      break;\n    }\n\n    Ok(Animation {\n      name: name.unwrap_or(AnimationName::None),\n      duration: duration.unwrap_or(Time::Seconds(0.0)),\n      timing_function: timing_function.unwrap_or(EasingFunction::Ease),\n      iteration_count: iteration_count.unwrap_or(AnimationIterationCount::Number(1.0)),\n      direction: direction.unwrap_or(AnimationDirection::Normal),\n      play_state: play_state.unwrap_or(AnimationPlayState::Running),\n      delay: delay.unwrap_or(Time::Seconds(0.0)),\n      fill_mode: fill_mode.unwrap_or(AnimationFillMode::None),\n      timeline: timeline.unwrap_or(AnimationTimeline::Auto),\n    })\n  }\n}\n\nimpl<'i> ToCss for Animation<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match &self.name {\n      AnimationName::None => {}\n      AnimationName::Ident(CustomIdent(name)) | AnimationName::String(CSSString(name)) => {\n        if !self.duration.is_zero() || !self.delay.is_zero() {\n          self.duration.to_css(dest)?;\n          dest.write_char(' ')?;\n        }\n\n        if !self.timing_function.is_ease() || EasingFunction::is_ident(&name) {\n          self.timing_function.to_css(dest)?;\n          dest.write_char(' ')?;\n        }\n\n        if !self.delay.is_zero() {\n          self.delay.to_css(dest)?;\n          dest.write_char(' ')?;\n        }\n\n        if self.iteration_count != AnimationIterationCount::default() || name.as_ref() == \"infinite\" {\n          self.iteration_count.to_css(dest)?;\n          dest.write_char(' ')?;\n        }\n\n        if self.direction != AnimationDirection::default() || AnimationDirection::parse_string(&name).is_ok() {\n          self.direction.to_css(dest)?;\n          dest.write_char(' ')?;\n        }\n\n        if self.fill_mode != AnimationFillMode::default()\n          || (!name.eq_ignore_ascii_case(\"none\") && AnimationFillMode::parse_string(&name).is_ok())\n        {\n          self.fill_mode.to_css(dest)?;\n          dest.write_char(' ')?;\n        }\n\n        if self.play_state != AnimationPlayState::default() || AnimationPlayState::parse_string(&name).is_ok() {\n          self.play_state.to_css(dest)?;\n          dest.write_char(' ')?;\n        }\n      }\n    }\n\n    // Eventually we could output a string here to avoid duplicating some properties above.\n    // Chrome does not yet support strings, however.\n    self.name.to_css(dest)?;\n\n    if self.name != AnimationName::None && self.timeline != AnimationTimeline::default() {\n      dest.write_char(' ')?;\n      self.timeline.to_css(dest)?;\n    }\n\n    Ok(())\n  }\n}\n\n/// A list of animations.\npub type AnimationList<'i> = SmallVec<[Animation<'i>; 1]>;\n\n#[derive(Default)]\npub(crate) struct AnimationHandler<'i> {\n  names: Option<(SmallVec<[AnimationName<'i>; 1]>, VendorPrefix)>,\n  durations: Option<(SmallVec<[Time; 1]>, VendorPrefix)>,\n  timing_functions: Option<(SmallVec<[EasingFunction; 1]>, VendorPrefix)>,\n  iteration_counts: Option<(SmallVec<[AnimationIterationCount; 1]>, VendorPrefix)>,\n  directions: Option<(SmallVec<[AnimationDirection; 1]>, VendorPrefix)>,\n  play_states: Option<(SmallVec<[AnimationPlayState; 1]>, VendorPrefix)>,\n  delays: Option<(SmallVec<[Time; 1]>, VendorPrefix)>,\n  fill_modes: Option<(SmallVec<[AnimationFillMode; 1]>, VendorPrefix)>,\n  timelines: Option<SmallVec<[AnimationTimeline<'i>; 1]>>,\n  range_starts: Option<SmallVec<[AnimationRangeStart; 1]>>,\n  range_ends: Option<SmallVec<[AnimationRangeEnd; 1]>>,\n  has_any: bool,\n}\n\nimpl<'i> PropertyHandler<'i> for AnimationHandler<'i> {\n  fn handle_property(\n    &mut self,\n    property: &Property<'i>,\n    dest: &mut DeclarationList<'i>,\n    context: &mut PropertyHandlerContext<'i, '_>,\n  ) -> bool {\n    macro_rules! maybe_flush {\n      ($prop: ident, $val: expr, $vp: ident) => {{\n        // If two vendor prefixes for the same property have different\n        // values, we need to flush what we have immediately to preserve order.\n        if let Some((val, prefixes)) = &self.$prop {\n          if val != $val && !prefixes.contains(*$vp) {\n            self.flush(dest, context);\n          }\n        }\n      }};\n    }\n\n    macro_rules! property {\n      ($prop: ident, $val: expr, $vp: ident) => {{\n        maybe_flush!($prop, $val, $vp);\n\n        // Otherwise, update the value and add the prefix.\n        if let Some((val, prefixes)) = &mut self.$prop {\n          *val = $val.clone();\n          *prefixes |= *$vp;\n        } else {\n          self.$prop = Some(($val.clone(), *$vp));\n          self.has_any = true;\n        }\n      }};\n    }\n\n    match property {\n      Property::AnimationName(val, vp) => property!(names, val, vp),\n      Property::AnimationDuration(val, vp) => property!(durations, val, vp),\n      Property::AnimationTimingFunction(val, vp) => property!(timing_functions, val, vp),\n      Property::AnimationIterationCount(val, vp) => property!(iteration_counts, val, vp),\n      Property::AnimationDirection(val, vp) => property!(directions, val, vp),\n      Property::AnimationPlayState(val, vp) => property!(play_states, val, vp),\n      Property::AnimationDelay(val, vp) => property!(delays, val, vp),\n      Property::AnimationFillMode(val, vp) => property!(fill_modes, val, vp),\n      Property::AnimationTimeline(val) => {\n        self.timelines = Some(val.clone());\n        self.has_any = true;\n      }\n      Property::AnimationRangeStart(val) => {\n        self.range_starts = Some(val.clone());\n        self.has_any = true;\n      }\n      Property::AnimationRangeEnd(val) => {\n        self.range_ends = Some(val.clone());\n        self.has_any = true;\n      }\n      Property::AnimationRange(val) => {\n        self.range_starts = Some(val.iter().map(|v| v.start.clone()).collect());\n        self.range_ends = Some(val.iter().map(|v| v.end.clone()).collect());\n        self.has_any = true;\n      }\n      Property::Animation(val, vp) => {\n        let names = val.iter().map(|b| b.name.clone()).collect();\n        maybe_flush!(names, &names, vp);\n\n        let durations = val.iter().map(|b| b.duration.clone()).collect();\n        maybe_flush!(durations, &durations, vp);\n\n        let timing_functions = val.iter().map(|b| b.timing_function.clone()).collect();\n        maybe_flush!(timing_functions, &timing_functions, vp);\n\n        let iteration_counts = val.iter().map(|b| b.iteration_count.clone()).collect();\n        maybe_flush!(iteration_counts, &iteration_counts, vp);\n\n        let directions = val.iter().map(|b| b.direction.clone()).collect();\n        maybe_flush!(directions, &directions, vp);\n\n        let play_states = val.iter().map(|b| b.play_state.clone()).collect();\n        maybe_flush!(play_states, &play_states, vp);\n\n        let delays = val.iter().map(|b| b.delay.clone()).collect();\n        maybe_flush!(delays, &delays, vp);\n\n        let fill_modes = val.iter().map(|b| b.fill_mode.clone()).collect();\n        maybe_flush!(fill_modes, &fill_modes, vp);\n\n        self.timelines = Some(val.iter().map(|b| b.timeline.clone()).collect());\n\n        property!(names, &names, vp);\n        property!(durations, &durations, vp);\n        property!(timing_functions, &timing_functions, vp);\n        property!(iteration_counts, &iteration_counts, vp);\n        property!(directions, &directions, vp);\n        property!(play_states, &play_states, vp);\n        property!(delays, &delays, vp);\n        property!(fill_modes, &fill_modes, vp);\n\n        // The animation shorthand resets animation-range\n        // https://drafts.csswg.org/scroll-animations/#named-range-animation-declaration\n        self.range_starts = None;\n        self.range_ends = None;\n      }\n      Property::Unparsed(val) if is_animation_property(&val.property_id) => {\n        let mut val = Cow::Borrowed(val);\n        if matches!(val.property_id, PropertyId::Animation(_)) {\n          use crate::properties::custom::Token;\n\n          // Find an identifier that isn't a keyword and replace it with an\n          // AnimationName token so it is scoped in CSS modules.\n          for token in &mut val.to_mut().value.0 {\n            match token {\n              TokenOrValue::Token(Token::Ident(id)) => {\n                if AnimationDirection::parse_string(&id).is_err()\n                  && AnimationPlayState::parse_string(&id).is_err()\n                  && AnimationFillMode::parse_string(&id).is_err()\n                  && !EasingFunction::is_ident(&id)\n                  && id.as_ref() != \"infinite\"\n                  && id.as_ref() != \"auto\"\n                {\n                  *token = TokenOrValue::AnimationName(AnimationName::Ident(CustomIdent(id.clone())));\n                }\n              }\n              TokenOrValue::Token(Token::String(s)) => {\n                *token = TokenOrValue::AnimationName(AnimationName::String(CSSString(s.clone())));\n              }\n              _ => {}\n            }\n          }\n\n          self.range_starts = None;\n          self.range_ends = None;\n        }\n\n        self.flush(dest, context);\n        dest.push(Property::Unparsed(\n          val.get_prefixed(context.targets, Feature::Animation),\n        ));\n      }\n      _ => return false,\n    }\n\n    true\n  }\n\n  fn finalize(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {\n    self.flush(dest, context);\n  }\n}\n\nimpl<'i> AnimationHandler<'i> {\n  fn flush(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {\n    if !self.has_any {\n      return;\n    }\n\n    self.has_any = false;\n\n    let mut names = std::mem::take(&mut self.names);\n    let mut durations = std::mem::take(&mut self.durations);\n    let mut timing_functions = std::mem::take(&mut self.timing_functions);\n    let mut iteration_counts = std::mem::take(&mut self.iteration_counts);\n    let mut directions = std::mem::take(&mut self.directions);\n    let mut play_states = std::mem::take(&mut self.play_states);\n    let mut delays = std::mem::take(&mut self.delays);\n    let mut fill_modes = std::mem::take(&mut self.fill_modes);\n    let mut timelines_value = std::mem::take(&mut self.timelines);\n    let range_starts = std::mem::take(&mut self.range_starts);\n    let range_ends = std::mem::take(&mut self.range_ends);\n\n    if let (\n      Some((names, names_vp)),\n      Some((durations, durations_vp)),\n      Some((timing_functions, timing_functions_vp)),\n      Some((iteration_counts, iteration_counts_vp)),\n      Some((directions, directions_vp)),\n      Some((play_states, play_states_vp)),\n      Some((delays, delays_vp)),\n      Some((fill_modes, fill_modes_vp)),\n    ) = (\n      &mut names,\n      &mut durations,\n      &mut timing_functions,\n      &mut iteration_counts,\n      &mut directions,\n      &mut play_states,\n      &mut delays,\n      &mut fill_modes,\n    ) {\n      // Only use shorthand syntax if the number of animations matches on all properties.\n      let len = names.len();\n      let intersection = *names_vp\n        & *durations_vp\n        & *timing_functions_vp\n        & *iteration_counts_vp\n        & *directions_vp\n        & *play_states_vp\n        & *delays_vp\n        & *fill_modes_vp;\n      let mut timelines = if let Some(timelines) = &mut timelines_value {\n        Cow::Borrowed(timelines)\n      } else if !intersection.contains(VendorPrefix::None) {\n        // Prefixed animation shorthand does not support animation-timeline\n        Cow::Owned(std::iter::repeat(AnimationTimeline::Auto).take(len).collect())\n      } else {\n        Cow::Owned(SmallVec::new())\n      };\n\n      if !intersection.is_empty()\n        && durations.len() == len\n        && timing_functions.len() == len\n        && iteration_counts.len() == len\n        && directions.len() == len\n        && play_states.len() == len\n        && delays.len() == len\n        && fill_modes.len() == len\n        && timelines.len() == len\n      {\n        let timeline_property = if timelines.iter().any(|t| *t != AnimationTimeline::Auto)\n          && (intersection != VendorPrefix::None\n            || !context\n              .targets\n              .is_compatible(crate::compat::Feature::AnimationTimelineShorthand))\n        {\n          Some(Property::AnimationTimeline(timelines.clone().into_owned()))\n        } else {\n          None\n        };\n\n        let animations = izip!(\n          names.drain(..),\n          durations.drain(..),\n          timing_functions.drain(..),\n          iteration_counts.drain(..),\n          directions.drain(..),\n          play_states.drain(..),\n          delays.drain(..),\n          fill_modes.drain(..),\n          timelines.to_mut().drain(..)\n        )\n        .map(\n          |(\n            name,\n            duration,\n            timing_function,\n            iteration_count,\n            direction,\n            play_state,\n            delay,\n            fill_mode,\n            timeline,\n          )| {\n            Animation {\n              name,\n              duration,\n              timing_function,\n              iteration_count,\n              direction,\n              play_state,\n              delay,\n              fill_mode,\n              timeline: if timeline_property.is_some() {\n                AnimationTimeline::Auto\n              } else {\n                timeline\n              },\n            }\n          },\n        )\n        .collect();\n        let prefix = context.targets.prefixes(intersection, Feature::Animation);\n        dest.push(Property::Animation(animations, prefix));\n        names_vp.remove(intersection);\n        durations_vp.remove(intersection);\n        timing_functions_vp.remove(intersection);\n        iteration_counts_vp.remove(intersection);\n        directions_vp.remove(intersection);\n        play_states_vp.remove(intersection);\n        delays_vp.remove(intersection);\n        fill_modes_vp.remove(intersection);\n\n        if let Some(p) = timeline_property {\n          dest.push(p);\n        }\n        timelines_value = None;\n      }\n    }\n\n    macro_rules! prop {\n      ($var: ident, $property: ident) => {\n        if let Some((val, vp)) = $var {\n          if !vp.is_empty() {\n            let prefix = context.targets.prefixes(vp, Feature::$property);\n            dest.push(Property::$property(val, prefix))\n          }\n        }\n      };\n    }\n\n    prop!(names, AnimationName);\n    prop!(durations, AnimationDuration);\n    prop!(timing_functions, AnimationTimingFunction);\n    prop!(iteration_counts, AnimationIterationCount);\n    prop!(directions, AnimationDirection);\n    prop!(play_states, AnimationPlayState);\n    prop!(delays, AnimationDelay);\n    prop!(fill_modes, AnimationFillMode);\n\n    if let Some(val) = timelines_value {\n      dest.push(Property::AnimationTimeline(val));\n    }\n\n    match (range_starts, range_ends) {\n      (Some(range_starts), Some(range_ends)) => {\n        if range_starts.len() == range_ends.len() {\n          dest.push(Property::AnimationRange(\n            range_starts\n              .into_iter()\n              .zip(range_ends.into_iter())\n              .map(|(start, end)| AnimationRange { start, end })\n              .collect(),\n          ));\n        } else {\n          dest.push(Property::AnimationRangeStart(range_starts));\n          dest.push(Property::AnimationRangeEnd(range_ends));\n        }\n      }\n      (range_starts, range_ends) => {\n        if let Some(range_starts) = range_starts {\n          dest.push(Property::AnimationRangeStart(range_starts));\n        }\n\n        if let Some(range_ends) = range_ends {\n          dest.push(Property::AnimationRangeEnd(range_ends));\n        }\n      }\n    }\n  }\n}\n\n#[inline]\nfn is_animation_property(property_id: &PropertyId) -> bool {\n  match property_id {\n    PropertyId::AnimationName(_)\n    | PropertyId::AnimationDuration(_)\n    | PropertyId::AnimationTimingFunction(_)\n    | PropertyId::AnimationIterationCount(_)\n    | PropertyId::AnimationDirection(_)\n    | PropertyId::AnimationPlayState(_)\n    | PropertyId::AnimationDelay(_)\n    | PropertyId::AnimationFillMode(_)\n    | PropertyId::AnimationComposition\n    | PropertyId::AnimationTimeline\n    | PropertyId::AnimationRange\n    | PropertyId::AnimationRangeStart\n    | PropertyId::AnimationRangeEnd\n    | PropertyId::Animation(_) => true,\n    _ => false,\n  }\n}\n"
  },
  {
    "path": "src/properties/background.rs",
    "content": "//! CSS properties related to backgrounds.\n\nuse crate::context::PropertyHandlerContext;\nuse crate::declaration::{DeclarationBlock, DeclarationList};\nuse crate::error::{ParserError, PrinterError};\nuse crate::macros::*;\nuse crate::prefixes::Feature;\nuse crate::printer::Printer;\nuse crate::properties::{Property, PropertyId, VendorPrefix};\nuse crate::targets::{Browsers, Targets};\nuse crate::traits::{FallbackValues, IsCompatible, Parse, PropertyHandler, Shorthand, ToCss};\nuse crate::values::color::ColorFallbackKind;\nuse crate::values::image::ImageFallback;\nuse crate::values::{color::CssColor, image::Image, length::LengthPercentageOrAuto, position::*};\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse cssparser::*;\nuse itertools::izip;\nuse smallvec::SmallVec;\n\n/// A value for the [background-size](https://www.w3.org/TR/css-backgrounds-3/#background-size) property.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum BackgroundSize {\n  /// An explicit background size.\n  Explicit {\n    /// The width of the background.\n    width: LengthPercentageOrAuto,\n    /// The height of the background.\n    height: LengthPercentageOrAuto,\n  },\n  /// The `cover` keyword. Scales the background image to cover both the width and height of the element.\n  Cover,\n  /// The `contain` keyword. Scales the background image so that it fits within the element.\n  Contain,\n}\n\nimpl Default for BackgroundSize {\n  fn default() -> BackgroundSize {\n    BackgroundSize::Explicit {\n      width: LengthPercentageOrAuto::Auto,\n      height: LengthPercentageOrAuto::Auto,\n    }\n  }\n}\n\nimpl<'i> Parse<'i> for BackgroundSize {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    if let Ok(width) = input.try_parse(LengthPercentageOrAuto::parse) {\n      let height = input\n        .try_parse(LengthPercentageOrAuto::parse)\n        .unwrap_or(LengthPercentageOrAuto::Auto);\n      return Ok(BackgroundSize::Explicit { width, height });\n    }\n\n    let location = input.current_source_location();\n    let ident = input.expect_ident()?;\n    Ok(match_ignore_ascii_case! { ident,\n      \"cover\" => BackgroundSize::Cover,\n      \"contain\" => BackgroundSize::Contain,\n      _ => return Err(location.new_unexpected_token_error(\n        cssparser::Token::Ident(ident.clone())\n      ))\n    })\n  }\n}\n\nimpl ToCss for BackgroundSize {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    use BackgroundSize::*;\n\n    match &self {\n      Cover => dest.write_str(\"cover\"),\n      Contain => dest.write_str(\"contain\"),\n      Explicit { width, height } => {\n        width.to_css(dest)?;\n        if *height != LengthPercentageOrAuto::Auto {\n          dest.write_str(\" \")?;\n          height.to_css(dest)?;\n        }\n        Ok(())\n      }\n    }\n  }\n}\n\nimpl IsCompatible for BackgroundSize {\n  fn is_compatible(&self, browsers: Browsers) -> bool {\n    match self {\n      BackgroundSize::Explicit { width, height } => {\n        width.is_compatible(browsers) && height.is_compatible(browsers)\n      }\n      BackgroundSize::Cover | BackgroundSize::Contain => true,\n    }\n  }\n}\n\nenum_property! {\n  /// A [`<repeat-style>`](https://www.w3.org/TR/css-backgrounds-3/#typedef-repeat-style) value,\n  /// used within the `background-repeat` property to represent how a background image is repeated\n  /// in a single direction.\n  ///\n  /// See [BackgroundRepeat](BackgroundRepeat).\n  pub enum BackgroundRepeatKeyword {\n    /// The image is repeated in this direction.\n    Repeat,\n    /// The image is repeated so that it fits, and then spaced apart evenly.\n    Space,\n    /// The image is scaled so that it repeats an even number of times.\n    Round,\n    /// The image is placed once and not repeated in this direction.\n    NoRepeat,\n  }\n}\n\n/// A value for the [background-repeat](https://www.w3.org/TR/css-backgrounds-3/#background-repeat) property.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub struct BackgroundRepeat {\n  /// A repeat style for the x direction.\n  pub x: BackgroundRepeatKeyword,\n  /// A repeat style for the y direction.\n  pub y: BackgroundRepeatKeyword,\n}\n\nimpl Default for BackgroundRepeat {\n  fn default() -> BackgroundRepeat {\n    BackgroundRepeat {\n      x: BackgroundRepeatKeyword::Repeat,\n      y: BackgroundRepeatKeyword::Repeat,\n    }\n  }\n}\n\nimpl<'i> Parse<'i> for BackgroundRepeat {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    use BackgroundRepeatKeyword::*;\n    let state = input.state();\n    let ident = input.expect_ident()?;\n\n    match_ignore_ascii_case! { ident,\n      \"repeat-x\" => return Ok(BackgroundRepeat { x: Repeat, y: NoRepeat }),\n      \"repeat-y\" => return Ok(BackgroundRepeat { x: NoRepeat, y: Repeat }),\n      _ => {}\n    }\n\n    input.reset(&state);\n\n    let x = BackgroundRepeatKeyword::parse(input)?;\n    let y = input.try_parse(BackgroundRepeatKeyword::parse).unwrap_or(x.clone());\n    Ok(BackgroundRepeat { x, y })\n  }\n}\n\nimpl ToCss for BackgroundRepeat {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    use BackgroundRepeatKeyword::*;\n    match (&self.x, &self.y) {\n      (Repeat, NoRepeat) => dest.write_str(\"repeat-x\"),\n      (NoRepeat, Repeat) => dest.write_str(\"repeat-y\"),\n      (x, y) => {\n        x.to_css(dest)?;\n        if y != x {\n          dest.write_str(\" \")?;\n          y.to_css(dest)?;\n        }\n        Ok(())\n      }\n    }\n  }\n}\n\nimpl IsCompatible for BackgroundRepeat {\n  fn is_compatible(&self, _browsers: Browsers) -> bool {\n    true\n  }\n}\n\nenum_property! {\n  /// A value for the [background-attachment](https://www.w3.org/TR/css-backgrounds-3/#background-attachment) property.\n  pub enum BackgroundAttachment {\n    /// The background scrolls with the container.\n    Scroll,\n    /// The background is fixed to the viewport.\n    Fixed,\n    /// The background is fixed with regard to the element’s contents.\n    Local,\n  }\n}\n\nimpl Default for BackgroundAttachment {\n  fn default() -> BackgroundAttachment {\n    BackgroundAttachment::Scroll\n  }\n}\n\nenum_property! {\n  /// A value for the [background-origin](https://www.w3.org/TR/css-backgrounds-3/#background-origin) property.\n  pub enum BackgroundOrigin {\n    /// The position is relative to the border box.\n    BorderBox,\n    /// The position is relative to the padding box.\n    PaddingBox,\n    /// The position is relative to the content box.\n    ContentBox,\n  }\n}\n\nenum_property! {\n  /// A value for the [background-clip](https://drafts.csswg.org/css-backgrounds-4/#background-clip) property.\n  pub enum BackgroundClip {\n    /// The background is clipped to the border box.\n    BorderBox,\n    /// The background is clipped to the padding box.\n    PaddingBox,\n    /// The background is clipped to the content box.\n    ContentBox,\n    /// The background is clipped to the area painted by the border.\n    Border,\n    /// The background is clipped to the text content of the element.\n    Text,\n  }\n}\n\nimpl PartialEq<BackgroundOrigin> for BackgroundClip {\n  fn eq(&self, other: &BackgroundOrigin) -> bool {\n    match (self, other) {\n      (BackgroundClip::BorderBox, BackgroundOrigin::BorderBox)\n      | (BackgroundClip::PaddingBox, BackgroundOrigin::PaddingBox)\n      | (BackgroundClip::ContentBox, BackgroundOrigin::ContentBox) => true,\n      _ => false,\n    }\n  }\n}\n\nimpl Into<BackgroundClip> for BackgroundOrigin {\n  fn into(self) -> BackgroundClip {\n    match self {\n      BackgroundOrigin::BorderBox => BackgroundClip::BorderBox,\n      BackgroundOrigin::PaddingBox => BackgroundClip::PaddingBox,\n      BackgroundOrigin::ContentBox => BackgroundClip::ContentBox,\n    }\n  }\n}\n\nimpl Default for BackgroundClip {\n  fn default() -> BackgroundClip {\n    BackgroundClip::BorderBox\n  }\n}\n\nimpl BackgroundClip {\n  fn is_background_box(&self) -> bool {\n    matches!(\n      self,\n      BackgroundClip::BorderBox | BackgroundClip::PaddingBox | BackgroundClip::ContentBox\n    )\n  }\n}\n\ndefine_list_shorthand! {\n  /// A value for the [background-position](https://drafts.csswg.org/css-backgrounds/#background-position) shorthand property.\n  pub struct BackgroundPosition {\n    /// The x-position.\n    x: BackgroundPositionX(HorizontalPosition),\n    /// The y-position.\n    y: BackgroundPositionY(VerticalPosition),\n  }\n}\n\nimpl From<Position> for BackgroundPosition {\n  fn from(pos: Position) -> Self {\n    BackgroundPosition { x: pos.x, y: pos.y }\n  }\n}\n\nimpl Into<Position> for &BackgroundPosition {\n  fn into(self) -> Position {\n    Position {\n      x: self.x.clone(),\n      y: self.y.clone(),\n    }\n  }\n}\n\nimpl Default for BackgroundPosition {\n  fn default() -> Self {\n    Position::default().into()\n  }\n}\n\nimpl<'i> Parse<'i> for BackgroundPosition {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let pos = Position::parse(input)?;\n    Ok(pos.into())\n  }\n}\n\nimpl ToCss for BackgroundPosition {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    let pos: Position = self.into();\n    pos.to_css(dest)\n  }\n}\n\n/// A value for the [background](https://www.w3.org/TR/css-backgrounds-3/#background) shorthand property.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct Background<'i> {\n  /// The background image.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  pub image: Image<'i>,\n  /// The background color.\n  pub color: CssColor,\n  /// The background position.\n  pub position: BackgroundPosition,\n  /// How the background image should repeat.\n  pub repeat: BackgroundRepeat,\n  /// The size of the background image.\n  pub size: BackgroundSize,\n  /// The background attachment.\n  pub attachment: BackgroundAttachment,\n  /// The background origin.\n  pub origin: BackgroundOrigin,\n  /// How the background should be clipped.\n  pub clip: BackgroundClip,\n}\n\nimpl<'i> Parse<'i> for Background<'i> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let mut color: Option<CssColor> = None;\n    let mut position: Option<BackgroundPosition> = None;\n    let mut size: Option<BackgroundSize> = None;\n    let mut image: Option<Image> = None;\n    let mut repeat: Option<BackgroundRepeat> = None;\n    let mut attachment: Option<BackgroundAttachment> = None;\n    let mut origin: Option<BackgroundOrigin> = None;\n    let mut clip: Option<BackgroundClip> = None;\n\n    loop {\n      // TODO: only allowed on the last background.\n      if color.is_none() {\n        if let Ok(value) = input.try_parse(CssColor::parse) {\n          color = Some(value);\n          continue;\n        }\n      }\n\n      if position.is_none() {\n        if let Ok(value) = input.try_parse(BackgroundPosition::parse) {\n          position = Some(value);\n\n          size = input\n            .try_parse(|input| {\n              input.expect_delim('/')?;\n              BackgroundSize::parse(input)\n            })\n            .ok();\n\n          continue;\n        }\n      }\n\n      if image.is_none() {\n        if let Ok(value) = input.try_parse(Image::parse) {\n          image = Some(value);\n          continue;\n        }\n      }\n\n      if repeat.is_none() {\n        if let Ok(value) = input.try_parse(BackgroundRepeat::parse) {\n          repeat = Some(value);\n          continue;\n        }\n      }\n\n      if attachment.is_none() {\n        if let Ok(value) = input.try_parse(BackgroundAttachment::parse) {\n          attachment = Some(value);\n          continue;\n        }\n      }\n\n      if origin.is_none() {\n        if let Ok(value) = input.try_parse(BackgroundOrigin::parse) {\n          origin = Some(value);\n          continue;\n        }\n      }\n\n      if clip.is_none() {\n        if let Ok(value) = input.try_parse(BackgroundClip::parse) {\n          clip = Some(value);\n          continue;\n        }\n      }\n\n      break;\n    }\n\n    if clip.is_none() {\n      if let Some(origin) = origin {\n        clip = Some(origin.into());\n      }\n    }\n\n    Ok(Background {\n      image: image.unwrap_or_default(),\n      color: color.unwrap_or_default(),\n      position: position.unwrap_or_default(),\n      repeat: repeat.unwrap_or_default(),\n      size: size.unwrap_or_default(),\n      attachment: attachment.unwrap_or_default(),\n      origin: origin.unwrap_or(BackgroundOrigin::PaddingBox),\n      clip: clip.unwrap_or(BackgroundClip::BorderBox),\n    })\n  }\n}\n\nimpl<'i> ToCss for Background<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    let mut has_output = false;\n\n    if self.color != CssColor::default() {\n      self.color.to_css(dest)?;\n      has_output = true;\n    }\n\n    if self.image != Image::default() {\n      if has_output {\n        dest.write_str(\" \")?;\n      }\n      self.image.to_css(dest)?;\n      has_output = true;\n    }\n\n    let position: Position = (&self.position).into();\n    if !position.is_zero() || self.size != BackgroundSize::default() {\n      if has_output {\n        dest.write_str(\" \")?;\n      }\n      position.to_css(dest)?;\n\n      if self.size != BackgroundSize::default() {\n        dest.delim('/', true)?;\n        self.size.to_css(dest)?;\n      }\n\n      has_output = true;\n    }\n\n    if self.repeat != BackgroundRepeat::default() {\n      if has_output {\n        dest.write_str(\" \")?;\n      }\n\n      self.repeat.to_css(dest)?;\n      has_output = true;\n    }\n\n    if self.attachment != BackgroundAttachment::default() {\n      if has_output {\n        dest.write_str(\" \")?;\n      }\n\n      self.attachment.to_css(dest)?;\n      has_output = true;\n    }\n\n    let output_padding_box = self.origin != BackgroundOrigin::PaddingBox\n      || (self.clip != BackgroundOrigin::BorderBox && self.clip.is_background_box());\n    if output_padding_box {\n      if has_output {\n        dest.write_str(\" \")?;\n      }\n\n      self.origin.to_css(dest)?;\n      has_output = true;\n    }\n\n    if (output_padding_box && self.clip != self.origin) || self.clip != BackgroundOrigin::BorderBox {\n      if has_output {\n        dest.write_str(\" \")?;\n      }\n\n      self.clip.to_css(dest)?;\n      has_output = true;\n    }\n\n    // If nothing was output, then this is the initial value, e.g. background: transparent\n    if !has_output {\n      if dest.minify {\n        // `0 0` is the shortest valid background value\n        self.position.to_css(dest)?;\n      } else {\n        dest.write_str(\"none\")?;\n      }\n    }\n\n    Ok(())\n  }\n}\n\nimpl<'i> ImageFallback<'i> for Background<'i> {\n  #[inline]\n  fn get_image(&self) -> &Image<'i> {\n    &self.image\n  }\n\n  #[inline]\n  fn with_image(&self, image: Image<'i>) -> Self {\n    Background { image, ..self.clone() }\n  }\n\n  #[inline]\n  fn get_necessary_fallbacks(&self, targets: Targets) -> ColorFallbackKind {\n    self.color.get_necessary_fallbacks(targets) | self.get_image().get_necessary_fallbacks(targets)\n  }\n\n  #[inline]\n  fn get_fallback(&self, kind: ColorFallbackKind) -> Self {\n    Background {\n      color: self.color.get_fallback(kind),\n      image: self.image.get_fallback(kind),\n      ..self.clone()\n    }\n  }\n}\n\nimpl<'i> Shorthand<'i> for SmallVec<[Background<'i>; 1]> {\n  fn from_longhands(decls: &DeclarationBlock<'i>, vendor_prefix: VendorPrefix) -> Option<(Self, bool)> {\n    let mut color = None;\n    let mut images = None;\n    let mut x_positions = None;\n    let mut y_positions = None;\n    let mut repeats = None;\n    let mut sizes = None;\n    let mut attachments = None;\n    let mut origins = None;\n    let mut clips = None;\n\n    let mut count = 0;\n    let mut important_count = 0;\n    let mut length = None;\n    for (property, important) in decls.iter() {\n      let len = match property {\n        Property::BackgroundColor(value) => {\n          color = Some(value.clone());\n          count += 1;\n          if important {\n            important_count += 1;\n          }\n          continue;\n        }\n        Property::BackgroundImage(value) => {\n          images = Some(value.clone());\n          value.len()\n        }\n        Property::BackgroundPosition(value) => {\n          x_positions = Some(value.iter().map(|v| v.x.clone()).collect());\n          y_positions = Some(value.iter().map(|v| v.y.clone()).collect());\n          value.len()\n        }\n        Property::BackgroundPositionX(value) => {\n          x_positions = Some(value.clone());\n          value.len()\n        }\n        Property::BackgroundPositionY(value) => {\n          y_positions = Some(value.clone());\n          value.len()\n        }\n        Property::BackgroundRepeat(value) => {\n          repeats = Some(value.clone());\n          value.len()\n        }\n        Property::BackgroundSize(value) => {\n          sizes = Some(value.clone());\n          value.len()\n        }\n        Property::BackgroundAttachment(value) => {\n          attachments = Some(value.clone());\n          value.len()\n        }\n        Property::BackgroundOrigin(value) => {\n          origins = Some(value.clone());\n          value.len()\n        }\n        Property::BackgroundClip(value, vp) => {\n          if *vp != vendor_prefix {\n            return None;\n          }\n          clips = Some(value.clone());\n          value.len()\n        }\n        Property::Background(val) => {\n          color = Some(val.last().unwrap().color.clone());\n          images = Some(val.iter().map(|b| b.image.clone()).collect());\n          x_positions = Some(val.iter().map(|b| b.position.x.clone()).collect());\n          y_positions = Some(val.iter().map(|b| b.position.y.clone()).collect());\n          repeats = Some(val.iter().map(|b| b.repeat.clone()).collect());\n          sizes = Some(val.iter().map(|b| b.size.clone()).collect());\n          attachments = Some(val.iter().map(|b| b.attachment.clone()).collect());\n          origins = Some(val.iter().map(|b| b.origin.clone()).collect());\n          clips = Some(val.iter().map(|b| b.clip.clone()).collect());\n          val.len()\n        }\n        _ => continue,\n      };\n\n      // Lengths must be equal.\n      if length.is_none() {\n        length = Some(len);\n      } else if length.unwrap() != len {\n        return None;\n      }\n\n      count += 1;\n      if important {\n        important_count += 1;\n      }\n    }\n\n    // !important flags must match to produce a shorthand.\n    if important_count > 0 && important_count != count {\n      return None;\n    }\n\n    if color.is_some()\n      && images.is_some()\n      && x_positions.is_some()\n      && y_positions.is_some()\n      && repeats.is_some()\n      && sizes.is_some()\n      && attachments.is_some()\n      && origins.is_some()\n      && clips.is_some()\n    {\n      let length = length.unwrap();\n      let values = izip!(\n        images.unwrap().drain(..),\n        x_positions.unwrap().drain(..),\n        y_positions.unwrap().drain(..),\n        repeats.unwrap().drain(..),\n        sizes.unwrap().drain(..),\n        attachments.unwrap().drain(..),\n        origins.unwrap().drain(..),\n        clips.unwrap().drain(..),\n      )\n      .enumerate()\n      .map(\n        |(i, (image, x_position, y_position, repeat, size, attachment, origin, clip))| Background {\n          color: if i == length - 1 {\n            color.clone().unwrap()\n          } else {\n            CssColor::default()\n          },\n          image,\n          position: BackgroundPosition {\n            x: x_position,\n            y: y_position,\n          },\n          repeat,\n          size,\n          attachment,\n          origin,\n          clip: clip,\n        },\n      )\n      .collect();\n      return Some((values, important_count > 0));\n    }\n\n    None\n  }\n\n  fn longhands(vendor_prefix: VendorPrefix) -> Vec<PropertyId<'static>> {\n    vec![\n      PropertyId::BackgroundColor,\n      PropertyId::BackgroundImage,\n      PropertyId::BackgroundPositionX,\n      PropertyId::BackgroundPositionY,\n      PropertyId::BackgroundRepeat,\n      PropertyId::BackgroundSize,\n      PropertyId::BackgroundAttachment,\n      PropertyId::BackgroundOrigin,\n      PropertyId::BackgroundClip(vendor_prefix),\n    ]\n  }\n\n  fn longhand(&self, property_id: &PropertyId) -> Option<Property<'i>> {\n    match property_id {\n      PropertyId::BackgroundColor => Some(Property::BackgroundColor(self.last().unwrap().color.clone())),\n      PropertyId::BackgroundImage => Some(Property::BackgroundImage(\n        self.iter().map(|v| v.image.clone()).collect(),\n      )),\n      PropertyId::BackgroundPositionX => Some(Property::BackgroundPositionX(\n        self.iter().map(|v| v.position.x.clone()).collect(),\n      )),\n      PropertyId::BackgroundPositionY => Some(Property::BackgroundPositionY(\n        self.iter().map(|v| v.position.y.clone()).collect(),\n      )),\n      PropertyId::BackgroundRepeat => Some(Property::BackgroundRepeat(\n        self.iter().map(|v| v.repeat.clone()).collect(),\n      )),\n      PropertyId::BackgroundSize => Some(Property::BackgroundSize(self.iter().map(|v| v.size.clone()).collect())),\n      PropertyId::BackgroundAttachment => Some(Property::BackgroundAttachment(\n        self.iter().map(|v| v.attachment.clone()).collect(),\n      )),\n      PropertyId::BackgroundOrigin => Some(Property::BackgroundOrigin(\n        self.iter().map(|v| v.origin.clone()).collect(),\n      )),\n      PropertyId::BackgroundClip(vp) => Some(Property::BackgroundClip(\n        self.iter().map(|v| v.clip.clone()).collect(),\n        *vp,\n      )),\n      _ => None,\n    }\n  }\n\n  fn set_longhand(&mut self, property: &Property<'i>) -> Result<(), ()> {\n    macro_rules! longhand {\n      ($value: ident, $key: ident $(.$k: ident)*) => {{\n        if $value.len() != self.len() {\n          return Err(());\n        }\n        for (i, item) in self.iter_mut().enumerate() {\n          item.$key$(.$k)* = $value[i].clone();\n        }\n      }};\n    }\n\n    match property {\n      Property::BackgroundColor(value) => self.last_mut().unwrap().color = value.clone(),\n      Property::BackgroundImage(value) => longhand!(value, image),\n      Property::BackgroundPositionX(value) => longhand!(value, position.x),\n      Property::BackgroundPositionY(value) => longhand!(value, position.y),\n      Property::BackgroundPosition(value) => longhand!(value, position),\n      Property::BackgroundRepeat(value) => longhand!(value, repeat),\n      Property::BackgroundSize(value) => longhand!(value, size),\n      Property::BackgroundAttachment(value) => longhand!(value, attachment),\n      Property::BackgroundOrigin(value) => longhand!(value, origin),\n      Property::BackgroundClip(value, _vp) => longhand!(value, clip),\n      _ => return Err(()),\n    }\n\n    Ok(())\n  }\n}\n\nproperty_bitflags! {\n  #[derive(Default)]\n  struct BackgroundProperty: u16 {\n    const BackgroundColor = 1 << 0;\n    const BackgroundImage = 1 << 1;\n    const BackgroundPositionX = 1 << 2;\n    const BackgroundPositionY = 1 << 3;\n    const BackgroundPosition = Self::BackgroundPositionX.bits() | Self::BackgroundPositionY.bits();\n    const BackgroundRepeat = 1 << 4;\n    const BackgroundSize = 1 << 5;\n    const BackgroundAttachment = 1 << 6;\n    const BackgroundOrigin = 1 << 7;\n    const BackgroundClip(_vp) = 1 << 8;\n    const Background = Self::BackgroundColor.bits() | Self::BackgroundImage.bits() | Self::BackgroundPosition.bits() | Self::BackgroundRepeat.bits() | Self::BackgroundSize.bits() | Self::BackgroundAttachment.bits() | Self::BackgroundOrigin.bits() | Self::BackgroundClip.bits();\n  }\n}\n\n#[derive(Default)]\npub(crate) struct BackgroundHandler<'i> {\n  color: Option<CssColor>,\n  images: Option<SmallVec<[Image<'i>; 1]>>,\n  has_prefix: bool,\n  x_positions: Option<SmallVec<[HorizontalPosition; 1]>>,\n  y_positions: Option<SmallVec<[VerticalPosition; 1]>>,\n  repeats: Option<SmallVec<[BackgroundRepeat; 1]>>,\n  sizes: Option<SmallVec<[BackgroundSize; 1]>>,\n  attachments: Option<SmallVec<[BackgroundAttachment; 1]>>,\n  origins: Option<SmallVec<[BackgroundOrigin; 1]>>,\n  clips: Option<(SmallVec<[BackgroundClip; 1]>, VendorPrefix)>,\n  decls: Vec<Property<'i>>,\n  flushed_properties: BackgroundProperty,\n  has_any: bool,\n}\n\nimpl<'i> PropertyHandler<'i> for BackgroundHandler<'i> {\n  fn handle_property(\n    &mut self,\n    property: &Property<'i>,\n    dest: &mut DeclarationList<'i>,\n    context: &mut PropertyHandlerContext<'i, '_>,\n  ) -> bool {\n    macro_rules! background_image {\n      ($val: expr) => {\n        flush!(images, $val);\n\n        // Store prefixed properties. Clear if we hit an unprefixed property and we have\n        // targets. In this case, the necessary prefixes will be generated.\n        self.has_prefix = $val.iter().any(|x| x.has_vendor_prefix());\n        if self.has_prefix {\n          self.decls.push(property.clone())\n        } else if context.targets.browsers.is_some() {\n          self.decls.clear();\n        }\n      };\n    }\n\n    macro_rules! flush {\n      ($key: ident, $val: expr) => {{\n        if self.$key.is_some() && self.$key.as_ref().unwrap() != $val && matches!(context.targets.browsers, Some(targets) if !$val.is_compatible(targets)) {\n          self.flush(dest, context);\n        }\n      }};\n    }\n\n    match &property {\n      Property::BackgroundColor(val) => {\n        flush!(color, val);\n        self.color = Some(val.clone());\n      }\n      Property::BackgroundImage(val) => {\n        background_image!(val);\n        self.images = Some(val.clone())\n      }\n      Property::BackgroundPosition(val) => {\n        self.x_positions = Some(val.iter().map(|p| p.x.clone()).collect());\n        self.y_positions = Some(val.iter().map(|p| p.y.clone()).collect());\n      }\n      Property::BackgroundPositionX(val) => self.x_positions = Some(val.clone()),\n      Property::BackgroundPositionY(val) => self.y_positions = Some(val.clone()),\n      Property::BackgroundRepeat(val) => self.repeats = Some(val.clone()),\n      Property::BackgroundSize(val) => self.sizes = Some(val.clone()),\n      Property::BackgroundAttachment(val) => self.attachments = Some(val.clone()),\n      Property::BackgroundOrigin(val) => self.origins = Some(val.clone()),\n      Property::BackgroundClip(val, vendor_prefix) => {\n        if let Some((clips, vp)) = &mut self.clips {\n          if vendor_prefix != vp && val != clips {\n            self.flush(dest, context);\n            self.clips = Some((val.clone(), *vendor_prefix))\n          } else {\n            if val != clips {\n              *clips = val.clone();\n            }\n            *vp |= *vendor_prefix;\n          }\n        } else {\n          self.clips = Some((val.clone(), *vendor_prefix))\n        }\n      }\n      Property::Background(val) => {\n        let images: SmallVec<[Image; 1]> = val.iter().map(|b| b.image.clone()).collect();\n        background_image!(&images);\n        let color = val.last().unwrap().color.clone();\n        flush!(color, &color);\n        let clips = val.iter().map(|b| b.clip.clone()).collect();\n        let mut clips_vp = VendorPrefix::None;\n        if let Some((cur_clips, cur_clips_vp)) = &mut self.clips {\n          if clips_vp != *cur_clips_vp && *cur_clips != clips {\n            self.flush(dest, context);\n          } else {\n            clips_vp |= *cur_clips_vp;\n          }\n        }\n        self.color = Some(color);\n        self.images = Some(images);\n        self.x_positions = Some(val.iter().map(|b| b.position.x.clone()).collect());\n        self.y_positions = Some(val.iter().map(|b| b.position.y.clone()).collect());\n        self.repeats = Some(val.iter().map(|b| b.repeat.clone()).collect());\n        self.sizes = Some(val.iter().map(|b| b.size.clone()).collect());\n        self.attachments = Some(val.iter().map(|b| b.attachment.clone()).collect());\n        self.origins = Some(val.iter().map(|b| b.origin.clone()).collect());\n        self.clips = Some((clips, clips_vp));\n      }\n      Property::Unparsed(val) if is_background_property(&val.property_id) => {\n        self.flush(dest, context);\n        let mut unparsed = val.clone();\n        context.add_unparsed_fallbacks(&mut unparsed);\n        self\n          .flushed_properties\n          .insert(BackgroundProperty::try_from(&unparsed.property_id).unwrap());\n        dest.push(Property::Unparsed(unparsed));\n      }\n      _ => return false,\n    }\n\n    self.has_any = true;\n    true\n  }\n\n  fn finalize(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {\n    // If the last declaration is prefixed, pop the last value\n    // so it isn't duplicated when we flush.\n    if self.has_prefix {\n      self.decls.pop();\n    }\n\n    dest.extend(self.decls.drain(..));\n    self.flush(dest, context);\n    self.flushed_properties = BackgroundProperty::empty();\n  }\n}\n\nimpl<'i> BackgroundHandler<'i> {\n  fn flush(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {\n    if !self.has_any {\n      return;\n    }\n\n    self.has_any = false;\n\n    macro_rules! push {\n      ($prop: ident, $val: expr) => {\n        dest.push(Property::$prop($val));\n        self.flushed_properties.insert(BackgroundProperty::$prop);\n      };\n    }\n\n    let color = std::mem::take(&mut self.color);\n    let mut images = std::mem::take(&mut self.images);\n    let mut x_positions = std::mem::take(&mut self.x_positions);\n    let mut y_positions = std::mem::take(&mut self.y_positions);\n    let mut repeats = std::mem::take(&mut self.repeats);\n    let mut sizes = std::mem::take(&mut self.sizes);\n    let mut attachments = std::mem::take(&mut self.attachments);\n    let mut origins = std::mem::take(&mut self.origins);\n    let mut clips = std::mem::take(&mut self.clips);\n\n    if let (\n      Some(color),\n      Some(images),\n      Some(x_positions),\n      Some(y_positions),\n      Some(repeats),\n      Some(sizes),\n      Some(attachments),\n      Some(origins),\n      Some(clips),\n    ) = (\n      &color,\n      &mut images,\n      &mut x_positions,\n      &mut y_positions,\n      &mut repeats,\n      &mut sizes,\n      &mut attachments,\n      &mut origins,\n      &mut clips,\n    ) {\n      // Only use shorthand syntax if the number of layers matches on all properties.\n      let len = images.len();\n      if x_positions.len() == len\n        && y_positions.len() == len\n        && repeats.len() == len\n        && sizes.len() == len\n        && attachments.len() == len\n        && origins.len() == len\n        && clips.0.len() == len\n      {\n        let clip_prefixes = if clips.0.iter().any(|clip| *clip == BackgroundClip::Text) {\n          context.targets.prefixes(clips.1, Feature::BackgroundClip)\n        } else {\n          clips.1\n        };\n\n        let clip_property = if clip_prefixes != VendorPrefix::None {\n          Some(Property::BackgroundClip(clips.0.clone(), clip_prefixes))\n        } else {\n          None\n        };\n\n        let mut backgrounds: SmallVec<[Background<'i>; 1]> = izip!(\n          images.drain(..),\n          x_positions.drain(..),\n          y_positions.drain(..),\n          repeats.drain(..),\n          sizes.drain(..),\n          attachments.drain(..),\n          origins.drain(..),\n          clips.0.drain(..)\n        )\n        .enumerate()\n        .map(\n          |(i, (image, x_position, y_position, repeat, size, attachment, origin, clip))| Background {\n            color: if i == len - 1 {\n              color.clone()\n            } else {\n              CssColor::default()\n            },\n            image,\n            position: BackgroundPosition {\n              x: x_position,\n              y: y_position,\n            },\n            repeat,\n            size,\n            attachment,\n            origin,\n            clip: if clip_prefixes == VendorPrefix::None {\n              clip\n            } else {\n              BackgroundClip::default()\n            },\n          },\n        )\n        .collect();\n\n        if !self.flushed_properties.intersects(BackgroundProperty::Background) {\n          for fallback in backgrounds.get_fallbacks(context.targets) {\n            push!(Background, fallback);\n          }\n        }\n\n        push!(Background, backgrounds);\n\n        if let Some(clip) = clip_property {\n          dest.push(clip);\n          self.flushed_properties.insert(BackgroundProperty::BackgroundClip);\n        }\n\n        self.reset();\n        return;\n      }\n    }\n\n    if let Some(mut color) = color {\n      if !self.flushed_properties.contains(BackgroundProperty::BackgroundColor) {\n        for fallback in color.get_fallbacks(context.targets) {\n          push!(BackgroundColor, fallback);\n        }\n      }\n\n      push!(BackgroundColor, color);\n    }\n\n    if let Some(mut images) = images {\n      if !self.flushed_properties.contains(BackgroundProperty::BackgroundImage) {\n        for fallback in images.get_fallbacks(context.targets) {\n          push!(BackgroundImage, fallback);\n        }\n      }\n\n      push!(BackgroundImage, images);\n    }\n\n    match (&mut x_positions, &mut y_positions) {\n      (Some(x_positions), Some(y_positions)) if x_positions.len() == y_positions.len() => {\n        let positions = izip!(x_positions.drain(..), y_positions.drain(..))\n          .map(|(x, y)| BackgroundPosition { x, y })\n          .collect();\n        push!(BackgroundPosition, positions);\n      }\n      _ => {\n        if let Some(x_positions) = x_positions {\n          push!(BackgroundPositionX, x_positions);\n        }\n\n        if let Some(y_positions) = y_positions {\n          push!(BackgroundPositionY, y_positions);\n        }\n      }\n    }\n\n    if let Some(repeats) = repeats {\n      push!(BackgroundRepeat, repeats);\n    }\n\n    if let Some(sizes) = sizes {\n      push!(BackgroundSize, sizes);\n    }\n\n    if let Some(attachments) = attachments {\n      push!(BackgroundAttachment, attachments);\n    }\n\n    if let Some(origins) = origins {\n      push!(BackgroundOrigin, origins);\n    }\n\n    if let Some((clips, vp)) = clips {\n      let prefixes = if clips.iter().any(|clip| *clip == BackgroundClip::Text) {\n        context.targets.prefixes(vp, Feature::BackgroundClip)\n      } else {\n        vp\n      };\n      dest.push(Property::BackgroundClip(clips, prefixes));\n      self.flushed_properties.insert(BackgroundProperty::BackgroundClip);\n    }\n\n    self.reset();\n  }\n\n  fn reset(&mut self) {\n    self.color = None;\n    self.images = None;\n    self.x_positions = None;\n    self.y_positions = None;\n    self.repeats = None;\n    self.sizes = None;\n    self.attachments = None;\n    self.origins = None;\n    self.clips = None\n  }\n}\n\n#[inline]\nfn is_background_property(property_id: &PropertyId) -> bool {\n  match property_id {\n    PropertyId::BackgroundColor\n    | PropertyId::BackgroundImage\n    | PropertyId::BackgroundPosition\n    | PropertyId::BackgroundPositionX\n    | PropertyId::BackgroundPositionY\n    | PropertyId::BackgroundRepeat\n    | PropertyId::BackgroundSize\n    | PropertyId::BackgroundAttachment\n    | PropertyId::BackgroundOrigin\n    | PropertyId::BackgroundClip(_)\n    | PropertyId::Background => true,\n    _ => false,\n  }\n}\n"
  },
  {
    "path": "src/properties/border.rs",
    "content": "//! CSS properties related to borders.\n\nuse super::border_image::*;\nuse super::border_radius::*;\nuse crate::compat::Feature;\nuse crate::context::PropertyHandlerContext;\nuse crate::declaration::{DeclarationBlock, DeclarationList};\nuse crate::error::{ParserError, PrinterError};\nuse crate::logical::PropertyCategory;\nuse crate::macros::*;\nuse crate::printer::Printer;\nuse crate::properties::custom::UnparsedProperty;\nuse crate::properties::{Property, PropertyId};\nuse crate::targets::Browsers;\nuse crate::targets::Targets;\nuse crate::traits::{FallbackValues, IsCompatible, Parse, PropertyHandler, Shorthand, ToCss};\nuse crate::values::color::{ColorFallbackKind, CssColor};\nuse crate::values::length::*;\nuse crate::values::rect::Rect;\nuse crate::values::size::Size2D;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse cssparser::*;\n\n/// A value for the [border-width](https://www.w3.org/TR/css-backgrounds-3/#border-width) property.\n#[derive(Debug, Clone, PartialEq, Parse, ToCss)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum BorderSideWidth {\n  /// A UA defined `thin` value.\n  Thin,\n  /// A UA defined `medium` value.\n  Medium,\n  /// A UA defined `thick` value.\n  Thick,\n  /// An explicit width.\n  Length(Length),\n}\n\nimpl Default for BorderSideWidth {\n  fn default() -> BorderSideWidth {\n    BorderSideWidth::Medium\n  }\n}\n\nimpl IsCompatible for BorderSideWidth {\n  fn is_compatible(&self, browsers: Browsers) -> bool {\n    match self {\n      BorderSideWidth::Length(length) => length.is_compatible(browsers),\n      _ => true,\n    }\n  }\n}\n\nenum_property! {\n  /// A [`<line-style>`](https://drafts.csswg.org/css-backgrounds/#typedef-line-style) value, used in the `border-style` property.\n  pub enum LineStyle {\n    /// No border.\n    None,\n    /// Similar to `none` but with different rules for tables.\n    Hidden,\n    /// Looks as if the content on the inside of the border is sunken into the canvas.\n    Inset,\n    /// Looks as if it were carved in the canvas.\n    Groove,\n    /// Looks as if the content on the inside of the border is coming out of the canvas.\n    Outset,\n    /// Looks as if it were coming out of the canvas.\n    Ridge,\n    /// A series of round dots.\n    Dotted,\n    /// A series of square-ended dashes.\n    Dashed,\n    /// A single line segment.\n    Solid,\n    /// Two parallel solid lines with some space between them.\n    Double,\n  }\n}\n\nimpl Default for LineStyle {\n  fn default() -> LineStyle {\n    LineStyle::None\n  }\n}\n\nimpl IsCompatible for LineStyle {\n  fn is_compatible(&self, _browsers: Browsers) -> bool {\n    true\n  }\n}\n\n/// A generic type that represents the `border` and `outline` shorthand properties.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct GenericBorder<S, const P: u8> {\n  /// The width of the border.\n  pub width: BorderSideWidth,\n  /// The border style.\n  pub style: S,\n  /// The border color.\n  pub color: CssColor,\n}\n\n#[cfg(feature = \"into_owned\")]\nimpl<'any, S, const P: u8> static_self::IntoOwned<'any> for GenericBorder<S, P>\nwhere\n  S: static_self::IntoOwned<'any>,\n{\n  type Owned = GenericBorder<S::Owned, P>;\n  fn into_owned(self) -> Self::Owned {\n    GenericBorder {\n      width: self.width,\n      style: self.style.into_owned(),\n      color: self.color,\n    }\n  }\n}\n\nimpl<S: Default, const P: u8> Default for GenericBorder<S, P> {\n  fn default() -> GenericBorder<S, P> {\n    GenericBorder {\n      width: BorderSideWidth::Medium,\n      style: S::default(),\n      color: CssColor::current_color(),\n    }\n  }\n}\n\nimpl<'i, S: Parse<'i> + Default, const P: u8> Parse<'i> for GenericBorder<S, P> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    // Order doesn't matter...\n    let mut color = None;\n    let mut style = None;\n    let mut width = None;\n    let mut any = false;\n    loop {\n      if width.is_none() {\n        if let Ok(value) = input.try_parse(|i| BorderSideWidth::parse(i)) {\n          width = Some(value);\n          any = true;\n        }\n      }\n      if style.is_none() {\n        if let Ok(value) = input.try_parse(S::parse) {\n          style = Some(value);\n          any = true;\n          continue;\n        }\n      }\n      if color.is_none() {\n        if let Ok(value) = input.try_parse(|i| CssColor::parse(i)) {\n          color = Some(value);\n          any = true;\n          continue;\n        }\n      }\n      break;\n    }\n    if any {\n      Ok(GenericBorder {\n        width: width.unwrap_or(BorderSideWidth::Medium),\n        style: style.unwrap_or_default(),\n        color: color.unwrap_or_else(|| CssColor::current_color()),\n      })\n    } else {\n      Err(input.new_custom_error(ParserError::InvalidDeclaration))\n    }\n  }\n}\n\nimpl<S: ToCss + Default + PartialEq, const P: u8> ToCss for GenericBorder<S, P> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    if *self == Self::default() {\n      self.style.to_css(dest)?;\n      return Ok(());\n    }\n\n    let mut needs_space = false;\n    if self.width != BorderSideWidth::default() {\n      self.width.to_css(dest)?;\n      needs_space = true;\n    }\n    if self.style != S::default() {\n      if needs_space {\n        dest.write_str(\" \")?;\n      }\n      self.style.to_css(dest)?;\n      needs_space = true;\n    }\n    if self.color != CssColor::current_color() {\n      if needs_space {\n        dest.write_str(\" \")?;\n      }\n      self.color.to_css(dest)?;\n    }\n    Ok(())\n  }\n}\n\nimpl<S: Clone, const P: u8> FallbackValues for GenericBorder<S, P> {\n  fn get_fallbacks(&mut self, targets: Targets) -> Vec<Self> {\n    self\n      .color\n      .get_fallbacks(targets)\n      .into_iter()\n      .map(|color| GenericBorder {\n        color,\n        width: self.width.clone(),\n        style: self.style.clone(),\n      })\n      .collect()\n  }\n}\n\n/// A value for the [border-top](https://www.w3.org/TR/css-backgrounds-3/#propdef-border-top) shorthand property.\npub type BorderTop = GenericBorder<LineStyle, 0>;\n/// A value for the [border-right](https://www.w3.org/TR/css-backgrounds-3/#propdef-border-right) shorthand property.\npub type BorderRight = GenericBorder<LineStyle, 1>;\n/// A value for the [border-bottom](https://www.w3.org/TR/css-backgrounds-3/#propdef-border-bottom) shorthand property.\npub type BorderBottom = GenericBorder<LineStyle, 2>;\n/// A value for the [border-left](https://www.w3.org/TR/css-backgrounds-3/#propdef-border-left) shorthand property.\npub type BorderLeft = GenericBorder<LineStyle, 3>;\n/// A value for the [border-block-start](https://drafts.csswg.org/css-logical/#propdef-border-block-start) shorthand property.\npub type BorderBlockStart = GenericBorder<LineStyle, 4>;\n/// A value for the [border-block-end](https://drafts.csswg.org/css-logical/#propdef-border-block-end) shorthand property.\npub type BorderBlockEnd = GenericBorder<LineStyle, 5>;\n/// A value for the [border-inline-start](https://drafts.csswg.org/css-logical/#propdef-border-inline-start) shorthand property.\npub type BorderInlineStart = GenericBorder<LineStyle, 6>;\n/// A value for the [border-inline-end](https://drafts.csswg.org/css-logical/#propdef-border-inline-end) shorthand property.\npub type BorderInlineEnd = GenericBorder<LineStyle, 7>;\n/// A value for the [border-block](https://drafts.csswg.org/css-logical/#propdef-border-block) shorthand property.\npub type BorderBlock = GenericBorder<LineStyle, 8>;\n/// A value for the [border-inline](https://drafts.csswg.org/css-logical/#propdef-border-inline) shorthand property.\npub type BorderInline = GenericBorder<LineStyle, 9>;\n/// A value for the [border](https://www.w3.org/TR/css-backgrounds-3/#propdef-border) shorthand property.\npub type Border = GenericBorder<LineStyle, 10>;\n\nimpl_shorthand! {\n  BorderTop(BorderTop) {\n    width: [BorderTopWidth],\n    style: [BorderTopStyle],\n    color: [BorderTopColor],\n  }\n}\n\nimpl_shorthand! {\n  BorderRight(BorderRight) {\n    width: [BorderRightWidth],\n    style: [BorderRightStyle],\n    color: [BorderRightColor],\n  }\n}\n\nimpl_shorthand! {\n  BorderBottom(BorderBottom) {\n    width: [BorderBottomWidth],\n    style: [BorderBottomStyle],\n    color: [BorderBottomColor],\n  }\n}\n\nimpl_shorthand! {\n  BorderLeft(BorderLeft) {\n    width: [BorderLeftWidth],\n    style: [BorderLeftStyle],\n    color: [BorderLeftColor],\n  }\n}\n\nimpl_shorthand! {\n  BorderBlockStart(BorderBlockStart) {\n    width: [BorderBlockStartWidth],\n    style: [BorderBlockStartStyle],\n    color: [BorderBlockStartColor],\n  }\n}\n\nimpl_shorthand! {\n  BorderBlockEnd(BorderBlockEnd) {\n    width: [BorderBlockEndWidth],\n    style: [BorderBlockEndStyle],\n    color: [BorderBlockEndColor],\n  }\n}\n\nimpl_shorthand! {\n  BorderInlineStart(BorderInlineStart) {\n    width: [BorderInlineStartWidth],\n    style: [BorderInlineStartStyle],\n    color: [BorderInlineStartColor],\n  }\n}\n\nimpl_shorthand! {\n  BorderInlineEnd(BorderInlineEnd) {\n    width: [BorderInlineEndWidth],\n    style: [BorderInlineEndStyle],\n    color: [BorderInlineEndColor],\n  }\n}\n\nimpl_shorthand! {\n  BorderBlock(BorderBlock) {\n    width: [BorderBlockStartWidth, BorderBlockEndWidth],\n    style: [BorderBlockStartStyle, BorderBlockEndStyle],\n    color: [BorderBlockStartColor, BorderBlockEndColor],\n  }\n}\n\nimpl_shorthand! {\n  BorderInline(BorderInline) {\n    width: [BorderInlineStartWidth, BorderInlineEndWidth],\n    style: [BorderInlineStartStyle, BorderInlineEndStyle],\n    color: [BorderInlineStartColor, BorderInlineEndColor],\n  }\n}\n\nimpl_shorthand! {\n  Border(Border) {\n    width: [BorderTopWidth, BorderRightWidth, BorderBottomWidth, BorderLeftWidth],\n    style: [BorderTopStyle, BorderRightStyle, BorderBottomStyle, BorderLeftStyle],\n    color: [BorderTopColor, BorderRightColor, BorderBottomColor, BorderLeftColor],\n  }\n}\n\nsize_shorthand! {\n  /// A value for the [border-block-color](https://drafts.csswg.org/css-logical/#propdef-border-block-color) shorthand property.\n  pub struct BorderBlockColor<CssColor> {\n    /// The block start value.\n    start: BorderBlockStartColor,\n    /// The block end value.\n    end: BorderBlockEndColor,\n  }\n}\n\nsize_shorthand! {\n  /// A value for the [border-block-style](https://drafts.csswg.org/css-logical/#propdef-border-block-style) shorthand property.\n  pub struct BorderBlockStyle<LineStyle> {\n    /// The block start value.\n    start: BorderBlockStartStyle,\n    /// The block end value.\n    end: BorderBlockEndStyle,\n  }\n}\n\nsize_shorthand! {\n  /// A value for the [border-block-width](https://drafts.csswg.org/css-logical/#propdef-border-block-width) shorthand property.\n  pub struct BorderBlockWidth<BorderSideWidth> {\n    /// The block start value.\n    start: BorderBlockStartWidth,\n    /// The block end value.\n    end: BorderBlockEndWidth,\n  }\n}\n\nsize_shorthand! {\n  /// A value for the [border-inline-color](https://drafts.csswg.org/css-logical/#propdef-border-inline-color) shorthand property.\n  pub struct BorderInlineColor<CssColor> {\n    /// The inline start value.\n    start: BorderInlineStartColor,\n    /// The inline end value.\n    end: BorderInlineEndColor,\n  }\n}\n\nsize_shorthand! {\n  /// A value for the [border-inline-style](https://drafts.csswg.org/css-logical/#propdef-border-inline-style) shorthand property.\n  pub struct BorderInlineStyle<LineStyle> {\n    /// The inline start value.\n    start: BorderInlineStartStyle,\n    /// The inline end value.\n    end: BorderInlineEndStyle,\n  }\n}\n\nsize_shorthand! {\n  /// A value for the [border-inline-width](https://drafts.csswg.org/css-logical/#propdef-border-inline-width) shorthand property.\n  pub struct BorderInlineWidth<BorderSideWidth> {\n    /// The inline start value.\n    start: BorderInlineStartWidth,\n    /// The inline end value.\n    end: BorderInlineEndWidth,\n  }\n}\n\nrect_shorthand! {\n  /// A value for the [border-color](https://drafts.csswg.org/css-backgrounds/#propdef-border-color) shorthand property.\n  pub struct BorderColor<CssColor> {\n    BorderTopColor,\n    BorderRightColor,\n    BorderBottomColor,\n    BorderLeftColor\n  }\n}\n\nrect_shorthand! {\n  /// A value for the [border-style](https://drafts.csswg.org/css-backgrounds/#propdef-border-style) shorthand property.\n  pub struct BorderStyle<LineStyle> {\n    BorderTopStyle,\n    BorderRightStyle,\n    BorderBottomStyle,\n    BorderLeftStyle\n  }\n}\n\nrect_shorthand! {\n  /// A value for the [border-width](https://drafts.csswg.org/css-backgrounds/#propdef-border-width) shorthand property.\n  pub struct BorderWidth<BorderSideWidth> {\n    BorderTopWidth,\n    BorderRightWidth,\n    BorderBottomWidth,\n    BorderLeftWidth\n  }\n}\n\nmacro_rules! impl_fallbacks {\n  ($t: ident $(, $name: ident)+) => {\n    impl FallbackValues for $t {\n      fn get_fallbacks(&mut self, targets: Targets) -> Vec<Self> {\n        let mut fallbacks = ColorFallbackKind::empty();\n        $(\n          fallbacks |= self.$name.get_necessary_fallbacks(targets);\n        )+\n\n        let mut res = Vec::new();\n        if fallbacks.contains(ColorFallbackKind::RGB) {\n          res.push($t {\n            $(\n              $name: self.$name.get_fallback(ColorFallbackKind::RGB),\n            )+\n          });\n        }\n\n        if fallbacks.contains(ColorFallbackKind::P3) {\n          res.push($t {\n            $(\n              $name: self.$name.get_fallback(ColorFallbackKind::P3),\n            )+\n          });\n        }\n\n        if fallbacks.contains(ColorFallbackKind::LAB) {\n          $(\n            self.$name = self.$name.get_fallback(ColorFallbackKind::LAB);\n          )+\n        }\n\n        res\n      }\n    }\n  }\n}\n\nimpl_fallbacks!(BorderBlockColor, start, end);\nimpl_fallbacks!(BorderInlineColor, start, end);\nimpl_fallbacks!(BorderColor, top, right, bottom, left);\n\n#[derive(Default, Debug, PartialEq)]\nstruct BorderShorthand {\n  pub width: Option<BorderSideWidth>,\n  pub style: Option<LineStyle>,\n  pub color: Option<CssColor>,\n}\n\nimpl BorderShorthand {\n  pub fn set_border<const P: u8>(&mut self, border: &GenericBorder<LineStyle, P>) {\n    self.width = Some(border.width.clone());\n    self.style = Some(border.style.clone());\n    self.color = Some(border.color.clone());\n  }\n\n  pub fn is_valid(&self) -> bool {\n    self.width.is_some() && self.style.is_some() && self.color.is_some()\n  }\n\n  pub fn reset(&mut self) {\n    self.width = None;\n    self.style = None;\n    self.color = None;\n  }\n\n  pub fn to_border<const P: u8>(&self) -> GenericBorder<LineStyle, P> {\n    GenericBorder {\n      width: self.width.clone().unwrap(),\n      style: self.style.clone().unwrap(),\n      color: self.color.clone().unwrap(),\n    }\n  }\n}\n\nproperty_bitflags! {\n  #[derive(Debug, Default)]\n  struct BorderProperty: u32 {\n    const BorderTopColor = 1 << 0;\n    const BorderBottomColor = 1 << 1;\n    const BorderLeftColor = 1 << 2;\n    const BorderRightColor = 1 << 3;\n    const BorderBlockStartColor = 1 << 4;\n    const BorderBlockEndColor = 1 << 5;\n    const BorderBlockColor = Self::BorderBlockStartColor.bits() | Self::BorderBlockEndColor.bits();\n    const BorderInlineStartColor = 1 << 6;\n    const BorderInlineEndColor = 1 << 7;\n    const BorderInlineColor = Self::BorderInlineStartColor.bits() | Self::BorderInlineEndColor.bits();\n    const BorderTopWidth = 1 << 8;\n    const BorderBottomWidth = 1 << 9;\n    const BorderLeftWidth = 1 << 10;\n    const BorderRightWidth = 1 << 11;\n    const BorderBlockStartWidth = 1 << 12;\n    const BorderBlockEndWidth = 1 << 13;\n    const BorderBlockWidth = Self::BorderBlockStartWidth.bits() | Self::BorderBlockEndWidth.bits();\n    const BorderInlineStartWidth = 1 << 14;\n    const BorderInlineEndWidth = 1 << 15;\n    const BorderInlineWidth = Self::BorderInlineStartWidth.bits() | Self::BorderInlineEndWidth.bits();\n    const BorderTopStyle = 1 << 16;\n    const BorderBottomStyle = 1 << 17;\n    const BorderLeftStyle = 1 << 18;\n    const BorderRightStyle = 1 << 19;\n    const BorderBlockStartStyle = 1 << 20;\n    const BorderBlockEndStyle = 1 << 21;\n    const BorderBlockStyle = Self::BorderBlockStartStyle.bits() | Self::BorderBlockEndStyle.bits();\n    const BorderInlineStartStyle = 1 << 22;\n    const BorderInlineEndStyle = 1 << 23;\n    const BorderInlineStyle = Self::BorderInlineStartStyle.bits() | Self::BorderInlineEndStyle.bits();\n    const BorderTop = Self::BorderTopColor.bits() | Self::BorderTopWidth.bits() | Self::BorderTopStyle.bits();\n    const BorderBottom = Self::BorderBottomColor.bits() | Self::BorderBottomWidth.bits() | Self::BorderBottomStyle.bits();\n    const BorderLeft = Self::BorderLeftColor.bits() | Self::BorderLeftWidth.bits() | Self::BorderLeftStyle.bits();\n    const BorderRight = Self::BorderRightColor.bits() | Self::BorderRightWidth.bits() | Self::BorderRightStyle.bits();\n    const BorderBlockStart = Self::BorderBlockStartColor.bits() | Self::BorderBlockStartWidth.bits() | Self::BorderBlockStartStyle.bits();\n    const BorderBlockEnd = Self::BorderBlockEndColor.bits() | Self::BorderBlockEndWidth.bits() | Self::BorderBlockEndStyle.bits();\n    const BorderInlineStart = Self::BorderInlineStartColor.bits() | Self::BorderInlineStartWidth.bits() | Self::BorderInlineStartStyle.bits();\n    const BorderInlineEnd = Self::BorderInlineEndColor.bits() | Self::BorderInlineEndWidth.bits() | Self::BorderInlineEndStyle.bits();\n    const BorderBlock = Self::BorderBlockStart.bits() | Self::BorderBlockEnd.bits();\n    const BorderInline = Self::BorderInlineStart.bits() | Self::BorderInlineEnd.bits();\n    const BorderWidth = Self::BorderLeftWidth.bits() | Self::BorderRightWidth.bits() | Self::BorderTopWidth.bits() | Self::BorderBottomWidth.bits();\n    const BorderStyle = Self::BorderLeftStyle.bits() | Self::BorderRightStyle.bits() | Self::BorderTopStyle.bits() | Self::BorderBottomStyle.bits();\n    const BorderColor = Self::BorderLeftColor.bits() | Self::BorderRightColor.bits() | Self::BorderTopColor.bits() | Self::BorderBottomColor.bits();\n    const Border = Self::BorderWidth.bits() | Self::BorderStyle.bits() | Self::BorderColor.bits();\n  }\n}\n\n#[derive(Debug, Default)]\npub(crate) struct BorderHandler<'i> {\n  border_top: BorderShorthand,\n  border_bottom: BorderShorthand,\n  border_left: BorderShorthand,\n  border_right: BorderShorthand,\n  border_block_start: BorderShorthand,\n  border_block_end: BorderShorthand,\n  border_inline_start: BorderShorthand,\n  border_inline_end: BorderShorthand,\n  category: PropertyCategory,\n  border_image_handler: BorderImageHandler<'i>,\n  border_radius_handler: BorderRadiusHandler<'i>,\n  flushed_properties: BorderProperty,\n  has_any: bool,\n}\n\nimpl<'i> PropertyHandler<'i> for BorderHandler<'i> {\n  fn handle_property(\n    &mut self,\n    property: &Property<'i>,\n    dest: &mut DeclarationList<'i>,\n    context: &mut PropertyHandlerContext<'i, '_>,\n  ) -> bool {\n    use Property::*;\n\n    macro_rules! flush {\n      ($key: ident, $prop: ident, $val: expr, $category: ident) => {{\n        if PropertyCategory::$category != self.category {\n          self.flush(dest, context);\n        }\n\n        if self.$key.$prop.is_some() && self.$key.$prop.as_ref().unwrap() != $val && matches!(context.targets.browsers, Some(targets) if !$val.is_compatible(targets)) {\n          self.flush(dest, context);\n        }\n      }};\n    }\n\n    macro_rules! property {\n      ($key: ident, $prop: ident, $val: expr, $category: ident) => {{\n        flush!($key, $prop, $val, $category);\n        self.$key.$prop = Some($val.clone());\n        self.category = PropertyCategory::$category;\n        self.has_any = true;\n      }};\n    }\n\n    macro_rules! set_border {\n      ($key: ident, $val: ident, $category: ident) => {{\n        if PropertyCategory::$category != self.category {\n          self.flush(dest, context);\n        }\n        self.$key.set_border($val);\n        self.category = PropertyCategory::$category;\n        self.has_any = true;\n      }};\n    }\n\n    match &property {\n      BorderTopColor(val) => property!(border_top, color, val, Physical),\n      BorderBottomColor(val) => property!(border_bottom, color, val, Physical),\n      BorderLeftColor(val) => property!(border_left, color, val, Physical),\n      BorderRightColor(val) => property!(border_right, color, val, Physical),\n      BorderBlockStartColor(val) => property!(border_block_start, color, val, Logical),\n      BorderBlockEndColor(val) => property!(border_block_end, color, val, Logical),\n      BorderBlockColor(val) => {\n        property!(border_block_start, color, &val.start, Logical);\n        property!(border_block_end, color, &val.end, Logical);\n      }\n      BorderInlineStartColor(val) => property!(border_inline_start, color, val, Logical),\n      BorderInlineEndColor(val) => property!(border_inline_end, color, val, Logical),\n      BorderInlineColor(val) => {\n        property!(border_inline_start, color, &val.start, Logical);\n        property!(border_inline_end, color, &val.end, Logical);\n      }\n      BorderTopWidth(val) => property!(border_top, width, val, Physical),\n      BorderBottomWidth(val) => property!(border_bottom, width, val, Physical),\n      BorderLeftWidth(val) => property!(border_left, width, val, Physical),\n      BorderRightWidth(val) => property!(border_right, width, val, Physical),\n      BorderBlockStartWidth(val) => property!(border_block_start, width, val, Logical),\n      BorderBlockEndWidth(val) => property!(border_block_end, width, val, Logical),\n      BorderBlockWidth(val) => {\n        property!(border_block_start, width, &val.start, Logical);\n        property!(border_block_end, width, &val.end, Logical);\n      }\n      BorderInlineStartWidth(val) => property!(border_inline_start, width, val, Logical),\n      BorderInlineEndWidth(val) => property!(border_inline_end, width, val, Logical),\n      BorderInlineWidth(val) => {\n        property!(border_inline_start, width, &val.start, Logical);\n        property!(border_inline_end, width, &val.end, Logical);\n      }\n      BorderTopStyle(val) => property!(border_top, style, val, Physical),\n      BorderBottomStyle(val) => property!(border_bottom, style, val, Physical),\n      BorderLeftStyle(val) => property!(border_left, style, val, Physical),\n      BorderRightStyle(val) => property!(border_right, style, val, Physical),\n      BorderBlockStartStyle(val) => property!(border_block_start, style, val, Logical),\n      BorderBlockEndStyle(val) => property!(border_block_end, style, val, Logical),\n      BorderBlockStyle(val) => {\n        property!(border_block_start, style, &val.start, Logical);\n        property!(border_block_end, style, &val.end, Logical);\n      }\n      BorderInlineStartStyle(val) => property!(border_inline_start, style, val, Logical),\n      BorderInlineEndStyle(val) => property!(border_inline_end, style, val, Logical),\n      BorderInlineStyle(val) => {\n        property!(border_inline_start, style, &val.start, Logical);\n        property!(border_inline_end, style, &val.end, Logical);\n      }\n      BorderTop(val) => set_border!(border_top, val, Physical),\n      BorderBottom(val) => set_border!(border_bottom, val, Physical),\n      BorderLeft(val) => set_border!(border_left, val, Physical),\n      BorderRight(val) => set_border!(border_right, val, Physical),\n      BorderBlockStart(val) => set_border!(border_block_start, val, Logical),\n      BorderBlockEnd(val) => set_border!(border_block_end, val, Logical),\n      BorderInlineStart(val) => set_border!(border_inline_start, val, Logical),\n      BorderInlineEnd(val) => set_border!(border_inline_end, val, Logical),\n      BorderBlock(val) => {\n        set_border!(border_block_start, val, Logical);\n        set_border!(border_block_end, val, Logical);\n      }\n      BorderInline(val) => {\n        set_border!(border_inline_start, val, Logical);\n        set_border!(border_inline_end, val, Logical);\n      }\n      BorderWidth(val) => {\n        property!(border_top, width, &val.top, Physical);\n        property!(border_right, width, &val.right, Physical);\n        property!(border_bottom, width, &val.bottom, Physical);\n        property!(border_left, width, &val.left, Physical);\n        self.border_block_start.width = None;\n        self.border_block_end.width = None;\n        self.border_inline_start.width = None;\n        self.border_inline_end.width = None;\n        self.has_any = true;\n      }\n      BorderStyle(val) => {\n        property!(border_top, style, &val.top, Physical);\n        property!(border_right, style, &val.right, Physical);\n        property!(border_bottom, style, &val.bottom, Physical);\n        property!(border_left, style, &val.left, Physical);\n        self.border_block_start.style = None;\n        self.border_block_end.style = None;\n        self.border_inline_start.style = None;\n        self.border_inline_end.style = None;\n        self.has_any = true;\n      }\n      BorderColor(val) => {\n        property!(border_top, color, &val.top, Physical);\n        property!(border_right, color, &val.right, Physical);\n        property!(border_bottom, color, &val.bottom, Physical);\n        property!(border_left, color, &val.left, Physical);\n        self.border_block_start.color = None;\n        self.border_block_end.color = None;\n        self.border_inline_start.color = None;\n        self.border_inline_end.color = None;\n        self.has_any = true;\n      }\n      Border(val) => {\n        // dest.clear();\n        self.border_top.set_border(val);\n        self.border_bottom.set_border(val);\n        self.border_left.set_border(val);\n        self.border_right.set_border(val);\n        self.border_block_start.reset();\n        self.border_block_end.reset();\n        self.border_inline_start.reset();\n        self.border_inline_end.reset();\n\n        // Setting the `border` property resets `border-image`.\n        self.border_image_handler.reset();\n        self.has_any = true;\n      }\n      Unparsed(val) if is_border_property(&val.property_id) => {\n        self.flush(dest, context);\n        self.flush_unparsed(&val, dest, context);\n      }\n      _ => {\n        if self.border_image_handler.will_flush(property) {\n          self.flush(dest, context);\n        }\n\n        return self.border_image_handler.handle_property(property, dest, context)\n          || self.border_radius_handler.handle_property(property, dest, context);\n      }\n    }\n\n    true\n  }\n\n  fn finalize(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {\n    self.flush(dest, context);\n    self.flushed_properties = BorderProperty::empty();\n    self.border_image_handler.finalize(dest, context);\n    self.border_radius_handler.finalize(dest, context);\n  }\n}\n\nimpl<'i> BorderHandler<'i> {\n  fn flush(&mut self, dest: &mut DeclarationList, context: &mut PropertyHandlerContext<'i, '_>) {\n    if !self.has_any {\n      return;\n    }\n\n    self.has_any = false;\n\n    let logical_supported = !context.should_compile_logical(Feature::LogicalBorders);\n    let logical_shorthand_supported = !context.should_compile_logical(Feature::LogicalBorderShorthand);\n    macro_rules! logical_prop {\n      ($ltr: ident, $ltr_key: ident, $rtl: ident, $rtl_key: ident, $val: expr) => {{\n        context.add_logical_rule(Property::$ltr($val.clone()), Property::$rtl($val.clone()));\n      }};\n    }\n\n    macro_rules! push {\n      ($prop: ident, $val: expr) => {{\n        self.flushed_properties.insert(BorderProperty::$prop);\n        dest.push(Property::$prop($val));\n      }};\n    }\n\n    macro_rules! fallbacks {\n      ($prop: ident => $val: expr) => {{\n        let mut val = $val;\n        if !self.flushed_properties.contains(BorderProperty::$prop) {\n          let fallbacks = val.get_fallbacks(context.targets);\n          for fallback in fallbacks {\n            dest.push(Property::$prop(fallback))\n          }\n        }\n        push!($prop, val);\n      }};\n    }\n\n    macro_rules! prop {\n      (BorderInlineStart => $val: expr) => {\n        if logical_supported {\n          fallbacks!(BorderInlineStart => $val);\n        } else {\n          logical_prop!(BorderLeft, border_left, BorderRight, border_right, $val);\n        }\n      };\n      (BorderInlineStartWidth => $val: expr) => {\n        if logical_supported {\n          push!(BorderInlineStartWidth, $val);\n        } else {\n          logical_prop!(BorderLeftWidth, border_left_width, BorderRightWidth, border_right_width, $val);\n        }\n      };\n      (BorderInlineStartColor => $val: expr) => {\n        if logical_supported {\n          fallbacks!(BorderInlineStartColor => $val);\n        } else {\n          logical_prop!(BorderLeftColor, border_left_color, BorderRightColor, border_right_color, $val);\n        }\n      };\n      (BorderInlineStartStyle => $val: expr) => {\n        if logical_supported {\n          push!(BorderInlineStartStyle, $val);\n        } else {\n          logical_prop!(BorderLeftStyle, border_left_style, BorderRightStyle, border_right_style, $val);\n        }\n      };\n      (BorderInlineEnd => $val: expr) => {\n        if logical_supported {\n          fallbacks!(BorderInlineEnd => $val);\n        } else {\n          logical_prop!(BorderRight, border_right, BorderLeft, border_left, $val);\n        }\n      };\n      (BorderInlineEndWidth => $val: expr) => {\n        if logical_supported {\n          push!(BorderInlineEndWidth, $val);\n        } else {\n          logical_prop!(BorderRightWidth, border_right_width, BorderLeftWidth, border_left_width, $val);\n        }\n      };\n      (BorderInlineEndColor => $val: expr) => {\n        if logical_supported {\n          fallbacks!(BorderInlineEndColor => $val);\n        } else {\n          logical_prop!(BorderRightColor, border_right_color, BorderLeftColor, border_left_color, $val);\n        }\n      };\n      (BorderInlineEndStyle => $val: expr) => {\n        if logical_supported {\n          push!(BorderInlineEndStyle, $val);\n        } else {\n          logical_prop!(BorderRightStyle, border_right_style, BorderLeftStyle, border_left_style, $val);\n        }\n      };\n      (BorderBlockStart => $val: expr) => {\n        if logical_supported {\n          fallbacks!(BorderBlockStart => $val);\n        } else {\n          fallbacks!(BorderTop => $val);\n        }\n      };\n      (BorderBlockStartWidth => $val: expr) => {\n        if logical_supported {\n          push!(BorderBlockStartWidth, $val);\n        } else {\n          push!(BorderTopWidth, $val);\n        }\n      };\n      (BorderBlockStartColor => $val: expr) => {\n        if logical_supported {\n          fallbacks!(BorderBlockStartColor => $val);\n        } else {\n          fallbacks!(BorderTopColor => $val);\n        }\n      };\n      (BorderBlockStartStyle => $val: expr) => {\n        if logical_supported {\n          push!(BorderBlockStartStyle, $val);\n        } else {\n          push!(BorderTopStyle, $val);\n        }\n      };\n      (BorderBlockEnd => $val: expr) => {\n        if logical_supported {\n          fallbacks!(BorderBlockEnd => $val);\n        } else {\n          fallbacks!(BorderBottom => $val);\n        }\n      };\n      (BorderBlockEndWidth => $val: expr) => {\n        if logical_supported {\n          push!(BorderBlockEndWidth, $val);\n        } else {\n          push!(BorderBottomWidth, $val);\n        }\n      };\n      (BorderBlockEndColor => $val: expr) => {\n        if logical_supported {\n          fallbacks!(BorderBlockEndColor => $val);\n        } else {\n          fallbacks!(BorderBottomColor => $val);\n        }\n      };\n      (BorderBlockEndStyle => $val: expr) => {\n        if logical_supported {\n          push!(BorderBlockEndStyle, $val);\n        } else {\n          push!(BorderBottomStyle, $val);\n        }\n      };\n      (BorderLeftColor => $val: expr) => {\n        fallbacks!(BorderLeftColor => $val);\n      };\n      (BorderRightColor => $val: expr) => {\n        fallbacks!(BorderRightColor => $val);\n      };\n      (BorderTopColor => $val: expr) => {\n        fallbacks!(BorderTopColor => $val);\n      };\n      (BorderBottomColor => $val: expr) => {\n        fallbacks!(BorderBottomColor => $val);\n      };\n      (BorderColor => $val: expr) => {\n        fallbacks!(BorderColor => $val);\n      };\n      (BorderBlockColor => $val: expr) => {\n        fallbacks!(BorderBlockColor => $val);\n      };\n      (BorderInlineColor => $val: expr) => {\n        fallbacks!(BorderInlineColor => $val);\n      };\n      (BorderLeft => $val: expr) => {\n        fallbacks!(BorderLeft => $val);\n      };\n      (BorderRight => $val: expr) => {\n        fallbacks!(BorderRight => $val);\n      };\n      (BorderTop => $val: expr) => {\n        fallbacks!(BorderTop => $val);\n      };\n      (BorderBottom => $val: expr) => {\n        fallbacks!(BorderBottom => $val);\n      };\n      (BorderBlockStart => $val: expr) => {\n        fallbacks!(BorderBlockStart => $val);\n      };\n      (BorderBlockEnd => $val: expr) => {\n        fallbacks!(BorderBlockEnd => $val);\n      };\n      (BorderInlineStart => $val: expr) => {\n        fallbacks!(BorderInlineStart => $val);\n      };\n      (BorderInlineEnd => $val: expr) => {\n        fallbacks!(BorderInlineEnd => $val);\n      };\n      (BorderInline => $val: expr) => {\n        fallbacks!(BorderInline => $val);\n      };\n      (BorderBlock => $val: expr) => {\n        fallbacks!(BorderBlock => $val);\n      };\n      (Border => $val: expr) => {\n        fallbacks!(Border => $val);\n      };\n      ($prop: ident => $val: expr) => {\n        push!($prop, $val);\n      };\n    }\n\n    macro_rules! flush_category {\n      (\n        $block_start_prop: ident,\n        $block_start_width: ident,\n        $block_start_style: ident,\n        $block_start_color: ident,\n        $block_start: expr,\n        $block_end_prop: ident,\n        $block_end_width: ident,\n        $block_end_style: ident,\n        $block_end_color: ident,\n        $block_end: expr,\n        $inline_start_prop: ident,\n        $inline_start_width: ident,\n        $inline_start_style: ident,\n        $inline_start_color: ident,\n        $inline_start: expr,\n        $inline_end_prop: ident,\n        $inline_end_width: ident,\n        $inline_end_style: ident,\n        $inline_end_color: ident,\n        $inline_end: expr,\n        $is_logical: expr\n      ) => {\n        macro_rules! shorthand {\n          ($prop: ident, $key: ident) => {{\n            let has_prop = $block_start.$key.is_some() && $block_end.$key.is_some() && $inline_start.$key.is_some() && $inline_end.$key.is_some();\n            if has_prop {\n              if !$is_logical || ($block_start.$key == $block_end.$key && $block_end.$key == $inline_start.$key && $inline_start.$key == $inline_end.$key) {\n                let rect = $prop {\n                  top: std::mem::take(&mut $block_start.$key).unwrap(),\n                  right: std::mem::take(&mut $inline_end.$key).unwrap(),\n                  bottom: std::mem::take(&mut $block_end.$key).unwrap(),\n                  left: std::mem::take(&mut $inline_start.$key).unwrap()\n                };\n                prop!($prop => rect);\n              }\n            }\n          }};\n        }\n\n        macro_rules! logical_shorthand {\n          ($prop: ident, $key: ident, $start: expr, $end: expr) => {{\n            let has_prop = $start.$key.is_some() && $end.$key.is_some();\n            if has_prop {\n              prop!($prop => $prop {\n                start: std::mem::take(&mut $start.$key).unwrap(),\n                end: std::mem::take(&mut $end.$key).unwrap(),\n              });\n              $end.$key = None;\n            }\n            has_prop\n          }};\n        }\n\n        if $block_start.is_valid() && $block_end.is_valid() && $inline_start.is_valid() && $inline_end.is_valid() {\n          let top_eq_bottom = $block_start == $block_end;\n          let left_eq_right = $inline_start == $inline_end;\n          let top_eq_left = $block_start == $inline_start;\n          let top_eq_right = $block_start == $inline_end;\n          let bottom_eq_left = $block_end == $inline_start;\n          let bottom_eq_right = $block_end == $inline_end;\n\n          macro_rules! is_eq {\n            ($key: ident) => {\n              $block_start.$key == $block_end.$key &&\n              $inline_start.$key == $inline_end.$key &&\n              $inline_start.$key == $block_start.$key\n            };\n          }\n\n          macro_rules! prop_diff {\n            ($border: expr, $fallback: expr, $border_fallback: literal) => {\n              if !$is_logical && is_eq!(color) && is_eq!(style) {\n                prop!(Border => $border.to_border());\n                shorthand!(BorderWidth, width);\n              } else if !$is_logical && is_eq!(width) && is_eq!(style) {\n                prop!(Border => $border.to_border());\n                shorthand!(BorderColor, color);\n              } else if !$is_logical && is_eq!(width) && is_eq!(color) {\n                prop!(Border => $border.to_border());\n                shorthand!(BorderStyle, style);\n              } else {\n                if $border_fallback {\n                  prop!(Border => $border.to_border());\n                }\n                $fallback\n              }\n            };\n          }\n\n          macro_rules! side_diff {\n            ($border: expr, $other: expr, $prop: ident, $width: ident, $style: ident, $color: ident) => {\n              let eq_width = $border.width == $other.width;\n              let eq_style = $border.style == $other.style;\n              let eq_color = $border.color == $other.color;\n\n              // If only one of the sub-properties is different, only emit that.\n              // Otherwise, emit the full border value.\n              if eq_width && eq_style {\n                prop!($color => $other.color.clone().unwrap());\n              } else if eq_width && eq_color {\n                prop!($style => $other.style.clone().unwrap());\n              } else if eq_style && eq_color {\n                prop!($width => $other.width.clone().unwrap());\n              } else {\n                prop!($prop => $other.to_border());\n              }\n            };\n          }\n\n          if top_eq_bottom && top_eq_left && top_eq_right {\n            prop!(Border => $block_start.to_border());\n          } else if top_eq_bottom && top_eq_left {\n            prop!(Border => $block_start.to_border());\n            side_diff!($block_start, $inline_end, $inline_end_prop, $inline_end_width, $inline_end_style, $inline_end_color);\n          } else if top_eq_bottom && top_eq_right {\n            prop!(Border => $block_start.to_border());\n            side_diff!($block_start, $inline_start, $inline_start_prop, $inline_start_width, $inline_start_style, $inline_start_color);\n          } else if left_eq_right && bottom_eq_left {\n            prop!(Border => $inline_start.to_border());\n            side_diff!($inline_start, $block_start, $block_start_prop, $block_start_width, $block_start_style, $block_start_color);\n          } else if left_eq_right && top_eq_left {\n            prop!(Border => $inline_start.to_border());\n            side_diff!($inline_start, $block_end, $block_end_prop, $block_end_width, $block_end_style, $block_end_color);\n          } else if top_eq_bottom {\n            prop_diff!($block_start, {\n              // Try to use border-inline shorthands for the opposide direction if possible.\n              let mut handled = false;\n              if $is_logical {\n                let mut diff = 0;\n                if $inline_start.width != $block_start.width || $inline_end.width != $block_start.width {\n                  diff += 1;\n                }\n                if $inline_start.style != $block_start.style || $inline_end.style != $block_start.style {\n                  diff += 1;\n                }\n                if $inline_start.color != $block_start.color || $inline_end.color != $block_start.color {\n                  diff += 1;\n                }\n\n                if diff == 1 {\n                  if $inline_start.width != $block_start.width {\n                    prop!(BorderInlineWidth => BorderInlineWidth {\n                      start: $inline_start.width.clone().unwrap(),\n                      end: $inline_end.width.clone().unwrap(),\n                    });\n                    handled = true;\n                  } else if $inline_start.style != $block_start.style {\n                    prop!(BorderInlineStyle => BorderInlineStyle {\n                      start: $inline_start.style.clone().unwrap(),\n                      end: $inline_end.style.clone().unwrap()\n                    });\n                    handled = true;\n                  } else if $inline_start.color != $block_start.color {\n                    prop!(BorderInlineColor => BorderInlineColor {\n                      start: $inline_start.color.clone().unwrap(),\n                      end: $inline_end.color.clone().unwrap()\n                    });\n                    handled = true;\n                  }\n                } else if diff > 1 && $inline_start.width == $inline_end.width && $inline_start.style == $inline_end.style && $inline_start.color == $inline_end.color {\n                  prop!(BorderInline => $inline_start.to_border());\n                  handled = true;\n                }\n              }\n\n              if !handled {\n                side_diff!($block_start, $inline_start, $inline_start_prop, $inline_start_width, $inline_start_style, $inline_start_color);\n                side_diff!($block_start, $inline_end, $inline_end_prop, $inline_end_width, $inline_end_style, $inline_end_color);\n              }\n            }, true);\n          } else if left_eq_right {\n            prop_diff!($inline_start, {\n              // We know already that top != bottom, so no need to try to use border-block.\n              side_diff!($inline_start, $block_start, $block_start_prop, $block_start_width, $block_start_style, $block_start_color);\n              side_diff!($inline_start, $block_end, $block_end_prop, $block_end_width, $block_end_style, $block_end_color);\n            }, true);\n          } else if bottom_eq_right {\n            prop_diff!($block_end, {\n              side_diff!($block_end, $block_start, $block_start_prop, $block_start_width, $block_start_style, $block_start_color);\n              side_diff!($block_end, $inline_start, $inline_start_prop, $inline_start_width, $inline_start_style, $inline_start_color);\n            }, true);\n          } else {\n            prop_diff!($block_start, {\n              prop!($block_start_prop => $block_start.to_border());\n              prop!($block_end_prop => $block_end.to_border());\n              prop!($inline_start_prop => $inline_start.to_border());\n              prop!($inline_end_prop => $inline_end.to_border());\n            }, false);\n          }\n        } else {\n          shorthand!(BorderStyle, style);\n          shorthand!(BorderWidth, width);\n          shorthand!(BorderColor, color);\n\n          macro_rules! side {\n            ($val: expr, $shorthand: ident, $width: ident, $style: ident, $color: ident) => {\n              if $val.is_valid() {\n                prop!($shorthand => $val.to_border());\n              } else {\n                if let Some(style) = &$val.style {\n                  prop!($style => style.clone());\n                }\n\n                if let Some(width) = &$val.width {\n                  prop!($width => width.clone());\n                }\n\n                if let Some(color) = &$val.color {\n                  prop!($color => color.clone());\n                }\n              }\n            };\n          }\n\n          if $is_logical && $block_start == $block_end && $block_start.is_valid() {\n            if logical_supported {\n              if logical_shorthand_supported {\n                prop!(BorderBlock => $block_start.to_border());\n              } else {\n                prop!(BorderBlockStart => $block_start.to_border());\n                prop!(BorderBlockEnd => $block_start.to_border());\n              }\n            } else {\n              prop!(BorderTop => $block_start.to_border());\n              prop!(BorderBottom => $block_start.to_border());\n            }\n          } else {\n            if $is_logical && logical_shorthand_supported && !$block_start.is_valid() && !$block_end.is_valid() {\n              logical_shorthand!(BorderBlockStyle, style, $block_start, $block_end);\n              logical_shorthand!(BorderBlockWidth, width, $block_start, $block_end);\n              logical_shorthand!(BorderBlockColor, color, $block_start, $block_end);\n            }\n\n            side!($block_start, $block_start_prop, $block_start_width, $block_start_style, $block_start_color);\n            side!($block_end, $block_end_prop, $block_end_width, $block_end_style, $block_end_color);\n          }\n\n          if $is_logical && $inline_start == $inline_end && $inline_start.is_valid() {\n            if logical_supported {\n              if logical_shorthand_supported {\n                prop!(BorderInline => $inline_start.to_border());\n              } else {\n                prop!(BorderInlineStart => $inline_start.to_border());\n                prop!(BorderInlineEnd => $inline_start.to_border());\n              }\n            } else {\n              prop!(BorderLeft => $inline_start.to_border());\n              prop!(BorderRight => $inline_start.to_border());\n            }\n          } else {\n            if $is_logical && !$inline_start.is_valid() && !$inline_end.is_valid() {\n              if logical_shorthand_supported {\n                logical_shorthand!(BorderInlineStyle, style, $inline_start, $inline_end);\n                logical_shorthand!(BorderInlineWidth, width, $inline_start, $inline_end);\n                logical_shorthand!(BorderInlineColor, color, $inline_start, $inline_end);\n              } else {\n                // If both values of an inline logical property are equal, then we can just convert them to physical properties.\n                macro_rules! inline_prop {\n                  ($key: ident, $left: ident, $right: ident) => {\n                    if $inline_start.$key.is_some() && $inline_start.$key == $inline_end.$key {\n                      prop!($left => std::mem::take(&mut $inline_start.$key).unwrap());\n                      prop!($right => std::mem::take(&mut $inline_end.$key).unwrap());\n                    }\n                  }\n                }\n\n                inline_prop!(style, BorderLeftStyle, BorderRightStyle);\n                inline_prop!(width, BorderLeftWidth, BorderRightWidth);\n                inline_prop!(color, BorderLeftColor, BorderRightColor);\n              }\n            }\n\n            side!($inline_start, $inline_start_prop, $inline_start_width, $inline_start_style, $inline_start_color);\n            side!($inline_end, $inline_end_prop, $inline_end_width, $inline_end_style, $inline_end_color);\n          }\n        }\n      };\n    }\n\n    flush_category!(\n      BorderTop,\n      BorderTopWidth,\n      BorderTopStyle,\n      BorderTopColor,\n      self.border_top,\n      BorderBottom,\n      BorderBottomWidth,\n      BorderBottomStyle,\n      BorderBottomColor,\n      self.border_bottom,\n      BorderLeft,\n      BorderLeftWidth,\n      BorderLeftStyle,\n      BorderLeftColor,\n      self.border_left,\n      BorderRight,\n      BorderRightWidth,\n      BorderRightStyle,\n      BorderRightColor,\n      self.border_right,\n      false\n    );\n\n    flush_category!(\n      BorderBlockStart,\n      BorderBlockStartWidth,\n      BorderBlockStartStyle,\n      BorderBlockStartColor,\n      self.border_block_start,\n      BorderBlockEnd,\n      BorderBlockEndWidth,\n      BorderBlockEndStyle,\n      BorderBlockEndColor,\n      self.border_block_end,\n      BorderInlineStart,\n      BorderInlineStartWidth,\n      BorderInlineStartStyle,\n      BorderInlineStartColor,\n      self.border_inline_start,\n      BorderInlineEnd,\n      BorderInlineEndWidth,\n      BorderInlineEndStyle,\n      BorderInlineEndColor,\n      self.border_inline_end,\n      true\n    );\n\n    self.border_top.reset();\n    self.border_bottom.reset();\n    self.border_left.reset();\n    self.border_right.reset();\n    self.border_block_start.reset();\n    self.border_block_end.reset();\n    self.border_inline_start.reset();\n    self.border_inline_end.reset();\n  }\n\n  fn flush_unparsed(\n    &mut self,\n    unparsed: &UnparsedProperty<'i>,\n    dest: &mut DeclarationList<'i>,\n    context: &mut PropertyHandlerContext<'i, '_>,\n  ) {\n    let logical_supported = !context.should_compile_logical(Feature::LogicalBorders);\n    if logical_supported {\n      let mut unparsed = unparsed.clone();\n      context.add_unparsed_fallbacks(&mut unparsed);\n      self\n        .flushed_properties\n        .insert(BorderProperty::try_from(&unparsed.property_id).unwrap());\n      dest.push(Property::Unparsed(unparsed));\n      return;\n    }\n\n    macro_rules! prop {\n      ($id: ident) => {{\n        let mut unparsed = unparsed.with_property_id(PropertyId::$id);\n        context.add_unparsed_fallbacks(&mut unparsed);\n        dest.push(Property::Unparsed(unparsed));\n        self.flushed_properties.insert(BorderProperty::$id);\n      }};\n    }\n\n    macro_rules! logical_prop {\n      ($ltr: ident, $ltr_key: ident, $rtl: ident, $rtl_key: ident) => {{\n        context.add_logical_rule(\n          Property::Unparsed(unparsed.with_property_id(PropertyId::$ltr)),\n          Property::Unparsed(unparsed.with_property_id(PropertyId::$rtl)),\n        );\n      }};\n    }\n\n    use PropertyId::*;\n    match &unparsed.property_id {\n      BorderInlineStart => logical_prop!(BorderLeft, border_left, BorderRight, border_right),\n      BorderInlineStartWidth => {\n        logical_prop!(BorderLeftWidth, border_left_width, BorderRightWidth, border_right_width)\n      }\n      BorderInlineStartColor => {\n        logical_prop!(BorderLeftColor, border_left_color, BorderRightColor, border_right_color)\n      }\n      BorderInlineStartStyle => {\n        logical_prop!(BorderLeftStyle, border_left_style, BorderRightStyle, border_right_style)\n      }\n      BorderInlineEnd => logical_prop!(BorderRight, border_right, BorderLeft, border_left),\n      BorderInlineEndWidth => {\n        logical_prop!(BorderRightWidth, border_right_width, BorderLeftWidth, border_left_width)\n      }\n      BorderInlineEndColor => {\n        logical_prop!(BorderRightColor, border_right_color, BorderLeftColor, border_left_color)\n      }\n      BorderInlineEndStyle => {\n        logical_prop!(BorderRightStyle, border_right_style, BorderLeftStyle, border_left_style)\n      }\n      BorderBlockStart => prop!(BorderTop),\n      BorderBlockStartWidth => prop!(BorderTopWidth),\n      BorderBlockStartColor => prop!(BorderTopColor),\n      BorderBlockStartStyle => prop!(BorderTopStyle),\n      BorderBlockEnd => prop!(BorderBottom),\n      BorderBlockEndWidth => prop!(BorderBottomWidth),\n      BorderBlockEndColor => prop!(BorderBottomColor),\n      BorderBlockEndStyle => prop!(BorderBottomStyle),\n      property_id => {\n        let mut unparsed = unparsed.clone();\n        context.add_unparsed_fallbacks(&mut unparsed);\n        dest.push(Property::Unparsed(unparsed));\n        self.flushed_properties.insert(BorderProperty::try_from(property_id).unwrap());\n      }\n    }\n  }\n}\n\nfn is_border_property(property_id: &PropertyId) -> bool {\n  match property_id {\n    PropertyId::BorderTopColor\n    | PropertyId::BorderBottomColor\n    | PropertyId::BorderLeftColor\n    | PropertyId::BorderRightColor\n    | PropertyId::BorderBlockStartColor\n    | PropertyId::BorderBlockEndColor\n    | PropertyId::BorderBlockColor\n    | PropertyId::BorderInlineStartColor\n    | PropertyId::BorderInlineEndColor\n    | PropertyId::BorderInlineColor\n    | PropertyId::BorderTopWidth\n    | PropertyId::BorderBottomWidth\n    | PropertyId::BorderLeftWidth\n    | PropertyId::BorderRightWidth\n    | PropertyId::BorderBlockStartWidth\n    | PropertyId::BorderBlockEndWidth\n    | PropertyId::BorderBlockWidth\n    | PropertyId::BorderInlineStartWidth\n    | PropertyId::BorderInlineEndWidth\n    | PropertyId::BorderInlineWidth\n    | PropertyId::BorderTopStyle\n    | PropertyId::BorderBottomStyle\n    | PropertyId::BorderLeftStyle\n    | PropertyId::BorderRightStyle\n    | PropertyId::BorderBlockStartStyle\n    | PropertyId::BorderBlockEndStyle\n    | PropertyId::BorderBlockStyle\n    | PropertyId::BorderInlineStartStyle\n    | PropertyId::BorderInlineEndStyle\n    | PropertyId::BorderInlineStyle\n    | PropertyId::BorderTop\n    | PropertyId::BorderBottom\n    | PropertyId::BorderLeft\n    | PropertyId::BorderRight\n    | PropertyId::BorderBlockStart\n    | PropertyId::BorderBlockEnd\n    | PropertyId::BorderInlineStart\n    | PropertyId::BorderInlineEnd\n    | PropertyId::BorderBlock\n    | PropertyId::BorderInline\n    | PropertyId::BorderWidth\n    | PropertyId::BorderStyle\n    | PropertyId::BorderColor\n    | PropertyId::Border => true,\n    _ => false,\n  }\n}\n"
  },
  {
    "path": "src/properties/border_image.rs",
    "content": "//! CSS properties related to border images.\n\nuse crate::context::PropertyHandlerContext;\nuse crate::declaration::{DeclarationBlock, DeclarationList};\nuse crate::error::{ParserError, PrinterError};\nuse crate::prefixes::Feature;\nuse crate::printer::Printer;\nuse crate::properties::{Property, PropertyId, VendorPrefix};\nuse crate::targets::{Browsers, Targets};\nuse crate::traits::{IsCompatible, Parse, PropertyHandler, Shorthand, ToCss};\nuse crate::values::image::Image;\nuse crate::values::number::CSSNumber;\nuse crate::values::rect::Rect;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse crate::{compat, macros::*};\nuse crate::{\n  traits::FallbackValues,\n  values::{\n    length::*,\n    percentage::{NumberOrPercentage, Percentage},\n  },\n};\nuse cssparser::*;\n\nenum_property! {\n  /// A single [border-image-repeat](https://www.w3.org/TR/css-backgrounds-3/#border-image-repeat) keyword.\n  pub enum BorderImageRepeatKeyword {\n    /// The image is stretched to fill the area.\n    Stretch,\n    /// The image is tiled (repeated) to fill the area.\n    Repeat,\n     /// The image is scaled so that it repeats an even number of times.\n    Round,\n    /// The image is repeated so that it fits, and then spaced apart evenly.\n    Space,\n  }\n}\n\nimpl IsCompatible for BorderImageRepeatKeyword {\n  fn is_compatible(&self, browsers: Browsers) -> bool {\n    use BorderImageRepeatKeyword::*;\n    match self {\n      Round => compat::Feature::BorderImageRepeatRound.is_compatible(browsers),\n      Space => compat::Feature::BorderImageRepeatSpace.is_compatible(browsers),\n      Stretch | Repeat => true,\n    }\n  }\n}\n\n/// A value for the [border-image-repeat](https://www.w3.org/TR/css-backgrounds-3/#border-image-repeat) property.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub struct BorderImageRepeat {\n  /// The horizontal repeat value.\n  pub horizontal: BorderImageRepeatKeyword,\n  /// The vertical repeat value.\n  pub vertical: BorderImageRepeatKeyword,\n}\n\nimpl Default for BorderImageRepeat {\n  fn default() -> BorderImageRepeat {\n    BorderImageRepeat {\n      horizontal: BorderImageRepeatKeyword::Stretch,\n      vertical: BorderImageRepeatKeyword::Stretch,\n    }\n  }\n}\n\nimpl<'i> Parse<'i> for BorderImageRepeat {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let horizontal = BorderImageRepeatKeyword::parse(input)?;\n    let vertical = input.try_parse(BorderImageRepeatKeyword::parse).ok();\n    Ok(BorderImageRepeat {\n      horizontal,\n      vertical: vertical.unwrap_or(horizontal),\n    })\n  }\n}\n\nimpl ToCss for BorderImageRepeat {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    self.horizontal.to_css(dest)?;\n    if self.horizontal != self.vertical {\n      dest.write_str(\" \")?;\n      self.vertical.to_css(dest)?;\n    }\n    Ok(())\n  }\n}\n\nimpl IsCompatible for BorderImageRepeat {\n  fn is_compatible(&self, browsers: Browsers) -> bool {\n    self.horizontal.is_compatible(browsers) && self.vertical.is_compatible(browsers)\n  }\n}\n\n/// A value for the [border-image-width](https://www.w3.org/TR/css-backgrounds-3/#border-image-width) property.\n#[derive(Debug, Clone, PartialEq, Parse, ToCss)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum BorderImageSideWidth {\n  /// A number representing a multiple of the border width.\n  Number(CSSNumber),\n  /// An explicit length or percentage.\n  LengthPercentage(LengthPercentage),\n  /// The `auto` keyword, representing the natural width of the image slice.\n  Auto,\n}\n\nimpl Default for BorderImageSideWidth {\n  fn default() -> BorderImageSideWidth {\n    BorderImageSideWidth::Number(1.0)\n  }\n}\n\nimpl IsCompatible for BorderImageSideWidth {\n  fn is_compatible(&self, browsers: Browsers) -> bool {\n    match self {\n      BorderImageSideWidth::LengthPercentage(l) => l.is_compatible(browsers),\n      _ => true,\n    }\n  }\n}\n\n/// A value for the [border-image-slice](https://www.w3.org/TR/css-backgrounds-3/#border-image-slice) property.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub struct BorderImageSlice {\n  /// The offsets from the edges of the image.\n  pub offsets: Rect<NumberOrPercentage>,\n  /// Whether the middle of the border image should be preserved.\n  pub fill: bool,\n}\n\nimpl Default for BorderImageSlice {\n  fn default() -> BorderImageSlice {\n    BorderImageSlice {\n      offsets: Rect::all(NumberOrPercentage::Percentage(Percentage(1.0))),\n      fill: false,\n    }\n  }\n}\n\nimpl<'i> Parse<'i> for BorderImageSlice {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let mut fill = input.try_parse(|i| i.expect_ident_matching(\"fill\")).is_ok();\n    let offsets = Rect::parse(input)?;\n    if !fill {\n      fill = input.try_parse(|i| i.expect_ident_matching(\"fill\")).is_ok();\n    }\n    Ok(BorderImageSlice { offsets, fill })\n  }\n}\n\nimpl ToCss for BorderImageSlice {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    self.offsets.to_css(dest)?;\n    if self.fill {\n      dest.write_str(\" fill\")?;\n    }\n    Ok(())\n  }\n}\n\nimpl IsCompatible for BorderImageSlice {\n  fn is_compatible(&self, _browsers: Browsers) -> bool {\n    true\n  }\n}\n\ndefine_shorthand! {\n  /// A value for the [border-image](https://www.w3.org/TR/css-backgrounds-3/#border-image) shorthand property.\n  #[derive(Default)]\n  pub struct BorderImage<'i>(VendorPrefix) {\n    /// The border image.\n    #[cfg_attr(feature = \"serde\", serde(borrow))]\n    source: BorderImageSource(Image<'i>),\n    /// The offsets that define where the image is sliced.\n    slice: BorderImageSlice(BorderImageSlice),\n    /// The width of the border image.\n    width: BorderImageWidth(Rect<BorderImageSideWidth>),\n    /// The amount that the image extends beyond the border box.\n    outset: BorderImageOutset(Rect<LengthOrNumber>),\n    /// How the border image is scaled and tiled.\n    repeat: BorderImageRepeat(BorderImageRepeat),\n  }\n}\n\nimpl<'i> Parse<'i> for BorderImage<'i> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    BorderImage::parse_with_callback(input, |_| false)\n  }\n}\n\nimpl<'i> BorderImage<'i> {\n  pub(crate) fn parse_with_callback<'t, F>(\n    input: &mut Parser<'i, 't>,\n    mut callback: F,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>>\n  where\n    F: FnMut(&mut Parser<'i, 't>) -> bool,\n  {\n    let mut source: Option<Image> = None;\n    let mut slice: Option<BorderImageSlice> = None;\n    let mut width: Option<Rect<BorderImageSideWidth>> = None;\n    let mut outset: Option<Rect<LengthOrNumber>> = None;\n    let mut repeat: Option<BorderImageRepeat> = None;\n    loop {\n      if slice.is_none() {\n        if let Ok(value) = input.try_parse(|input| BorderImageSlice::parse(input)) {\n          slice = Some(value);\n          // Parse border image width and outset, if applicable.\n          let maybe_width_outset: Result<_, cssparser::ParseError<'_, ParserError<'i>>> =\n            input.try_parse(|input| {\n              input.expect_delim('/')?;\n\n              // Parse border image width, if applicable.\n              let w = input.try_parse(|input| Rect::parse(input)).ok();\n\n              // Parse border image outset if applicable.\n              let o = input\n                .try_parse(|input| {\n                  input.expect_delim('/')?;\n                  Rect::parse(input)\n                })\n                .ok();\n              if w.is_none() && o.is_none() {\n                Err(input.new_custom_error(ParserError::InvalidDeclaration))\n              } else {\n                Ok((w, o))\n              }\n            });\n          if let Ok((w, o)) = maybe_width_outset {\n            width = w;\n            outset = o;\n          }\n          continue;\n        }\n      }\n\n      if source.is_none() {\n        if let Ok(value) = input.try_parse(|input| Image::parse(input)) {\n          source = Some(value);\n          continue;\n        }\n      }\n\n      if repeat.is_none() {\n        if let Ok(value) = input.try_parse(|input| BorderImageRepeat::parse(input)) {\n          repeat = Some(value);\n          continue;\n        }\n      }\n\n      if callback(input) {\n        continue;\n      }\n\n      break;\n    }\n\n    if source.is_some() || slice.is_some() || width.is_some() || outset.is_some() || repeat.is_some() {\n      Ok(BorderImage {\n        source: source.unwrap_or_default(),\n        slice: slice.unwrap_or_default(),\n        width: width.unwrap_or(Rect::all(BorderImageSideWidth::default())),\n        outset: outset.unwrap_or(Rect::all(LengthOrNumber::default())),\n        repeat: repeat.unwrap_or_default(),\n      })\n    } else {\n      Err(input.new_custom_error(ParserError::InvalidDeclaration))\n    }\n  }\n\n  pub(crate) fn to_css_internal<W>(\n    source: &Image<'i>,\n    slice: &BorderImageSlice,\n    width: &Rect<BorderImageSideWidth>,\n    outset: &Rect<LengthOrNumber>,\n    repeat: &BorderImageRepeat,\n    dest: &mut Printer<W>,\n  ) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    if *source != Image::default() {\n      source.to_css(dest)?;\n    }\n    let has_slice = *slice != BorderImageSlice::default();\n    let has_width = *width != Rect::all(BorderImageSideWidth::default());\n    let has_outset = *outset != Rect::all(LengthOrNumber::Number(0.0));\n    if has_slice || has_width || has_outset {\n      dest.write_str(\" \")?;\n      slice.to_css(dest)?;\n      if has_width || has_outset {\n        dest.delim('/', true)?;\n      }\n      if has_width {\n        width.to_css(dest)?;\n      }\n\n      if has_outset {\n        dest.delim('/', true)?;\n        outset.to_css(dest)?;\n      }\n    }\n\n    if *repeat != BorderImageRepeat::default() {\n      dest.write_str(\" \")?;\n      repeat.to_css(dest)?;\n    }\n\n    Ok(())\n  }\n}\n\nimpl<'i> ToCss for BorderImage<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    BorderImage::to_css_internal(&self.source, &self.slice, &self.width, &self.outset, &self.repeat, dest)\n  }\n}\n\nimpl<'i> FallbackValues for BorderImage<'i> {\n  fn get_fallbacks(&mut self, targets: Targets) -> Vec<Self> {\n    self\n      .source\n      .get_fallbacks(targets)\n      .into_iter()\n      .map(|source| BorderImage { source, ..self.clone() })\n      .collect()\n  }\n}\n\nproperty_bitflags! {\n  #[derive(Default, Debug)]\n  struct BorderImageProperty: u16 {\n    const BorderImageSource = 1 << 0;\n    const BorderImageSlice = 1 << 1;\n    const BorderImageWidth = 1 << 2;\n    const BorderImageOutset = 1 << 3;\n    const BorderImageRepeat = 1 << 4;\n    const BorderImage(_vp) = Self::BorderImageSource.bits() | Self::BorderImageSlice.bits() | Self::BorderImageWidth.bits() | Self::BorderImageOutset.bits() | Self::BorderImageRepeat.bits();\n  }\n}\n\n#[derive(Debug)]\npub(crate) struct BorderImageHandler<'i> {\n  source: Option<Image<'i>>,\n  slice: Option<BorderImageSlice>,\n  width: Option<Rect<BorderImageSideWidth>>,\n  outset: Option<Rect<LengthOrNumber>>,\n  repeat: Option<BorderImageRepeat>,\n  vendor_prefix: VendorPrefix,\n  flushed_properties: BorderImageProperty,\n  has_any: bool,\n}\n\nimpl<'i> Default for BorderImageHandler<'i> {\n  fn default() -> Self {\n    BorderImageHandler {\n      vendor_prefix: VendorPrefix::empty(),\n      source: None,\n      slice: None,\n      width: None,\n      outset: None,\n      repeat: None,\n      flushed_properties: BorderImageProperty::empty(),\n      has_any: false,\n    }\n  }\n}\n\nimpl<'i> PropertyHandler<'i> for BorderImageHandler<'i> {\n  fn handle_property(\n    &mut self,\n    property: &Property<'i>,\n    dest: &mut DeclarationList<'i>,\n    context: &mut PropertyHandlerContext<'i, '_>,\n  ) -> bool {\n    use Property::*;\n    macro_rules! property {\n      ($name: ident, $val: ident) => {{\n        if self.vendor_prefix != VendorPrefix::None {\n          self.flush(dest, context);\n        }\n        flush!($name, $val);\n        self.vendor_prefix = VendorPrefix::None;\n        self.$name = Some($val.clone());\n        self.has_any = true;\n      }};\n    }\n\n    macro_rules! flush {\n      ($name: ident, $val: expr) => {{\n        if self.$name.is_some() && self.$name.as_ref().unwrap() != $val && matches!(context.targets.browsers, Some(targets) if !$val.is_compatible(targets)) {\n          self.flush(dest, context);\n        }\n      }};\n    }\n\n    match property {\n      BorderImageSource(val) => property!(source, val),\n      BorderImageSlice(val) => property!(slice, val),\n      BorderImageWidth(val) => property!(width, val),\n      BorderImageOutset(val) => property!(outset, val),\n      BorderImageRepeat(val) => property!(repeat, val),\n      BorderImage(val, vp) => {\n        flush!(source, &val.source);\n        flush!(slice, &val.slice);\n        flush!(width, &val.width);\n        flush!(outset, &val.outset);\n        flush!(repeat, &val.repeat);\n        self.source = Some(val.source.clone());\n        self.slice = Some(val.slice.clone());\n        self.width = Some(val.width.clone());\n        self.outset = Some(val.outset.clone());\n        self.repeat = Some(val.repeat.clone());\n        self.vendor_prefix |= *vp;\n        self.has_any = true;\n      }\n      Unparsed(val) if is_border_image_property(&val.property_id) => {\n        self.flush(dest, context);\n\n        // Even if we weren't able to parse the value (e.g. due to var() references),\n        // we can still add vendor prefixes to the property itself.\n        let mut unparsed = if matches!(val.property_id, PropertyId::BorderImage(_)) {\n          val.get_prefixed(context.targets, Feature::BorderImage)\n        } else {\n          val.clone()\n        };\n\n        context.add_unparsed_fallbacks(&mut unparsed);\n        self\n          .flushed_properties\n          .insert(BorderImageProperty::try_from(&unparsed.property_id).unwrap());\n        dest.push(Property::Unparsed(unparsed));\n      }\n      _ => return false,\n    }\n\n    true\n  }\n\n  fn finalize(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {\n    self.flush(dest, context);\n    self.flushed_properties = BorderImageProperty::empty();\n  }\n}\n\nimpl<'i> BorderImageHandler<'i> {\n  pub fn reset(&mut self) {\n    self.source = None;\n    self.slice = None;\n    self.width = None;\n    self.outset = None;\n    self.repeat = None;\n  }\n\n  pub fn will_flush(&self, property: &Property<'i>) -> bool {\n    use Property::*;\n    match property {\n      BorderImageSource(_) | BorderImageSlice(_) | BorderImageWidth(_) | BorderImageOutset(_)\n      | BorderImageRepeat(_) => self.vendor_prefix != VendorPrefix::None,\n      Unparsed(val) => is_border_image_property(&val.property_id),\n      _ => false,\n    }\n  }\n\n  fn flush(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {\n    if !self.has_any {\n      return;\n    }\n\n    self.has_any = false;\n\n    macro_rules! push {\n      ($prop: ident, $val: expr) => {\n        dest.push(Property::$prop($val));\n        self.flushed_properties.insert(BorderImageProperty::$prop);\n      };\n    }\n\n    let source = std::mem::take(&mut self.source);\n    let slice = std::mem::take(&mut self.slice);\n    let width = std::mem::take(&mut self.width);\n    let outset = std::mem::take(&mut self.outset);\n    let repeat = std::mem::take(&mut self.repeat);\n\n    if source.is_some() && slice.is_some() && width.is_some() && outset.is_some() && repeat.is_some() {\n      let mut border_image = BorderImage {\n        source: source.unwrap(),\n        slice: slice.unwrap(),\n        width: width.unwrap(),\n        outset: outset.unwrap(),\n        repeat: repeat.unwrap(),\n      };\n\n      let mut prefix = self.vendor_prefix;\n      if prefix.contains(VendorPrefix::None) && !border_image.slice.fill {\n        prefix = context.targets.prefixes(self.vendor_prefix, Feature::BorderImage);\n        if !self.flushed_properties.intersects(BorderImageProperty::BorderImage) {\n          let fallbacks = border_image.get_fallbacks(context.targets);\n          for fallback in fallbacks {\n            // Match prefix of fallback. e.g. -webkit-linear-gradient\n            // can only be used in -webkit-border-image, not -moz-border-image.\n            // However, if border-image is unprefixed, gradients can still be.\n            let mut p = fallback.source.get_vendor_prefix() & prefix;\n            if p.is_empty() {\n              p = prefix;\n            }\n            dest.push(Property::BorderImage(fallback, p));\n          }\n        }\n      }\n\n      let p = border_image.source.get_vendor_prefix() & prefix;\n      if !p.is_empty() {\n        prefix = p;\n      }\n\n      dest.push(Property::BorderImage(border_image, prefix));\n      self.flushed_properties.insert(BorderImageProperty::BorderImage);\n    } else {\n      if let Some(mut source) = source {\n        if !self.flushed_properties.contains(BorderImageProperty::BorderImageSource) {\n          let fallbacks = source.get_fallbacks(context.targets);\n          for fallback in fallbacks {\n            dest.push(Property::BorderImageSource(fallback));\n          }\n        }\n\n        push!(BorderImageSource, source);\n      }\n\n      if let Some(slice) = slice {\n        push!(BorderImageSlice, slice);\n      }\n\n      if let Some(width) = width {\n        push!(BorderImageWidth, width);\n      }\n\n      if let Some(outset) = outset {\n        push!(BorderImageOutset, outset);\n      }\n\n      if let Some(repeat) = repeat {\n        push!(BorderImageRepeat, repeat);\n      }\n    }\n\n    self.vendor_prefix = VendorPrefix::empty();\n  }\n}\n\n#[inline]\nfn is_border_image_property(property_id: &PropertyId) -> bool {\n  match property_id {\n    PropertyId::BorderImageSource\n    | PropertyId::BorderImageSlice\n    | PropertyId::BorderImageWidth\n    | PropertyId::BorderImageOutset\n    | PropertyId::BorderImageRepeat\n    | PropertyId::BorderImage(_) => true,\n    _ => false,\n  }\n}\n"
  },
  {
    "path": "src/properties/border_radius.rs",
    "content": "//! The CSS border radius property.\n\nuse crate::compat;\nuse crate::context::PropertyHandlerContext;\nuse crate::declaration::{DeclarationBlock, DeclarationList};\nuse crate::error::{ParserError, PrinterError};\nuse crate::logical::PropertyCategory;\nuse crate::macros::define_shorthand;\nuse crate::prefixes::Feature;\nuse crate::printer::Printer;\nuse crate::properties::{Property, PropertyId, VendorPrefix};\nuse crate::traits::{IsCompatible, Parse, PropertyHandler, Shorthand, ToCss, Zero};\nuse crate::values::length::*;\nuse crate::values::rect::Rect;\nuse crate::values::size::Size2D;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse cssparser::*;\n\ndefine_shorthand! {\n  /// A value for the [border-radius](https://www.w3.org/TR/css-backgrounds-3/#border-radius) property.\n  pub struct BorderRadius(VendorPrefix) {\n    /// The x and y radius values for the top left corner.\n    top_left: BorderTopLeftRadius(Size2D<LengthPercentage>, VendorPrefix),\n    /// The x and y radius values for the top right corner.\n    top_right: BorderTopRightRadius(Size2D<LengthPercentage>, VendorPrefix),\n    /// The x and y radius values for the bottom right corner.\n    bottom_right: BorderBottomRightRadius(Size2D<LengthPercentage>, VendorPrefix),\n    /// The x and y radius values for the bottom left corner.\n    bottom_left: BorderBottomLeftRadius(Size2D<LengthPercentage>, VendorPrefix),\n  }\n}\n\nimpl Default for BorderRadius {\n  fn default() -> BorderRadius {\n    let zero = Size2D(LengthPercentage::zero(), LengthPercentage::zero());\n    BorderRadius {\n      top_left: zero.clone(),\n      top_right: zero.clone(),\n      bottom_right: zero.clone(),\n      bottom_left: zero,\n    }\n  }\n}\n\nimpl<'i> Parse<'i> for BorderRadius {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let widths: Rect<LengthPercentage> = Rect::parse(input)?;\n    let heights = if input.try_parse(|input| input.expect_delim('/')).is_ok() {\n      Rect::parse(input)?\n    } else {\n      widths.clone()\n    };\n\n    Ok(BorderRadius {\n      top_left: Size2D(widths.0, heights.0),\n      top_right: Size2D(widths.1, heights.1),\n      bottom_right: Size2D(widths.2, heights.2),\n      bottom_left: Size2D(widths.3, heights.3),\n    })\n  }\n}\n\nimpl ToCss for BorderRadius {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    let widths = Rect::new(\n      &self.top_left.0,\n      &self.top_right.0,\n      &self.bottom_right.0,\n      &self.bottom_left.0,\n    );\n    let heights = Rect::new(\n      &self.top_left.1,\n      &self.top_right.1,\n      &self.bottom_right.1,\n      &self.bottom_left.1,\n    );\n\n    widths.to_css(dest)?;\n    if widths != heights {\n      dest.delim('/', true)?;\n      heights.to_css(dest)?;\n    }\n\n    Ok(())\n  }\n}\n\n#[derive(Default, Debug)]\npub(crate) struct BorderRadiusHandler<'i> {\n  top_left: Option<(Size2D<LengthPercentage>, VendorPrefix)>,\n  top_right: Option<(Size2D<LengthPercentage>, VendorPrefix)>,\n  bottom_right: Option<(Size2D<LengthPercentage>, VendorPrefix)>,\n  bottom_left: Option<(Size2D<LengthPercentage>, VendorPrefix)>,\n  start_start: Option<Property<'i>>,\n  start_end: Option<Property<'i>>,\n  end_end: Option<Property<'i>>,\n  end_start: Option<Property<'i>>,\n  category: PropertyCategory,\n  has_any: bool,\n}\n\nimpl<'i> PropertyHandler<'i> for BorderRadiusHandler<'i> {\n  fn handle_property(\n    &mut self,\n    property: &Property<'i>,\n    dest: &mut DeclarationList<'i>,\n    context: &mut PropertyHandlerContext<'i, '_>,\n  ) -> bool {\n    use Property::*;\n\n    macro_rules! maybe_flush {\n      ($prop: ident, $val: expr, $vp: expr) => {{\n        // If two vendor prefixes for the same property have different\n        // values, we need to flush what we have immediately to preserve order.\n        if let Some((val, prefixes)) = &self.$prop {\n          if val != $val && !prefixes.contains(*$vp) {\n            self.flush(dest, context);\n          }\n        }\n\n        if self.$prop.is_some() && matches!(context.targets.browsers, Some(targets) if !$val.is_compatible(targets)) {\n          self.flush(dest, context);\n        }\n      }};\n    }\n\n    macro_rules! property {\n      ($prop: ident, $val: expr, $vp: ident) => {{\n        if self.category != PropertyCategory::Physical {\n          self.flush(dest, context);\n        }\n\n        maybe_flush!($prop, $val, $vp);\n\n        // Otherwise, update the value and add the prefix.\n        if let Some((val, prefixes)) = &mut self.$prop {\n          *val = $val.clone();\n          *prefixes |= *$vp;\n        } else {\n          self.$prop = Some(($val.clone(), *$vp));\n          self.has_any = true;\n        }\n\n        self.category = PropertyCategory::Physical;\n      }};\n    }\n\n    macro_rules! logical_property {\n      ($prop: ident) => {{\n        if self.category != PropertyCategory::Logical {\n          self.flush(dest, context);\n        }\n\n        self.$prop = Some(property.clone());\n        self.category = PropertyCategory::Logical;\n        self.has_any = true;\n      }};\n    }\n\n    match property {\n      BorderTopLeftRadius(val, vp) => property!(top_left, val, vp),\n      BorderTopRightRadius(val, vp) => property!(top_right, val, vp),\n      BorderBottomRightRadius(val, vp) => property!(bottom_right, val, vp),\n      BorderBottomLeftRadius(val, vp) => property!(bottom_left, val, vp),\n      BorderStartStartRadius(_) => logical_property!(start_start),\n      BorderStartEndRadius(_) => logical_property!(start_end),\n      BorderEndEndRadius(_) => logical_property!(end_end),\n      BorderEndStartRadius(_) => logical_property!(end_start),\n      BorderRadius(val, vp) => {\n        self.start_start = None;\n        self.start_end = None;\n        self.end_end = None;\n        self.end_start = None;\n        maybe_flush!(top_left, &val.top_left, vp);\n        maybe_flush!(top_right, &val.top_right, vp);\n        maybe_flush!(bottom_right, &val.bottom_right, vp);\n        maybe_flush!(bottom_left, &val.bottom_left, vp);\n        property!(top_left, &val.top_left, vp);\n        property!(top_right, &val.top_right, vp);\n        property!(bottom_right, &val.bottom_right, vp);\n        property!(bottom_left, &val.bottom_left, vp);\n      }\n      Unparsed(val) if is_border_radius_property(&val.property_id) => {\n        // Even if we weren't able to parse the value (e.g. due to var() references),\n        // we can still add vendor prefixes to the property itself.\n        match &val.property_id {\n          PropertyId::BorderStartStartRadius => logical_property!(start_start),\n          PropertyId::BorderStartEndRadius => logical_property!(start_end),\n          PropertyId::BorderEndEndRadius => logical_property!(end_end),\n          PropertyId::BorderEndStartRadius => logical_property!(end_start),\n          _ => {\n            self.flush(dest, context);\n            dest.push(Property::Unparsed(\n              val.get_prefixed(context.targets, Feature::BorderRadius),\n            ));\n          }\n        }\n      }\n      _ => return false,\n    }\n\n    true\n  }\n\n  fn finalize(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {\n    self.flush(dest, context);\n  }\n}\n\nimpl<'i> BorderRadiusHandler<'i> {\n  fn flush(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {\n    if !self.has_any {\n      return;\n    }\n\n    self.has_any = false;\n\n    let mut top_left = std::mem::take(&mut self.top_left);\n    let mut top_right = std::mem::take(&mut self.top_right);\n    let mut bottom_right = std::mem::take(&mut self.bottom_right);\n    let mut bottom_left = std::mem::take(&mut self.bottom_left);\n    let start_start = std::mem::take(&mut self.start_start);\n    let start_end = std::mem::take(&mut self.start_end);\n    let end_end = std::mem::take(&mut self.end_end);\n    let end_start = std::mem::take(&mut self.end_start);\n\n    if let (\n      Some((top_left, tl_prefix)),\n      Some((top_right, tr_prefix)),\n      Some((bottom_right, br_prefix)),\n      Some((bottom_left, bl_prefix)),\n    ) = (&mut top_left, &mut top_right, &mut bottom_right, &mut bottom_left)\n    {\n      let intersection = *tl_prefix & *tr_prefix & *br_prefix & *bl_prefix;\n      if !intersection.is_empty() {\n        let prefix = context.targets.prefixes(intersection, Feature::BorderRadius);\n        dest.push(Property::BorderRadius(\n          BorderRadius {\n            top_left: top_left.clone(),\n            top_right: top_right.clone(),\n            bottom_right: bottom_right.clone(),\n            bottom_left: bottom_left.clone(),\n          },\n          prefix,\n        ));\n        tl_prefix.remove(intersection);\n        tr_prefix.remove(intersection);\n        br_prefix.remove(intersection);\n        bl_prefix.remove(intersection);\n      }\n    }\n\n    macro_rules! single_property {\n      ($prop: ident, $key: ident) => {\n        if let Some((val, mut vp)) = $key {\n          if !vp.is_empty() {\n            vp = context.targets.prefixes(vp, Feature::$prop);\n            dest.push(Property::$prop(val, vp))\n          }\n        }\n      };\n    }\n\n    let logical_supported = !context.should_compile_logical(compat::Feature::LogicalBorderRadius);\n\n    macro_rules! logical_property {\n      ($prop: ident, $key: ident, $ltr: ident, $rtl: ident) => {\n        if let Some(val) = $key {\n          if logical_supported {\n            dest.push(val);\n          } else {\n            let vp = context.targets.prefixes(VendorPrefix::None, Feature::$ltr);\n            match val {\n              Property::BorderStartStartRadius(val)\n              | Property::BorderStartEndRadius(val)\n              | Property::BorderEndEndRadius(val)\n              | Property::BorderEndStartRadius(val) => {\n                context.add_logical_rule(Property::$ltr(val.clone(), vp), Property::$rtl(val, vp));\n              }\n              Property::Unparsed(val) => {\n                context.add_logical_rule(\n                  Property::Unparsed(val.with_property_id(PropertyId::$ltr(vp))),\n                  Property::Unparsed(val.with_property_id(PropertyId::$rtl(vp))),\n                );\n              }\n              _ => {}\n            }\n          }\n        }\n      };\n    }\n\n    single_property!(BorderTopLeftRadius, top_left);\n    single_property!(BorderTopRightRadius, top_right);\n    single_property!(BorderBottomRightRadius, bottom_right);\n    single_property!(BorderBottomLeftRadius, bottom_left);\n    logical_property!(\n      BorderStartStartRadius,\n      start_start,\n      BorderTopLeftRadius,\n      BorderTopRightRadius\n    );\n    logical_property!(\n      BorderStartEndRadius,\n      start_end,\n      BorderTopRightRadius,\n      BorderTopLeftRadius\n    );\n    logical_property!(\n      BorderEndEndRadius,\n      end_end,\n      BorderBottomRightRadius,\n      BorderBottomLeftRadius\n    );\n    logical_property!(\n      BorderEndStartRadius,\n      end_start,\n      BorderBottomLeftRadius,\n      BorderBottomRightRadius\n    );\n  }\n}\n\n#[inline]\nfn is_border_radius_property(property_id: &PropertyId) -> bool {\n  if is_logical_border_radius_property(property_id) {\n    return true;\n  }\n\n  match property_id {\n    PropertyId::BorderTopLeftRadius(_)\n    | PropertyId::BorderTopRightRadius(_)\n    | PropertyId::BorderBottomRightRadius(_)\n    | PropertyId::BorderBottomLeftRadius(_)\n    | PropertyId::BorderRadius(_) => true,\n    _ => false,\n  }\n}\n\n#[inline]\nfn is_logical_border_radius_property(property_id: &PropertyId) -> bool {\n  match property_id {\n    PropertyId::BorderStartStartRadius\n    | PropertyId::BorderStartEndRadius\n    | PropertyId::BorderEndEndRadius\n    | PropertyId::BorderEndStartRadius => true,\n    _ => false,\n  }\n}\n"
  },
  {
    "path": "src/properties/box_shadow.rs",
    "content": "//! The CSS box-shadow property.\n\nuse super::PropertyId;\nuse crate::context::PropertyHandlerContext;\nuse crate::declaration::DeclarationList;\nuse crate::error::{ParserError, PrinterError};\nuse crate::prefixes::Feature;\nuse crate::printer::Printer;\nuse crate::properties::Property;\nuse crate::targets::Browsers;\nuse crate::traits::{IsCompatible, Parse, PropertyHandler, ToCss, Zero};\nuse crate::values::color::{ColorFallbackKind, CssColor};\nuse crate::values::length::Length;\nuse crate::vendor_prefix::VendorPrefix;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse cssparser::*;\nuse smallvec::SmallVec;\n\n/// A value for the [box-shadow](https://drafts.csswg.org/css-backgrounds/#box-shadow) property.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"camelCase\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub struct BoxShadow {\n  /// The color of the box shadow.\n  pub color: CssColor,\n  /// The x offset of the shadow.\n  pub x_offset: Length,\n  /// The y offset of the shadow.\n  pub y_offset: Length,\n  /// The blur radius of the shadow.\n  pub blur: Length,\n  /// The spread distance of the shadow.\n  pub spread: Length,\n  /// Whether the shadow is inset within the box.\n  pub inset: bool,\n}\n\nimpl<'i> Parse<'i> for BoxShadow {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let mut color = None;\n    let mut lengths = None;\n    let mut inset = false;\n\n    loop {\n      if !inset {\n        if input.try_parse(|input| input.expect_ident_matching(\"inset\")).is_ok() {\n          inset = true;\n          continue;\n        }\n      }\n\n      if lengths.is_none() {\n        let value = input.try_parse::<_, _, ParseError<ParserError<'i>>>(|input| {\n          let horizontal = Length::parse(input)?;\n          let vertical = Length::parse(input)?;\n          let blur = input.try_parse(Length::parse).unwrap_or(Length::zero());\n          let spread = input.try_parse(Length::parse).unwrap_or(Length::zero());\n          Ok((horizontal, vertical, blur, spread))\n        });\n\n        if let Ok(value) = value {\n          lengths = Some(value);\n          continue;\n        }\n      }\n\n      if color.is_none() {\n        if let Ok(value) = input.try_parse(CssColor::parse) {\n          color = Some(value);\n          continue;\n        }\n      }\n\n      break;\n    }\n\n    let lengths = lengths.ok_or(input.new_error(BasicParseErrorKind::QualifiedRuleInvalid))?;\n    Ok(BoxShadow {\n      color: color.unwrap_or(CssColor::current_color()),\n      x_offset: lengths.0,\n      y_offset: lengths.1,\n      blur: lengths.2,\n      spread: lengths.3,\n      inset,\n    })\n  }\n}\n\nimpl ToCss for BoxShadow {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    if self.inset {\n      dest.write_str(\"inset \")?;\n    }\n\n    self.x_offset.to_css(dest)?;\n    dest.write_char(' ')?;\n    self.y_offset.to_css(dest)?;\n\n    if self.blur != Length::zero() || self.spread != Length::zero() {\n      dest.write_char(' ')?;\n      self.blur.to_css(dest)?;\n\n      if self.spread != Length::zero() {\n        dest.write_char(' ')?;\n        self.spread.to_css(dest)?;\n      }\n    }\n\n    if self.color != CssColor::current_color() {\n      dest.write_char(' ')?;\n      self.color.to_css(dest)?;\n    }\n\n    Ok(())\n  }\n}\n\nimpl IsCompatible for BoxShadow {\n  fn is_compatible(&self, browsers: Browsers) -> bool {\n    self.color.is_compatible(browsers)\n      && self.x_offset.is_compatible(browsers)\n      && self.y_offset.is_compatible(browsers)\n      && self.blur.is_compatible(browsers)\n      && self.spread.is_compatible(browsers)\n  }\n}\n\n#[derive(Default)]\npub(crate) struct BoxShadowHandler {\n  box_shadows: Option<(SmallVec<[BoxShadow; 1]>, VendorPrefix)>,\n  flushed: bool,\n}\n\nimpl<'i> PropertyHandler<'i> for BoxShadowHandler {\n  fn handle_property(\n    &mut self,\n    property: &Property<'i>,\n    dest: &mut DeclarationList<'i>,\n    context: &mut PropertyHandlerContext<'i, '_>,\n  ) -> bool {\n    match property {\n      Property::BoxShadow(box_shadows, prefix) => {\n        if self.box_shadows.is_some()\n          && matches!(context.targets.browsers, Some(browsers) if !box_shadows.is_compatible(browsers))\n        {\n          self.flush(dest, context);\n        }\n\n        if let Some((val, prefixes)) = &mut self.box_shadows {\n          if val != box_shadows && !prefixes.contains(*prefix) {\n            self.flush(dest, context);\n            self.box_shadows = Some((box_shadows.clone(), *prefix));\n          } else {\n            *val = box_shadows.clone();\n            *prefixes |= *prefix;\n          }\n        } else {\n          self.box_shadows = Some((box_shadows.clone(), *prefix));\n        }\n      }\n      Property::Unparsed(unparsed) if matches!(unparsed.property_id, PropertyId::BoxShadow(_)) => {\n        self.flush(dest, context);\n\n        let mut unparsed = unparsed.clone();\n        context.add_unparsed_fallbacks(&mut unparsed);\n        dest.push(Property::Unparsed(unparsed));\n        self.flushed = true;\n      }\n      _ => return false,\n    }\n\n    true\n  }\n\n  fn finalize(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {\n    self.flush(dest, context);\n    self.flushed = false;\n  }\n}\n\nimpl BoxShadowHandler {\n  fn flush<'i>(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {\n    if self.box_shadows.is_none() {\n      return;\n    }\n\n    let box_shadows = std::mem::take(&mut self.box_shadows);\n\n    if let Some((box_shadows, prefixes)) = box_shadows {\n      if !self.flushed {\n        let mut prefixes = context.targets.prefixes(prefixes, Feature::BoxShadow);\n        let mut fallbacks = ColorFallbackKind::empty();\n        for shadow in &box_shadows {\n          fallbacks |= shadow.color.get_necessary_fallbacks(context.targets);\n        }\n\n        if fallbacks.contains(ColorFallbackKind::RGB) {\n          let rgb = box_shadows\n            .iter()\n            .map(|shadow| BoxShadow {\n              color: shadow.color.to_rgb().unwrap_or_else(|_| shadow.color.clone()),\n              ..shadow.clone()\n            })\n            .collect();\n          dest.push(Property::BoxShadow(rgb, prefixes));\n          if prefixes.contains(VendorPrefix::None) {\n            prefixes = VendorPrefix::None;\n          } else {\n            // Only output RGB for prefixed property (e.g. -webkit-box-shadow)\n            return;\n          }\n        }\n\n        if fallbacks.contains(ColorFallbackKind::P3) {\n          let p3 = box_shadows\n            .iter()\n            .map(|shadow| BoxShadow {\n              color: shadow.color.to_p3().unwrap_or_else(|_| shadow.color.clone()),\n              ..shadow.clone()\n            })\n            .collect();\n          dest.push(Property::BoxShadow(p3, VendorPrefix::None));\n        }\n\n        if fallbacks.contains(ColorFallbackKind::LAB) {\n          let lab = box_shadows\n            .iter()\n            .map(|shadow| BoxShadow {\n              color: shadow.color.to_lab().unwrap_or_else(|_| shadow.color.clone()),\n              ..shadow.clone()\n            })\n            .collect();\n          dest.push(Property::BoxShadow(lab, VendorPrefix::None));\n        } else {\n          dest.push(Property::BoxShadow(box_shadows, prefixes))\n        }\n      } else {\n        dest.push(Property::BoxShadow(box_shadows, prefixes))\n      }\n    }\n\n    self.flushed = true;\n  }\n}\n"
  },
  {
    "path": "src/properties/contain.rs",
    "content": "//! CSS properties related to containment.\n\n#![allow(non_upper_case_globals)]\n\nuse cssparser::*;\nuse smallvec::SmallVec;\n\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse crate::{\n  context::PropertyHandlerContext,\n  declaration::{DeclarationBlock, DeclarationList},\n  error::{ParserError, PrinterError},\n  macros::{define_shorthand, enum_property, shorthand_handler},\n  printer::Printer,\n  properties::{Property, PropertyId},\n  rules::container::ContainerName as ContainerIdent,\n  targets::Browsers,\n  traits::{IsCompatible, Parse, PropertyHandler, Shorthand, ToCss},\n};\n\nenum_property! {\n  /// A value for the [container-type](https://drafts.csswg.org/css-contain-3/#container-type) property.\n  /// Establishes the element as a query container for the purpose of container queries.\n  pub enum ContainerType {\n    /// The element is not a query container for any container size queries,\n    /// but remains a query container for container style queries.\n    Normal,\n    /// Establishes a query container for container size queries on the container’s own inline axis.\n    InlineSize,\n    /// Establishes a query container for container size queries on both the inline and block axis.\n    Size,\n    /// Establishes a query container for container scroll-state queries\n    ScrollState,\n  }\n}\n\nimpl Default for ContainerType {\n  fn default() -> Self {\n    ContainerType::Normal\n  }\n}\n\nimpl IsCompatible for ContainerType {\n  fn is_compatible(&self, _browsers: Browsers) -> bool {\n    true\n  }\n}\n\n/// A value for the [container-name](https://drafts.csswg.org/css-contain-3/#container-name) property.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub enum ContainerNameList<'i> {\n  /// The `none` keyword.\n  None,\n  /// A list of container names.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  Names(SmallVec<[ContainerIdent<'i>; 1]>),\n}\n\nimpl<'i> Default for ContainerNameList<'i> {\n  fn default() -> Self {\n    ContainerNameList::None\n  }\n}\n\nimpl<'i> Parse<'i> for ContainerNameList<'i> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    if input.try_parse(|input| input.expect_ident_matching(\"none\")).is_ok() {\n      return Ok(ContainerNameList::None);\n    }\n\n    let mut names = SmallVec::new();\n    while let Ok(name) = input.try_parse(ContainerIdent::parse) {\n      names.push(name);\n    }\n\n    if names.is_empty() {\n      return Err(input.new_error_for_next_token());\n    } else {\n      return Ok(ContainerNameList::Names(names));\n    }\n  }\n}\n\nimpl<'i> ToCss for ContainerNameList<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      ContainerNameList::None => dest.write_str(\"none\"),\n      ContainerNameList::Names(names) => {\n        let mut first = true;\n        for name in names {\n          if first {\n            first = false;\n          } else {\n            dest.write_char(' ')?;\n          }\n          name.to_css(dest)?;\n        }\n        Ok(())\n      }\n    }\n  }\n}\n\nimpl IsCompatible for ContainerNameList<'_> {\n  fn is_compatible(&self, _browsers: Browsers) -> bool {\n    true\n  }\n}\n\ndefine_shorthand! {\n  /// A value for the [container](https://drafts.csswg.org/css-contain-3/#container-shorthand) shorthand property.\n  pub struct Container<'i> {\n    /// The container name.\n    #[cfg_attr(feature = \"serde\", serde(borrow))]\n    name: ContainerName(ContainerNameList<'i>),\n    /// The container type.\n    container_type: ContainerType(ContainerType),\n  }\n}\n\nimpl<'i> Parse<'i> for Container<'i> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let name = ContainerNameList::parse(input)?;\n    let container_type = if input.try_parse(|input| input.expect_delim('/')).is_ok() {\n      ContainerType::parse(input)?\n    } else {\n      ContainerType::default()\n    };\n    Ok(Container { name, container_type })\n  }\n}\n\nimpl<'i> ToCss for Container<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    self.name.to_css(dest)?;\n    if self.container_type != ContainerType::default() {\n      dest.delim('/', true)?;\n      self.container_type.to_css(dest)?;\n    }\n    Ok(())\n  }\n}\n\nshorthand_handler!(ContainerHandler -> Container<'i> {\n  name: ContainerName(ContainerNameList<'i>),\n  container_type: ContainerType(ContainerType),\n});\n"
  },
  {
    "path": "src/properties/css_modules.rs",
    "content": "//! Properties related to CSS modules.\n\nuse crate::dependencies::Location;\nuse crate::error::{ParserError, PrinterError};\nuse crate::printer::Printer;\nuse crate::traits::{Parse, ToCss};\nuse crate::values::ident::{CustomIdent, CustomIdentList};\nuse crate::values::string::CowArcStr;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse cssparser::*;\nuse smallvec::SmallVec;\n\n/// A value for the [composes](https://github.com/css-modules/css-modules/#dependencies) property from CSS modules.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct Composes<'i> {\n  /// A list of class names to compose.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  pub names: CustomIdentList<'i>,\n  /// Where the class names are composed from.\n  pub from: Option<Specifier<'i>>,\n  /// The source location of the `composes` property.\n  pub loc: Location,\n}\n\n/// Defines where the class names referenced in the `composes` property are located.\n///\n/// See [Composes](Composes).\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub enum Specifier<'i> {\n  /// The referenced name is global.\n  Global,\n  /// The referenced name comes from the specified file.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  File(CowArcStr<'i>),\n  /// The referenced name comes from a source index (used during bundling).\n  SourceIndex(u32),\n}\n\nimpl<'i> Parse<'i> for Composes<'i> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let loc = input.current_source_location();\n    let mut names = SmallVec::new();\n    while let Ok(name) = input.try_parse(parse_one_ident) {\n      names.push(name);\n    }\n\n    if names.is_empty() {\n      return Err(input.new_custom_error(ParserError::InvalidDeclaration));\n    }\n\n    let from = if input.try_parse(|input| input.expect_ident_matching(\"from\")).is_ok() {\n      Some(Specifier::parse(input)?)\n    } else {\n      None\n    };\n\n    Ok(Composes {\n      names,\n      from,\n      loc: loc.into(),\n    })\n  }\n}\n\nfn parse_one_ident<'i, 't>(\n  input: &mut Parser<'i, 't>,\n) -> Result<CustomIdent<'i>, ParseError<'i, ParserError<'i>>> {\n  let name = CustomIdent::parse(input)?;\n  if name.0.eq_ignore_ascii_case(\"from\") {\n    return Err(input.new_error_for_next_token());\n  }\n\n  Ok(name)\n}\n\nimpl ToCss for Composes<'_> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    let mut first = true;\n    for name in &self.names {\n      if first {\n        first = false;\n      } else {\n        dest.write_char(' ')?;\n      }\n      name.to_css(dest)?;\n    }\n\n    if let Some(from) = &self.from {\n      dest.write_str(\" from \")?;\n      from.to_css(dest)?;\n    }\n\n    Ok(())\n  }\n}\n\nimpl<'i> Parse<'i> for Specifier<'i> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    if let Ok(file) = input.try_parse(|input| input.expect_string_cloned()) {\n      Ok(Specifier::File(file.into()))\n    } else {\n      input.expect_ident_matching(\"global\")?;\n      Ok(Specifier::Global)\n    }\n  }\n}\n\nimpl<'i> ToCss for Specifier<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      Specifier::Global => dest.write_str(\"global\")?,\n      Specifier::File(file) => serialize_string(&file, dest)?,\n      Specifier::SourceIndex(..) => {}\n    }\n    Ok(())\n  }\n}\n"
  },
  {
    "path": "src/properties/custom.rs",
    "content": "//! CSS custom properties and unparsed token values.\n\nuse crate::error::{ParserError, PrinterError, PrinterErrorKind};\nuse crate::macros::enum_property;\nuse crate::prefixes::Feature;\nuse crate::printer::Printer;\nuse crate::properties::PropertyId;\nuse crate::rules::supports::SupportsCondition;\nuse crate::stylesheet::ParserOptions;\nuse crate::targets::{should_compile, Features, Targets};\nuse crate::traits::{Parse, ParseWithOptions, ToCss};\nuse crate::values::angle::Angle;\nuse crate::values::color::{\n  parse_hsl_hwb_components, parse_rgb_components, ColorFallbackKind, ComponentParser, CssColor, LightDarkColor,\n  HSL, RGB, RGBA,\n};\nuse crate::values::ident::{CustomIdent, DashedIdent, DashedIdentReference, Ident};\nuse crate::values::length::{serialize_dimension, LengthValue};\nuse crate::values::number::CSSInteger;\nuse crate::values::percentage::Percentage;\nuse crate::values::resolution::Resolution;\nuse crate::values::string::CowArcStr;\nuse crate::values::time::Time;\nuse crate::values::url::Url;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse cssparser::color::parse_hash_color;\nuse cssparser::*;\n\nuse super::AnimationName;\n#[cfg(feature = \"serde\")]\nuse crate::serialization::ValueWrapper;\n\n/// A CSS custom property, representing any unknown property.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct CustomProperty<'i> {\n  /// The name of the property.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  pub name: CustomPropertyName<'i>,\n  /// The property value, stored as a raw token list.\n  pub value: TokenList<'i>,\n}\n\nimpl<'i> CustomProperty<'i> {\n  /// Parses a custom property with the given name.\n  pub fn parse<'t>(\n    name: CustomPropertyName<'i>,\n    input: &mut Parser<'i, 't>,\n    options: &ParserOptions<'_, 'i>,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let value = input.parse_until_before(Delimiter::Bang | Delimiter::Semicolon, |input| {\n      TokenList::parse(input, options, 0)\n    })?;\n    Ok(CustomProperty { name, value })\n  }\n}\n\n/// A CSS custom property name.\n#[derive(Debug, Clone, PartialEq, Eq, Hash)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize), serde(untagged))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub enum CustomPropertyName<'i> {\n  /// An author-defined CSS custom property.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  Custom(DashedIdent<'i>),\n  /// An unknown CSS property.\n  Unknown(Ident<'i>),\n}\n\nimpl<'i> From<CowArcStr<'i>> for CustomPropertyName<'i> {\n  fn from(name: CowArcStr<'i>) -> Self {\n    if name.starts_with(\"--\") {\n      CustomPropertyName::Custom(DashedIdent(name))\n    } else {\n      CustomPropertyName::Unknown(Ident(name))\n    }\n  }\n}\n\nimpl<'i> From<CowRcStr<'i>> for CustomPropertyName<'i> {\n  fn from(name: CowRcStr<'i>) -> Self {\n    CustomPropertyName::from(CowArcStr::from(name))\n  }\n}\n\nimpl<'i> AsRef<str> for CustomPropertyName<'i> {\n  #[inline]\n  fn as_ref(&self) -> &str {\n    match self {\n      CustomPropertyName::Custom(c) => c.as_ref(),\n      CustomPropertyName::Unknown(u) => u.as_ref(),\n    }\n  }\n}\n\nimpl<'i> ToCss for CustomPropertyName<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      CustomPropertyName::Custom(c) => c.to_css(dest),\n      CustomPropertyName::Unknown(u) => u.to_css(dest),\n    }\n  }\n}\n\n#[cfg(feature = \"serde\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"serde\")))]\nimpl<'i, 'de: 'i> serde::Deserialize<'de> for CustomPropertyName<'i> {\n  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n  where\n    D: serde::Deserializer<'de>,\n  {\n    let name = CowArcStr::deserialize(deserializer)?;\n    Ok(name.into())\n  }\n}\n\n/// A known property with an unparsed value.\n///\n/// This type is used when the value of a known property could not\n/// be parsed, e.g. in the case css `var()` references are encountered.\n/// In this case, the raw tokens are stored instead.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"camelCase\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct UnparsedProperty<'i> {\n  /// The id of the property.\n  pub property_id: PropertyId<'i>,\n  /// The property value, stored as a raw token list.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  pub value: TokenList<'i>,\n}\n\nimpl<'i> UnparsedProperty<'i> {\n  /// Parses a property with the given id as a token list.\n  pub fn parse<'t>(\n    property_id: PropertyId<'i>,\n    input: &mut Parser<'i, 't>,\n    options: &ParserOptions<'_, 'i>,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let value = input.parse_until_before(Delimiter::Bang | Delimiter::Semicolon, |input| {\n      TokenList::parse(input, options, 0)\n    })?;\n    Ok(UnparsedProperty { property_id, value })\n  }\n\n  pub(crate) fn get_prefixed(&self, targets: Targets, feature: Feature) -> UnparsedProperty<'i> {\n    let mut clone = self.clone();\n    let prefix = self.property_id.prefix();\n    clone.property_id = clone.property_id.with_prefix(targets.prefixes(prefix.or_none(), feature));\n    clone\n  }\n\n  /// Returns a new UnparsedProperty with the same value and the given property id.\n  pub fn with_property_id(&self, property_id: PropertyId<'i>) -> UnparsedProperty<'i> {\n    UnparsedProperty {\n      property_id,\n      value: self.value.clone(),\n    }\n  }\n\n  /// Substitutes variables and re-parses the property.\n  #[cfg(feature = \"substitute_variables\")]\n  #[cfg_attr(docsrs, doc(cfg(feature = \"substitute_variables\")))]\n  pub fn substitute_variables<'x>(\n    mut self,\n    vars: &std::collections::HashMap<&str, TokenList<'i>>,\n  ) -> Result<super::Property<'x>, ()> {\n    use super::Property;\n    use crate::stylesheet::PrinterOptions;\n    use static_self::IntoOwned;\n\n    // Substitute variables in the token list.\n    self.value.substitute_variables(vars);\n\n    // Now stringify and re-parse the property to its fully parsed form.\n    // Ideally we'd be able to reuse the tokens rather than printing, but cssparser doesn't provide a way to do that.\n    let mut css = String::new();\n    let mut dest = Printer::new(&mut css, PrinterOptions::default());\n    self.value.to_css(&mut dest, false).unwrap();\n    let property =\n      Property::parse_string(self.property_id.clone(), &css, ParserOptions::default()).map_err(|_| ())?;\n    Ok(property.into_owned())\n  }\n}\n\n/// A raw list of CSS tokens, with embedded parsed values.\n#[derive(Debug, Clone, PartialEq, Eq, Hash)]\n#[cfg_attr(feature = \"visitor\", derive(Visit), visit(visit_token_list, TOKENS))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize), serde(transparent))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct TokenList<'i>(#[cfg_attr(feature = \"serde\", serde(borrow))] pub Vec<TokenOrValue<'i>>);\n\n/// A raw CSS token, or a parsed value.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit), visit(visit_token, TOKENS), visit_types(TOKENS | COLORS | URLS | VARIABLES | ENVIRONMENT_VARIABLES | FUNCTIONS | LENGTHS | ANGLES | TIMES | RESOLUTIONS | DASHED_IDENTS))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub enum TokenOrValue<'i> {\n  /// A token.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  Token(Token<'i>),\n  /// A parsed CSS color.\n  Color(CssColor),\n  /// A color with unresolved components.\n  UnresolvedColor(UnresolvedColor<'i>),\n  /// A parsed CSS url.\n  Url(Url<'i>),\n  /// A CSS variable reference.\n  Var(Variable<'i>),\n  /// A CSS environment variable reference.\n  Env(EnvironmentVariable<'i>),\n  /// A custom CSS function.\n  Function(Function<'i>),\n  /// A length.\n  Length(LengthValue),\n  /// An angle.\n  Angle(Angle),\n  /// A time.\n  Time(Time),\n  /// A resolution.\n  Resolution(Resolution),\n  /// A dashed ident.\n  DashedIdent(DashedIdent<'i>),\n  /// An animation name.\n  AnimationName(AnimationName<'i>),\n}\n\nimpl<'i> From<Token<'i>> for TokenOrValue<'i> {\n  fn from(token: Token<'i>) -> TokenOrValue<'i> {\n    TokenOrValue::Token(token)\n  }\n}\n\nimpl<'i> TokenOrValue<'i> {\n  /// Returns whether the token is whitespace.\n  pub fn is_whitespace(&self) -> bool {\n    matches!(self, TokenOrValue::Token(Token::WhiteSpace(_)))\n  }\n}\n\nimpl<'a> Eq for TokenOrValue<'a> {}\n\nimpl<'a> std::hash::Hash for TokenOrValue<'a> {\n  fn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n    let tag = std::mem::discriminant(self);\n    tag.hash(state);\n    match self {\n      TokenOrValue::Token(t) => t.hash(state),\n      _ => {\n        // This function is primarily used to deduplicate selectors.\n        // Values inside selectors should be exceedingly rare and implementing\n        // Hash for them is somewhat complex due to floating point values.\n        // For now, we just ignore them, which only means there are more\n        // hash collisions. For such a rare case this is probably fine.\n      }\n    }\n  }\n}\n\nimpl<'i> ParseWithOptions<'i> for TokenList<'i> {\n  fn parse_with_options<'t>(\n    input: &mut Parser<'i, 't>,\n    options: &ParserOptions<'_, 'i>,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    TokenList::parse(input, options, 0)\n  }\n}\n\nimpl<'i> TokenList<'i> {\n  pub(crate) fn parse<'t>(\n    input: &mut Parser<'i, 't>,\n    options: &ParserOptions<'_, 'i>,\n    depth: usize,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let mut tokens = vec![];\n    TokenList::parse_into(input, &mut tokens, options, depth)?;\n\n    // Slice off leading and trailing whitespace if there are at least two tokens.\n    // If there is only one token, we must preserve it. e.g. `--foo: ;` is valid.\n    if tokens.len() >= 2 {\n      let mut slice = &tokens[..];\n      if matches!(tokens.first(), Some(token) if token.is_whitespace()) {\n        slice = &slice[1..];\n      }\n      if matches!(tokens.last(), Some(token) if token.is_whitespace()) {\n        slice = &slice[..slice.len() - 1];\n      }\n      return Ok(TokenList(slice.to_vec()));\n    }\n\n    return Ok(TokenList(tokens));\n  }\n\n  pub(crate) fn parse_raw<'t>(\n    input: &mut Parser<'i, 't>,\n    tokens: &mut Vec<TokenOrValue<'i>>,\n    options: &ParserOptions<'_, 'i>,\n    depth: usize,\n  ) -> Result<(), ParseError<'i, ParserError<'i>>> {\n    if depth > 500 {\n      return Err(input.new_custom_error(ParserError::MaximumNestingDepth));\n    }\n\n    loop {\n      let state = input.state();\n      match input.next_including_whitespace_and_comments() {\n        Ok(token @ &cssparser::Token::ParenthesisBlock)\n        | Ok(token @ &cssparser::Token::SquareBracketBlock)\n        | Ok(token @ &cssparser::Token::CurlyBracketBlock) => {\n          tokens.push(Token::from(token).into());\n          let closing_delimiter = match token {\n            cssparser::Token::ParenthesisBlock => Token::CloseParenthesis,\n            cssparser::Token::SquareBracketBlock => Token::CloseSquareBracket,\n            cssparser::Token::CurlyBracketBlock => Token::CloseCurlyBracket,\n            _ => unreachable!(),\n          };\n\n          input.parse_nested_block(|input| TokenList::parse_raw(input, tokens, options, depth + 1))?;\n          tokens.push(closing_delimiter.into());\n        }\n        Ok(token @ &cssparser::Token::Function(_)) => {\n          tokens.push(Token::from(token).into());\n          input.parse_nested_block(|input| TokenList::parse_raw(input, tokens, options, depth + 1))?;\n          tokens.push(Token::CloseParenthesis.into());\n        }\n        Ok(token) if token.is_parse_error() => {\n          return Err(ParseError {\n            kind: ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken(token.clone())),\n            location: state.source_location(),\n          })\n        }\n        Ok(token) => {\n          tokens.push(Token::from(token).into());\n        }\n        Err(_) => break,\n      }\n    }\n\n    Ok(())\n  }\n\n  fn parse_into<'t>(\n    input: &mut Parser<'i, 't>,\n    tokens: &mut Vec<TokenOrValue<'i>>,\n    options: &ParserOptions<'_, 'i>,\n    depth: usize,\n  ) -> Result<(), ParseError<'i, ParserError<'i>>> {\n    if depth > 500 {\n      return Err(input.new_custom_error(ParserError::MaximumNestingDepth));\n    }\n\n    loop {\n      let state = input.state();\n      match input.next_including_whitespace_and_comments() {\n        Ok(&cssparser::Token::Function(ref f)) => {\n          // Attempt to parse embedded color values into hex tokens.\n          let f = f.into();\n          if let Some(color) = try_parse_color_token(&f, &state, input) {\n            tokens.push(TokenOrValue::Color(color));\n          } else if let Ok(color) = input.try_parse(|input| UnresolvedColor::parse(&f, input, options)) {\n            tokens.push(TokenOrValue::UnresolvedColor(color));\n          } else if f == \"url\" {\n            input.reset(&state);\n            tokens.push(TokenOrValue::Url(Url::parse(input)?));\n          } else if f == \"var\" {\n            let var = input.parse_nested_block(|input| {\n              let var = Variable::parse(input, options, depth + 1)?;\n              Ok(TokenOrValue::Var(var))\n            })?;\n            tokens.push(var);\n          } else if f == \"env\" {\n            let env = input.parse_nested_block(|input| {\n              let env = EnvironmentVariable::parse_nested(input, options, depth + 1)?;\n              Ok(TokenOrValue::Env(env))\n            })?;\n            tokens.push(env);\n          } else {\n            let arguments = input.parse_nested_block(|input| TokenList::parse(input, options, depth + 1))?;\n            tokens.push(TokenOrValue::Function(Function {\n              name: Ident(f),\n              arguments,\n            }));\n          }\n        }\n        Ok(&cssparser::Token::Hash(ref h)) | Ok(&cssparser::Token::IDHash(ref h)) => {\n          if let Ok((r, g, b, a)) = parse_hash_color(h.as_bytes()) {\n            tokens.push(TokenOrValue::Color(CssColor::RGBA(RGBA::new(r, g, b, a))));\n          } else {\n            tokens.push(Token::Hash(h.into()).into());\n          }\n        }\n        Ok(&cssparser::Token::UnquotedUrl(_)) => {\n          input.reset(&state);\n          tokens.push(TokenOrValue::Url(Url::parse(input)?));\n        }\n        Ok(&cssparser::Token::Ident(ref name)) if name.starts_with(\"--\") => {\n          tokens.push(TokenOrValue::DashedIdent(name.into()));\n        }\n        Ok(token @ &cssparser::Token::ParenthesisBlock)\n        | Ok(token @ &cssparser::Token::SquareBracketBlock)\n        | Ok(token @ &cssparser::Token::CurlyBracketBlock) => {\n          tokens.push(Token::from(token).into());\n          let closing_delimiter = match token {\n            cssparser::Token::ParenthesisBlock => Token::CloseParenthesis,\n            cssparser::Token::SquareBracketBlock => Token::CloseSquareBracket,\n            cssparser::Token::CurlyBracketBlock => Token::CloseCurlyBracket,\n            _ => unreachable!(),\n          };\n\n          input.parse_nested_block(|input| TokenList::parse_into(input, tokens, options, depth + 1))?;\n\n          tokens.push(closing_delimiter.into());\n        }\n        Ok(token @ cssparser::Token::Dimension { .. }) => {\n          let value = if let Ok(length) = LengthValue::try_from(token) {\n            TokenOrValue::Length(length)\n          } else if let Ok(angle) = Angle::try_from(token) {\n            TokenOrValue::Angle(angle)\n          } else if let Ok(time) = Time::try_from(token) {\n            TokenOrValue::Time(time)\n          } else if let Ok(resolution) = Resolution::try_from(token) {\n            TokenOrValue::Resolution(resolution)\n          } else {\n            TokenOrValue::Token(token.into())\n          };\n          tokens.push(value);\n        }\n        Ok(token) if token.is_parse_error() => {\n          return Err(ParseError {\n            kind: ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken(token.clone())),\n            location: state.source_location(),\n          })\n        }\n        Ok(token) => {\n          tokens.push(Token::from(token).into());\n        }\n        Err(_) => break,\n      }\n    }\n\n    Ok(())\n  }\n}\n\n#[inline]\nfn try_parse_color_token<'i, 't>(\n  f: &CowArcStr<'i>,\n  state: &ParserState,\n  input: &mut Parser<'i, 't>,\n) -> Option<CssColor> {\n  match_ignore_ascii_case! { &*f,\n    \"rgb\" | \"rgba\" | \"hsl\" | \"hsla\" | \"hwb\" | \"lab\" | \"lch\" | \"oklab\" | \"oklch\" | \"color\" | \"color-mix\" | \"light-dark\" => {\n      let s = input.state();\n      input.reset(&state);\n      if let Ok(color) = CssColor::parse(input) {\n        return Some(color)\n      }\n      input.reset(&s);\n    },\n    _ => {}\n  }\n\n  None\n}\n\nimpl<'i> TokenList<'i> {\n  pub(crate) fn to_css<W>(&self, dest: &mut Printer<W>, is_custom_property: bool) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    for token_or_value in self.0.iter() {\n      match token_or_value {\n        TokenOrValue::Color(color) => {\n          color.to_css(dest)?;\n        }\n        TokenOrValue::UnresolvedColor(color) => {\n          color.to_css(dest, is_custom_property)?;\n        }\n        TokenOrValue::Url(url) => {\n          if dest.dependencies.is_some() && is_custom_property && !url.is_absolute() {\n            return Err(dest.error(\n              PrinterErrorKind::AmbiguousUrlInCustomProperty {\n                url: url.url.as_ref().to_owned(),\n              },\n              url.loc,\n            ));\n          }\n          url.to_css(dest)?;\n        }\n        TokenOrValue::Var(var) => {\n          var.to_css(dest, is_custom_property)?;\n        }\n        TokenOrValue::Env(env) => {\n          env.to_css(dest, is_custom_property)?;\n        }\n        TokenOrValue::Function(f) => {\n          f.to_css(dest, is_custom_property)?;\n        }\n        TokenOrValue::Length(v) => {\n          // Do not serialize unitless zero lengths in custom properties as it may break calc().\n          let (value, unit) = v.to_unit_value();\n          serialize_dimension(value, unit, dest)?;\n        }\n        TokenOrValue::Angle(v) => {\n          v.to_css(dest)?;\n        }\n        TokenOrValue::Time(v) => {\n          v.to_css(dest)?;\n        }\n        TokenOrValue::Resolution(v) => {\n          v.to_css(dest)?;\n        }\n        TokenOrValue::DashedIdent(v) => {\n          v.to_css(dest)?;\n        }\n        TokenOrValue::AnimationName(v) => {\n          v.to_css(dest)?;\n        }\n        TokenOrValue::Token(token) => match token {\n          Token::Dimension { value, unit, .. } => {\n            serialize_dimension(*value, unit, dest)?;\n          }\n          Token::Number { value, .. } => {\n            value.to_css(dest)?;\n          }\n          _ => {\n            token.to_css(dest)?;\n          }\n        },\n      };\n    }\n\n    Ok(())\n  }\n\n  pub(crate) fn to_css_raw<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    for token_or_value in &self.0 {\n      match token_or_value {\n        TokenOrValue::Token(token) => {\n          token.to_css(dest)?;\n        }\n        _ => {\n          return Err(PrinterError {\n            kind: PrinterErrorKind::FmtError,\n            loc: None,\n          })\n        }\n      }\n    }\n\n    Ok(())\n  }\n\n  pub(crate) fn starts_with_whitespace(&self) -> bool {\n    matches!(self.0.get(0), Some(TokenOrValue::Token(Token::WhiteSpace(_))))\n  }\n}\n\n/// A raw CSS token.\n// Copied from cssparser to change CowRcStr to CowArcStr\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub enum Token<'a> {\n  /// A [`<ident-token>`](https://drafts.csswg.org/css-syntax/#ident-token-diagram)\n  #[cfg_attr(feature = \"serde\", serde(with = \"ValueWrapper::<CowArcStr>\"))]\n  Ident(#[cfg_attr(feature = \"serde\", serde(borrow))] CowArcStr<'a>),\n\n  /// A [`<at-keyword-token>`](https://drafts.csswg.org/css-syntax/#at-keyword-token-diagram)\n  ///\n  /// The value does not include the `@` marker.\n  #[cfg_attr(feature = \"serde\", serde(with = \"ValueWrapper::<CowArcStr>\"))]\n  AtKeyword(CowArcStr<'a>),\n\n  /// A [`<hash-token>`](https://drafts.csswg.org/css-syntax/#hash-token-diagram) with the type flag set to \"unrestricted\"\n  ///\n  /// The value does not include the `#` marker.\n  #[cfg_attr(feature = \"serde\", serde(with = \"ValueWrapper::<CowArcStr>\"))]\n  Hash(CowArcStr<'a>),\n\n  /// A [`<hash-token>`](https://drafts.csswg.org/css-syntax/#hash-token-diagram) with the type flag set to \"id\"\n  ///\n  /// The value does not include the `#` marker.\n  #[cfg_attr(feature = \"serde\", serde(rename = \"id-hash\", with = \"ValueWrapper::<CowArcStr>\"))]\n  IDHash(CowArcStr<'a>), // Hash that is a valid ID selector.\n\n  /// A [`<string-token>`](https://drafts.csswg.org/css-syntax/#string-token-diagram)\n  ///\n  /// The value does not include the quotes.\n  #[cfg_attr(feature = \"serde\", serde(with = \"ValueWrapper::<CowArcStr>\"))]\n  String(CowArcStr<'a>),\n\n  /// A [`<url-token>`](https://drafts.csswg.org/css-syntax/#url-token-diagram)\n  ///\n  /// The value does not include the `url(` `)` markers.  Note that `url( <string-token> )` is represented by a\n  /// `Function` token.\n  #[cfg_attr(feature = \"serde\", serde(with = \"ValueWrapper::<CowArcStr>\"))]\n  UnquotedUrl(CowArcStr<'a>),\n\n  /// A `<delim-token>`\n  #[cfg_attr(feature = \"serde\", serde(with = \"ValueWrapper::<char>\"))]\n  Delim(char),\n\n  /// A [`<number-token>`](https://drafts.csswg.org/css-syntax/#number-token-diagram)\n  Number {\n    /// Whether the number had a `+` or `-` sign.\n    ///\n    /// This is used is some cases like the <An+B> micro syntax. (See the `parse_nth` function.)\n    #[cfg_attr(feature = \"serde\", serde(skip))]\n    has_sign: bool,\n\n    /// The value as a float\n    value: f32,\n\n    /// If the origin source did not include a fractional part, the value as an integer.\n    #[cfg_attr(feature = \"serde\", serde(skip))]\n    int_value: Option<i32>,\n  },\n\n  /// A [`<percentage-token>`](https://drafts.csswg.org/css-syntax/#percentage-token-diagram)\n  Percentage {\n    /// Whether the number had a `+` or `-` sign.\n    #[cfg_attr(feature = \"serde\", serde(skip))]\n    has_sign: bool,\n\n    /// The value as a float, divided by 100 so that the nominal range is 0.0 to 1.0.\n    #[cfg_attr(feature = \"serde\", serde(rename = \"value\"))]\n    unit_value: f32,\n\n    /// If the origin source did not include a fractional part, the value as an integer.\n    /// It is **not** divided by 100.\n    #[cfg_attr(feature = \"serde\", serde(skip))]\n    int_value: Option<i32>,\n  },\n\n  /// A [`<dimension-token>`](https://drafts.csswg.org/css-syntax/#dimension-token-diagram)\n  Dimension {\n    /// Whether the number had a `+` or `-` sign.\n    ///\n    /// This is used is some cases like the <An+B> micro syntax. (See the `parse_nth` function.)\n    #[cfg_attr(feature = \"serde\", serde(skip))]\n    has_sign: bool,\n\n    /// The value as a float\n    value: f32,\n\n    /// If the origin source did not include a fractional part, the value as an integer.\n    #[cfg_attr(feature = \"serde\", serde(skip))]\n    int_value: Option<i32>,\n\n    /// The unit, e.g. \"px\" in `12px`\n    unit: CowArcStr<'a>,\n  },\n\n  /// A [`<whitespace-token>`](https://drafts.csswg.org/css-syntax/#whitespace-token-diagram)\n  #[cfg_attr(feature = \"serde\", serde(with = \"ValueWrapper::<CowArcStr>\"))]\n  WhiteSpace(CowArcStr<'a>),\n\n  /// A comment.\n  ///\n  /// The CSS Syntax spec does not generate tokens for comments,\n  /// But we do, because we can (borrowed &str makes it cheap).\n  ///\n  /// The value does not include the `/*` `*/` markers.\n  #[cfg_attr(feature = \"serde\", serde(with = \"ValueWrapper::<CowArcStr>\"))]\n  Comment(CowArcStr<'a>),\n\n  /// A `:` `<colon-token>`\n  Colon, // :\n\n  /// A `;` `<semicolon-token>`\n  Semicolon, // ;\n\n  /// A `,` `<comma-token>`\n  Comma, // ,\n\n  /// A `~=` [`<include-match-token>`](https://drafts.csswg.org/css-syntax/#include-match-token-diagram)\n  IncludeMatch,\n\n  /// A `|=` [`<dash-match-token>`](https://drafts.csswg.org/css-syntax/#dash-match-token-diagram)\n  DashMatch,\n\n  /// A `^=` [`<prefix-match-token>`](https://drafts.csswg.org/css-syntax/#prefix-match-token-diagram)\n  PrefixMatch,\n\n  /// A `$=` [`<suffix-match-token>`](https://drafts.csswg.org/css-syntax/#suffix-match-token-diagram)\n  SuffixMatch,\n\n  /// A `*=` [`<substring-match-token>`](https://drafts.csswg.org/css-syntax/#substring-match-token-diagram)\n  SubstringMatch,\n\n  /// A `<!--` [`<CDO-token>`](https://drafts.csswg.org/css-syntax/#CDO-token-diagram)\n  #[cfg_attr(feature = \"serde\", serde(rename = \"cdo\"))]\n  CDO,\n\n  /// A `-->` [`<CDC-token>`](https://drafts.csswg.org/css-syntax/#CDC-token-diagram)\n  #[cfg_attr(feature = \"serde\", serde(rename = \"cdc\"))]\n  CDC,\n\n  /// A [`<function-token>`](https://drafts.csswg.org/css-syntax/#function-token-diagram)\n  ///\n  /// The value (name) does not include the `(` marker.\n  #[cfg_attr(feature = \"serde\", serde(with = \"ValueWrapper::<CowArcStr>\"))]\n  Function(CowArcStr<'a>),\n\n  /// A `<(-token>`\n  ParenthesisBlock,\n\n  /// A `<[-token>`\n  SquareBracketBlock,\n\n  /// A `<{-token>`\n  CurlyBracketBlock,\n\n  /// A `<bad-url-token>`\n  ///\n  /// This token always indicates a parse error.\n  #[cfg_attr(feature = \"serde\", serde(with = \"ValueWrapper::<CowArcStr>\"))]\n  BadUrl(CowArcStr<'a>),\n\n  /// A `<bad-string-token>`\n  ///\n  /// This token always indicates a parse error.\n  #[cfg_attr(feature = \"serde\", serde(with = \"ValueWrapper::<CowArcStr>\"))]\n  BadString(CowArcStr<'a>),\n\n  /// A `<)-token>`\n  ///\n  /// When obtained from one of the `Parser::next*` methods,\n  /// this token is always unmatched and indicates a parse error.\n  CloseParenthesis,\n\n  /// A `<]-token>`\n  ///\n  /// When obtained from one of the `Parser::next*` methods,\n  /// this token is always unmatched and indicates a parse error.\n  CloseSquareBracket,\n\n  /// A `<}-token>`\n  ///\n  /// When obtained from one of the `Parser::next*` methods,\n  /// this token is always unmatched and indicates a parse error.\n  CloseCurlyBracket,\n}\n\nimpl<'a> From<&cssparser::Token<'a>> for Token<'a> {\n  #[inline]\n  fn from(t: &cssparser::Token<'a>) -> Token<'a> {\n    match t {\n      cssparser::Token::Ident(x) => Token::Ident(x.into()),\n      cssparser::Token::AtKeyword(x) => Token::AtKeyword(x.into()),\n      cssparser::Token::Hash(x) => Token::Hash(x.into()),\n      cssparser::Token::IDHash(x) => Token::IDHash(x.into()),\n      cssparser::Token::QuotedString(x) => Token::String(x.into()),\n      cssparser::Token::UnquotedUrl(x) => Token::UnquotedUrl(x.into()),\n      cssparser::Token::Function(x) => Token::Function(x.into()),\n      cssparser::Token::BadUrl(x) => Token::BadUrl(x.into()),\n      cssparser::Token::BadString(x) => Token::BadString(x.into()),\n      cssparser::Token::Delim(c) => Token::Delim(*c),\n      cssparser::Token::Number {\n        has_sign,\n        value,\n        int_value,\n      } => Token::Number {\n        has_sign: *has_sign,\n        value: *value,\n        int_value: *int_value,\n      },\n      cssparser::Token::Dimension {\n        has_sign,\n        value,\n        int_value,\n        unit,\n      } => Token::Dimension {\n        has_sign: *has_sign,\n        value: *value,\n        int_value: *int_value,\n        unit: unit.into(),\n      },\n      cssparser::Token::Percentage {\n        has_sign,\n        unit_value,\n        int_value,\n      } => Token::Percentage {\n        has_sign: *has_sign,\n        unit_value: *unit_value,\n        int_value: *int_value,\n      },\n      cssparser::Token::WhiteSpace(w) => Token::WhiteSpace((*w).into()),\n      cssparser::Token::Comment(c) => Token::Comment((*c).into()),\n      cssparser::Token::Colon => Token::Colon,\n      cssparser::Token::Semicolon => Token::Semicolon,\n      cssparser::Token::Comma => Token::Comma,\n      cssparser::Token::IncludeMatch => Token::IncludeMatch,\n      cssparser::Token::DashMatch => Token::DashMatch,\n      cssparser::Token::PrefixMatch => Token::PrefixMatch,\n      cssparser::Token::SuffixMatch => Token::SuffixMatch,\n      cssparser::Token::SubstringMatch => Token::SubstringMatch,\n      cssparser::Token::CDO => Token::CDO,\n      cssparser::Token::CDC => Token::CDC,\n      cssparser::Token::ParenthesisBlock => Token::ParenthesisBlock,\n      cssparser::Token::SquareBracketBlock => Token::SquareBracketBlock,\n      cssparser::Token::CurlyBracketBlock => Token::CurlyBracketBlock,\n      cssparser::Token::CloseParenthesis => Token::CloseParenthesis,\n      cssparser::Token::CloseSquareBracket => Token::CloseSquareBracket,\n      cssparser::Token::CloseCurlyBracket => Token::CloseCurlyBracket,\n    }\n  }\n}\n\nimpl<'a> ToCss for Token<'a> {\n  #[inline]\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    use cssparser::ToCss;\n    match self {\n      Token::Ident(x) => cssparser::Token::Ident(x.as_ref().into()).to_css(dest)?,\n      Token::AtKeyword(x) => cssparser::Token::AtKeyword(x.as_ref().into()).to_css(dest)?,\n      Token::Hash(x) => cssparser::Token::Hash(x.as_ref().into()).to_css(dest)?,\n      Token::IDHash(x) => cssparser::Token::IDHash(x.as_ref().into()).to_css(dest)?,\n      Token::String(x) => cssparser::Token::QuotedString(x.as_ref().into()).to_css(dest)?,\n      Token::UnquotedUrl(x) => cssparser::Token::UnquotedUrl(x.as_ref().into()).to_css(dest)?,\n      Token::Function(x) => cssparser::Token::Function(x.as_ref().into()).to_css(dest)?,\n      Token::BadUrl(x) => cssparser::Token::BadUrl(x.as_ref().into()).to_css(dest)?,\n      Token::BadString(x) => cssparser::Token::BadString(x.as_ref().into()).to_css(dest)?,\n      Token::Delim(c) => cssparser::Token::Delim(*c).to_css(dest)?,\n      Token::Number {\n        has_sign,\n        value,\n        int_value,\n      } => cssparser::Token::Number {\n        has_sign: *has_sign,\n        value: *value,\n        int_value: *int_value,\n      }\n      .to_css(dest)?,\n      Token::Dimension {\n        has_sign,\n        value,\n        int_value,\n        unit,\n      } => cssparser::Token::Dimension {\n        has_sign: *has_sign,\n        value: *value,\n        int_value: *int_value,\n        unit: unit.as_ref().into(),\n      }\n      .to_css(dest)?,\n      Token::Percentage {\n        has_sign,\n        unit_value,\n        int_value,\n      } => cssparser::Token::Percentage {\n        has_sign: *has_sign,\n        unit_value: *unit_value,\n        int_value: *int_value,\n      }\n      .to_css(dest)?,\n      Token::WhiteSpace(w) => {\n        if dest.minify {\n          dest.write_char(' ')?;\n        } else {\n          dest.write_str(&w)?;\n        }\n      }\n      Token::Comment(c) => {\n        if !dest.minify {\n          cssparser::Token::Comment(c).to_css(dest)?;\n        }\n      }\n      Token::Colon => cssparser::Token::Colon.to_css(dest)?,\n      Token::Semicolon => cssparser::Token::Semicolon.to_css(dest)?,\n      Token::Comma => cssparser::Token::Comma.to_css(dest)?,\n      Token::IncludeMatch => cssparser::Token::IncludeMatch.to_css(dest)?,\n      Token::DashMatch => cssparser::Token::DashMatch.to_css(dest)?,\n      Token::PrefixMatch => cssparser::Token::PrefixMatch.to_css(dest)?,\n      Token::SuffixMatch => cssparser::Token::SuffixMatch.to_css(dest)?,\n      Token::SubstringMatch => cssparser::Token::SubstringMatch.to_css(dest)?,\n      Token::CDO => cssparser::Token::CDO.to_css(dest)?,\n      Token::CDC => cssparser::Token::CDC.to_css(dest)?,\n      Token::ParenthesisBlock => cssparser::Token::ParenthesisBlock.to_css(dest)?,\n      Token::SquareBracketBlock => cssparser::Token::SquareBracketBlock.to_css(dest)?,\n      Token::CurlyBracketBlock => cssparser::Token::CurlyBracketBlock.to_css(dest)?,\n      Token::CloseParenthesis => cssparser::Token::CloseParenthesis.to_css(dest)?,\n      Token::CloseSquareBracket => cssparser::Token::CloseSquareBracket.to_css(dest)?,\n      Token::CloseCurlyBracket => cssparser::Token::CloseCurlyBracket.to_css(dest)?,\n    }\n\n    Ok(())\n  }\n}\n\nimpl<'a> Eq for Token<'a> {}\n\nimpl<'a> std::hash::Hash for Token<'a> {\n  fn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n    let tag = std::mem::discriminant(self);\n    tag.hash(state);\n    match self {\n      Token::Ident(x) => x.hash(state),\n      Token::AtKeyword(x) => x.hash(state),\n      Token::Hash(x) => x.hash(state),\n      Token::IDHash(x) => x.hash(state),\n      Token::String(x) => x.hash(state),\n      Token::UnquotedUrl(x) => x.hash(state),\n      Token::Function(x) => x.hash(state),\n      Token::BadUrl(x) => x.hash(state),\n      Token::BadString(x) => x.hash(state),\n      Token::Delim(x) => x.hash(state),\n      Token::Number {\n        has_sign,\n        value,\n        int_value,\n      } => {\n        has_sign.hash(state);\n        integer_decode(*value).hash(state);\n        int_value.hash(state);\n      }\n      Token::Dimension {\n        has_sign,\n        value,\n        int_value,\n        unit,\n      } => {\n        has_sign.hash(state);\n        integer_decode(*value).hash(state);\n        int_value.hash(state);\n        unit.hash(state);\n      }\n      Token::Percentage {\n        has_sign,\n        unit_value,\n        int_value,\n      } => {\n        has_sign.hash(state);\n        integer_decode(*unit_value).hash(state);\n        int_value.hash(state);\n      }\n      Token::WhiteSpace(w) => w.hash(state),\n      Token::Comment(c) => c.hash(state),\n      Token::Colon\n      | Token::Semicolon\n      | Token::Comma\n      | Token::IncludeMatch\n      | Token::DashMatch\n      | Token::PrefixMatch\n      | Token::SuffixMatch\n      | Token::SubstringMatch\n      | Token::CDO\n      | Token::CDC\n      | Token::ParenthesisBlock\n      | Token::SquareBracketBlock\n      | Token::CurlyBracketBlock\n      | Token::CloseParenthesis\n      | Token::CloseSquareBracket\n      | Token::CloseCurlyBracket => {}\n    }\n  }\n}\n\n/// Converts a floating point value into its mantissa, exponent,\n/// and sign components so that it can be hashed.\nfn integer_decode(v: f32) -> (u32, i16, i8) {\n  let bits: u32 = f32::to_bits(v);\n  let sign: i8 = if bits >> 31 == 0 { 1 } else { -1 };\n  let mut exponent: i16 = ((bits >> 23) & 0xff) as i16;\n  let mantissa = if exponent == 0 {\n    (bits & 0x7fffff) << 1\n  } else {\n    (bits & 0x7fffff) | 0x800000\n  };\n  // Exponent bias + mantissa shift\n  exponent -= 127 + 23;\n  (mantissa, exponent, sign)\n}\n\nimpl<'i> TokenList<'i> {\n  pub(crate) fn get_necessary_fallbacks(&self, targets: Targets) -> ColorFallbackKind {\n    let mut fallbacks = ColorFallbackKind::empty();\n    for token in &self.0 {\n      match token {\n        TokenOrValue::Color(color) => {\n          fallbacks |= color.get_possible_fallbacks(targets);\n        }\n        TokenOrValue::Function(f) => {\n          fallbacks |= f.arguments.get_necessary_fallbacks(targets);\n        }\n        TokenOrValue::Var(v) => {\n          if let Some(fallback) = &v.fallback {\n            fallbacks |= fallback.get_necessary_fallbacks(targets);\n          }\n        }\n        TokenOrValue::Env(v) => {\n          if let Some(fallback) = &v.fallback {\n            fallbacks |= fallback.get_necessary_fallbacks(targets);\n          }\n        }\n        _ => {}\n      }\n    }\n\n    fallbacks\n  }\n\n  pub(crate) fn get_fallback(&self, kind: ColorFallbackKind) -> Self {\n    let tokens = self\n      .0\n      .iter()\n      .map(|token| match token {\n        TokenOrValue::Color(color) => TokenOrValue::Color(color.get_fallback(kind)),\n        TokenOrValue::Function(f) => TokenOrValue::Function(f.get_fallback(kind)),\n        TokenOrValue::Var(v) => TokenOrValue::Var(v.get_fallback(kind)),\n        TokenOrValue::Env(e) => TokenOrValue::Env(e.get_fallback(kind)),\n        _ => token.clone(),\n      })\n      .collect();\n    TokenList(tokens)\n  }\n\n  pub(crate) fn get_fallbacks(&mut self, targets: Targets) -> Vec<(SupportsCondition<'i>, Self)> {\n    // Get the full list of possible fallbacks, and remove the lowest one, which will replace\n    // the original declaration. The remaining fallbacks need to be added as @supports rules.\n    let mut fallbacks = self.get_necessary_fallbacks(targets);\n    let lowest_fallback = fallbacks.lowest();\n    fallbacks.remove(lowest_fallback);\n\n    let mut res = Vec::new();\n    if fallbacks.contains(ColorFallbackKind::P3) {\n      res.push((\n        ColorFallbackKind::P3.supports_condition(),\n        self.get_fallback(ColorFallbackKind::P3),\n      ));\n    }\n\n    if fallbacks.contains(ColorFallbackKind::LAB) {\n      res.push((\n        ColorFallbackKind::LAB.supports_condition(),\n        self.get_fallback(ColorFallbackKind::LAB),\n      ));\n    }\n\n    if !lowest_fallback.is_empty() {\n      for token in self.0.iter_mut() {\n        match token {\n          TokenOrValue::Color(color) => {\n            *color = color.get_fallback(lowest_fallback);\n          }\n          TokenOrValue::Function(f) => *f = f.get_fallback(lowest_fallback),\n          TokenOrValue::Var(v) if v.fallback.is_some() => *v = v.get_fallback(lowest_fallback),\n          TokenOrValue::Env(v) if v.fallback.is_some() => *v = v.get_fallback(lowest_fallback),\n          _ => {}\n        }\n      }\n    }\n\n    res\n  }\n\n  pub(crate) fn get_features(&self) -> Features {\n    let mut features = Features::empty();\n    for token in &self.0 {\n      match token {\n        TokenOrValue::Color(color) => {\n          features |= color.get_features();\n        }\n        TokenOrValue::UnresolvedColor(unresolved_color) => {\n          features |= Features::SpaceSeparatedColorNotation;\n          match unresolved_color {\n            UnresolvedColor::LightDark { light, dark } => {\n              features |= Features::LightDark;\n              features |= light.get_features();\n              features |= dark.get_features();\n            }\n            _ => {}\n          }\n        }\n        TokenOrValue::Function(f) => {\n          features |= f.arguments.get_features();\n        }\n        TokenOrValue::Var(v) => {\n          if let Some(fallback) = &v.fallback {\n            features |= fallback.get_features();\n          }\n        }\n        TokenOrValue::Env(v) => {\n          if let Some(fallback) = &v.fallback {\n            features |= fallback.get_features();\n          }\n        }\n        _ => {}\n      }\n    }\n\n    features\n  }\n\n  /// Substitutes variables with the provided values.\n  #[cfg(feature = \"substitute_variables\")]\n  #[cfg_attr(docsrs, doc(cfg(feature = \"substitute_variables\")))]\n  pub fn substitute_variables(&mut self, vars: &std::collections::HashMap<&str, TokenList<'i>>) {\n    self.visit(&mut VarInliner { vars }).unwrap()\n  }\n}\n\n#[cfg(feature = \"substitute_variables\")]\nstruct VarInliner<'a, 'i> {\n  vars: &'a std::collections::HashMap<&'a str, TokenList<'i>>,\n}\n\n#[cfg(feature = \"substitute_variables\")]\nimpl<'a, 'i> crate::visitor::Visitor<'i> for VarInliner<'a, 'i> {\n  type Error = std::convert::Infallible;\n\n  fn visit_types(&self) -> crate::visitor::VisitTypes {\n    crate::visit_types!(TOKENS | VARIABLES)\n  }\n\n  fn visit_token_list(&mut self, tokens: &mut TokenList<'i>) -> Result<(), Self::Error> {\n    let mut i = 0;\n    let mut seen = std::collections::HashSet::new();\n    while i < tokens.0.len() {\n      let token = &mut tokens.0[i];\n      token.visit(self).unwrap();\n      if let TokenOrValue::Var(var) = token {\n        if let Some(value) = self.vars.get(var.name.ident.0.as_ref()) {\n          // Ignore circular references.\n          if seen.insert(var.name.ident.0.clone()) {\n            tokens.0.splice(i..i + 1, value.0.iter().cloned());\n            // Don't advance. We need to replace any variables in the value.\n            continue;\n          }\n        } else if let Some(fallback) = &var.fallback {\n          let fallback = fallback.0.clone();\n          if seen.insert(var.name.ident.0.clone()) {\n            tokens.0.splice(i..i + 1, fallback.into_iter());\n            continue;\n          }\n        }\n      }\n      seen.clear();\n      i += 1;\n    }\n    Ok(())\n  }\n}\n\n/// A CSS variable reference.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(feature = \"visitor\", visit(visit_variable, VARIABLES))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct Variable<'i> {\n  /// The variable name.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  pub name: DashedIdentReference<'i>,\n  /// A fallback value in case the variable is not defined.\n  pub fallback: Option<TokenList<'i>>,\n}\n\nimpl<'i> Variable<'i> {\n  fn parse<'t>(\n    input: &mut Parser<'i, 't>,\n    options: &ParserOptions<'_, 'i>,\n    depth: usize,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let name = DashedIdentReference::parse_with_options(input, options)?;\n\n    let fallback = if input.try_parse(|input| input.expect_comma()).is_ok() {\n      Some(TokenList::parse(input, options, depth)?)\n    } else {\n      None\n    };\n\n    Ok(Variable { name, fallback })\n  }\n\n  fn to_css<W>(&self, dest: &mut Printer<W>, is_custom_property: bool) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    dest.write_str(\"var(\")?;\n    self.name.to_css(dest)?;\n    if let Some(fallback) = &self.fallback {\n      dest.write_char(',')?;\n      if !fallback.starts_with_whitespace() {\n        dest.whitespace()?;\n      }\n      fallback.to_css(dest, is_custom_property)?;\n    }\n    dest.write_char(')')\n  }\n\n  fn get_fallback(&self, kind: ColorFallbackKind) -> Self {\n    Variable {\n      name: self.name.clone(),\n      fallback: self.fallback.as_ref().map(|fallback| fallback.get_fallback(kind)),\n    }\n  }\n}\n\n/// A CSS environment variable reference.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(\n  feature = \"visitor\",\n  derive(Visit),\n  visit(visit_environment_variable, ENVIRONMENT_VARIABLES)\n)]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct EnvironmentVariable<'i> {\n  /// The environment variable name.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  pub name: EnvironmentVariableName<'i>,\n  /// Optional indices into the dimensions of the environment variable.\n  #[cfg_attr(feature = \"serde\", serde(default))]\n  pub indices: Vec<CSSInteger>,\n  /// A fallback value in case the variable is not defined.\n  pub fallback: Option<TokenList<'i>>,\n}\n\n/// A CSS environment variable name.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", rename_all = \"lowercase\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub enum EnvironmentVariableName<'i> {\n  /// A UA-defined environment variable.\n  #[cfg_attr(\n    feature = \"serde\",\n    serde(with = \"crate::serialization::ValueWrapper::<UAEnvironmentVariable>\")\n  )]\n  UA(UAEnvironmentVariable),\n  /// A custom author-defined environment variable.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  Custom(DashedIdentReference<'i>),\n  /// An unknown environment variable.\n  #[cfg_attr(feature = \"serde\", serde(with = \"crate::serialization::ValueWrapper::<CustomIdent>\"))]\n  Unknown(CustomIdent<'i>),\n}\n\nenum_property! {\n  /// A UA-defined environment variable name.\n  pub enum UAEnvironmentVariable {\n    /// The safe area inset from the top of the viewport.\n    SafeAreaInsetTop,\n    /// The safe area inset from the right of the viewport.\n    SafeAreaInsetRight,\n    /// The safe area inset from the bottom of the viewport.\n    SafeAreaInsetBottom,\n    /// The safe area inset from the left of the viewport.\n    SafeAreaInsetLeft,\n    /// The viewport segment width.\n    ViewportSegmentWidth,\n    /// The viewport segment height.\n    ViewportSegmentHeight,\n    /// The viewport segment top position.\n    ViewportSegmentTop,\n    /// The viewport segment left position.\n    ViewportSegmentLeft,\n    /// The viewport segment bottom position.\n    ViewportSegmentBottom,\n    /// The viewport segment right position.\n    ViewportSegmentRight,\n  }\n}\n\nimpl<'i> EnvironmentVariableName<'i> {\n  /// Returns the name of the environment variable as a string.\n  pub fn name(&self) -> &str {\n    match self {\n      EnvironmentVariableName::UA(ua) => ua.as_str(),\n      EnvironmentVariableName::Custom(c) => c.ident.as_ref(),\n      EnvironmentVariableName::Unknown(u) => u.0.as_ref(),\n    }\n  }\n}\n\nimpl<'i> Parse<'i> for EnvironmentVariableName<'i> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    if let Ok(ua) = input.try_parse(UAEnvironmentVariable::parse) {\n      return Ok(EnvironmentVariableName::UA(ua));\n    }\n\n    if let Ok(dashed) =\n      input.try_parse(|input| DashedIdentReference::parse_with_options(input, &ParserOptions::default()))\n    {\n      return Ok(EnvironmentVariableName::Custom(dashed));\n    }\n\n    let ident = CustomIdent::parse(input)?;\n    return Ok(EnvironmentVariableName::Unknown(ident));\n  }\n}\n\nimpl<'i> ToCss for EnvironmentVariableName<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      EnvironmentVariableName::UA(ua) => ua.to_css(dest),\n      EnvironmentVariableName::Custom(custom) => custom.to_css(dest),\n      EnvironmentVariableName::Unknown(unknown) => unknown.to_css(dest),\n    }\n  }\n}\n\nimpl<'i> EnvironmentVariable<'i> {\n  pub(crate) fn parse<'t>(\n    input: &mut Parser<'i, 't>,\n    options: &ParserOptions<'_, 'i>,\n    depth: usize,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    input.expect_function_matching(\"env\")?;\n    input.parse_nested_block(|input| Self::parse_nested(input, options, depth))\n  }\n\n  pub(crate) fn parse_nested<'t>(\n    input: &mut Parser<'i, 't>,\n    options: &ParserOptions<'_, 'i>,\n    depth: usize,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let name = EnvironmentVariableName::parse(input)?;\n    let mut indices = Vec::new();\n    while let Ok(index) = input.try_parse(CSSInteger::parse) {\n      indices.push(index);\n    }\n\n    let fallback = if input.try_parse(|input| input.expect_comma()).is_ok() {\n      Some(TokenList::parse(input, options, depth + 1)?)\n    } else {\n      None\n    };\n\n    Ok(EnvironmentVariable {\n      name,\n      indices,\n      fallback,\n    })\n  }\n\n  pub(crate) fn to_css<W>(&self, dest: &mut Printer<W>, is_custom_property: bool) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    dest.write_str(\"env(\")?;\n    self.name.to_css(dest)?;\n\n    for item in &self.indices {\n      dest.write_char(' ')?;\n      item.to_css(dest)?;\n    }\n\n    if let Some(fallback) = &self.fallback {\n      dest.write_char(',')?;\n      if !fallback.starts_with_whitespace() {\n        dest.whitespace()?;\n      }\n      fallback.to_css(dest, is_custom_property)?;\n    }\n    dest.write_char(')')\n  }\n\n  fn get_fallback(&self, kind: ColorFallbackKind) -> Self {\n    EnvironmentVariable {\n      name: self.name.clone(),\n      indices: self.indices.clone(),\n      fallback: self.fallback.as_ref().map(|fallback| fallback.get_fallback(kind)),\n    }\n  }\n}\n\n/// A custom CSS function.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(feature = \"visitor\", visit(visit_function, FUNCTIONS))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct Function<'i> {\n  /// The function name.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  pub name: Ident<'i>,\n  /// The function arguments.\n  pub arguments: TokenList<'i>,\n}\n\nimpl<'i> Function<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>, is_custom_property: bool) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    self.name.to_css(dest)?;\n    dest.write_char('(')?;\n    self.arguments.to_css(dest, is_custom_property)?;\n    dest.write_char(')')\n  }\n\n  fn get_fallback(&self, kind: ColorFallbackKind) -> Self {\n    Function {\n      name: self.name.clone(),\n      arguments: self.arguments.get_fallback(kind),\n    }\n  }\n}\n\n/// A color value with an unresolved alpha value (e.g. a variable).\n/// These can be converted from the modern slash syntax to older comma syntax.\n/// This can only be done when the only unresolved component is the alpha\n/// since variables can resolve to multiple tokens.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", rename_all = \"lowercase\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub enum UnresolvedColor<'i> {\n  /// An rgb() color.\n  RGB {\n    /// The red component.\n    r: f32,\n    /// The green component.\n    g: f32,\n    /// The blue component.\n    b: f32,\n    /// The unresolved alpha component.\n    #[cfg_attr(feature = \"serde\", serde(borrow))]\n    alpha: TokenList<'i>,\n  },\n  /// An hsl() color.\n  HSL {\n    /// The hue component.\n    h: f32,\n    /// The saturation component.\n    s: f32,\n    /// The lightness component.\n    l: f32,\n    /// The unresolved alpha component.\n    #[cfg_attr(feature = \"serde\", serde(borrow))]\n    alpha: TokenList<'i>,\n  },\n  /// The light-dark() function.\n  #[cfg_attr(feature = \"serde\", serde(rename = \"light-dark\"))]\n  LightDark {\n    /// The light value.\n    light: TokenList<'i>,\n    /// The dark value.\n    dark: TokenList<'i>,\n  },\n}\n\nimpl<'i> LightDarkColor for UnresolvedColor<'i> {\n  #[inline]\n  fn light_dark(light: Self, dark: Self) -> Self {\n    UnresolvedColor::LightDark {\n      light: TokenList(vec![TokenOrValue::UnresolvedColor(light)]),\n      dark: TokenList(vec![TokenOrValue::UnresolvedColor(dark)]),\n    }\n  }\n}\n\nimpl<'i> UnresolvedColor<'i> {\n  fn parse<'t>(\n    f: &CowArcStr<'i>,\n    input: &mut Parser<'i, 't>,\n    options: &ParserOptions<'_, 'i>,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let mut parser = ComponentParser::new(false);\n    match_ignore_ascii_case! { &*f,\n      \"rgb\" => {\n        input.parse_nested_block(|input| {\n          parser.parse_relative::<RGB, _, _>(input, |input, parser| {\n            let (r, g, b, is_legacy) = parse_rgb_components(input, parser)?;\n            if is_legacy {\n              return Err(input.new_custom_error(ParserError::InvalidValue))\n            }\n            input.expect_delim('/')?;\n            let alpha = TokenList::parse(input, options, 0)?;\n            Ok(UnresolvedColor::RGB { r, g, b, alpha })\n          })\n        })\n      },\n      \"hsl\" => {\n        input.parse_nested_block(|input| {\n          parser.parse_relative::<HSL, _, _>(input, |input, parser| {\n            let (h, s, l, is_legacy) = parse_hsl_hwb_components::<HSL>(input, parser, false)?;\n            if is_legacy {\n              return Err(input.new_custom_error(ParserError::InvalidValue))\n            }\n            input.expect_delim('/')?;\n            let alpha = TokenList::parse(input, options, 0)?;\n            Ok(UnresolvedColor::HSL { h, s, l, alpha })\n          })\n        })\n      },\n      \"light-dark\" => {\n        input.parse_nested_block(|input| {\n          let light = input.parse_until_before(Delimiter::Comma, |input|\n            TokenList::parse(input, options, 0)\n          )?;\n          input.expect_comma()?;\n          let dark = TokenList::parse(input, options, 0)?;\n          Ok(UnresolvedColor::LightDark { light, dark })\n        })\n      },\n      _ => Err(input.new_custom_error(ParserError::InvalidValue))\n    }\n  }\n\n  fn to_css<W>(&self, dest: &mut Printer<W>, is_custom_property: bool) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      UnresolvedColor::RGB { r, g, b, alpha } => {\n        if should_compile!(dest.targets.current, SpaceSeparatedColorNotation) {\n          dest.write_str(\"rgba(\")?;\n          r.to_css(dest)?;\n          dest.delim(',', false)?;\n          g.to_css(dest)?;\n          dest.delim(',', false)?;\n          b.to_css(dest)?;\n          dest.delim(',', false)?;\n          alpha.to_css(dest, is_custom_property)?;\n          dest.write_char(')')?;\n          return Ok(());\n        }\n\n        dest.write_str(\"rgb(\")?;\n        r.to_css(dest)?;\n        dest.write_char(' ')?;\n        g.to_css(dest)?;\n        dest.write_char(' ')?;\n        b.to_css(dest)?;\n        dest.delim('/', true)?;\n        alpha.to_css(dest, is_custom_property)?;\n        dest.write_char(')')\n      }\n      UnresolvedColor::HSL { h, s, l, alpha } => {\n        if should_compile!(dest.targets.current, SpaceSeparatedColorNotation) {\n          dest.write_str(\"hsla(\")?;\n          h.to_css(dest)?;\n          dest.delim(',', false)?;\n          Percentage(*s / 100.0).to_css(dest)?;\n          dest.delim(',', false)?;\n          Percentage(*l / 100.0).to_css(dest)?;\n          dest.delim(',', false)?;\n          alpha.to_css(dest, is_custom_property)?;\n          dest.write_char(')')?;\n          return Ok(());\n        }\n\n        dest.write_str(\"hsl(\")?;\n        h.to_css(dest)?;\n        dest.write_char(' ')?;\n        Percentage(*s / 100.0).to_css(dest)?;\n        dest.write_char(' ')?;\n        Percentage(*l / 100.0).to_css(dest)?;\n        dest.delim('/', true)?;\n        alpha.to_css(dest, is_custom_property)?;\n        dest.write_char(')')\n      }\n      UnresolvedColor::LightDark { light, dark } => {\n        if should_compile!(dest.targets.current, LightDark) {\n          dest.write_str(\"var(--lightningcss-light\")?;\n          dest.delim(',', false)?;\n          light.to_css(dest, is_custom_property)?;\n          dest.write_char(')')?;\n          dest.whitespace()?;\n          dest.write_str(\"var(--lightningcss-dark\")?;\n          dest.delim(',', false)?;\n          dark.to_css(dest, is_custom_property)?;\n          return dest.write_char(')');\n        }\n\n        dest.write_str(\"light-dark(\")?;\n        light.to_css(dest, is_custom_property)?;\n        dest.delim(',', false)?;\n        dark.to_css(dest, is_custom_property)?;\n        dest.write_char(')')\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/properties/display.rs",
    "content": "//! CSS properties related to display.\n\nuse super::custom::UnparsedProperty;\nuse super::{Property, PropertyId};\nuse crate::context::PropertyHandlerContext;\nuse crate::declaration::DeclarationList;\nuse crate::error::{ParserError, PrinterError};\nuse crate::macros::enum_property;\nuse crate::prefixes::{is_flex_2009, Feature};\nuse crate::printer::Printer;\nuse crate::traits::{Parse, PropertyHandler, ToCss};\nuse crate::vendor_prefix::VendorPrefix;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse cssparser::*;\n\nenum_property! {\n  /// A [`<display-outside>`](https://drafts.csswg.org/css-display-3/#typedef-display-outside) value.\n  #[allow(missing_docs)]\n  pub enum DisplayOutside {\n    Block,\n    Inline,\n    RunIn,\n  }\n}\n\n/// A [`<display-inside>`](https://drafts.csswg.org/css-display-3/#typedef-display-inside) value.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"vendorPrefix\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[allow(missing_docs)]\npub enum DisplayInside {\n  Flow,\n  FlowRoot,\n  Table,\n  Flex(VendorPrefix),\n  Box(VendorPrefix),\n  Grid,\n  Ruby,\n}\n\nimpl<'i> Parse<'i> for DisplayInside {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let location = input.current_source_location();\n    let ident = input.expect_ident()?;\n    match_ignore_ascii_case! { &*ident,\n      \"flow\" => Ok(DisplayInside::Flow),\n      \"flow-root\" => Ok(DisplayInside::FlowRoot),\n      \"table\" => Ok(DisplayInside::Table),\n      \"flex\" => Ok(DisplayInside::Flex(VendorPrefix::None)),\n      \"-webkit-flex\" => Ok(DisplayInside::Flex(VendorPrefix::WebKit)),\n      \"-ms-flexbox\" => Ok(DisplayInside::Flex(VendorPrefix::Ms)),\n      \"-webkit-box\" => Ok(DisplayInside::Box(VendorPrefix::WebKit)),\n      \"-moz-box\" => Ok(DisplayInside::Box(VendorPrefix::Moz)),\n      \"grid\" => Ok(DisplayInside::Grid),\n      \"ruby\" => Ok(DisplayInside::Ruby),\n      _ => Err(location.new_unexpected_token_error(\n        cssparser::Token::Ident(ident.clone())\n      ))\n    }\n  }\n}\n\nimpl ToCss for DisplayInside {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      DisplayInside::Flow => dest.write_str(\"flow\"),\n      DisplayInside::FlowRoot => dest.write_str(\"flow-root\"),\n      DisplayInside::Table => dest.write_str(\"table\"),\n      DisplayInside::Flex(prefix) => {\n        prefix.to_css(dest)?;\n        if *prefix == VendorPrefix::Ms {\n          dest.write_str(\"flexbox\")\n        } else {\n          dest.write_str(\"flex\")\n        }\n      }\n      DisplayInside::Box(prefix) => {\n        prefix.to_css(dest)?;\n        dest.write_str(\"box\")\n      }\n      DisplayInside::Grid => dest.write_str(\"grid\"),\n      DisplayInside::Ruby => dest.write_str(\"ruby\"),\n    }\n  }\n}\n\nimpl DisplayInside {\n  fn is_equivalent(&self, other: &DisplayInside) -> bool {\n    match (self, other) {\n      (DisplayInside::Flex(_), DisplayInside::Flex(_)) => true,\n      (DisplayInside::Box(_), DisplayInside::Box(_)) => true,\n      (DisplayInside::Flex(_), DisplayInside::Box(_)) => true,\n      (DisplayInside::Box(_), DisplayInside::Flex(_)) => true,\n      _ => self == other,\n    }\n  }\n}\n\n/// A pair of inside and outside display values, as used in the `display` property.\n///\n/// See [Display](Display).\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"camelCase\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct DisplayPair {\n  /// The outside display value.\n  pub outside: DisplayOutside,\n  /// The inside display value.\n  pub inside: DisplayInside,\n  /// Whether this is a list item.\n  pub is_list_item: bool,\n}\n\nimpl<'i> Parse<'i> for DisplayPair {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let mut list_item = false;\n    let mut outside = None;\n    let mut inside = None;\n\n    loop {\n      if input.try_parse(|input| input.expect_ident_matching(\"list-item\")).is_ok() {\n        list_item = true;\n        continue;\n      }\n\n      if outside.is_none() {\n        if let Ok(o) = input.try_parse(DisplayOutside::parse) {\n          outside = Some(o);\n          continue;\n        }\n      }\n\n      if inside.is_none() {\n        if let Ok(i) = input.try_parse(DisplayInside::parse) {\n          inside = Some(i);\n          continue;\n        }\n      }\n\n      break;\n    }\n\n    if list_item || inside.is_some() || outside.is_some() {\n      let inside = inside.unwrap_or(DisplayInside::Flow);\n      let outside = outside.unwrap_or(match inside {\n        // \"If <display-outside> is omitted, the element’s outside display type\n        // defaults to block — except for ruby, which defaults to inline.\"\n        // https://drafts.csswg.org/css-display/#inside-model\n        DisplayInside::Ruby => DisplayOutside::Inline,\n        _ => DisplayOutside::Block,\n      });\n\n      if list_item && !matches!(inside, DisplayInside::Flow | DisplayInside::FlowRoot) {\n        return Err(input.new_custom_error(ParserError::InvalidDeclaration));\n      }\n\n      return Ok(DisplayPair {\n        outside,\n        inside,\n        is_list_item: list_item,\n      });\n    }\n\n    let location = input.current_source_location();\n    let ident = input.expect_ident()?;\n    match_ignore_ascii_case! { &*ident,\n      \"inline-block\" => Ok(DisplayPair {\n        outside: DisplayOutside::Inline,\n        inside: DisplayInside::FlowRoot,\n        is_list_item: false\n      }),\n      \"inline-table\" => Ok(DisplayPair {\n        outside: DisplayOutside::Inline,\n        inside: DisplayInside::Table,\n        is_list_item: false\n      }),\n      \"inline-flex\" => Ok(DisplayPair {\n        outside: DisplayOutside::Inline,\n        inside: DisplayInside::Flex(VendorPrefix::None),\n        is_list_item: false\n      }),\n      \"-webkit-inline-flex\" => Ok(DisplayPair {\n        outside: DisplayOutside::Inline,\n        inside: DisplayInside::Flex(VendorPrefix::WebKit),\n        is_list_item: false\n      }),\n      \"-ms-inline-flexbox\" => Ok(DisplayPair {\n        outside: DisplayOutside::Inline,\n        inside: DisplayInside::Flex(VendorPrefix::Ms),\n        is_list_item: false\n      }),\n      \"-webkit-inline-box\" => Ok(DisplayPair {\n        outside: DisplayOutside::Inline,\n        inside: DisplayInside::Box(VendorPrefix::WebKit),\n        is_list_item: false\n      }),\n      \"-moz-inline-box\" => Ok(DisplayPair {\n        outside: DisplayOutside::Inline,\n        inside: DisplayInside::Box(VendorPrefix::Moz),\n        is_list_item: false\n      }),\n      \"inline-grid\" => Ok(DisplayPair {\n        outside: DisplayOutside::Inline,\n        inside: DisplayInside::Grid,\n        is_list_item: false\n      }),\n      _ => Err(location.new_unexpected_token_error(\n        cssparser::Token::Ident(ident.clone())\n      ))\n    }\n  }\n}\n\nimpl ToCss for DisplayPair {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      DisplayPair {\n        outside: DisplayOutside::Inline,\n        inside: DisplayInside::FlowRoot,\n        is_list_item: false,\n      } => dest.write_str(\"inline-block\"),\n      DisplayPair {\n        outside: DisplayOutside::Inline,\n        inside: DisplayInside::Table,\n        is_list_item: false,\n      } => dest.write_str(\"inline-table\"),\n      DisplayPair {\n        outside: DisplayOutside::Inline,\n        inside: DisplayInside::Flex(prefix),\n        is_list_item: false,\n      } => {\n        prefix.to_css(dest)?;\n        if *prefix == VendorPrefix::Ms {\n          dest.write_str(\"inline-flexbox\")\n        } else {\n          dest.write_str(\"inline-flex\")\n        }\n      }\n      DisplayPair {\n        outside: DisplayOutside::Inline,\n        inside: DisplayInside::Box(prefix),\n        is_list_item: false,\n      } => {\n        prefix.to_css(dest)?;\n        dest.write_str(\"inline-box\")\n      }\n      DisplayPair {\n        outside: DisplayOutside::Inline,\n        inside: DisplayInside::Grid,\n        is_list_item: false,\n      } => dest.write_str(\"inline-grid\"),\n      DisplayPair {\n        outside,\n        inside,\n        is_list_item,\n      } => {\n        let default_outside = match inside {\n          DisplayInside::Ruby => DisplayOutside::Inline,\n          _ => DisplayOutside::Block,\n        };\n\n        let mut needs_space = false;\n        if *outside != default_outside || (*inside == DisplayInside::Flow && !*is_list_item) {\n          outside.to_css(dest)?;\n          needs_space = true;\n        }\n\n        if *inside != DisplayInside::Flow {\n          if needs_space {\n            dest.write_char(' ')?;\n          }\n          inside.to_css(dest)?;\n          needs_space = true;\n        }\n\n        if *is_list_item {\n          if needs_space {\n            dest.write_char(' ')?;\n          }\n          dest.write_str(\"list-item\")?;\n        }\n\n        Ok(())\n      }\n    }\n  }\n}\n\nenum_property! {\n  /// A `display` keyword.\n  ///\n  /// See [Display](Display).\n  #[allow(missing_docs)]\n  pub enum DisplayKeyword {\n    None,\n    Contents,\n    TableRowGroup,\n    TableHeaderGroup,\n    TableFooterGroup,\n    TableRow,\n    TableCell,\n    TableColumnGroup,\n    TableColumn,\n    TableCaption,\n    RubyBase,\n    RubyText,\n    RubyBaseContainer,\n    RubyTextContainer,\n  }\n}\n\n/// A value for the [display](https://drafts.csswg.org/css-display-3/#the-display-properties) property.\n#[derive(Debug, Clone, PartialEq, Parse, ToCss)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum Display {\n  /// A display keyword.\n  #[cfg_attr(\n    feature = \"serde\",\n    serde(with = \"crate::serialization::ValueWrapper::<DisplayKeyword>\")\n  )]\n  Keyword(DisplayKeyword),\n  /// The inside and outside display values.\n  Pair(DisplayPair),\n}\n\nenum_property! {\n  /// A value for the [visibility](https://drafts.csswg.org/css-display-3/#visibility) property.\n  pub enum Visibility {\n    /// The element is visible.\n    Visible,\n    /// The element is hidden.\n    Hidden,\n    /// The element is collapsed.\n    Collapse,\n  }\n}\n\n#[derive(Default)]\npub(crate) struct DisplayHandler<'i> {\n  decls: Vec<Property<'i>>,\n  display: Option<Display>,\n}\n\nimpl<'i> PropertyHandler<'i> for DisplayHandler<'i> {\n  fn handle_property(\n    &mut self,\n    property: &Property<'i>,\n    dest: &mut DeclarationList<'i>,\n    context: &mut PropertyHandlerContext<'i, '_>,\n  ) -> bool {\n    if let Property::Display(display) = property {\n      match (&self.display, display) {\n        (Some(Display::Pair(cur)), Display::Pair(new)) => {\n          // If the new value is different but equivalent (e.g. different vendor prefix),\n          // we need to preserve multiple values.\n          if cur.outside == new.outside\n            && cur.is_list_item == new.is_list_item\n            && cur.inside != new.inside\n            && cur.inside.is_equivalent(&new.inside)\n          {\n            // If we have targets, and there is no vendor prefix, clear the existing\n            // declarations. The prefixes will be filled in later. Otherwise, if there\n            // are no targets, or there is a vendor prefix, add a new declaration.\n            if context.targets.browsers.is_some() && new.inside == DisplayInside::Flex(VendorPrefix::None) {\n              self.decls.clear();\n            } else if context.targets.browsers.is_none() || cur.inside != DisplayInside::Flex(VendorPrefix::None) {\n              self.decls.push(Property::Display(self.display.clone().unwrap()));\n            }\n          }\n        }\n        _ => {}\n      }\n\n      self.display = Some(display.clone());\n      return true;\n    }\n\n    if matches!(\n      property,\n      Property::Unparsed(UnparsedProperty {\n        property_id: PropertyId::Display,\n        ..\n      })\n    ) {\n      self.finalize(dest, context);\n      dest.push(property.clone());\n      return true;\n    }\n\n    false\n  }\n\n  fn finalize(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {\n    if self.display.is_none() {\n      return;\n    }\n\n    dest.extend(self.decls.drain(..));\n\n    if let Some(display) = std::mem::take(&mut self.display) {\n      // If we have an unprefixed `flex` value, then add the necessary prefixed values.\n      if let Display::Pair(DisplayPair {\n        inside: DisplayInside::Flex(VendorPrefix::None),\n        outside,\n        ..\n      }) = display\n      {\n        let prefixes = context.targets.prefixes(VendorPrefix::None, Feature::DisplayFlex);\n\n        if let Some(targets) = context.targets.browsers {\n          // Handle legacy -webkit-box/-moz-box values if needed.\n          if is_flex_2009(targets) {\n            if prefixes.contains(VendorPrefix::WebKit) {\n              dest.push(Property::Display(Display::Pair(DisplayPair {\n                inside: DisplayInside::Box(VendorPrefix::WebKit),\n                outside: outside.clone(),\n                is_list_item: false,\n              })));\n            }\n\n            if prefixes.contains(VendorPrefix::Moz) {\n              dest.push(Property::Display(Display::Pair(DisplayPair {\n                inside: DisplayInside::Box(VendorPrefix::Moz),\n                outside: outside.clone(),\n                is_list_item: false,\n              })));\n            }\n          }\n        }\n\n        if prefixes.contains(VendorPrefix::WebKit) {\n          dest.push(Property::Display(Display::Pair(DisplayPair {\n            inside: DisplayInside::Flex(VendorPrefix::WebKit),\n            outside: outside.clone(),\n            is_list_item: false,\n          })));\n        }\n\n        if prefixes.contains(VendorPrefix::Ms) {\n          dest.push(Property::Display(Display::Pair(DisplayPair {\n            inside: DisplayInside::Flex(VendorPrefix::Ms),\n            outside: outside.clone(),\n            is_list_item: false,\n          })));\n        }\n      }\n\n      dest.push(Property::Display(display))\n    }\n  }\n}\n"
  },
  {
    "path": "src/properties/effects.rs",
    "content": "//! CSS properties related to filters and effects.\n\nuse crate::macros::enum_property;\nuse crate::error::{ParserError, PrinterError};\nuse crate::printer::Printer;\nuse crate::targets::{Browsers, Targets};\nuse crate::traits::{FallbackValues, IsCompatible, Parse, ToCss, Zero};\nuse crate::values::color::ColorFallbackKind;\nuse crate::values::{angle::Angle, color::CssColor, length::Length, percentage::NumberOrPercentage, url::Url};\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse cssparser::*;\nuse smallvec::SmallVec;\n\n/// A [filter](https://drafts.fxtf.org/filter-effects-1/#filter-functions) function.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub enum Filter<'i> {\n  /// A `blur()` filter.\n  Blur(Length),\n  /// A `brightness()` filter.\n  Brightness(NumberOrPercentage),\n  /// A `contrast()` filter.\n  Contrast(NumberOrPercentage),\n  /// A `grayscale()` filter.\n  Grayscale(NumberOrPercentage),\n  /// A `hue-rotate()` filter.\n  HueRotate(Angle),\n  /// An `invert()` filter.\n  Invert(NumberOrPercentage),\n  /// An `opacity()` filter.\n  Opacity(NumberOrPercentage),\n  /// A `saturate()` filter.\n  Saturate(NumberOrPercentage),\n  /// A `sepia()` filter.\n  Sepia(NumberOrPercentage),\n  /// A `drop-shadow()` filter.\n  DropShadow(DropShadow),\n  /// A `url()` reference to an SVG filter.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  Url(Url<'i>),\n}\n\nimpl<'i> Parse<'i> for Filter<'i> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    if let Ok(url) = input.try_parse(Url::parse) {\n      return Ok(Filter::Url(url));\n    }\n\n    let location = input.current_source_location();\n    let function = input.expect_function()?;\n    match_ignore_ascii_case! { &function,\n      \"blur\" => {\n        input.parse_nested_block(|input| {\n          Ok(Filter::Blur(input.try_parse(Length::parse).unwrap_or(Length::zero())))\n        })\n      },\n      \"brightness\" => {\n        input.parse_nested_block(|input| {\n          Ok(Filter::Brightness(input.try_parse(NumberOrPercentage::parse).unwrap_or(NumberOrPercentage::Number(1.0))))\n        })\n      },\n      \"contrast\" => {\n        input.parse_nested_block(|input| {\n          Ok(Filter::Contrast(input.try_parse(NumberOrPercentage::parse).unwrap_or(NumberOrPercentage::Number(1.0))))\n        })\n      },\n      \"grayscale\" => {\n        input.parse_nested_block(|input| {\n          Ok(Filter::Grayscale(input.try_parse(NumberOrPercentage::parse).unwrap_or(NumberOrPercentage::Number(1.0))))\n        })\n      },\n      \"hue-rotate\" => {\n        input.parse_nested_block(|input| {\n          // Spec has an exception for unitless zero angles: https://github.com/w3c/fxtf-drafts/issues/228\n          Ok(Filter::HueRotate(input.try_parse(Angle::parse_with_unitless_zero).unwrap_or(Angle::zero())))\n        })\n      },\n      \"invert\" => {\n        input.parse_nested_block(|input| {\n          Ok(Filter::Invert(input.try_parse(NumberOrPercentage::parse).unwrap_or(NumberOrPercentage::Number(1.0))))\n        })\n      },\n      \"opacity\" => {\n        input.parse_nested_block(|input| {\n          Ok(Filter::Opacity(input.try_parse(NumberOrPercentage::parse).unwrap_or(NumberOrPercentage::Number(1.0))))\n        })\n      },\n      \"saturate\" => {\n        input.parse_nested_block(|input| {\n          Ok(Filter::Saturate(input.try_parse(NumberOrPercentage::parse).unwrap_or(NumberOrPercentage::Number(1.0))))\n        })\n      },\n      \"sepia\" => {\n        input.parse_nested_block(|input| {\n          Ok(Filter::Sepia(input.try_parse(NumberOrPercentage::parse).unwrap_or(NumberOrPercentage::Number(1.0))))\n        })\n      },\n      \"drop-shadow\" => {\n        input.parse_nested_block(|input| {\n          Ok(Filter::DropShadow(DropShadow::parse(input)?))\n        })\n      },\n      _ => Err(location.new_unexpected_token_error(\n        cssparser::Token::Ident(function.clone())\n      ))\n    }\n  }\n}\n\nimpl<'i> ToCss for Filter<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      Filter::Blur(val) => {\n        dest.write_str(\"blur(\")?;\n        if *val != Length::zero() {\n          val.to_css(dest)?;\n        }\n        dest.write_char(')')\n      }\n      Filter::Brightness(val) => {\n        dest.write_str(\"brightness(\")?;\n        let v: f32 = val.into();\n        if v != 1.0 {\n          val.to_css(dest)?;\n        }\n        dest.write_char(')')\n      }\n      Filter::Contrast(val) => {\n        dest.write_str(\"contrast(\")?;\n        let v: f32 = val.into();\n        if v != 1.0 {\n          val.to_css(dest)?;\n        }\n        dest.write_char(')')\n      }\n      Filter::Grayscale(val) => {\n        dest.write_str(\"grayscale(\")?;\n        let v: f32 = val.into();\n        if v != 1.0 {\n          val.to_css(dest)?;\n        }\n        dest.write_char(')')\n      }\n      Filter::HueRotate(val) => {\n        dest.write_str(\"hue-rotate(\")?;\n        if !val.is_zero() {\n          val.to_css(dest)?;\n        }\n        dest.write_char(')')\n      }\n      Filter::Invert(val) => {\n        dest.write_str(\"invert(\")?;\n        let v: f32 = val.into();\n        if v != 1.0 {\n          val.to_css(dest)?;\n        }\n        dest.write_char(')')\n      }\n      Filter::Opacity(val) => {\n        dest.write_str(\"opacity(\")?;\n        let v: f32 = val.into();\n        if v != 1.0 {\n          val.to_css(dest)?;\n        }\n        dest.write_char(')')\n      }\n      Filter::Saturate(val) => {\n        dest.write_str(\"saturate(\")?;\n        let v: f32 = val.into();\n        if v != 1.0 {\n          val.to_css(dest)?;\n        }\n        dest.write_char(')')\n      }\n      Filter::Sepia(val) => {\n        dest.write_str(\"sepia(\")?;\n        let v: f32 = val.into();\n        if v != 1.0 {\n          val.to_css(dest)?;\n        }\n        dest.write_char(')')\n      }\n      Filter::DropShadow(val) => {\n        dest.write_str(\"drop-shadow(\")?;\n        val.to_css(dest)?;\n        dest.write_char(')')\n      }\n      Filter::Url(url) => url.to_css(dest),\n    }\n  }\n}\n\nimpl<'i> Filter<'i> {\n  fn get_fallback(&self, kind: ColorFallbackKind) -> Self {\n    match self {\n      Filter::DropShadow(shadow) => Filter::DropShadow(shadow.get_fallback(kind)),\n      _ => self.clone(),\n    }\n  }\n}\n\nimpl IsCompatible for Filter<'_> {\n  fn is_compatible(&self, _browsers: Browsers) -> bool {\n    true\n  }\n}\n\n/// A [`drop-shadow()`](https://drafts.fxtf.org/filter-effects-1/#funcdef-filter-drop-shadow) filter function.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"camelCase\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub struct DropShadow {\n  /// The color of the drop shadow.\n  pub color: CssColor,\n  /// The x offset of the drop shadow.\n  pub x_offset: Length,\n  /// The y offset of the drop shadow.\n  pub y_offset: Length,\n  /// The blur radius of the drop shadow.\n  pub blur: Length,\n}\n\nimpl<'i> Parse<'i> for DropShadow {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let mut color = None;\n    let mut lengths = None;\n\n    loop {\n      if lengths.is_none() {\n        let value = input.try_parse::<_, _, ParseError<ParserError<'i>>>(|input| {\n          let horizontal = Length::parse(input)?;\n          let vertical = Length::parse(input)?;\n          let blur = input.try_parse(Length::parse).unwrap_or(Length::zero());\n          Ok((horizontal, vertical, blur))\n        });\n\n        if let Ok(value) = value {\n          lengths = Some(value);\n          continue;\n        }\n      }\n\n      if color.is_none() {\n        if let Ok(value) = input.try_parse(CssColor::parse) {\n          color = Some(value);\n          continue;\n        }\n      }\n\n      break;\n    }\n\n    let lengths = lengths.ok_or(input.new_error(BasicParseErrorKind::QualifiedRuleInvalid))?;\n    Ok(DropShadow {\n      color: color.unwrap_or(CssColor::current_color()),\n      x_offset: lengths.0,\n      y_offset: lengths.1,\n      blur: lengths.2,\n    })\n  }\n}\n\nimpl ToCss for DropShadow {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    self.x_offset.to_css(dest)?;\n    dest.write_char(' ')?;\n    self.y_offset.to_css(dest)?;\n\n    if self.blur != Length::zero() {\n      dest.write_char(' ')?;\n      self.blur.to_css(dest)?;\n    }\n\n    if self.color != CssColor::current_color() {\n      dest.write_char(' ')?;\n      self.color.to_css(dest)?;\n    }\n\n    Ok(())\n  }\n}\n\nimpl DropShadow {\n  fn get_fallback(&self, kind: ColorFallbackKind) -> DropShadow {\n    DropShadow {\n      color: self.color.get_fallback(kind),\n      ..self.clone()\n    }\n  }\n}\n\n/// A value for the [filter](https://drafts.fxtf.org/filter-effects-1/#FilterProperty) and\n/// [backdrop-filter](https://drafts.fxtf.org/filter-effects-2/#BackdropFilterProperty) properties.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub enum FilterList<'i> {\n  /// The `none` keyword.\n  None,\n  /// A list of filter functions.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  Filters(SmallVec<[Filter<'i>; 1]>),\n}\n\nimpl<'i> Parse<'i> for FilterList<'i> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    if input.try_parse(|input| input.expect_ident_matching(\"none\")).is_ok() {\n      return Ok(FilterList::None);\n    }\n\n    let mut filters = SmallVec::new();\n    while let Ok(filter) = input.try_parse(Filter::parse) {\n      filters.push(filter);\n    }\n\n    Ok(FilterList::Filters(filters))\n  }\n}\n\nimpl<'i> ToCss for FilterList<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      FilterList::None => dest.write_str(\"none\"),\n      FilterList::Filters(filters) => {\n        let mut first = true;\n        for filter in filters {\n          if first {\n            first = false;\n          } else {\n            dest.whitespace()?;\n          }\n          filter.to_css(dest)?;\n        }\n        Ok(())\n      }\n    }\n  }\n}\n\nimpl<'i> FallbackValues for FilterList<'i> {\n  fn get_fallbacks(&mut self, targets: Targets) -> Vec<Self> {\n    let mut res = Vec::new();\n    let mut fallbacks = ColorFallbackKind::empty();\n    if let FilterList::Filters(filters) = self {\n      for shadow in filters.iter() {\n        if let Filter::DropShadow(shadow) = &shadow {\n          fallbacks |= shadow.color.get_necessary_fallbacks(targets);\n        }\n      }\n\n      if fallbacks.contains(ColorFallbackKind::RGB) {\n        res.push(FilterList::Filters(\n          filters\n            .iter()\n            .map(|filter| filter.get_fallback(ColorFallbackKind::RGB))\n            .collect(),\n        ));\n      }\n\n      if fallbacks.contains(ColorFallbackKind::P3) {\n        res.push(FilterList::Filters(\n          filters\n            .iter()\n            .map(|filter| filter.get_fallback(ColorFallbackKind::P3))\n            .collect(),\n        ));\n      }\n\n      if fallbacks.contains(ColorFallbackKind::LAB) {\n        for filter in filters.iter_mut() {\n          *filter = filter.get_fallback(ColorFallbackKind::LAB);\n        }\n      }\n    }\n\n    res\n  }\n}\n\nimpl IsCompatible for FilterList<'_> {\n  fn is_compatible(&self, _browsers: Browsers) -> bool {\n    true\n  }\n}\n\nenum_property! {\n  /// A [`<blend-mode>`](https://www.w3.org/TR/compositing-1/#ltblendmodegt) value.\n  pub enum BlendMode {\n    /// The default blend mode; the top layer is drawn over the bottom layer.\n    Normal,\n    /// The source and destination are multiplied.\n    Multiply,\n    /// Multiplies the complements of the backdrop and source, then complements the result.\n    Screen,\n    /// Multiplies or screens, depending on the backdrop color.\n    Overlay,\n    /// Selects the darker of the backdrop and source.\n    Darken,\n    /// Selects the lighter of the backdrop and source.\n    Lighten,\n    /// Brightens the backdrop to reflect the source.\n    ColorDodge,\n    /// Darkens the backdrop to reflect the source.\n    ColorBurn,\n    /// Multiplies or screens, depending on the source color.\n    HardLight,\n    /// Darkens or lightens, depending on the source color.\n    SoftLight,\n    /// Subtracts the darker from the lighter.\n    Difference,\n    /// Similar to difference, but with lower contrast.\n    Exclusion,\n    /// The hue of the source with the saturation and luminosity of the backdrop.\n    Hue,\n    /// The saturation of the source with the hue and luminosity of the backdrop.\n    Saturation,\n    /// The hue and saturation of the source with the luminosity of the backdrop.\n    Color,\n    /// The luminosity of the source with the hue and saturation of the backdrop.\n    Luminosity,\n    /// Adds the source to the backdrop, producing a darker result.\n    PlusDarker,\n    /// Adds the source to the backdrop, producing a lighter result.\n    PlusLighter,\n  }\n}\n"
  },
  {
    "path": "src/properties/flex.rs",
    "content": "//! CSS properties related to flexbox layout.\n\nuse super::align::{\n  AlignContent, AlignItems, AlignSelf, ContentDistribution, ContentPosition, JustifyContent, SelfPosition,\n};\nuse super::{Property, PropertyId};\nuse crate::context::PropertyHandlerContext;\nuse crate::declaration::{DeclarationBlock, DeclarationList};\nuse crate::error::{ParserError, PrinterError};\nuse crate::macros::*;\nuse crate::prefixes::{is_flex_2009, Feature};\nuse crate::printer::Printer;\nuse crate::traits::{FromStandard, Parse, PropertyHandler, Shorthand, ToCss, Zero};\nuse crate::values::number::{CSSInteger, CSSNumber};\nuse crate::values::{\n  length::{LengthPercentage, LengthPercentageOrAuto},\n  percentage::Percentage,\n};\nuse crate::vendor_prefix::VendorPrefix;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse cssparser::*;\n\nenum_property! {\n  /// A value for the [flex-direction](https://www.w3.org/TR/2018/CR-css-flexbox-1-20181119/#propdef-flex-direction) property.\n  pub enum FlexDirection {\n    /// Flex items are laid out in a row.\n    Row,\n    /// Flex items are laid out in a row, and reversed.\n    RowReverse,\n    /// Flex items are laid out in a column.\n    Column,\n    /// Flex items are laid out in a column, and reversed.\n    ColumnReverse,\n  }\n}\n\nimpl Default for FlexDirection {\n  fn default() -> FlexDirection {\n    FlexDirection::Row\n  }\n}\n\nenum_property! {\n  /// A value for the [flex-wrap](https://www.w3.org/TR/2018/CR-css-flexbox-1-20181119/#flex-wrap-property) property.\n  pub enum FlexWrap {\n    /// The flex items do not wrap.\n    \"nowrap\": NoWrap,\n    /// The flex items wrap.\n    \"wrap\": Wrap,\n    /// The flex items wrap, in reverse.\n    \"wrap-reverse\": WrapReverse,\n  }\n}\n\nimpl Default for FlexWrap {\n  fn default() -> FlexWrap {\n    FlexWrap::NoWrap\n  }\n}\n\nimpl FromStandard<FlexWrap> for FlexWrap {\n  fn from_standard(wrap: &FlexWrap) -> Option<FlexWrap> {\n    Some(wrap.clone())\n  }\n}\n\ndefine_shorthand! {\n  /// A value for the [flex-flow](https://www.w3.org/TR/2018/CR-css-flexbox-1-20181119/#flex-flow-property) shorthand property.\n  pub struct FlexFlow(VendorPrefix) {\n    /// The direction that flex items flow.\n    direction: FlexDirection(FlexDirection, VendorPrefix),\n    /// How the flex items wrap.\n    wrap: FlexWrap(FlexWrap, VendorPrefix),\n  }\n}\n\nimpl<'i> Parse<'i> for FlexFlow {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let mut direction = None;\n    let mut wrap = None;\n    loop {\n      if direction.is_none() {\n        if let Ok(value) = input.try_parse(FlexDirection::parse) {\n          direction = Some(value);\n          continue;\n        }\n      }\n      if wrap.is_none() {\n        if let Ok(value) = input.try_parse(FlexWrap::parse) {\n          wrap = Some(value);\n          continue;\n        }\n      }\n      break;\n    }\n\n    Ok(FlexFlow {\n      direction: direction.unwrap_or_default(),\n      wrap: wrap.unwrap_or_default(),\n    })\n  }\n}\n\nimpl ToCss for FlexFlow {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    let mut needs_space = false;\n    if self.direction != FlexDirection::default() || self.wrap == FlexWrap::default() {\n      self.direction.to_css(dest)?;\n      needs_space = true;\n    }\n\n    if self.wrap != FlexWrap::default() {\n      if needs_space {\n        dest.write_str(\" \")?;\n      }\n      self.wrap.to_css(dest)?;\n    }\n\n    Ok(())\n  }\n}\n\ndefine_shorthand! {\n/// A value for the [flex](https://www.w3.org/TR/2018/CR-css-flexbox-1-20181119/#flex-property) shorthand property.\n  pub struct Flex(VendorPrefix) {\n    /// The flex grow factor.\n    grow: FlexGrow(CSSNumber, VendorPrefix),\n    /// The flex shrink factor.\n    shrink: FlexShrink(CSSNumber, VendorPrefix),\n    /// The flex basis.\n    basis: FlexBasis(LengthPercentageOrAuto, VendorPrefix),\n  }\n}\n\nimpl<'i> Parse<'i> for Flex {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    if input.try_parse(|input| input.expect_ident_matching(\"none\")).is_ok() {\n      return Ok(Flex {\n        grow: 0.0,\n        shrink: 0.0,\n        basis: LengthPercentageOrAuto::Auto,\n      });\n    }\n\n    let mut grow = None;\n    let mut shrink = None;\n    let mut basis = None;\n\n    loop {\n      if grow.is_none() {\n        if let Ok(val) = input.try_parse(CSSNumber::parse) {\n          grow = Some(val);\n          shrink = input.try_parse(CSSNumber::parse).ok();\n          continue;\n        }\n      }\n\n      if basis.is_none() {\n        if let Ok(val) = input.try_parse(LengthPercentageOrAuto::parse) {\n          basis = Some(val);\n          continue;\n        }\n      }\n\n      break;\n    }\n\n    Ok(Flex {\n      grow: grow.unwrap_or(1.0),\n      shrink: shrink.unwrap_or(1.0),\n      basis: basis.unwrap_or(LengthPercentageOrAuto::LengthPercentage(LengthPercentage::Percentage(\n        Percentage(0.0),\n      ))),\n    })\n  }\n}\n\nimpl ToCss for Flex {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    if self.grow == 0.0 && self.shrink == 0.0 && self.basis == LengthPercentageOrAuto::Auto {\n      dest.write_str(\"none\")?;\n      return Ok(());\n    }\n\n    #[derive(PartialEq)]\n    enum ZeroKind {\n      NonZero,\n      Length,\n      Percentage,\n    }\n\n    // If the basis is unitless 0, we must write all three components to disambiguate.\n    // If the basis is 0%, we can omit the basis.\n    let basis_kind = match &self.basis {\n      LengthPercentageOrAuto::LengthPercentage(lp) => match lp {\n        LengthPercentage::Dimension(l) if l.is_zero() => ZeroKind::Length,\n        LengthPercentage::Percentage(p) if p.is_zero() => ZeroKind::Percentage,\n        _ => ZeroKind::NonZero,\n      },\n      _ => ZeroKind::NonZero,\n    };\n\n    if self.grow != 1.0 || self.shrink != 1.0 || basis_kind != ZeroKind::NonZero {\n      self.grow.to_css(dest)?;\n      if self.shrink != 1.0 || basis_kind == ZeroKind::Length {\n        dest.write_str(\" \")?;\n        self.shrink.to_css(dest)?;\n      }\n    }\n\n    if basis_kind != ZeroKind::Percentage {\n      if self.grow != 1.0 || self.shrink != 1.0 || basis_kind == ZeroKind::Length {\n        dest.write_str(\" \")?;\n      }\n      self.basis.to_css(dest)?;\n    }\n\n    Ok(())\n  }\n}\n\n// Old flex (2009): https://www.w3.org/TR/2009/WD-css3-flexbox-20090723/\n\nenum_property! {\n  /// A value for the legacy (prefixed) [box-orient](https://www.w3.org/TR/2009/WD-css3-flexbox-20090723/#orientation) property.\n  /// Partially equivalent to `flex-direction` in the standard syntax.\n  pub enum BoxOrient {\n    /// Items are laid out horizontally.\n    Horizontal,\n    /// Items are laid out vertically.\n    Vertical,\n    /// Items are laid out along the inline axis, according to the writing direction.\n    InlineAxis,\n    /// Items are laid out along the block axis, according to the writing direction.\n    BlockAxis,\n  }\n}\n\nimpl FlexDirection {\n  fn to_2009(&self) -> (BoxOrient, BoxDirection) {\n    match self {\n      FlexDirection::Row => (BoxOrient::Horizontal, BoxDirection::Normal),\n      FlexDirection::Column => (BoxOrient::Vertical, BoxDirection::Normal),\n      FlexDirection::RowReverse => (BoxOrient::Horizontal, BoxDirection::Reverse),\n      FlexDirection::ColumnReverse => (BoxOrient::Vertical, BoxDirection::Reverse),\n    }\n  }\n}\n\nenum_property! {\n  /// A value for the legacy (prefixed) [box-direction](https://www.w3.org/TR/2009/WD-css3-flexbox-20090723/#displayorder) property.\n  /// Partially equivalent to the `flex-direction` property in the standard syntax.\n  pub enum BoxDirection {\n    /// Items flow in the natural direction.\n    Normal,\n    /// Items flow in the reverse direction.\n    Reverse,\n  }\n}\n\nenum_property! {\n  /// A value for the legacy (prefixed) [box-align](https://www.w3.org/TR/2009/WD-css3-flexbox-20090723/#alignment) property.\n  /// Equivalent to the `align-items` property in the standard syntax.\n  pub enum BoxAlign {\n    /// Items are aligned to the start.\n    Start,\n    /// Items are aligned to the end.\n    End,\n    /// Items are centered.\n    Center,\n    /// Items are aligned to the baseline.\n    Baseline,\n    /// Items are stretched.\n    Stretch,\n  }\n}\n\nimpl FromStandard<AlignItems> for BoxAlign {\n  fn from_standard(align: &AlignItems) -> Option<BoxAlign> {\n    match align {\n      AlignItems::SelfPosition { overflow: None, value } => match value {\n        SelfPosition::Start | SelfPosition::FlexStart => Some(BoxAlign::Start),\n        SelfPosition::End | SelfPosition::FlexEnd => Some(BoxAlign::End),\n        SelfPosition::Center => Some(BoxAlign::Center),\n        _ => None,\n      },\n      AlignItems::Stretch => Some(BoxAlign::Stretch),\n      _ => None,\n    }\n  }\n}\n\nenum_property! {\n  /// A value for the legacy (prefixed) [box-pack](https://www.w3.org/TR/2009/WD-css3-flexbox-20090723/#packing) property.\n  /// Equivalent to the `justify-content` property in the standard syntax.\n  pub enum BoxPack {\n    /// Items are justified to the start.\n    Start,\n    /// Items are justified to the end.\n    End,\n    /// Items are centered.\n    Center,\n    /// Items are justified to the start and end.\n    Justify,\n  }\n}\n\nimpl FromStandard<JustifyContent> for BoxPack {\n  fn from_standard(justify: &JustifyContent) -> Option<BoxPack> {\n    match justify {\n      JustifyContent::ContentDistribution(cd) => match cd {\n        ContentDistribution::SpaceBetween => Some(BoxPack::Justify),\n        _ => None,\n      },\n      JustifyContent::ContentPosition { overflow: None, value } => match value {\n        ContentPosition::Start | ContentPosition::FlexStart => Some(BoxPack::Start),\n        ContentPosition::End | ContentPosition::FlexEnd => Some(BoxPack::End),\n        ContentPosition::Center => Some(BoxPack::Center),\n      },\n      _ => None,\n    }\n  }\n}\n\nenum_property! {\n  /// A value for the legacy (prefixed) [box-lines](https://www.w3.org/TR/2009/WD-css3-flexbox-20090723/#multiple) property.\n  /// Equivalent to the `flex-wrap` property in the standard syntax.\n  pub enum BoxLines {\n    /// Items are laid out in a single line.\n    Single,\n    /// Items may wrap into multiple lines.\n    Multiple,\n  }\n}\n\nimpl FromStandard<FlexWrap> for BoxLines {\n  fn from_standard(wrap: &FlexWrap) -> Option<BoxLines> {\n    match wrap {\n      FlexWrap::NoWrap => Some(BoxLines::Single),\n      FlexWrap::Wrap => Some(BoxLines::Multiple),\n      _ => None,\n    }\n  }\n}\n\ntype BoxOrdinalGroup = CSSInteger;\nimpl FromStandard<CSSInteger> for BoxOrdinalGroup {\n  fn from_standard(order: &CSSInteger) -> Option<BoxOrdinalGroup> {\n    Some(*order)\n  }\n}\n\n// Old flex (2012): https://www.w3.org/TR/2012/WD-css3-flexbox-20120322/\n\nenum_property! {\n  /// A value for the legacy (prefixed) [flex-pack](https://www.w3.org/TR/2012/WD-css3-flexbox-20120322/#flex-pack) property.\n  /// Equivalent to the `justify-content` property in the standard syntax.\n  pub enum FlexPack {\n    /// Items are justified to the start.\n    Start,\n    /// Items are justified to the end.\n    End,\n    /// Items are centered.\n    Center,\n    /// Items are justified to the start and end.\n    Justify,\n    /// Items are distributed evenly, with half size spaces on either end.\n    Distribute,\n  }\n}\n\nimpl FromStandard<JustifyContent> for FlexPack {\n  fn from_standard(justify: &JustifyContent) -> Option<FlexPack> {\n    match justify {\n      JustifyContent::ContentDistribution(cd) => match cd {\n        ContentDistribution::SpaceBetween => Some(FlexPack::Justify),\n        ContentDistribution::SpaceAround => Some(FlexPack::Distribute),\n        _ => None,\n      },\n      JustifyContent::ContentPosition { overflow: None, value } => match value {\n        ContentPosition::Start | ContentPosition::FlexStart => Some(FlexPack::Start),\n        ContentPosition::End | ContentPosition::FlexEnd => Some(FlexPack::End),\n        ContentPosition::Center => Some(FlexPack::Center),\n      },\n      _ => None,\n    }\n  }\n}\n\n/// A value for the legacy (prefixed) [flex-align](https://www.w3.org/TR/2012/WD-css3-flexbox-20120322/#flex-align) property.\npub type FlexAlign = BoxAlign;\n\nenum_property! {\n  /// A value for the legacy (prefixed) [flex-item-align](https://www.w3.org/TR/2012/WD-css3-flexbox-20120322/#flex-align) property.\n  /// Equivalent to the `align-self` property in the standard syntax.\n  pub enum FlexItemAlign {\n    /// Equivalent to the value of `flex-align`.\n    Auto,\n    /// The item is aligned to the start.\n    Start,\n    /// The item is aligned to the end.\n    End,\n    /// The item is centered.\n    Center,\n    /// The item is aligned to the baseline.\n    Baseline,\n    /// The item is stretched.\n    Stretch,\n  }\n}\n\nimpl FromStandard<AlignSelf> for FlexItemAlign {\n  fn from_standard(justify: &AlignSelf) -> Option<FlexItemAlign> {\n    match justify {\n      AlignSelf::Auto => Some(FlexItemAlign::Auto),\n      AlignSelf::Stretch => Some(FlexItemAlign::Stretch),\n      AlignSelf::SelfPosition { overflow: None, value } => match value {\n        SelfPosition::Start | SelfPosition::FlexStart => Some(FlexItemAlign::Start),\n        SelfPosition::End | SelfPosition::FlexEnd => Some(FlexItemAlign::End),\n        SelfPosition::Center => Some(FlexItemAlign::Center),\n        _ => None,\n      },\n      _ => None,\n    }\n  }\n}\n\nenum_property! {\n  /// A value for the legacy (prefixed) [flex-line-pack](https://www.w3.org/TR/2012/WD-css3-flexbox-20120322/#flex-line-pack) property.\n  /// Equivalent to the `align-content` property in the standard syntax.\n  pub enum FlexLinePack {\n    /// Content is aligned to the start.\n    Start,\n    /// Content is aligned to the end.\n    End,\n    /// Content is centered.\n    Center,\n    /// Content is justified.\n    Justify,\n    /// Content is distributed evenly, with half size spaces on either end.\n    Distribute,\n    /// Content is stretched.\n    Stretch,\n  }\n}\n\nimpl FromStandard<AlignContent> for FlexLinePack {\n  fn from_standard(justify: &AlignContent) -> Option<FlexLinePack> {\n    match justify {\n      AlignContent::ContentDistribution(cd) => match cd {\n        ContentDistribution::SpaceBetween => Some(FlexLinePack::Justify),\n        ContentDistribution::SpaceAround => Some(FlexLinePack::Distribute),\n        ContentDistribution::Stretch => Some(FlexLinePack::Stretch),\n        _ => None,\n      },\n      AlignContent::ContentPosition { overflow: None, value } => match value {\n        ContentPosition::Start | ContentPosition::FlexStart => Some(FlexLinePack::Start),\n        ContentPosition::End | ContentPosition::FlexEnd => Some(FlexLinePack::End),\n        ContentPosition::Center => Some(FlexLinePack::Center),\n      },\n      _ => None,\n    }\n  }\n}\n\n#[derive(Default, Debug)]\npub(crate) struct FlexHandler {\n  direction: Option<(FlexDirection, VendorPrefix)>,\n  box_orient: Option<(BoxOrient, VendorPrefix)>,\n  box_direction: Option<(BoxDirection, VendorPrefix)>,\n  wrap: Option<(FlexWrap, VendorPrefix)>,\n  box_lines: Option<(BoxLines, VendorPrefix)>,\n  grow: Option<(CSSNumber, VendorPrefix)>,\n  box_flex: Option<(CSSNumber, VendorPrefix)>,\n  flex_positive: Option<(CSSNumber, VendorPrefix)>,\n  shrink: Option<(CSSNumber, VendorPrefix)>,\n  flex_negative: Option<(CSSNumber, VendorPrefix)>,\n  basis: Option<(LengthPercentageOrAuto, VendorPrefix)>,\n  preferred_size: Option<(LengthPercentageOrAuto, VendorPrefix)>,\n  order: Option<(CSSInteger, VendorPrefix)>,\n  box_ordinal_group: Option<(BoxOrdinalGroup, VendorPrefix)>,\n  flex_order: Option<(CSSInteger, VendorPrefix)>,\n  has_any: bool,\n}\n\nimpl<'i> PropertyHandler<'i> for FlexHandler {\n  fn handle_property(\n    &mut self,\n    property: &Property<'i>,\n    dest: &mut DeclarationList<'i>,\n    context: &mut PropertyHandlerContext<'i, '_>,\n  ) -> bool {\n    use Property::*;\n\n    macro_rules! maybe_flush {\n      ($prop: ident, $val: expr, $vp: ident) => {{\n        // If two vendor prefixes for the same property have different\n        // values, we need to flush what we have immediately to preserve order.\n        if let Some((val, prefixes)) = &self.$prop {\n          if val != $val && !prefixes.contains(*$vp) {\n            self.flush(dest, context);\n          }\n        }\n      }};\n    }\n\n    macro_rules! property {\n      ($prop: ident, $val: expr, $vp: ident) => {{\n        maybe_flush!($prop, $val, $vp);\n\n        // Otherwise, update the value and add the prefix.\n        if let Some((val, prefixes)) = &mut self.$prop {\n          *val = $val.clone();\n          *prefixes |= *$vp;\n        } else {\n          self.$prop = Some(($val.clone(), *$vp));\n          self.has_any = true;\n        }\n      }};\n    }\n\n    match property {\n      FlexDirection(val, vp) => {\n        if context.targets.browsers.is_some() {\n          self.box_direction = None;\n          self.box_orient = None;\n        }\n        property!(direction, val, vp);\n      }\n      BoxOrient(val, vp) => property!(box_orient, val, vp),\n      BoxDirection(val, vp) => property!(box_direction, val, vp),\n      FlexWrap(val, vp) => {\n        if context.targets.browsers.is_some() {\n          self.box_lines = None;\n        }\n        property!(wrap, val, vp);\n      }\n      BoxLines(val, vp) => property!(box_lines, val, vp),\n      FlexFlow(val, vp) => {\n        if context.targets.browsers.is_some() {\n          self.box_direction = None;\n          self.box_orient = None;\n        }\n        property!(direction, &val.direction, vp);\n        property!(wrap, &val.wrap, vp);\n      }\n      FlexGrow(val, vp) => {\n        if context.targets.browsers.is_some() {\n          self.box_flex = None;\n          self.flex_positive = None;\n        }\n        property!(grow, val, vp);\n      }\n      BoxFlex(val, vp) => property!(box_flex, val, vp),\n      FlexPositive(val, vp) => property!(flex_positive, val, vp),\n      FlexShrink(val, vp) => {\n        if context.targets.browsers.is_some() {\n          self.flex_negative = None;\n        }\n        property!(shrink, val, vp);\n      }\n      FlexNegative(val, vp) => property!(flex_negative, val, vp),\n      FlexBasis(val, vp) => {\n        if context.targets.browsers.is_some() {\n          self.preferred_size = None;\n        }\n        property!(basis, val, vp);\n      }\n      FlexPreferredSize(val, vp) => property!(preferred_size, val, vp),\n      Flex(val, vp) => {\n        if context.targets.browsers.is_some() {\n          self.box_flex = None;\n          self.flex_positive = None;\n          self.flex_negative = None;\n          self.preferred_size = None;\n        }\n        maybe_flush!(grow, &val.grow, vp);\n        maybe_flush!(shrink, &val.shrink, vp);\n        maybe_flush!(basis, &val.basis, vp);\n        property!(grow, &val.grow, vp);\n        property!(shrink, &val.shrink, vp);\n        property!(basis, &val.basis, vp);\n      }\n      Order(val, vp) => {\n        if context.targets.browsers.is_some() {\n          self.box_ordinal_group = None;\n          self.flex_order = None;\n        }\n        property!(order, val, vp);\n      }\n      BoxOrdinalGroup(val, vp) => property!(box_ordinal_group, val, vp),\n      FlexOrder(val, vp) => property!(flex_order, val, vp),\n      Unparsed(val) if is_flex_property(&val.property_id) => {\n        self.flush(dest, context);\n        dest.push(property.clone()) // TODO: prefix?\n      }\n      _ => return false,\n    }\n\n    true\n  }\n\n  fn finalize(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {\n    self.flush(dest, context);\n  }\n}\n\nimpl FlexHandler {\n  fn flush<'i>(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {\n    if !self.has_any {\n      return;\n    }\n\n    self.has_any = false;\n\n    let mut direction = std::mem::take(&mut self.direction);\n    let mut wrap = std::mem::take(&mut self.wrap);\n    let mut grow = std::mem::take(&mut self.grow);\n    let mut shrink = std::mem::take(&mut self.shrink);\n    let mut basis = std::mem::take(&mut self.basis);\n    let box_orient = std::mem::take(&mut self.box_orient);\n    let box_direction = std::mem::take(&mut self.box_direction);\n    let box_flex = std::mem::take(&mut self.box_flex);\n    let box_ordinal_group = std::mem::take(&mut self.box_ordinal_group);\n    let box_lines = std::mem::take(&mut self.box_lines);\n    let flex_positive = std::mem::take(&mut self.flex_positive);\n    let flex_negative = std::mem::take(&mut self.flex_negative);\n    let preferred_size = std::mem::take(&mut self.preferred_size);\n    let order = std::mem::take(&mut self.order);\n    let flex_order = std::mem::take(&mut self.flex_order);\n\n    macro_rules! single_property {\n      ($prop: ident, $key: ident $(, 2012: $prop_2012: ident )? $(, 2009: $prop_2009: ident )?) => {\n        if let Some((val, prefix)) = $key {\n          if !prefix.is_empty() {\n            let mut prefix = context.targets.prefixes(prefix, Feature::$prop);\n            if prefix.contains(VendorPrefix::None) {\n              $(\n                // 2009 spec, implemented by webkit and firefox.\n                if let Some(targets) = context.targets.browsers {\n                  let mut prefixes_2009 = VendorPrefix::empty();\n                  if is_flex_2009(targets) {\n                    prefixes_2009 |= VendorPrefix::WebKit;\n                  }\n                  if prefix.contains(VendorPrefix::Moz) {\n                    prefixes_2009 |= VendorPrefix::Moz;\n                  }\n                  if !prefixes_2009.is_empty() {\n                    if let Some(v) = $prop_2009::from_standard(&val) {\n                      dest.push(Property::$prop_2009(v, prefixes_2009));\n                    }\n                  }\n                }\n              )?\n            }\n\n            $(\n              let mut ms = true;\n              if prefix.contains(VendorPrefix::Ms) {\n                dest.push(Property::$prop_2012(val.clone(), VendorPrefix::Ms));\n                ms = false;\n              }\n              if !ms {\n                prefix.remove(VendorPrefix::Ms);\n              }\n            )?\n\n            // Firefox only implemented the 2009 spec prefixed.\n            prefix.remove(VendorPrefix::Moz);\n            dest.push(Property::$prop(val, prefix))\n          }\n        }\n      };\n    }\n\n    macro_rules! legacy_property {\n      ($prop: ident, $key: expr) => {\n        if let Some((val, prefix)) = $key {\n          if !prefix.is_empty() {\n            dest.push(Property::$prop(val, prefix))\n          }\n        }\n      };\n    }\n\n    // Legacy properties. These are only set if the final standard properties were unset.\n    legacy_property!(BoxOrient, box_orient);\n    legacy_property!(BoxDirection, box_direction);\n    legacy_property!(BoxOrdinalGroup, box_ordinal_group);\n    legacy_property!(BoxFlex, box_flex);\n    legacy_property!(BoxLines, box_lines);\n    legacy_property!(FlexPositive, flex_positive);\n    legacy_property!(FlexNegative, flex_negative);\n    legacy_property!(FlexPreferredSize, preferred_size.clone());\n    legacy_property!(FlexOrder, flex_order.clone());\n\n    if let Some((direction, _)) = direction {\n      if let Some(targets) = context.targets.browsers {\n        let prefixes = context.targets.prefixes(VendorPrefix::None, Feature::FlexDirection);\n        let mut prefixes_2009 = VendorPrefix::empty();\n        if is_flex_2009(targets) {\n          prefixes_2009 |= VendorPrefix::WebKit;\n        }\n        if prefixes.contains(VendorPrefix::Moz) {\n          prefixes_2009 |= VendorPrefix::Moz;\n        }\n        if !prefixes_2009.is_empty() {\n          let (orient, dir) = direction.to_2009();\n          dest.push(Property::BoxOrient(orient, prefixes_2009));\n          dest.push(Property::BoxDirection(dir, prefixes_2009));\n        }\n      }\n    }\n\n    if let (Some((direction, dir_prefix)), Some((wrap, wrap_prefix))) = (&mut direction, &mut wrap) {\n      let intersection = *dir_prefix & *wrap_prefix;\n      if !intersection.is_empty() {\n        let mut prefix = context.targets.prefixes(intersection, Feature::FlexFlow);\n        // Firefox only implemented the 2009 spec prefixed.\n        prefix.remove(VendorPrefix::Moz);\n        dest.push(Property::FlexFlow(\n          FlexFlow {\n            direction: *direction,\n            wrap: *wrap,\n          },\n          prefix,\n        ));\n        dir_prefix.remove(intersection);\n        wrap_prefix.remove(intersection);\n      }\n    }\n\n    single_property!(FlexDirection, direction);\n    single_property!(FlexWrap, wrap, 2009: BoxLines);\n\n    if let Some(targets) = context.targets.browsers {\n      if let Some((grow, _)) = grow {\n        let prefixes = context.targets.prefixes(VendorPrefix::None, Feature::FlexGrow);\n        let mut prefixes_2009 = VendorPrefix::empty();\n        if is_flex_2009(targets) {\n          prefixes_2009 |= VendorPrefix::WebKit;\n        }\n        if prefixes.contains(VendorPrefix::Moz) {\n          prefixes_2009 |= VendorPrefix::Moz;\n        }\n        if !prefixes_2009.is_empty() {\n          dest.push(Property::BoxFlex(grow, prefixes_2009));\n        }\n      }\n    }\n\n    if let (Some((grow, grow_prefix)), Some((shrink, shrink_prefix)), Some((basis, basis_prefix))) =\n      (&mut grow, &mut shrink, &mut basis)\n    {\n      let intersection = *grow_prefix & *shrink_prefix & *basis_prefix;\n      if !intersection.is_empty() {\n        let mut prefix = context.targets.prefixes(intersection, Feature::Flex);\n        // Firefox only implemented the 2009 spec prefixed.\n        prefix.remove(VendorPrefix::Moz);\n        dest.push(Property::Flex(\n          Flex {\n            grow: *grow,\n            shrink: *shrink,\n            basis: basis.clone(),\n          },\n          prefix,\n        ));\n        grow_prefix.remove(intersection);\n        shrink_prefix.remove(intersection);\n        basis_prefix.remove(intersection);\n      }\n    }\n\n    single_property!(FlexGrow, grow, 2012: FlexPositive);\n    single_property!(FlexShrink, shrink, 2012: FlexNegative);\n    single_property!(FlexBasis, basis, 2012: FlexPreferredSize);\n    single_property!(Order, order, 2012: FlexOrder, 2009: BoxOrdinalGroup);\n  }\n}\n\n#[inline]\nfn is_flex_property(property_id: &PropertyId) -> bool {\n  match property_id {\n    PropertyId::FlexDirection(_)\n    | PropertyId::BoxOrient(_)\n    | PropertyId::BoxDirection(_)\n    | PropertyId::FlexWrap(_)\n    | PropertyId::BoxLines(_)\n    | PropertyId::FlexFlow(_)\n    | PropertyId::FlexGrow(_)\n    | PropertyId::BoxFlex(_)\n    | PropertyId::FlexPositive(_)\n    | PropertyId::FlexShrink(_)\n    | PropertyId::FlexNegative(_)\n    | PropertyId::FlexBasis(_)\n    | PropertyId::FlexPreferredSize(_)\n    | PropertyId::Flex(_)\n    | PropertyId::Order(_)\n    | PropertyId::BoxOrdinalGroup(_)\n    | PropertyId::FlexOrder(_) => true,\n    _ => false,\n  }\n}\n"
  },
  {
    "path": "src/properties/font.rs",
    "content": "//! CSS properties related to fonts.\n\nuse std::collections::HashSet;\n\nuse super::{Property, PropertyId};\nuse crate::compat::Feature;\nuse crate::context::PropertyHandlerContext;\nuse crate::declaration::{DeclarationBlock, DeclarationList};\nuse crate::error::{ParserError, PrinterError};\nuse crate::macros::*;\nuse crate::printer::Printer;\nuse crate::targets::should_compile;\nuse crate::traits::{IsCompatible, Parse, PropertyHandler, Shorthand, ToCss};\nuse crate::values::length::LengthValue;\nuse crate::values::number::CSSNumber;\nuse crate::values::string::CowArcStr;\nuse crate::values::{angle::Angle, length::LengthPercentage, percentage::Percentage};\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse cssparser::*;\n\n/// A value for the [font-weight](https://www.w3.org/TR/css-fonts-4/#font-weight-prop) property.\n#[derive(Debug, Clone, PartialEq, Parse, ToCss)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum FontWeight {\n  /// An absolute font weight.\n  Absolute(AbsoluteFontWeight),\n  /// The `bolder` keyword.\n  Bolder,\n  /// The `lighter` keyword.\n  Lighter,\n}\n\nimpl Default for FontWeight {\n  fn default() -> FontWeight {\n    FontWeight::Absolute(AbsoluteFontWeight::default())\n  }\n}\n\nimpl IsCompatible for FontWeight {\n  fn is_compatible(&self, browsers: crate::targets::Browsers) -> bool {\n    match self {\n      FontWeight::Absolute(a) => a.is_compatible(browsers),\n      FontWeight::Bolder | FontWeight::Lighter => true,\n    }\n  }\n}\n\n/// An [absolute font weight](https://www.w3.org/TR/css-fonts-4/#font-weight-absolute-values),\n/// as used in the `font-weight` property.\n///\n/// See [FontWeight](FontWeight).\n#[derive(Debug, Clone, PartialEq, Parse)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub enum AbsoluteFontWeight {\n  /// An explicit weight.\n  Weight(CSSNumber),\n  /// Same as `400`.\n  Normal,\n  /// Same as `700`.\n  Bold,\n}\n\nimpl Default for AbsoluteFontWeight {\n  fn default() -> AbsoluteFontWeight {\n    AbsoluteFontWeight::Normal\n  }\n}\n\nimpl ToCss for AbsoluteFontWeight {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    use AbsoluteFontWeight::*;\n    match self {\n      Weight(val) => val.to_css(dest),\n      Normal => dest.write_str(if dest.minify { \"400\" } else { \"normal\" }),\n      Bold => dest.write_str(if dest.minify { \"700\" } else { \"bold\" }),\n    }\n  }\n}\n\nimpl IsCompatible for AbsoluteFontWeight {\n  fn is_compatible(&self, browsers: crate::targets::Browsers) -> bool {\n    match self {\n      // Older browsers only supported 100, 200, 300, ...900 rather than arbitrary values.\n      AbsoluteFontWeight::Weight(val) if !(*val >= 100.0 && *val <= 900.0 && *val % 100.0 == 0.0) => {\n        Feature::FontWeightNumber.is_compatible(browsers)\n      }\n      _ => true,\n    }\n  }\n}\n\nenum_property! {\n  /// An [absolute font size](https://www.w3.org/TR/css-fonts-3/#absolute-size-value),\n  /// as used in the `font-size` property.\n  ///\n  /// See [FontSize](FontSize).\n  #[allow(missing_docs)]\n  pub enum AbsoluteFontSize {\n    \"xx-small\": XXSmall,\n    \"x-small\": XSmall,\n    \"small\": Small,\n    \"medium\": Medium,\n    \"large\": Large,\n    \"x-large\": XLarge,\n    \"xx-large\": XXLarge,\n    \"xxx-large\": XXXLarge,\n  }\n}\n\nimpl IsCompatible for AbsoluteFontSize {\n  fn is_compatible(&self, browsers: crate::targets::Browsers) -> bool {\n    use AbsoluteFontSize::*;\n    match self {\n      XXXLarge => Feature::FontSizeXXXLarge.is_compatible(browsers),\n      _ => true,\n    }\n  }\n}\n\nenum_property! {\n  /// A [relative font size](https://www.w3.org/TR/css-fonts-3/#relative-size-value),\n  /// as used in the `font-size` property.\n  ///\n  /// See [FontSize](FontSize).\n  #[allow(missing_docs)]\n  pub enum RelativeFontSize {\n    Smaller,\n    Larger,\n  }\n}\n\n/// A value for the [font-size](https://www.w3.org/TR/css-fonts-4/#font-size-prop) property.\n#[derive(Debug, Clone, PartialEq, Parse, ToCss)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum FontSize {\n  /// An explicit size.\n  Length(LengthPercentage),\n  /// An absolute font size keyword.\n  Absolute(AbsoluteFontSize),\n  /// A relative font size keyword.\n  Relative(RelativeFontSize),\n}\n\nimpl IsCompatible for FontSize {\n  fn is_compatible(&self, browsers: crate::targets::Browsers) -> bool {\n    match self {\n      FontSize::Length(LengthPercentage::Dimension(LengthValue::Rem(..))) => {\n        Feature::FontSizeRem.is_compatible(browsers)\n      }\n      FontSize::Length(l) => l.is_compatible(browsers),\n      FontSize::Absolute(a) => a.is_compatible(browsers),\n      FontSize::Relative(..) => true,\n    }\n  }\n}\n\nenum_property! {\n  /// A [font stretch keyword](https://www.w3.org/TR/css-fonts-4/#font-stretch-prop),\n  /// as used in the `font-stretch` property.\n  ///\n  /// See [FontStretch](FontStretch).\n  pub enum FontStretchKeyword {\n    /// 100%\n    \"normal\": Normal,\n    /// 50%\n    \"ultra-condensed\": UltraCondensed,\n    /// 62.5%\n    \"extra-condensed\": ExtraCondensed,\n    /// 75%\n    \"condensed\": Condensed,\n    /// 87.5%\n    \"semi-condensed\": SemiCondensed,\n    /// 112.5%\n    \"semi-expanded\": SemiExpanded,\n    /// 125%\n    \"expanded\": Expanded,\n    /// 150%\n    \"extra-expanded\": ExtraExpanded,\n    /// 200%\n    \"ultra-expanded\": UltraExpanded,\n  }\n}\n\nimpl Default for FontStretchKeyword {\n  fn default() -> FontStretchKeyword {\n    FontStretchKeyword::Normal\n  }\n}\n\nimpl Into<Percentage> for &FontStretchKeyword {\n  fn into(self) -> Percentage {\n    use FontStretchKeyword::*;\n    let val = match self {\n      UltraCondensed => 0.5,\n      ExtraCondensed => 0.625,\n      Condensed => 0.75,\n      SemiCondensed => 0.875,\n      Normal => 1.0,\n      SemiExpanded => 1.125,\n      Expanded => 1.25,\n      ExtraExpanded => 1.5,\n      UltraExpanded => 2.0,\n    };\n    Percentage(val)\n  }\n}\n\n/// A value for the [font-stretch](https://www.w3.org/TR/css-fonts-4/#font-stretch-prop) property.\n#[derive(Debug, Clone, PartialEq, Parse)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum FontStretch {\n  /// A font stretch keyword.\n  Keyword(FontStretchKeyword),\n  /// A percentage.\n  Percentage(Percentage),\n}\n\nimpl Default for FontStretch {\n  fn default() -> FontStretch {\n    FontStretch::Keyword(FontStretchKeyword::default())\n  }\n}\n\nimpl Into<Percentage> for &FontStretch {\n  fn into(self) -> Percentage {\n    match self {\n      FontStretch::Percentage(val) => val.clone(),\n      FontStretch::Keyword(keyword) => keyword.into(),\n    }\n  }\n}\n\nimpl ToCss for FontStretch {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    if dest.minify {\n      let percentage: Percentage = self.into();\n      return percentage.to_css(dest);\n    }\n\n    match self {\n      FontStretch::Percentage(val) => val.to_css(dest),\n      FontStretch::Keyword(val) => val.to_css(dest),\n    }\n  }\n}\n\nimpl IsCompatible for FontStretch {\n  fn is_compatible(&self, browsers: crate::targets::Browsers) -> bool {\n    match self {\n      FontStretch::Percentage(..) => Feature::FontStretchPercentage.is_compatible(browsers),\n      FontStretch::Keyword(..) => true,\n    }\n  }\n}\n\nenum_property! {\n  /// A [generic font family](https://www.w3.org/TR/css-fonts-4/#generic-font-families) name,\n  /// as used in the `font-family` property.\n  ///\n  /// See [FontFamily](FontFamily).\n  #[allow(missing_docs)]\n  #[derive(Eq, Hash)]\n  pub enum GenericFontFamily {\n    \"serif\": Serif,\n    \"sans-serif\": SansSerif,\n    \"cursive\": Cursive,\n    \"fantasy\": Fantasy,\n    \"monospace\": Monospace,\n    \"system-ui\": SystemUI,\n    \"emoji\": Emoji,\n    \"math\": Math,\n    \"fangsong\": FangSong,\n    \"ui-serif\": UISerif,\n    \"ui-sans-serif\": UISansSerif,\n    \"ui-monospace\": UIMonospace,\n    \"ui-rounded\": UIRounded,\n\n    // CSS wide keywords. These must be parsed as identifiers so they\n    // don't get serialized as strings.\n    // https://www.w3.org/TR/css-values-4/#common-keywords\n    \"initial\": Initial,\n    \"inherit\": Inherit,\n    \"unset\": Unset,\n    // Default is also reserved by the <custom-ident> type.\n    // https://www.w3.org/TR/css-values-4/#custom-idents\n    \"default\": Default,\n\n    // CSS defaulting keywords\n    // https://drafts.csswg.org/css-cascade-5/#defaulting-keywords\n    \"revert\": Revert,\n    \"revert-layer\": RevertLayer,\n  }\n}\n\nimpl IsCompatible for GenericFontFamily {\n  fn is_compatible(&self, browsers: crate::targets::Browsers) -> bool {\n    use GenericFontFamily::*;\n    match self {\n      SystemUI => Feature::FontFamilySystemUi.is_compatible(browsers),\n      UISerif | UISansSerif | UIMonospace | UIRounded => Feature::ExtendedSystemFonts.is_compatible(browsers),\n      _ => true,\n    }\n  }\n}\n\n/// A value for the [font-family](https://www.w3.org/TR/css-fonts-4/#font-family-prop) property.\n#[derive(Debug, Clone, PartialEq, Eq, Hash)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize), serde(untagged))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub enum FontFamily<'i> {\n  /// A generic family name.\n  Generic(GenericFontFamily),\n  /// A custom family name.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  FamilyName(FamilyName<'i>),\n}\n\nimpl<'i> Parse<'i> for FontFamily<'i> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    if let Ok(value) = input.try_parse(GenericFontFamily::parse) {\n      return Ok(FontFamily::Generic(value));\n    }\n\n    let family = FamilyName::parse(input)?;\n    Ok(FontFamily::FamilyName(family))\n  }\n}\n\nimpl<'i> ToCss for FontFamily<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      FontFamily::Generic(val) => val.to_css(dest),\n      FontFamily::FamilyName(val) => val.to_css(dest),\n    }\n  }\n}\n\n/// A font [family name](https://drafts.csswg.org/css-fonts/#family-name-syntax).\n#[derive(Debug, Clone, PartialEq, Eq, Hash)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize), serde(transparent))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct FamilyName<'i>(#[cfg_attr(feature = \"serde\", serde(borrow))] CowArcStr<'i>);\n\nimpl<'i> Parse<'i> for FamilyName<'i> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    if let Ok(value) = input.try_parse(|i| i.expect_string_cloned()) {\n      return Ok(FamilyName(value.into()));\n    }\n\n    let value: CowArcStr<'i> = input.expect_ident()?.into();\n    let mut string = None;\n    while let Ok(ident) = input.try_parse(|i| i.expect_ident_cloned()) {\n      if string.is_none() {\n        string = Some(value.to_string());\n      }\n\n      if let Some(string) = &mut string {\n        string.push(' ');\n        string.push_str(&ident);\n      }\n    }\n\n    let value = if let Some(string) = string {\n      string.into()\n    } else {\n      value\n    };\n\n    Ok(FamilyName(value))\n  }\n}\n\nimpl<'i> ToCss for FamilyName<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    // Generic family names such as sans-serif must be quoted if parsed as a string.\n    // CSS wide keywords, as well as \"default\", must also be quoted.\n    // https://www.w3.org/TR/css-fonts-4/#family-name-syntax\n    let val = &self.0;\n    if !val.is_empty() && !GenericFontFamily::parse_string(val).is_ok() {\n      // Family names with two or more consecutive spaces must be quoted to preserve the spaces.\n      let needs_quotes = val.contains(\"  \");\n      if needs_quotes {\n        serialize_string(&val, dest)?;\n        return Ok(());\n      }\n\n      let mut id = String::new();\n      let mut first = true;\n      for slice in val.split(' ') {\n        if first {\n          first = false;\n        } else {\n          id.push(' ');\n        }\n        serialize_identifier(slice, &mut id)?;\n      }\n      if id.len() < val.len() + 2 {\n        return dest.write_str(&id);\n      }\n    }\n    serialize_string(&val, dest)?;\n    Ok(())\n  }\n}\n\nimpl IsCompatible for FontFamily<'_> {\n  fn is_compatible(&self, browsers: crate::targets::Browsers) -> bool {\n    match self {\n      FontFamily::Generic(g) => g.is_compatible(browsers),\n      FontFamily::FamilyName(..) => true,\n    }\n  }\n}\n\n/// A value for the [font-style](https://www.w3.org/TR/css-fonts-4/#font-style-prop) property.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum FontStyle {\n  /// Normal font style.\n  Normal,\n  /// Italic font style.\n  Italic,\n  /// Oblique font style, with a custom angle.\n  Oblique(#[cfg_attr(feature = \"serde\", serde(default = \"FontStyle::default_oblique_angle\"))] Angle),\n}\n\nimpl Default for FontStyle {\n  fn default() -> FontStyle {\n    FontStyle::Normal\n  }\n}\n\nimpl FontStyle {\n  #[inline]\n  pub(crate) fn default_oblique_angle() -> Angle {\n    Angle::Deg(14.0)\n  }\n}\n\nimpl<'i> Parse<'i> for FontStyle {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let location = input.current_source_location();\n    let ident = input.expect_ident()?;\n    match_ignore_ascii_case! { &*ident,\n      \"normal\" => Ok(FontStyle::Normal),\n      \"italic\" => Ok(FontStyle::Italic),\n      \"oblique\" => {\n        let angle = input.try_parse(Angle::parse).unwrap_or(FontStyle::default_oblique_angle());\n        Ok(FontStyle::Oblique(angle))\n      },\n      _ => Err(location.new_unexpected_token_error(\n        cssparser::Token::Ident(ident.clone())\n      ))\n    }\n  }\n}\n\nimpl ToCss for FontStyle {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      FontStyle::Normal => dest.write_str(\"normal\"),\n      FontStyle::Italic => dest.write_str(\"italic\"),\n      FontStyle::Oblique(angle) => {\n        dest.write_str(\"oblique\")?;\n        if *angle != FontStyle::default_oblique_angle() {\n          dest.write_char(' ')?;\n          angle.to_css(dest)?;\n        }\n        Ok(())\n      }\n    }\n  }\n}\n\nimpl IsCompatible for FontStyle {\n  fn is_compatible(&self, browsers: crate::targets::Browsers) -> bool {\n    match self {\n      FontStyle::Oblique(angle) if *angle != FontStyle::default_oblique_angle() => {\n        Feature::FontStyleObliqueAngle.is_compatible(browsers)\n      }\n      FontStyle::Normal | FontStyle::Italic | FontStyle::Oblique(..) => true,\n    }\n  }\n}\n\nenum_property! {\n  /// A value for the [font-variant-caps](https://www.w3.org/TR/css-fonts-4/#font-variant-caps-prop) property.\n  pub enum FontVariantCaps {\n    /// No special capitalization features are applied.\n    Normal,\n    /// The small capitals feature is used for lower case letters.\n    SmallCaps,\n    /// Small capitals are used for both upper and lower case letters.\n    AllSmallCaps,\n    /// Petite capitals are used.\n    PetiteCaps,\n    /// Petite capitals are used for both upper and lower case letters.\n    AllPetiteCaps,\n    /// Enables display of mixture of small capitals for uppercase letters with normal lowercase letters.\n    Unicase,\n    /// Uses titling capitals.\n    TitlingCaps,\n  }\n}\n\nimpl Default for FontVariantCaps {\n  fn default() -> FontVariantCaps {\n    FontVariantCaps::Normal\n  }\n}\n\nimpl FontVariantCaps {\n  fn is_css2(&self) -> bool {\n    matches!(self, FontVariantCaps::Normal | FontVariantCaps::SmallCaps)\n  }\n\n  fn parse_css2<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let value = Self::parse(input)?;\n    if !value.is_css2() {\n      return Err(input.new_custom_error(ParserError::InvalidValue));\n    }\n    Ok(value)\n  }\n}\n\nimpl IsCompatible for FontVariantCaps {\n  fn is_compatible(&self, _browsers: crate::targets::Browsers) -> bool {\n    true\n  }\n}\n\n/// A value for the [line-height](https://www.w3.org/TR/2020/WD-css-inline-3-20200827/#propdef-line-height) property.\n#[derive(Debug, Clone, PartialEq, Parse, ToCss)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum LineHeight {\n  /// The UA sets the line height based on the font.\n  Normal,\n  /// A multiple of the element's font size.\n  Number(CSSNumber),\n  /// An explicit height.\n  Length(LengthPercentage),\n}\n\nimpl Default for LineHeight {\n  fn default() -> LineHeight {\n    LineHeight::Normal\n  }\n}\n\nimpl IsCompatible for LineHeight {\n  fn is_compatible(&self, browsers: crate::targets::Browsers) -> bool {\n    match self {\n      LineHeight::Length(l) => l.is_compatible(browsers),\n      LineHeight::Normal | LineHeight::Number(..) => true,\n    }\n  }\n}\n\nenum_property! {\n  /// A keyword for the [vertical align](https://drafts.csswg.org/css2/#propdef-vertical-align) property.\n  pub enum VerticalAlignKeyword {\n    /// Align the baseline of the box with the baseline of the parent box.\n    Baseline,\n    /// Lower the baseline of the box to the proper position for subscripts of the parent’s box.\n    Sub,\n    /// Raise the baseline of the box to the proper position for superscripts of the parent’s box.\n    Super,\n    /// Align the top of the aligned subtree with the top of the line box.\n    Top,\n    /// Align the top of the box with the top of the parent’s content area.\n    TextTop,\n    /// Align the vertical midpoint of the box with the baseline of the parent box plus half the x-height of the parent.\n    Middle,\n    /// Align the bottom of the aligned subtree with the bottom of the line box.\n    Bottom,\n    /// Align the bottom of the box with the bottom of the parent’s content area.\n    TextBottom,\n  }\n}\n\n/// A value for the [vertical align](https://drafts.csswg.org/css2/#propdef-vertical-align) property.\n// TODO: there is a more extensive spec in CSS3 but it doesn't seem any browser implements it? https://www.w3.org/TR/css-inline-3/#transverse-alignment\n#[derive(Debug, Clone, PartialEq, Parse, ToCss)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum VerticalAlign {\n  /// A vertical align keyword.\n  Keyword(VerticalAlignKeyword),\n  /// An explicit length.\n  Length(LengthPercentage),\n}\n\ndefine_shorthand! {\n  /// A value for the [font](https://www.w3.org/TR/css-fonts-4/#font-prop) shorthand property.\n  pub struct Font<'i> {\n    /// The font family.\n    #[cfg_attr(feature = \"serde\", serde(borrow))]\n    family: FontFamily(Vec<FontFamily<'i>>),\n    /// The font size.\n    size: FontSize(FontSize),\n    /// The font style.\n    style: FontStyle(FontStyle),\n    /// The font weight.\n    weight: FontWeight(FontWeight),\n    /// The font stretch.\n    stretch: FontStretch(FontStretch),\n    /// The line height.\n    line_height: LineHeight(LineHeight),\n    /// How the text should be capitalized. Only CSS 2.1 values are supported.\n    variant_caps: FontVariantCaps(FontVariantCaps),\n  }\n}\n\nimpl<'i> Parse<'i> for Font<'i> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let mut style = None;\n    let mut weight = None;\n    let mut stretch = None;\n    let size;\n    let mut variant_caps = None;\n    let mut count = 0;\n\n    loop {\n      // Skip \"normal\" since it is valid for several properties, but we don't know which ones it will be used for yet.\n      if input.try_parse(|input| input.expect_ident_matching(\"normal\")).is_ok() {\n        count += 1;\n        continue;\n      }\n      if style.is_none() {\n        if let Ok(value) = input.try_parse(FontStyle::parse) {\n          style = Some(value);\n          count += 1;\n          continue;\n        }\n      }\n      if weight.is_none() {\n        if let Ok(value) = input.try_parse(FontWeight::parse) {\n          weight = Some(value);\n          count += 1;\n          continue;\n        }\n      }\n      if variant_caps.is_none() {\n        if let Ok(value) = input.try_parse(FontVariantCaps::parse_css2) {\n          variant_caps = Some(value);\n          count += 1;\n          continue;\n        }\n      }\n\n      if stretch.is_none() {\n        if let Ok(value) = input.try_parse(FontStretchKeyword::parse) {\n          stretch = Some(FontStretch::Keyword(value));\n          count += 1;\n          continue;\n        }\n      }\n      size = Some(FontSize::parse(input)?);\n      break;\n    }\n\n    if count > 4 {\n      return Err(input.new_custom_error(ParserError::InvalidDeclaration));\n    }\n\n    let size = match size {\n      Some(s) => s,\n      None => return Err(input.new_custom_error(ParserError::InvalidDeclaration)),\n    };\n\n    let line_height = if input.try_parse(|input| input.expect_delim('/')).is_ok() {\n      Some(LineHeight::parse(input)?)\n    } else {\n      None\n    };\n\n    let family = input.parse_comma_separated(FontFamily::parse)?;\n    Ok(Font {\n      family,\n      size,\n      style: style.unwrap_or_default(),\n      weight: weight.unwrap_or_default(),\n      stretch: stretch.unwrap_or_default(),\n      line_height: line_height.unwrap_or_default(),\n      variant_caps: variant_caps.unwrap_or_default(),\n    })\n  }\n}\n\nimpl<'i> ToCss for Font<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    if self.style != FontStyle::default() {\n      self.style.to_css(dest)?;\n      dest.write_char(' ')?;\n    }\n\n    if self.variant_caps != FontVariantCaps::default() {\n      self.variant_caps.to_css(dest)?;\n      dest.write_char(' ')?;\n    }\n\n    if self.weight != FontWeight::default() {\n      self.weight.to_css(dest)?;\n      dest.write_char(' ')?;\n    }\n\n    if self.stretch != FontStretch::default() {\n      self.stretch.to_css(dest)?;\n      dest.write_char(' ')?;\n    }\n\n    self.size.to_css(dest)?;\n\n    if self.line_height != LineHeight::default() {\n      dest.delim('/', true)?;\n      self.line_height.to_css(dest)?;\n    }\n\n    dest.write_char(' ')?;\n\n    let len = self.family.len();\n    for (idx, val) in self.family.iter().enumerate() {\n      val.to_css(dest)?;\n      if idx < len - 1 {\n        dest.delim(',', false)?;\n      }\n    }\n\n    Ok(())\n  }\n}\n\nproperty_bitflags! {\n  #[derive(Default, Debug)]\n  struct FontProperty: u8 {\n    const FontFamily = 1 << 0;\n    const FontSize = 1 << 1;\n    const FontStyle = 1 << 2;\n    const FontWeight = 1 << 3;\n    const FontStretch = 1 << 4;\n    const LineHeight = 1 << 5;\n    const FontVariantCaps = 1 << 6;\n    const Font = Self::FontFamily.bits() | Self::FontSize.bits() | Self::FontStyle.bits() | Self::FontWeight.bits() | Self::FontStretch.bits() | Self::LineHeight.bits() | Self::FontVariantCaps.bits();\n  }\n}\n\n#[derive(Default, Debug)]\npub(crate) struct FontHandler<'i> {\n  family: Option<Vec<FontFamily<'i>>>,\n  size: Option<FontSize>,\n  style: Option<FontStyle>,\n  weight: Option<FontWeight>,\n  stretch: Option<FontStretch>,\n  line_height: Option<LineHeight>,\n  variant_caps: Option<FontVariantCaps>,\n  flushed_properties: FontProperty,\n  has_any: bool,\n}\n\nimpl<'i> PropertyHandler<'i> for FontHandler<'i> {\n  fn handle_property(\n    &mut self,\n    property: &Property<'i>,\n    dest: &mut DeclarationList<'i>,\n    context: &mut PropertyHandlerContext<'i, '_>,\n  ) -> bool {\n    use Property::*;\n\n    macro_rules! flush {\n      ($prop: ident, $val: expr) => {{\n        if self.$prop.is_some() && self.$prop.as_ref().unwrap() != $val && matches!(context.targets.browsers, Some(targets) if !$val.is_compatible(targets)) {\n          self.flush(dest, context);\n        }\n      }};\n    }\n\n    macro_rules! property {\n      ($prop: ident, $val: ident) => {{\n        flush!($prop, $val);\n        self.$prop = Some($val.clone());\n        self.has_any = true;\n      }};\n    }\n\n    match property {\n      FontFamily(val) => property!(family, val),\n      FontSize(val) => property!(size, val),\n      FontStyle(val) => property!(style, val),\n      FontWeight(val) => property!(weight, val),\n      FontStretch(val) => property!(stretch, val),\n      FontVariantCaps(val) => property!(variant_caps, val),\n      LineHeight(val) => property!(line_height, val),\n      Font(val) => {\n        flush!(family, &val.family);\n        flush!(size, &val.size);\n        flush!(style, &val.style);\n        flush!(weight, &val.weight);\n        flush!(stretch, &val.stretch);\n        flush!(line_height, &val.line_height);\n        flush!(variant_caps, &val.variant_caps);\n        self.family = Some(val.family.clone());\n        self.size = Some(val.size.clone());\n        self.style = Some(val.style.clone());\n        self.weight = Some(val.weight.clone());\n        self.stretch = Some(val.stretch.clone());\n        self.line_height = Some(val.line_height.clone());\n        self.variant_caps = Some(val.variant_caps.clone());\n        self.has_any = true;\n        // TODO: reset other properties\n      }\n      Unparsed(val) if is_font_property(&val.property_id) => {\n        self.flush(dest, context);\n        self\n          .flushed_properties\n          .insert(FontProperty::try_from(&val.property_id).unwrap());\n        dest.push(property.clone());\n      }\n      _ => return false,\n    }\n\n    true\n  }\n\n  fn finalize(&mut self, decls: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {\n    self.flush(decls, context);\n    self.flushed_properties = FontProperty::empty();\n  }\n}\n\nimpl<'i> FontHandler<'i> {\n  fn flush(&mut self, decls: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {\n    if !self.has_any {\n      return;\n    }\n\n    self.has_any = false;\n\n    macro_rules! push {\n      ($prop: ident, $val: expr) => {\n        decls.push(Property::$prop($val));\n        self.flushed_properties.insert(FontProperty::$prop);\n      };\n    }\n\n    let mut family = std::mem::take(&mut self.family);\n    if !self.flushed_properties.contains(FontProperty::FontFamily) {\n      family = compatible_font_family(family, !should_compile!(context.targets, FontFamilySystemUi));\n    }\n    let size = std::mem::take(&mut self.size);\n    let style = std::mem::take(&mut self.style);\n    let weight = std::mem::take(&mut self.weight);\n    let stretch = std::mem::take(&mut self.stretch);\n    let line_height = std::mem::take(&mut self.line_height);\n    let variant_caps = std::mem::take(&mut self.variant_caps);\n\n    if let Some(family) = &mut family {\n      if family.len() > 1 {\n        // Dedupe.\n        let mut seen = HashSet::new();\n        family.retain(|f| seen.insert(f.clone()));\n      }\n    }\n\n    if family.is_some()\n      && size.is_some()\n      && style.is_some()\n      && weight.is_some()\n      && stretch.is_some()\n      && line_height.is_some()\n      && variant_caps.is_some()\n    {\n      let caps = variant_caps.unwrap();\n      push!(\n        Font,\n        Font {\n          family: family.unwrap(),\n          size: size.unwrap(),\n          style: style.unwrap(),\n          weight: weight.unwrap(),\n          stretch: stretch.unwrap(),\n          line_height: line_height.unwrap(),\n          variant_caps: if caps.is_css2() {\n            caps\n          } else {\n            FontVariantCaps::default()\n          },\n        }\n      );\n\n      // The `font` property only accepts CSS 2.1 values for font-variant caps.\n      // If we have a CSS 3+ value, we need to add a separate property.\n      if !caps.is_css2() {\n        push!(FontVariantCaps, variant_caps.unwrap());\n      }\n    } else {\n      if let Some(val) = family {\n        push!(FontFamily, val);\n      }\n\n      if let Some(val) = size {\n        push!(FontSize, val);\n      }\n\n      if let Some(val) = style {\n        push!(FontStyle, val);\n      }\n\n      if let Some(val) = variant_caps {\n        push!(FontVariantCaps, val);\n      }\n\n      if let Some(val) = weight {\n        push!(FontWeight, val);\n      }\n\n      if let Some(val) = stretch {\n        push!(FontStretch, val);\n      }\n\n      if let Some(val) = line_height {\n        push!(LineHeight, val);\n      }\n    }\n  }\n}\n\nconst SYSTEM_UI: FontFamily = FontFamily::Generic(GenericFontFamily::SystemUI);\n\nconst DEFAULT_SYSTEM_FONTS: &[&str] = &[\n  // #1: Supported as the '-apple-system' value (macOS, Safari >= 9.2 < 11, Firefox >= 43)\n  \"-apple-system\",\n  // #2: Supported as the 'BlinkMacSystemFont' value (macOS, Chrome < 56)\n  \"BlinkMacSystemFont\",\n  \"Segoe UI\",  // Windows >= Vista\n  \"Roboto\",    // Android >= 4\n  \"Noto Sans\", // Plasma >= 5.5\n  \"Ubuntu\",    // Ubuntu >= 10.10\n  \"Cantarell\", // GNOME >= 3\n  \"Helvetica Neue\",\n];\n\n/// [`system-ui`](https://www.w3.org/TR/css-fonts-4/#system-ui-def) is a special generic font family\n/// It is platform dependent but if not supported by the target will simply be ignored\n/// This list is an attempt at providing that support\n#[inline]\nfn compatible_font_family(mut family: Option<Vec<FontFamily>>, is_supported: bool) -> Option<Vec<FontFamily>> {\n  if is_supported {\n    return family;\n  }\n\n  if let Some(families) = &mut family {\n    if let Some(position) = families.iter().position(|v| *v == SYSTEM_UI) {\n      families.splice(\n        (position + 1)..(position + 1),\n        DEFAULT_SYSTEM_FONTS\n          .iter()\n          .map(|name| FontFamily::FamilyName(FamilyName(CowArcStr::from(*name)))),\n      );\n    }\n  }\n\n  return family;\n}\n\n#[inline]\nfn is_font_property(property_id: &PropertyId) -> bool {\n  match property_id {\n    PropertyId::FontFamily\n    | PropertyId::FontSize\n    | PropertyId::FontStyle\n    | PropertyId::FontWeight\n    | PropertyId::FontStretch\n    | PropertyId::FontVariantCaps\n    | PropertyId::LineHeight\n    | PropertyId::Font => true,\n    _ => false,\n  }\n}\n"
  },
  {
    "path": "src/properties/grid.rs",
    "content": "//! CSS properties related to grid layout.\n\n#![allow(non_upper_case_globals)]\n\nuse crate::context::PropertyHandlerContext;\nuse crate::declaration::{DeclarationBlock, DeclarationList};\nuse crate::error::{Error, ErrorLocation, ParserError, PrinterError, PrinterErrorKind};\nuse crate::macros::{define_shorthand, impl_shorthand};\nuse crate::printer::Printer;\nuse crate::properties::{Property, PropertyId};\nuse crate::traits::{Parse, PropertyHandler, Shorthand, ToCss};\nuse crate::values::ident::CustomIdent;\nuse crate::values::length::serialize_dimension;\nuse crate::values::number::{CSSInteger, CSSNumber};\nuse crate::values::{ident::CustomIdentList, length::LengthPercentage};\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse bitflags::bitflags;\nuse cssparser::*;\nuse smallvec::SmallVec;\n\n#[cfg(feature = \"serde\")]\nuse crate::serialization::ValueWrapper;\n\n/// A [track sizing](https://drafts.csswg.org/css-grid-2/#track-sizing) value\n/// for the `grid-template-rows` and `grid-template-columns` properties.\n#[derive(Debug, Clone, PartialEq, Parse, ToCss)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub enum TrackSizing<'i> {\n  /// No explicit grid tracks.\n  None,\n  /// A list of grid tracks.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  TrackList(TrackList<'i>),\n}\n\n/// A [`<track-list>`](https://drafts.csswg.org/css-grid-2/#typedef-track-list) value,\n/// as used in the `grid-template-rows` and `grid-template-columns` properties.\n///\n/// See [TrackSizing](TrackSizing).\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"camelCase\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct TrackList<'i> {\n  /// A list of line names.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  pub line_names: Vec<CustomIdentList<'i>>,\n  /// A list of grid track items.\n  pub items: Vec<TrackListItem<'i>>,\n}\n\n/// Either a track size or `repeat()` function.\n///\n/// See [TrackList](TrackList).\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub enum TrackListItem<'i> {\n  /// A track size.\n  TrackSize(TrackSize),\n  /// A `repeat()` function.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  TrackRepeat(TrackRepeat<'i>),\n}\n\n/// A [`<track-size>`](https://drafts.csswg.org/css-grid-2/#typedef-track-size) value,\n/// as used in the `grid-template-rows` and `grid-template-columns` properties.\n///\n/// See [TrackListItem](TrackListItem).\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum TrackSize {\n  /// An explicit track breadth.\n  #[cfg_attr(feature = \"serde\", serde(with = \"ValueWrapper::<TrackBreadth>\"))]\n  TrackBreadth(TrackBreadth),\n  /// The `minmax()` function.\n  MinMax {\n    /// The minimum value.\n    min: TrackBreadth,\n    /// The maximum value.\n    max: TrackBreadth,\n  },\n  /// The `fit-content()` function.\n  #[cfg_attr(feature = \"serde\", serde(with = \"ValueWrapper::<LengthPercentage>\"))]\n  FitContent(LengthPercentage),\n}\n\nimpl Default for TrackSize {\n  fn default() -> TrackSize {\n    TrackSize::TrackBreadth(TrackBreadth::Auto)\n  }\n}\n\n/// A [track size list](https://drafts.csswg.org/css-grid-2/#auto-tracks), as used\n/// in the `grid-auto-rows` and `grid-auto-columns` properties.\n#[derive(Debug, Clone, PartialEq, Default)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize), serde(transparent))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub struct TrackSizeList(pub SmallVec<[TrackSize; 1]>);\n\n/// A [`<track-breadth>`](https://drafts.csswg.org/css-grid-2/#typedef-track-breadth) value.\n///\n/// See [TrackSize](TrackSize).\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum TrackBreadth {\n  /// An explicit length.\n  Length(LengthPercentage),\n  /// A flex factor.\n  Flex(CSSNumber),\n  /// The `min-content` keyword.\n  MinContent,\n  /// The `max-content` keyword.\n  MaxContent,\n  /// The `auto` keyword.\n  Auto,\n}\n\n/// A [`<track-repeat>`](https://drafts.csswg.org/css-grid-2/#typedef-track-repeat) value,\n/// representing the `repeat()` function in a track list.\n///\n/// See [TrackListItem](TrackListItem).\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"camelCase\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct TrackRepeat<'i> {\n  /// The repeat count.\n  pub count: RepeatCount,\n  /// The line names to repeat.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  pub line_names: Vec<CustomIdentList<'i>>,\n  /// The track sizes to repeat.\n  pub track_sizes: Vec<TrackSize>,\n}\n\n/// A [`<repeat-count>`](https://drafts.csswg.org/css-grid-2/#typedef-track-repeat) value,\n/// used in the `repeat()` function.\n///\n/// See [TrackRepeat](TrackRepeat).\n#[derive(Debug, Clone, PartialEq, Parse, ToCss)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum RepeatCount {\n  /// The number of times to repeat.\n  Number(CSSInteger),\n  /// The `auto-fill` keyword.\n  AutoFill,\n  /// The `auto-fit` keyword.\n  AutoFit,\n}\n\nimpl<'i> Parse<'i> for TrackSize {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    if let Ok(breadth) = input.try_parse(TrackBreadth::parse) {\n      return Ok(TrackSize::TrackBreadth(breadth));\n    }\n\n    if input.try_parse(|input| input.expect_function_matching(\"minmax\")).is_ok() {\n      return input.parse_nested_block(|input| {\n        let min = TrackBreadth::parse_internal(input, false)?;\n        input.expect_comma()?;\n        Ok(TrackSize::MinMax {\n          min,\n          max: TrackBreadth::parse(input)?,\n        })\n      });\n    }\n\n    input.expect_function_matching(\"fit-content\")?;\n    let len = input.parse_nested_block(LengthPercentage::parse)?;\n    Ok(TrackSize::FitContent(len))\n  }\n}\n\nimpl ToCss for TrackSize {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      TrackSize::TrackBreadth(breadth) => breadth.to_css(dest),\n      TrackSize::MinMax { min, max } => {\n        dest.write_str(\"minmax(\")?;\n        min.to_css(dest)?;\n        dest.delim(',', false)?;\n        max.to_css(dest)?;\n        dest.write_char(')')\n      }\n      TrackSize::FitContent(len) => {\n        dest.write_str(\"fit-content(\")?;\n        len.to_css(dest)?;\n        dest.write_char(')')\n      }\n    }\n  }\n}\n\nimpl<'i> Parse<'i> for TrackBreadth {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    Self::parse_internal(input, true)\n  }\n}\n\nimpl TrackBreadth {\n  fn parse_internal<'i, 't>(\n    input: &mut Parser<'i, 't>,\n    allow_flex: bool,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    if let Ok(len) = input.try_parse(LengthPercentage::parse) {\n      return Ok(TrackBreadth::Length(len));\n    }\n\n    if allow_flex {\n      if let Ok(flex) = input.try_parse(Self::parse_flex) {\n        return Ok(TrackBreadth::Flex(flex));\n      }\n    }\n\n    let location = input.current_source_location();\n    let ident = input.expect_ident()?;\n    match_ignore_ascii_case! { &*ident,\n      \"auto\" => Ok(TrackBreadth::Auto),\n      \"min-content\" => Ok(TrackBreadth::MinContent),\n      \"max-content\" => Ok(TrackBreadth::MaxContent),\n      _ => Err(location.new_unexpected_token_error(\n        cssparser::Token::Ident(ident.clone())\n      ))\n    }\n  }\n\n  fn parse_flex<'i, 't>(input: &mut Parser<'i, 't>) -> Result<CSSNumber, ParseError<'i, ParserError<'i>>> {\n    let location = input.current_source_location();\n    match *input.next()? {\n      Token::Dimension { value, ref unit, .. } if unit.eq_ignore_ascii_case(\"fr\") && value.is_sign_positive() => {\n        Ok(value)\n      }\n      ref t => Err(location.new_unexpected_token_error(t.clone())),\n    }\n  }\n}\n\nimpl ToCss for TrackBreadth {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      TrackBreadth::Auto => dest.write_str(\"auto\"),\n      TrackBreadth::MinContent => dest.write_str(\"min-content\"),\n      TrackBreadth::MaxContent => dest.write_str(\"max-content\"),\n      TrackBreadth::Length(len) => len.to_css(dest),\n      TrackBreadth::Flex(flex) => serialize_dimension(*flex, \"fr\", dest),\n    }\n  }\n}\n\nimpl<'i> Parse<'i> for TrackRepeat<'i> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    input.expect_function_matching(\"repeat\")?;\n    input.parse_nested_block(|input| {\n      let count = RepeatCount::parse(input)?;\n      input.expect_comma()?;\n\n      let mut line_names = Vec::new();\n      let mut track_sizes = Vec::new();\n\n      loop {\n        let line_name = input.try_parse(parse_line_names).unwrap_or_default();\n        line_names.push(line_name);\n\n        if let Ok(track_size) = input.try_parse(TrackSize::parse) {\n          // TODO: error handling\n          track_sizes.push(track_size)\n        } else {\n          break;\n        }\n      }\n\n      Ok(TrackRepeat {\n        count,\n        line_names,\n        track_sizes,\n      })\n    })\n  }\n}\n\nimpl<'i> ToCss for TrackRepeat<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    dest.write_str(\"repeat(\")?;\n    self.count.to_css(dest)?;\n    dest.delim(',', false)?;\n\n    let mut track_sizes_iter = self.track_sizes.iter();\n    let mut first = true;\n    for names in self.line_names.iter() {\n      if !names.is_empty() {\n        serialize_line_names(names, dest)?;\n      }\n\n      if let Some(size) = track_sizes_iter.next() {\n        // Whitespace is required if there are no line names.\n        if !names.is_empty() {\n          dest.whitespace()?;\n        } else if !first {\n          dest.write_char(' ')?;\n        }\n        size.to_css(dest)?;\n      }\n\n      first = false;\n    }\n\n    dest.write_char(')')\n  }\n}\n\nfn parse_line_names<'i, 't>(\n  input: &mut Parser<'i, 't>,\n) -> Result<CustomIdentList<'i>, ParseError<'i, ParserError<'i>>> {\n  input.expect_square_bracket_block()?;\n  input.parse_nested_block(|input| {\n    let mut values = SmallVec::new();\n    while let Ok(ident) = input.try_parse(CustomIdent::parse) {\n      values.push(ident)\n    }\n    Ok(values)\n  })\n}\n\nfn serialize_line_names<W>(names: &[CustomIdent], dest: &mut Printer<W>) -> Result<(), PrinterError>\nwhere\n  W: std::fmt::Write,\n{\n  dest.write_char('[')?;\n  let mut first = true;\n  for name in names {\n    if first {\n      first = false;\n    } else {\n      dest.write_char(' ')?;\n    }\n    write_ident(&name.0, dest)?;\n  }\n  dest.write_char(']')\n}\n\nfn write_ident<W>(name: &str, dest: &mut Printer<W>) -> Result<(), PrinterError>\nwhere\n  W: std::fmt::Write,\n{\n  let css_module_grid_enabled = dest.css_module.as_ref().map_or(false, |css_module| css_module.config.grid);\n  if css_module_grid_enabled {\n    if let Some(css_module) = &mut dest.css_module {\n      if let Some(last) = css_module.config.pattern.segments.last() {\n        if !matches!(last, crate::css_modules::Segment::Local) {\n          return Err(Error {\n            kind: PrinterErrorKind::InvalidCssModulesPatternInGrid,\n            loc: Some(ErrorLocation {\n              filename: dest.filename().into(),\n              line: dest.loc.line,\n              column: dest.loc.column,\n            }),\n          });\n        }\n      }\n    }\n  }\n  dest.write_ident(name, css_module_grid_enabled)?;\n  Ok(())\n}\n\nimpl<'i> Parse<'i> for TrackList<'i> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let mut line_names = Vec::new();\n    let mut items = Vec::new();\n\n    loop {\n      let line_name = input.try_parse(parse_line_names).unwrap_or_default();\n      line_names.push(line_name);\n\n      if let Ok(track_size) = input.try_parse(TrackSize::parse) {\n        // TODO: error handling\n        items.push(TrackListItem::TrackSize(track_size));\n      } else if let Ok(repeat) = input.try_parse(TrackRepeat::parse) {\n        // TODO: error handling\n        items.push(TrackListItem::TrackRepeat(repeat))\n      } else {\n        break;\n      }\n    }\n\n    if items.is_empty() {\n      return Err(input.new_custom_error(ParserError::InvalidDeclaration));\n    }\n\n    Ok(TrackList { line_names, items })\n  }\n}\n\nimpl<'i> ToCss for TrackList<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    let mut items_iter = self.items.iter();\n    let line_names_iter = self.line_names.iter();\n    let mut first = true;\n\n    for names in line_names_iter {\n      if !names.is_empty() {\n        serialize_line_names(names, dest)?;\n      }\n\n      if let Some(item) = items_iter.next() {\n        // Whitespace is required if there are no line names.\n        if !names.is_empty() {\n          dest.whitespace()?;\n        } else if !first {\n          dest.write_char(' ')?;\n        }\n        match item {\n          TrackListItem::TrackRepeat(repeat) => repeat.to_css(dest)?,\n          TrackListItem::TrackSize(size) => size.to_css(dest)?,\n        };\n      }\n\n      first = false;\n    }\n\n    Ok(())\n  }\n}\n\nimpl<'i> TrackList<'i> {\n  fn is_explicit(&self) -> bool {\n    self.items.iter().all(|item| matches!(item, TrackListItem::TrackSize(_)))\n  }\n}\n\nimpl<'i> TrackSizing<'i> {\n  fn is_explicit(&self) -> bool {\n    match self {\n      TrackSizing::None => true,\n      TrackSizing::TrackList(list) => list.is_explicit(),\n    }\n  }\n}\n\nimpl<'i> Parse<'i> for TrackSizeList {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let mut res = SmallVec::new();\n    while let Ok(size) = input.try_parse(TrackSize::parse) {\n      res.push(size)\n    }\n    if res.len() == 1 && res[0] == TrackSize::default() {\n      res.clear();\n    }\n    Ok(TrackSizeList(res))\n  }\n}\n\nimpl ToCss for TrackSizeList {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    if self.0.len() == 0 {\n      return dest.write_str(\"auto\");\n    }\n\n    let mut first = true;\n    for item in &self.0 {\n      if first {\n        first = false;\n      } else {\n        dest.write_char(' ')?;\n      }\n      item.to_css(dest)?;\n    }\n    Ok(())\n  }\n}\n\n/// A value for the [grid-template-areas](https://drafts.csswg.org/css-grid-2/#grid-template-areas-property) property.\n/// none | <string>+\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum GridTemplateAreas {\n  /// No named grid areas.\n  None,\n  /// Defines the list of named grid areas.\n  Areas {\n    /// The number of columns in the grid.\n    columns: u32,\n    /// A flattened list of grid area names.\n    /// Unnamed areas specified by the `.` token are represented as `None`.\n    areas: Vec<Option<String>>,\n  },\n}\n\nimpl<'i> Parse<'i> for GridTemplateAreas {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    if input.try_parse(|input| input.expect_ident_matching(\"none\")).is_ok() {\n      return Ok(GridTemplateAreas::None);\n    }\n\n    let mut tokens = Vec::new();\n    let mut row = 0;\n    let mut columns = 0;\n    while let Ok(s) = input.try_parse(|input| input.expect_string().map(|s| s.as_ref().to_owned())) {\n      let parsed_columns = Self::parse_string(&s, &mut tokens)\n        .map_err(|()| input.new_error(BasicParseErrorKind::QualifiedRuleInvalid))?;\n\n      if row == 0 {\n        columns = parsed_columns;\n      } else if parsed_columns != columns {\n        return Err(input.new_custom_error(ParserError::InvalidDeclaration));\n      }\n\n      row += 1;\n    }\n\n    Ok(GridTemplateAreas::Areas { columns, areas: tokens })\n  }\n}\n\nimpl GridTemplateAreas {\n  fn parse_string(string: &str, tokens: &mut Vec<Option<String>>) -> Result<u32, ()> {\n    let mut string = string;\n    let mut column = 0;\n    loop {\n      let rest = string.trim_start_matches(HTML_SPACE_CHARACTERS);\n      if rest.is_empty() {\n        // Each string must produce a valid token.\n        if column == 0 {\n          return Err(());\n        }\n        break;\n      }\n\n      column += 1;\n\n      if rest.starts_with('.') {\n        string = &rest[rest.find(|c| c != '.').unwrap_or(rest.len())..];\n        tokens.push(None);\n        continue;\n      }\n\n      if !rest.starts_with(is_name_code_point) {\n        return Err(());\n      }\n\n      let token_len = rest.find(|c| !is_name_code_point(c)).unwrap_or(rest.len());\n      let token = &rest[..token_len];\n      tokens.push(Some(token.into()));\n      string = &rest[token_len..];\n    }\n\n    Ok(column)\n  }\n}\n\nstatic HTML_SPACE_CHARACTERS: &'static [char] = &['\\u{0020}', '\\u{0009}', '\\u{000a}', '\\u{000c}', '\\u{000d}'];\n\nfn is_name_code_point(c: char) -> bool {\n  c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' || c >= '\\u{80}' || c == '_' || c >= '0' && c <= '9' || c == '-'\n}\n\nimpl ToCss for GridTemplateAreas {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      GridTemplateAreas::None => dest.write_str(\"none\"),\n      GridTemplateAreas::Areas { areas, .. } => {\n        let mut iter = areas.iter();\n        let mut next = iter.next();\n        let mut first = true;\n        while next.is_some() {\n          if !first && !dest.minify {\n            dest.newline()?;\n          }\n\n          self.write_string(dest, &mut iter, &mut next)?;\n\n          if first {\n            first = false;\n            if !dest.minify {\n              // Indent by the width of \"grid-template-areas: \", so the rows line up.\n              dest.indent_by(21);\n            }\n          }\n        }\n\n        if !dest.minify {\n          dest.dedent_by(21);\n        }\n\n        Ok(())\n      }\n    }\n  }\n}\n\nimpl GridTemplateAreas {\n  fn write_string<'a, W>(\n    &self,\n    dest: &mut Printer<W>,\n    iter: &mut std::slice::Iter<'a, Option<String>>,\n    next: &mut Option<&'a Option<String>>,\n  ) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    let columns = match self {\n      GridTemplateAreas::Areas { columns, .. } => *columns,\n      _ => unreachable!(),\n    };\n\n    dest.write_char('\"')?;\n\n    let mut last_was_null = false;\n    for i in 0..columns {\n      if let Some(token) = next {\n        if let Some(string) = token {\n          if i > 0 && (!last_was_null || !dest.minify) {\n            dest.write_char(' ')?;\n          }\n          write_ident(string, dest)?;\n          last_was_null = false;\n        } else {\n          if i > 0 && (last_was_null || !dest.minify) {\n            dest.write_char(' ')?;\n          }\n          dest.write_char('.')?;\n          last_was_null = true;\n        }\n      }\n\n      *next = iter.next();\n    }\n\n    dest.write_char('\"')\n  }\n}\n\n/// A value for the [grid-template](https://drafts.csswg.org/css-grid-2/#explicit-grid-shorthand) shorthand property.\n///\n/// none | [ <'grid-template-rows'> / <'grid-template-columns'> ] | [ <line-names>? <string> <track-size>? <line-names>? ]+ [ / <explicit-track-list> ]?\n///\n/// If `areas` is not `None`, then `rows` must also not be `None`.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct GridTemplate<'i> {\n  /// The grid template rows.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  pub rows: TrackSizing<'i>,\n  /// The grid template columns.\n  pub columns: TrackSizing<'i>,\n  /// The named grid areas.\n  pub areas: GridTemplateAreas,\n}\n\nimpl<'i> Parse<'i> for GridTemplate<'i> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    if input.try_parse(|input| input.expect_ident_matching(\"none\")).is_ok() {\n      input.expect_exhausted()?;\n      return Ok(GridTemplate {\n        rows: TrackSizing::None,\n        columns: TrackSizing::None,\n        areas: GridTemplateAreas::None,\n      });\n    }\n\n    let start = input.state();\n    let mut line_names: Vec<CustomIdentList<'i>> = Vec::new();\n    let mut items = Vec::new();\n    let mut columns = 0;\n    let mut row = 0;\n    let mut tokens = Vec::new();\n\n    loop {\n      if let Ok(first_names) = input.try_parse(parse_line_names) {\n        if let Some(last_names) = line_names.last_mut() {\n          last_names.extend(first_names);\n        } else {\n          line_names.push(first_names);\n        }\n      }\n\n      if let Ok(string) = input.try_parse(|input| input.expect_string().map(|s| s.as_ref().to_owned())) {\n        let parsed_columns = GridTemplateAreas::parse_string(&string, &mut tokens)\n          .map_err(|()| input.new_custom_error(ParserError::InvalidDeclaration))?;\n\n        if row == 0 {\n          columns = parsed_columns;\n        } else if parsed_columns != columns {\n          return Err(input.new_custom_error(ParserError::InvalidDeclaration));\n        }\n\n        row += 1;\n\n        let track_size = input.try_parse(TrackSize::parse).unwrap_or_default();\n        items.push(TrackListItem::TrackSize(track_size));\n\n        let last_names = input.try_parse(parse_line_names).unwrap_or_default();\n        line_names.push(last_names);\n      } else {\n        break;\n      }\n    }\n\n    if !tokens.is_empty() {\n      if line_names.len() == items.len() {\n        line_names.push(Default::default());\n      }\n\n      let areas = GridTemplateAreas::Areas { columns, areas: tokens };\n      let rows = TrackSizing::TrackList(TrackList { line_names, items });\n      let columns = if input.try_parse(|input| input.expect_delim('/')).is_ok() {\n        let list = TrackList::parse(input)?;\n        if !list.is_explicit() {\n          return Err(input.new_custom_error(ParserError::InvalidDeclaration));\n        }\n        TrackSizing::TrackList(list)\n      } else {\n        TrackSizing::None\n      };\n      Ok(GridTemplate { rows, columns, areas })\n    } else {\n      input.reset(&start);\n      let rows = TrackSizing::parse(input)?;\n      input.expect_delim('/')?;\n      let columns = TrackSizing::parse(input)?;\n      Ok(GridTemplate {\n        rows,\n        columns,\n        areas: GridTemplateAreas::None,\n      })\n    }\n  }\n}\n\nimpl ToCss for GridTemplate<'_> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    self.to_css_with_indent(dest, 15)\n  }\n}\n\nimpl GridTemplate<'_> {\n  fn to_css_with_indent<W>(&self, dest: &mut Printer<W>, indent: u8) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match &self.areas {\n      GridTemplateAreas::None => {\n        if self.rows == TrackSizing::None && self.columns == TrackSizing::None {\n          dest.write_str(\"none\")?;\n        } else {\n          self.rows.to_css(dest)?;\n          dest.delim('/', true)?;\n          self.columns.to_css(dest)?;\n        }\n      }\n      GridTemplateAreas::Areas { areas, .. } => {\n        let track_list = match &self.rows {\n          TrackSizing::TrackList(list) => list,\n          _ => unreachable!(),\n        };\n\n        let mut areas_iter = areas.iter();\n        let mut line_names_iter = track_list.line_names.iter();\n        let mut items_iter = track_list.items.iter();\n\n        let mut next = areas_iter.next();\n        let mut first = true;\n        let mut indented = false;\n        while next.is_some() {\n          macro_rules! newline {\n            () => {\n              if !dest.minify {\n                if !indented {\n                  // Indent by the width of \"grid-template: \", so the rows line up.\n                  dest.indent_by(indent);\n                  indented = true;\n                }\n                dest.newline()?;\n              }\n            };\n          }\n\n          if let Some(line_names) = line_names_iter.next() {\n            if !line_names.is_empty() {\n              if !dest.minify && line_names.len() == 2 {\n                dest.whitespace()?;\n                serialize_line_names(&line_names[0..1], dest)?;\n                newline!();\n                serialize_line_names(&line_names[1..], dest)?;\n              } else {\n                if !first {\n                  newline!();\n                }\n                serialize_line_names(line_names, dest)?;\n              }\n              dest.whitespace()?;\n            } else if !first {\n              newline!();\n            }\n          } else if !first {\n            newline!();\n          }\n\n          self.areas.write_string(dest, &mut areas_iter, &mut next)?;\n\n          if let Some(item) = items_iter.next() {\n            if *item != TrackListItem::TrackSize(TrackSize::default()) {\n              dest.whitespace()?;\n              match item {\n                TrackListItem::TrackSize(size) => size.to_css(dest)?,\n                _ => unreachable!(),\n              }\n            }\n          }\n\n          first = false;\n        }\n\n        if let Some(line_names) = line_names_iter.next() {\n          if !line_names.is_empty() {\n            dest.whitespace()?;\n            serialize_line_names(line_names, dest)?;\n          }\n        }\n\n        if let TrackSizing::TrackList(track_list) = &self.columns {\n          dest.newline()?;\n          dest.delim('/', false)?;\n          track_list.to_css(dest)?;\n        }\n\n        if indented {\n          dest.dedent_by(indent);\n        }\n      }\n    }\n\n    Ok(())\n  }\n}\n\nimpl<'i> GridTemplate<'i> {\n  #[inline]\n  fn is_valid(rows: &TrackSizing, columns: &TrackSizing, areas: &GridTemplateAreas) -> bool {\n    // The `grid-template` shorthand supports only explicit track values (i.e. no `repeat()`)\n    // combined with grid-template-areas. If there are no areas, then any track values are allowed.\n    *areas == GridTemplateAreas::None\n      || (*rows != TrackSizing::None && rows.is_explicit() && columns.is_explicit())\n  }\n}\n\nimpl_shorthand! {\n  GridTemplate(GridTemplate<'i>) {\n    rows: [GridTemplateRows],\n    columns: [GridTemplateColumns],\n    areas: [GridTemplateAreas],\n  }\n\n  fn is_valid(shorthand) {\n    GridTemplate::is_valid(&shorthand.rows, &shorthand.columns, &shorthand.areas)\n  }\n}\n\nbitflags! {\n  /// A value for the [grid-auto-flow](https://drafts.csswg.org/css-grid-2/#grid-auto-flow-property) property.\n  ///\n  /// [ row | column ] || dense\n  ///\n  /// The `Row` or `Column` flags may be combined with the `Dense` flag, but the `Row` and `Column` flags may\n  /// not be combined.\n  #[cfg_attr(feature = \"visitor\", derive(Visit))]\n  #[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize), serde(from = \"SerializedGridAutoFlow\", into = \"SerializedGridAutoFlow\"))]\n  #[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n  #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]\n  pub struct GridAutoFlow: u8 {\n    /// The auto-placement algorithm places items by filling each row, adding new rows as necessary.\n    const Row    = 0b00;\n    /// The auto-placement algorithm places items by filling each column, adding new columns as necessary.\n    const Column = 0b01;\n    /// If specified, a dense packing algorithm is used, which fills in holes in the grid.\n    const Dense  = 0b10;\n  }\n}\n\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\nstruct SerializedGridAutoFlow {\n  /// The direction of the auto flow.\n  direction: AutoFlowDirection,\n  /// If specified, a dense packing algorithm is used, which fills in holes in the grid.\n  dense: bool,\n}\n\nimpl From<GridAutoFlow> for SerializedGridAutoFlow {\n  fn from(flow: GridAutoFlow) -> Self {\n    Self {\n      direction: if flow.contains(GridAutoFlow::Column) {\n        AutoFlowDirection::Column\n      } else {\n        AutoFlowDirection::Row\n      },\n      dense: flow.contains(GridAutoFlow::Dense),\n    }\n  }\n}\n\nimpl From<SerializedGridAutoFlow> for GridAutoFlow {\n  fn from(s: SerializedGridAutoFlow) -> GridAutoFlow {\n    let mut flow = match s.direction {\n      AutoFlowDirection::Row => GridAutoFlow::Row,\n      AutoFlowDirection::Column => GridAutoFlow::Column,\n    };\n    if s.dense {\n      flow |= GridAutoFlow::Dense\n    }\n\n    flow\n  }\n}\n\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"lowercase\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\nenum AutoFlowDirection {\n  Row,\n  Column,\n}\n\n#[cfg(feature = \"jsonschema\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"jsonschema\")))]\nimpl<'a> schemars::JsonSchema for GridAutoFlow {\n  fn is_referenceable() -> bool {\n    true\n  }\n\n  fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {\n    SerializedGridAutoFlow::json_schema(gen)\n  }\n\n  fn schema_name() -> String {\n    \"GridAutoFlow\".into()\n  }\n}\n\nimpl Default for GridAutoFlow {\n  fn default() -> GridAutoFlow {\n    GridAutoFlow::Row\n  }\n}\n\nimpl GridAutoFlow {\n  fn direction(self) -> GridAutoFlow {\n    self & GridAutoFlow::Column\n  }\n}\n\nimpl<'i> Parse<'i> for GridAutoFlow {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let mut flow = GridAutoFlow::Row;\n\n    macro_rules! match_dense {\n      () => {\n        if input.try_parse(|input| input.expect_ident_matching(\"dense\")).is_ok() {\n          flow |= GridAutoFlow::Dense;\n        }\n      };\n    }\n\n    let location = input.current_source_location();\n    let ident = input.expect_ident()?;\n    match_ignore_ascii_case! { &ident,\n      \"row\" => {\n        match_dense!();\n      },\n      \"column\" => {\n        flow = GridAutoFlow::Column;\n        match_dense!();\n      },\n      \"dense\" => {\n        let location = input.current_source_location();\n        input.try_parse(|input| {\n          let ident = input.expect_ident()?;\n          match_ignore_ascii_case! { &ident,\n            \"row\" => {},\n            \"column\" => {\n              flow = GridAutoFlow::Column;\n            },\n            _ => return Err(location.new_unexpected_token_error(\n              cssparser::Token::Ident(ident.clone())\n            ))\n          }\n          Ok(())\n        })?;\n        flow |= GridAutoFlow::Dense;\n      },\n      _ => return Err(location.new_unexpected_token_error(\n        cssparser::Token::Ident(ident.clone())\n      ))\n    }\n\n    Ok(flow)\n  }\n}\n\nimpl ToCss for GridAutoFlow {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    let s = if *self == GridAutoFlow::Row {\n      \"row\"\n    } else if *self == GridAutoFlow::Column {\n      \"column\"\n    } else if *self == GridAutoFlow::Row | GridAutoFlow::Dense {\n      if dest.minify {\n        \"dense\"\n      } else {\n        \"row dense\"\n      }\n    } else if *self == GridAutoFlow::Column | GridAutoFlow::Dense {\n      \"column dense\"\n    } else {\n      unreachable!();\n    };\n\n    dest.write_str(s)\n  }\n}\n\n/// A value for the [grid](https://drafts.csswg.org/css-grid-2/#grid-shorthand) shorthand property.\n///\n/// <'grid-template'> | <'grid-template-rows'> / [ auto-flow && dense? ] <'grid-auto-columns'>? | [ auto-flow && dense? ] <'grid-auto-rows'>? / <'grid-template-columns'>\n///\n/// Explicit and implicit values may not be combined.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"camelCase\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct Grid<'i> {\n  /// Explicit grid template rows.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  pub rows: TrackSizing<'i>,\n  /// Explicit grid template columns.\n  pub columns: TrackSizing<'i>,\n  /// Explicit grid template areas.\n  pub areas: GridTemplateAreas,\n  /// The grid auto rows.\n  pub auto_rows: TrackSizeList,\n  /// The grid auto columns.\n  pub auto_columns: TrackSizeList,\n  /// The grid auto flow.\n  pub auto_flow: GridAutoFlow,\n}\n\nimpl<'i> Parse<'i> for Grid<'i> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    // <'grid-template'>\n    if let Ok(template) = input.try_parse(GridTemplate::parse) {\n      Ok(Grid {\n        rows: template.rows,\n        columns: template.columns,\n        areas: template.areas,\n        auto_rows: TrackSizeList::default(),\n        auto_columns: TrackSizeList::default(),\n        auto_flow: GridAutoFlow::default(),\n      })\n\n    // <'grid-template-rows'> / [ auto-flow && dense? ] <'grid-auto-columns'>?\n    } else if let Ok(rows) = input.try_parse(TrackSizing::parse) {\n      input.expect_delim('/')?;\n      let auto_flow = parse_grid_auto_flow(input, GridAutoFlow::Column)?;\n      let auto_columns = TrackSizeList::parse(input).unwrap_or_default();\n      Ok(Grid {\n        rows,\n        columns: TrackSizing::None,\n        areas: GridTemplateAreas::None,\n        auto_rows: TrackSizeList::default(),\n        auto_columns,\n        auto_flow,\n      })\n\n    // [ auto-flow && dense? ] <'grid-auto-rows'>? / <'grid-template-columns'>\n    } else {\n      let auto_flow = parse_grid_auto_flow(input, GridAutoFlow::Row)?;\n      let auto_rows = input.try_parse(TrackSizeList::parse).unwrap_or_default();\n      input.expect_delim('/')?;\n      let columns = TrackSizing::parse(input)?;\n      Ok(Grid {\n        rows: TrackSizing::None,\n        columns,\n        areas: GridTemplateAreas::None,\n        auto_rows,\n        auto_columns: TrackSizeList::default(),\n        auto_flow,\n      })\n    }\n  }\n}\n\nfn parse_grid_auto_flow<'i, 't>(\n  input: &mut Parser<'i, 't>,\n  flow: GridAutoFlow,\n) -> Result<GridAutoFlow, ParseError<'i, ParserError<'i>>> {\n  if input.try_parse(|input| input.expect_ident_matching(\"auto-flow\")).is_ok() {\n    if input.try_parse(|input| input.expect_ident_matching(\"dense\")).is_ok() {\n      Ok(flow | GridAutoFlow::Dense)\n    } else {\n      Ok(flow)\n    }\n  } else if input.try_parse(|input| input.expect_ident_matching(\"dense\")).is_ok() {\n    input.expect_ident_matching(\"auto-flow\")?;\n    Ok(flow | GridAutoFlow::Dense)\n  } else {\n    Err(input.new_error_for_next_token())\n  }\n}\n\nimpl ToCss for Grid<'_> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    let is_auto_initial = self.auto_rows == TrackSizeList::default()\n      && self.auto_columns == TrackSizeList::default()\n      && self.auto_flow == GridAutoFlow::default();\n\n    // Handle the case where areas is set but rows is None (auto-flow syntax).\n    // In this case, output \"auto-flow / columns\" format.\n    if self.areas != GridTemplateAreas::None && self.rows == TrackSizing::None {\n      dest.write_str(\"auto-flow\")?;\n      if self.auto_flow.contains(GridAutoFlow::Dense) {\n        dest.write_str(\" dense\")?;\n      }\n      if self.auto_rows != TrackSizeList::default() {\n        dest.write_char(' ')?;\n        self.auto_rows.to_css(dest)?;\n      }\n      dest.delim('/', true)?;\n      self.columns.to_css(dest)?;\n      return Ok(());\n    }\n\n    // Handle the case where areas is set but columns is None (auto-flow column syntax).\n    // In this case, output \"rows / auto-flow\" format.\n    if self.areas != GridTemplateAreas::None\n      && self.columns == TrackSizing::None\n      && self.auto_flow.direction() == GridAutoFlow::Column\n    {\n      self.rows.to_css(dest)?;\n      dest.delim('/', true)?;\n      dest.write_str(\"auto-flow\")?;\n      if self.auto_flow.contains(GridAutoFlow::Dense) {\n        dest.write_str(\" dense\")?;\n      }\n      if self.auto_columns != TrackSizeList::default() {\n        dest.write_char(' ')?;\n        self.auto_columns.to_css(dest)?;\n      }\n      return Ok(());\n    }\n\n    if self.areas != GridTemplateAreas::None\n      || (self.rows != TrackSizing::None && self.columns != TrackSizing::None)\n      || (self.areas == GridTemplateAreas::None && is_auto_initial)\n    {\n      if !is_auto_initial {\n        unreachable!(\"invalid grid shorthand: mixed implicit and explicit values\");\n      }\n      let template = GridTemplate {\n        rows: self.rows.clone(),\n        columns: self.columns.clone(),\n        areas: self.areas.clone(),\n      };\n      template.to_css_with_indent(dest, 6)?;\n    } else if self.auto_flow.direction() == GridAutoFlow::Column {\n      if self.columns != TrackSizing::None || self.auto_rows != TrackSizeList::default() {\n        unreachable!(\"invalid grid shorthand: mixed implicit and explicit values\");\n      }\n      self.rows.to_css(dest)?;\n      dest.delim('/', true)?;\n      dest.write_str(\"auto-flow\")?;\n      if self.auto_flow.contains(GridAutoFlow::Dense) {\n        dest.write_str(\" dense\")?;\n      }\n      if self.auto_columns != TrackSizeList::default() {\n        dest.write_char(' ')?;\n        self.auto_columns.to_css(dest)?;\n      }\n    } else {\n      if self.rows != TrackSizing::None || self.auto_columns != TrackSizeList::default() {\n        unreachable!(\"invalid grid shorthand: mixed implicit and explicit values\");\n      }\n      dest.write_str(\"auto-flow\")?;\n      if self.auto_flow.contains(GridAutoFlow::Dense) {\n        dest.write_str(\" dense\")?;\n      }\n      if self.auto_rows != TrackSizeList::default() {\n        dest.write_char(' ')?;\n        self.auto_rows.to_css(dest)?;\n      }\n      dest.delim('/', true)?;\n      self.columns.to_css(dest)?;\n    }\n\n    Ok(())\n  }\n}\n\nimpl<'i> Grid<'i> {\n  #[inline]\n  fn is_valid(\n    rows: &TrackSizing,\n    columns: &TrackSizing,\n    areas: &GridTemplateAreas,\n    auto_rows: &TrackSizeList,\n    auto_columns: &TrackSizeList,\n    auto_flow: &GridAutoFlow,\n  ) -> bool {\n    let default_track_size_list = TrackSizeList::default();\n\n    // When areas is set but rows is None (auto-flow syntax like \"grid: auto-flow / 1fr\"),\n    // we can output the auto-flow shorthand along with \"grid-template-areas\" separately.\n    // ⚠️ The case of `grid: 1fr / auto-flow` does not require such handling.\n    if *areas != GridTemplateAreas::None && *rows == TrackSizing::None {\n      return auto_flow.direction() == GridAutoFlow::Row;\n    }\n\n    // The `grid` shorthand can either be fully explicit (e.g. same as `grid-template`),\n    // or explicit along a single axis. If there are auto rows, then there cannot be explicit rows, for example.\n    let is_template = GridTemplate::is_valid(rows, columns, areas);\n    let is_explicit = *auto_rows == default_track_size_list\n      && *auto_columns == default_track_size_list\n      && *auto_flow == GridAutoFlow::default();\n    // grid-auto-flow: row shorthand syntax:\n    // [ auto-flow && dense? ] <'grid-auto-rows'>? / <'grid-template-columns'>\n    let is_auto_rows = auto_flow.direction() == GridAutoFlow::Row\n      && *rows == TrackSizing::None\n      && *auto_columns == default_track_size_list;\n    // grid-auto-flow: column shorthand syntax:\n    // <'grid-template-rows'> / [ auto-flow && dense? ] <'grid-auto-columns'>?\n    let is_auto_columns = auto_flow.direction() == GridAutoFlow::Column\n      && *columns == TrackSizing::None\n      && *auto_rows == default_track_size_list;\n\n    (is_template && is_explicit) || is_auto_rows || is_auto_columns\n  }\n}\n\n// TODO: shorthand `grid: auto-flow 1fr / 100px` https://drafts.csswg.org/css-grid/#example-dec34e0f\nimpl_shorthand! {\n  Grid(Grid<'i>) {\n    rows: [GridTemplateRows],\n    columns: [GridTemplateColumns],\n    areas: [GridTemplateAreas],\n    auto_rows: [GridAutoRows],\n    auto_columns: [GridAutoColumns],\n    auto_flow: [GridAutoFlow],\n  }\n\n  fn is_valid(grid) {\n    Grid::is_valid(&grid.rows, &grid.columns, &grid.areas, &grid.auto_rows, &grid.auto_columns, &grid.auto_flow)\n  }\n}\n\n/// A [`<grid-line>`](https://drafts.csswg.org/css-grid-2/#typedef-grid-row-start-grid-line) value,\n/// used in the `grid-row-start`, `grid-row-end`, `grid-column-start`, and `grid-column-end` properties.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub enum GridLine<'i> {\n  /// Automatic placement.\n  Auto,\n  /// A named grid area name (automatically postfixed by `-start` or `-end`), or and explicit grid line name.\n  Area {\n    /// A grid area name.\n    name: CustomIdent<'i>,\n  },\n  /// The Nth grid line, optionally filtered by line name. Negative numbers count backwards from the end.\n  Line {\n    /// A line number.\n    index: CSSInteger,\n    /// A line name to filter by.\n    #[cfg_attr(feature = \"serde\", serde(borrow))]\n    name: Option<CustomIdent<'i>>,\n  },\n  /// A grid span based on the Nth grid line from the opposite edge, optionally filtered by line name.\n  Span {\n    /// A line number.\n    index: CSSInteger,\n    /// A line name to filter by.\n    name: Option<CustomIdent<'i>>,\n  },\n}\n\nimpl<'i> Parse<'i> for GridLine<'i> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    if input.try_parse(|input| input.expect_ident_matching(\"auto\")).is_ok() {\n      return Ok(GridLine::Auto);\n    }\n\n    if input.try_parse(|input| input.expect_ident_matching(\"span\")).is_ok() {\n      // TODO: is calc() supported here??\n      let (index, name) = if let Ok(line_number) = input.try_parse(CSSInteger::parse) {\n        let ident = input.try_parse(CustomIdent::parse).ok();\n        (line_number, ident)\n      } else if let Ok(ident) = input.try_parse(CustomIdent::parse) {\n        let line_number = input.try_parse(CSSInteger::parse).unwrap_or(1);\n        (line_number, Some(ident))\n      } else {\n        return Err(input.new_custom_error(ParserError::InvalidDeclaration));\n      };\n\n      if index == 0 {\n        return Err(input.new_custom_error(ParserError::InvalidDeclaration));\n      }\n\n      return Ok(GridLine::Span { index, name });\n    }\n\n    if let Ok(index) = input.try_parse(CSSInteger::parse) {\n      if index == 0 {\n        return Err(input.new_custom_error(ParserError::InvalidDeclaration));\n      }\n      let name = input.try_parse(CustomIdent::parse).ok();\n      return Ok(GridLine::Line { index, name });\n    }\n\n    let name = CustomIdent::parse(input)?;\n    if let Ok(index) = input.try_parse(CSSInteger::parse) {\n      if index == 0 {\n        return Err(input.new_custom_error(ParserError::InvalidDeclaration));\n      }\n      return Ok(GridLine::Line {\n        index,\n        name: Some(name),\n      });\n    }\n\n    Ok(GridLine::Area { name })\n  }\n}\n\nimpl ToCss for GridLine<'_> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      GridLine::Auto => dest.write_str(\"auto\"),\n      GridLine::Area { name } => write_ident(&name.0, dest),\n      GridLine::Line { index, name } => {\n        index.to_css(dest)?;\n        if let Some(id) = name {\n          dest.write_char(' ')?;\n          write_ident(&id.0, dest)?;\n        }\n        Ok(())\n      }\n      GridLine::Span { index, name } => {\n        dest.write_str(\"span \")?;\n        if *index != 1 || name.is_none() {\n          index.to_css(dest)?;\n          if name.is_some() {\n            dest.write_char(' ')?;\n          }\n        }\n\n        if let Some(id) = name {\n          write_ident(&id.0, dest)?;\n        }\n        Ok(())\n      }\n    }\n  }\n}\n\nimpl<'i> GridLine<'i> {\n  fn default_end_value(&self) -> GridLine<'i> {\n    if matches!(self, GridLine::Area { .. }) {\n      self.clone()\n    } else {\n      GridLine::Auto\n    }\n  }\n\n  fn can_omit_end(&self, end: &GridLine) -> bool {\n    if let GridLine::Area { name: start_id } = &self {\n      matches!(end, GridLine::Area { name: end_id } if end_id == start_id)\n    } else if matches!(end, GridLine::Auto) {\n      true\n    } else {\n      false\n    }\n  }\n}\n\nmacro_rules! impl_grid_placement {\n  ($name: ident) => {\n    impl<'i> Parse<'i> for $name<'i> {\n      fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n        let start = GridLine::parse(input)?;\n        let end = if input.try_parse(|input| input.expect_delim('/')).is_ok() {\n          GridLine::parse(input)?\n        } else {\n          start.default_end_value()\n        };\n\n        Ok($name { start, end })\n      }\n    }\n\n    impl ToCss for $name<'_> {\n      fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n      where\n        W: std::fmt::Write,\n      {\n        self.start.to_css(dest)?;\n\n        if !self.start.can_omit_end(&self.end) {\n          dest.delim('/', true)?;\n          self.end.to_css(dest)?;\n        }\n        Ok(())\n      }\n    }\n  };\n}\n\ndefine_shorthand! {\n  /// A value for the [grid-row](https://drafts.csswg.org/css-grid-2/#propdef-grid-row) shorthand property.\n  /// <grid-line> [ / <grid-line> ]?\n  pub struct GridRow<'i> {\n    /// The starting line.\n    #[cfg_attr(feature = \"serde\", serde(borrow))]\n    start: GridRowStart(GridLine<'i>),\n    /// The ending line.\n    end: GridRowEnd(GridLine<'i>),\n  }\n}\n\ndefine_shorthand! {\n  /// A value for the [grid-column](https://drafts.csswg.org/css-grid-2/#propdef-grid-column) shorthand property.\n  /// <grid-line> [ / <grid-line> ]?\n  pub struct GridColumn<'i> {\n    /// The starting line.\n    #[cfg_attr(feature = \"serde\", serde(borrow))]\n    start: GridColumnStart(GridLine<'i>),\n    /// The ending line.\n    end: GridColumnEnd(GridLine<'i>),\n  }\n}\n\nimpl_grid_placement!(GridRow);\nimpl_grid_placement!(GridColumn);\n\ndefine_shorthand! {\n  /// A value for the [grid-area](https://drafts.csswg.org/css-grid-2/#propdef-grid-area) shorthand property.\n  /// <grid-line> [ / <grid-line> ]{0,3}\n  pub struct GridArea<'i> {\n    /// The grid row start placement.\n    #[cfg_attr(feature = \"serde\", serde(borrow))]\n    row_start: GridRowStart(GridLine<'i>),\n    /// The grid column start placement.\n    column_start: GridColumnStart(GridLine<'i>),\n    /// The grid row end placement.\n    row_end: GridRowEnd(GridLine<'i>),\n    /// The grid column end placement.\n    column_end: GridColumnEnd(GridLine<'i>),\n  }\n}\n\nimpl<'i> Parse<'i> for GridArea<'i> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let row_start = GridLine::parse(input)?;\n    let column_start = if input.try_parse(|input| input.expect_delim('/')).is_ok() {\n      GridLine::parse(input)?\n    } else {\n      let opposite = row_start.default_end_value();\n      return Ok(GridArea {\n        row_start,\n        column_start: opposite.clone(),\n        row_end: opposite.clone(),\n        column_end: opposite,\n      });\n    };\n\n    let row_end = if input.try_parse(|input| input.expect_delim('/')).is_ok() {\n      GridLine::parse(input)?\n    } else {\n      let row_end = row_start.default_end_value();\n      let column_end = column_start.default_end_value();\n      return Ok(GridArea {\n        row_start,\n        column_start,\n        row_end,\n        column_end,\n      });\n    };\n\n    let column_end = if input.try_parse(|input| input.expect_delim('/')).is_ok() {\n      GridLine::parse(input)?\n    } else {\n      let column_end = column_start.default_end_value();\n      return Ok(GridArea {\n        row_start,\n        column_start,\n        row_end,\n        column_end,\n      });\n    };\n\n    Ok(GridArea {\n      row_start,\n      column_start,\n      row_end,\n      column_end,\n    })\n  }\n}\n\nimpl ToCss for GridArea<'_> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    self.row_start.to_css(dest)?;\n\n    let can_omit_column_end = self.column_start.can_omit_end(&self.column_end);\n    let can_omit_row_end = can_omit_column_end && self.row_start.can_omit_end(&self.row_end);\n    let can_omit_column_start = can_omit_row_end && self.row_start.can_omit_end(&self.column_start);\n\n    if !can_omit_column_start {\n      dest.delim('/', true)?;\n      self.column_start.to_css(dest)?;\n    }\n\n    if !can_omit_row_end {\n      dest.delim('/', true)?;\n      self.row_end.to_css(dest)?;\n    }\n\n    if !can_omit_column_end {\n      dest.delim('/', true)?;\n      self.column_end.to_css(dest)?;\n    }\n\n    Ok(())\n  }\n}\n\n#[derive(Default, Debug)]\npub(crate) struct GridHandler<'i> {\n  rows: Option<TrackSizing<'i>>,\n  columns: Option<TrackSizing<'i>>,\n  areas: Option<GridTemplateAreas>,\n  auto_rows: Option<TrackSizeList>,\n  auto_columns: Option<TrackSizeList>,\n  auto_flow: Option<GridAutoFlow>,\n  row_start: Option<GridLine<'i>>,\n  column_start: Option<GridLine<'i>>,\n  row_end: Option<GridLine<'i>>,\n  column_end: Option<GridLine<'i>>,\n  has_any: bool,\n}\n\nimpl<'i> PropertyHandler<'i> for GridHandler<'i> {\n  fn handle_property(\n    &mut self,\n    property: &Property<'i>,\n    dest: &mut DeclarationList<'i>,\n    context: &mut PropertyHandlerContext<'i, '_>,\n  ) -> bool {\n    use Property::*;\n\n    match property {\n      GridTemplateColumns(columns) => self.columns = Some(columns.clone()),\n      GridTemplateRows(rows) => self.rows = Some(rows.clone()),\n      GridTemplateAreas(areas) => self.areas = Some(areas.clone()),\n      GridAutoColumns(auto_columns) => self.auto_columns = Some(auto_columns.clone()),\n      GridAutoRows(auto_rows) => self.auto_rows = Some(auto_rows.clone()),\n      GridAutoFlow(auto_flow) => self.auto_flow = Some(auto_flow.clone()),\n      GridTemplate(template) => {\n        self.rows = Some(template.rows.clone());\n        self.columns = Some(template.columns.clone());\n        self.areas = Some(template.areas.clone());\n      }\n      Grid(grid) => {\n        self.rows = Some(grid.rows.clone());\n        self.columns = Some(grid.columns.clone());\n        self.areas = Some(grid.areas.clone());\n        self.auto_rows = Some(grid.auto_rows.clone());\n        self.auto_columns = Some(grid.auto_columns.clone());\n        self.auto_flow = Some(grid.auto_flow.clone());\n      }\n      GridRowStart(row_start) => self.row_start = Some(row_start.clone()),\n      GridRowEnd(row_end) => self.row_end = Some(row_end.clone()),\n      GridColumnStart(column_start) => self.column_start = Some(column_start.clone()),\n      GridColumnEnd(column_end) => self.column_end = Some(column_end.clone()),\n      GridRow(row) => {\n        self.row_start = Some(row.start.clone());\n        self.row_end = Some(row.end.clone());\n      }\n      GridColumn(column) => {\n        self.column_start = Some(column.start.clone());\n        self.column_end = Some(column.end.clone());\n      }\n      GridArea(area) => {\n        self.row_start = Some(area.row_start.clone());\n        self.row_end = Some(area.row_end.clone());\n        self.column_start = Some(area.column_start.clone());\n        self.column_end = Some(area.column_end.clone());\n      }\n      Unparsed(val) if is_grid_property(&val.property_id) => {\n        self.finalize(dest, context);\n        dest.push(property.clone());\n      }\n      _ => return false,\n    }\n\n    self.has_any = true;\n    true\n  }\n\n  fn finalize(&mut self, dest: &mut DeclarationList<'i>, _: &mut PropertyHandlerContext<'i, '_>) {\n    if !self.has_any {\n      return;\n    }\n\n    self.has_any = false;\n\n    let mut rows = std::mem::take(&mut self.rows);\n    let mut columns = std::mem::take(&mut self.columns);\n    let mut areas = std::mem::take(&mut self.areas);\n    let mut auto_rows = std::mem::take(&mut self.auto_rows);\n    let mut auto_columns = std::mem::take(&mut self.auto_columns);\n    let mut auto_flow = std::mem::take(&mut self.auto_flow);\n    let mut row_start = std::mem::take(&mut self.row_start);\n    let mut row_end = std::mem::take(&mut self.row_end);\n    let mut column_start = std::mem::take(&mut self.column_start);\n    let mut column_end = std::mem::take(&mut self.column_end);\n\n    if let (Some(rows_val), Some(columns_val), Some(areas_val)) = (&rows, &columns, &areas) {\n      let mut has_template = true;\n      if let (Some(auto_rows_val), Some(auto_columns_val), Some(auto_flow_val)) =\n        (&auto_rows, &auto_columns, &auto_flow)\n      {\n        // The `grid` shorthand can either be fully explicit (e.g. same as `grid-template`),\n        // or explicit along a single axis. If there are auto rows, then there cannot be explicit rows, for example.\n        if Grid::is_valid(\n          rows_val,\n          columns_val,\n          areas_val,\n          auto_rows_val,\n          auto_columns_val,\n          auto_flow_val,\n        ) {\n          let needs_separate_areas = *areas_val != GridTemplateAreas::None\n            && ((*rows_val == TrackSizing::None && auto_flow_val.direction() == GridAutoFlow::Row)\n              || (*columns_val == TrackSizing::None && auto_flow_val.direction() == GridAutoFlow::Column));\n\n          // Pad areas with \".\" for missing rows. But don't pad if we're using auto-flow syntax,\n          // because grid-template-areas should remain as-is in that case.\n          // Use tuple to avoid double cloning when needs_separate_areas is true.\n          let (areas_for_grid, areas_for_output) = if needs_separate_areas {\n            // Take the original areas directly to avoid cloning when needs_separate_areas is true\n            (areas_val.clone(), Some(areas_val.clone()))\n          } else {\n            (GridHandler::pad_grid_template_areas(rows_val, areas_val.clone()), None)\n          };\n\n          dest.push(Property::Grid(Grid {\n            rows: rows_val.clone(),\n            columns: columns_val.clone(),\n            areas: areas_for_grid,\n            auto_rows: auto_rows_val.clone(),\n            auto_columns: auto_columns_val.clone(),\n            auto_flow: auto_flow_val.clone(),\n          }));\n\n          has_template = false;\n          auto_rows = None;\n          auto_columns = None;\n          auto_flow = None;\n\n          // When areas is set but rows/columns is None (auto-flow syntax), also output\n          // grid-template-areas separately since grid shorthand can't represent this combination.\n          if let Some(areas) = areas_for_output {\n            dest.push(Property::GridTemplateAreas(areas));\n          }\n        }\n      }\n\n      // The `grid-template` shorthand supports only explicit track values (i.e. no `repeat()`)\n      // combined with grid-template-areas. If there are no areas, then any track values are allowed.\n      if has_template && GridTemplate::is_valid(rows_val, columns_val, areas_val) {\n        // Pad areas with \".\" for missing rows\n        let padded_areas = GridHandler::pad_grid_template_areas(rows_val, areas_val.clone());\n\n        dest.push(Property::GridTemplate(GridTemplate {\n          rows: rows_val.clone(),\n          columns: columns_val.clone(),\n          areas: padded_areas,\n        }));\n\n        has_template = false;\n      }\n\n      if !has_template {\n        rows = None;\n        columns = None;\n        areas = None;\n      }\n    }\n\n    if row_start.is_some() && row_end.is_some() && column_start.is_some() && column_end.is_some() {\n      dest.push(Property::GridArea(GridArea {\n        row_start: std::mem::take(&mut row_start).unwrap(),\n        row_end: std::mem::take(&mut row_end).unwrap(),\n        column_start: std::mem::take(&mut column_start).unwrap(),\n        column_end: std::mem::take(&mut column_end).unwrap(),\n      }))\n    } else {\n      if row_start.is_some() && row_end.is_some() {\n        dest.push(Property::GridRow(GridRow {\n          start: std::mem::take(&mut row_start).unwrap(),\n          end: std::mem::take(&mut row_end).unwrap(),\n        }))\n      }\n\n      if column_start.is_some() && column_end.is_some() {\n        dest.push(Property::GridColumn(GridColumn {\n          start: std::mem::take(&mut column_start).unwrap(),\n          end: std::mem::take(&mut column_end).unwrap(),\n        }))\n      }\n    }\n\n    macro_rules! single_property {\n      ($prop: ident, $key: ident) => {\n        if let Some(val) = $key {\n          dest.push(Property::$prop(val))\n        }\n      };\n    }\n\n    single_property!(GridTemplateRows, rows);\n    single_property!(GridTemplateColumns, columns);\n    single_property!(GridTemplateAreas, areas);\n    single_property!(GridAutoRows, auto_rows);\n    single_property!(GridAutoColumns, auto_columns);\n    single_property!(GridAutoFlow, auto_flow);\n    single_property!(GridRowStart, row_start);\n    single_property!(GridRowEnd, row_end);\n    single_property!(GridColumnStart, column_start);\n    single_property!(GridColumnEnd, column_end);\n  }\n}\n\n/// Pads grid template areas with \".\" (None) for missing rows.\n/// All the remaining unnamed areas in a grid can be referred using null cell\n/// tokens. A null cell token is a sequence of one or more . (U+002E FULL STOP)\n/// characters, e.g., ., ..., or ..... etc. A null cell token can be used to\n/// create empty spaces in the grid.\n/// Spec: https://drafts.csswg.org/css-grid/#ref-for-string-value①\nimpl GridHandler<'_> {\n  fn pad_grid_template_areas(rows: &TrackSizing, areas: GridTemplateAreas) -> GridTemplateAreas {\n    match (rows, areas) {\n      (TrackSizing::TrackList(rows_list), GridTemplateAreas::Areas { columns, areas }) => {\n        let rows_count = rows_list.items.len();\n        let areas_rows_count = areas.len() / columns as usize;\n        if areas_rows_count < rows_count {\n          let mut padded_areas = areas;\n          // Fill each missing row with \".\" (represented as None)\n          for _ in areas_rows_count..rows_count {\n            for _ in 0..columns {\n              padded_areas.push(None);\n            }\n          }\n          GridTemplateAreas::Areas {\n            columns,\n            areas: padded_areas,\n          }\n        } else {\n          GridTemplateAreas::Areas { columns, areas }\n        }\n      }\n      (_, areas) => areas,\n    }\n  }\n}\n\n#[inline]\nfn is_grid_property(property_id: &PropertyId) -> bool {\n  match property_id {\n    PropertyId::GridTemplateColumns\n    | PropertyId::GridTemplateRows\n    | PropertyId::GridTemplateAreas\n    | PropertyId::GridAutoColumns\n    | PropertyId::GridAutoRows\n    | PropertyId::GridAutoFlow\n    | PropertyId::GridTemplate\n    | PropertyId::Grid\n    | PropertyId::GridRowStart\n    | PropertyId::GridRowEnd\n    | PropertyId::GridColumnStart\n    | PropertyId::GridColumnEnd\n    | PropertyId::GridRow\n    | PropertyId::GridColumn\n    | PropertyId::GridArea => true,\n    _ => false,\n  }\n}\n"
  },
  {
    "path": "src/properties/list.rs",
    "content": "//! CSS properties related to lists and counters.\n\nuse super::{Property, PropertyId};\nuse crate::context::PropertyHandlerContext;\nuse crate::declaration::{DeclarationBlock, DeclarationList};\nuse crate::error::{ParserError, PrinterError};\nuse crate::macros::{define_shorthand, enum_property, shorthand_handler};\nuse crate::printer::Printer;\nuse crate::targets::{Browsers, Targets};\nuse crate::traits::{FallbackValues, IsCompatible, Parse, PropertyHandler, Shorthand, ToCss};\nuse crate::values::string::CSSString;\nuse crate::values::{ident::CustomIdent, image::Image};\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse cssparser::*;\n\n/// A value for the [list-style-type](https://www.w3.org/TR/2020/WD-css-lists-3-20201117/#text-markers) property.\n#[derive(Debug, Clone, PartialEq, Parse, ToCss)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub enum ListStyleType<'i> {\n  /// No marker.\n  None,\n  /// An explicit marker string.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  String(CSSString<'i>),\n  /// A named counter style.\n  CounterStyle(CounterStyle<'i>),\n}\n\nimpl Default for ListStyleType<'_> {\n  fn default() -> Self {\n    ListStyleType::CounterStyle(CounterStyle::Predefined(PredefinedCounterStyle::Disc))\n  }\n}\n\nimpl IsCompatible for ListStyleType<'_> {\n  fn is_compatible(&self, browsers: Browsers) -> bool {\n    match self {\n      ListStyleType::CounterStyle(c) => c.is_compatible(browsers),\n      ListStyleType::String(..) => crate::compat::Feature::StringListStyleType.is_compatible(browsers),\n      ListStyleType::None => true,\n    }\n  }\n}\n\n/// A [counter-style](https://www.w3.org/TR/css-counter-styles-3/#typedef-counter-style) name.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub enum CounterStyle<'i> {\n  /// A predefined counter style name.\n  #[cfg_attr(\n    feature = \"serde\",\n    serde(with = \"crate::serialization::ValueWrapper::<PredefinedCounterStyle>\")\n  )]\n  Predefined(PredefinedCounterStyle),\n  /// A custom counter style name.\n  #[cfg_attr(\n    feature = \"serde\",\n    serde(borrow, with = \"crate::serialization::ValueWrapper::<CustomIdent>\")\n  )]\n  Name(CustomIdent<'i>),\n  /// An inline [`symbols()`](https://www.w3.org/TR/css-counter-styles-3/#symbols-function) definition.\n  Symbols {\n    /// The counter system.\n    #[cfg_attr(feature = \"serde\", serde(default))]\n    system: SymbolsType,\n    /// The symbols.\n    symbols: Vec<Symbol<'i>>,\n  },\n}\n\nmacro_rules! counter_styles {\n  (\n    $(#[$outer:meta])*\n    $vis:vis enum $name:ident {\n      $(\n        $(#[$meta: meta])*\n        $id: ident,\n      )+\n    }\n  ) => {\n    enum_property! {\n      /// A [predefined counter](https://www.w3.org/TR/css-counter-styles-3/#predefined-counters) style.\n      #[allow(missing_docs)]\n      pub enum PredefinedCounterStyle {\n        $(\n           $(#[$meta])*\n           $id,\n        )+\n      }\n    }\n\n    impl IsCompatible for PredefinedCounterStyle {\n      fn is_compatible(&self, browsers: Browsers) -> bool {\n        match self {\n          $(\n            PredefinedCounterStyle::$id => pastey::paste! {\n              crate::compat::Feature::[<$id ListStyleType>].is_compatible(browsers)\n            },\n          )+\n        }\n      }\n    }\n  };\n}\n\ncounter_styles! {\n  /// A [predefined counter](https://www.w3.org/TR/css-counter-styles-3/#predefined-counters) style.\n  #[allow(missing_docs)]\n  pub enum PredefinedCounterStyle {\n    // https://www.w3.org/TR/css-counter-styles-3/#simple-numeric\n    Decimal,\n    DecimalLeadingZero,\n    ArabicIndic,\n    Armenian,\n    UpperArmenian,\n    LowerArmenian,\n    Bengali,\n    Cambodian,\n    Khmer,\n    CjkDecimal,\n    Devanagari,\n    Georgian,\n    Gujarati,\n    Gurmukhi,\n    Hebrew,\n    Kannada,\n    Lao,\n    Malayalam,\n    Mongolian,\n    Myanmar,\n    Oriya,\n    Persian,\n    LowerRoman,\n    UpperRoman,\n    Tamil,\n    Telugu,\n    Thai,\n    Tibetan,\n\n    // https://www.w3.org/TR/css-counter-styles-3/#simple-alphabetic\n    LowerAlpha,\n    LowerLatin,\n    UpperAlpha,\n    UpperLatin,\n    LowerGreek,\n    Hiragana,\n    HiraganaIroha,\n    Katakana,\n    KatakanaIroha,\n\n    // https://www.w3.org/TR/css-counter-styles-3/#simple-symbolic\n    Disc,\n    Circle,\n    Square,\n    DisclosureOpen,\n    DisclosureClosed,\n\n    // https://www.w3.org/TR/css-counter-styles-3/#simple-fixed\n    CjkEarthlyBranch,\n    CjkHeavenlyStem,\n\n    // https://www.w3.org/TR/css-counter-styles-3/#complex-cjk\n    JapaneseInformal,\n    JapaneseFormal,\n    KoreanHangulFormal,\n    KoreanHanjaInformal,\n    KoreanHanjaFormal,\n    SimpChineseInformal,\n    SimpChineseFormal,\n    TradChineseInformal,\n    TradChineseFormal,\n    EthiopicNumeric,\n  }\n}\n\nimpl<'i> Parse<'i> for CounterStyle<'i> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    if let Ok(predefined) = input.try_parse(PredefinedCounterStyle::parse) {\n      return Ok(CounterStyle::Predefined(predefined));\n    }\n\n    if input.try_parse(|input| input.expect_function_matching(\"symbols\")).is_ok() {\n      return input.parse_nested_block(|input| {\n        let t = input.try_parse(SymbolsType::parse).unwrap_or_default();\n\n        let mut symbols = Vec::new();\n        while let Ok(s) = input.try_parse(Symbol::parse) {\n          symbols.push(s);\n        }\n\n        Ok(CounterStyle::Symbols { system: t, symbols })\n      });\n    }\n\n    let name = CustomIdent::parse(input)?;\n    Ok(CounterStyle::Name(name))\n  }\n}\n\nimpl ToCss for CounterStyle<'_> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      CounterStyle::Predefined(style) => style.to_css(dest),\n      CounterStyle::Name(name) => {\n        if let Some(css_module) = &mut dest.css_module {\n          css_module.reference(&name.0, dest.loc.source_index)\n        }\n        name.to_css(dest)\n      }\n      CounterStyle::Symbols { system: t, symbols } => {\n        dest.write_str(\"symbols(\")?;\n        let mut needs_space = false;\n        if *t != SymbolsType::Symbolic {\n          t.to_css(dest)?;\n          needs_space = true;\n        }\n\n        for symbol in symbols {\n          if needs_space {\n            dest.write_char(' ')?;\n          }\n          symbol.to_css(dest)?;\n          needs_space = true;\n        }\n        dest.write_char(')')\n      }\n    }\n  }\n}\n\nimpl IsCompatible for CounterStyle<'_> {\n  fn is_compatible(&self, browsers: Browsers) -> bool {\n    match self {\n      CounterStyle::Name(..) => true,\n      CounterStyle::Predefined(p) => p.is_compatible(browsers),\n      CounterStyle::Symbols { .. } => crate::compat::Feature::SymbolsListStyleType.is_compatible(browsers),\n    }\n  }\n}\n\nenum_property! {\n  /// A [`<symbols-type>`](https://www.w3.org/TR/css-counter-styles-3/#typedef-symbols-type) value,\n  /// as used in the `symbols()` function.\n  ///\n  /// See [CounterStyle](CounterStyle).\n  #[allow(missing_docs)]\n  pub enum SymbolsType {\n    Cyclic,\n    Numeric,\n    Alphabetic,\n    Symbolic,\n    Fixed,\n  }\n}\n\nimpl Default for SymbolsType {\n  fn default() -> Self {\n    SymbolsType::Symbolic\n  }\n}\n\n/// A single [symbol](https://www.w3.org/TR/css-counter-styles-3/#funcdef-symbols) as used in the\n/// `symbols()` function.\n///\n/// See [CounterStyle](CounterStyle).\n#[derive(Debug, Clone, PartialEq, Parse, ToCss)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub enum Symbol<'i> {\n  /// A string.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  String(CSSString<'i>),\n  /// An image.\n  Image(Image<'i>),\n}\n\nenum_property! {\n  /// A value for the [list-style-position](https://www.w3.org/TR/2020/WD-css-lists-3-20201117/#list-style-position-property) property.\n  pub enum ListStylePosition {\n    /// The list marker is placed inside the element.\n    Inside,\n    /// The list marker is placed outside the element.\n    Outside,\n  }\n}\n\nimpl Default for ListStylePosition {\n  fn default() -> ListStylePosition {\n    ListStylePosition::Outside\n  }\n}\n\nimpl IsCompatible for ListStylePosition {\n  fn is_compatible(&self, _browsers: Browsers) -> bool {\n    true\n  }\n}\n\nenum_property! {\n  /// A value for the [marker-side](https://www.w3.org/TR/2020/WD-css-lists-3-20201117/#marker-side) property.\n  #[allow(missing_docs)]\n  pub enum MarkerSide {\n    MatchSelf,\n    MatchParent,\n  }\n}\n\ndefine_shorthand! {\n  /// A value for the [list-style](https://www.w3.org/TR/2020/WD-css-lists-3-20201117/#list-style-property) shorthand property.\n  pub struct ListStyle<'i> {\n    /// The position of the list marker.\n    position: ListStylePosition(ListStylePosition),\n    /// The list marker image.\n    #[cfg_attr(feature = \"serde\", serde(borrow))]\n    image: ListStyleImage(Image<'i>),\n    /// The list style type.\n    list_style_type: ListStyleType(ListStyleType<'i>),\n  }\n}\n\nimpl<'i> Parse<'i> for ListStyle<'i> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let mut position = None;\n    let mut image = None;\n    let mut list_style_type = None;\n    let mut nones = 0;\n\n    loop {\n      // `none` is ambiguous - both list-style-image and list-style-type support it.\n      if input.try_parse(|input| input.expect_ident_matching(\"none\")).is_ok() {\n        nones += 1;\n        if nones > 2 {\n          return Err(input.new_custom_error(ParserError::InvalidValue));\n        }\n        continue;\n      }\n\n      if image.is_none() {\n        if let Ok(val) = input.try_parse(Image::parse) {\n          image = Some(val);\n          continue;\n        }\n      }\n\n      if position.is_none() {\n        if let Ok(val) = input.try_parse(ListStylePosition::parse) {\n          position = Some(val);\n          continue;\n        }\n      }\n\n      if list_style_type.is_none() {\n        if let Ok(val) = input.try_parse(ListStyleType::parse) {\n          list_style_type = Some(val);\n          continue;\n        }\n      }\n\n      break;\n    }\n\n    // Assign the `none` to the opposite property from the one we have a value for,\n    // or both in case neither list-style-image or list-style-type have a value.\n    match (nones, image, list_style_type) {\n      (2, None, None) | (1, None, None) => Ok(ListStyle {\n        position: position.unwrap_or_default(),\n        image: Image::None,\n        list_style_type: ListStyleType::None,\n      }),\n      (1, Some(image), None) => Ok(ListStyle {\n        position: position.unwrap_or_default(),\n        image,\n        list_style_type: ListStyleType::None,\n      }),\n      (1, None, Some(list_style_type)) => Ok(ListStyle {\n        position: position.unwrap_or_default(),\n        image: Image::None,\n        list_style_type,\n      }),\n      (0, image, list_style_type) => Ok(ListStyle {\n        position: position.unwrap_or_default(),\n        image: image.unwrap_or_default(),\n        list_style_type: list_style_type.unwrap_or_default(),\n      }),\n      _ => Err(input.new_custom_error(ParserError::InvalidValue)),\n    }\n  }\n}\n\nimpl<'i> ToCss for ListStyle<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    let mut needs_space = false;\n    if self.position != ListStylePosition::default() {\n      self.position.to_css(dest)?;\n      needs_space = true;\n    }\n\n    if self.image != Image::default() {\n      if needs_space {\n        dest.write_char(' ')?;\n      }\n      self.image.to_css(dest)?;\n      needs_space = true;\n    }\n\n    if self.list_style_type != ListStyleType::default() {\n      if needs_space {\n        dest.write_char(' ')?;\n      }\n      self.list_style_type.to_css(dest)?;\n      needs_space = true;\n    }\n\n    if !needs_space {\n      self.position.to_css(dest)?;\n    }\n\n    Ok(())\n  }\n}\n\nimpl<'i> FallbackValues for ListStyle<'i> {\n  fn get_fallbacks(&mut self, targets: Targets) -> Vec<Self> {\n    self\n      .image\n      .get_fallbacks(targets)\n      .into_iter()\n      .map(|image| ListStyle { image, ..self.clone() })\n      .collect()\n  }\n}\n\nshorthand_handler!(ListStyleHandler -> ListStyle<'i> fallbacks: true {\n  image: ListStyleImage(Image<'i>, fallback: true, image: true),\n  list_style_type: ListStyleType(ListStyleType<'i>),\n  position: ListStylePosition(ListStylePosition),\n});\n"
  },
  {
    "path": "src/properties/margin_padding.rs",
    "content": "use crate::compat::Feature;\nuse crate::context::PropertyHandlerContext;\nuse crate::declaration::{DeclarationBlock, DeclarationList};\nuse crate::error::{ParserError, PrinterError};\nuse crate::logical::PropertyCategory;\nuse crate::macros::{define_shorthand, rect_shorthand, size_shorthand};\nuse crate::printer::Printer;\nuse crate::properties::{Property, PropertyId};\nuse crate::traits::{IsCompatible, Parse, PropertyHandler, Shorthand, ToCss};\nuse crate::values::{length::LengthPercentageOrAuto, rect::Rect, size::Size2D};\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse cssparser::*;\n\nrect_shorthand! {\n  /// A value for the [margin](https://drafts.csswg.org/css-box-4/#propdef-margin) shorthand property.\n  pub struct Margin<LengthPercentageOrAuto> {\n    MarginTop,\n    MarginRight,\n    MarginBottom,\n    MarginLeft\n  }\n}\n\nrect_shorthand! {\n  /// A value for the [padding](https://drafts.csswg.org/css-box-4/#propdef-padding) shorthand property.\n  pub struct Padding<LengthPercentageOrAuto> {\n    PaddingTop,\n    PaddingRight,\n    PaddingBottom,\n    PaddingLeft\n  }\n}\n\nrect_shorthand! {\n  /// A value for the [scroll-margin](https://drafts.csswg.org/css-scroll-snap/#scroll-margin) shorthand property.\n  pub struct ScrollMargin<LengthPercentageOrAuto> {\n    ScrollMarginTop,\n    ScrollMarginRight,\n    ScrollMarginBottom,\n    ScrollMarginLeft\n  }\n}\n\nrect_shorthand! {\n  /// A value for the [scroll-padding](https://drafts.csswg.org/css-scroll-snap/#scroll-padding) shorthand property.\n  pub struct ScrollPadding<LengthPercentageOrAuto> {\n    ScrollPaddingTop,\n    ScrollPaddingRight,\n    ScrollPaddingBottom,\n    ScrollPaddingLeft\n  }\n}\n\nrect_shorthand! {\n  /// A value for the [inset](https://drafts.csswg.org/css-logical/#propdef-inset) shorthand property.\n  pub struct Inset<LengthPercentageOrAuto> {\n    Top,\n    Right,\n    Bottom,\n    Left\n  }\n}\n\nsize_shorthand! {\n  /// A value for the [margin-block](https://drafts.csswg.org/css-logical/#propdef-margin-block) shorthand property.\n  pub struct MarginBlock<LengthPercentageOrAuto> {\n    /// The block start value.\n    block_start: MarginBlockStart,\n    /// The block end value.\n    block_end: MarginBlockEnd,\n  }\n}\n\nsize_shorthand! {\n  /// A value for the [margin-inline](https://drafts.csswg.org/css-logical/#propdef-margin-inline) shorthand property.\n  pub struct MarginInline<LengthPercentageOrAuto> {\n    /// The inline start value.\n    inline_start: MarginInlineStart,\n    /// The inline end value.\n    inline_end: MarginInlineEnd,\n  }\n}\n\nsize_shorthand! {\n  /// A value for the [padding-block](https://drafts.csswg.org/css-logical/#propdef-padding-block) shorthand property.\n  pub struct PaddingBlock<LengthPercentageOrAuto> {\n     /// The block start value.\n    block_start: PaddingBlockStart,\n    /// The block end value.\n    block_end: PaddingBlockEnd,\n  }\n}\n\nsize_shorthand! {\n  /// A value for the [padding-inline](https://drafts.csswg.org/css-logical/#propdef-padding-inline) shorthand property.\n  pub struct PaddingInline<LengthPercentageOrAuto> {\n    /// The inline start value.\n    inline_start: PaddingInlineStart,\n    /// The inline end value.\n    inline_end: PaddingInlineEnd,\n  }\n}\n\nsize_shorthand! {\n  /// A value for the [scroll-margin-block](https://drafts.csswg.org/css-scroll-snap/#propdef-scroll-margin-block) shorthand property.\n  pub struct ScrollMarginBlock<LengthPercentageOrAuto> {\n     /// The block start value.\n    block_start: ScrollMarginBlockStart,\n    /// The block end value.\n    block_end: ScrollMarginBlockEnd,\n  }\n}\n\nsize_shorthand! {\n  /// A value for the [scroll-margin-inline](https://drafts.csswg.org/css-scroll-snap/#propdef-scroll-margin-inline) shorthand property.\n  pub struct ScrollMarginInline<LengthPercentageOrAuto> {\n    /// The inline start value.\n    inline_start: ScrollMarginInlineStart,\n    /// The inline end value.\n    inline_end: ScrollMarginInlineEnd,\n  }\n}\n\nsize_shorthand! {\n  /// A value for the [scroll-padding-block](https://drafts.csswg.org/css-scroll-snap/#propdef-scroll-padding-block) shorthand property.\n  pub struct ScrollPaddingBlock<LengthPercentageOrAuto> {\n     /// The block start value.\n    block_start: ScrollPaddingBlockStart,\n    /// The block end value.\n    block_end: ScrollPaddingBlockEnd,\n  }\n}\n\nsize_shorthand! {\n  /// A value for the [scroll-padding-inline](https://drafts.csswg.org/css-scroll-snap/#propdef-scroll-padding-inline) shorthand property.\n  pub struct ScrollPaddingInline<LengthPercentageOrAuto> {\n    /// The inline start value.\n    inline_start: ScrollPaddingInlineStart,\n    /// The inline end value.\n    inline_end: ScrollPaddingInlineEnd,\n  }\n}\n\nsize_shorthand! {\n  /// A value for the [inset-block](https://drafts.csswg.org/css-logical/#propdef-inset-block) shorthand property.\n  pub struct InsetBlock<LengthPercentageOrAuto> {\n     /// The block start value.\n    block_start: InsetBlockStart,\n    /// The block end value.\n    block_end: InsetBlockEnd,\n  }\n}\n\nsize_shorthand! {\n  /// A value for the [inset-inline](https://drafts.csswg.org/css-logical/#propdef-inset-inline) shorthand property.\n  pub struct InsetInline<LengthPercentageOrAuto> {\n    /// The inline start value.\n    inline_start: InsetInlineStart,\n    /// The inline end value.\n    inline_end: InsetInlineEnd,\n  }\n}\n\nmacro_rules! side_handler {\n  ($name: ident, $top: ident, $bottom: ident, $left: ident, $right: ident, $block_start: ident, $block_end: ident, $inline_start: ident, $inline_end: ident, $shorthand: ident, $block_shorthand: ident, $inline_shorthand: ident, $shorthand_category: ident $(, $feature: ident, $shorthand_feature: ident)?) => {\n    #[derive(Debug, Default)]\n    pub(crate) struct $name<'i> {\n      top: Option<LengthPercentageOrAuto>,\n      bottom: Option<LengthPercentageOrAuto>,\n      left: Option<LengthPercentageOrAuto>,\n      right: Option<LengthPercentageOrAuto>,\n      block_start: Option<Property<'i>>,\n      block_end: Option<Property<'i>>,\n      inline_start: Option<Property<'i>>,\n      inline_end: Option<Property<'i>>,\n      has_any: bool,\n      category: PropertyCategory\n    }\n\n    impl<'i> PropertyHandler<'i> for $name<'i> {\n      fn handle_property(&mut self, property: &Property<'i>, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) -> bool {\n        use Property::*;\n\n        macro_rules! flush {\n          ($key: ident, $val: expr, $category: ident) => {{\n            // If the category changes betweet logical and physical,\n            // or if the value contains syntax that isn't supported across all targets,\n            // preserve the previous value as a fallback.\n            if PropertyCategory::$category != self.category || (self.$key.is_some() && matches!(context.targets.browsers, Some(targets) if !$val.is_compatible(targets))) {\n              self.flush(dest, context);\n            }\n          }}\n        }\n\n        macro_rules! property {\n          ($key: ident, $val: ident, $category: ident) => {{\n            flush!($key, $val, $category);\n            self.$key = Some($val.clone());\n            self.category = PropertyCategory::$category;\n            self.has_any = true;\n          }};\n        }\n\n        macro_rules! logical_property {\n          ($prop: ident, $val: expr) => {{\n            // Assume unparsed properties might contain unsupported syntax that we must preserve as a fallback.\n            if self.category != PropertyCategory::Logical || (self.$prop.is_some() && matches!($val, Property::Unparsed(_))) {\n              self.flush(dest, context);\n            }\n\n            self.$prop = Some($val);\n            self.category = PropertyCategory::Logical;\n            self.has_any = true;\n          }};\n        }\n\n        match &property {\n          $top(val) => property!(top, val, Physical),\n          $bottom(val) => property!(bottom, val, Physical),\n          $left(val) => property!(left, val, Physical),\n          $right(val) => property!(right, val, Physical),\n          $block_start(val) => {\n            flush!(block_start, val, Logical);\n            logical_property!(block_start, property.clone());\n          },\n          $block_end(val) => {\n            flush!(block_end, val, Logical);\n            logical_property!(block_end, property.clone());\n          },\n          $inline_start(val) => {\n            flush!(inline_start, val, Logical);\n            logical_property!(inline_start, property.clone())\n          },\n          $inline_end(val) => {\n            flush!(inline_end, val, Logical);\n            logical_property!(inline_end, property.clone());\n          },\n          $block_shorthand(val) => {\n            flush!(block_start, val.block_start, Logical);\n            flush!(block_end, val.block_end, Logical);\n            logical_property!(block_start, Property::$block_start(val.block_start.clone()));\n            logical_property!(block_end, Property::$block_end(val.block_end.clone()));\n          },\n          $inline_shorthand(val) => {\n            flush!(inline_start, val.inline_start, Logical);\n            flush!(inline_end, val.inline_end, Logical);\n            logical_property!(inline_start, Property::$inline_start(val.inline_start.clone()));\n            logical_property!(inline_end, Property::$inline_end(val.inline_end.clone()));\n          },\n          $shorthand(val) => {\n            flush!(top, val.top, $shorthand_category);\n            flush!(right, val.right, $shorthand_category);\n            flush!(bottom, val.bottom, $shorthand_category);\n            flush!(left, val.left, $shorthand_category);\n            self.top = Some(val.top.clone());\n            self.right = Some(val.right.clone());\n            self.bottom = Some(val.bottom.clone());\n            self.left = Some(val.left.clone());\n            self.block_start = None;\n            self.block_end = None;\n            self.inline_start = None;\n            self.inline_end = None;\n            self.has_any = true;\n          }\n          Unparsed(val) if matches!(val.property_id, PropertyId::$top | PropertyId::$bottom | PropertyId::$left | PropertyId::$right | PropertyId::$block_start | PropertyId::$block_end | PropertyId::$inline_start | PropertyId::$inline_end | PropertyId::$block_shorthand | PropertyId::$inline_shorthand | PropertyId::$shorthand) => {\n            // Even if we weren't able to parse the value (e.g. due to var() references),\n            // we can still add vendor prefixes to the property itself.\n            match &val.property_id {\n              PropertyId::$block_start => logical_property!(block_start, property.clone()),\n              PropertyId::$block_end => logical_property!(block_end, property.clone()),\n              PropertyId::$inline_start => logical_property!(inline_start, property.clone()),\n              PropertyId::$inline_end => logical_property!(inline_end, property.clone()),\n              _ => {\n                self.flush(dest, context);\n                dest.push(property.clone());\n              }\n            }\n          }\n          _ => return false\n        }\n\n        true\n      }\n\n      fn finalize(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {\n        self.flush(dest, context);\n      }\n    }\n\n    impl<'i> $name<'i> {\n      fn flush(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {\n        if !self.has_any {\n          return\n        }\n\n        self.has_any = false;\n\n        let top = std::mem::take(&mut self.top);\n        let bottom = std::mem::take(&mut self.bottom);\n        let left = std::mem::take(&mut self.left);\n        let right = std::mem::take(&mut self.right);\n        let logical_supported = true $(&& !context.should_compile_logical(Feature::$feature))?;\n\n        if (PropertyCategory::$shorthand_category != PropertyCategory::Logical || logical_supported) && top.is_some() && bottom.is_some() && left.is_some() && right.is_some() {\n          dest.push(Property::$shorthand($shorthand {\n            top: top.unwrap(),\n            right: right.unwrap(),\n            bottom: bottom.unwrap(),\n            left: left.unwrap()\n          }));\n        } else {\n          if let Some(val) = top {\n            dest.push(Property::$top(val));\n          }\n\n          if let Some(val) = bottom {\n            dest.push(Property::$bottom(val));\n          }\n\n          if let Some(val) = left {\n            dest.push(Property::$left(val));\n          }\n\n          if let Some(val) = right {\n            dest.push(Property::$right(val));\n          }\n        }\n\n        let block_start = std::mem::take(&mut self.block_start);\n        let block_end = std::mem::take(&mut self.block_end);\n        let inline_start = std::mem::take(&mut self.inline_start);\n        let inline_end = std::mem::take(&mut self.inline_end);\n\n        macro_rules! logical_side {\n          ($start: ident, $end: ident, $shorthand_prop: ident, $start_prop: ident, $end_prop: ident) => {\n            let shorthand_supported = logical_supported $(&& !context.should_compile_logical(Feature::$shorthand_feature))?;\n            if let (Some(Property::$start_prop(start)), Some(Property::$end_prop(end)), true) = (&$start, &$end, shorthand_supported) {\n              dest.push(Property::$shorthand_prop($shorthand_prop {\n                $start: start.clone(),\n                $end: end.clone()\n              }));\n            } else {\n              if let Some(val) = $start {\n                dest.push(val);\n              }\n\n              if let Some(val) = $end {\n                dest.push(val);\n              }\n            }\n          };\n        }\n\n        macro_rules! prop {\n          ($val: ident, $logical: ident, $physical: ident) => {\n            match $val {\n              Some(Property::$logical(val)) => {\n                dest.push(Property::$physical(val));\n              }\n              Some(Property::Unparsed(val)) => {\n                dest.push(Property::Unparsed(val.with_property_id(PropertyId::$physical)));\n              }\n              _ => {}\n            }\n          }\n        }\n\n        if logical_supported {\n          logical_side!(block_start, block_end, $block_shorthand, $block_start, $block_end);\n        } else {\n          prop!(block_start, $block_start, $top);\n          prop!(block_end, $block_end, $bottom);\n        }\n\n        if logical_supported {\n          logical_side!(inline_start, inline_end, $inline_shorthand, $inline_start, $inline_end);\n        } else if inline_start.is_some() || inline_end.is_some() {\n          if matches!((&inline_start, &inline_end), (Some(Property::$inline_start(start)), Some(Property::$inline_end(end))) if start == end) {\n            prop!(inline_start, $inline_start, $left);\n            prop!(inline_end, $inline_end, $right);\n          } else {\n            macro_rules! logical_prop {\n              ($val: ident, $logical: ident, $ltr: ident, $rtl: ident) => {\n                match $val {\n                  Some(Property::$logical(val)) => {\n                    context.add_logical_rule(\n                      Property::$ltr(val.clone()),\n                      Property::$rtl(val)\n                    );\n                  }\n                  Some(Property::Unparsed(val)) => {\n                    context.add_logical_rule(\n                      Property::Unparsed(val.with_property_id(PropertyId::$ltr)),\n                      Property::Unparsed(val.with_property_id(PropertyId::$rtl))\n                    );\n                  }\n                  _ => {}\n                }\n              }\n            }\n\n            logical_prop!(inline_start, $inline_start, $left, $right);\n            logical_prop!(inline_end, $inline_end, $right, $left);\n          }\n        }\n      }\n    }\n  };\n}\n\nside_handler!(\n  MarginHandler,\n  MarginTop,\n  MarginBottom,\n  MarginLeft,\n  MarginRight,\n  MarginBlockStart,\n  MarginBlockEnd,\n  MarginInlineStart,\n  MarginInlineEnd,\n  Margin,\n  MarginBlock,\n  MarginInline,\n  Physical,\n  LogicalMargin,\n  LogicalMarginShorthand\n);\n\nside_handler!(\n  PaddingHandler,\n  PaddingTop,\n  PaddingBottom,\n  PaddingLeft,\n  PaddingRight,\n  PaddingBlockStart,\n  PaddingBlockEnd,\n  PaddingInlineStart,\n  PaddingInlineEnd,\n  Padding,\n  PaddingBlock,\n  PaddingInline,\n  Physical,\n  LogicalPadding,\n  LogicalPaddingShorthand\n);\n\nside_handler!(\n  ScrollMarginHandler,\n  ScrollMarginTop,\n  ScrollMarginBottom,\n  ScrollMarginLeft,\n  ScrollMarginRight,\n  ScrollMarginBlockStart,\n  ScrollMarginBlockEnd,\n  ScrollMarginInlineStart,\n  ScrollMarginInlineEnd,\n  ScrollMargin,\n  ScrollMarginBlock,\n  ScrollMarginInline,\n  Physical\n);\n\nside_handler!(\n  ScrollPaddingHandler,\n  ScrollPaddingTop,\n  ScrollPaddingBottom,\n  ScrollPaddingLeft,\n  ScrollPaddingRight,\n  ScrollPaddingBlockStart,\n  ScrollPaddingBlockEnd,\n  ScrollPaddingInlineStart,\n  ScrollPaddingInlineEnd,\n  ScrollPadding,\n  ScrollPaddingBlock,\n  ScrollPaddingInline,\n  Physical\n);\n\nside_handler!(\n  InsetHandler,\n  Top,\n  Bottom,\n  Left,\n  Right,\n  InsetBlockStart,\n  InsetBlockEnd,\n  InsetInlineStart,\n  InsetInlineEnd,\n  Inset,\n  InsetBlock,\n  InsetInline,\n  Logical,\n  LogicalInset,\n  LogicalInset\n);\n"
  },
  {
    "path": "src/properties/masking.rs",
    "content": "//! CSS properties related to clipping and masking.\n\nuse super::background::{BackgroundRepeat, BackgroundSize};\nuse super::border_image::{BorderImage, BorderImageRepeat, BorderImageSideWidth, BorderImageSlice};\nuse super::PropertyId;\nuse crate::context::PropertyHandlerContext;\nuse crate::declaration::{DeclarationBlock, DeclarationList};\nuse crate::error::{ParserError, PrinterError};\nuse crate::macros::{define_list_shorthand, define_shorthand, enum_property, property_bitflags};\nuse crate::prefixes::Feature;\nuse crate::printer::Printer;\nuse crate::properties::Property;\nuse crate::targets::{Browsers, Targets};\nuse crate::traits::{FallbackValues, IsCompatible, Parse, PropertyHandler, Shorthand, ToCss};\nuse crate::values::image::ImageFallback;\nuse crate::values::length::LengthOrNumber;\nuse crate::values::rect::Rect;\nuse crate::values::{image::Image, position::Position, shape::BasicShape, url::Url};\nuse crate::vendor_prefix::VendorPrefix;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse cssparser::*;\nuse itertools::izip;\nuse smallvec::SmallVec;\n\nenum_property! {\n  /// A value for the [mask-type](https://www.w3.org/TR/css-masking-1/#the-mask-type) property.\n  pub enum MaskType {\n    /// The luminance values of the mask is used.\n    Luminance,\n    /// The alpha values of the mask is used.\n    Alpha,\n  }\n}\n\nenum_property! {\n  /// A value for the [mask-mode](https://www.w3.org/TR/css-masking-1/#the-mask-mode) property.\n  pub enum MaskMode {\n    /// The luminance values of the mask image is used.\n    Luminance,\n    /// The alpha values of the mask image is used.\n    Alpha,\n    /// If an SVG source is used, the value matches the `mask-type` property. Otherwise, the alpha values are used.\n    MatchSource,\n  }\n}\n\nimpl Default for MaskMode {\n  fn default() -> MaskMode {\n    MaskMode::MatchSource\n  }\n}\n\nenum_property! {\n  /// A value for the [-webkit-mask-source-type](https://github.com/WebKit/WebKit/blob/6eece09a1c31e47489811edd003d1e36910e9fd3/Source/WebCore/css/CSSProperties.json#L6578-L6587)\n  /// property.\n  ///\n  /// See also [MaskMode](MaskMode).\n  pub enum WebKitMaskSourceType {\n    /// Equivalent to `match-source` in the standard `mask-mode` syntax.\n    Auto,\n    /// The luminance values of the mask image is used.\n    Luminance,\n    /// The alpha values of the mask image is used.\n    Alpha,\n  }\n}\n\nimpl From<MaskMode> for WebKitMaskSourceType {\n  fn from(mode: MaskMode) -> WebKitMaskSourceType {\n    match mode {\n      MaskMode::Luminance => WebKitMaskSourceType::Luminance,\n      MaskMode::Alpha => WebKitMaskSourceType::Alpha,\n      MaskMode::MatchSource => WebKitMaskSourceType::Auto,\n    }\n  }\n}\n\nenum_property! {\n  /// A [`<geometry-box>`](https://www.w3.org/TR/css-masking-1/#typedef-geometry-box) value\n  /// as used in the `mask-clip` and `clip-path` properties.\n  pub enum GeometryBox {\n    /// The painted content is clipped to the content box.\n    BorderBox,\n    /// The painted content is clipped to the padding box.\n    PaddingBox,\n    /// The painted content is clipped to the border box.\n    ContentBox,\n    /// The painted content is clipped to the margin box.\n    MarginBox,\n    /// The painted content is clipped to the object bounding box.\n    FillBox,\n    /// The painted content is clipped to the stroke bounding box.\n    StrokeBox,\n    /// Uses the nearest SVG viewport as reference box.\n    ViewBox,\n  }\n}\n\nimpl Default for GeometryBox {\n  fn default() -> GeometryBox {\n    GeometryBox::BorderBox\n  }\n}\n\n/// A value for the [mask-clip](https://www.w3.org/TR/css-masking-1/#the-mask-clip) property.\n#[derive(Debug, Clone, PartialEq, Parse, ToCss)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum MaskClip {\n  /// A geometry box.\n  GeometryBox(GeometryBox),\n  /// The painted content is not clipped.\n  NoClip,\n}\n\nimpl IsCompatible for MaskClip {\n  fn is_compatible(&self, browsers: Browsers) -> bool {\n    match self {\n      MaskClip::GeometryBox(g) => g.is_compatible(browsers),\n      MaskClip::NoClip => true,\n    }\n  }\n}\n\nimpl Into<MaskClip> for GeometryBox {\n  fn into(self) -> MaskClip {\n    MaskClip::GeometryBox(self.clone())\n  }\n}\n\nimpl IsCompatible for GeometryBox {\n  fn is_compatible(&self, _browsers: Browsers) -> bool {\n    true\n  }\n}\n\nenum_property! {\n  /// A value for the [mask-composite](https://www.w3.org/TR/css-masking-1/#the-mask-composite) property.\n  pub enum MaskComposite {\n    /// The source is placed over the destination.\n    Add,\n    /// The source is placed, where it falls outside of the destination.\n    Subtract,\n    /// The parts of source that overlap the destination, replace the destination.\n    Intersect,\n    /// The non-overlapping regions of source and destination are combined.\n    Exclude,\n  }\n}\n\nimpl Default for MaskComposite {\n  fn default() -> MaskComposite {\n    MaskComposite::Add\n  }\n}\n\nenum_property! {\n  /// A value for the [-webkit-mask-composite](https://developer.mozilla.org/en-US/docs/Web/CSS/-webkit-mask-composite)\n  /// property.\n  ///\n  /// See also [MaskComposite](MaskComposite).\n  #[allow(missing_docs)]\n  pub enum WebKitMaskComposite {\n    Clear,\n    Copy,\n    /// Equivalent to `add` in the standard `mask-composite` syntax.\n    SourceOver,\n    /// Equivalent to `intersect` in the standard `mask-composite` syntax.\n    SourceIn,\n    /// Equivalent to `subtract` in the standard `mask-composite` syntax.\n    SourceOut,\n    SourceAtop,\n    DestinationOver,\n    DestinationIn,\n    DestinationOut,\n    DestinationAtop,\n    /// Equivalent to `exclude` in the standard `mask-composite` syntax.\n    Xor,\n  }\n}\n\nimpl From<MaskComposite> for WebKitMaskComposite {\n  fn from(composite: MaskComposite) -> WebKitMaskComposite {\n    match composite {\n      MaskComposite::Add => WebKitMaskComposite::SourceOver,\n      MaskComposite::Subtract => WebKitMaskComposite::SourceOut,\n      MaskComposite::Intersect => WebKitMaskComposite::SourceIn,\n      MaskComposite::Exclude => WebKitMaskComposite::Xor,\n    }\n  }\n}\n\ndefine_list_shorthand! {\n  /// A value for the [mask](https://www.w3.org/TR/css-masking-1/#the-mask) shorthand property.\n  pub struct Mask<'i>(VendorPrefix) {\n    /// The mask image.\n    #[cfg_attr(feature = \"serde\", serde(borrow))]\n    image: MaskImage(Image<'i>, VendorPrefix),\n    /// The position of the mask.\n    position: MaskPosition(Position, VendorPrefix),\n    /// The size of the mask image.\n    size: MaskSize(BackgroundSize, VendorPrefix),\n    /// How the mask repeats.\n    repeat: MaskRepeat(BackgroundRepeat, VendorPrefix),\n    /// The box in which the mask is clipped.\n    clip: MaskClip(MaskClip, VendorPrefix),\n    /// The origin of the mask.\n    origin: MaskOrigin(GeometryBox, VendorPrefix),\n    /// How the mask is composited with the element.\n    composite: MaskComposite(MaskComposite),\n    /// How the mask image is interpreted.\n    mode: MaskMode(MaskMode),\n  }\n}\n\nimpl<'i> Parse<'i> for Mask<'i> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let mut image: Option<Image> = None;\n    let mut position: Option<Position> = None;\n    let mut size: Option<BackgroundSize> = None;\n    let mut repeat: Option<BackgroundRepeat> = None;\n    let mut clip: Option<MaskClip> = None;\n    let mut origin: Option<GeometryBox> = None;\n    let mut composite: Option<MaskComposite> = None;\n    let mut mode: Option<MaskMode> = None;\n\n    loop {\n      if image.is_none() {\n        if let Ok(value) = input.try_parse(Image::parse) {\n          image = Some(value);\n          continue;\n        }\n      }\n\n      if position.is_none() {\n        if let Ok(value) = input.try_parse(Position::parse) {\n          position = Some(value);\n          size = input\n            .try_parse(|input| {\n              input.expect_delim('/')?;\n              BackgroundSize::parse(input)\n            })\n            .ok();\n          continue;\n        }\n      }\n\n      if repeat.is_none() {\n        if let Ok(value) = input.try_parse(BackgroundRepeat::parse) {\n          repeat = Some(value);\n          continue;\n        }\n      }\n\n      if origin.is_none() {\n        if let Ok(value) = input.try_parse(GeometryBox::parse) {\n          origin = Some(value);\n          continue;\n        }\n      }\n\n      if clip.is_none() {\n        if let Ok(value) = input.try_parse(MaskClip::parse) {\n          clip = Some(value);\n          continue;\n        }\n      }\n\n      if composite.is_none() {\n        if let Ok(value) = input.try_parse(MaskComposite::parse) {\n          composite = Some(value);\n          continue;\n        }\n      }\n\n      if mode.is_none() {\n        if let Ok(value) = input.try_parse(MaskMode::parse) {\n          mode = Some(value);\n          continue;\n        }\n      }\n\n      break;\n    }\n\n    if clip.is_none() {\n      if let Some(origin) = origin {\n        clip = Some(origin.into());\n      }\n    }\n\n    Ok(Mask {\n      image: image.unwrap_or_default(),\n      position: position.unwrap_or_default(),\n      repeat: repeat.unwrap_or_default(),\n      size: size.unwrap_or_default(),\n      origin: origin.unwrap_or(GeometryBox::BorderBox),\n      clip: clip.unwrap_or(GeometryBox::BorderBox.into()),\n      composite: composite.unwrap_or(MaskComposite::Add),\n      mode: mode.unwrap_or(MaskMode::MatchSource),\n    })\n  }\n}\n\nimpl<'i> ToCss for Mask<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    self.image.to_css(dest)?;\n\n    if self.position != Position::default() || self.size != BackgroundSize::default() {\n      dest.write_char(' ')?;\n      self.position.to_css(dest)?;\n\n      if self.size != BackgroundSize::default() {\n        dest.delim('/', true)?;\n        self.size.to_css(dest)?;\n      }\n    }\n\n    if self.repeat != BackgroundRepeat::default() {\n      dest.write_char(' ')?;\n      self.repeat.to_css(dest)?;\n    }\n\n    if self.origin != GeometryBox::BorderBox || self.clip != GeometryBox::BorderBox.into() {\n      dest.write_char(' ')?;\n      self.origin.to_css(dest)?;\n\n      if self.clip != self.origin.into() {\n        dest.write_char(' ')?;\n        self.clip.to_css(dest)?;\n      }\n    }\n\n    if self.composite != MaskComposite::default() {\n      dest.write_char(' ')?;\n      self.composite.to_css(dest)?;\n    }\n\n    if self.mode != MaskMode::default() {\n      dest.write_char(' ')?;\n      self.mode.to_css(dest)?;\n    }\n\n    Ok(())\n  }\n}\n\n// TODO: shorthand handler?\nimpl<'i> ImageFallback<'i> for Mask<'i> {\n  #[inline]\n  fn get_image(&self) -> &Image<'i> {\n    &self.image\n  }\n\n  #[inline]\n  fn with_image(&self, image: Image<'i>) -> Self {\n    Mask { image, ..self.clone() }\n  }\n}\n\n/// A value for the [clip-path](https://www.w3.org/TR/css-masking-1/#the-clip-path) property.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub enum ClipPath<'i> {\n  /// No clip path.\n  None,\n  /// A url reference to an SVG path element.\n  #[cfg_attr(feature = \"serde\", serde(borrow, with = \"crate::serialization::ValueWrapper::<Url>\"))]\n  Url(Url<'i>),\n  /// A basic shape, positioned according to the reference box.\n  #[cfg_attr(feature = \"serde\", serde(rename_all = \"camelCase\"))]\n  Shape {\n    /// A basic shape.\n    shape: Box<BasicShape>,\n    /// A reference box that the shape is positioned according to.\n    reference_box: GeometryBox,\n  },\n  /// A reference box.\n  #[cfg_attr(feature = \"serde\", serde(with = \"crate::serialization::ValueWrapper::<GeometryBox>\"))]\n  Box(GeometryBox),\n}\n\nimpl<'i> Parse<'i> for ClipPath<'i> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    if let Ok(url) = input.try_parse(Url::parse) {\n      return Ok(ClipPath::Url(url));\n    }\n\n    if let Ok(shape) = input.try_parse(BasicShape::parse) {\n      let b = input.try_parse(GeometryBox::parse).unwrap_or_default();\n      return Ok(ClipPath::Shape {\n        shape: Box::new(shape),\n        reference_box: b,\n      });\n    }\n\n    if let Ok(b) = input.try_parse(GeometryBox::parse) {\n      if let Ok(shape) = input.try_parse(BasicShape::parse) {\n        return Ok(ClipPath::Shape {\n          shape: Box::new(shape),\n          reference_box: b,\n        });\n      }\n      return Ok(ClipPath::Box(b));\n    }\n\n    input.expect_ident_matching(\"none\")?;\n    Ok(ClipPath::None)\n  }\n}\n\nimpl<'i> ToCss for ClipPath<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      ClipPath::None => dest.write_str(\"none\"),\n      ClipPath::Url(url) => url.to_css(dest),\n      ClipPath::Shape {\n        shape,\n        reference_box: b,\n      } => {\n        shape.to_css(dest)?;\n        if *b != GeometryBox::default() {\n          dest.write_char(' ')?;\n          b.to_css(dest)?;\n        }\n        Ok(())\n      }\n      ClipPath::Box(b) => b.to_css(dest),\n    }\n  }\n}\n\nenum_property! {\n  /// A value for the [mask-border-mode](https://www.w3.org/TR/css-masking-1/#the-mask-border-mode) property.\n  pub enum MaskBorderMode {\n    /// The luminance values of the mask image is used.\n    Luminance,\n    /// The alpha values of the mask image is used.\n    Alpha,\n  }\n}\n\nimpl Default for MaskBorderMode {\n  fn default() -> MaskBorderMode {\n    MaskBorderMode::Alpha\n  }\n}\n\ndefine_shorthand! {\n  /// A value for the [mask-border](https://www.w3.org/TR/css-masking-1/#the-mask-border) shorthand property.\n  #[derive(Default)]\n  pub struct MaskBorder<'i> {\n    /// The mask image.\n    #[cfg_attr(feature = \"serde\", serde(borrow))]\n    source: MaskBorderSource(Image<'i>),\n    /// The offsets that define where the image is sliced.\n    slice: MaskBorderSlice(BorderImageSlice),\n    /// The width of the mask image.\n    width: MaskBorderWidth(Rect<BorderImageSideWidth>),\n    /// The amount that the image extends beyond the border box.\n    outset: MaskBorderOutset(Rect<LengthOrNumber>),\n    /// How the mask image is scaled and tiled.\n    repeat: MaskBorderRepeat(BorderImageRepeat),\n    /// How the mask image is interpreted.\n    mode: MaskBorderMode(MaskBorderMode),\n  }\n}\n\nimpl<'i> Parse<'i> for MaskBorder<'i> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let mut mode: Option<MaskBorderMode> = None;\n    let border_image = BorderImage::parse_with_callback(input, |input| {\n      if mode.is_none() {\n        if let Ok(value) = input.try_parse(MaskBorderMode::parse) {\n          mode = Some(value);\n          return true;\n        }\n      }\n      false\n    });\n\n    if border_image.is_ok() || mode.is_some() {\n      let border_image = border_image.unwrap_or_default();\n      Ok(MaskBorder {\n        source: border_image.source,\n        slice: border_image.slice,\n        width: border_image.width,\n        outset: border_image.outset,\n        repeat: border_image.repeat,\n        mode: mode.unwrap_or_default(),\n      })\n    } else {\n      Err(input.new_custom_error(ParserError::InvalidDeclaration))\n    }\n  }\n}\n\nimpl<'i> ToCss for MaskBorder<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    BorderImage::to_css_internal(&self.source, &self.slice, &self.width, &self.outset, &self.repeat, dest)?;\n    if self.mode != MaskBorderMode::default() {\n      dest.write_char(' ')?;\n      self.mode.to_css(dest)?;\n    }\n    Ok(())\n  }\n}\n\nimpl<'i> FallbackValues for MaskBorder<'i> {\n  fn get_fallbacks(&mut self, targets: Targets) -> Vec<Self> {\n    self\n      .source\n      .get_fallbacks(targets)\n      .into_iter()\n      .map(|source| MaskBorder { source, ..self.clone() })\n      .collect()\n  }\n}\n\nimpl<'i> Into<BorderImage<'i>> for MaskBorder<'i> {\n  fn into(self) -> BorderImage<'i> {\n    BorderImage {\n      source: self.source,\n      slice: self.slice,\n      width: self.width,\n      outset: self.outset,\n      repeat: self.repeat,\n    }\n  }\n}\n\nproperty_bitflags! {\n  #[derive(Default, Debug)]\n  struct MaskProperty: u16 {\n    const MaskImage(_vp) = 1 << 0;\n    const MaskPosition(_vp) = 1 << 1;\n    const MaskSize(_vp) = 1 << 2;\n    const MaskRepeat(_vp) = 1 << 3;\n    const MaskClip(_vp) = 1 << 4;\n    const MaskOrigin(_vp) = 1 << 5;\n    const MaskComposite = 1 << 6;\n    const MaskMode = 1 << 7;\n    const Mask(_vp) = Self::MaskImage.bits() | Self::MaskPosition.bits() | Self::MaskSize.bits() | Self::MaskRepeat.bits() | Self::MaskClip.bits() | Self::MaskOrigin.bits() | Self::MaskComposite.bits() | Self::MaskMode.bits();\n\n    const MaskBorderSource = 1 << 7;\n    const MaskBorderMode = 1 << 8;\n    const MaskBorderSlice = 1 << 9;\n    const MaskBorderWidth = 1 << 10;\n    const MaskBorderOutset = 1 << 11;\n    const MaskBorderRepeat = 1 << 12;\n    const MaskBorder = Self::MaskBorderSource.bits() | Self::MaskBorderMode.bits() | Self::MaskBorderSlice.bits() | Self::MaskBorderWidth.bits() | Self::MaskBorderOutset.bits() | Self::MaskBorderRepeat.bits();\n  }\n}\n\n#[derive(Default)]\npub(crate) struct MaskHandler<'i> {\n  images: Option<(SmallVec<[Image<'i>; 1]>, VendorPrefix)>,\n  positions: Option<(SmallVec<[Position; 1]>, VendorPrefix)>,\n  sizes: Option<(SmallVec<[BackgroundSize; 1]>, VendorPrefix)>,\n  repeats: Option<(SmallVec<[BackgroundRepeat; 1]>, VendorPrefix)>,\n  clips: Option<(SmallVec<[MaskClip; 1]>, VendorPrefix)>,\n  origins: Option<(SmallVec<[GeometryBox; 1]>, VendorPrefix)>,\n  composites: Option<SmallVec<[MaskComposite; 1]>>,\n  modes: Option<SmallVec<[MaskMode; 1]>>,\n  border_source: Option<(Image<'i>, VendorPrefix)>,\n  border_mode: Option<MaskBorderMode>,\n  border_slice: Option<(BorderImageSlice, VendorPrefix)>,\n  border_width: Option<(Rect<BorderImageSideWidth>, VendorPrefix)>,\n  border_outset: Option<(Rect<LengthOrNumber>, VendorPrefix)>,\n  border_repeat: Option<(BorderImageRepeat, VendorPrefix)>,\n  flushed_properties: MaskProperty,\n  has_any: bool,\n}\n\nimpl<'i> PropertyHandler<'i> for MaskHandler<'i> {\n  fn handle_property(\n    &mut self,\n    property: &Property<'i>,\n    dest: &mut DeclarationList<'i>,\n    context: &mut PropertyHandlerContext<'i, '_>,\n  ) -> bool {\n    macro_rules! maybe_flush {\n      ($prop: ident, $val: expr, $vp: expr) => {{\n        // If two vendor prefixes for the same property have different\n        // values, we need to flush what we have immediately to preserve order.\n        if let Some((val, prefixes)) = &self.$prop {\n          if val != $val && !prefixes.contains(*$vp) {\n            self.flush(dest, context);\n          }\n        }\n\n        if self.$prop.is_some() && matches!(context.targets.browsers, Some(targets) if !$val.is_compatible(targets)) {\n          self.flush(dest, context);\n        }\n      }};\n    }\n\n    macro_rules! property {\n      ($prop: ident, $val: expr, $vp: expr) => {{\n        maybe_flush!($prop, $val, $vp);\n\n        // Otherwise, update the value and add the prefix.\n        if let Some((val, prefixes)) = &mut self.$prop {\n          *val = $val.clone();\n          *prefixes |= *$vp;\n        } else {\n          self.$prop = Some(($val.clone(), *$vp));\n          self.has_any = true;\n        }\n      }};\n    }\n\n    macro_rules! border_shorthand {\n      ($val: expr, $vp: expr) => {\n        let source = $val.source.clone();\n        maybe_flush!(border_source, &source, &$vp);\n\n        let slice = $val.slice.clone();\n        maybe_flush!(border_slice, &slice, &$vp);\n\n        let width = $val.width.clone();\n        maybe_flush!(border_width, &width, &$vp);\n\n        let outset = $val.outset.clone();\n        maybe_flush!(border_outset, &outset, &$vp);\n\n        let repeat = $val.repeat.clone();\n        maybe_flush!(border_repeat, &repeat, &$vp);\n\n        property!(border_source, &source, &$vp);\n        property!(border_slice, &slice, &$vp);\n        property!(border_width, &width, &$vp);\n        property!(border_outset, &outset, &$vp);\n        property!(border_repeat, &repeat, &$vp);\n      };\n    }\n\n    match property {\n      Property::MaskImage(val, vp) => property!(images, val, vp),\n      Property::MaskPosition(val, vp) => property!(positions, val, vp),\n      Property::MaskSize(val, vp) => property!(sizes, val, vp),\n      Property::MaskRepeat(val, vp) => property!(repeats, val, vp),\n      Property::MaskClip(val, vp) => property!(clips, val, vp),\n      Property::MaskOrigin(val, vp) => property!(origins, val, vp),\n      Property::MaskComposite(val) => self.composites = Some(val.clone()),\n      Property::MaskMode(val) => self.modes = Some(val.clone()),\n      Property::Mask(val, prefix) => {\n        let images = val.iter().map(|b| b.image.clone()).collect();\n        maybe_flush!(images, &images, prefix);\n\n        let positions = val.iter().map(|b| b.position.clone()).collect();\n        maybe_flush!(positions, &positions, prefix);\n\n        let sizes = val.iter().map(|b| b.size.clone()).collect();\n        maybe_flush!(sizes, &sizes, prefix);\n\n        let repeats = val.iter().map(|b| b.repeat.clone()).collect();\n        maybe_flush!(repeats, &repeats, prefix);\n\n        let clips = val.iter().map(|b| b.clip.clone()).collect();\n        maybe_flush!(clips, &clips, prefix);\n\n        let origins = val.iter().map(|b| b.origin.clone()).collect();\n        maybe_flush!(origins, &origins, prefix);\n\n        self.composites = Some(val.iter().map(|b| b.composite.clone()).collect());\n        self.modes = Some(val.iter().map(|b| b.mode.clone()).collect());\n\n        property!(images, &images, prefix);\n        property!(positions, &positions, prefix);\n        property!(sizes, &sizes, prefix);\n        property!(repeats, &repeats, prefix);\n        property!(clips, &clips, prefix);\n        property!(origins, &origins, prefix);\n      }\n      Property::Unparsed(val) if is_mask_property(&val.property_id) => {\n        self.flush(dest, context);\n        let mut unparsed = val.get_prefixed(context.targets, Feature::Mask);\n        context.add_unparsed_fallbacks(&mut unparsed);\n        self\n          .flushed_properties\n          .insert(MaskProperty::try_from(&val.property_id).unwrap());\n        dest.push(Property::Unparsed(unparsed));\n      }\n      Property::MaskBorderSource(val) => property!(border_source, val, &VendorPrefix::None),\n      Property::WebKitMaskBoxImageSource(val, _) => property!(border_source, val, &VendorPrefix::WebKit),\n      Property::MaskBorderMode(val) => self.border_mode = Some(val.clone()),\n      Property::MaskBorderSlice(val) => property!(border_slice, val, &VendorPrefix::None),\n      Property::WebKitMaskBoxImageSlice(val, _) => property!(border_slice, val, &VendorPrefix::WebKit),\n      Property::MaskBorderWidth(val) => property!(border_width, val, &VendorPrefix::None),\n      Property::WebKitMaskBoxImageWidth(val, _) => property!(border_width, val, &VendorPrefix::WebKit),\n      Property::MaskBorderOutset(val) => property!(border_outset, val, &VendorPrefix::None),\n      Property::WebKitMaskBoxImageOutset(val, _) => property!(border_outset, val, &VendorPrefix::WebKit),\n      Property::MaskBorderRepeat(val) => property!(border_repeat, val, &VendorPrefix::None),\n      Property::WebKitMaskBoxImageRepeat(val, _) => property!(border_repeat, val, &VendorPrefix::WebKit),\n      Property::MaskBorder(val) => {\n        border_shorthand!(val, VendorPrefix::None);\n        self.border_mode = Some(val.mode.clone());\n      }\n      Property::WebKitMaskBoxImage(val, _) => {\n        border_shorthand!(val, VendorPrefix::WebKit);\n      }\n      Property::Unparsed(val) if is_mask_border_property(&val.property_id) => {\n        self.flush(dest, context);\n        // Add vendor prefixes and expand color fallbacks.\n        let mut val = val.clone();\n        let prefix = context\n          .targets\n          .prefixes(val.property_id.prefix().or_none(), Feature::MaskBorder);\n        if prefix.contains(VendorPrefix::WebKit) {\n          if let Some(property_id) = get_webkit_mask_property(&val.property_id) {\n            let mut clone = val.clone();\n            clone.property_id = property_id;\n            context.add_unparsed_fallbacks(&mut clone);\n            dest.push(Property::Unparsed(clone));\n          }\n        }\n\n        context.add_unparsed_fallbacks(&mut val);\n        self\n          .flushed_properties\n          .insert(MaskProperty::try_from(&val.property_id).unwrap());\n        dest.push(Property::Unparsed(val));\n      }\n      _ => return false,\n    }\n\n    self.has_any = true;\n    true\n  }\n\n  fn finalize(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {\n    self.flush(dest, context);\n    self.flushed_properties = MaskProperty::empty();\n  }\n}\n\nimpl<'i> MaskHandler<'i> {\n  fn flush(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {\n    if !self.has_any {\n      return;\n    }\n\n    self.has_any = false;\n\n    self.flush_mask(dest, context);\n    self.flush_mask_border(dest, context);\n  }\n\n  fn flush_mask(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {\n    let mut images = std::mem::take(&mut self.images);\n    let mut positions = std::mem::take(&mut self.positions);\n    let mut sizes = std::mem::take(&mut self.sizes);\n    let mut repeats = std::mem::take(&mut self.repeats);\n    let mut clips = std::mem::take(&mut self.clips);\n    let mut origins = std::mem::take(&mut self.origins);\n    let mut composites = std::mem::take(&mut self.composites);\n    let mut modes = std::mem::take(&mut self.modes);\n\n    if let (\n      Some((images, images_vp)),\n      Some((positions, positions_vp)),\n      Some((sizes, sizes_vp)),\n      Some((repeats, repeats_vp)),\n      Some((clips, clips_vp)),\n      Some((origins, origins_vp)),\n      Some(composites_val),\n      Some(mode_vals),\n    ) = (\n      &mut images,\n      &mut positions,\n      &mut sizes,\n      &mut repeats,\n      &mut clips,\n      &mut origins,\n      &mut composites,\n      &mut modes,\n    ) {\n      // Only use shorthand syntax if the number of masks matches on all properties.\n      let len = images.len();\n      let intersection = *images_vp & *positions_vp & *sizes_vp & *repeats_vp & *clips_vp & *origins_vp;\n      if !intersection.is_empty()\n        && positions.len() == len\n        && sizes.len() == len\n        && repeats.len() == len\n        && clips.len() == len\n        && origins.len() == len\n        && composites_val.len() == len\n        && mode_vals.len() == len\n      {\n        let mut masks: SmallVec<[Mask<'i>; 1]> = izip!(\n          images.drain(..),\n          positions.drain(..),\n          sizes.drain(..),\n          repeats.drain(..),\n          clips.drain(..),\n          origins.drain(..),\n          composites_val.drain(..),\n          mode_vals.drain(..)\n        )\n        .map(|(image, position, size, repeat, clip, origin, composite, mode)| Mask {\n          image,\n          position,\n          size,\n          repeat,\n          clip,\n          origin,\n          composite,\n          mode,\n        })\n        .collect();\n\n        let mut prefix = context.targets.prefixes(intersection, Feature::Mask);\n        if !self.flushed_properties.intersects(MaskProperty::Mask) {\n          for fallback in masks.get_fallbacks(context.targets) {\n            // Match prefix of fallback. e.g. -webkit-linear-gradient\n            // can only be used in -webkit-mask-image.\n            // However, if mask-image is unprefixed, gradients can still be.\n            let mut p = fallback\n              .iter()\n              .fold(VendorPrefix::empty(), |p, mask| p | mask.image.get_vendor_prefix())\n              - VendorPrefix::None\n              & prefix;\n            if p.is_empty() {\n              p = prefix;\n            }\n            self.flush_mask_shorthand(fallback, p, dest);\n          }\n\n          let p = masks\n            .iter()\n            .fold(VendorPrefix::empty(), |p, mask| p | mask.image.get_vendor_prefix())\n            - VendorPrefix::None\n            & prefix;\n          if !p.is_empty() {\n            prefix = p;\n          }\n        }\n\n        self.flush_mask_shorthand(masks, prefix, dest);\n        self.flushed_properties.insert(MaskProperty::Mask);\n\n        images_vp.remove(intersection);\n        positions_vp.remove(intersection);\n        sizes_vp.remove(intersection);\n        repeats_vp.remove(intersection);\n        clips_vp.remove(intersection);\n        origins_vp.remove(intersection);\n        composites = None;\n        modes = None;\n      }\n    }\n\n    macro_rules! prop {\n      ($var: ident, $property: ident) => {\n        if let Some((val, vp)) = $var {\n          if !vp.is_empty() {\n            let prefix = context.targets.prefixes(vp, Feature::$property);\n            dest.push(Property::$property(val, prefix));\n            self.flushed_properties.insert(MaskProperty::$property);\n          }\n        }\n      };\n    }\n\n    if let Some((mut images, vp)) = images {\n      if !vp.is_empty() {\n        let mut prefix = vp;\n        if !self.flushed_properties.contains(MaskProperty::MaskImage) {\n          prefix = context.targets.prefixes(prefix, Feature::MaskImage);\n          for fallback in images.get_fallbacks(context.targets) {\n            // Match prefix of fallback. e.g. -webkit-linear-gradient\n            // can only be used in -webkit-mask-image.\n            // However, if mask-image is unprefixed, gradients can still be.\n            let mut p = fallback\n              .iter()\n              .fold(VendorPrefix::empty(), |p, image| p | image.get_vendor_prefix())\n              - VendorPrefix::None\n              & prefix;\n            if p.is_empty() {\n              p = prefix;\n            }\n            dest.push(Property::MaskImage(fallback, p))\n          }\n\n          let p = images\n            .iter()\n            .fold(VendorPrefix::empty(), |p, image| p | image.get_vendor_prefix())\n            - VendorPrefix::None\n            & prefix;\n          if !p.is_empty() {\n            prefix = p;\n          }\n        }\n\n        dest.push(Property::MaskImage(images, prefix));\n        self.flushed_properties.insert(MaskProperty::MaskImage);\n      }\n    }\n\n    prop!(positions, MaskPosition);\n    prop!(sizes, MaskSize);\n    prop!(repeats, MaskRepeat);\n    prop!(clips, MaskClip);\n    prop!(origins, MaskOrigin);\n\n    if let Some(composites) = composites {\n      let prefix = context.targets.prefixes(VendorPrefix::None, Feature::MaskComposite);\n      if prefix.contains(VendorPrefix::WebKit) {\n        dest.push(Property::WebKitMaskComposite(\n          composites.iter().map(|c| (*c).into()).collect(),\n        ));\n      }\n\n      dest.push(Property::MaskComposite(composites));\n      self.flushed_properties.insert(MaskProperty::MaskComposite);\n    }\n\n    if let Some(modes) = modes {\n      let prefix = context.targets.prefixes(VendorPrefix::None, Feature::Mask);\n      if prefix.contains(VendorPrefix::WebKit) {\n        dest.push(Property::WebKitMaskSourceType(\n          modes.iter().map(|c| (*c).into()).collect(),\n          VendorPrefix::WebKit,\n        ));\n      }\n\n      dest.push(Property::MaskMode(modes));\n      self.flushed_properties.insert(MaskProperty::MaskMode);\n    }\n  }\n\n  fn flush_mask_shorthand(\n    &self,\n    masks: SmallVec<[Mask<'i>; 1]>,\n    prefix: VendorPrefix,\n    dest: &mut DeclarationList<'i>,\n  ) {\n    if prefix.contains(VendorPrefix::WebKit)\n      && masks\n        .iter()\n        .any(|mask| mask.composite != MaskComposite::default() || mask.mode != MaskMode::default())\n    {\n      // Prefixed shorthand syntax did not support mask-composite or mask-mode. These map to different webkit-specific properties.\n      // -webkit-mask-composite uses a different syntax than mask-composite.\n      // -webkit-mask-source-type is equivalent to mask-mode, but only supported in Safari, not Chrome.\n      let mut webkit = masks.clone();\n      let mut composites: SmallVec<[WebKitMaskComposite; 1]> = SmallVec::new();\n      let mut modes: SmallVec<[WebKitMaskSourceType; 1]> = SmallVec::new();\n      let mut needs_composites = false;\n      let mut needs_modes = false;\n      for mask in &mut webkit {\n        let composite = std::mem::take(&mut mask.composite);\n        if composite != MaskComposite::default() {\n          needs_composites = true;\n        }\n        composites.push(composite.into());\n\n        let mode = std::mem::take(&mut mask.mode);\n        if mode != MaskMode::default() {\n          needs_modes = true;\n        }\n        modes.push(mode.into());\n      }\n\n      dest.push(Property::Mask(webkit, VendorPrefix::WebKit));\n      if needs_composites {\n        dest.push(Property::WebKitMaskComposite(composites));\n      }\n      if needs_modes {\n        dest.push(Property::WebKitMaskSourceType(modes, VendorPrefix::WebKit));\n      }\n\n      let prefix = prefix - VendorPrefix::WebKit;\n      if !prefix.is_empty() {\n        dest.push(Property::Mask(masks, prefix));\n      }\n    } else {\n      dest.push(Property::Mask(masks, prefix));\n    }\n  }\n\n  fn flush_mask_border(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {\n    let mut source = std::mem::take(&mut self.border_source);\n    let mut slice = std::mem::take(&mut self.border_slice);\n    let mut width = std::mem::take(&mut self.border_width);\n    let mut outset = std::mem::take(&mut self.border_outset);\n    let mut repeat = std::mem::take(&mut self.border_repeat);\n    let mut mode = std::mem::take(&mut self.border_mode);\n\n    if let (\n      Some((source, source_vp)),\n      Some((slice, slice_vp)),\n      Some((width, width_vp)),\n      Some((outset, outset_vp)),\n      Some((repeat, repeat_vp)),\n    ) = (&mut source, &mut slice, &mut width, &mut outset, &mut repeat)\n    {\n      let intersection = *source_vp & *slice_vp & *width_vp & *outset_vp & *repeat_vp;\n      if !intersection.is_empty() && (!intersection.contains(VendorPrefix::None) || mode.is_some()) {\n        let mut mask_border = MaskBorder {\n          source: source.clone(),\n          slice: slice.clone(),\n          width: width.clone(),\n          outset: outset.clone(),\n          repeat: repeat.clone(),\n          mode: mode.unwrap_or_default(),\n        };\n\n        let mut prefix = context.targets.prefixes(intersection, Feature::MaskBorder);\n        if !self.flushed_properties.intersects(MaskProperty::MaskBorder) {\n          // Get vendor prefix and color fallbacks.\n          let fallbacks = mask_border.get_fallbacks(context.targets);\n          for fallback in fallbacks {\n            let mut p = fallback.source.get_vendor_prefix() - VendorPrefix::None & prefix;\n            if p.is_empty() {\n              p = prefix;\n            }\n\n            if p.contains(VendorPrefix::WebKit) {\n              dest.push(Property::WebKitMaskBoxImage(\n                fallback.clone().into(),\n                VendorPrefix::WebKit,\n              ));\n            }\n\n            if p.contains(VendorPrefix::None) {\n              dest.push(Property::MaskBorder(fallback));\n            }\n          }\n        }\n\n        let p = mask_border.source.get_vendor_prefix() - VendorPrefix::None & prefix;\n        if !p.is_empty() {\n          prefix = p;\n        }\n\n        if prefix.contains(VendorPrefix::WebKit) {\n          dest.push(Property::WebKitMaskBoxImage(\n            mask_border.clone().into(),\n            VendorPrefix::WebKit,\n          ));\n        }\n\n        if prefix.contains(VendorPrefix::None) {\n          dest.push(Property::MaskBorder(mask_border));\n          self.flushed_properties.insert(MaskProperty::MaskBorder);\n          mode = None;\n        }\n\n        source_vp.remove(intersection);\n        slice_vp.remove(intersection);\n        width_vp.remove(intersection);\n        outset_vp.remove(intersection);\n        repeat_vp.remove(intersection);\n      }\n    }\n\n    if let Some((mut source, mut prefix)) = source {\n      prefix = context.targets.prefixes(prefix, Feature::MaskBorderSource);\n\n      if !self.flushed_properties.contains(MaskProperty::MaskBorderSource) {\n        // Get vendor prefix and color fallbacks.\n        let fallbacks = source.get_fallbacks(context.targets);\n        for fallback in fallbacks {\n          if prefix.contains(VendorPrefix::WebKit) {\n            dest.push(Property::WebKitMaskBoxImageSource(\n              fallback.clone(),\n              VendorPrefix::WebKit,\n            ));\n          }\n\n          if prefix.contains(VendorPrefix::None) {\n            dest.push(Property::MaskBorderSource(fallback));\n          }\n        }\n      }\n\n      if prefix.contains(VendorPrefix::WebKit) {\n        dest.push(Property::WebKitMaskBoxImageSource(source.clone(), VendorPrefix::WebKit));\n      }\n\n      if prefix.contains(VendorPrefix::None) {\n        dest.push(Property::MaskBorderSource(source));\n        self.flushed_properties.insert(MaskProperty::MaskBorderSource);\n      }\n    }\n\n    macro_rules! prop {\n      ($val: expr, $prop: ident, $webkit: ident) => {\n        if let Some((val, mut prefix)) = $val {\n          prefix = context.targets.prefixes(prefix, Feature::$prop);\n          if prefix.contains(VendorPrefix::WebKit) {\n            dest.push(Property::$webkit(val.clone(), VendorPrefix::WebKit));\n          }\n\n          if prefix.contains(VendorPrefix::None) {\n            dest.push(Property::$prop(val));\n          }\n          self.flushed_properties.insert(MaskProperty::$prop);\n        }\n      };\n    }\n\n    prop!(slice, MaskBorderSlice, WebKitMaskBoxImageSlice);\n    prop!(width, MaskBorderWidth, WebKitMaskBoxImageWidth);\n    prop!(outset, MaskBorderOutset, WebKitMaskBoxImageOutset);\n    prop!(repeat, MaskBorderRepeat, WebKitMaskBoxImageRepeat);\n\n    if let Some(mode) = mode {\n      dest.push(Property::MaskBorderMode(mode));\n      self.flushed_properties.insert(MaskProperty::MaskBorderMode);\n    }\n  }\n}\n\n#[inline]\nfn is_mask_property(property_id: &PropertyId) -> bool {\n  match property_id {\n    PropertyId::MaskImage(_)\n    | PropertyId::MaskPosition(_)\n    | PropertyId::MaskSize(_)\n    | PropertyId::MaskRepeat(_)\n    | PropertyId::MaskClip(_)\n    | PropertyId::MaskOrigin(_)\n    | PropertyId::MaskComposite\n    | PropertyId::MaskMode\n    | PropertyId::Mask(_) => true,\n    _ => false,\n  }\n}\n\n#[inline]\nfn is_mask_border_property(property_id: &PropertyId) -> bool {\n  match property_id {\n    PropertyId::MaskBorderSource\n    | PropertyId::MaskBorderSlice\n    | PropertyId::MaskBorderWidth\n    | PropertyId::MaskBorderOutset\n    | PropertyId::MaskBorderRepeat\n    | PropertyId::MaskBorderMode\n    | PropertyId::MaskBorder => true,\n    _ => false,\n  }\n}\n\n#[inline]\npub(crate) fn get_webkit_mask_property(property_id: &PropertyId) -> Option<PropertyId<'static>> {\n  Some(match property_id {\n    PropertyId::MaskBorderSource => PropertyId::WebKitMaskBoxImageSource(VendorPrefix::WebKit),\n    PropertyId::MaskBorderSlice => PropertyId::WebKitMaskBoxImageSlice(VendorPrefix::WebKit),\n    PropertyId::MaskBorderWidth => PropertyId::WebKitMaskBoxImageWidth(VendorPrefix::WebKit),\n    PropertyId::MaskBorderOutset => PropertyId::WebKitMaskBoxImageOutset(VendorPrefix::WebKit),\n    PropertyId::MaskBorderRepeat => PropertyId::WebKitMaskBoxImageRepeat(VendorPrefix::WebKit),\n    PropertyId::MaskBorder => PropertyId::WebKitMaskBoxImage(VendorPrefix::WebKit),\n    PropertyId::MaskComposite => PropertyId::WebKitMaskComposite,\n    PropertyId::MaskMode => PropertyId::WebKitMaskSourceType(VendorPrefix::WebKit),\n    _ => return None,\n  })\n}\n"
  },
  {
    "path": "src/properties/mod.rs",
    "content": "//! CSS property values.\n//!\n//! Each property provides parsing and serialization support using the [Parse](super::traits::Parse)\n//! and [ToCss](super::traits::ToCss) traits. Properties are fully parsed as defined by the CSS spec,\n//! and printed in their canonical form. For example, most CSS properties are case-insensitive, and\n//! may be written in various orders, but when printed they are lower cased as appropriate and in a\n//! standard order.\n//!\n//! CSS properties often also contain many implicit values that are automatically filled in during\n//! parsing when omitted. These are also omitted when possible during serialization. Many properties\n//! also implement the [Default](std::default::Default) trait, which returns the initial value for the property.\n//!\n//! Shorthand properties are represented as structs containing fields for each of the sub-properties.\n//! If some of the sub-properties are not specified in the shorthand, their default values are filled in.\n//!\n//! The [Property](Property) enum contains the values of all properties, and can be used to parse property values by name.\n//! The [PropertyId](PropertyId) enum represents only property names, and not values and is used to refer to known properties.\n//!\n//! # Example\n//!\n//! This example shows how the `background` shorthand property is parsed and serialized. The `parse_string`\n//! function parses the background into a structure with all missing fields filled in with their default values.\n//! When printed using the `to_css_string` function, the components are in their canonical order, and default\n//! values are removed.\n//!\n//! ```\n//! use smallvec::smallvec;\n//! use lightningcss::{\n//!   properties::{Property, PropertyId, background::*},\n//!   values::{url::Url, image::Image, color::{CssColor, RGBA}, position::*, length::*},\n//!   stylesheet::{ParserOptions, PrinterOptions},\n//!   dependencies::Location,\n//! };\n//!\n//! let background = Property::parse_string(\n//!   PropertyId::from(\"background\"),\n//!   \"url('img.png') repeat fixed 20px 10px / 50px 100px\",\n//!   ParserOptions::default()\n//! ).unwrap();\n//!\n//! assert_eq!(\n//!   background,\n//!   Property::Background(smallvec![Background {\n//!     image: Image::Url(Url {\n//!       url: \"img.png\".into(),\n//!       loc: Location { line: 1, column: 1 }\n//!     }),\n//!     color: CssColor::RGBA(RGBA {\n//!       red: 0,\n//!       green: 0,\n//!       blue: 0,\n//!       alpha: 0\n//!     }),\n//!     position: BackgroundPosition {\n//!       x: HorizontalPosition::Length(LengthPercentage::px(20.0)),\n//!       y: VerticalPosition::Length(LengthPercentage::px(10.0)),\n//!     },\n//!     repeat: BackgroundRepeat {\n//!       x: BackgroundRepeatKeyword::Repeat,\n//!       y: BackgroundRepeatKeyword::Repeat,\n//!     },\n//!     size: BackgroundSize::Explicit {\n//!       width: LengthPercentageOrAuto::LengthPercentage(LengthPercentage::px(50.0)),\n//!       height: LengthPercentageOrAuto::LengthPercentage(LengthPercentage::px(100.0)),\n//!     },\n//!     attachment: BackgroundAttachment::Fixed,\n//!     origin: BackgroundOrigin::PaddingBox,\n//!     clip: BackgroundClip::BorderBox,\n//!   }])\n//! );\n//!\n//! assert_eq!(\n//!   background.to_css_string(false, PrinterOptions::default()).unwrap(),\n//!   r#\"background: url(\"img.png\") 20px 10px / 50px 100px fixed\"#\n//! );\n//! ```\n//!\n//! If you have a [cssparser::Parser](cssparser::Parser) already, you can also use the `parse` and `to_css`\n//! methods instead, rather than parsing from a string.\n//!\n//! # Unparsed and custom properties\n//!\n//! Custom and unknown properties are represented by the [CustomProperty](custom::CustomProperty) struct, and the\n//! `Property::Custom` variant. The value of these properties is not parsed, and is stored as a raw\n//! [TokenList](custom::TokenList), with the name as a string.\n//!\n//! If a known property is unable to be parsed, e.g. it contains `var()` references, then it is represented by the\n//! [UnparsedProperty](custom::UnparsedProperty) struct, and the `Property::Unparsed` variant. The value is stored\n//! as a raw [TokenList](custom::TokenList), with a [PropertyId](PropertyId) as the name.\n\n#![deny(missing_docs)]\n\npub mod align;\npub mod animation;\npub mod background;\npub mod border;\npub mod border_image;\npub mod border_radius;\npub mod box_shadow;\npub mod contain;\npub mod css_modules;\npub mod custom;\npub mod display;\npub mod effects;\npub mod flex;\npub mod font;\npub mod grid;\npub mod list;\npub(crate) mod margin_padding;\npub mod masking;\npub mod outline;\npub mod overflow;\npub mod position;\npub(crate) mod prefix_handler;\npub mod size;\npub mod svg;\npub mod text;\npub mod transform;\npub mod transition;\npub mod ui;\n\nuse crate::declaration::DeclarationBlock;\nuse crate::error::{ParserError, PrinterError};\nuse crate::logical::{LogicalGroup, PropertyCategory};\nuse crate::macros::enum_property;\nuse crate::parser::starts_with_ignore_ascii_case;\nuse crate::parser::ParserOptions;\nuse crate::prefixes::Feature;\nuse crate::printer::{Printer, PrinterOptions};\nuse crate::targets::Targets;\nuse crate::traits::{Parse, ParseWithOptions, Shorthand, ToCss};\nuse crate::values::number::{CSSInteger, CSSNumber};\nuse crate::values::string::CowArcStr;\nuse crate::values::{\n  alpha::*, color::*, easing::EasingFunction, ident::DashedIdentReference, ident::NoneOrCustomIdentList, image::*,\n  length::*, position::*, rect::*, shape::FillRule, size::Size2D, time::Time,\n};\nuse crate::vendor_prefix::VendorPrefix;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse align::*;\nuse animation::*;\nuse background::*;\nuse border::*;\nuse border_image::*;\nuse border_radius::*;\nuse box_shadow::*;\nuse contain::*;\nuse css_modules::*;\nuse cssparser::*;\nuse custom::*;\nuse display::*;\nuse effects::*;\nuse flex::*;\nuse font::*;\nuse grid::*;\nuse list::*;\nuse margin_padding::*;\nuse masking::*;\nuse outline::*;\nuse overflow::*;\nuse size::*;\nuse smallvec::{smallvec, SmallVec};\n#[cfg(feature = \"into_owned\")]\nuse static_self::IntoOwned;\nuse svg::*;\nuse text::*;\nuse transform::*;\nuse transition::*;\nuse ui::*;\n\nmacro_rules! define_properties {\n  (\n    $(\n      $(#[$meta: meta])*\n      $name: literal: $property: ident($type: ty $(, $vp: ty)?) $( / $prefix: ident )* $( unprefixed: $unprefixed: literal )? $( options: $options: literal )? $( shorthand: $shorthand: literal )? $( [ logical_group: $logical_group: ident, category: $logical_category: ident ] )? $( if $condition: ident )?,\n    )+\n  ) => {\n    /// A CSS property id.\n    #[derive(Debug, Clone, PartialEq, Eq, Hash)]\n    #[cfg_attr(feature = \"visitor\", derive(Visit))]\n    #[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n    pub enum PropertyId<'i> {\n      $(\n        #[doc=concat!(\"The `\", $name, \"` property.\")]\n        $(#[$meta])*\n        $property$(($vp))?,\n      )+\n      /// The `all` property.\n      All,\n      /// An unknown or custom property name.\n      Custom(CustomPropertyName<'i>)\n    }\n\n    macro_rules! vp_name {\n      ($x: ty, $n: ident) => {\n        $n\n      };\n      ($x: ty, $n: expr) => {\n        $n\n      };\n    }\n\n    macro_rules! get_allowed_prefixes {\n      ($v: literal) => {\n        VendorPrefix::empty()\n      };\n      () => {\n        VendorPrefix::None\n      };\n    }\n\n    impl<'i> From<CowArcStr<'i>> for PropertyId<'i> {\n      fn from(name: CowArcStr<'i>) -> PropertyId<'i> {\n        let name_ref = name.as_ref();\n        let (prefix, name_ref) = if starts_with_ignore_ascii_case(name_ref, \"-webkit-\") {\n          (VendorPrefix::WebKit, &name_ref[8..])\n        } else if starts_with_ignore_ascii_case(name_ref, \"-moz-\") {\n          (VendorPrefix::Moz, &name_ref[5..])\n        } else if starts_with_ignore_ascii_case(name_ref, \"-o-\") {\n          (VendorPrefix::O, &name_ref[3..])\n        } else if starts_with_ignore_ascii_case(name_ref, \"-ms-\") {\n          (VendorPrefix::Ms, &name_ref[4..])\n        } else {\n          (VendorPrefix::None, name_ref)\n        };\n\n        Self::from_name_and_prefix(name_ref, prefix)\n          .unwrap_or_else(|_| PropertyId::Custom(name.into()))\n      }\n    }\n\n    impl<'i> From<&'i str> for PropertyId<'i> {\n      #[inline]\n      fn from(name: &'i str) -> PropertyId<'i> {\n        PropertyId::from(CowArcStr::from(name))\n      }\n    }\n\n    impl<'i> Parse<'i> for PropertyId<'i> {\n      fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n        let name = input.expect_ident()?;\n        Ok(CowArcStr::from(name).into())\n      }\n    }\n\n    impl<'i> ToCss for PropertyId<'i> {\n      fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError> where W: std::fmt::Write {\n        let mut first = true;\n        macro_rules! delim {\n          () => {\n            #[allow(unused_assignments)]\n            if first {\n              first = false;\n            } else {\n              dest.delim(',', false)?;\n            }\n          };\n        }\n\n        let name = self.name();\n        for p in self.prefix().or_none() {\n          delim!();\n          p.to_css(dest)?;\n          dest.write_str(name)?;\n        }\n\n        Ok(())\n      }\n    }\n\n    impl<'i> PropertyId<'i> {\n      fn from_name_and_prefix(name: &str, prefix: VendorPrefix) -> Result<Self, ()> {\n        match_ignore_ascii_case! { name.as_ref(),\n          $(\n            $(#[$meta])*\n            $name => {\n              macro_rules! get_propertyid {\n                ($v: ty) => {\n                  PropertyId::$property(prefix)\n                };\n                () => {\n                  PropertyId::$property\n                };\n              }\n\n              let allowed_prefixes = get_allowed_prefixes!($($unprefixed)?) $(| VendorPrefix::$prefix)*;\n              if allowed_prefixes.contains(prefix) {\n                return Ok(get_propertyid!($($vp)?))\n              }\n            },\n          )+\n          \"all\" => return Ok(PropertyId::All),\n          _ => {}\n        }\n\n        Err(())\n      }\n\n      /// Returns the vendor prefix for this property id.\n      pub fn prefix(&self) -> VendorPrefix {\n        use PropertyId::*;\n        match self {\n          $(\n            $(#[$meta])*\n            $property$((vp_name!($vp, prefix)))? => {\n              $(\n                macro_rules! return_prefix {\n                  ($v: ty) => {\n                    return *prefix;\n                  };\n                }\n\n                return_prefix!($vp);\n              )?\n              #[allow(unreachable_code)]\n              VendorPrefix::empty()\n            },\n          )+\n          _ => VendorPrefix::empty()\n        }\n      }\n\n      pub(crate) fn with_prefix(&self, prefix: VendorPrefix) -> PropertyId<'i> {\n        use PropertyId::*;\n        match self {\n          $(\n            $(#[$meta])*\n            $property$((vp_name!($vp, _p)))? => {\n              macro_rules! get_prefixed {\n                ($v: ty) => {\n                  PropertyId::$property(prefix)\n                };\n                () => {\n                  PropertyId::$property\n                }\n              }\n\n              get_prefixed!($($vp)?)\n            },\n          )+\n          _ => self.clone()\n        }\n      }\n\n      pub(crate) fn add_prefix(&mut self, prefix: VendorPrefix) {\n        use PropertyId::*;\n        match self {\n          $(\n            $(#[$meta])*\n            $property$((vp_name!($vp, p)))? => {\n              macro_rules! get_prefixed {\n                ($v: ty) => {{\n                  *p |= prefix;\n                }};\n                () => {{}};\n              }\n\n              get_prefixed!($($vp)?)\n            },\n          )+\n          _ => {}\n        }\n      }\n\n      pub(crate) fn set_prefixes_for_targets(&mut self, targets: Targets) {\n        match self {\n          $(\n            $(#[$meta])*\n            #[allow(unused_variables)]\n            PropertyId::$property$((vp_name!($vp, prefix)))? => {\n              macro_rules! get_prefixed {\n                ($v: ty, $u: literal) => {};\n                ($v: ty) => {{\n                  *prefix = targets.prefixes(*prefix, Feature::$property);\n                }};\n                () => {};\n              }\n\n              get_prefixed!($($vp)? $(, $unprefixed)?);\n            },\n          )+\n          _ => {}\n        }\n      }\n\n      /// Returns the property name, without any vendor prefixes.\n      pub fn name(&self) -> &str {\n        use PropertyId::*;\n\n        match self {\n          $(\n            $(#[$meta])*\n            $property$((vp_name!($vp, _p)))? => $name,\n          )+\n          All => \"all\",\n          Custom(name) => name.as_ref()\n        }\n      }\n\n      /// Returns whether a property is a shorthand.\n      pub fn is_shorthand(&self) -> bool {\n        $(\n          macro_rules! shorthand {\n            ($s: literal) => {\n              if let PropertyId::$property$((vp_name!($vp, _prefix)))? = self {\n                return true\n              }\n            };\n            () => {}\n          }\n\n          shorthand!($($shorthand)?);\n        )+\n\n        false\n      }\n\n      /// Returns a shorthand value for this property id from the given declaration block.\n      pub(crate) fn shorthand_value<'a>(&self, decls: &DeclarationBlock<'a>) -> Option<(Property<'a>, bool)> {\n        // Inline function to remap lifetime names.\n        #[inline]\n        fn shorthand_value<'a, 'i>(property_id: &PropertyId<'a>, decls: &DeclarationBlock<'i>) -> Option<(Property<'i>, bool)> {\n          $(\n            #[allow(unused_macros)]\n            macro_rules! prefix {\n              ($v: ty, $p: ident) => {\n                *$p\n              };\n              ($p: ident) => {\n                VendorPrefix::None\n              };\n            }\n\n            macro_rules! shorthand {\n              ($s: literal) => {\n                if let PropertyId::$property$((vp_name!($vp, prefix)))? = &property_id {\n                  if let Some((val, important)) = <$type>::from_longhands(decls, prefix!($($vp,)? prefix)) {\n                    return Some((Property::$property(val $(, *vp_name!($vp, prefix))?), important))\n                  }\n                }\n              };\n              () => {}\n            }\n\n            shorthand!($($shorthand)?);\n          )+\n\n          None\n        }\n\n        shorthand_value(self, decls)\n      }\n\n      /// Returns a list of longhand property ids for a shorthand.\n      pub fn longhands(&self) -> Option<Vec<PropertyId<'static>>> {\n        macro_rules! prefix_default {\n          ($x: ty, $p: ident) => {\n            *$p\n          };\n          () => {\n            VendorPrefix::None\n          };\n        }\n\n        $(\n          macro_rules! shorthand {\n            ($s: literal) => {\n              if let PropertyId::$property$((vp_name!($vp, prefix)))? = self {\n                return Some(<$type>::longhands(prefix_default!($($vp, prefix)?)));\n              }\n            };\n            () => {}\n          }\n\n          shorthand!($($shorthand)?);\n        )+\n\n        None\n      }\n\n      /// Returns the logical property group for this property.\n      pub(crate) fn logical_group(&self) -> Option<LogicalGroup> {\n        $(\n          macro_rules! group {\n            ($g: ident) => {\n              if let PropertyId::$property$((vp_name!($vp, _prefix)))? = self {\n                return Some(LogicalGroup::$g)\n              }\n            };\n            () => {}\n          }\n\n          group!($($logical_group)?);\n        )+\n\n        None\n      }\n\n      /// Returns whether the property is logical or physical.\n      pub(crate) fn category(&self) -> Option<PropertyCategory> {\n        $(\n          macro_rules! category {\n            ($c: ident) => {\n              if let PropertyId::$property$((vp_name!($vp, _prefix)))? = self {\n                return Some(PropertyCategory::$c)\n              }\n            };\n            () => {}\n          }\n\n          category!($($logical_category)?);\n        )+\n\n        None\n      }\n    }\n\n    #[cfg(feature = \"serde\")]\n    #[cfg_attr(docsrs, doc(cfg(feature = \"serde\")))]\n    impl<'i> serde::Serialize for PropertyId<'i> {\n      fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>\n      where\n        S: serde::Serializer,\n      {\n        use serde::ser::SerializeStruct;\n\n        let name = self.name();\n        let prefix = self.prefix();\n\n        if prefix.is_empty() {\n          let mut s = serializer.serialize_struct(\"PropertyId\", 1)?;\n          s.serialize_field(\"property\", name)?;\n          s.end()\n        } else {\n          let mut s = serializer.serialize_struct(\"PropertyId\", 2)?;\n          s.serialize_field(\"property\", name)?;\n          s.serialize_field(\"vendor_prefix\", &prefix)?;\n          s.end()\n        }\n      }\n    }\n\n    #[cfg(feature = \"serde\")]\n    #[cfg_attr(docsrs, doc(cfg(feature = \"serde\")))]\n    impl<'i, 'de: 'i> serde::Deserialize<'de> for PropertyId<'i> {\n      fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n      where\n        D: serde::Deserializer<'de>,\n      {\n        #[derive(serde::Deserialize)]\n        #[serde(field_identifier, rename_all = \"snake_case\")]\n        enum Field {\n          Property,\n          VendorPrefix\n        }\n\n        struct PropertyIdVisitor;\n        impl<'de> serde::de::Visitor<'de> for PropertyIdVisitor {\n          type Value = PropertyId<'de>;\n\n          fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {\n            formatter.write_str(\"a PropertyId\")\n          }\n\n          fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>\n          where\n            A: serde::de::MapAccess<'de>,\n          {\n            let mut property: Option<CowArcStr> = None;\n            let mut vendor_prefix = None;\n            while let Some(key) = map.next_key()? {\n              match key {\n                Field::Property => {\n                  property = Some(map.next_value()?);\n                }\n                Field::VendorPrefix => {\n                  vendor_prefix = Some(map.next_value()?);\n                }\n              }\n            }\n\n            let property = property.ok_or_else(|| serde::de::Error::missing_field(\"property\"))?;\n            let vendor_prefix = vendor_prefix.unwrap_or(VendorPrefix::None);\n            let property_id = PropertyId::from_name_and_prefix(property.as_ref(), vendor_prefix)\n              .unwrap_or_else(|_| PropertyId::Custom(property.into()));\n            Ok(property_id)\n          }\n        }\n\n        deserializer.deserialize_any(PropertyIdVisitor)\n      }\n    }\n\n    #[cfg(feature = \"jsonschema\")]\n    #[cfg_attr(docsrs, doc(cfg(feature = \"jsonschema\")))]\n    impl<'i> schemars::JsonSchema for PropertyId<'i> {\n      fn is_referenceable() -> bool {\n        true\n      }\n\n      fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {\n        macro_rules! property {\n          ($n: literal) => {\n            fn property(_: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {\n              schemars::schema::Schema::Object(schemars::schema::SchemaObject {\n                instance_type: Some(schemars::schema::InstanceType::String.into()),\n                enum_values: Some(vec![$n.into()]),\n                ..Default::default()\n              })\n            }\n          }\n        }\n\n        schemars::schema::Schema::Object(schemars::schema::SchemaObject {\n          subschemas: Some(Box::new(schemars::schema::SubschemaValidation {\n            one_of: Some(vec![\n              $(\n                {\n                  property!($name);\n\n                  macro_rules! with_prefix {\n                    ($v: ty) => {{\n                      #[derive(schemars::JsonSchema)]\n                      struct T<'i> {\n                        #[schemars(rename = \"property\", schema_with = \"property\")]\n                        _property: &'i u8,\n                        #[schemars(rename = \"vendorPrefix\")]\n                        _vendor_prefix: VendorPrefix,\n                      }\n\n                      T::json_schema(gen)\n                    }};\n                    () => {{\n                      #[derive(schemars::JsonSchema)]\n                      struct T<'i> {\n                        #[schemars(rename = \"property\", schema_with = \"property\")]\n                        _property: &'i u8,\n                      }\n\n                      T::json_schema(gen)\n                    }};\n                  }\n\n                  with_prefix!($($vp)?)\n                },\n              )+\n              {\n                property!(\"all\");\n\n                #[derive(schemars::JsonSchema)]\n                struct T<'i> {\n                  #[schemars(rename = \"property\", schema_with = \"property\")]\n                  _property: &'i u8,\n                }\n\n                T::json_schema(gen)\n              },\n              {\n                #[derive(schemars::JsonSchema)]\n                struct T {\n                  #[schemars(rename = \"property\")]\n                  _property: String,\n                }\n\n                T::json_schema(gen)\n              }\n            ]),\n            ..Default::default()\n          })),\n          ..Default::default()\n        })\n      }\n\n      fn schema_name() -> String {\n        \"PropertyId\".into()\n      }\n    }\n\n    /// A CSS property.\n    #[derive(Debug, Clone, PartialEq)]\n    #[cfg_attr(feature = \"visitor\", derive(Visit), visit(visit_property, PROPERTIES))]\n    #[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n    pub enum Property<'i> {\n      $(\n        #[doc=concat!(\"The `\", $name, \"` property.\")]\n        $(#[$meta])*\n        $property($type, $($vp)?),\n      )+\n      /// The [all](https://drafts.csswg.org/css-cascade-5/#all-shorthand) shorthand property.\n      All(CSSWideKeyword),\n      /// An unparsed property.\n      Unparsed(UnparsedProperty<'i>),\n      /// A custom or unknown property.\n      Custom(CustomProperty<'i>),\n    }\n\n    impl<'i> Property<'i> {\n      /// Parses a CSS property by name.\n      pub fn parse<'t>(property_id: PropertyId<'i>, input: &mut Parser<'i, 't>, options: &ParserOptions<'_, 'i>) -> Result<Property<'i>, ParseError<'i, ParserError<'i>>> {\n        let state = input.state();\n\n        match property_id {\n          $(\n            $(#[$meta])*\n            PropertyId::$property$((vp_name!($vp, prefix)))? $(if options.$condition.is_some())? => {\n              if let Ok(c) = <$type>::parse_with_options(input, options) {\n                if input.expect_exhausted().is_ok() {\n                  return Ok(Property::$property(c $(, vp_name!($vp, prefix))?))\n                }\n              }\n            },\n          )+\n          PropertyId::All => return Ok(Property::All(CSSWideKeyword::parse(input)?)),\n          PropertyId::Custom(name) => return Ok(Property::Custom(CustomProperty::parse(name, input, options)?)),\n          _ => {}\n        };\n\n        // If a value was unable to be parsed, treat as an unparsed property.\n        // This is different from a custom property, handled below, in that the property name is known\n        // and stored as an enum rather than a string. This lets property handlers more easily deal with it.\n        // Ideally we'd only do this if var() or env() references were seen, but err on the safe side for now.\n        input.reset(&state);\n        return Ok(Property::Unparsed(UnparsedProperty::parse(property_id, input, options)?))\n      }\n\n      /// Returns the property id for this property.\n      pub fn property_id(&self) -> PropertyId<'i> {\n        use Property::*;\n\n        match self {\n          $(\n            $(#[$meta])*\n            $property(_, $(vp_name!($vp, p))?) => PropertyId::$property$((*vp_name!($vp, p)))?,\n          )+\n          All(_) => PropertyId::All,\n          Unparsed(unparsed) => unparsed.property_id.clone(),\n          Custom(custom) => PropertyId::Custom(custom.name.clone())\n        }\n      }\n\n      /// Parses a CSS property from a string.\n      pub fn parse_string(property_id: PropertyId<'i>, input: &'i str, options: ParserOptions<'_, 'i>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n        let mut input = ParserInput::new(input);\n        let mut parser = Parser::new(&mut input);\n        Self::parse(property_id, &mut parser, &options)\n      }\n\n      /// Sets the vendor prefixes for this property.\n      ///\n      /// If the property doesn't support vendor prefixes, this function does nothing.\n      /// If vendor prefixes are set which do not exist for the property, they are ignored\n      /// and only the valid prefixes are set.\n      pub fn set_prefix(&mut self, prefix: VendorPrefix) {\n        use Property::*;\n        match self {\n          $(\n            $(#[$meta])*\n            $property(_, $(vp_name!($vp, p))?) => {\n              macro_rules! set {\n                ($v: ty) => {\n                  *p = (prefix & (get_allowed_prefixes!($($unprefixed)?) $(| VendorPrefix::$prefix)*)).or(*p);\n                };\n                () => {};\n              }\n\n              set!($($vp)?);\n            },\n          )+\n          _ => {}\n        }\n      }\n\n      /// Serializes the value of a CSS property without its name or `!important` flag.\n      pub fn value_to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError> where W: std::fmt::Write {\n        use Property::*;\n\n        match self {\n          $(\n            $(#[$meta])*\n            $property(val, $(vp_name!($vp, _p))?) => {\n              val.to_css(dest)\n            }\n          )+\n          All(keyword) => keyword.to_css(dest),\n          Unparsed(unparsed) => {\n            unparsed.value.to_css(dest, false)\n          }\n          Custom(custom) => {\n            custom.value.to_css(dest, matches!(custom.name, CustomPropertyName::Custom(..)))\n          }\n        }\n      }\n\n      /// Serializes the value of a CSS property as a string.\n      pub fn value_to_css_string(&self, options: PrinterOptions) -> Result<String, PrinterError> {\n        let mut s = String::new();\n        let mut printer = Printer::new(&mut s, options);\n        self.value_to_css(&mut printer)?;\n        Ok(s)\n      }\n\n      /// Serializes the CSS property, with an optional `!important` flag.\n      pub fn to_css<W>(&self, dest: &mut Printer<W>, important: bool) -> Result<(), PrinterError> where W: std::fmt::Write {\n        use Property::*;\n\n        let mut first = true;\n        macro_rules! start {\n          () => {\n            #[allow(unused_assignments)]\n            if first {\n              first = false;\n            } else {\n              dest.write_char(';')?;\n              dest.newline()?;\n            }\n          };\n        }\n\n        macro_rules! write_important {\n          () => {\n            if important {\n              dest.whitespace()?;\n              dest.write_str(\"!important\")?;\n            }\n          }\n        }\n\n        let (name, prefix) = match self {\n          $(\n            $(#[$meta])*\n            $property(_, $(vp_name!($vp, prefix))?) => {\n              macro_rules! get_prefix {\n                ($v: ty) => {\n                  *prefix\n                };\n                () => {\n                  VendorPrefix::None\n                };\n              }\n\n              ($name, get_prefix!($($vp)?))\n            },\n          )+\n          All(_) => (\"all\", VendorPrefix::None),\n          Unparsed(unparsed) => {\n            let mut prefix = unparsed.property_id.prefix();\n            if prefix.is_empty() {\n              prefix = VendorPrefix::None;\n            }\n            (unparsed.property_id.name(), prefix)\n          },\n          Custom(custom) => {\n            custom.name.to_css(dest)?;\n            dest.write_char(':')?;\n            if !custom.value.starts_with_whitespace() {\n              dest.whitespace()?;\n            }\n            self.value_to_css(dest)?;\n            write_important!();\n            return Ok(())\n          }\n        };\n        for p in prefix {\n          start!();\n          p.to_css(dest)?;\n          dest.write_str(name)?;\n          dest.delim(':', false)?;\n          self.value_to_css(dest)?;\n          write_important!();\n        }\n        Ok(())\n      }\n\n      /// Serializes the CSS property to a string, with an optional `!important` flag.\n      pub fn to_css_string(&self, important: bool, options: PrinterOptions) -> Result<String, PrinterError> {\n        let mut s = String::new();\n        let mut printer = Printer::new(&mut s, options);\n        self.to_css(&mut printer, important)?;\n        Ok(s)\n      }\n\n      /// Returns the given longhand property for a shorthand.\n      pub fn longhand(&self, property_id: &PropertyId) -> Option<Property<'i>> {\n        $(\n          macro_rules! shorthand {\n            ($s: literal) => {\n              if let Property::$property(val $(, vp_name!($vp, prefix))?) = self {\n                $(\n                  if *vp_name!($vp, prefix) != property_id.prefix() {\n                    return None\n                  }\n                )?\n                return val.longhand(property_id)\n              }\n            };\n            () => {}\n          }\n\n          shorthand!($($shorthand)?);\n        )+\n\n        None\n      }\n\n      /// Updates this shorthand from a longhand property.\n      pub fn set_longhand(&mut self, property: &Property<'i>) -> Result<(), ()> {\n        $(\n          macro_rules! shorthand {\n            ($s: literal) => {\n              if let Property::$property(val $(, vp_name!($vp, prefix))?) = self {\n                $(\n                  if *vp_name!($vp, prefix) != property.property_id().prefix() {\n                    return Err(())\n                  }\n                )?\n                return val.set_longhand(property)\n              }\n            };\n            () => {}\n          }\n\n          shorthand!($($shorthand)?);\n        )+\n        Err(())\n      }\n    }\n\n    #[cfg(feature = \"serde\")]\n    #[cfg_attr(docsrs, doc(cfg(feature = \"serde\")))]\n    impl<'i> serde::Serialize for Property<'i> {\n      fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>\n      where\n        S: serde::Serializer,\n      {\n        use serde::ser::SerializeStruct;\n        use Property::*;\n\n        match self {\n          Unparsed(unparsed) => {\n            let mut s = serializer.serialize_struct(\"Property\", 2)?;\n            s.serialize_field(\"property\", \"unparsed\")?;\n            s.serialize_field(\"value\", unparsed)?;\n            return s.end()\n          }\n          Custom(unparsed) => {\n            let mut s = serializer.serialize_struct(\"Property\", 2)?;\n            s.serialize_field(\"property\", \"custom\")?;\n            s.serialize_field(\"value\", unparsed)?;\n            return s.end()\n          }\n          _ => {}\n        }\n\n        let id = self.property_id();\n        let name = id.name();\n        let prefix = id.prefix();\n\n        let mut s = if prefix.is_empty() {\n          let mut s = serializer.serialize_struct(\"Property\", 2)?;\n          s.serialize_field(\"property\", name)?;\n          s\n        } else {\n          let mut s = serializer.serialize_struct(\"Property\", 3)?;\n          s.serialize_field(\"property\", name)?;\n          s.serialize_field(\"vendorPrefix\", &prefix)?;\n          s\n        };\n\n        match self {\n          $(\n            $(#[$meta])*\n            $property(value, $(vp_name!($vp, _p))?) => {\n              s.serialize_field(\"value\", value)?;\n            }\n          )+\n          All(value) => {\n            s.serialize_field(\"value\", value)?;\n          }\n          Unparsed(_) | Custom(_) => unreachable!()\n        }\n\n        s.end()\n      }\n    }\n\n    #[cfg(feature = \"serde\")]\n    #[cfg_attr(docsrs, doc(cfg(feature = \"serde\")))]\n    impl<'i, 'de: 'i> serde::Deserialize<'de> for Property<'i> {\n      fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n      where\n        D: serde::Deserializer<'de>,\n      {\n        enum ContentOrRaw<'de> {\n          Content(serde_content::Value<'de>),\n          Raw(CowArcStr<'de>)\n        }\n\n        struct PartialProperty<'de> {\n          property_id: PropertyId<'de>,\n          value: ContentOrRaw<'de>,\n        }\n\n        #[derive(serde::Deserialize)]\n        #[serde(field_identifier, rename_all = \"camelCase\")]\n        enum Field {\n          Property,\n          VendorPrefix,\n          Value,\n          Raw\n        }\n\n        struct PropertyIdVisitor;\n        impl<'de> serde::de::Visitor<'de> for PropertyIdVisitor {\n          type Value = PartialProperty<'de>;\n\n          fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {\n            formatter.write_str(\"a Property\")\n          }\n\n          fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>\n          where\n            A: serde::de::MapAccess<'de>,\n          {\n            let mut property: Option<CowArcStr> = None;\n            let mut vendor_prefix = None;\n            let mut value: Option<ContentOrRaw<'de>> = None;\n            while let Some(key) = map.next_key()? {\n              match key {\n                Field::Property => {\n                  property = Some(map.next_value()?);\n                }\n                Field::VendorPrefix => {\n                  vendor_prefix = Some(map.next_value()?);\n                }\n                Field::Value => {\n                  value = Some(ContentOrRaw::Content(map.next_value()?));\n                }\n                Field::Raw => {\n                  value = Some(ContentOrRaw::Raw(map.next_value()?));\n                }\n              }\n            }\n\n            let property = property.ok_or_else(|| serde::de::Error::missing_field(\"property\"))?;\n            let vendor_prefix = vendor_prefix.unwrap_or(VendorPrefix::None);\n            let value = value.ok_or_else(|| serde::de::Error::missing_field(\"value\"))?;\n            let property_id = PropertyId::from_name_and_prefix(property.as_ref(), vendor_prefix)\n              .unwrap_or_else(|_| PropertyId::from(property));\n            Ok(PartialProperty {\n              property_id,\n              value,\n            })\n          }\n        }\n\n        let partial = deserializer.deserialize_any(PropertyIdVisitor)?;\n\n        let content = match partial.value {\n          ContentOrRaw::Raw(raw) => {\n            let res = Property::parse_string(partial.property_id, raw.as_ref(), ParserOptions::default())\n              .map_err(|_| serde::de::Error::custom(\"Could not parse value\"))?;\n            return Ok(res.into_owned())\n          }\n          ContentOrRaw::Content(content) => content\n        };\n\n        let deserializer = serde_content::Deserializer::new(content).coerce_numbers();\n        match partial.property_id {\n          $(\n            $(#[$meta])*\n            PropertyId::$property$((vp_name!($vp, prefix)))? => {\n              let value = <$type>::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;\n              Ok(Property::$property(value $(, vp_name!($vp, prefix))?))\n            },\n          )+\n          PropertyId::Custom(name) => {\n            if name.as_ref() == \"unparsed\" {\n              let value = UnparsedProperty::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;\n              Ok(Property::Unparsed(value))\n            } else {\n              let value = CustomProperty::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;\n              Ok(Property::Custom(value))\n            }\n          }\n          PropertyId::All => {\n            let value = CSSWideKeyword::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;\n            Ok(Property::All(value))\n          }\n        }\n      }\n    }\n\n    #[cfg(feature = \"jsonschema\")]\n    #[cfg_attr(docsrs, doc(cfg(feature = \"jsonschema\")))]\n    impl<'i> schemars::JsonSchema for Property<'i> {\n      fn is_referenceable() -> bool {\n        true\n      }\n\n      fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {\n        macro_rules! property {\n          ($n: literal) => {\n            fn property(_: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {\n              schemars::schema::Schema::Object(schemars::schema::SchemaObject {\n                instance_type: Some(schemars::schema::InstanceType::String.into()),\n                enum_values: Some(vec![$n.into()]),\n                ..Default::default()\n              })\n            }\n          }\n        }\n\n        schemars::schema::Schema::Object(schemars::schema::SchemaObject {\n          subschemas: Some(Box::new(schemars::schema::SubschemaValidation {\n            one_of: Some(vec![\n              $(\n                {\n                  property!($name);\n\n                  macro_rules! with_prefix {\n                    ($v: ty) => {{\n                      #[derive(schemars::JsonSchema)]\n                      struct T<'i> {\n                        #[schemars(rename = \"property\", schema_with = \"property\")]\n                        _property: &'i u8,\n                        #[schemars(rename = \"vendorPrefix\")]\n                        _vendor_prefix: VendorPrefix,\n                        #[schemars(rename = \"value\")]\n                        _value: $type,\n                      }\n\n                      T::json_schema(gen)\n                    }};\n                    () => {{\n                      #[derive(schemars::JsonSchema)]\n                      struct T<'i> {\n                        #[schemars(rename = \"property\", schema_with = \"property\")]\n                        _property: &'i u8,\n                        #[schemars(rename = \"value\")]\n                        _value: $type,\n                      }\n\n                      T::json_schema(gen)\n                    }};\n                  }\n\n                  with_prefix!($($vp)?)\n                },\n              )+\n              {\n                property!(\"all\");\n                #[derive(schemars::JsonSchema)]\n                struct T {\n                  #[schemars(rename = \"property\", schema_with = \"property\")]\n                  _property: u8,\n                  #[schemars(rename = \"value\")]\n                  _value: CSSWideKeyword\n                }\n                T::json_schema(gen)\n              },\n              {\n                property!(\"unparsed\");\n\n                #[derive(schemars::JsonSchema)]\n                struct T<'i> {\n                  #[schemars(rename = \"property\", schema_with = \"property\")]\n                  _property: &'i u8,\n                  #[schemars(rename = \"value\")]\n                  _value: UnparsedProperty<'i>,\n                }\n\n                T::json_schema(gen)\n              },\n              {\n                property!(\"custom\");\n\n                #[derive(schemars::JsonSchema)]\n                struct T<'i> {\n                  #[schemars(rename = \"property\", schema_with = \"property\")]\n                  _property: &'i u8,\n                  #[schemars(rename = \"value\")]\n                  _value: CustomProperty<'i>,\n                }\n\n                T::json_schema(gen)\n              }\n            ]),\n            ..Default::default()\n          })),\n          ..Default::default()\n        })\n      }\n\n      fn schema_name() -> String {\n        \"Declaration\".into()\n      }\n    }\n  };\n}\n\ndefine_properties! {\n  \"background-color\": BackgroundColor(CssColor),\n  \"background-image\": BackgroundImage(SmallVec<[Image<'i>; 1]>),\n  \"background-position-x\": BackgroundPositionX(SmallVec<[HorizontalPosition; 1]>),\n  \"background-position-y\": BackgroundPositionY(SmallVec<[VerticalPosition; 1]>),\n  \"background-position\": BackgroundPosition(SmallVec<[BackgroundPosition; 1]>) shorthand: true,\n  \"background-size\": BackgroundSize(SmallVec<[BackgroundSize; 1]>),\n  \"background-repeat\": BackgroundRepeat(SmallVec<[BackgroundRepeat; 1]>),\n  \"background-attachment\": BackgroundAttachment(SmallVec<[BackgroundAttachment; 1]>),\n  \"background-clip\": BackgroundClip(SmallVec<[BackgroundClip; 1]>, VendorPrefix) / WebKit / Moz,\n  \"background-origin\": BackgroundOrigin(SmallVec<[BackgroundOrigin; 1]>),\n  \"background\": Background(SmallVec<[Background<'i>; 1]>) shorthand: true,\n\n  \"box-shadow\": BoxShadow(SmallVec<[BoxShadow; 1]>, VendorPrefix) / WebKit / Moz,\n  \"opacity\": Opacity(AlphaValue),\n  \"color\": Color(CssColor),\n  \"display\": Display(Display),\n  \"visibility\": Visibility(Visibility),\n\n  \"width\": Width(Size) [logical_group: Size, category: Physical],\n  \"height\": Height(Size) [logical_group: Size, category: Physical],\n  \"min-width\": MinWidth(Size) [logical_group: MinSize, category: Physical],\n  \"min-height\": MinHeight(Size) [logical_group: MinSize, category: Physical],\n  \"max-width\": MaxWidth(MaxSize) [logical_group: MaxSize, category: Physical],\n  \"max-height\": MaxHeight(MaxSize) [logical_group: MaxSize, category: Physical],\n  \"block-size\": BlockSize(Size) [logical_group: Size, category: Logical],\n  \"inline-size\": InlineSize(Size) [logical_group: Size, category: Logical],\n  \"min-block-size\": MinBlockSize(Size) [logical_group: MinSize, category: Logical],\n  \"min-inline-size\": MinInlineSize(Size) [logical_group: MinSize, category: Logical],\n  \"max-block-size\": MaxBlockSize(MaxSize) [logical_group: MaxSize, category: Logical],\n  \"max-inline-size\": MaxInlineSize(MaxSize) [logical_group: MaxSize, category: Logical],\n  \"box-sizing\": BoxSizing(BoxSizing, VendorPrefix) / WebKit / Moz,\n  \"aspect-ratio\": AspectRatio(AspectRatio),\n\n  \"overflow\": Overflow(Overflow) shorthand: true,\n  \"overflow-x\": OverflowX(OverflowKeyword),\n  \"overflow-y\": OverflowY(OverflowKeyword),\n  \"text-overflow\": TextOverflow(TextOverflow, VendorPrefix) / O,\n\n  // https://www.w3.org/TR/2020/WD-css-position-3-20200519\n  \"position\": Position(position::Position),\n  \"top\": Top(LengthPercentageOrAuto) [logical_group: Inset, category: Physical],\n  \"bottom\": Bottom(LengthPercentageOrAuto) [logical_group: Inset, category: Physical],\n  \"left\": Left(LengthPercentageOrAuto) [logical_group: Inset, category: Physical],\n  \"right\": Right(LengthPercentageOrAuto) [logical_group: Inset, category: Physical],\n  \"inset-block-start\": InsetBlockStart(LengthPercentageOrAuto) [logical_group: Inset, category: Logical],\n  \"inset-block-end\": InsetBlockEnd(LengthPercentageOrAuto) [logical_group: Inset, category: Logical],\n  \"inset-inline-start\": InsetInlineStart(LengthPercentageOrAuto) [logical_group: Inset, category: Logical],\n  \"inset-inline-end\": InsetInlineEnd(LengthPercentageOrAuto) [logical_group: Inset, category: Logical],\n  \"inset-block\": InsetBlock(InsetBlock) shorthand: true,\n  \"inset-inline\": InsetInline(InsetInline) shorthand: true,\n  \"inset\": Inset(Inset) shorthand: true,\n\n  \"border-spacing\": BorderSpacing(Size2D<Length>),\n\n  \"border-top-color\": BorderTopColor(CssColor) [logical_group: BorderColor, category: Physical],\n  \"border-bottom-color\": BorderBottomColor(CssColor) [logical_group: BorderColor, category: Physical],\n  \"border-left-color\": BorderLeftColor(CssColor) [logical_group: BorderColor, category: Physical],\n  \"border-right-color\": BorderRightColor(CssColor) [logical_group: BorderColor, category: Physical],\n  \"border-block-start-color\": BorderBlockStartColor(CssColor) [logical_group: BorderColor, category: Logical],\n  \"border-block-end-color\": BorderBlockEndColor(CssColor) [logical_group: BorderColor, category: Logical],\n  \"border-inline-start-color\": BorderInlineStartColor(CssColor) [logical_group: BorderColor, category: Logical],\n  \"border-inline-end-color\": BorderInlineEndColor(CssColor) [logical_group: BorderColor, category: Logical],\n\n  \"border-top-style\": BorderTopStyle(LineStyle) [logical_group: BorderStyle, category: Physical],\n  \"border-bottom-style\": BorderBottomStyle(LineStyle) [logical_group: BorderStyle, category: Physical],\n  \"border-left-style\": BorderLeftStyle(LineStyle) [logical_group: BorderStyle, category: Physical],\n  \"border-right-style\": BorderRightStyle(LineStyle) [logical_group: BorderStyle, category: Physical],\n  \"border-block-start-style\": BorderBlockStartStyle(LineStyle) [logical_group: BorderStyle, category: Logical],\n  \"border-block-end-style\": BorderBlockEndStyle(LineStyle) [logical_group: BorderStyle, category: Logical],\n  \"border-inline-start-style\": BorderInlineStartStyle(LineStyle) [logical_group: BorderStyle, category: Logical],\n  \"border-inline-end-style\": BorderInlineEndStyle(LineStyle) [logical_group: BorderStyle, category: Logical],\n\n  \"border-top-width\": BorderTopWidth(BorderSideWidth) [logical_group: BorderWidth, category: Physical],\n  \"border-bottom-width\": BorderBottomWidth(BorderSideWidth) [logical_group: BorderWidth, category: Physical],\n  \"border-left-width\": BorderLeftWidth(BorderSideWidth) [logical_group: BorderWidth, category: Physical],\n  \"border-right-width\": BorderRightWidth(BorderSideWidth) [logical_group: BorderWidth, category: Physical],\n  \"border-block-start-width\": BorderBlockStartWidth(BorderSideWidth) [logical_group: BorderWidth, category: Logical],\n  \"border-block-end-width\": BorderBlockEndWidth(BorderSideWidth) [logical_group: BorderWidth, category: Logical],\n  \"border-inline-start-width\": BorderInlineStartWidth(BorderSideWidth) [logical_group: BorderWidth, category: Logical],\n  \"border-inline-end-width\": BorderInlineEndWidth(BorderSideWidth) [logical_group: BorderWidth, category: Logical],\n\n  \"border-top-left-radius\": BorderTopLeftRadius(Size2D<LengthPercentage>, VendorPrefix) / WebKit / Moz [logical_group: BorderRadius, category: Physical],\n  \"border-top-right-radius\": BorderTopRightRadius(Size2D<LengthPercentage>, VendorPrefix) / WebKit / Moz [logical_group: BorderRadius, category: Physical],\n  \"border-bottom-left-radius\": BorderBottomLeftRadius(Size2D<LengthPercentage>, VendorPrefix) / WebKit / Moz [logical_group: BorderRadius, category: Physical],\n  \"border-bottom-right-radius\": BorderBottomRightRadius(Size2D<LengthPercentage>, VendorPrefix) / WebKit / Moz [logical_group: BorderRadius, category: Physical],\n  \"border-start-start-radius\": BorderStartStartRadius(Size2D<LengthPercentage>) [logical_group: BorderRadius, category: Logical],\n  \"border-start-end-radius\": BorderStartEndRadius(Size2D<LengthPercentage>) [logical_group: BorderRadius, category: Logical],\n  \"border-end-start-radius\": BorderEndStartRadius(Size2D<LengthPercentage>) [logical_group: BorderRadius, category: Logical],\n  \"border-end-end-radius\": BorderEndEndRadius(Size2D<LengthPercentage>) [logical_group: BorderRadius, category: Logical],\n  \"border-radius\": BorderRadius(BorderRadius, VendorPrefix) / WebKit / Moz shorthand: true,\n\n  \"border-image-source\": BorderImageSource(Image<'i>),\n  \"border-image-outset\": BorderImageOutset(Rect<LengthOrNumber>),\n  \"border-image-repeat\": BorderImageRepeat(BorderImageRepeat),\n  \"border-image-width\": BorderImageWidth(Rect<BorderImageSideWidth>),\n  \"border-image-slice\": BorderImageSlice(BorderImageSlice),\n  \"border-image\": BorderImage(BorderImage<'i>, VendorPrefix) / WebKit / Moz / O shorthand: true,\n\n  \"border-color\": BorderColor(BorderColor) shorthand: true,\n  \"border-style\": BorderStyle(BorderStyle) shorthand: true,\n  \"border-width\": BorderWidth(BorderWidth) shorthand: true,\n\n  \"border-block-color\": BorderBlockColor(BorderBlockColor) shorthand: true,\n  \"border-block-style\": BorderBlockStyle(BorderBlockStyle) shorthand: true,\n  \"border-block-width\": BorderBlockWidth(BorderBlockWidth) shorthand: true,\n\n  \"border-inline-color\": BorderInlineColor(BorderInlineColor) shorthand: true,\n  \"border-inline-style\": BorderInlineStyle(BorderInlineStyle) shorthand: true,\n  \"border-inline-width\": BorderInlineWidth(BorderInlineWidth) shorthand: true,\n\n  \"border\": Border(Border) shorthand: true,\n  \"border-top\": BorderTop(BorderTop) shorthand: true,\n  \"border-bottom\": BorderBottom(BorderBottom) shorthand: true,\n  \"border-left\": BorderLeft(BorderLeft) shorthand: true,\n  \"border-right\": BorderRight(BorderRight) shorthand: true,\n  \"border-block\": BorderBlock(BorderBlock) shorthand: true,\n  \"border-block-start\": BorderBlockStart(BorderBlockStart) shorthand: true,\n  \"border-block-end\": BorderBlockEnd(BorderBlockEnd) shorthand: true,\n  \"border-inline\": BorderInline(BorderInline) shorthand: true,\n  \"border-inline-start\": BorderInlineStart(BorderInlineStart) shorthand: true,\n  \"border-inline-end\": BorderInlineEnd(BorderInlineEnd) shorthand: true,\n\n  \"outline\": Outline(Outline) shorthand: true,\n  \"outline-color\": OutlineColor(CssColor),\n  \"outline-style\": OutlineStyle(OutlineStyle),\n  \"outline-width\": OutlineWidth(BorderSideWidth),\n\n  // Flex properties: https://www.w3.org/TR/2018/CR-css-flexbox-1-20181119\n  \"flex-direction\": FlexDirection(FlexDirection, VendorPrefix) / WebKit / Ms,\n  \"flex-wrap\": FlexWrap(FlexWrap, VendorPrefix) / WebKit / Ms,\n  \"flex-flow\": FlexFlow(FlexFlow, VendorPrefix) / WebKit / Ms shorthand: true,\n  \"flex-grow\": FlexGrow(CSSNumber, VendorPrefix) / WebKit,\n  \"flex-shrink\": FlexShrink(CSSNumber, VendorPrefix) / WebKit,\n  \"flex-basis\": FlexBasis(LengthPercentageOrAuto, VendorPrefix) / WebKit,\n  \"flex\": Flex(Flex, VendorPrefix) / WebKit / Ms shorthand: true,\n  \"order\": Order(CSSInteger, VendorPrefix) / WebKit,\n\n  // Align properties: https://www.w3.org/TR/2020/WD-css-align-3-20200421\n  \"align-content\": AlignContent(AlignContent, VendorPrefix) / WebKit,\n  \"justify-content\": JustifyContent(JustifyContent, VendorPrefix) / WebKit,\n  \"place-content\": PlaceContent(PlaceContent) shorthand: true,\n  \"align-self\": AlignSelf(AlignSelf, VendorPrefix) / WebKit,\n  \"justify-self\": JustifySelf(JustifySelf),\n  \"place-self\": PlaceSelf(PlaceSelf) shorthand: true,\n  \"align-items\": AlignItems(AlignItems, VendorPrefix) / WebKit,\n  \"justify-items\": JustifyItems(JustifyItems),\n  \"place-items\": PlaceItems(PlaceItems) shorthand: true,\n  \"row-gap\": RowGap(GapValue),\n  \"column-gap\": ColumnGap(GapValue),\n  \"gap\": Gap(Gap) shorthand: true,\n\n  // Old flex (2009): https://www.w3.org/TR/2009/WD-css3-flexbox-20090723/\n  \"box-orient\": BoxOrient(BoxOrient, VendorPrefix) / WebKit / Moz unprefixed: false,\n  \"box-direction\": BoxDirection(BoxDirection, VendorPrefix) / WebKit / Moz unprefixed: false,\n  \"box-ordinal-group\": BoxOrdinalGroup(CSSInteger, VendorPrefix) / WebKit / Moz unprefixed: false,\n  \"box-align\": BoxAlign(BoxAlign, VendorPrefix) / WebKit / Moz unprefixed: false,\n  \"box-flex\": BoxFlex(CSSNumber, VendorPrefix) / WebKit / Moz unprefixed: false,\n  \"box-flex-group\": BoxFlexGroup(CSSInteger, VendorPrefix) / WebKit unprefixed: false,\n  \"box-pack\": BoxPack(BoxPack, VendorPrefix) / WebKit / Moz unprefixed: false,\n  \"box-lines\": BoxLines(BoxLines, VendorPrefix) / WebKit / Moz unprefixed: false,\n\n  // Old flex (2012): https://www.w3.org/TR/2012/WD-css3-flexbox-20120322/\n  \"flex-pack\": FlexPack(FlexPack, VendorPrefix) / Ms unprefixed: false,\n  \"flex-order\": FlexOrder(CSSInteger, VendorPrefix) / Ms unprefixed: false,\n  \"flex-align\": FlexAlign(BoxAlign, VendorPrefix) / Ms unprefixed: false,\n  \"flex-item-align\": FlexItemAlign(FlexItemAlign, VendorPrefix) / Ms unprefixed: false,\n  \"flex-line-pack\": FlexLinePack(FlexLinePack, VendorPrefix) / Ms unprefixed: false,\n\n  // Microsoft extensions\n  \"flex-positive\": FlexPositive(CSSNumber, VendorPrefix) / Ms unprefixed: false,\n  \"flex-negative\": FlexNegative(CSSNumber, VendorPrefix) / Ms unprefixed: false,\n  \"flex-preferred-size\": FlexPreferredSize(LengthPercentageOrAuto, VendorPrefix) / Ms unprefixed: false,\n\n  \"grid-template-columns\": GridTemplateColumns(TrackSizing<'i>),\n  \"grid-template-rows\": GridTemplateRows(TrackSizing<'i>),\n  \"grid-auto-columns\": GridAutoColumns(TrackSizeList),\n  \"grid-auto-rows\": GridAutoRows(TrackSizeList),\n  \"grid-auto-flow\": GridAutoFlow(GridAutoFlow),\n  \"grid-template-areas\": GridTemplateAreas(GridTemplateAreas),\n  \"grid-template\": GridTemplate(GridTemplate<'i>) shorthand: true,\n  \"grid\": Grid(Grid<'i>) shorthand: true,\n  \"grid-row-start\": GridRowStart(GridLine<'i>),\n  \"grid-row-end\": GridRowEnd(GridLine<'i>),\n  \"grid-column-start\": GridColumnStart(GridLine<'i>),\n  \"grid-column-end\": GridColumnEnd(GridLine<'i>),\n  \"grid-row\": GridRow(GridRow<'i>) shorthand: true,\n  \"grid-column\": GridColumn(GridColumn<'i>) shorthand: true,\n  \"grid-area\": GridArea(GridArea<'i>) shorthand: true,\n\n  \"margin-top\": MarginTop(LengthPercentageOrAuto) [logical_group: Margin, category: Physical],\n  \"margin-bottom\": MarginBottom(LengthPercentageOrAuto) [logical_group: Margin, category: Physical],\n  \"margin-left\": MarginLeft(LengthPercentageOrAuto) [logical_group: Margin, category: Physical],\n  \"margin-right\": MarginRight(LengthPercentageOrAuto) [logical_group: Margin, category: Physical],\n  \"margin-block-start\": MarginBlockStart(LengthPercentageOrAuto) [logical_group: Margin, category: Logical],\n  \"margin-block-end\": MarginBlockEnd(LengthPercentageOrAuto) [logical_group: Margin, category: Logical],\n  \"margin-inline-start\": MarginInlineStart(LengthPercentageOrAuto) [logical_group: Margin, category: Logical],\n  \"margin-inline-end\": MarginInlineEnd(LengthPercentageOrAuto) [logical_group: Margin, category: Logical],\n  \"margin-block\": MarginBlock(MarginBlock) shorthand: true,\n  \"margin-inline\": MarginInline(MarginInline) shorthand: true,\n  \"margin\": Margin(Margin) shorthand: true,\n\n  \"padding-top\": PaddingTop(LengthPercentageOrAuto) [logical_group: Padding, category: Physical],\n  \"padding-bottom\": PaddingBottom(LengthPercentageOrAuto) [logical_group: Padding, category: Physical],\n  \"padding-left\": PaddingLeft(LengthPercentageOrAuto) [logical_group: Padding, category: Physical],\n  \"padding-right\": PaddingRight(LengthPercentageOrAuto) [logical_group: Padding, category: Physical],\n  \"padding-block-start\": PaddingBlockStart(LengthPercentageOrAuto) [logical_group: Padding, category: Logical],\n  \"padding-block-end\": PaddingBlockEnd(LengthPercentageOrAuto) [logical_group: Padding, category: Logical],\n  \"padding-inline-start\": PaddingInlineStart(LengthPercentageOrAuto) [logical_group: Padding, category: Logical],\n  \"padding-inline-end\": PaddingInlineEnd(LengthPercentageOrAuto) [logical_group: Padding, category: Logical],\n  \"padding-block\": PaddingBlock(PaddingBlock) shorthand: true,\n  \"padding-inline\": PaddingInline(PaddingInline) shorthand: true,\n  \"padding\": Padding(Padding) shorthand: true,\n\n  \"scroll-margin-top\": ScrollMarginTop(LengthPercentageOrAuto) [logical_group: ScrollMargin, category: Physical],\n  \"scroll-margin-bottom\": ScrollMarginBottom(LengthPercentageOrAuto) [logical_group: ScrollMargin, category: Physical],\n  \"scroll-margin-left\": ScrollMarginLeft(LengthPercentageOrAuto) [logical_group: ScrollMargin, category: Physical],\n  \"scroll-margin-right\": ScrollMarginRight(LengthPercentageOrAuto) [logical_group: ScrollMargin, category: Physical],\n  \"scroll-margin-block-start\": ScrollMarginBlockStart(LengthPercentageOrAuto) [logical_group: ScrollMargin, category: Logical],\n  \"scroll-margin-block-end\": ScrollMarginBlockEnd(LengthPercentageOrAuto) [logical_group: ScrollMargin, category: Logical],\n  \"scroll-margin-inline-start\": ScrollMarginInlineStart(LengthPercentageOrAuto) [logical_group: ScrollMargin, category: Logical],\n  \"scroll-margin-inline-end\": ScrollMarginInlineEnd(LengthPercentageOrAuto) [logical_group: ScrollMargin, category: Logical],\n  \"scroll-margin-block\": ScrollMarginBlock(ScrollMarginBlock) shorthand: true,\n  \"scroll-margin-inline\": ScrollMarginInline(ScrollMarginInline) shorthand: true,\n  \"scroll-margin\": ScrollMargin(ScrollMargin) shorthand: true,\n\n  \"scroll-padding-top\": ScrollPaddingTop(LengthPercentageOrAuto) [logical_group: ScrollPadding, category: Physical],\n  \"scroll-padding-bottom\": ScrollPaddingBottom(LengthPercentageOrAuto) [logical_group: ScrollPadding, category: Physical],\n  \"scroll-padding-left\": ScrollPaddingLeft(LengthPercentageOrAuto) [logical_group: ScrollPadding, category: Physical],\n  \"scroll-padding-right\": ScrollPaddingRight(LengthPercentageOrAuto) [logical_group: ScrollPadding, category: Physical],\n  \"scroll-padding-block-start\": ScrollPaddingBlockStart(LengthPercentageOrAuto) [logical_group: ScrollPadding, category: Logical],\n  \"scroll-padding-block-end\": ScrollPaddingBlockEnd(LengthPercentageOrAuto) [logical_group: ScrollPadding, category: Logical],\n  \"scroll-padding-inline-start\": ScrollPaddingInlineStart(LengthPercentageOrAuto) [logical_group: ScrollPadding, category: Logical],\n  \"scroll-padding-inline-end\": ScrollPaddingInlineEnd(LengthPercentageOrAuto) [logical_group: ScrollPadding, category: Logical],\n  \"scroll-padding-block\": ScrollPaddingBlock(ScrollPaddingBlock) shorthand: true,\n  \"scroll-padding-inline\": ScrollPaddingInline(ScrollPaddingInline) shorthand: true,\n  \"scroll-padding\": ScrollPadding(ScrollPadding) shorthand: true,\n\n  \"font-weight\": FontWeight(FontWeight),\n  \"font-size\": FontSize(FontSize),\n  \"font-stretch\": FontStretch(FontStretch),\n  \"font-family\": FontFamily(Vec<FontFamily<'i>>),\n  \"font-style\": FontStyle(FontStyle),\n  \"font-variant-caps\": FontVariantCaps(FontVariantCaps),\n  \"line-height\": LineHeight(LineHeight),\n  \"font\": Font(Font<'i>) shorthand: true,\n  \"vertical-align\": VerticalAlign(VerticalAlign),\n  \"font-palette\": FontPalette(DashedIdentReference<'i>),\n\n  \"transition-property\": TransitionProperty(SmallVec<[PropertyId<'i>; 1]>, VendorPrefix) / WebKit / Moz / Ms,\n  \"transition-duration\": TransitionDuration(SmallVec<[Time; 1]>, VendorPrefix) / WebKit / Moz / Ms,\n  \"transition-delay\": TransitionDelay(SmallVec<[Time; 1]>, VendorPrefix) / WebKit / Moz / Ms,\n  \"transition-timing-function\": TransitionTimingFunction(SmallVec<[EasingFunction; 1]>, VendorPrefix) / WebKit / Moz / Ms,\n  \"transition\": Transition(SmallVec<[Transition<'i>; 1]>, VendorPrefix) / WebKit / Moz / Ms shorthand: true,\n\n  \"animation-name\": AnimationName(AnimationNameList<'i>, VendorPrefix) / WebKit / Moz / O,\n  \"animation-duration\": AnimationDuration(SmallVec<[Time; 1]>, VendorPrefix) / WebKit / Moz / O,\n  \"animation-timing-function\": AnimationTimingFunction(SmallVec<[EasingFunction; 1]>, VendorPrefix) / WebKit / Moz / O,\n  \"animation-iteration-count\": AnimationIterationCount(SmallVec<[AnimationIterationCount; 1]>, VendorPrefix) / WebKit / Moz / O,\n  \"animation-direction\": AnimationDirection(SmallVec<[AnimationDirection; 1]>, VendorPrefix) / WebKit / Moz / O,\n  \"animation-play-state\": AnimationPlayState(SmallVec<[AnimationPlayState; 1]>, VendorPrefix) / WebKit / Moz / O,\n  \"animation-delay\": AnimationDelay(SmallVec<[Time; 1]>, VendorPrefix) / WebKit / Moz / O,\n  \"animation-fill-mode\": AnimationFillMode(SmallVec<[AnimationFillMode; 1]>, VendorPrefix) / WebKit / Moz / O,\n  \"animation-composition\": AnimationComposition(SmallVec<[AnimationComposition; 1]>),\n  \"animation-timeline\": AnimationTimeline(SmallVec<[AnimationTimeline<'i>; 1]>),\n  \"animation-range-start\": AnimationRangeStart(SmallVec<[AnimationRangeStart; 1]>),\n  \"animation-range-end\": AnimationRangeEnd(SmallVec<[AnimationRangeEnd; 1]>),\n  \"animation-range\": AnimationRange(SmallVec<[AnimationRange; 1]>),\n  \"animation\": Animation(AnimationList<'i>, VendorPrefix) / WebKit / Moz / O shorthand: true,\n\n  // https://drafts.csswg.org/css-transforms-2/\n  \"transform\": Transform(TransformList, VendorPrefix) / WebKit / Moz / Ms / O,\n  \"transform-origin\": TransformOrigin(Position, VendorPrefix) / WebKit / Moz / Ms / O, // TODO: handle z offset syntax\n  \"transform-style\": TransformStyle(TransformStyle, VendorPrefix) / WebKit / Moz,\n  \"transform-box\": TransformBox(TransformBox),\n  \"backface-visibility\": BackfaceVisibility(BackfaceVisibility, VendorPrefix) / WebKit / Moz,\n  \"perspective\": Perspective(Perspective, VendorPrefix) / WebKit / Moz,\n  \"perspective-origin\": PerspectiveOrigin(Position, VendorPrefix) / WebKit / Moz,\n  \"translate\": Translate(Translate),\n  \"rotate\": Rotate(Rotate),\n  \"scale\": Scale(Scale),\n\n  // https://www.w3.org/TR/2021/CRD-css-text-3-20210422\n  \"text-transform\": TextTransform(TextTransform),\n  \"white-space\": WhiteSpace(WhiteSpace),\n  \"tab-size\": TabSize(LengthOrNumber, VendorPrefix) / Moz / O,\n  \"word-break\": WordBreak(WordBreak),\n  \"line-break\": LineBreak(LineBreak),\n  \"hyphens\": Hyphens(Hyphens, VendorPrefix) / WebKit / Moz / Ms,\n  \"overflow-wrap\": OverflowWrap(OverflowWrap),\n  \"word-wrap\": WordWrap(OverflowWrap),\n  \"text-align\": TextAlign(TextAlign),\n  \"text-align-last\": TextAlignLast(TextAlignLast, VendorPrefix) / Moz,\n  \"text-justify\": TextJustify(TextJustify),\n  \"word-spacing\": WordSpacing(Spacing),\n  \"letter-spacing\": LetterSpacing(Spacing),\n  \"text-indent\": TextIndent(TextIndent),\n\n  // https://www.w3.org/TR/2020/WD-css-text-decor-4-20200506\n  \"text-decoration-line\": TextDecorationLine(TextDecorationLine, VendorPrefix) / WebKit / Moz,\n  \"text-decoration-style\": TextDecorationStyle(TextDecorationStyle, VendorPrefix) / WebKit / Moz,\n  \"text-decoration-color\": TextDecorationColor(CssColor, VendorPrefix) / WebKit / Moz,\n  \"text-decoration-thickness\": TextDecorationThickness(TextDecorationThickness),\n  \"text-decoration\": TextDecoration(TextDecoration, VendorPrefix) / WebKit / Moz shorthand: true,\n  \"text-decoration-skip-ink\": TextDecorationSkipInk(TextDecorationSkipInk, VendorPrefix) / WebKit,\n  \"text-emphasis-style\": TextEmphasisStyle(TextEmphasisStyle<'i>, VendorPrefix) / WebKit,\n  \"text-emphasis-color\": TextEmphasisColor(CssColor, VendorPrefix) / WebKit,\n  \"text-emphasis\": TextEmphasis(TextEmphasis<'i>, VendorPrefix) / WebKit shorthand: true,\n  \"text-emphasis-position\": TextEmphasisPosition(TextEmphasisPosition, VendorPrefix) / WebKit,\n  \"text-shadow\": TextShadow(SmallVec<[TextShadow; 1]>),\n\n  // https://w3c.github.io/csswg-drafts/css-size-adjust/\n  \"text-size-adjust\": TextSizeAdjust(TextSizeAdjust, VendorPrefix) / WebKit / Moz / Ms,\n\n  // https://drafts.csswg.org/css-writing-modes-3/\n  \"direction\": Direction(Direction),\n  \"unicode-bidi\": UnicodeBidi(UnicodeBidi),\n\n  // https://www.w3.org/TR/css-break-3/\n  \"box-decoration-break\": BoxDecorationBreak(BoxDecorationBreak, VendorPrefix) / WebKit,\n\n  // https://www.w3.org/TR/2021/WD-css-ui-4-20210316\n  \"resize\": Resize(Resize),\n  \"cursor\": Cursor(Cursor<'i>),\n  \"caret-color\": CaretColor(ColorOrAuto),\n  \"caret-shape\": CaretShape(CaretShape),\n  \"caret\": Caret(Caret) shorthand: true,\n  \"user-select\": UserSelect(UserSelect, VendorPrefix) / WebKit / Moz / Ms,\n  \"accent-color\": AccentColor(ColorOrAuto),\n  \"appearance\": Appearance(Appearance<'i>, VendorPrefix) / WebKit / Moz / Ms,\n\n  // https://www.w3.org/TR/2020/WD-css-lists-3-20201117\n  \"list-style-type\": ListStyleType(ListStyleType<'i>),\n  \"list-style-image\": ListStyleImage(Image<'i>),\n  \"list-style-position\": ListStylePosition(ListStylePosition),\n  \"list-style\": ListStyle(ListStyle<'i>) shorthand: true,\n  \"marker-side\": MarkerSide(MarkerSide),\n\n  // CSS modules\n  \"composes\": Composes(Composes<'i>) if css_modules,\n\n  // https://www.w3.org/TR/SVG2/painting.html\n  \"fill\": Fill(SVGPaint<'i>),\n  \"fill-rule\": FillRule(FillRule),\n  \"fill-opacity\": FillOpacity(AlphaValue),\n  \"stroke\": Stroke(SVGPaint<'i>),\n  \"stroke-opacity\": StrokeOpacity(AlphaValue),\n  \"stroke-width\": StrokeWidth(LengthPercentage),\n  \"stroke-linecap\": StrokeLinecap(StrokeLinecap),\n  \"stroke-linejoin\": StrokeLinejoin(StrokeLinejoin),\n  \"stroke-miterlimit\": StrokeMiterlimit(CSSNumber),\n  \"stroke-dasharray\": StrokeDasharray(StrokeDasharray),\n  \"stroke-dashoffset\": StrokeDashoffset(LengthPercentage),\n  \"marker-start\": MarkerStart(Marker<'i>),\n  \"marker-mid\": MarkerMid(Marker<'i>),\n  \"marker-end\": MarkerEnd(Marker<'i>),\n  \"marker\": Marker(Marker<'i>),\n  \"color-interpolation\": ColorInterpolation(ColorInterpolation),\n  \"color-interpolation-filters\": ColorInterpolationFilters(ColorInterpolation),\n  \"color-rendering\": ColorRendering(ColorRendering),\n  \"shape-rendering\": ShapeRendering(ShapeRendering),\n  \"text-rendering\": TextRendering(TextRendering),\n  \"image-rendering\": ImageRendering(ImageRendering),\n\n  // https://www.w3.org/TR/css-masking-1/\n  \"clip-path\": ClipPath(ClipPath<'i>, VendorPrefix) / WebKit,\n  \"clip-rule\": ClipRule(FillRule),\n  \"mask-image\": MaskImage(SmallVec<[Image<'i>; 1]>, VendorPrefix) / WebKit,\n  \"mask-mode\": MaskMode(SmallVec<[MaskMode; 1]>),\n  \"mask-repeat\": MaskRepeat(SmallVec<[BackgroundRepeat; 1]>, VendorPrefix) / WebKit,\n  \"mask-position-x\": MaskPositionX(SmallVec<[HorizontalPosition; 1]>),\n  \"mask-position-y\": MaskPositionY(SmallVec<[VerticalPosition; 1]>),\n  \"mask-position\": MaskPosition(SmallVec<[Position; 1]>, VendorPrefix) / WebKit,\n  \"mask-clip\": MaskClip(SmallVec<[MaskClip; 1]>, VendorPrefix) / WebKit,\n  \"mask-origin\": MaskOrigin(SmallVec<[GeometryBox; 1]>, VendorPrefix) / WebKit,\n  \"mask-size\": MaskSize(SmallVec<[BackgroundSize; 1]>, VendorPrefix) / WebKit,\n  \"mask-composite\": MaskComposite(SmallVec<[MaskComposite; 1]>),\n  \"mask-type\": MaskType(MaskType),\n  \"mask\": Mask(SmallVec<[Mask<'i>; 1]>, VendorPrefix) / WebKit shorthand: true,\n  \"mask-border-source\": MaskBorderSource(Image<'i>),\n  \"mask-border-mode\": MaskBorderMode(MaskBorderMode),\n  \"mask-border-slice\": MaskBorderSlice(BorderImageSlice),\n  \"mask-border-width\": MaskBorderWidth(Rect<BorderImageSideWidth>),\n  \"mask-border-outset\": MaskBorderOutset(Rect<LengthOrNumber>),\n  \"mask-border-repeat\": MaskBorderRepeat(BorderImageRepeat),\n  \"mask-border\": MaskBorder(MaskBorder<'i>) shorthand: true,\n\n  // WebKit additions\n  \"-webkit-mask-composite\": WebKitMaskComposite(SmallVec<[WebKitMaskComposite; 1]>),\n  \"mask-source-type\": WebKitMaskSourceType(SmallVec<[WebKitMaskSourceType; 1]>, VendorPrefix) / WebKit unprefixed: false,\n  \"mask-box-image\": WebKitMaskBoxImage(BorderImage<'i>, VendorPrefix) / WebKit unprefixed: false,\n  \"mask-box-image-source\": WebKitMaskBoxImageSource(Image<'i>, VendorPrefix) / WebKit unprefixed: false,\n  \"mask-box-image-slice\": WebKitMaskBoxImageSlice(BorderImageSlice, VendorPrefix) / WebKit unprefixed: false,\n  \"mask-box-image-width\": WebKitMaskBoxImageWidth(Rect<BorderImageSideWidth>, VendorPrefix) / WebKit unprefixed: false,\n  \"mask-box-image-outset\": WebKitMaskBoxImageOutset(Rect<LengthOrNumber>, VendorPrefix) / WebKit unprefixed: false,\n  \"mask-box-image-repeat\": WebKitMaskBoxImageRepeat(BorderImageRepeat, VendorPrefix) / WebKit unprefixed: false,\n\n  // https://drafts.fxtf.org/filter-effects-1/\n  \"filter\": Filter(FilterList<'i>, VendorPrefix) / WebKit,\n  \"backdrop-filter\": BackdropFilter(FilterList<'i>, VendorPrefix) / WebKit,\n\n  // https://www.w3.org/TR/compositing-1/\n  \"mix-blend-mode\": MixBlendMode(BlendMode),\n\n  // https://drafts.csswg.org/css2/\n  \"z-index\": ZIndex(position::ZIndex),\n\n  // https://drafts.csswg.org/css-contain-3/\n  \"container-type\": ContainerType(ContainerType),\n  \"container-name\": ContainerName(ContainerNameList<'i>),\n  \"container\": Container(Container<'i>) shorthand: true,\n\n  // https://w3c.github.io/csswg-drafts/css-view-transitions-1/\n  \"view-transition-name\": ViewTransitionName(ViewTransitionName<'i>),\n  // https://drafts.csswg.org/css-view-transitions-2/\n  \"view-transition-class\": ViewTransitionClass(NoneOrCustomIdentList<'i>),\n  \"view-transition-group\": ViewTransitionGroup(ViewTransitionGroup<'i>),\n\n  // https://drafts.csswg.org/css-color-adjust/\n  \"color-scheme\": ColorScheme(ColorScheme),\n  \"print-color-adjust\": PrintColorAdjust(PrintColorAdjust, VendorPrefix) / WebKit,\n}\n\nimpl<'i, T: smallvec::Array<Item = V>, V: Parse<'i>> Parse<'i> for SmallVec<T> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    // Copied from cssparser `parse_comma_separated` but using SmallVec instead of Vec.\n    let mut values = smallvec![];\n    loop {\n      input.skip_whitespace(); // Unnecessary for correctness, but may help try() in parse_one rewind less.\n      match input.parse_until_before(Delimiter::Comma, &mut V::parse) {\n        Ok(v) => values.push(v),\n        Err(err) => return Err(err),\n      }\n      match input.next() {\n        Err(_) => return Ok(values),\n        Ok(&cssparser::Token::Comma) => continue,\n        Ok(_) => unreachable!(),\n      }\n    }\n  }\n}\n\nimpl<T: smallvec::Array<Item = V>, V: ToCss> ToCss for SmallVec<T> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    let len = self.len();\n    for (idx, val) in self.iter().enumerate() {\n      val.to_css(dest)?;\n      if idx < len - 1 {\n        dest.delim(',', false)?;\n      }\n    }\n    Ok(())\n  }\n}\n\nimpl<'i, T: Parse<'i>> Parse<'i> for Vec<T> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    input.parse_comma_separated(|input| T::parse(input))\n  }\n}\n\nimpl<T: ToCss> ToCss for Vec<T> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    let len = self.len();\n    for (idx, val) in self.iter().enumerate() {\n      val.to_css(dest)?;\n      if idx < len - 1 {\n        dest.delim(',', false)?;\n      }\n    }\n    Ok(())\n  }\n}\n\nenum_property! {\n  /// A [CSS-wide keyword](https://drafts.csswg.org/css-cascade-5/#defaulting-keywords).\n  pub enum CSSWideKeyword {\n    /// The property's initial value.\n    \"initial\": Initial,\n    /// The property's computed value on the parent element.\n    \"inherit\": Inherit,\n    /// Either inherit or initial depending on whether the property is inherited.\n    \"unset\": Unset,\n    /// Rolls back the cascade to the cascaded value of the earlier origin.\n    \"revert\": Revert,\n    /// Rolls back the cascade to the value of the previous cascade layer.\n    \"revert-layer\": RevertLayer,\n  }\n}\n"
  },
  {
    "path": "src/properties/outline.rs",
    "content": "//! CSS properties related to outlines.\n\nuse super::border::{BorderSideWidth, GenericBorder, LineStyle};\nuse super::{Property, PropertyId};\nuse crate::context::PropertyHandlerContext;\nuse crate::declaration::{DeclarationBlock, DeclarationList};\nuse crate::error::{ParserError, PrinterError};\nuse crate::macros::{impl_shorthand, shorthand_handler};\nuse crate::printer::Printer;\nuse crate::targets::Browsers;\nuse crate::traits::{FallbackValues, IsCompatible, Parse, PropertyHandler, Shorthand, ToCss};\nuse crate::values::color::CssColor;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse cssparser::*;\n\n/// A value for the [outline-style](https://drafts.csswg.org/css-ui/#outline-style) property.\n#[derive(Debug, Clone, PartialEq, Parse, ToCss)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum OutlineStyle {\n  /// The `auto` keyword.\n  Auto,\n  /// A value equivalent to the `border-style` property.\n  LineStyle(LineStyle),\n}\n\nimpl Default for OutlineStyle {\n  fn default() -> OutlineStyle {\n    OutlineStyle::LineStyle(LineStyle::None)\n  }\n}\n\nimpl IsCompatible for OutlineStyle {\n  fn is_compatible(&self, _browsers: Browsers) -> bool {\n    true\n  }\n}\n\n/// A value for the [outline](https://drafts.csswg.org/css-ui/#outline) shorthand property.\npub type Outline = GenericBorder<OutlineStyle, 11>;\n\nimpl_shorthand! {\n  Outline(Outline) {\n    width: [OutlineWidth],\n    style: [OutlineStyle],\n    color: [OutlineColor],\n  }\n}\n\nshorthand_handler!(OutlineHandler -> Outline fallbacks: true {\n  width: OutlineWidth(BorderSideWidth),\n  style: OutlineStyle(OutlineStyle),\n  color: OutlineColor(CssColor, fallback: true),\n});\n"
  },
  {
    "path": "src/properties/overflow.rs",
    "content": "//! CSS properties related to overflow.\n\nuse super::{Property, PropertyId};\nuse crate::compat::Feature;\nuse crate::context::PropertyHandlerContext;\nuse crate::declaration::{DeclarationBlock, DeclarationList};\nuse crate::error::{ParserError, PrinterError};\nuse crate::macros::{define_shorthand, enum_property};\nuse crate::printer::Printer;\nuse crate::traits::{Parse, PropertyHandler, Shorthand, ToCss};\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse cssparser::*;\n\nenum_property! {\n  /// An [overflow](https://www.w3.org/TR/css-overflow-3/#overflow-properties) keyword\n  /// as used in the `overflow-x`, `overflow-y`, and `overflow` properties.\n  pub enum OverflowKeyword {\n    /// Overflowing content is visible.\n    Visible,\n    /// Overflowing content is hidden. Programmatic scrolling is allowed.\n    Hidden,\n    /// Overflowing content is clipped. Programmatic scrolling is not allowed.\n    Clip,\n    /// The element is scrollable.\n    Scroll,\n    /// Overflowing content scrolls if needed.\n    Auto,\n  }\n}\n\ndefine_shorthand! {\n  /// A value for the [overflow](https://www.w3.org/TR/css-overflow-3/#overflow-properties) shorthand property.\n  pub struct Overflow {\n    /// The overflow mode for the x direction.\n    x: OverflowX(OverflowKeyword),\n    /// The overflow mode for the y direction.\n    y: OverflowY(OverflowKeyword),\n  }\n}\n\nimpl<'i> Parse<'i> for Overflow {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let x = OverflowKeyword::parse(input)?;\n    let y = input.try_parse(OverflowKeyword::parse).unwrap_or_else(|_| x.clone());\n    Ok(Overflow { x, y })\n  }\n}\n\nimpl ToCss for Overflow {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    self.x.to_css(dest)?;\n    if self.y != self.x {\n      dest.write_char(' ')?;\n      self.y.to_css(dest)?;\n    }\n    Ok(())\n  }\n}\n\nenum_property! {\n  /// A value for the [text-overflow](https://www.w3.org/TR/css-overflow-3/#text-overflow) property.\n  pub enum TextOverflow {\n    /// Overflowing text is clipped.\n    Clip,\n    /// Overflowing text is truncated with an ellipsis.\n    Ellipsis,\n  }\n}\n\n#[derive(Default)]\npub(crate) struct OverflowHandler {\n  x: Option<OverflowKeyword>,\n  y: Option<OverflowKeyword>,\n}\n\nimpl<'i> PropertyHandler<'i> for OverflowHandler {\n  fn handle_property(\n    &mut self,\n    property: &Property<'i>,\n    dest: &mut DeclarationList<'i>,\n    context: &mut PropertyHandlerContext<'i, '_>,\n  ) -> bool {\n    use Property::*;\n\n    match property {\n      OverflowX(val) => self.x = Some(*val),\n      OverflowY(val) => self.y = Some(*val),\n      Overflow(val) => {\n        self.x = Some(val.x);\n        self.y = Some(val.y);\n      }\n      Unparsed(val)\n        if matches!(\n          val.property_id,\n          PropertyId::OverflowX | PropertyId::OverflowY | PropertyId::Overflow\n        ) =>\n      {\n        self.finalize(dest, context);\n        dest.push(property.clone());\n      }\n      _ => return false,\n    }\n\n    true\n  }\n\n  fn finalize(&mut self, dest: &mut DeclarationList, context: &mut PropertyHandlerContext<'i, '_>) {\n    if self.x.is_none() && self.y.is_none() {\n      return;\n    }\n\n    let x = std::mem::take(&mut self.x);\n    let y = std::mem::take(&mut self.y);\n\n    match (x, y) {\n      // Only use shorthand syntax if the x and y values are the\n      // same or the two-value syntax is supported by all targets.\n      (Some(x), Some(y)) if x == y || context.targets.is_compatible(Feature::OverflowShorthand) => {\n        dest.push(Property::Overflow(Overflow { x, y }))\n      }\n      _ => {\n        if let Some(x) = x {\n          dest.push(Property::OverflowX(x))\n        }\n\n        if let Some(y) = y {\n          dest.push(Property::OverflowY(y))\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/properties/position.rs",
    "content": "//! CSS properties related to positioning.\n\nuse super::Property;\nuse crate::context::PropertyHandlerContext;\nuse crate::declaration::DeclarationList;\nuse crate::error::{ParserError, PrinterError};\nuse crate::prefixes::Feature;\nuse crate::printer::Printer;\nuse crate::traits::{Parse, PropertyHandler, ToCss};\nuse crate::values::number::CSSInteger;\nuse crate::vendor_prefix::VendorPrefix;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse cssparser::*;\n\n/// A value for the [position](https://www.w3.org/TR/css-position-3/#position-property) property.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum Position {\n  /// The box is laid in the document flow.\n  Static,\n  /// The box is laid out in the document flow and offset from the resulting position.\n  Relative,\n  /// The box is taken out of document flow and positioned in reference to its relative ancestor.\n  Absolute,\n  /// Similar to relative but adjusted according to the ancestor scrollable element.\n  Sticky(VendorPrefix),\n  /// The box is taken out of the document flow and positioned in reference to the page viewport.\n  Fixed,\n}\n\nimpl<'i> Parse<'i> for Position {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let location = input.current_source_location();\n    let ident = input.expect_ident()?;\n    match_ignore_ascii_case! { &*ident,\n      \"static\" => Ok(Position::Static),\n      \"relative\" => Ok(Position::Relative),\n      \"absolute\" => Ok(Position::Absolute),\n      \"fixed\" => Ok(Position::Fixed),\n      \"sticky\" => Ok(Position::Sticky(VendorPrefix::None)),\n      \"-webkit-sticky\" => Ok(Position::Sticky(VendorPrefix::WebKit)),\n      _ => Err(location.new_unexpected_token_error(\n        cssparser::Token::Ident(ident.clone())\n      ))\n    }\n  }\n}\n\nimpl ToCss for Position {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      Position::Static => dest.write_str(\"static\"),\n      Position::Relative => dest.write_str(\"relative\"),\n      Position::Absolute => dest.write_str(\"absolute\"),\n      Position::Fixed => dest.write_str(\"fixed\"),\n      Position::Sticky(prefix) => {\n        prefix.to_css(dest)?;\n        dest.write_str(\"sticky\")\n      }\n    }\n  }\n}\n\n/// A value for the [z-index](https://drafts.csswg.org/css2/#z-index) property.\n#[derive(Debug, Clone, PartialEq, Parse, ToCss)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum ZIndex {\n  /// The `auto` keyword.\n  Auto,\n  /// An integer value.\n  Integer(CSSInteger),\n}\n\n#[derive(Default)]\npub(crate) struct PositionHandler {\n  position: Option<Position>,\n}\n\nimpl<'i> PropertyHandler<'i> for PositionHandler {\n  fn handle_property(\n    &mut self,\n    property: &Property<'i>,\n    _: &mut DeclarationList<'i>,\n    _: &mut PropertyHandlerContext<'i, '_>,\n  ) -> bool {\n    if let Property::Position(position) = property {\n      if let (Some(Position::Sticky(cur)), Position::Sticky(new)) = (&mut self.position, position) {\n        *cur |= *new;\n      } else {\n        self.position = Some(position.clone());\n      }\n\n      return true;\n    }\n\n    false\n  }\n\n  fn finalize(&mut self, dest: &mut DeclarationList, context: &mut PropertyHandlerContext<'i, '_>) {\n    if self.position.is_none() {\n      return;\n    }\n\n    if let Some(position) = std::mem::take(&mut self.position) {\n      match position {\n        Position::Sticky(mut prefix) => {\n          prefix = context.targets.prefixes(prefix, Feature::Sticky);\n          if prefix.contains(VendorPrefix::WebKit) {\n            dest.push(Property::Position(Position::Sticky(VendorPrefix::WebKit)))\n          }\n\n          if prefix.contains(VendorPrefix::None) {\n            dest.push(Property::Position(Position::Sticky(VendorPrefix::None)))\n          }\n        }\n        _ => dest.push(Property::Position(position)),\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/properties/prefix_handler.rs",
    "content": "#![allow(non_snake_case)]\nuse super::{Property, PropertyId};\nuse crate::context::PropertyHandlerContext;\nuse crate::declaration::DeclarationList;\nuse crate::prefixes::Feature;\nuse crate::traits::{FallbackValues, IsCompatible, PropertyHandler};\nuse crate::vendor_prefix::VendorPrefix;\n\nmacro_rules! define_prefixes {\n  (\n    $( $name: ident, )+\n  ) => {\n    #[derive(Default)]\n    pub(crate) struct PrefixHandler {\n      $(\n        $name: Option<usize>,\n      )+\n    }\n\n    impl<'i> PropertyHandler<'i> for PrefixHandler {\n      fn handle_property(&mut self, property: &Property<'i>, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext) -> bool {\n        match property {\n          $(\n            Property::$name(val, prefix) => {\n              if let Some(i) = self.$name {\n                if let Some(decl) = dest.get_mut(i) {\n                  if let Property::$name(cur, prefixes) = decl {\n                    // If the value is the same, update the prefix.\n                    // If the prefix is the same, then update the value.\n                    if val == cur || prefixes.contains(*prefix) {\n                      *cur = val.clone();\n                      *prefixes |= *prefix;\n                      *prefixes = context.targets.prefixes(*prefixes, Feature::$name);\n                      return true\n                    }\n                  }\n                }\n              }\n\n              // Update the prefixes based on the targets.\n              let prefixes = context.targets.prefixes(*prefix, Feature::$name);\n\n              // Store the index of the property, so we can update it later.\n              self.$name = Some(dest.len());\n              dest.push(Property::$name(val.clone(), prefixes))\n            }\n          )+\n          _ => return false\n        }\n\n        true\n      }\n\n      fn finalize(&mut self, _: &mut DeclarationList, _: &mut PropertyHandlerContext) {}\n    }\n  };\n}\n\ndefine_prefixes! {\n  TransformOrigin,\n  TransformStyle,\n  BackfaceVisibility,\n  Perspective,\n  PerspectiveOrigin,\n  BoxSizing,\n  TabSize,\n  Hyphens,\n  TextAlignLast,\n  TextDecorationSkipInk,\n  TextOverflow,\n  UserSelect,\n  Appearance,\n  ClipPath,\n  BoxDecorationBreak,\n  TextSizeAdjust,\n  PrintColorAdjust,\n}\n\nmacro_rules! define_fallbacks {\n  (\n    $( $name: ident$(($p: ident))?, )+\n  ) => {\n    pastey::paste! {\n      #[derive(Default)]\n      pub(crate) struct FallbackHandler {\n        $(\n          [<$name:snake>]: Option<usize>\n        ),+\n      }\n    }\n\n    impl<'i> PropertyHandler<'i> for FallbackHandler {\n      fn handle_property(&mut self, property: &Property<'i>, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) -> bool {\n        match property {\n          $(\n            Property::$name(val $(, mut $p)?) => {\n              let mut val = val.clone();\n              $(\n                $p = context.targets.prefixes($p, Feature::$name);\n              )?\n              if pastey::paste! { self.[<$name:snake>] }.is_none() {\n                let fallbacks = val.get_fallbacks(context.targets);\n                #[allow(unused_variables)]\n                let has_fallbacks = !fallbacks.is_empty();\n                for fallback in fallbacks {\n                  dest.push(Property::$name(fallback $(, $p)?))\n                }\n\n                $(\n                  if has_fallbacks && $p.contains(VendorPrefix::None) {\n                    $p = VendorPrefix::None;\n                  }\n                )?\n              }\n\n              if pastey::paste! { self.[<$name:snake>] }.is_none() || matches!(context.targets.browsers, Some(targets) if !val.is_compatible(targets)) {\n                pastey::paste! { self.[<$name:snake>] = Some(dest.len()) };\n                dest.push(Property::$name(val $(, $p)?));\n              } else if let Some(index) = pastey::paste! { self.[<$name:snake>] } {\n                dest[index] = Property::$name(val $(, $p)?);\n              }\n            }\n          )+\n          Property::Unparsed(val) => {\n            let (mut unparsed, index) = match val.property_id {\n              $(\n                PropertyId::$name$(($p))? => {\n                  macro_rules! get_prefixed {\n                    ($vp: ident) => {\n                      if $vp.contains(VendorPrefix::None) {\n                        val.get_prefixed(context.targets, Feature::$name)\n                      } else {\n                        val.clone()\n                      }\n                    };\n                    () => {\n                      val.clone()\n                    };\n                  }\n\n                  let val = get_prefixed!($($p)?);\n                  (val, pastey::paste! { &mut self.[<$name:snake>] })\n                }\n              )+\n              _ => return false\n            };\n\n            // Unparsed properties are always \"valid\", meaning they always override previous declarations.\n            context.add_unparsed_fallbacks(&mut unparsed);\n            if let Some(index) = *index {\n              dest[index] = Property::Unparsed(unparsed);\n            } else {\n              *index = Some(dest.len());\n              dest.push(Property::Unparsed(unparsed));\n            }\n          }\n          _ => return false\n        }\n\n        true\n      }\n\n      fn finalize(&mut self, _: &mut DeclarationList, _: &mut PropertyHandlerContext) {\n        $(\n          pastey::paste! { self.[<$name:snake>] = None };\n        )+\n      }\n    }\n  };\n}\n\ndefine_fallbacks! {\n  Color,\n  TextShadow,\n  Filter(prefix),\n  BackdropFilter(prefix),\n  Fill,\n  Stroke,\n  CaretColor,\n  Caret,\n}\n"
  },
  {
    "path": "src/properties/size.rs",
    "content": "//! CSS properties related to box sizing.\n\nuse crate::compat::Feature;\nuse crate::context::PropertyHandlerContext;\nuse crate::declaration::DeclarationList;\nuse crate::error::{ParserError, PrinterError};\nuse crate::logical::PropertyCategory;\nuse crate::macros::{enum_property, property_bitflags};\nuse crate::printer::Printer;\nuse crate::properties::{Property, PropertyId};\nuse crate::traits::{IsCompatible, Parse, PropertyHandler, ToCss};\nuse crate::values::length::LengthPercentage;\nuse crate::values::ratio::Ratio;\nuse crate::vendor_prefix::VendorPrefix;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse cssparser::*;\n\n#[cfg(feature = \"serde\")]\nuse crate::serialization::*;\n\n// https://drafts.csswg.org/css-sizing-3/#specifying-sizes\n// https://www.w3.org/TR/css-sizing-4/#sizing-values\n\n/// A value for the [preferred size properties](https://drafts.csswg.org/css-sizing-3/#preferred-size-properties),\n/// i.e. `width` and `height.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum Size {\n  /// The `auto` keyword.\n  Auto,\n  /// An explicit length or percentage.\n  #[cfg_attr(feature = \"serde\", serde(with = \"ValueWrapper::<LengthPercentage>\"))]\n  LengthPercentage(LengthPercentage),\n  /// The `min-content` keyword.\n  #[cfg_attr(feature = \"serde\", serde(with = \"PrefixWrapper\"))]\n  MinContent(VendorPrefix),\n  /// The `max-content` keyword.\n  #[cfg_attr(feature = \"serde\", serde(with = \"PrefixWrapper\"))]\n  MaxContent(VendorPrefix),\n  /// The `fit-content` keyword.\n  #[cfg_attr(feature = \"serde\", serde(with = \"PrefixWrapper\"))]\n  FitContent(VendorPrefix),\n  /// The `fit-content()` function.\n  #[cfg_attr(feature = \"serde\", serde(with = \"ValueWrapper::<LengthPercentage>\"))]\n  FitContentFunction(LengthPercentage),\n  /// The `stretch` keyword, or the `-webkit-fill-available` or `-moz-available` prefixed keywords.\n  #[cfg_attr(feature = \"serde\", serde(with = \"PrefixWrapper\"))]\n  Stretch(VendorPrefix),\n  /// The `contain` keyword.\n  Contain,\n}\n\nimpl<'i> Parse<'i> for Size {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let res = input.try_parse(|input| {\n      let ident = input.expect_ident()?;\n      Ok(match_ignore_ascii_case! { &*ident,\n        \"auto\" => Size::Auto,\n        \"min-content\" => Size::MinContent(VendorPrefix::None),\n        \"-webkit-min-content\" => Size::MinContent(VendorPrefix::WebKit),\n        \"-moz-min-content\" => Size::MinContent(VendorPrefix::Moz),\n        \"max-content\" => Size::MaxContent(VendorPrefix::None),\n        \"-webkit-max-content\" => Size::MaxContent(VendorPrefix::WebKit),\n        \"-moz-max-content\" => Size::MaxContent(VendorPrefix::Moz),\n        \"stretch\" => Size::Stretch(VendorPrefix::None),\n        \"-webkit-fill-available\" => Size::Stretch(VendorPrefix::WebKit),\n        \"-moz-available\" => Size::Stretch(VendorPrefix::Moz),\n        \"fit-content\" => Size::FitContent(VendorPrefix::None),\n        \"-webkit-fit-content\" => Size::FitContent(VendorPrefix::WebKit),\n        \"-moz-fit-content\" => Size::FitContent(VendorPrefix::Moz),\n        \"contain\" => Size::Contain,\n        _ => return Err(input.new_custom_error(ParserError::InvalidValue))\n      })\n    });\n\n    if res.is_ok() {\n      return res;\n    }\n\n    if let Ok(res) = input.try_parse(parse_fit_content) {\n      return Ok(Size::FitContentFunction(res));\n    }\n\n    let lp = input.try_parse(LengthPercentage::parse)?;\n    Ok(Size::LengthPercentage(lp))\n  }\n}\n\nimpl ToCss for Size {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    use Size::*;\n    match self {\n      Auto => dest.write_str(\"auto\"),\n      Contain => dest.write_str(\"contain\"),\n      MinContent(vp) => {\n        vp.to_css(dest)?;\n        dest.write_str(\"min-content\")\n      }\n      MaxContent(vp) => {\n        vp.to_css(dest)?;\n        dest.write_str(\"max-content\")\n      }\n      FitContent(vp) => {\n        vp.to_css(dest)?;\n        dest.write_str(\"fit-content\")\n      }\n      Stretch(vp) => match *vp {\n        VendorPrefix::None => dest.write_str(\"stretch\"),\n        VendorPrefix::WebKit => dest.write_str(\"-webkit-fill-available\"),\n        VendorPrefix::Moz => dest.write_str(\"-moz-available\"),\n        _ => unreachable!(),\n      },\n      FitContentFunction(l) => {\n        dest.write_str(\"fit-content(\")?;\n        l.to_css(dest)?;\n        dest.write_str(\")\")\n      }\n      LengthPercentage(l) => l.to_css(dest),\n    }\n  }\n}\n\nimpl IsCompatible for Size {\n  fn is_compatible(&self, browsers: crate::targets::Browsers) -> bool {\n    use Size::*;\n    match self {\n      LengthPercentage(l) => l.is_compatible(browsers),\n      MinContent(..) => Feature::MinContentSize.is_compatible(browsers),\n      MaxContent(..) => Feature::MaxContentSize.is_compatible(browsers),\n      FitContent(..) => Feature::FitContentSize.is_compatible(browsers),\n      FitContentFunction(l) => {\n        Feature::FitContentFunctionSize.is_compatible(browsers) && l.is_compatible(browsers)\n      }\n      Stretch(vp) => match *vp {\n        VendorPrefix::None => Feature::StretchSize,\n        VendorPrefix::WebKit | VendorPrefix::Moz => Feature::WebkitFillAvailableSize,\n        _ => return false,\n      }\n      .is_compatible(browsers),\n      Contain => false, // ??? no data in mdn\n      Auto => true,\n    }\n  }\n}\n\n/// A value for the [minimum](https://drafts.csswg.org/css-sizing-3/#min-size-properties)\n/// and [maximum](https://drafts.csswg.org/css-sizing-3/#max-size-properties) size properties,\n/// e.g. `min-width` and `max-height`.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum MaxSize {\n  /// The `none` keyword.\n  None,\n  /// An explicit length or percentage.\n  #[cfg_attr(feature = \"serde\", serde(with = \"ValueWrapper::<LengthPercentage>\"))]\n  LengthPercentage(LengthPercentage),\n  /// The `min-content` keyword.\n  #[cfg_attr(feature = \"serde\", serde(with = \"PrefixWrapper\"))]\n  MinContent(VendorPrefix),\n  /// The `max-content` keyword.\n  #[cfg_attr(feature = \"serde\", serde(with = \"PrefixWrapper\"))]\n  MaxContent(VendorPrefix),\n  /// The `fit-content` keyword.\n  #[cfg_attr(feature = \"serde\", serde(with = \"PrefixWrapper\"))]\n  FitContent(VendorPrefix),\n  /// The `fit-content()` function.\n  #[cfg_attr(feature = \"serde\", serde(with = \"ValueWrapper::<LengthPercentage>\"))]\n  FitContentFunction(LengthPercentage),\n  /// The `stretch` keyword, or the `-webkit-fill-available` or `-moz-available` prefixed keywords.\n  #[cfg_attr(feature = \"serde\", serde(with = \"PrefixWrapper\"))]\n  Stretch(VendorPrefix),\n  /// The `contain` keyword.\n  Contain,\n}\n\nimpl<'i> Parse<'i> for MaxSize {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let res = input.try_parse(|input| {\n      let ident = input.expect_ident()?;\n      Ok(match_ignore_ascii_case! { &*ident,\n        \"none\" => MaxSize::None,\n        \"min-content\" => MaxSize::MinContent(VendorPrefix::None),\n        \"-webkit-min-content\" => MaxSize::MinContent(VendorPrefix::WebKit),\n        \"-moz-min-content\" => MaxSize::MinContent(VendorPrefix::Moz),\n        \"max-content\" => MaxSize::MaxContent(VendorPrefix::None),\n        \"-webkit-max-content\" => MaxSize::MaxContent(VendorPrefix::WebKit),\n        \"-moz-max-content\" => MaxSize::MaxContent(VendorPrefix::Moz),\n        \"stretch\" => MaxSize::Stretch(VendorPrefix::None),\n        \"-webkit-fill-available\" => MaxSize::Stretch(VendorPrefix::WebKit),\n        \"-moz-available\" => MaxSize::Stretch(VendorPrefix::Moz),\n        \"fit-content\" => MaxSize::FitContent(VendorPrefix::None),\n        \"-webkit-fit-content\" => MaxSize::FitContent(VendorPrefix::WebKit),\n        \"-moz-fit-content\" => MaxSize::FitContent(VendorPrefix::Moz),\n        \"contain\" => MaxSize::Contain,\n        _ => return Err(input.new_custom_error(ParserError::InvalidValue))\n      })\n    });\n\n    if res.is_ok() {\n      return res;\n    }\n\n    if let Ok(res) = input.try_parse(parse_fit_content) {\n      return Ok(MaxSize::FitContentFunction(res));\n    }\n\n    let lp = input.try_parse(LengthPercentage::parse)?;\n    Ok(MaxSize::LengthPercentage(lp))\n  }\n}\n\nimpl ToCss for MaxSize {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    use MaxSize::*;\n    match self {\n      None => dest.write_str(\"none\"),\n      Contain => dest.write_str(\"contain\"),\n      MinContent(vp) => {\n        vp.to_css(dest)?;\n        dest.write_str(\"min-content\")\n      }\n      MaxContent(vp) => {\n        vp.to_css(dest)?;\n        dest.write_str(\"max-content\")\n      }\n      FitContent(vp) => {\n        vp.to_css(dest)?;\n        dest.write_str(\"fit-content\")\n      }\n      Stretch(vp) => match *vp {\n        VendorPrefix::None => dest.write_str(\"stretch\"),\n        VendorPrefix::WebKit => dest.write_str(\"-webkit-fill-available\"),\n        VendorPrefix::Moz => dest.write_str(\"-moz-available\"),\n        _ => unreachable!(),\n      },\n      FitContentFunction(l) => {\n        dest.write_str(\"fit-content(\")?;\n        l.to_css(dest)?;\n        dest.write_str(\")\")\n      }\n      LengthPercentage(l) => l.to_css(dest),\n    }\n  }\n}\n\nimpl IsCompatible for MaxSize {\n  fn is_compatible(&self, browsers: crate::targets::Browsers) -> bool {\n    use MaxSize::*;\n    match self {\n      LengthPercentage(l) => l.is_compatible(browsers),\n      MinContent(..) => Feature::MinContentSize.is_compatible(browsers),\n      MaxContent(..) => Feature::MaxContentSize.is_compatible(browsers),\n      FitContent(..) => Feature::FitContentSize.is_compatible(browsers),\n      FitContentFunction(l) => {\n        Feature::FitContentFunctionSize.is_compatible(browsers) && l.is_compatible(browsers)\n      }\n      Stretch(vp) => match *vp {\n        VendorPrefix::None => Feature::StretchSize,\n        VendorPrefix::WebKit | VendorPrefix::Moz => Feature::WebkitFillAvailableSize,\n        _ => return false,\n      }\n      .is_compatible(browsers),\n      Contain => false, // ??? no data in mdn\n      None => true,\n    }\n  }\n}\n\nfn parse_fit_content<'i, 't>(\n  input: &mut Parser<'i, 't>,\n) -> Result<LengthPercentage, ParseError<'i, ParserError<'i>>> {\n  input.expect_function_matching(\"fit-content\")?;\n  input.parse_nested_block(|input| LengthPercentage::parse(input))\n}\n\nenum_property! {\n  /// A value for the [box-sizing](https://drafts.csswg.org/css-sizing-3/#box-sizing) property.\n  pub enum BoxSizing {\n    /// Exclude the margin/border/padding from the width and height.\n    ContentBox,\n    /// Include the padding and border (but not the margin) in the width and height.\n    BorderBox,\n  }\n}\n\n/// A value for the [aspect-ratio](https://drafts.csswg.org/css-sizing-4/#aspect-ratio) property.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub struct AspectRatio {\n  /// The `auto` keyword.\n  pub auto: bool,\n  /// A preferred aspect ratio for the box, specified as width / height.\n  pub ratio: Option<Ratio>,\n}\n\nimpl<'i> Parse<'i> for AspectRatio {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let location = input.current_source_location();\n    let mut auto = input.try_parse(|i| i.expect_ident_matching(\"auto\"));\n    let ratio = input.try_parse(Ratio::parse);\n    if auto.is_err() {\n      auto = input.try_parse(|i| i.expect_ident_matching(\"auto\"));\n    }\n    if auto.is_err() && ratio.is_err() {\n      return Err(location.new_custom_error(ParserError::InvalidValue));\n    }\n\n    Ok(AspectRatio {\n      auto: auto.is_ok(),\n      ratio: ratio.ok(),\n    })\n  }\n}\n\nimpl ToCss for AspectRatio {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    if self.auto {\n      dest.write_str(\"auto\")?;\n    }\n\n    if let Some(ratio) = &self.ratio {\n      if self.auto {\n        dest.write_char(' ')?;\n      }\n      ratio.to_css(dest)?;\n    }\n\n    Ok(())\n  }\n}\n\nproperty_bitflags! {\n  #[derive(Default)]\n  struct SizeProperty: u16 {\n    const Width = 1 << 0;\n    const Height = 1 << 1;\n    const MinWidth = 1 << 2;\n    const MinHeight = 1 << 3;\n    const MaxWidth = 1 << 4;\n    const MaxHeight = 1 << 5;\n    const BlockSize = 1 << 6;\n    const InlineSize = 1 << 7;\n    const MinBlockSize  = 1 << 8;\n    const MinInlineSize = 1 << 9;\n    const MaxBlockSize = 1 << 10;\n    const MaxInlineSize = 1 << 11;\n  }\n}\n\n#[derive(Default)]\npub(crate) struct SizeHandler {\n  width: Option<Size>,\n  height: Option<Size>,\n  min_width: Option<Size>,\n  min_height: Option<Size>,\n  max_width: Option<MaxSize>,\n  max_height: Option<MaxSize>,\n  block_size: Option<Size>,\n  inline_size: Option<Size>,\n  min_block_size: Option<Size>,\n  min_inline_size: Option<Size>,\n  max_block_size: Option<MaxSize>,\n  max_inline_size: Option<MaxSize>,\n  has_any: bool,\n  flushed_properties: SizeProperty,\n  category: PropertyCategory,\n}\n\nimpl<'i> PropertyHandler<'i> for SizeHandler {\n  fn handle_property(\n    &mut self,\n    property: &Property<'i>,\n    dest: &mut DeclarationList<'i>,\n    context: &mut PropertyHandlerContext<'i, '_>,\n  ) -> bool {\n    let logical_supported = !context.should_compile_logical(Feature::LogicalSize);\n\n    macro_rules! property {\n      ($prop: ident, $val: ident, $category: ident) => {{\n        // If the category changes betweet logical and physical,\n        // or if the value contains syntax that isn't supported across all targets,\n        // preserve the previous value as a fallback.\n        if PropertyCategory::$category != self.category || (self.$prop.is_some() && matches!(context.targets.browsers, Some(targets) if !$val.is_compatible(targets))) {\n          self.flush(dest, context);\n        }\n\n        self.$prop = Some($val.clone());\n        self.category = PropertyCategory::$category;\n        self.has_any = true;\n      }};\n    }\n\n    match property {\n      Property::Width(v) => property!(width, v, Physical),\n      Property::Height(v) => property!(height, v, Physical),\n      Property::MinWidth(v) => property!(min_width, v, Physical),\n      Property::MinHeight(v) => property!(min_height, v, Physical),\n      Property::MaxWidth(v) => property!(max_width, v, Physical),\n      Property::MaxHeight(v) => property!(max_height, v, Physical),\n      Property::BlockSize(size) => property!(block_size, size, Logical),\n      Property::MinBlockSize(size) => property!(min_block_size, size, Logical),\n      Property::MaxBlockSize(size) => property!(max_block_size, size, Logical),\n      Property::InlineSize(size) => property!(inline_size, size, Logical),\n      Property::MinInlineSize(size) => property!(min_inline_size, size, Logical),\n      Property::MaxInlineSize(size) => property!(max_inline_size, size, Logical),\n      Property::Unparsed(unparsed) => {\n        self.flush(dest, context);\n        macro_rules! logical_unparsed {\n          ($physical: ident) => {\n            if logical_supported {\n              self\n                .flushed_properties\n                .insert(SizeProperty::try_from(&unparsed.property_id).unwrap());\n              dest.push(property.clone());\n            } else {\n              dest.push(Property::Unparsed(\n                unparsed.with_property_id(PropertyId::$physical),\n              ));\n              self.flushed_properties.insert(SizeProperty::$physical);\n            }\n          };\n        }\n\n        match &unparsed.property_id {\n          PropertyId::Width\n          | PropertyId::Height\n          | PropertyId::MinWidth\n          | PropertyId::MaxWidth\n          | PropertyId::MinHeight\n          | PropertyId::MaxHeight => {\n            self\n              .flushed_properties\n              .insert(SizeProperty::try_from(&unparsed.property_id).unwrap());\n            dest.push(property.clone());\n          }\n          PropertyId::BlockSize => logical_unparsed!(Height),\n          PropertyId::MinBlockSize => logical_unparsed!(MinHeight),\n          PropertyId::MaxBlockSize => logical_unparsed!(MaxHeight),\n          PropertyId::InlineSize => logical_unparsed!(Width),\n          PropertyId::MinInlineSize => logical_unparsed!(MinWidth),\n          PropertyId::MaxInlineSize => logical_unparsed!(MaxWidth),\n          _ => return false,\n        }\n      }\n      _ => return false,\n    }\n\n    true\n  }\n\n  fn finalize(&mut self, dest: &mut DeclarationList, context: &mut PropertyHandlerContext<'i, '_>) {\n    self.flush(dest, context);\n    self.flushed_properties = SizeProperty::empty();\n  }\n}\n\nimpl SizeHandler {\n  fn flush<'i>(&mut self, dest: &mut DeclarationList, context: &mut PropertyHandlerContext<'i, '_>) {\n    if !self.has_any {\n      return;\n    }\n\n    self.has_any = false;\n    let logical_supported = !context.should_compile_logical(Feature::LogicalSize);\n\n    macro_rules! prefix {\n      ($prop: ident, $size: ident, $feature: ident) => {\n        if !self.flushed_properties.contains(SizeProperty::$prop) {\n          let prefixes =\n            context.targets.prefixes(VendorPrefix::None, crate::prefixes::Feature::$feature) - VendorPrefix::None;\n          for prefix in prefixes {\n            dest.push(Property::$prop($size::$feature(prefix)));\n          }\n        }\n      };\n    }\n\n    macro_rules! property {\n      ($prop: ident, $val: ident, $size: ident) => {{\n        if let Some(val) = std::mem::take(&mut self.$val) {\n          match val {\n            $size::Stretch(VendorPrefix::None) => prefix!($prop, $size, Stretch),\n            $size::MinContent(VendorPrefix::None) => prefix!($prop, $size, MinContent),\n            $size::MaxContent(VendorPrefix::None) => prefix!($prop, $size, MaxContent),\n            $size::FitContent(VendorPrefix::None) => prefix!($prop, $size, FitContent),\n            _ => {}\n          }\n          dest.push(Property::$prop(val.clone()));\n          self.flushed_properties.insert(SizeProperty::$prop);\n        }\n      }};\n    }\n\n    macro_rules! logical {\n      ($prop: ident, $val: ident, $physical: ident, $size: ident) => {\n        if logical_supported {\n          property!($prop, $val, $size);\n        } else {\n          property!($physical, $val, $size);\n        }\n      };\n    }\n\n    property!(Width, width, Size);\n    property!(MinWidth, min_width, Size);\n    property!(MaxWidth, max_width, MaxSize);\n    property!(Height, height, Size);\n    property!(MinHeight, min_height, Size);\n    property!(MaxHeight, max_height, MaxSize);\n    logical!(BlockSize, block_size, Height, Size);\n    logical!(MinBlockSize, min_block_size, MinHeight, Size);\n    logical!(MaxBlockSize, max_block_size, MaxHeight, MaxSize);\n    logical!(InlineSize, inline_size, Width, Size);\n    logical!(MinInlineSize, min_inline_size, MinWidth, Size);\n    logical!(MaxInlineSize, max_inline_size, MaxWidth, MaxSize);\n  }\n}\n"
  },
  {
    "path": "src/properties/svg.rs",
    "content": "//! CSS properties used in SVG.\n\nuse crate::error::{ParserError, PrinterError};\nuse crate::macros::enum_property;\nuse crate::printer::Printer;\nuse crate::targets::{Browsers, Targets};\nuse crate::traits::{FallbackValues, IsCompatible, Parse, ToCss};\nuse crate::values::length::LengthPercentage;\nuse crate::values::{color::CssColor, url::Url};\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse cssparser::*;\n\n/// An SVG [`<paint>`](https://www.w3.org/TR/SVG2/painting.html#SpecifyingPaint) value\n/// used in the `fill` and `stroke` properties.\n#[derive(Debug, Clone, PartialEq, Parse, ToCss)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub enum SVGPaint<'i> {\n  /// A URL reference to a paint server element, e.g. `linearGradient`, `radialGradient`, and `pattern`.\n  Url {\n    #[cfg_attr(feature = \"serde\", serde(borrow))]\n    /// The url of the paint server.\n    url: Url<'i>,\n    /// A fallback to be used used in case the paint server cannot be resolved.\n    fallback: Option<SVGPaintFallback>,\n  },\n  /// A solid color paint.\n  #[cfg_attr(feature = \"serde\", serde(with = \"crate::serialization::ValueWrapper::<CssColor>\"))]\n  Color(CssColor),\n  /// Use the paint value of fill from a context element.\n  ContextFill,\n  /// Use the paint value of stroke from a context element.\n  ContextStroke,\n  /// No paint.\n  None,\n}\n\n/// A fallback for an SVG paint in case a paint server `url()` cannot be resolved.\n///\n/// See [SVGPaint](SVGPaint).\n#[derive(Debug, Clone, PartialEq, Parse, ToCss)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum SVGPaintFallback {\n  /// No fallback.\n  None,\n  /// A solid color.\n  Color(CssColor),\n}\n\nimpl<'i> FallbackValues for SVGPaint<'i> {\n  fn get_fallbacks(&mut self, targets: Targets) -> Vec<Self> {\n    match self {\n      SVGPaint::Color(color) => color\n        .get_fallbacks(targets)\n        .into_iter()\n        .map(|color| SVGPaint::Color(color))\n        .collect(),\n      SVGPaint::Url {\n        url,\n        fallback: Some(SVGPaintFallback::Color(color)),\n      } => color\n        .get_fallbacks(targets)\n        .into_iter()\n        .map(|color| SVGPaint::Url {\n          url: url.clone(),\n          fallback: Some(SVGPaintFallback::Color(color)),\n        })\n        .collect(),\n      _ => Vec::new(),\n    }\n  }\n}\n\nimpl IsCompatible for SVGPaint<'_> {\n  fn is_compatible(&self, browsers: Browsers) -> bool {\n    match self {\n      SVGPaint::Color(c)\n      | SVGPaint::Url {\n        fallback: Some(SVGPaintFallback::Color(c)),\n        ..\n      } => c.is_compatible(browsers),\n      SVGPaint::Url { .. } | SVGPaint::None | SVGPaint::ContextFill | SVGPaint::ContextStroke => true,\n    }\n  }\n}\n\nenum_property! {\n  /// A value for the [stroke-linecap](https://www.w3.org/TR/SVG2/painting.html#LineCaps) property.\n  pub enum StrokeLinecap {\n    /// The stroke does not extend beyond its endpoints.\n    Butt,\n    /// The ends of the stroke are rounded.\n    Round,\n    /// The ends of the stroke are squared.\n    Square,\n  }\n}\n\nenum_property! {\n  /// A value for the [stroke-linejoin](https://www.w3.org/TR/SVG2/painting.html#LineJoin) property.\n  pub enum StrokeLinejoin {\n    /// A sharp corner is to be used to join path segments.\n    Miter,\n    /// Same as `miter` but clipped beyond `stroke-miterlimit`.\n    MiterClip,\n    /// A round corner is to be used to join path segments.\n    Round,\n    /// A bevelled corner is to be used to join path segments.\n    Bevel,\n    /// An arcs corner is to be used to join path segments.\n    Arcs,\n  }\n}\n\n/// A value for the [stroke-dasharray](https://www.w3.org/TR/SVG2/painting.html#StrokeDashing) property.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum StrokeDasharray {\n  /// No dashing is used.\n  None,\n  /// Specifies a dashing pattern to use.\n  Values(Vec<LengthPercentage>),\n}\n\nimpl<'i> Parse<'i> for StrokeDasharray {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    if input.try_parse(|input| input.expect_ident_matching(\"none\")).is_ok() {\n      return Ok(StrokeDasharray::None);\n    }\n\n    input.skip_whitespace();\n    let mut results = vec![LengthPercentage::parse(input)?];\n    loop {\n      input.skip_whitespace();\n      let comma_location = input.current_source_location();\n      let comma = input.try_parse(|i| i.expect_comma()).is_ok();\n      if let Ok(item) = input.try_parse(LengthPercentage::parse) {\n        results.push(item);\n      } else if comma {\n        return Err(comma_location.new_unexpected_token_error(Token::Comma));\n      } else {\n        break;\n      }\n    }\n\n    Ok(StrokeDasharray::Values(results))\n  }\n}\n\nimpl ToCss for StrokeDasharray {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      StrokeDasharray::None => dest.write_str(\"none\"),\n      StrokeDasharray::Values(values) => {\n        let mut first = true;\n        for value in values {\n          if first {\n            first = false;\n          } else {\n            dest.write_char(' ')?;\n          }\n          value.to_css_unitless(dest)?;\n        }\n        Ok(())\n      }\n    }\n  }\n}\n\n/// A value for the [marker](https://www.w3.org/TR/SVG2/painting.html#VertexMarkerProperties) properties.\n#[derive(Debug, Clone, PartialEq, Parse, ToCss)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub enum Marker<'i> {\n  /// No marker.\n  None,\n  /// A url reference to a `<marker>` element.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  Url(Url<'i>),\n}\n\n/// A value for the [color-interpolation](https://www.w3.org/TR/SVG2/painting.html#ColorInterpolation) property.\n#[derive(Debug, Clone, Copy, PartialEq, Parse, ToCss)]\n#[css(case = lower)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"lowercase\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum ColorInterpolation {\n  /// The UA can choose between sRGB or linearRGB.\n  Auto,\n  /// Color interpolation occurs in the sRGB color space.\n  SRGB,\n  /// Color interpolation occurs in the linearized RGB color space\n  LinearRGB,\n}\n\n/// A value for the [color-rendering](https://www.w3.org/TR/SVG2/painting.html#ColorRendering) property.\n#[derive(Debug, Clone, Copy, PartialEq, Parse, ToCss)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"lowercase\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum ColorRendering {\n  /// The UA can choose a tradeoff between speed and quality.\n  Auto,\n  /// The UA shall optimize speed over quality.\n  OptimizeSpeed,\n  /// The UA shall optimize quality over speed.\n  OptimizeQuality,\n}\n\n/// A value for the [shape-rendering](https://www.w3.org/TR/SVG2/painting.html#ShapeRendering) property.\n#[derive(Debug, Clone, Copy, PartialEq, Parse, ToCss)]\n#[css(case = lower)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"lowercase\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum ShapeRendering {\n  /// The UA can choose an appropriate tradeoff.\n  Auto,\n  /// The UA shall optimize speed.\n  OptimizeSpeed,\n  /// The UA shall optimize crisp edges.\n  CrispEdges,\n  /// The UA shall optimize geometric precision.\n  GeometricPrecision,\n}\n\n/// A value for the [text-rendering](https://www.w3.org/TR/SVG2/painting.html#TextRendering) property.\n#[derive(Debug, Clone, Copy, PartialEq, Parse, ToCss)]\n#[css(case = lower)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"lowercase\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum TextRendering {\n  /// The UA can choose an appropriate tradeoff.\n  Auto,\n  /// The UA shall optimize speed.\n  OptimizeSpeed,\n  /// The UA shall optimize legibility.\n  OptimizeLegibility,\n  /// The UA shall optimize geometric precision.\n  GeometricPrecision,\n}\n\n/// A value for the [image-rendering](https://www.w3.org/TR/SVG2/painting.html#ImageRendering) property.\n#[derive(Debug, Clone, Copy, PartialEq, Parse, ToCss)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"lowercase\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum ImageRendering {\n  /// The UA can choose a tradeoff between speed and quality.\n  Auto,\n  /// The UA shall optimize speed over quality.\n  OptimizeSpeed,\n  /// The UA shall optimize quality over speed.\n  OptimizeQuality,\n}\n"
  },
  {
    "path": "src/properties/text.rs",
    "content": "//! CSS properties related to text.\n\n#![allow(non_upper_case_globals)]\n\nuse super::{Property, PropertyId};\nuse crate::compat;\nuse crate::context::PropertyHandlerContext;\nuse crate::declaration::{DeclarationBlock, DeclarationList};\nuse crate::error::{ParserError, PrinterError};\nuse crate::macros::{define_shorthand, enum_property};\nuse crate::prefixes::Feature;\nuse crate::printer::Printer;\nuse crate::targets::{should_compile, Browsers, Targets};\nuse crate::traits::{FallbackValues, IsCompatible, Parse, PropertyHandler, Shorthand, ToCss, Zero};\nuse crate::values::calc::{Calc, MathFunction};\nuse crate::values::color::{ColorFallbackKind, CssColor};\nuse crate::values::length::{Length, LengthPercentage, LengthValue};\nuse crate::values::percentage::Percentage;\nuse crate::values::string::CSSString;\nuse crate::vendor_prefix::VendorPrefix;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse bitflags::bitflags;\nuse cssparser::*;\nuse smallvec::SmallVec;\n\nenum_property! {\n  /// Defines how text case should be transformed in the\n  /// [text-transform](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#text-transform-property) property.\n  pub enum TextTransformCase {\n    /// Text should not be transformed.\n    None,\n    /// Text should be uppercased.\n    Uppercase,\n    /// Text should be lowercased.\n    Lowercase,\n    /// Each word should be capitalized.\n    Capitalize,\n  }\n}\n\nimpl Default for TextTransformCase {\n  fn default() -> TextTransformCase {\n    TextTransformCase::None\n  }\n}\n\nbitflags! {\n  /// Defines how ideographic characters should be transformed in the\n  /// [text-transform](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#text-transform-property) property.\n  ///\n  /// All combinations of flags is supported.\n  #[cfg_attr(feature = \"visitor\", derive(Visit))]\n  #[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize), serde(from = \"SerializedTextTransformOther\", into = \"SerializedTextTransformOther\"))]\n  #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]\n  pub struct TextTransformOther: u8 {\n    /// Puts all typographic character units in full-width form.\n    const FullWidth    = 0b00000001;\n    /// Converts all small Kana characters to the equivalent full-size Kana.\n    const FullSizeKana = 0b00000010;\n  }\n}\n\nimpl<'i> Parse<'i> for TextTransformOther {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let location = input.current_source_location();\n    let ident = input.expect_ident()?;\n    match_ignore_ascii_case! { &ident,\n      \"full-width\" => Ok(TextTransformOther::FullWidth),\n      \"full-size-kana\" => Ok(TextTransformOther::FullSizeKana),\n      _ => Err(location.new_unexpected_token_error(\n        cssparser::Token::Ident(ident.clone())\n      ))\n    }\n  }\n}\n\nimpl ToCss for TextTransformOther {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    let mut needs_space = false;\n    if self.contains(TextTransformOther::FullWidth) {\n      dest.write_str(\"full-width\")?;\n      needs_space = true;\n    }\n\n    if self.contains(TextTransformOther::FullSizeKana) {\n      if needs_space {\n        dest.write_char(' ')?;\n      }\n      dest.write_str(\"full-size-kana\")?;\n    }\n\n    Ok(())\n  }\n}\n\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"camelCase\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\nstruct SerializedTextTransformOther {\n  /// Puts all typographic character units in full-width form.\n  full_width: bool,\n  /// Converts all small Kana characters to the equivalent full-size Kana.\n  full_size_kana: bool,\n}\n\nimpl From<TextTransformOther> for SerializedTextTransformOther {\n  fn from(t: TextTransformOther) -> Self {\n    Self {\n      full_width: t.contains(TextTransformOther::FullWidth),\n      full_size_kana: t.contains(TextTransformOther::FullSizeKana),\n    }\n  }\n}\n\nimpl From<SerializedTextTransformOther> for TextTransformOther {\n  fn from(t: SerializedTextTransformOther) -> Self {\n    let mut res = TextTransformOther::empty();\n    if t.full_width {\n      res |= TextTransformOther::FullWidth;\n    }\n    if t.full_size_kana {\n      res |= TextTransformOther::FullSizeKana;\n    }\n    res\n  }\n}\n\n#[cfg(feature = \"jsonschema\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"jsonschema\")))]\nimpl<'a> schemars::JsonSchema for TextTransformOther {\n  fn is_referenceable() -> bool {\n    true\n  }\n\n  fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {\n    SerializedTextTransformOther::json_schema(gen)\n  }\n\n  fn schema_name() -> String {\n    \"TextTransformOther\".into()\n  }\n}\n\n/// A value for the [text-transform](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#text-transform-property) property.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub struct TextTransform {\n  /// How case should be transformed.\n  pub case: TextTransformCase,\n  /// How ideographic characters should be transformed.\n  #[cfg_attr(feature = \"serde\", serde(flatten))]\n  pub other: TextTransformOther,\n}\n\nimpl<'i> Parse<'i> for TextTransform {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let mut case = None;\n    let mut other = TextTransformOther::empty();\n\n    loop {\n      if case.is_none() {\n        if let Ok(c) = input.try_parse(TextTransformCase::parse) {\n          case = Some(c);\n          if c == TextTransformCase::None {\n            other = TextTransformOther::empty();\n            break;\n          }\n          continue;\n        }\n      }\n\n      if let Ok(o) = input.try_parse(TextTransformOther::parse) {\n        other |= o;\n        continue;\n      }\n\n      break;\n    }\n\n    Ok(TextTransform {\n      case: case.unwrap_or_default(),\n      other,\n    })\n  }\n}\n\nimpl ToCss for TextTransform {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    let mut needs_space = false;\n    if self.case != TextTransformCase::None || self.other.is_empty() {\n      self.case.to_css(dest)?;\n      needs_space = true;\n    }\n\n    if !self.other.is_empty() {\n      if needs_space {\n        dest.write_char(' ')?;\n      }\n      self.other.to_css(dest)?;\n    }\n    Ok(())\n  }\n}\n\nenum_property! {\n  /// A value for the [white-space](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#white-space-property) property.\n  pub enum WhiteSpace {\n    /// Sequences of white space are collapsed into a single character.\n    \"normal\": Normal,\n    /// White space is not collapsed.\n    \"pre\": Pre,\n    /// White space is collapsed, but no line wrapping occurs.\n    \"nowrap\": NoWrap,\n    /// White space is preserved, but line wrapping occurs.\n    \"pre-wrap\": PreWrap,\n    /// Like pre-wrap, but with different line breaking rules.\n    \"break-spaces\": BreakSpaces,\n    /// White space is collapsed, but with different line breaking rules.\n    \"pre-line\": PreLine,\n  }\n}\n\nenum_property! {\n  /// A value for the [word-break](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#word-break-property) property.\n  pub enum WordBreak {\n    /// Words break according to their customary rules.\n    Normal,\n    /// Breaking is forbidden within “words”.\n    KeepAll,\n    /// Breaking is allowed within “words”.\n    BreakAll,\n    /// Breaking is allowed if there is no otherwise acceptable break points in a line.\n    BreakWord,\n  }\n}\n\nenum_property! {\n  /// A value for the [line-break](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#line-break-property) property.\n  pub enum LineBreak {\n    /// The UA determines the set of line-breaking restrictions to use.\n    Auto,\n    /// Breaks text using the least restrictive set of line-breaking rules.\n    Loose,\n    /// Breaks text using the most common set of line-breaking rules.\n    Normal,\n    /// Breaks text using the most stringent set of line-breaking rules.\n    Strict,\n    /// There is a soft wrap opportunity around every typographic character unit.\n    Anywhere,\n  }\n}\nenum_property! {\n  /// A value for the [hyphens](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#hyphenation) property.\n  pub enum Hyphens {\n    /// Words are not hyphenated.\n    None,\n    /// Words are only hyphenated where there are characters inside the word that explicitly suggest hyphenation opportunities.\n    Manual,\n    /// Words may be broken at hyphenation opportunities determined automatically by the UA.\n    Auto,\n  }\n}\n\nenum_property! {\n  /// A value for the [overflow-wrap](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#overflow-wrap-property) property.\n  pub enum OverflowWrap {\n    /// Lines may break only at allowed break points.\n    Normal,\n    /// Breaking is allowed if there is no otherwise acceptable break points in a line.\n    Anywhere,\n    /// As for anywhere except that soft wrap opportunities introduced by break-word are\n    /// not considered when calculating min-content intrinsic sizes.\n    BreakWord,\n  }\n}\n\nenum_property! {\n  /// A value for the [text-align](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#text-align-property) property.\n  pub enum TextAlign {\n    /// Inline-level content is aligned to the start edge of the line box.\n    Start,\n    /// Inline-level content is aligned to the end edge of the line box.\n    End,\n    /// Inline-level content is aligned to the line-left edge of the line box.\n    Left,\n    /// Inline-level content is aligned to the line-right edge of the line box.\n    Right,\n    /// Inline-level content is centered within the line box.\n    Center,\n    /// Text is justified according to the method specified by the text-justify property.\n    Justify,\n    /// Matches the parent element.\n    MatchParent,\n    /// Same as justify, but also justifies the last line.\n    JustifyAll,\n  }\n}\n\nenum_property! {\n  /// A value for the [text-align-last](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#text-align-last-property) property.\n  pub enum TextAlignLast {\n    /// Content on the affected line is aligned per `text-align-all` unless set to `justify`, in which case it is start-aligned.\n    Auto,\n    /// Inline-level content is aligned to the start edge of the line box.\n    Start,\n    /// Inline-level content is aligned to the end edge of the line box.\n    End,\n    /// Inline-level content is aligned to the line-left edge of the line box.\n    Left,\n    /// Inline-level content is aligned to the line-right edge of the line box.\n    Right,\n    /// Inline-level content is centered within the line box.\n    Center,\n    /// Text is justified according to the method specified by the text-justify property.\n    Justify,\n    /// Matches the parent element.\n    MatchParent,\n  }\n}\n\nenum_property! {\n  /// A value for the [text-justify](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#text-justify-property) property.\n  pub enum TextJustify {\n    /// The UA determines the justification algorithm to follow.\n    Auto,\n    /// Justification is disabled.\n    None,\n    /// Justification adjusts spacing at word separators only.\n    InterWord,\n    /// Justification adjusts spacing between each character.\n    InterCharacter,\n  }\n}\n\n/// A value for the [word-spacing](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#word-spacing-property)\n/// and [letter-spacing](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#letter-spacing-property) properties.\n#[derive(Debug, Clone, PartialEq, Parse, ToCss)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum Spacing {\n  /// No additional spacing is applied.\n  Normal,\n  /// Additional spacing between each word or letter.\n  Length(Length),\n}\n\n/// A value for the [text-indent](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#text-indent-property) property.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"camelCase\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub struct TextIndent {\n  /// The amount to indent.\n  pub value: LengthPercentage,\n  /// Inverts which lines are affected.\n  pub hanging: bool,\n  /// Affects the first line after each hard break.\n  pub each_line: bool,\n}\n\nimpl<'i> Parse<'i> for TextIndent {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let mut value = None;\n    let mut hanging = false;\n    let mut each_line = false;\n\n    loop {\n      if value.is_none() {\n        if let Ok(val) = input.try_parse(LengthPercentage::parse) {\n          value = Some(val);\n          continue;\n        }\n      }\n\n      if !hanging {\n        if input.try_parse(|input| input.expect_ident_matching(\"hanging\")).is_ok() {\n          hanging = true;\n          continue;\n        }\n      }\n\n      if !each_line {\n        if input.try_parse(|input| input.expect_ident_matching(\"each-line\")).is_ok() {\n          each_line = true;\n          continue;\n        }\n      }\n\n      break;\n    }\n\n    if let Some(value) = value {\n      Ok(TextIndent {\n        value,\n        hanging,\n        each_line,\n      })\n    } else {\n      Err(input.new_custom_error(ParserError::InvalidDeclaration))\n    }\n  }\n}\n\nimpl ToCss for TextIndent {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    self.value.to_css(dest)?;\n    if self.hanging {\n      dest.write_str(\" hanging\")?;\n    }\n    if self.each_line {\n      dest.write_str(\" each-line\")?;\n    }\n    Ok(())\n  }\n}\n\n/// A value for the [text-size-adjust](https://w3c.github.io/csswg-drafts/css-size-adjust/#adjustment-control) property.\n#[derive(Debug, Clone, PartialEq, Parse, ToCss)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum TextSizeAdjust {\n  /// Use the default size adjustment when displaying on a small device.\n  Auto,\n  /// No size adjustment when displaying on a small device.\n  None,\n  /// When displaying on a small device, the font size is multiplied by this percentage.\n  Percentage(Percentage),\n}\n\nbitflags! {\n  /// A value for the [text-decoration-line](https://www.w3.org/TR/2020/WD-css-text-decor-4-20200506/#text-decoration-line-property) property.\n  ///\n  /// Multiple lines may be specified by combining the flags.\n  #[cfg_attr(feature = \"visitor\", derive(Visit))]\n  #[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize), serde(from = \"SerializedTextDecorationLine\", into = \"SerializedTextDecorationLine\"))]\n  #[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n  #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]\n  pub struct TextDecorationLine: u8 {\n    /// Each line of text is underlined.\n    const Underline     = 0b00000001;\n    /// Each line of text has a line over it.\n    const Overline      = 0b00000010;\n    /// Each line of text has a line through the middle.\n    const LineThrough   = 0b00000100;\n    /// The text blinks.\n    const Blink         = 0b00001000;\n    /// The text is decorated as a spelling error.\n    const SpellingError = 0b00010000;\n    /// The text is decorated as a grammar error.\n    const GrammarError  = 0b00100000;\n  }\n}\n\nimpl Default for TextDecorationLine {\n  fn default() -> TextDecorationLine {\n    TextDecorationLine::empty()\n  }\n}\n\nimpl<'i> Parse<'i> for TextDecorationLine {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let mut value = TextDecorationLine::empty();\n    let mut any = false;\n\n    loop {\n      let flag: Result<_, ParseError<'i, ParserError<'i>>> = input.try_parse(|input| {\n        let location = input.current_source_location();\n        let ident = input.expect_ident()?;\n        Ok(match_ignore_ascii_case! { &ident,\n          \"none\" if value.is_empty() => TextDecorationLine::empty(),\n          \"underline\" => TextDecorationLine::Underline,\n          \"overline\" => TextDecorationLine::Overline,\n          \"line-through\" => TextDecorationLine::LineThrough,\n          \"blink\" =>TextDecorationLine::Blink,\n          \"spelling-error\" if value.is_empty() => TextDecorationLine::SpellingError,\n          \"grammar-error\" if value.is_empty() => TextDecorationLine::GrammarError,\n          _ => return Err(location.new_unexpected_token_error(\n            cssparser::Token::Ident(ident.clone())\n          ))\n        })\n      });\n\n      if let Ok(flag) = flag {\n        value |= flag;\n        any = true;\n      } else {\n        break;\n      }\n    }\n\n    if !any {\n      return Err(input.new_custom_error(ParserError::InvalidDeclaration));\n    }\n\n    Ok(value)\n  }\n}\n\nimpl ToCss for TextDecorationLine {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    if self.is_empty() {\n      return dest.write_str(\"none\");\n    }\n\n    if self.contains(TextDecorationLine::SpellingError) {\n      return dest.write_str(\"spelling-error\");\n    }\n\n    if self.contains(TextDecorationLine::GrammarError) {\n      return dest.write_str(\"grammar-error\");\n    }\n\n    let mut needs_space = false;\n    macro_rules! val {\n      ($val: ident, $str: expr) => {\n        #[allow(unused_assignments)]\n        if self.contains(TextDecorationLine::$val) {\n          if needs_space {\n            dest.write_char(' ')?;\n          }\n          dest.write_str($str)?;\n          needs_space = true;\n        }\n      };\n    }\n\n    val!(Underline, \"underline\");\n    val!(Overline, \"overline\");\n    val!(LineThrough, \"line-through\");\n    val!(Blink, \"blink\");\n    Ok(())\n  }\n}\n\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize), serde(untagged))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\nenum SerializedTextDecorationLine {\n  Exclusive(ExclusiveTextDecorationLine),\n  Other(Vec<OtherTextDecorationLine>),\n}\n\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\nenum ExclusiveTextDecorationLine {\n  None,\n  SpellingError,\n  GrammarError,\n}\n\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\nenum OtherTextDecorationLine {\n  Underline,\n  Overline,\n  LineThrough,\n  Blink,\n}\n\nimpl From<TextDecorationLine> for SerializedTextDecorationLine {\n  fn from(l: TextDecorationLine) -> Self {\n    if l.is_empty() {\n      return Self::Exclusive(ExclusiveTextDecorationLine::None);\n    }\n\n    macro_rules! exclusive {\n      ($t: ident) => {\n        if l.contains(TextDecorationLine::$t) {\n          return Self::Exclusive(ExclusiveTextDecorationLine::$t);\n        }\n      };\n    }\n\n    exclusive!(SpellingError);\n    exclusive!(GrammarError);\n\n    let mut v = Vec::new();\n    macro_rules! other {\n      ($t: ident) => {\n        if l.contains(TextDecorationLine::$t) {\n          v.push(OtherTextDecorationLine::$t)\n        }\n      };\n    }\n\n    other!(Underline);\n    other!(Overline);\n    other!(LineThrough);\n    other!(Blink);\n    Self::Other(v)\n  }\n}\n\nimpl From<SerializedTextDecorationLine> for TextDecorationLine {\n  fn from(l: SerializedTextDecorationLine) -> Self {\n    match l {\n      SerializedTextDecorationLine::Exclusive(v) => match v {\n        ExclusiveTextDecorationLine::None => TextDecorationLine::empty(),\n        ExclusiveTextDecorationLine::SpellingError => TextDecorationLine::SpellingError,\n        ExclusiveTextDecorationLine::GrammarError => TextDecorationLine::GrammarError,\n      },\n      SerializedTextDecorationLine::Other(v) => {\n        let mut res = TextDecorationLine::empty();\n        for val in v {\n          res |= match val {\n            OtherTextDecorationLine::Underline => TextDecorationLine::Underline,\n            OtherTextDecorationLine::Overline => TextDecorationLine::Overline,\n            OtherTextDecorationLine::LineThrough => TextDecorationLine::LineThrough,\n            OtherTextDecorationLine::Blink => TextDecorationLine::Blink,\n          }\n        }\n        res\n      }\n    }\n  }\n}\n\n#[cfg(feature = \"jsonschema\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"jsonschema\")))]\nimpl<'a> schemars::JsonSchema for TextDecorationLine {\n  fn is_referenceable() -> bool {\n    true\n  }\n\n  fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {\n    SerializedTextDecorationLine::json_schema(gen)\n  }\n\n  fn schema_name() -> String {\n    \"TextDecorationLine\".into()\n  }\n}\n\nenum_property! {\n  /// A value for the [text-decoration-style](https://www.w3.org/TR/2020/WD-css-text-decor-4-20200506/#text-decoration-style-property) property.\n  pub enum TextDecorationStyle {\n    /// A single line segment.\n    Solid,\n    /// Two parallel solid lines with some space between them.\n    Double,\n    /// A series of round dots.\n    Dotted,\n    /// A series of square-ended dashes.\n    Dashed,\n    /// A wavy line.\n    Wavy,\n  }\n}\n\nimpl Default for TextDecorationStyle {\n  fn default() -> TextDecorationStyle {\n    TextDecorationStyle::Solid\n  }\n}\n\n/// A value for the [text-decoration-thickness](https://www.w3.org/TR/2020/WD-css-text-decor-4-20200506/#text-decoration-width-property) property.\n#[derive(Debug, Clone, PartialEq, Parse, ToCss)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum TextDecorationThickness {\n  /// The UA chooses an appropriate thickness for text decoration lines.\n  Auto,\n  /// Use the thickness defined in the current font.\n  FromFont,\n  /// An explicit length.\n  LengthPercentage(LengthPercentage),\n}\n\nimpl Default for TextDecorationThickness {\n  fn default() -> TextDecorationThickness {\n    TextDecorationThickness::Auto\n  }\n}\n\ndefine_shorthand! {\n  /// A value for the [text-decoration](https://www.w3.org/TR/2020/WD-css-text-decor-4-20200506/#text-decoration-property) shorthand property.\n  pub struct TextDecoration(VendorPrefix) {\n    /// The lines to display.\n    line: TextDecorationLine(TextDecorationLine, VendorPrefix),\n    /// The thickness of the lines.\n    thickness: TextDecorationThickness(TextDecorationThickness),\n    /// The style of the lines.\n    style: TextDecorationStyle(TextDecorationStyle, VendorPrefix),\n    /// The color of the lines.\n    color: TextDecorationColor(CssColor, VendorPrefix),\n  }\n}\n\nimpl<'i> Parse<'i> for TextDecoration {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let mut line = None;\n    let mut thickness = None;\n    let mut style = None;\n    let mut color = None;\n\n    loop {\n      macro_rules! prop {\n        ($key: ident, $type: ident) => {\n          if $key.is_none() {\n            if let Ok(val) = input.try_parse($type::parse) {\n              $key = Some(val);\n              continue;\n            }\n          }\n        };\n      }\n\n      prop!(line, TextDecorationLine);\n      prop!(thickness, TextDecorationThickness);\n      prop!(style, TextDecorationStyle);\n      prop!(color, CssColor);\n      break;\n    }\n\n    Ok(TextDecoration {\n      line: line.unwrap_or_default(),\n      thickness: thickness.unwrap_or_default(),\n      style: style.unwrap_or_default(),\n      color: color.unwrap_or(CssColor::current_color()),\n    })\n  }\n}\n\nimpl ToCss for TextDecoration {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    self.line.to_css(dest)?;\n    if self.line.is_empty() {\n      return Ok(());\n    }\n\n    let mut needs_space = true;\n    if self.thickness != TextDecorationThickness::default() {\n      dest.write_char(' ')?;\n      self.thickness.to_css(dest)?;\n      needs_space = true;\n    }\n\n    if self.style != TextDecorationStyle::default() {\n      if needs_space {\n        dest.write_char(' ')?;\n      }\n      self.style.to_css(dest)?;\n      needs_space = true;\n    }\n\n    if self.color != CssColor::current_color() {\n      if needs_space {\n        dest.write_char(' ')?;\n      }\n      self.color.to_css(dest)?;\n    }\n\n    Ok(())\n  }\n}\n\nimpl FallbackValues for TextDecoration {\n  fn get_fallbacks(&mut self, targets: Targets) -> Vec<Self> {\n    self\n      .color\n      .get_fallbacks(targets)\n      .into_iter()\n      .map(|color| TextDecoration { color, ..self.clone() })\n      .collect()\n  }\n}\n\nenum_property! {\n  /// A value for the [text-decoration-skip-ink](https://www.w3.org/TR/2020/WD-css-text-decor-4-20200506/#text-decoration-skip-ink-property) property.\n  pub enum TextDecorationSkipInk {\n    /// UAs may interrupt underlines and overlines.\n    Auto,\n    /// UAs must interrupt underlines and overlines.\n    None,\n    /// UA must draw continuous underlines and overlines.\n    All,\n  }\n}\n\nenum_property! {\n  /// A keyword for the [text-emphasis-style](https://www.w3.org/TR/2020/WD-css-text-decor-4-20200506/#text-emphasis-style-property) property.\n  ///\n  /// See [TextEmphasisStyle](TextEmphasisStyle).\n  pub enum TextEmphasisFillMode {\n    /// The shape is filled with solid color.\n    Filled,\n    /// The shape is hollow.\n    Open,\n  }\n}\n\nenum_property! {\n  /// A text emphasis shape for the [text-emphasis-style](https://www.w3.org/TR/2020/WD-css-text-decor-4-20200506/#text-emphasis-style-property) property.\n  ///\n  /// See [TextEmphasisStyle](TextEmphasisStyle).\n  pub enum TextEmphasisShape {\n    /// Display small circles as marks.\n    Dot,\n    /// Display large circles as marks.\n    Circle,\n    /// Display double circles as marks.\n    DoubleCircle,\n    /// Display triangles as marks.\n    Triangle,\n    /// Display sesames as marks.\n    Sesame,\n  }\n}\n\n/// A value for the [text-emphasis-style](https://www.w3.org/TR/2020/WD-css-text-decor-4-20200506/#text-emphasis-style-property) property.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub enum TextEmphasisStyle<'i> {\n  /// No emphasis.\n  None,\n  /// Defines the fill and shape of the marks.\n  Keyword {\n    /// The fill mode for the marks.\n    fill: TextEmphasisFillMode,\n    /// The shape of the marks.\n    shape: Option<TextEmphasisShape>,\n  },\n  /// Display the given string as marks.\n  #[cfg_attr(\n    feature = \"serde\",\n    serde(borrow, with = \"crate::serialization::ValueWrapper::<CSSString>\")\n  )]\n  String(CSSString<'i>),\n}\n\nimpl<'i> Default for TextEmphasisStyle<'i> {\n  fn default() -> TextEmphasisStyle<'i> {\n    TextEmphasisStyle::None\n  }\n}\n\nimpl<'i> Parse<'i> for TextEmphasisStyle<'i> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    if input.try_parse(|input| input.expect_ident_matching(\"none\")).is_ok() {\n      return Ok(TextEmphasisStyle::None);\n    }\n\n    if let Ok(s) = input.try_parse(CSSString::parse) {\n      return Ok(TextEmphasisStyle::String(s));\n    }\n\n    let mut shape = input.try_parse(TextEmphasisShape::parse).ok();\n    let fill = input.try_parse(TextEmphasisFillMode::parse).ok();\n    if shape.is_none() {\n      shape = input.try_parse(TextEmphasisShape::parse).ok();\n    }\n\n    if shape.is_none() && fill.is_none() {\n      return Err(input.new_custom_error(ParserError::InvalidDeclaration));\n    }\n\n    let fill = fill.unwrap_or(TextEmphasisFillMode::Filled);\n    Ok(TextEmphasisStyle::Keyword { fill, shape })\n  }\n}\n\nimpl<'i> ToCss for TextEmphasisStyle<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      TextEmphasisStyle::None => dest.write_str(\"none\"),\n      TextEmphasisStyle::String(s) => s.to_css(dest),\n      TextEmphasisStyle::Keyword { fill, shape } => {\n        let mut needs_space = false;\n        if *fill != TextEmphasisFillMode::Filled || shape.is_none() {\n          fill.to_css(dest)?;\n          needs_space = true;\n        }\n\n        if let Some(shape) = shape {\n          if needs_space {\n            dest.write_char(' ')?;\n          }\n          shape.to_css(dest)?;\n        }\n        Ok(())\n      }\n    }\n  }\n}\n\ndefine_shorthand! {\n  /// A value for the [text-emphasis](https://www.w3.org/TR/2020/WD-css-text-decor-4-20200506/#text-emphasis-property) shorthand property.\n  pub struct TextEmphasis<'i>(VendorPrefix) {\n    /// The text emphasis style.\n    #[cfg_attr(feature = \"serde\", serde(borrow))]\n    style: TextEmphasisStyle(TextEmphasisStyle<'i>, VendorPrefix),\n    /// The text emphasis color.\n    color: TextEmphasisColor(CssColor, VendorPrefix),\n  }\n}\n\nimpl<'i> Parse<'i> for TextEmphasis<'i> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let mut style = None;\n    let mut color = None;\n\n    loop {\n      if style.is_none() {\n        if let Ok(s) = input.try_parse(TextEmphasisStyle::parse) {\n          style = Some(s);\n          continue;\n        }\n      }\n\n      if color.is_none() {\n        if let Ok(c) = input.try_parse(CssColor::parse) {\n          color = Some(c);\n          continue;\n        }\n      }\n\n      break;\n    }\n\n    Ok(TextEmphasis {\n      style: style.unwrap_or_default(),\n      color: color.unwrap_or(CssColor::current_color()),\n    })\n  }\n}\n\nimpl<'i> ToCss for TextEmphasis<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    self.style.to_css(dest)?;\n\n    if self.style != TextEmphasisStyle::None && self.color != CssColor::current_color() {\n      dest.write_char(' ')?;\n      self.color.to_css(dest)?;\n    }\n\n    Ok(())\n  }\n}\n\nimpl<'i> FallbackValues for TextEmphasis<'i> {\n  fn get_fallbacks(&mut self, targets: Targets) -> Vec<Self> {\n    self\n      .color\n      .get_fallbacks(targets)\n      .into_iter()\n      .map(|color| TextEmphasis { color, ..self.clone() })\n      .collect()\n  }\n}\n\nenum_property! {\n  /// A vertical position keyword for the [text-emphasis-position](https://www.w3.org/TR/2020/WD-css-text-decor-4-20200506/#text-emphasis-position-property) property.\n  ///\n  /// See [TextEmphasisPosition](TextEmphasisPosition).\n  pub enum TextEmphasisPositionVertical {\n    /// Draw marks over the text in horizontal typographic modes.\n    Over,\n    /// Draw marks under the text in horizontal typographic modes.\n    Under,\n  }\n}\n\nenum_property! {\n  /// A horizontal position keyword for the [text-emphasis-position](https://www.w3.org/TR/2020/WD-css-text-decor-4-20200506/#text-emphasis-position-property) property.\n  ///\n  /// See [TextEmphasisPosition](TextEmphasisPosition).\n  pub enum TextEmphasisPositionHorizontal {\n    /// Draw marks to the right of the text in vertical typographic modes.\n    Left,\n    /// Draw marks to the left of the text in vertical typographic modes.\n    Right,\n  }\n}\n\n/// A value for the [text-emphasis-position](https://www.w3.org/TR/2020/WD-css-text-decor-4-20200506/#text-emphasis-position-property) property.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub struct TextEmphasisPosition {\n  /// The vertical position.\n  pub vertical: TextEmphasisPositionVertical,\n  /// The horizontal position.\n  pub horizontal: TextEmphasisPositionHorizontal,\n}\n\nimpl<'i> Parse<'i> for TextEmphasisPosition {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    if let Ok(horizontal) = input.try_parse(TextEmphasisPositionHorizontal::parse) {\n      let vertical = TextEmphasisPositionVertical::parse(input)?;\n      Ok(TextEmphasisPosition { horizontal, vertical })\n    } else {\n      let vertical = TextEmphasisPositionVertical::parse(input)?;\n      let horizontal = input\n        .try_parse(TextEmphasisPositionHorizontal::parse)\n        .unwrap_or(TextEmphasisPositionHorizontal::Right);\n      Ok(TextEmphasisPosition { horizontal, vertical })\n    }\n  }\n}\n\nenum_property! {\n  /// A value for the [box-decoration-break](https://www.w3.org/TR/css-break-3/#break-decoration) property.\n  pub enum BoxDecorationBreak {\n    /// The element is rendered with no breaks present, and then sliced by the breaks afterward.\n    Slice,\n    /// Each box fragment is independently wrapped with the border, padding, and margin.\n    Clone,\n  }\n}\n\nimpl Default for BoxDecorationBreak {\n  fn default() -> Self {\n    BoxDecorationBreak::Slice\n  }\n}\n\nimpl ToCss for TextEmphasisPosition {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    self.vertical.to_css(dest)?;\n    if self.horizontal != TextEmphasisPositionHorizontal::Right {\n      dest.write_char(' ')?;\n      self.horizontal.to_css(dest)?;\n    }\n    Ok(())\n  }\n}\n\n#[derive(Default)]\npub(crate) struct TextDecorationHandler<'i> {\n  line: Option<(TextDecorationLine, VendorPrefix)>,\n  thickness: Option<TextDecorationThickness>,\n  style: Option<(TextDecorationStyle, VendorPrefix)>,\n  color: Option<(CssColor, VendorPrefix)>,\n  emphasis_style: Option<(TextEmphasisStyle<'i>, VendorPrefix)>,\n  emphasis_color: Option<(CssColor, VendorPrefix)>,\n  emphasis_position: Option<(TextEmphasisPosition, VendorPrefix)>,\n  has_any: bool,\n}\n\nimpl<'i> PropertyHandler<'i> for TextDecorationHandler<'i> {\n  fn handle_property(\n    &mut self,\n    property: &Property<'i>,\n    dest: &mut DeclarationList<'i>,\n    context: &mut PropertyHandlerContext<'i, '_>,\n  ) -> bool {\n    use Property::*;\n\n    macro_rules! maybe_flush {\n      ($prop: ident, $val: expr, $vp: expr) => {{\n        // If two vendor prefixes for the same property have different\n        // values, we need to flush what we have immediately to preserve order.\n        if let Some((val, prefixes)) = &self.$prop {\n          if val != $val && !prefixes.contains(*$vp) {\n            self.finalize(dest, context);\n          }\n        }\n      }};\n    }\n\n    macro_rules! property {\n      ($prop: ident, $val: expr, $vp: expr) => {{\n        maybe_flush!($prop, $val, $vp);\n\n        // Otherwise, update the value and add the prefix.\n        if let Some((val, prefixes)) = &mut self.$prop {\n          *val = $val.clone();\n          *prefixes |= *$vp;\n        } else {\n          self.$prop = Some(($val.clone(), *$vp));\n          self.has_any = true;\n        }\n      }};\n    }\n\n    match property {\n      TextDecorationLine(val, vp) => property!(line, val, vp),\n      TextDecorationThickness(val) => {\n        self.thickness = Some(val.clone());\n        self.has_any = true;\n      }\n      TextDecorationStyle(val, vp) => property!(style, val, vp),\n      TextDecorationColor(val, vp) => property!(color, val, vp),\n      TextDecoration(val, vp) => {\n        maybe_flush!(line, &val.line, vp);\n        maybe_flush!(style, &val.style, vp);\n        maybe_flush!(color, &val.color, vp);\n        property!(line, &val.line, vp);\n        self.thickness = Some(val.thickness.clone());\n        property!(style, &val.style, vp);\n        property!(color, &val.color, vp);\n      }\n      TextEmphasisStyle(val, vp) => property!(emphasis_style, val, vp),\n      TextEmphasisColor(val, vp) => property!(emphasis_color, val, vp),\n      TextEmphasis(val, vp) => {\n        maybe_flush!(emphasis_style, &val.style, vp);\n        maybe_flush!(emphasis_color, &val.color, vp);\n        property!(emphasis_style, &val.style, vp);\n        property!(emphasis_color, &val.color, vp);\n      }\n      TextEmphasisPosition(val, vp) => property!(emphasis_position, val, vp),\n      TextAlign(align) => {\n        use super::text::*;\n        macro_rules! logical {\n          ($ltr: ident, $rtl: ident) => {{\n            let logical_supported = !context.should_compile_logical(compat::Feature::LogicalTextAlign);\n            if logical_supported {\n              dest.push(property.clone());\n            } else {\n              context.add_logical_rule(\n                Property::TextAlign(TextAlign::$ltr),\n                Property::TextAlign(TextAlign::$rtl),\n              );\n            }\n          }};\n        }\n\n        match align {\n          TextAlign::Start => logical!(Left, Right),\n          TextAlign::End => logical!(Right, Left),\n          _ => dest.push(property.clone()),\n        }\n      }\n      Unparsed(val) if is_text_decoration_property(&val.property_id) => {\n        self.finalize(dest, context);\n        let mut unparsed = val.get_prefixed(context.targets, Feature::TextDecoration);\n        context.add_unparsed_fallbacks(&mut unparsed);\n        dest.push(Property::Unparsed(unparsed))\n      }\n      Unparsed(val) if is_text_emphasis_property(&val.property_id) => {\n        self.finalize(dest, context);\n        let mut unparsed = val.get_prefixed(context.targets, Feature::TextEmphasis);\n        context.add_unparsed_fallbacks(&mut unparsed);\n        dest.push(Property::Unparsed(unparsed))\n      }\n      _ => return false,\n    }\n\n    true\n  }\n\n  fn finalize(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {\n    if !self.has_any {\n      return;\n    }\n\n    self.has_any = false;\n\n    let mut line = std::mem::take(&mut self.line);\n    let mut thickness = std::mem::take(&mut self.thickness);\n    let mut style = std::mem::take(&mut self.style);\n    let mut color = std::mem::take(&mut self.color);\n    let mut emphasis_style = std::mem::take(&mut self.emphasis_style);\n    let mut emphasis_color = std::mem::take(&mut self.emphasis_color);\n    let emphasis_position = std::mem::take(&mut self.emphasis_position);\n\n    if let (Some((line, line_vp)), Some(thickness_val), Some((style, style_vp)), Some((color, color_vp))) =\n      (&mut line, &mut thickness, &mut style, &mut color)\n    {\n      let intersection = *line_vp | *style_vp | *color_vp;\n      if !intersection.is_empty() {\n        let mut prefix = intersection;\n\n        // Some browsers don't support thickness in the shorthand property yet.\n        let supports_thickness = context.targets.is_compatible(compat::Feature::TextDecorationThicknessShorthand);\n        let mut decoration = TextDecoration {\n          line: line.clone(),\n          thickness: if supports_thickness {\n            thickness_val.clone()\n          } else {\n            TextDecorationThickness::default()\n          },\n          style: style.clone(),\n          color: color.clone(),\n        };\n\n        // Only add prefixes if one of the new sub-properties was used\n        if prefix.contains(VendorPrefix::None)\n          && (*style != TextDecorationStyle::default() || *color != CssColor::current_color())\n        {\n          prefix = context.targets.prefixes(VendorPrefix::None, Feature::TextDecoration);\n\n          let fallbacks = decoration.get_fallbacks(context.targets);\n          for fallback in fallbacks {\n            dest.push(Property::TextDecoration(fallback, prefix))\n          }\n        }\n\n        dest.push(Property::TextDecoration(decoration, prefix));\n        line_vp.remove(intersection);\n        style_vp.remove(intersection);\n        color_vp.remove(intersection);\n        if supports_thickness || *thickness_val == TextDecorationThickness::default() {\n          thickness = None;\n        }\n      }\n    }\n\n    macro_rules! color {\n      ($key: ident, $prop: ident) => {\n        if let Some((mut val, vp)) = $key {\n          if !vp.is_empty() {\n            let prefix = context.targets.prefixes(vp, Feature::$prop);\n            if prefix.contains(VendorPrefix::None) {\n              let fallbacks = val.get_fallbacks(context.targets);\n              for fallback in fallbacks {\n                dest.push(Property::$prop(fallback, prefix))\n              }\n            }\n            dest.push(Property::$prop(val, prefix))\n          }\n        }\n      };\n    }\n\n    macro_rules! single_property {\n      ($key: ident, $prop: ident) => {\n        if let Some((val, vp)) = $key {\n          if !vp.is_empty() {\n            let prefix = context.targets.prefixes(vp, Feature::$prop);\n            dest.push(Property::$prop(val, prefix))\n          }\n        }\n      };\n    }\n\n    single_property!(line, TextDecorationLine);\n    single_property!(style, TextDecorationStyle);\n    color!(color, TextDecorationColor);\n\n    if let Some(thickness) = thickness {\n      // Percentages in the text-decoration-thickness property are based on 1em.\n      // If unsupported, compile this to a calc() instead.\n      match thickness {\n        TextDecorationThickness::LengthPercentage(LengthPercentage::Percentage(p))\n          if should_compile!(context.targets, TextDecorationThicknessPercent) =>\n        {\n          let calc = Calc::Function(Box::new(MathFunction::Calc(Calc::Product(\n            p.0,\n            Box::new(Calc::Value(Box::new(LengthPercentage::Dimension(LengthValue::Em(1.0))))),\n          ))));\n          let thickness = TextDecorationThickness::LengthPercentage(LengthPercentage::Calc(Box::new(calc)));\n          dest.push(Property::TextDecorationThickness(thickness));\n        }\n        thickness => dest.push(Property::TextDecorationThickness(thickness)),\n      }\n    }\n\n    if let (Some((style, style_vp)), Some((color, color_vp))) = (&mut emphasis_style, &mut emphasis_color) {\n      let intersection = *style_vp | *color_vp;\n      if !intersection.is_empty() {\n        let prefix = context.targets.prefixes(intersection, Feature::TextEmphasis);\n        let mut emphasis = TextEmphasis {\n          style: style.clone(),\n          color: color.clone(),\n        };\n\n        if prefix.contains(VendorPrefix::None) {\n          let fallbacks = emphasis.get_fallbacks(context.targets);\n          for fallback in fallbacks {\n            dest.push(Property::TextEmphasis(fallback, prefix))\n          }\n        }\n\n        dest.push(Property::TextEmphasis(emphasis, prefix));\n        style_vp.remove(intersection);\n        color_vp.remove(intersection);\n      }\n    }\n\n    single_property!(emphasis_style, TextEmphasisStyle);\n    color!(emphasis_color, TextEmphasisColor);\n\n    if let Some((pos, vp)) = emphasis_position {\n      if !vp.is_empty() {\n        let mut prefix = context.targets.prefixes(vp, Feature::TextEmphasisPosition);\n        // Prefixed version does not support horizontal keyword.\n        if pos.horizontal != TextEmphasisPositionHorizontal::Right {\n          prefix = VendorPrefix::None;\n        }\n        dest.push(Property::TextEmphasisPosition(pos, prefix))\n      }\n    }\n  }\n}\n\n/// A value for the [text-shadow](https://www.w3.org/TR/2020/WD-css-text-decor-4-20200506/#text-shadow-property) property.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"camelCase\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub struct TextShadow {\n  /// The color of the text shadow.\n  pub color: CssColor,\n  /// The x offset of the text shadow.\n  pub x_offset: Length,\n  /// The y offset of the text shadow.\n  pub y_offset: Length,\n  /// The blur radius of the text shadow.\n  pub blur: Length,\n  /// The spread distance of the text shadow.\n  pub spread: Length, // added in Level 4 spec\n}\n\nimpl<'i> Parse<'i> for TextShadow {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let mut color = None;\n    let mut lengths = None;\n\n    loop {\n      if lengths.is_none() {\n        let value = input.try_parse::<_, _, ParseError<ParserError<'i>>>(|input| {\n          let horizontal = Length::parse(input)?;\n          let vertical = Length::parse(input)?;\n          let blur = input.try_parse(Length::parse).unwrap_or(Length::zero());\n          let spread = input.try_parse(Length::parse).unwrap_or(Length::zero());\n          Ok((horizontal, vertical, blur, spread))\n        });\n\n        if let Ok(value) = value {\n          lengths = Some(value);\n          continue;\n        }\n      }\n\n      if color.is_none() {\n        if let Ok(value) = input.try_parse(CssColor::parse) {\n          color = Some(value);\n          continue;\n        }\n      }\n\n      break;\n    }\n\n    let lengths = lengths.ok_or(input.new_error(BasicParseErrorKind::QualifiedRuleInvalid))?;\n    Ok(TextShadow {\n      color: color.unwrap_or(CssColor::current_color()),\n      x_offset: lengths.0,\n      y_offset: lengths.1,\n      blur: lengths.2,\n      spread: lengths.3,\n    })\n  }\n}\n\nimpl ToCss for TextShadow {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    self.x_offset.to_css(dest)?;\n    dest.write_char(' ')?;\n    self.y_offset.to_css(dest)?;\n\n    if self.blur != Length::zero() || self.spread != Length::zero() {\n      dest.write_char(' ')?;\n      self.blur.to_css(dest)?;\n\n      if self.spread != Length::zero() {\n        dest.write_char(' ')?;\n        self.spread.to_css(dest)?;\n      }\n    }\n\n    if self.color != CssColor::current_color() {\n      dest.write_char(' ')?;\n      self.color.to_css(dest)?;\n    }\n\n    Ok(())\n  }\n}\n\nimpl IsCompatible for TextShadow {\n  fn is_compatible(&self, browsers: Browsers) -> bool {\n    self.color.is_compatible(browsers)\n      && self.x_offset.is_compatible(browsers)\n      && self.y_offset.is_compatible(browsers)\n      && self.blur.is_compatible(browsers)\n      && self.spread.is_compatible(browsers)\n  }\n}\n\n#[inline]\nfn is_text_decoration_property(property_id: &PropertyId) -> bool {\n  match property_id {\n    PropertyId::TextDecorationLine(_)\n    | PropertyId::TextDecorationThickness\n    | PropertyId::TextDecorationStyle(_)\n    | PropertyId::TextDecorationColor(_)\n    | PropertyId::TextDecoration(_) => true,\n    _ => false,\n  }\n}\n\n#[inline]\nfn is_text_emphasis_property(property_id: &PropertyId) -> bool {\n  match property_id {\n    PropertyId::TextEmphasisStyle(_)\n    | PropertyId::TextEmphasisColor(_)\n    | PropertyId::TextEmphasis(_)\n    | PropertyId::TextEmphasisPosition(_) => true,\n    _ => false,\n  }\n}\n\nimpl FallbackValues for SmallVec<[TextShadow; 1]> {\n  fn get_fallbacks(&mut self, targets: Targets) -> Vec<Self> {\n    let mut fallbacks = ColorFallbackKind::empty();\n    for shadow in self.iter() {\n      fallbacks |= shadow.color.get_necessary_fallbacks(targets);\n    }\n\n    let mut res = Vec::new();\n    if fallbacks.contains(ColorFallbackKind::RGB) {\n      let rgb = self\n        .iter()\n        .map(|shadow| TextShadow {\n          color: shadow.color.to_rgb().unwrap(),\n          ..shadow.clone()\n        })\n        .collect();\n      res.push(rgb);\n    }\n\n    if fallbacks.contains(ColorFallbackKind::P3) {\n      let p3 = self\n        .iter()\n        .map(|shadow| TextShadow {\n          color: shadow.color.to_p3().unwrap(),\n          ..shadow.clone()\n        })\n        .collect();\n      res.push(p3);\n    }\n\n    if fallbacks.contains(ColorFallbackKind::LAB) {\n      for shadow in self.iter_mut() {\n        shadow.color = shadow.color.to_lab().unwrap();\n      }\n    }\n\n    res\n  }\n}\n\nenum_property! {\n  /// A value for the [direction](https://drafts.csswg.org/css-writing-modes-3/#direction) property.\n  pub enum Direction {\n    /// This value sets inline base direction (bidi directionality) to line-left-to-line-right.\n    Ltr,\n    /// This value sets inline base direction (bidi directionality) to line-right-to-line-left.\n    Rtl,\n  }\n}\n\nenum_property! {\n  /// A value for the [unicode-bidi](https://drafts.csswg.org/css-writing-modes-3/#unicode-bidi) property.\n  pub enum UnicodeBidi {\n    /// The box does not open an additional level of embedding.\n    Normal,\n    /// If the box is inline, this value creates a directional embedding by opening an additional level of embedding.\n    Embed,\n    /// On an inline box, this bidi-isolates its contents.\n    Isolate,\n    /// This value puts the box’s immediate inline content in a directional override.\n    BidiOverride,\n    /// This combines the isolation behavior of isolate with the directional override behavior of bidi-override.\n    IsolateOverride,\n    /// This value behaves as isolate except that the base directionality is determined using a heuristic rather than the direction property.\n    Plaintext,\n  }\n}\n"
  },
  {
    "path": "src/properties/transform.rs",
    "content": "//! CSS properties related to 2D and 3D transforms.\n\nuse super::{Property, PropertyId};\nuse crate::context::PropertyHandlerContext;\nuse crate::declaration::DeclarationList;\nuse crate::error::{ParserError, PrinterError};\nuse crate::macros::enum_property;\nuse crate::prefixes::Feature;\nuse crate::printer::Printer;\nuse crate::traits::{Parse, PropertyHandler, ToCss, Zero};\nuse crate::values::{\n  angle::Angle,\n  length::{Length, LengthPercentage},\n  percentage::NumberOrPercentage,\n};\nuse crate::vendor_prefix::VendorPrefix;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse cssparser::*;\nuse std::f32::consts::PI;\n\n/// A value for the [transform](https://www.w3.org/TR/2019/CR-css-transforms-1-20190214/#propdef-transform) property.\n#[derive(Debug, Clone, PartialEq, Default)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize), serde(transparent))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub struct TransformList(pub Vec<Transform>);\n\nimpl<'i> Parse<'i> for TransformList {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    if input.try_parse(|input| input.expect_ident_matching(\"none\")).is_ok() {\n      return Ok(TransformList(vec![]));\n    }\n\n    input.skip_whitespace();\n    let mut results = vec![Transform::parse(input)?];\n    loop {\n      input.skip_whitespace();\n      if let Ok(item) = input.try_parse(Transform::parse) {\n        results.push(item);\n      } else {\n        return Ok(TransformList(results));\n      }\n    }\n  }\n}\n\nimpl ToCss for TransformList {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    if self.0.is_empty() {\n      dest.write_str(\"none\")?;\n      return Ok(());\n    }\n\n    // TODO: Re-enable with a better solution\n    //       See: https://github.com/parcel-bundler/lightningcss/issues/288\n    // if dest.minify {\n    //   // Combine transforms into a single matrix.\n    //   if let Some(matrix) = self.to_matrix() {\n    //     // Generate based on the original transforms.\n    //     let mut base = String::new();\n    //     self.to_css_base(&mut Printer::new(\n    //       &mut base,\n    //       PrinterOptions {\n    //         minify: true,\n    //         ..PrinterOptions::default()\n    //       },\n    //     ))?;\n    //\n    //     // Decompose the matrix into transform functions if possible.\n    //     // If the resulting length is shorter than the original, use it.\n    //     if let Some(d) = matrix.decompose() {\n    //       let mut decomposed = String::new();\n    //       d.to_css_base(&mut Printer::new(\n    //         &mut decomposed,\n    //         PrinterOptions {\n    //           minify: true,\n    //           ..PrinterOptions::default()\n    //         },\n    //       ))?;\n    //       if decomposed.len() < base.len() {\n    //         base = decomposed;\n    //       }\n    //     }\n    //\n    //     // Also generate a matrix() or matrix3d() representation and compare that.\n    //     let mut mat = String::new();\n    //     if let Some(matrix) = matrix.to_matrix2d() {\n    //       Transform::Matrix(matrix).to_css(&mut Printer::new(\n    //         &mut mat,\n    //         PrinterOptions {\n    //           minify: true,\n    //           ..PrinterOptions::default()\n    //         },\n    //       ))?\n    //     } else {\n    //       Transform::Matrix3d(matrix).to_css(&mut Printer::new(\n    //         &mut mat,\n    //         PrinterOptions {\n    //           minify: true,\n    //           ..PrinterOptions::default()\n    //         },\n    //       ))?\n    //     }\n    //\n    //     if mat.len() < base.len() {\n    //       dest.write_str(&mat)?;\n    //     } else {\n    //       dest.write_str(&base)?;\n    //     }\n    //\n    //     return Ok(());\n    //   }\n    // }\n\n    self.to_css_base(dest)\n  }\n}\n\nimpl TransformList {\n  fn to_css_base<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    let mut first = true;\n    for item in &self.0 {\n      if first {\n        first = false;\n      } else {\n        dest.whitespace()?;\n      }\n      item.to_css(dest)?;\n    }\n    Ok(())\n  }\n\n  /// Converts the transform list to a 3D matrix if possible.\n  pub fn to_matrix(&self) -> Option<Matrix3d<f32>> {\n    let mut matrix = Matrix3d::identity();\n    for transform in &self.0 {\n      if let Some(m) = transform.to_matrix() {\n        matrix = m.multiply(&matrix);\n      } else {\n        return None;\n      }\n    }\n    Some(matrix)\n  }\n}\n\n/// An individual [transform function](https://www.w3.org/TR/2019/CR-css-transforms-1-20190214/#two-d-transform-functions).\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"camelCase\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum Transform {\n  /// A 2D translation.\n  Translate(LengthPercentage, LengthPercentage),\n  /// A translation in the X direction.\n  TranslateX(LengthPercentage),\n  /// A translation in the Y direction.\n  TranslateY(LengthPercentage),\n  /// A translation in the Z direction.\n  TranslateZ(Length),\n  /// A 3D translation.\n  Translate3d(LengthPercentage, LengthPercentage, Length),\n  /// A 2D scale.\n  Scale(NumberOrPercentage, NumberOrPercentage),\n  /// A scale in the X direction.\n  ScaleX(NumberOrPercentage),\n  /// A scale in the Y direction.\n  ScaleY(NumberOrPercentage),\n  /// A scale in the Z direction.\n  ScaleZ(NumberOrPercentage),\n  /// A 3D scale.\n  Scale3d(NumberOrPercentage, NumberOrPercentage, NumberOrPercentage),\n  /// A 2D rotation.\n  Rotate(Angle),\n  /// A rotation around the X axis.\n  RotateX(Angle),\n  /// A rotation around the Y axis.\n  RotateY(Angle),\n  /// A rotation around the Z axis.\n  RotateZ(Angle),\n  /// A 3D rotation.\n  Rotate3d(f32, f32, f32, Angle),\n  /// A 2D skew.\n  Skew(Angle, Angle),\n  /// A skew along the X axis.\n  SkewX(Angle),\n  /// A skew along the Y axis.\n  SkewY(Angle),\n  /// A perspective transform.\n  Perspective(Length),\n  /// A 2D matrix transform.\n  Matrix(Matrix<f32>),\n  /// A 3D matrix transform.\n  Matrix3d(Matrix3d<f32>),\n}\n\n/// A 2D matrix.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[allow(missing_docs)]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub struct Matrix<T> {\n  pub a: T,\n  pub b: T,\n  pub c: T,\n  pub d: T,\n  pub e: T,\n  pub f: T,\n}\n\nimpl Matrix<f32> {\n  /// Converts the matrix to a 3D matrix.\n  pub fn to_matrix3d(&self) -> Matrix3d<f32> {\n    Matrix3d {\n      m11: self.a,\n      m12: self.b,\n      m13: 0.0,\n      m14: 0.0,\n      m21: self.c,\n      m22: self.d,\n      m23: 0.0,\n      m24: 0.0,\n      m31: 0.0,\n      m32: 0.0,\n      m33: 1.0,\n      m34: 0.0,\n      m41: self.e,\n      m42: self.f,\n      m43: 0.0,\n      m44: 1.0,\n    }\n  }\n}\n\n/// A 3D matrix.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[allow(missing_docs)]\npub struct Matrix3d<T> {\n  pub m11: T,\n  pub m12: T,\n  pub m13: T,\n  pub m14: T,\n  pub m21: T,\n  pub m22: T,\n  pub m23: T,\n  pub m24: T,\n  pub m31: T,\n  pub m32: T,\n  pub m33: T,\n  pub m34: T,\n  pub m41: T,\n  pub m42: T,\n  pub m43: T,\n  pub m44: T,\n}\n\n// https://drafts.csswg.org/css-transforms-2/#mathematical-description\nimpl Matrix3d<f32> {\n  /// Creates an identity matrix.\n  pub fn identity() -> Matrix3d<f32> {\n    Matrix3d {\n      m11: 1.0,\n      m12: 0.0,\n      m13: 0.0,\n      m14: 0.0,\n      m21: 0.0,\n      m22: 1.0,\n      m23: 0.0,\n      m24: 0.0,\n      m31: 0.0,\n      m32: 0.0,\n      m33: 1.0,\n      m34: 0.0,\n      m41: 0.0,\n      m42: 0.0,\n      m43: 0.0,\n      m44: 1.0,\n    }\n  }\n\n  /// Creates a translation matrix.\n  pub fn translate(x: f32, y: f32, z: f32) -> Matrix3d<f32> {\n    Matrix3d {\n      m11: 1.0,\n      m12: 0.0,\n      m13: 0.0,\n      m14: 0.0,\n      m21: 0.0,\n      m22: 1.0,\n      m23: 0.0,\n      m24: 0.0,\n      m31: 0.0,\n      m32: 0.0,\n      m33: 1.0,\n      m34: 0.0,\n      m41: x,\n      m42: y,\n      m43: z,\n      m44: 1.0,\n    }\n  }\n\n  /// Creates a scale matrix.\n  pub fn scale(x: f32, y: f32, z: f32) -> Matrix3d<f32> {\n    Matrix3d {\n      m11: x,\n      m12: 0.0,\n      m13: 0.0,\n      m14: 0.0,\n      m21: 0.0,\n      m22: y,\n      m23: 0.0,\n      m24: 0.0,\n      m31: 0.0,\n      m32: 0.0,\n      m33: z,\n      m34: 0.0,\n      m41: 0.0,\n      m42: 0.0,\n      m43: 0.0,\n      m44: 1.0,\n    }\n  }\n\n  /// Creates a rotation matrix.\n  pub fn rotate(x: f32, y: f32, z: f32, angle: f32) -> Matrix3d<f32> {\n    // Normalize the vector.\n    let length = (x * x + y * y + z * z).sqrt();\n    if length == 0.0 {\n      // A direction vector that cannot be normalized, such as [0,0,0], will cause the rotation to not be applied.\n      return Matrix3d::identity();\n    }\n\n    let x = x / length;\n    let y = y / length;\n    let z = z / length;\n\n    let half_angle = angle / 2.0;\n    let sin = half_angle.sin();\n    let sc = sin * half_angle.cos();\n    let sq = sin * sin;\n    let m11 = 1.0 - 2.0 * (y * y + z * z) * sq;\n    let m12 = 2.0 * (x * y * sq + z * sc);\n    let m13 = 2.0 * (x * z * sq - y * sc);\n    let m21 = 2.0 * (x * y * sq - z * sc);\n    let m22 = 1.0 - 2.0 * (x * x + z * z) * sq;\n    let m23 = 2.0 * (y * z * sq + x * sc);\n    let m31 = 2.0 * (x * z * sq + y * sc);\n    let m32 = 2.0 * (y * z * sq - x * sc);\n    let m33 = 1.0 - 2.0 * (x * x + y * y) * sq;\n    Matrix3d {\n      m11,\n      m12,\n      m13,\n      m14: 0.0,\n      m21,\n      m22,\n      m23,\n      m24: 0.0,\n      m31,\n      m32,\n      m33,\n      m34: 0.0,\n      m41: 0.0,\n      m42: 0.0,\n      m43: 0.0,\n      m44: 1.0,\n    }\n  }\n\n  /// Creates a skew matrix.\n  pub fn skew(a: f32, b: f32) -> Matrix3d<f32> {\n    Matrix3d {\n      m11: 1.0,\n      m12: b.tan(),\n      m13: 0.0,\n      m14: 0.0,\n      m21: a.tan(),\n      m22: 1.0,\n      m23: 0.0,\n      m24: 0.0,\n      m31: 0.0,\n      m32: 0.0,\n      m33: 1.0,\n      m34: 0.0,\n      m41: 0.0,\n      m42: 0.0,\n      m43: 0.0,\n      m44: 1.0,\n    }\n  }\n\n  /// Creates a perspective matrix.\n  pub fn perspective(d: f32) -> Matrix3d<f32> {\n    Matrix3d {\n      m11: 1.0,\n      m12: 0.0,\n      m13: 0.0,\n      m14: 0.0,\n      m21: 0.0,\n      m22: 1.0,\n      m23: 0.0,\n      m24: 0.0,\n      m31: 0.0,\n      m32: 0.0,\n      m33: 1.0,\n      m34: -1.0 / d,\n      m41: 0.0,\n      m42: 0.0,\n      m43: 0.0,\n      m44: 1.0,\n    }\n  }\n\n  /// Multiplies this matrix by another, returning a new matrix.\n  pub fn multiply(&self, other: &Self) -> Self {\n    Matrix3d {\n      m11: self.m11 * other.m11 + self.m12 * other.m21 + self.m13 * other.m31 + self.m14 * other.m41,\n      m12: self.m11 * other.m12 + self.m12 * other.m22 + self.m13 * other.m32 + self.m14 * other.m42,\n      m13: self.m11 * other.m13 + self.m12 * other.m23 + self.m13 * other.m33 + self.m14 * other.m43,\n      m14: self.m11 * other.m14 + self.m12 * other.m24 + self.m13 * other.m34 + self.m14 * other.m44,\n      m21: self.m21 * other.m11 + self.m22 * other.m21 + self.m23 * other.m31 + self.m24 * other.m41,\n      m22: self.m21 * other.m12 + self.m22 * other.m22 + self.m23 * other.m32 + self.m24 * other.m42,\n      m23: self.m21 * other.m13 + self.m22 * other.m23 + self.m23 * other.m33 + self.m24 * other.m43,\n      m24: self.m21 * other.m14 + self.m22 * other.m24 + self.m23 * other.m34 + self.m24 * other.m44,\n      m31: self.m31 * other.m11 + self.m32 * other.m21 + self.m33 * other.m31 + self.m34 * other.m41,\n      m32: self.m31 * other.m12 + self.m32 * other.m22 + self.m33 * other.m32 + self.m34 * other.m42,\n      m33: self.m31 * other.m13 + self.m32 * other.m23 + self.m33 * other.m33 + self.m34 * other.m43,\n      m34: self.m31 * other.m14 + self.m32 * other.m24 + self.m33 * other.m34 + self.m34 * other.m44,\n      m41: self.m41 * other.m11 + self.m42 * other.m21 + self.m43 * other.m31 + self.m44 * other.m41,\n      m42: self.m41 * other.m12 + self.m42 * other.m22 + self.m43 * other.m32 + self.m44 * other.m42,\n      m43: self.m41 * other.m13 + self.m42 * other.m23 + self.m43 * other.m33 + self.m44 * other.m43,\n      m44: self.m41 * other.m14 + self.m42 * other.m24 + self.m43 * other.m34 + self.m44 * other.m44,\n    }\n  }\n\n  /// Returns whether this matrix could be converted to a 2D matrix.\n  pub fn is_2d(&self) -> bool {\n    self.m31 == 0.0\n      && self.m32 == 0.0\n      && self.m13 == 0.0\n      && self.m23 == 0.0\n      && self.m43 == 0.0\n      && self.m14 == 0.0\n      && self.m24 == 0.0\n      && self.m34 == 0.0\n      && self.m33 == 1.0\n      && self.m44 == 1.0\n  }\n\n  /// Attempts to convert the matrix to 2D.\n  /// Returns `None` if the conversion is not possible.\n  pub fn to_matrix2d(&self) -> Option<Matrix<f32>> {\n    if self.is_2d() {\n      return Some(Matrix {\n        a: self.m11,\n        b: self.m12,\n        c: self.m21,\n        d: self.m22,\n        e: self.m41,\n        f: self.m42,\n      });\n    }\n    None\n  }\n\n  /// Scales the matrix by the given factor.\n  pub fn scale_by_factor(&mut self, scaling_factor: f32) {\n    self.m11 *= scaling_factor;\n    self.m12 *= scaling_factor;\n    self.m13 *= scaling_factor;\n    self.m14 *= scaling_factor;\n    self.m21 *= scaling_factor;\n    self.m22 *= scaling_factor;\n    self.m23 *= scaling_factor;\n    self.m24 *= scaling_factor;\n    self.m31 *= scaling_factor;\n    self.m32 *= scaling_factor;\n    self.m33 *= scaling_factor;\n    self.m34 *= scaling_factor;\n    self.m41 *= scaling_factor;\n    self.m42 *= scaling_factor;\n    self.m43 *= scaling_factor;\n    self.m44 *= scaling_factor;\n  }\n\n  /// Returns the determinant of the matrix.\n  pub fn determinant(&self) -> f32 {\n    self.m14 * self.m23 * self.m32 * self.m41\n      - self.m13 * self.m24 * self.m32 * self.m41\n      - self.m14 * self.m22 * self.m33 * self.m41\n      + self.m12 * self.m24 * self.m33 * self.m41\n      + self.m13 * self.m22 * self.m34 * self.m41\n      - self.m12 * self.m23 * self.m34 * self.m41\n      - self.m14 * self.m23 * self.m31 * self.m42\n      + self.m13 * self.m24 * self.m31 * self.m42\n      + self.m14 * self.m21 * self.m33 * self.m42\n      - self.m11 * self.m24 * self.m33 * self.m42\n      - self.m13 * self.m21 * self.m34 * self.m42\n      + self.m11 * self.m23 * self.m34 * self.m42\n      + self.m14 * self.m22 * self.m31 * self.m43\n      - self.m12 * self.m24 * self.m31 * self.m43\n      - self.m14 * self.m21 * self.m32 * self.m43\n      + self.m11 * self.m24 * self.m32 * self.m43\n      + self.m12 * self.m21 * self.m34 * self.m43\n      - self.m11 * self.m22 * self.m34 * self.m43\n      - self.m13 * self.m22 * self.m31 * self.m44\n      + self.m12 * self.m23 * self.m31 * self.m44\n      + self.m13 * self.m21 * self.m32 * self.m44\n      - self.m11 * self.m23 * self.m32 * self.m44\n      - self.m12 * self.m21 * self.m33 * self.m44\n      + self.m11 * self.m22 * self.m33 * self.m44\n  }\n\n  /// Returns the inverse of the matrix if possible.\n  pub fn inverse(&self) -> Option<Matrix3d<f32>> {\n    let mut det = self.determinant();\n    if det == 0.0 {\n      return None;\n    }\n\n    det = 1.0 / det;\n    Some(Matrix3d {\n      m11: det\n        * (self.m23 * self.m34 * self.m42 - self.m24 * self.m33 * self.m42 + self.m24 * self.m32 * self.m43\n          - self.m22 * self.m34 * self.m43\n          - self.m23 * self.m32 * self.m44\n          + self.m22 * self.m33 * self.m44),\n      m12: det\n        * (self.m14 * self.m33 * self.m42 - self.m13 * self.m34 * self.m42 - self.m14 * self.m32 * self.m43\n          + self.m12 * self.m34 * self.m43\n          + self.m13 * self.m32 * self.m44\n          - self.m12 * self.m33 * self.m44),\n      m13: det\n        * (self.m13 * self.m24 * self.m42 - self.m14 * self.m23 * self.m42 + self.m14 * self.m22 * self.m43\n          - self.m12 * self.m24 * self.m43\n          - self.m13 * self.m22 * self.m44\n          + self.m12 * self.m23 * self.m44),\n      m14: det\n        * (self.m14 * self.m23 * self.m32 - self.m13 * self.m24 * self.m32 - self.m14 * self.m22 * self.m33\n          + self.m12 * self.m24 * self.m33\n          + self.m13 * self.m22 * self.m34\n          - self.m12 * self.m23 * self.m34),\n      m21: det\n        * (self.m24 * self.m33 * self.m41 - self.m23 * self.m34 * self.m41 - self.m24 * self.m31 * self.m43\n          + self.m21 * self.m34 * self.m43\n          + self.m23 * self.m31 * self.m44\n          - self.m21 * self.m33 * self.m44),\n      m22: det\n        * (self.m13 * self.m34 * self.m41 - self.m14 * self.m33 * self.m41 + self.m14 * self.m31 * self.m43\n          - self.m11 * self.m34 * self.m43\n          - self.m13 * self.m31 * self.m44\n          + self.m11 * self.m33 * self.m44),\n      m23: det\n        * (self.m14 * self.m23 * self.m41 - self.m13 * self.m24 * self.m41 - self.m14 * self.m21 * self.m43\n          + self.m11 * self.m24 * self.m43\n          + self.m13 * self.m21 * self.m44\n          - self.m11 * self.m23 * self.m44),\n      m24: det\n        * (self.m13 * self.m24 * self.m31 - self.m14 * self.m23 * self.m31 + self.m14 * self.m21 * self.m33\n          - self.m11 * self.m24 * self.m33\n          - self.m13 * self.m21 * self.m34\n          + self.m11 * self.m23 * self.m34),\n      m31: det\n        * (self.m22 * self.m34 * self.m41 - self.m24 * self.m32 * self.m41 + self.m24 * self.m31 * self.m42\n          - self.m21 * self.m34 * self.m42\n          - self.m22 * self.m31 * self.m44\n          + self.m21 * self.m32 * self.m44),\n      m32: det\n        * (self.m14 * self.m32 * self.m41 - self.m12 * self.m34 * self.m41 - self.m14 * self.m31 * self.m42\n          + self.m11 * self.m34 * self.m42\n          + self.m12 * self.m31 * self.m44\n          - self.m11 * self.m32 * self.m44),\n      m33: det\n        * (self.m12 * self.m24 * self.m41 - self.m14 * self.m22 * self.m41 + self.m14 * self.m21 * self.m42\n          - self.m11 * self.m24 * self.m42\n          - self.m12 * self.m21 * self.m44\n          + self.m11 * self.m22 * self.m44),\n      m34: det\n        * (self.m14 * self.m22 * self.m31 - self.m12 * self.m24 * self.m31 - self.m14 * self.m21 * self.m32\n          + self.m11 * self.m24 * self.m32\n          + self.m12 * self.m21 * self.m34\n          - self.m11 * self.m22 * self.m34),\n      m41: det\n        * (self.m23 * self.m32 * self.m41 - self.m22 * self.m33 * self.m41 - self.m23 * self.m31 * self.m42\n          + self.m21 * self.m33 * self.m42\n          + self.m22 * self.m31 * self.m43\n          - self.m21 * self.m32 * self.m43),\n      m42: det\n        * (self.m12 * self.m33 * self.m41 - self.m13 * self.m32 * self.m41 + self.m13 * self.m31 * self.m42\n          - self.m11 * self.m33 * self.m42\n          - self.m12 * self.m31 * self.m43\n          + self.m11 * self.m32 * self.m43),\n      m43: det\n        * (self.m13 * self.m22 * self.m41 - self.m12 * self.m23 * self.m41 - self.m13 * self.m21 * self.m42\n          + self.m11 * self.m23 * self.m42\n          + self.m12 * self.m21 * self.m43\n          - self.m11 * self.m22 * self.m43),\n      m44: det\n        * (self.m12 * self.m23 * self.m31 - self.m13 * self.m22 * self.m31 + self.m13 * self.m21 * self.m32\n          - self.m11 * self.m23 * self.m32\n          - self.m12 * self.m21 * self.m33\n          + self.m11 * self.m22 * self.m33),\n    })\n  }\n\n  /// Transposes the matrix.\n  pub fn transpose(&self) -> Self {\n    Self {\n      m11: self.m11,\n      m12: self.m21,\n      m13: self.m31,\n      m14: self.m41,\n      m21: self.m12,\n      m22: self.m22,\n      m23: self.m32,\n      m24: self.m42,\n      m31: self.m13,\n      m32: self.m23,\n      m33: self.m33,\n      m34: self.m43,\n      m41: self.m14,\n      m42: self.m24,\n      m43: self.m34,\n      m44: self.m44,\n    }\n  }\n\n  /// Multiplies a vector by the matrix.\n  pub fn multiply_vector(&self, pin: &[f32; 4]) -> [f32; 4] {\n    [\n      pin[0] * self.m11 + pin[1] * self.m21 + pin[2] * self.m31 + pin[3] * self.m41,\n      pin[0] * self.m12 + pin[1] * self.m22 + pin[2] * self.m32 + pin[3] * self.m42,\n      pin[0] * self.m13 + pin[1] * self.m23 + pin[2] * self.m33 + pin[3] * self.m43,\n      pin[0] * self.m14 + pin[1] * self.m24 + pin[2] * self.m34 + pin[3] * self.m44,\n    ]\n  }\n\n  /// Decomposes the matrix into a list of transform functions if possible.\n  pub fn decompose(&self) -> Option<TransformList> {\n    // https://drafts.csswg.org/css-transforms-2/#decomposing-a-3d-matrix\n    // Combine 2 point.\n    let combine = |a: [f32; 3], b: [f32; 3], ascl: f32, bscl: f32| {\n      [\n        (ascl * a[0]) + (bscl * b[0]),\n        (ascl * a[1]) + (bscl * b[1]),\n        (ascl * a[2]) + (bscl * b[2]),\n      ]\n    };\n\n    // Dot product.\n    let dot = |a: [f32; 3], b: [f32; 3]| a[0] * b[0] + a[1] * b[1] + a[2] * b[2];\n\n    // Cross product.\n    let cross = |row1: [f32; 3], row2: [f32; 3]| {\n      [\n        row1[1] * row2[2] - row1[2] * row2[1],\n        row1[2] * row2[0] - row1[0] * row2[2],\n        row1[0] * row2[1] - row1[1] * row2[0],\n      ]\n    };\n\n    if self.m44 == 0.0 {\n      return None;\n    }\n\n    let scaling_factor = self.m44;\n\n    // Normalize the matrix.\n    let mut matrix = self.clone();\n    matrix.scale_by_factor(1.0 / scaling_factor);\n\n    // perspective_matrix is used to solve for perspective, but it also provides\n    // an easy way to test for singularity of the upper 3x3 component.\n    let mut perspective_matrix = matrix.clone();\n    perspective_matrix.m14 = 0.0;\n    perspective_matrix.m24 = 0.0;\n    perspective_matrix.m34 = 0.0;\n    perspective_matrix.m44 = 1.0;\n\n    if perspective_matrix.determinant() == 0.0 {\n      return None;\n    }\n\n    let mut transforms = vec![];\n\n    // First, isolate perspective.\n    if matrix.m14 != 0.0 || matrix.m24 != 0.0 || matrix.m34 != 0.0 {\n      let right_hand_side: [f32; 4] = [matrix.m14, matrix.m24, matrix.m34, matrix.m44];\n\n      perspective_matrix = perspective_matrix.inverse().unwrap().transpose();\n      let perspective = perspective_matrix.multiply_vector(&right_hand_side);\n      if perspective[0] == 0.0 && perspective[1] == 0.0 && perspective[3] == 0.0 {\n        transforms.push(Transform::Perspective(Length::px(-1.0 / perspective[2])))\n      } else {\n        return None;\n      }\n    }\n\n    // Next take care of translation (easy).\n    // let translate = Translate3D(matrix.m41, matrix.m42, matrix.m43);\n    if matrix.m41 != 0.0 || matrix.m42 != 0.0 || matrix.m43 != 0.0 {\n      transforms.push(Transform::Translate3d(\n        LengthPercentage::px(matrix.m41),\n        LengthPercentage::px(matrix.m42),\n        Length::px(matrix.m43),\n      ));\n    }\n\n    // Now get scale and shear. 'row' is a 3 element array of 3 component vectors\n    let mut row = [\n      [matrix.m11, matrix.m12, matrix.m13],\n      [matrix.m21, matrix.m22, matrix.m23],\n      [matrix.m31, matrix.m32, matrix.m33],\n    ];\n\n    // Compute X scale factor and normalize first row.\n    let row0len = (row[0][0] * row[0][0] + row[0][1] * row[0][1] + row[0][2] * row[0][2]).sqrt();\n    let mut scale_x = row0len;\n    row[0] = [row[0][0] / row0len, row[0][1] / row0len, row[0][2] / row0len];\n\n    // Compute XY shear factor and make 2nd row orthogonal to 1st.\n    let mut skew_x = dot(row[0], row[1]);\n    row[1] = combine(row[1], row[0], 1.0, -skew_x);\n\n    // Now, compute Y scale and normalize 2nd row.\n    let row1len = (row[1][0] * row[1][0] + row[1][1] * row[1][1] + row[1][2] * row[1][2]).sqrt();\n    let mut scale_y = row1len;\n    row[1] = [row[1][0] / row1len, row[1][1] / row1len, row[1][2] / row1len];\n    skew_x /= scale_y;\n\n    // Compute XZ and YZ shears, orthogonalize 3rd row\n    let mut skew_y = dot(row[0], row[2]);\n    row[2] = combine(row[2], row[0], 1.0, -skew_y);\n    let mut skew_z = dot(row[1], row[2]);\n    row[2] = combine(row[2], row[1], 1.0, -skew_z);\n\n    // Next, get Z scale and normalize 3rd row.\n    let row2len = (row[2][0] * row[2][0] + row[2][1] * row[2][1] + row[2][2] * row[2][2]).sqrt();\n    let mut scale_z = row2len;\n    row[2] = [row[2][0] / row2len, row[2][1] / row2len, row[2][2] / row2len];\n    skew_y /= scale_z;\n    skew_z /= scale_z;\n\n    if skew_z != 0.0 {\n      return None; // ???\n    }\n\n    // Round to 5 digits of precision, which is what we print.\n    macro_rules! round {\n      ($var: ident) => {\n        $var = ($var * 100000.0).round() / 100000.0;\n      };\n    }\n\n    round!(skew_x);\n    round!(skew_y);\n    round!(skew_z);\n\n    if skew_x != 0.0 || skew_y != 0.0 || skew_z != 0.0 {\n      transforms.push(Transform::Skew(Angle::Rad(skew_x), Angle::Rad(skew_y)));\n    }\n\n    // At this point, the matrix (in rows) is orthonormal.\n    // Check for a coordinate system flip.  If the determinant\n    // is -1, then negate the matrix and the scaling factors.\n    if dot(row[0], cross(row[1], row[2])) < 0.0 {\n      scale_x = -scale_x;\n      scale_y = -scale_y;\n      scale_z = -scale_z;\n      for i in 0..3 {\n        row[i][0] *= -1.0;\n        row[i][1] *= -1.0;\n        row[i][2] *= -1.0;\n      }\n    }\n\n    round!(scale_x);\n    round!(scale_y);\n    round!(scale_z);\n\n    if scale_x != 1.0 || scale_y != 1.0 || scale_z != 1.0 {\n      transforms.push(Transform::Scale3d(\n        NumberOrPercentage::Number(scale_x),\n        NumberOrPercentage::Number(scale_y),\n        NumberOrPercentage::Number(scale_z),\n      ))\n    }\n\n    // Now, get the rotations out.\n    let mut rotate_x = 0.5 * ((1.0 + row[0][0] - row[1][1] - row[2][2]).max(0.0)).sqrt();\n    let mut rotate_y = 0.5 * ((1.0 - row[0][0] + row[1][1] - row[2][2]).max(0.0)).sqrt();\n    let mut rotate_z = 0.5 * ((1.0 - row[0][0] - row[1][1] + row[2][2]).max(0.0)).sqrt();\n    let rotate_w = 0.5 * ((1.0 + row[0][0] + row[1][1] + row[2][2]).max(0.0)).sqrt();\n\n    if row[2][1] > row[1][2] {\n      rotate_x = -rotate_x\n    }\n\n    if row[0][2] > row[2][0] {\n      rotate_y = -rotate_y\n    }\n\n    if row[1][0] > row[0][1] {\n      rotate_z = -rotate_z\n    }\n\n    let len = (rotate_x * rotate_x + rotate_y * rotate_y + rotate_z * rotate_z).sqrt();\n    if len != 0.0 {\n      rotate_x /= len;\n      rotate_y /= len;\n      rotate_z /= len;\n    }\n    let a = 2.0 * len.atan2(rotate_w);\n\n    // normalize the vector so one of the values is 1\n    let max = rotate_x.max(rotate_y).max(rotate_z);\n    rotate_x /= max;\n    rotate_y /= max;\n    rotate_z /= max;\n\n    if a != 0.0 {\n      transforms.push(Transform::Rotate3d(rotate_x, rotate_y, rotate_z, Angle::Rad(a)))\n    }\n\n    if transforms.is_empty() {\n      return None;\n    }\n\n    Some(TransformList(transforms))\n  }\n}\n\nimpl<'i> Parse<'i> for Transform {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let function = input.expect_function()?.clone();\n    input.parse_nested_block(|input| {\n      let location = input.current_source_location();\n      match_ignore_ascii_case! { &function,\n        \"matrix\" => {\n          let a = f32::parse(input)?;\n          input.expect_comma()?;\n          let b = f32::parse(input)?;\n          input.expect_comma()?;\n          let c = f32::parse(input)?;\n          input.expect_comma()?;\n          let d = f32::parse(input)?;\n          input.expect_comma()?;\n          let e = f32::parse(input)?;\n          input.expect_comma()?;\n          let f = f32::parse(input)?;\n          Ok(Transform::Matrix(Matrix { a, b, c, d, e, f }))\n        },\n        \"matrix3d\" => {\n          let m11 = f32::parse(input)?;\n          input.expect_comma()?;\n          let m12 = f32::parse(input)?;\n          input.expect_comma()?;\n          let m13 = f32::parse(input)?;\n          input.expect_comma()?;\n          let m14 = f32::parse(input)?;\n          input.expect_comma()?;\n          let m21 = f32::parse(input)?;\n          input.expect_comma()?;\n          let m22 = f32::parse(input)?;\n          input.expect_comma()?;\n          let m23 = f32::parse(input)?;\n          input.expect_comma()?;\n          let m24 = f32::parse(input)?;\n          input.expect_comma()?;\n          let m31 = f32::parse(input)?;\n          input.expect_comma()?;\n          let m32 = f32::parse(input)?;\n          input.expect_comma()?;\n          let m33 = f32::parse(input)?;\n          input.expect_comma()?;\n          let m34 = f32::parse(input)?;\n          input.expect_comma()?;\n          let m41 = f32::parse(input)?;\n          input.expect_comma()?;\n          let m42 = f32::parse(input)?;\n          input.expect_comma()?;\n          let m43 = f32::parse(input)?;\n          input.expect_comma()?;\n          let m44 = f32::parse(input)?;\n          Ok(Transform::Matrix3d(Matrix3d {\n            m11, m12, m13, m14,\n            m21, m22, m23, m24,\n            m31, m32, m33, m34,\n            m41, m42, m43, m44\n          }))\n        },\n        \"translate\" => {\n          let x = LengthPercentage::parse(input)?;\n          if input.try_parse(|input| input.expect_comma()).is_ok() {\n            let y = LengthPercentage::parse(input)?;\n            Ok(Transform::Translate(x, y))\n          } else {\n            Ok(Transform::Translate(x, LengthPercentage::zero()))\n          }\n        },\n        \"translatex\" => {\n          let x = LengthPercentage::parse(input)?;\n          Ok(Transform::TranslateX(x))\n        },\n        \"translatey\" => {\n          let y = LengthPercentage::parse(input)?;\n          Ok(Transform::TranslateY(y))\n        },\n        \"translatez\" => {\n          let z = Length::parse(input)?;\n          Ok(Transform::TranslateZ(z))\n        },\n        \"translate3d\" => {\n          let x = LengthPercentage::parse(input)?;\n          input.expect_comma()?;\n          let y = LengthPercentage::parse(input)?;\n          input.expect_comma()?;\n          let z = Length::parse(input)?;\n          Ok(Transform::Translate3d(x, y, z))\n        },\n        \"scale\" => {\n          let x = convert_percentage_to_number(input)?;\n          if input.try_parse(|input| input.expect_comma()).is_ok() {\n            let y = convert_percentage_to_number(input)?;\n            Ok(Transform::Scale(x, y))\n          } else {\n            Ok(Transform::Scale(x.clone(), x))\n          }\n        },\n        \"scalex\" => {\n          let x = convert_percentage_to_number(input)?;\n          Ok(Transform::ScaleX(x))\n        },\n        \"scaley\" => {\n          let y = convert_percentage_to_number(input)?;\n          Ok(Transform::ScaleY(y))\n        },\n        \"scalez\" => {\n          let z = convert_percentage_to_number(input)?;\n          Ok(Transform::ScaleZ(z))\n        },\n        \"scale3d\" => {\n          let x = convert_percentage_to_number(input)?;\n          input.expect_comma()?;\n          let y = convert_percentage_to_number(input)?;\n          input.expect_comma()?;\n          let z = convert_percentage_to_number(input)?;\n          Ok(Transform::Scale3d(x, y, z))\n        },\n        \"rotate\" => {\n          let angle = Angle::parse_with_unitless_zero(input)?;\n          Ok(Transform::Rotate(angle))\n        },\n        \"rotatex\" => {\n          let angle = Angle::parse_with_unitless_zero(input)?;\n          Ok(Transform::RotateX(angle))\n        },\n        \"rotatey\" => {\n          let angle = Angle::parse_with_unitless_zero(input)?;\n          Ok(Transform::RotateY(angle))\n        },\n        \"rotatez\" => {\n          let angle = Angle::parse_with_unitless_zero(input)?;\n          Ok(Transform::RotateZ(angle))\n        },\n        \"rotate3d\" => {\n          let x = f32::parse(input)?;\n          input.expect_comma()?;\n          let y = f32::parse(input)?;\n          input.expect_comma()?;\n          let z = f32::parse(input)?;\n          input.expect_comma()?;\n          let angle = Angle::parse_with_unitless_zero(input)?;\n          Ok(Transform::Rotate3d(x, y, z, angle))\n        },\n        \"skew\" => {\n          let x = Angle::parse_with_unitless_zero(input)?;\n          if input.try_parse(|input| input.expect_comma()).is_ok() {\n            let y = Angle::parse_with_unitless_zero(input)?;\n            Ok(Transform::Skew(x, y))\n          } else {\n            Ok(Transform::Skew(x, Angle::Deg(0.0)))\n          }\n        },\n        \"skewx\" => {\n          let angle = Angle::parse_with_unitless_zero(input)?;\n          Ok(Transform::SkewX(angle))\n        },\n        \"skewy\" => {\n          let angle = Angle::parse_with_unitless_zero(input)?;\n          Ok(Transform::SkewY(angle))\n        },\n        \"perspective\" => {\n          let len = Length::parse(input)?;\n          Ok(Transform::Perspective(len))\n        },\n        _ => Err(location.new_unexpected_token_error(\n          cssparser::Token::Ident(function.clone())\n        ))\n      }\n    })\n  }\n}\n\nimpl ToCss for Transform {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    use Transform::*;\n    match self {\n      Translate(x, y) => {\n        if dest.minify && x.is_zero() && !y.is_zero() {\n          dest.write_str(\"translateY(\")?;\n          y.to_css(dest)?\n        } else {\n          dest.write_str(\"translate(\")?;\n          x.to_css(dest)?;\n          if !y.is_zero() {\n            dest.delim(',', false)?;\n            y.to_css(dest)?;\n          }\n        }\n        dest.write_char(')')\n      }\n      TranslateX(x) => {\n        dest.write_str(if dest.minify { \"translate(\" } else { \"translateX(\" })?;\n        x.to_css(dest)?;\n        dest.write_char(')')\n      }\n      TranslateY(y) => {\n        dest.write_str(\"translateY(\")?;\n        y.to_css(dest)?;\n        dest.write_char(')')\n      }\n      TranslateZ(z) => {\n        dest.write_str(\"translateZ(\")?;\n        z.to_css(dest)?;\n        dest.write_char(')')\n      }\n      Translate3d(x, y, z) => {\n        if dest.minify && !x.is_zero() && y.is_zero() && z.is_zero() {\n          dest.write_str(\"translate(\")?;\n          x.to_css(dest)?;\n        } else if dest.minify && x.is_zero() && !y.is_zero() && z.is_zero() {\n          dest.write_str(\"translateY(\")?;\n          y.to_css(dest)?;\n        } else if dest.minify && x.is_zero() && y.is_zero() && !z.is_zero() {\n          dest.write_str(\"translateZ(\")?;\n          z.to_css(dest)?;\n        } else if dest.minify && z.is_zero() {\n          dest.write_str(\"translate(\")?;\n          x.to_css(dest)?;\n          dest.delim(',', false)?;\n          y.to_css(dest)?;\n        } else {\n          dest.write_str(\"translate3d(\")?;\n          x.to_css(dest)?;\n          dest.delim(',', false)?;\n          y.to_css(dest)?;\n          dest.delim(',', false)?;\n          z.to_css(dest)?;\n        }\n        dest.write_char(')')\n      }\n      Scale(x, y) => {\n        let x: f32 = x.into();\n        let y: f32 = y.into();\n        if dest.minify && x == 1.0 && y != 1.0 {\n          dest.write_str(\"scaleY(\")?;\n          y.to_css(dest)?;\n        } else if dest.minify && x != 1.0 && y == 1.0 {\n          dest.write_str(\"scaleX(\")?;\n          x.to_css(dest)?;\n        } else {\n          dest.write_str(\"scale(\")?;\n          x.to_css(dest)?;\n          if y != x {\n            dest.delim(',', false)?;\n            y.to_css(dest)?;\n          }\n        }\n        dest.write_char(')')\n      }\n      ScaleX(x) => {\n        let x: f32 = x.into();\n        dest.write_str(\"scaleX(\")?;\n        x.to_css(dest)?;\n        dest.write_char(')')\n      }\n      ScaleY(y) => {\n        let y: f32 = y.into();\n        dest.write_str(\"scaleY(\")?;\n        y.to_css(dest)?;\n        dest.write_char(')')\n      }\n      ScaleZ(z) => {\n        let z: f32 = z.into();\n        dest.write_str(\"scaleZ(\")?;\n        z.to_css(dest)?;\n        dest.write_char(')')\n      }\n      Scale3d(x, y, z) => {\n        let x: f32 = x.into();\n        let y: f32 = y.into();\n        let z: f32 = z.into();\n        if dest.minify && z == 1.0 && x == y {\n          // scale3d(x, x, 1) => scale(x)\n          dest.write_str(\"scale(\")?;\n          x.to_css(dest)?;\n        } else if dest.minify && x != 1.0 && y == 1.0 && z == 1.0 {\n          // scale3d(x, 1, 1) => scaleX(x)\n          dest.write_str(\"scaleX(\")?;\n          x.to_css(dest)?;\n        } else if dest.minify && x == 1.0 && y != 1.0 && z == 1.0 {\n          // scale3d(1, y, 1) => scaleY(y)\n          dest.write_str(\"scaleY(\")?;\n          y.to_css(dest)?;\n        } else if dest.minify && x == 1.0 && y == 1.0 && z != 1.0 {\n          // scale3d(1, 1, z) => scaleZ(z)\n          dest.write_str(\"scaleZ(\")?;\n          z.to_css(dest)?;\n        } else if dest.minify && z == 1.0 {\n          // scale3d(x, y, 1) => scale(x, y)\n          dest.write_str(\"scale(\")?;\n          x.to_css(dest)?;\n          dest.delim(',', false)?;\n          y.to_css(dest)?;\n        } else {\n          dest.write_str(\"scale3d(\")?;\n          x.to_css(dest)?;\n          dest.delim(',', false)?;\n          y.to_css(dest)?;\n          dest.delim(',', false)?;\n          z.to_css(dest)?;\n        }\n        dest.write_char(')')\n      }\n      Rotate(angle) => {\n        dest.write_str(\"rotate(\")?;\n        angle.to_css_with_unitless_zero(dest)?;\n        dest.write_char(')')\n      }\n      RotateX(angle) => {\n        dest.write_str(\"rotateX(\")?;\n        angle.to_css_with_unitless_zero(dest)?;\n        dest.write_char(')')\n      }\n      RotateY(angle) => {\n        dest.write_str(\"rotateY(\")?;\n        angle.to_css_with_unitless_zero(dest)?;\n        dest.write_char(')')\n      }\n      RotateZ(angle) => {\n        dest.write_str(if dest.minify { \"rotate(\" } else { \"rotateZ(\" })?;\n        angle.to_css_with_unitless_zero(dest)?;\n        dest.write_char(')')\n      }\n      Rotate3d(x, y, z, angle) => {\n        if dest.minify && *x == 1.0 && *y == 0.0 && *z == 0.0 {\n          // rotate3d(1, 0, 0, a) => rotateX(a)\n          dest.write_str(\"rotateX(\")?;\n          angle.to_css_with_unitless_zero(dest)?;\n        } else if dest.minify && *x == 0.0 && *y == 1.0 && *z == 0.0 {\n          // rotate3d(0, 1, 0, a) => rotateY(a)\n          dest.write_str(\"rotateY(\")?;\n          angle.to_css_with_unitless_zero(dest)?;\n        } else if dest.minify && *x == 0.0 && *y == 0.0 && *z == 1.0 {\n          // rotate3d(0, 0, 1, a) => rotate(a)\n          dest.write_str(\"rotate(\")?;\n          angle.to_css_with_unitless_zero(dest)?;\n        } else {\n          dest.write_str(\"rotate3d(\")?;\n          x.to_css(dest)?;\n          dest.delim(',', false)?;\n          y.to_css(dest)?;\n          dest.delim(',', false)?;\n          z.to_css(dest)?;\n          dest.delim(',', false)?;\n          angle.to_css_with_unitless_zero(dest)?;\n        }\n        dest.write_char(')')\n      }\n      Skew(x, y) => {\n        if dest.minify && x.is_zero() && !y.is_zero() {\n          dest.write_str(\"skewY(\")?;\n          y.to_css_with_unitless_zero(dest)?\n        } else {\n          dest.write_str(\"skew(\")?;\n          x.to_css(dest)?;\n          if !y.is_zero() {\n            dest.delim(',', false)?;\n            y.to_css_with_unitless_zero(dest)?;\n          }\n        }\n        dest.write_char(')')\n      }\n      SkewX(angle) => {\n        dest.write_str(if dest.minify { \"skew(\" } else { \"skewX(\" })?;\n        angle.to_css_with_unitless_zero(dest)?;\n        dest.write_char(')')\n      }\n      SkewY(angle) => {\n        dest.write_str(\"skewY(\")?;\n        angle.to_css_with_unitless_zero(dest)?;\n        dest.write_char(')')\n      }\n      Perspective(len) => {\n        dest.write_str(\"perspective(\")?;\n        len.to_css(dest)?;\n        dest.write_char(')')\n      }\n      Matrix(super::transform::Matrix { a, b, c, d, e, f }) => {\n        dest.write_str(\"matrix(\")?;\n        a.to_css(dest)?;\n        dest.delim(',', false)?;\n        b.to_css(dest)?;\n        dest.delim(',', false)?;\n        c.to_css(dest)?;\n        dest.delim(',', false)?;\n        d.to_css(dest)?;\n        dest.delim(',', false)?;\n        e.to_css(dest)?;\n        dest.delim(',', false)?;\n        f.to_css(dest)?;\n        dest.write_char(')')\n      }\n      Matrix3d(super::transform::Matrix3d {\n        m11,\n        m12,\n        m13,\n        m14,\n        m21,\n        m22,\n        m23,\n        m24,\n        m31,\n        m32,\n        m33,\n        m34,\n        m41,\n        m42,\n        m43,\n        m44,\n      }) => {\n        dest.write_str(\"matrix3d(\")?;\n        m11.to_css(dest)?;\n        dest.delim(',', false)?;\n        m12.to_css(dest)?;\n        dest.delim(',', false)?;\n        m13.to_css(dest)?;\n        dest.delim(',', false)?;\n        m14.to_css(dest)?;\n        dest.delim(',', false)?;\n        m21.to_css(dest)?;\n        dest.delim(',', false)?;\n        m22.to_css(dest)?;\n        dest.delim(',', false)?;\n        m23.to_css(dest)?;\n        dest.delim(',', false)?;\n        m24.to_css(dest)?;\n        dest.delim(',', false)?;\n        m31.to_css(dest)?;\n        dest.delim(',', false)?;\n        m32.to_css(dest)?;\n        dest.delim(',', false)?;\n        m33.to_css(dest)?;\n        dest.delim(',', false)?;\n        m34.to_css(dest)?;\n        dest.delim(',', false)?;\n        m41.to_css(dest)?;\n        dest.delim(',', false)?;\n        m42.to_css(dest)?;\n        dest.delim(',', false)?;\n        m43.to_css(dest)?;\n        dest.delim(',', false)?;\n        m44.to_css(dest)?;\n        dest.write_char(')')\n      }\n    }\n  }\n}\n\nimpl Transform {\n  /// Converts the transform to a 3D matrix.\n  pub fn to_matrix(&self) -> Option<Matrix3d<f32>> {\n    macro_rules! to_radians {\n      ($angle: ident) => {{\n        // If the angle is negative or more than a full circle, we cannot\n        // safely convert to a matrix. Transforms are interpolated numerically\n        // when types match, and this will have different results than\n        // when interpolating matrices with large angles.\n        // https://www.w3.org/TR/css-transforms-1/#matrix-interpolation\n        let rad = $angle.to_radians();\n        if rad < 0.0 || rad >= 2.0 * PI {\n          return None;\n        }\n\n        rad\n      }};\n    }\n\n    match &self {\n      Transform::Translate(LengthPercentage::Dimension(x), LengthPercentage::Dimension(y)) => {\n        if let (Some(x), Some(y)) = (x.to_px(), y.to_px()) {\n          return Some(Matrix3d::translate(x, y, 0.0));\n        }\n      }\n      Transform::TranslateX(LengthPercentage::Dimension(x)) => {\n        if let Some(x) = x.to_px() {\n          return Some(Matrix3d::translate(x, 0.0, 0.0));\n        }\n      }\n      Transform::TranslateY(LengthPercentage::Dimension(y)) => {\n        if let Some(y) = y.to_px() {\n          return Some(Matrix3d::translate(0.0, y, 0.0));\n        }\n      }\n      Transform::TranslateZ(z) => {\n        if let Some(z) = z.to_px() {\n          return Some(Matrix3d::translate(0.0, 0.0, z));\n        }\n      }\n      Transform::Translate3d(LengthPercentage::Dimension(x), LengthPercentage::Dimension(y), z) => {\n        if let (Some(x), Some(y), Some(z)) = (x.to_px(), y.to_px(), z.to_px()) {\n          return Some(Matrix3d::translate(x, y, z));\n        }\n      }\n      Transform::Scale(x, y) => return Some(Matrix3d::scale(x.into(), y.into(), 1.0)),\n      Transform::ScaleX(x) => return Some(Matrix3d::scale(x.into(), 1.0, 1.0)),\n      Transform::ScaleY(y) => return Some(Matrix3d::scale(1.0, y.into(), 1.0)),\n      Transform::ScaleZ(z) => return Some(Matrix3d::scale(1.0, 1.0, z.into())),\n      Transform::Scale3d(x, y, z) => return Some(Matrix3d::scale(x.into(), y.into(), z.into())),\n      Transform::Rotate(angle) | Transform::RotateZ(angle) => {\n        return Some(Matrix3d::rotate(0.0, 0.0, 1.0, to_radians!(angle)))\n      }\n      Transform::RotateX(angle) => return Some(Matrix3d::rotate(1.0, 0.0, 0.0, to_radians!(angle))),\n      Transform::RotateY(angle) => return Some(Matrix3d::rotate(0.0, 1.0, 0.0, to_radians!(angle))),\n      Transform::Rotate3d(x, y, z, angle) => return Some(Matrix3d::rotate(*x, *y, *z, to_radians!(angle))),\n      Transform::Skew(x, y) => return Some(Matrix3d::skew(to_radians!(x), to_radians!(y))),\n      Transform::SkewX(x) => return Some(Matrix3d::skew(to_radians!(x), 0.0)),\n      Transform::SkewY(y) => return Some(Matrix3d::skew(0.0, to_radians!(y))),\n      Transform::Perspective(len) => {\n        if let Some(len) = len.to_px() {\n          return Some(Matrix3d::perspective(len));\n        }\n      }\n      Transform::Matrix(m) => return Some(m.to_matrix3d()),\n      Transform::Matrix3d(m) => return Some(m.clone()),\n      _ => {}\n    }\n    None\n  }\n}\n\nenum_property! {\n  /// A value for the [transform-style](https://drafts.csswg.org/css-transforms-2/#transform-style-property) property.\n  #[allow(missing_docs)]\n  pub enum TransformStyle {\n    Flat,\n    Preserve3d,\n  }\n}\n\nenum_property! {\n  /// A value for the [transform-box](https://drafts.csswg.org/css-transforms-1/#transform-box) property.\n  pub enum TransformBox {\n    /// Uses the content box as reference box.\n    ContentBox,\n    /// Uses the border box as reference box.\n    BorderBox,\n    /// Uses the object bounding box as reference box.\n    FillBox,\n    /// Uses the stroke bounding box as reference box.\n    StrokeBox,\n    /// Uses the nearest SVG viewport as reference box.\n    ViewBox,\n  }\n}\n\nenum_property! {\n  /// A value for the [backface-visibility](https://drafts.csswg.org/css-transforms-2/#backface-visibility-property) property.\n  #[allow(missing_docs)]\n  pub enum BackfaceVisibility {\n    Visible,\n    Hidden,\n  }\n}\n\n/// A value for the [perspective](https://drafts.csswg.org/css-transforms-2/#perspective-property) property.\n#[derive(Debug, Clone, PartialEq, Parse, ToCss)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum Perspective {\n  /// No perspective transform is applied.\n  None,\n  /// Distance to the center of projection.\n  Length(Length),\n}\n\n/// A value for the [translate](https://drafts.csswg.org/css-transforms-2/#propdef-translate) property.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"lowercase\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum Translate {\n  /// The \"none\" keyword.\n  None,\n\n  /// The x, y, and z translations.\n  #[cfg_attr(feature = \"serde\", serde(untagged))]\n  XYZ {\n    /// The x translation.\n    x: LengthPercentage,\n    /// The y translation.\n    y: LengthPercentage,\n    /// The z translation.\n    z: Length,\n  },\n}\n\nimpl<'i> Parse<'i> for Translate {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    if input.try_parse(|i| i.expect_ident_matching(\"none\")).is_ok() {\n      return Ok(Translate::None);\n    }\n\n    let x = LengthPercentage::parse(input)?;\n    let y = input.try_parse(LengthPercentage::parse);\n    let z = if y.is_ok() {\n      input.try_parse(Length::parse).ok()\n    } else {\n      None\n    };\n\n    Ok(Translate::XYZ {\n      x,\n      y: y.unwrap_or(LengthPercentage::zero()),\n      z: z.unwrap_or(Length::zero()),\n    })\n  }\n}\n\nimpl ToCss for Translate {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      Translate::None => {\n        dest.write_str(\"none\")?;\n      }\n      Translate::XYZ { x, y, z } => {\n        x.to_css(dest)?;\n        if !y.is_zero() || !z.is_zero() {\n          dest.write_char(' ')?;\n          y.to_css(dest)?;\n          if !z.is_zero() {\n            dest.write_char(' ')?;\n            z.to_css(dest)?;\n          }\n        }\n      }\n    };\n\n    Ok(())\n  }\n}\n\nimpl Translate {\n  /// Converts the translation to a transform function.\n  pub fn to_transform(&self) -> Transform {\n    match self {\n      Translate::None => {\n        Transform::Translate3d(LengthPercentage::zero(), LengthPercentage::zero(), Length::zero())\n      }\n      Translate::XYZ { x, y, z } => Transform::Translate3d(x.clone(), y.clone(), z.clone()),\n    }\n  }\n}\n\n/// A value for the [rotate](https://drafts.csswg.org/css-transforms-2/#propdef-rotate) property.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"lowercase\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum Rotate {\n  /// The `none` keyword.\n  None,\n\n  /// Rotation on the x, y, and z axes.\n  #[cfg_attr(feature = \"serde\", serde(untagged))]\n  XYZ {\n    /// Rotation around the x axis.\n    x: f32,\n    /// Rotation around the y axis.\n    y: f32,\n    /// Rotation around the z axis.\n    z: f32,\n    /// The angle of rotation.\n    angle: Angle,\n  },\n}\n\nimpl<'i> Parse<'i> for Rotate {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    // CSS Transforms 2 §5.1:\n    // \"It must serialize as the keyword none if and only if none was originally specified.\"\n    // Keep `none` explicit so identity rotations (e.g. `0deg`) do not round-trip to `none`.\n    // https://drafts.csswg.org/css-transforms-2/#individual-transforms\n    if input.try_parse(|i| i.expect_ident_matching(\"none\")).is_ok() {\n      return Ok(Rotate::None);\n    }\n\n    let angle = input.try_parse(Angle::parse);\n    let (x, y, z) = input\n      .try_parse(|input| {\n        let location = input.current_source_location();\n        let ident = input.expect_ident()?;\n        match_ignore_ascii_case! { &*ident,\n          \"x\" => Ok((1.0, 0.0, 0.0)),\n          \"y\" => Ok((0.0, 1.0, 0.0)),\n          \"z\" => Ok((0.0, 0.0, 1.0)),\n          _ => Err(location.new_unexpected_token_error(\n            cssparser::Token::Ident(ident.clone())\n          ))\n        }\n      })\n      .or_else(\n        |_: ParseError<'i, ParserError<'i>>| -> Result<_, ParseError<'i, ParserError<'i>>> {\n          input.try_parse(|input| Ok((f32::parse(input)?, f32::parse(input)?, f32::parse(input)?)))\n        },\n      )\n      .unwrap_or((0.0, 0.0, 1.0));\n    let angle = angle.or_else(|_| Angle::parse(input))?;\n    Ok(Rotate::XYZ { x, y, z, angle })\n  }\n}\n\nimpl ToCss for Rotate {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      Rotate::None => dest.write_str(\"none\"),\n      Rotate::XYZ { x, y, z, angle } => {\n        // CSS Transforms 2 §5.1:\n        // \"If the axis is parallel with the x or y axes, it must serialize as the appropriate keyword.\"\n        // \"If a rotation about the z axis ... must serialize as just an <angle>.\"\n        // Normalize parallel vectors (including non-unit vectors); flip the angle for negative axis directions.\n        // https://drafts.csswg.org/css-transforms-2/#individual-transforms\n        if *y == 0.0 && *z == 0.0 && *x != 0.0 {\n          let angle = if *x < 0.0 { angle.clone() * -1.0 } else { angle.clone() };\n          dest.write_str(\"x \")?;\n          angle.to_css(dest)\n        } else if *x == 0.0 && *z == 0.0 && *y != 0.0 {\n          let angle = if *y < 0.0 { angle.clone() * -1.0 } else { angle.clone() };\n          dest.write_str(\"y \")?;\n          angle.to_css(dest)\n        } else if *x == 0.0 && *y == 0.0 && *z != 0.0 {\n          let angle = if *z < 0.0 { angle.clone() * -1.0 } else { angle.clone() };\n          angle.to_css(dest)\n        } else {\n          x.to_css(dest)?;\n          dest.write_char(' ')?;\n          y.to_css(dest)?;\n          dest.write_char(' ')?;\n          z.to_css(dest)?;\n          dest.write_char(' ')?;\n          angle.to_css(dest)\n        }\n      }\n    }\n  }\n}\n\nimpl Rotate {\n  /// Converts the rotation to a transform function.\n  pub fn to_transform(&self) -> Transform {\n    match self {\n      Rotate::None => Transform::Rotate3d(0.0, 0.0, 1.0, Angle::Deg(0.0)),\n      Rotate::XYZ { x, y, z, angle } => Transform::Rotate3d(*x, *y, *z, angle.clone()),\n    }\n  }\n}\n\n/// A value for the [scale](https://drafts.csswg.org/css-transforms-2/#propdef-scale) property.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"lowercase\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum Scale {\n  /// The \"none\" keyword.\n  None,\n\n  /// Scale on the x, y, and z axis.\n  #[cfg_attr(feature = \"serde\", serde(untagged))]\n  XYZ {\n    /// Scale on the x axis.\n    x: NumberOrPercentage,\n    /// Scale on the y axis.\n    y: NumberOrPercentage,\n    /// Scale on the z axis.\n    z: NumberOrPercentage,\n  },\n}\n\nfn convert_percentage_to_number<'i, 't>(\n  input: &mut Parser<'i, 't>,\n) -> Result<NumberOrPercentage, ParseError<'i, ParserError<'i>>> {\n  Ok(match NumberOrPercentage::parse(input)? {\n    NumberOrPercentage::Number(number) => NumberOrPercentage::Number(number),\n    NumberOrPercentage::Percentage(percent) => NumberOrPercentage::Number(percent.0),\n  })\n}\n\nimpl<'i> Parse<'i> for Scale {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    if input.try_parse(|i| i.expect_ident_matching(\"none\")).is_ok() {\n      return Ok(Scale::None);\n    }\n\n    let x = convert_percentage_to_number(input)?;\n    let y = input.try_parse(convert_percentage_to_number);\n    let z = if y.is_ok() {\n      input.try_parse(convert_percentage_to_number).ok()\n    } else {\n      None\n    };\n\n    Ok(Scale::XYZ {\n      x: x.clone(),\n      y: y.unwrap_or(x),\n      z: z.unwrap_or(NumberOrPercentage::Number(1.0)),\n    })\n  }\n}\n\nimpl ToCss for Scale {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      Scale::None => {\n        dest.write_str(\"none\")?;\n      }\n      Scale::XYZ { x, y, z } => {\n        let x: f32 = x.into();\n        let y: f32 = y.into();\n        let z: f32 = z.into();\n        x.to_css(dest)?;\n        if y != x || z != 1.0 {\n          dest.write_char(' ')?;\n          y.to_css(dest)?;\n          if z != 1.0 {\n            dest.write_char(' ')?;\n            z.to_css(dest)?;\n          }\n        }\n      }\n    }\n\n    Ok(())\n  }\n}\n\nimpl Scale {\n  /// Converts the scale to a transform function.\n  pub fn to_transform(&self) -> Transform {\n    match self {\n      Scale::None => Transform::Scale3d(\n        NumberOrPercentage::Number(1.0),\n        NumberOrPercentage::Number(1.0),\n        NumberOrPercentage::Number(1.0),\n      ),\n      Scale::XYZ { x, y, z } => Transform::Scale3d(x.clone(), y.clone(), z.clone()),\n    }\n  }\n}\n\n#[derive(Default)]\npub(crate) struct TransformHandler {\n  transform: Option<(TransformList, VendorPrefix)>,\n  translate: Option<Translate>,\n  rotate: Option<Rotate>,\n  scale: Option<Scale>,\n  has_any: bool,\n}\n\nimpl<'i> PropertyHandler<'i> for TransformHandler {\n  fn handle_property(\n    &mut self,\n    property: &Property<'i>,\n    dest: &mut DeclarationList<'i>,\n    context: &mut PropertyHandlerContext<'i, '_>,\n  ) -> bool {\n    use Property::*;\n\n    macro_rules! individual_property {\n      ($prop: ident, $val: ident) => {\n        if let Some((transform, _)) = &mut self.transform {\n          transform.0.push($val.to_transform())\n        } else {\n          self.$prop = Some($val.clone());\n          self.has_any = true;\n        }\n      };\n    }\n\n    match property {\n      Transform(val, vp) => {\n        // If two vendor prefixes for the same property have different\n        // values, we need to flush what we have immediately to preserve order.\n        if let Some((cur, prefixes)) = &self.transform {\n          if cur != val && !prefixes.contains(*vp) {\n            self.flush(dest, context);\n          }\n        }\n\n        // Otherwise, update the value and add the prefix.\n        if let Some((transform, prefixes)) = &mut self.transform {\n          *transform = val.clone();\n          *prefixes |= *vp;\n        } else {\n          self.transform = Some((val.clone(), *vp));\n          self.has_any = true;\n        }\n\n        self.translate = None;\n        self.rotate = None;\n        self.scale = None;\n      }\n      Translate(val) => individual_property!(translate, val),\n      Rotate(val) => individual_property!(rotate, val),\n      Scale(val) => individual_property!(scale, val),\n      Unparsed(val)\n        if matches!(\n          val.property_id,\n          PropertyId::Transform(_) | PropertyId::Translate | PropertyId::Rotate | PropertyId::Scale\n        ) =>\n      {\n        self.flush(dest, context);\n        let prop = if matches!(val.property_id, PropertyId::Transform(_)) {\n          Property::Unparsed(val.get_prefixed(context.targets, Feature::Transform))\n        } else {\n          property.clone()\n        };\n        dest.push(prop)\n      }\n      _ => return false,\n    }\n\n    true\n  }\n\n  fn finalize(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {\n    self.flush(dest, context);\n  }\n}\n\nimpl TransformHandler {\n  fn flush<'i>(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {\n    if !self.has_any {\n      return;\n    }\n\n    self.has_any = false;\n\n    let transform = std::mem::take(&mut self.transform);\n    let translate = std::mem::take(&mut self.translate);\n    let rotate = std::mem::take(&mut self.rotate);\n    let scale = std::mem::take(&mut self.scale);\n\n    if let Some((transform, prefix)) = transform {\n      let prefix = context.targets.prefixes(prefix, Feature::Transform);\n      dest.push(Property::Transform(transform, prefix))\n    }\n\n    if let Some(translate) = translate {\n      dest.push(Property::Translate(translate))\n    }\n\n    if let Some(rotate) = rotate {\n      dest.push(Property::Rotate(rotate))\n    }\n\n    if let Some(scale) = scale {\n      dest.push(Property::Scale(scale))\n    }\n  }\n}\n"
  },
  {
    "path": "src/properties/transition.rs",
    "content": "//! CSS properties related to transitions.\n\nuse super::{Property, PropertyId};\nuse crate::compat;\nuse crate::context::PropertyHandlerContext;\nuse crate::declaration::{DeclarationBlock, DeclarationList};\nuse crate::error::{ParserError, PrinterError};\nuse crate::macros::define_list_shorthand;\nuse crate::prefixes::Feature;\nuse crate::printer::Printer;\nuse crate::properties::masking::get_webkit_mask_property;\nuse crate::traits::{Parse, PropertyHandler, Shorthand, ToCss, Zero};\nuse crate::values::ident::CustomIdent;\nuse crate::values::{easing::EasingFunction, time::Time};\nuse crate::vendor_prefix::VendorPrefix;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse cssparser::*;\nuse itertools::izip;\nuse smallvec::SmallVec;\n\ndefine_list_shorthand! {\n  /// A value for the [transition](https://www.w3.org/TR/2018/WD-css-transitions-1-20181011/#transition-shorthand-property) property.\n  pub struct Transition<'i>(VendorPrefix) {\n    /// The property to transition.\n    #[cfg_attr(feature = \"serde\", serde(borrow))]\n    property: TransitionProperty(PropertyId<'i>, VendorPrefix),\n    /// The duration of the transition.\n    duration: TransitionDuration(Time, VendorPrefix),\n    /// The delay before the transition starts.\n    delay: TransitionDelay(Time, VendorPrefix),\n    /// The easing function for the transition.\n    timing_function: TransitionTimingFunction(EasingFunction, VendorPrefix),\n  }\n}\n\nimpl<'i> Parse<'i> for Transition<'i> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let mut property = None;\n    let mut duration = None;\n    let mut delay = None;\n    let mut timing_function = None;\n\n    loop {\n      if duration.is_none() {\n        if let Ok(value) = input.try_parse(Time::parse) {\n          duration = Some(value);\n          continue;\n        }\n      }\n\n      if timing_function.is_none() {\n        if let Ok(value) = input.try_parse(EasingFunction::parse) {\n          timing_function = Some(value);\n          continue;\n        }\n      }\n\n      if delay.is_none() {\n        if let Ok(value) = input.try_parse(Time::parse) {\n          delay = Some(value);\n          continue;\n        }\n      }\n\n      if property.is_none() {\n        if let Ok(value) = input.try_parse(PropertyId::parse) {\n          property = Some(value);\n          continue;\n        }\n      }\n\n      break;\n    }\n\n    Ok(Transition {\n      property: property.unwrap_or(PropertyId::All),\n      duration: duration.unwrap_or(Time::Seconds(0.0)),\n      delay: delay.unwrap_or(Time::Seconds(0.0)),\n      timing_function: timing_function.unwrap_or(EasingFunction::Ease),\n    })\n  }\n}\n\nimpl<'i> ToCss for Transition<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    self.property.to_css(dest)?;\n    if !self.duration.is_zero() || !self.delay.is_zero() {\n      dest.write_char(' ')?;\n      self.duration.to_css(dest)?;\n    }\n\n    if !self.timing_function.is_ease() {\n      dest.write_char(' ')?;\n      self.timing_function.to_css(dest)?;\n    }\n\n    if !self.delay.is_zero() {\n      dest.write_char(' ')?;\n      self.delay.to_css(dest)?;\n    }\n\n    Ok(())\n  }\n}\n\n/// A value for the [view-transition-name](https://drafts.csswg.org/css-view-transitions-1/#view-transition-name-prop) property.\n#[derive(Debug, Clone, PartialEq, Default, Parse, ToCss)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum ViewTransitionName<'i> {\n  /// The element will not participate independently in a view transition.\n  #[default]\n  None,\n  /// The `auto` keyword.\n  Auto,\n  /// A custom name.\n  #[cfg_attr(feature = \"serde\", serde(borrow, untagged))]\n  Custom(CustomIdent<'i>),\n}\n\n/// A value for the [view-transition-group](https://drafts.csswg.org/css-view-transitions-2/#view-transition-group-prop) property.\n#[derive(Debug, Clone, PartialEq, Default, Parse, ToCss)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum ViewTransitionGroup<'i> {\n  /// The `normal` keyword.\n  #[default]\n  Normal,\n  /// The `contain` keyword.\n  Contain,\n  /// The `nearest` keyword.\n  Nearest,\n  /// A custom group.\n  #[cfg_attr(feature = \"serde\", serde(borrow, untagged))]\n  Custom(CustomIdent<'i>),\n}\n\n#[derive(Default)]\npub(crate) struct TransitionHandler<'i> {\n  properties: Option<(SmallVec<[PropertyId<'i>; 1]>, VendorPrefix)>,\n  durations: Option<(SmallVec<[Time; 1]>, VendorPrefix)>,\n  delays: Option<(SmallVec<[Time; 1]>, VendorPrefix)>,\n  timing_functions: Option<(SmallVec<[EasingFunction; 1]>, VendorPrefix)>,\n  has_any: bool,\n}\n\nimpl<'i> PropertyHandler<'i> for TransitionHandler<'i> {\n  fn handle_property(\n    &mut self,\n    property: &Property<'i>,\n    dest: &mut DeclarationList<'i>,\n    context: &mut PropertyHandlerContext<'i, '_>,\n  ) -> bool {\n    use Property::*;\n\n    macro_rules! maybe_flush {\n      ($prop: ident, $val: expr, $vp: ident) => {{\n        // If two vendor prefixes for the same property have different\n        // values, we need to flush what we have immediately to preserve order.\n        if let Some((val, prefixes)) = &self.$prop {\n          if val != $val && !prefixes.contains(*$vp) {\n            self.flush(dest, context);\n          }\n        }\n      }};\n    }\n\n    macro_rules! property {\n      ($feature: ident, $prop: ident, $val: expr, $vp: ident) => {{\n        maybe_flush!($prop, $val, $vp);\n\n        // Otherwise, update the value and add the prefix.\n        if let Some((val, prefixes)) = &mut self.$prop {\n          *val = $val.clone();\n          *prefixes |= *$vp;\n          *prefixes = context.targets.prefixes(*prefixes, Feature::$feature);\n        } else {\n          let prefixes = context.targets.prefixes(*$vp, Feature::$feature);\n          self.$prop = Some(($val.clone(), prefixes));\n          self.has_any = true;\n        }\n      }};\n    }\n\n    match property {\n      TransitionProperty(val, vp) => {\n        let merged_values = merge_properties(val.iter());\n        property!(TransitionProperty, properties, &merged_values, vp);\n      }\n      TransitionDuration(val, vp) => property!(TransitionDuration, durations, val, vp),\n      TransitionDelay(val, vp) => property!(TransitionDelay, delays, val, vp),\n      TransitionTimingFunction(val, vp) => property!(TransitionTimingFunction, timing_functions, val, vp),\n      Transition(val, vp) => {\n        let properties: SmallVec<[PropertyId; 1]> = merge_properties(val.iter().map(|b| &b.property));\n        maybe_flush!(properties, &properties, vp);\n\n        let durations: SmallVec<[Time; 1]> = val.iter().map(|b| b.duration.clone()).collect();\n        maybe_flush!(durations, &durations, vp);\n\n        let delays: SmallVec<[Time; 1]> = val.iter().map(|b| b.delay.clone()).collect();\n        maybe_flush!(delays, &delays, vp);\n\n        let timing_functions: SmallVec<[EasingFunction; 1]> =\n          val.iter().map(|b| b.timing_function.clone()).collect();\n        maybe_flush!(timing_functions, &timing_functions, vp);\n\n        property!(TransitionProperty, properties, &properties, vp);\n        property!(TransitionDuration, durations, &durations, vp);\n        property!(TransitionDelay, delays, &delays, vp);\n        property!(TransitionTimingFunction, timing_functions, &timing_functions, vp);\n      }\n      Unparsed(val) if is_transition_property(&val.property_id) => {\n        self.flush(dest, context);\n        dest.push(Property::Unparsed(\n          val.get_prefixed(context.targets, Feature::Transition),\n        ));\n      }\n      _ => return false,\n    }\n\n    true\n  }\n\n  fn finalize(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {\n    self.flush(dest, context);\n  }\n}\n\nimpl<'i> TransitionHandler<'i> {\n  fn flush(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {\n    if !self.has_any {\n      return;\n    }\n\n    self.has_any = false;\n\n    let mut properties = std::mem::take(&mut self.properties);\n    let mut durations = std::mem::take(&mut self.durations);\n    let mut delays = std::mem::take(&mut self.delays);\n    let mut timing_functions = std::mem::take(&mut self.timing_functions);\n\n    let rtl_properties = if let Some((properties, _)) = &mut properties {\n      expand_properties(properties, context)\n    } else {\n      None\n    };\n\n    if let (\n      Some((properties, property_prefixes)),\n      Some((durations, duration_prefixes)),\n      Some((delays, delay_prefixes)),\n      Some((timing_functions, timing_prefixes)),\n    ) = (&mut properties, &mut durations, &mut delays, &mut timing_functions)\n    {\n      // Find the intersection of prefixes with the same value.\n      // Remove that from the prefixes of each of the properties. The remaining\n      // prefixes will be handled by outputting individual properties below.\n      let intersection = *property_prefixes & *duration_prefixes & *delay_prefixes & *timing_prefixes;\n      if !intersection.is_empty() {\n        macro_rules! get_transitions {\n          ($properties: ident) => {{\n            // transition-property determines the number of transitions. The values of other\n            // properties are repeated to match this length.\n            let mut transitions = SmallVec::with_capacity($properties.len());\n            let mut durations_iter = durations.iter().cycle().cloned();\n            let mut delays_iter = delays.iter().cycle().cloned();\n            let mut timing_iter = timing_functions.iter().cycle().cloned();\n            for property_id in $properties {\n              let duration = durations_iter.next().unwrap_or(Time::Seconds(0.0));\n              let delay = delays_iter.next().unwrap_or(Time::Seconds(0.0));\n              let timing_function = timing_iter.next().unwrap_or(EasingFunction::Ease);\n              let transition = Transition {\n                property: property_id.clone(),\n                duration,\n                delay,\n                timing_function,\n              };\n\n              // Expand vendor prefixes into multiple transitions.\n              for p in property_id.prefix().or_none() {\n                let mut t = transition.clone();\n                t.property = property_id.with_prefix(p);\n                transitions.push(t);\n              }\n            }\n            transitions\n          }};\n        }\n\n        let transitions: SmallVec<[Transition; 1]> = get_transitions!(properties);\n\n        if let Some(rtl_properties) = &rtl_properties {\n          let rtl_transitions = get_transitions!(rtl_properties);\n          context.add_logical_rule(\n            Property::Transition(transitions, intersection),\n            Property::Transition(rtl_transitions, intersection),\n          );\n        } else {\n          dest.push(Property::Transition(transitions.clone(), intersection));\n        }\n\n        property_prefixes.remove(intersection);\n        duration_prefixes.remove(intersection);\n        delay_prefixes.remove(intersection);\n        timing_prefixes.remove(intersection);\n      }\n    }\n\n    if let Some((properties, prefix)) = properties {\n      if !prefix.is_empty() {\n        if let Some(rtl_properties) = rtl_properties {\n          context.add_logical_rule(\n            Property::TransitionProperty(properties, prefix),\n            Property::TransitionProperty(rtl_properties, prefix),\n          );\n        } else {\n          dest.push(Property::TransitionProperty(properties, prefix));\n        }\n      }\n    }\n\n    if let Some((durations, prefix)) = durations {\n      if !prefix.is_empty() {\n        dest.push(Property::TransitionDuration(durations, prefix));\n      }\n    }\n\n    if let Some((delays, prefix)) = delays {\n      if !prefix.is_empty() {\n        dest.push(Property::TransitionDelay(delays, prefix));\n      }\n    }\n\n    if let Some((timing_functions, prefix)) = timing_functions {\n      if !prefix.is_empty() {\n        dest.push(Property::TransitionTimingFunction(timing_functions, prefix));\n      }\n    }\n\n    self.reset();\n  }\n\n  fn reset(&mut self) {\n    self.properties = None;\n    self.durations = None;\n    self.delays = None;\n    self.timing_functions = None;\n  }\n}\n\n#[inline]\nfn is_transition_property(property_id: &PropertyId) -> bool {\n  match property_id {\n    PropertyId::TransitionProperty(_)\n    | PropertyId::TransitionDuration(_)\n    | PropertyId::TransitionDelay(_)\n    | PropertyId::TransitionTimingFunction(_)\n    | PropertyId::Transition(_) => true,\n    _ => false,\n  }\n}\n\nfn merge_properties<'i: 'a, 'a>(val: impl Iterator<Item = &'a PropertyId<'i>>) -> SmallVec<[PropertyId<'i>; 1]> {\n  let mut merged_values = SmallVec::<[PropertyId<'_>; 1]>::with_capacity(val.size_hint().1.unwrap_or(1));\n  for p in val {\n    let without_prefix = p.with_prefix(VendorPrefix::empty());\n    if let Some(idx) = merged_values\n      .iter()\n      .position(|c| c.with_prefix(VendorPrefix::empty()) == without_prefix)\n    {\n      merged_values[idx].add_prefix(p.prefix());\n    } else {\n      merged_values.push(p.clone());\n    }\n  }\n\n  merged_values\n}\n\nfn expand_properties<'i>(\n  properties: &mut SmallVec<[PropertyId<'i>; 1]>,\n  context: &mut PropertyHandlerContext,\n) -> Option<SmallVec<[PropertyId<'i>; 1]>> {\n  let mut rtl_properties: Option<SmallVec<[PropertyId; 1]>> = None;\n  let mut i = 0;\n\n  macro_rules! replace {\n    ($properties: ident, $props: ident) => {\n      $properties[i] = $props[0].clone();\n      if $props.len() > 1 {\n        $properties.insert_many(i + 1, $props[1..].into_iter().cloned());\n      }\n    };\n  }\n\n  // Expand logical properties in place.\n  while i < properties.len() {\n    match get_logical_properties(&properties[i]) {\n      LogicalPropertyId::Block(feature, props) if context.should_compile_logical(feature) => {\n        replace!(properties, props);\n        if let Some(rtl_properties) = &mut rtl_properties {\n          replace!(rtl_properties, props);\n        }\n        i += props.len();\n      }\n      LogicalPropertyId::Inline(feature, ltr, rtl) if context.should_compile_logical(feature) => {\n        // Clone properties to create RTL version only when needed.\n        if rtl_properties.is_none() {\n          rtl_properties = Some(properties.clone());\n        }\n\n        replace!(properties, ltr);\n        if let Some(rtl_properties) = &mut rtl_properties {\n          replace!(rtl_properties, rtl);\n        }\n\n        i += ltr.len();\n      }\n      _ => {\n        // Expand vendor prefixes for targets.\n        properties[i].set_prefixes_for_targets(context.targets);\n\n        // Expand mask properties, which use different vendor-prefixed names.\n        if let Some(property_id) = get_webkit_mask_property(&properties[i]) {\n          if context\n            .targets\n            .prefixes(VendorPrefix::None, Feature::MaskBorder)\n            .contains(VendorPrefix::WebKit)\n          {\n            properties.insert(i, property_id);\n            i += 1;\n          }\n        }\n\n        if let Some(rtl_properties) = &mut rtl_properties {\n          rtl_properties[i].set_prefixes_for_targets(context.targets);\n\n          if let Some(property_id) = get_webkit_mask_property(&rtl_properties[i]) {\n            if context\n              .targets\n              .prefixes(VendorPrefix::None, Feature::MaskBorder)\n              .contains(VendorPrefix::WebKit)\n            {\n              rtl_properties.insert(i, property_id);\n            }\n          }\n        }\n        i += 1;\n      }\n    }\n  }\n\n  rtl_properties\n}\n\nenum LogicalPropertyId {\n  None,\n  Block(compat::Feature, &'static [PropertyId<'static>]),\n  Inline(\n    compat::Feature,\n    &'static [PropertyId<'static>],\n    &'static [PropertyId<'static>],\n  ),\n}\n\n#[inline]\nfn get_logical_properties(property_id: &PropertyId) -> LogicalPropertyId {\n  use compat::Feature::*;\n  use LogicalPropertyId::*;\n  use PropertyId::*;\n  match property_id {\n    BlockSize => Block(LogicalSize, &[Height]),\n    InlineSize => Inline(LogicalSize, &[Width], &[Height]),\n    MinBlockSize => Block(LogicalSize, &[MinHeight]),\n    MaxBlockSize => Block(LogicalSize, &[MaxHeight]),\n    MinInlineSize => Inline(LogicalSize, &[MinWidth], &[MinHeight]),\n    MaxInlineSize => Inline(LogicalSize, &[MaxWidth], &[MaxHeight]),\n\n    InsetBlockStart => Block(LogicalInset, &[Top]),\n    InsetBlockEnd => Block(LogicalInset, &[Bottom]),\n    InsetInlineStart => Inline(LogicalInset, &[Left], &[Right]),\n    InsetInlineEnd => Inline(LogicalInset, &[Right], &[Left]),\n    InsetBlock => Block(LogicalInset, &[Top, Bottom]),\n    InsetInline => Block(LogicalInset, &[Left, Right]),\n    Inset => Block(LogicalInset, &[Top, Bottom, Left, Right]),\n\n    MarginBlockStart => Block(LogicalMargin, &[MarginTop]),\n    MarginBlockEnd => Block(LogicalMargin, &[MarginBottom]),\n    MarginInlineStart => Inline(LogicalMargin, &[MarginLeft], &[MarginRight]),\n    MarginInlineEnd => Inline(LogicalMargin, &[MarginRight], &[MarginLeft]),\n    MarginBlock => Block(LogicalMargin, &[MarginTop, MarginBottom]),\n    MarginInline => Block(LogicalMargin, &[MarginLeft, MarginRight]),\n\n    PaddingBlockStart => Block(LogicalPadding, &[PaddingTop]),\n    PaddingBlockEnd => Block(LogicalPadding, &[PaddingBottom]),\n    PaddingInlineStart => Inline(LogicalPadding, &[PaddingLeft], &[PaddingRight]),\n    PaddingInlineEnd => Inline(LogicalPadding, &[PaddingRight], &[PaddingLeft]),\n    PaddingBlock => Block(LogicalPadding, &[PaddingTop, PaddingBottom]),\n    PaddingInline => Block(LogicalPadding, &[PaddingLeft, PaddingRight]),\n\n    BorderBlockStart => Block(LogicalBorders, &[BorderTop]),\n    BorderBlockStartWidth => Block(LogicalBorders, &[BorderTopWidth]),\n    BorderBlockStartColor => Block(LogicalBorders, &[BorderTopColor]),\n    BorderBlockStartStyle => Block(LogicalBorders, &[BorderTopStyle]),\n\n    BorderBlockEnd => Block(LogicalBorders, &[BorderBottom]),\n    BorderBlockEndWidth => Block(LogicalBorders, &[BorderBottomWidth]),\n    BorderBlockEndColor => Block(LogicalBorders, &[BorderBottomColor]),\n    BorderBlockEndStyle => Block(LogicalBorders, &[BorderBottomStyle]),\n\n    BorderInlineStart => Inline(LogicalBorders, &[BorderLeft], &[BorderRight]),\n    BorderInlineStartWidth => Inline(LogicalBorders, &[BorderLeftWidth], &[BorderRightWidth]),\n    BorderInlineStartColor => Inline(LogicalBorders, &[BorderLeftColor], &[BorderRightColor]),\n    BorderInlineStartStyle => Inline(LogicalBorders, &[BorderLeftStyle], &[BorderRightStyle]),\n\n    BorderInlineEnd => Inline(LogicalBorders, &[BorderRight], &[BorderLeft]),\n    BorderInlineEndWidth => Inline(LogicalBorders, &[BorderRightWidth], &[BorderLeftWidth]),\n    BorderInlineEndColor => Inline(LogicalBorders, &[BorderRightColor], &[BorderLeftColor]),\n    BorderInlineEndStyle => Inline(LogicalBorders, &[BorderRightStyle], &[BorderLeftStyle]),\n\n    BorderBlock => Block(LogicalBorders, &[BorderTop, BorderBottom]),\n    BorderBlockColor => Block(LogicalBorders, &[BorderTopColor, BorderBottomColor]),\n    BorderBlockWidth => Block(LogicalBorders, &[BorderTopWidth, BorderBottomWidth]),\n    BorderBlockStyle => Block(LogicalBorders, &[BorderTopStyle, BorderBottomStyle]),\n\n    BorderInline => Block(LogicalBorders, &[BorderLeft, BorderRight]),\n    BorderInlineColor => Block(LogicalBorders, &[BorderLeftColor, BorderRightColor]),\n    BorderInlineWidth => Block(LogicalBorders, &[BorderLeftWidth, BorderRightWidth]),\n    BorderInlineStyle => Block(LogicalBorders, &[BorderLeftStyle, BorderRightStyle]),\n\n    // Not worth using vendor prefixes for these since border-radius is supported\n    // everywhere custom properties (which are used to polyfill logical properties) are.\n    BorderStartStartRadius => Inline(\n      LogicalBorders,\n      &[BorderTopLeftRadius(VendorPrefix::None)],\n      &[BorderTopRightRadius(VendorPrefix::None)],\n    ),\n    BorderStartEndRadius => Inline(\n      LogicalBorders,\n      &[BorderTopRightRadius(VendorPrefix::None)],\n      &[BorderTopLeftRadius(VendorPrefix::None)],\n    ),\n    BorderEndStartRadius => Inline(\n      LogicalBorders,\n      &[BorderBottomLeftRadius(VendorPrefix::None)],\n      &[BorderBottomRightRadius(VendorPrefix::None)],\n    ),\n    BorderEndEndRadius => Inline(\n      LogicalBorders,\n      &[BorderBottomRightRadius(VendorPrefix::None)],\n      &[BorderBottomLeftRadius(VendorPrefix::None)],\n    ),\n\n    _ => None,\n  }\n}\n"
  },
  {
    "path": "src/properties/ui.rs",
    "content": "//! CSS properties related to user interface.\n\nuse crate::context::PropertyHandlerContext;\nuse crate::declaration::{DeclarationBlock, DeclarationList};\nuse crate::error::{ParserError, PrinterError};\nuse crate::macros::{define_shorthand, enum_property, shorthand_property};\nuse crate::printer::Printer;\nuse crate::properties::{Property, PropertyId};\nuse crate::targets::{should_compile, Browsers, Targets};\nuse crate::traits::{FallbackValues, IsCompatible, Parse, PropertyHandler, Shorthand, ToCss};\nuse crate::values::color::CssColor;\nuse crate::values::number::CSSNumber;\nuse crate::values::string::CowArcStr;\nuse crate::values::url::Url;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse bitflags::bitflags;\nuse cssparser::*;\nuse smallvec::SmallVec;\n\nuse super::custom::Token;\nuse super::{CustomProperty, CustomPropertyName, TokenList, TokenOrValue};\n\nenum_property! {\n  /// A value for the [resize](https://www.w3.org/TR/2021/WD-css-ui-4-20210316/#resize) property.\n  pub enum Resize {\n    /// The element does not allow resizing.\n    None,\n    /// The element is resizable in both the x and y directions.\n    Both,\n    /// The element is resizable in the x direction.\n    Horizontal,\n    /// The element is resizable in the y direction.\n    Vertical,\n    /// The element is resizable in the block direction, according to the writing mode.\n    Block,\n    /// The element is resizable in the inline direction, according to the writing mode.\n    Inline,\n  }\n}\n\n/// A [cursor image](https://www.w3.org/TR/2021/WD-css-ui-4-20210316/#cursor) value, used in the `cursor` property.\n///\n/// See [Cursor](Cursor).\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct CursorImage<'i> {\n  /// A url to the cursor image.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  pub url: Url<'i>,\n  /// The location in the image where the mouse pointer appears.\n  pub hotspot: Option<(CSSNumber, CSSNumber)>,\n}\n\nimpl<'i> Parse<'i> for CursorImage<'i> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let url = Url::parse(input)?;\n    let hotspot = if let Ok(x) = input.try_parse(CSSNumber::parse) {\n      let y = CSSNumber::parse(input)?;\n      Some((x, y))\n    } else {\n      None\n    };\n\n    Ok(CursorImage { url, hotspot })\n  }\n}\n\nimpl<'i> ToCss for CursorImage<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    self.url.to_css(dest)?;\n\n    if let Some((x, y)) = self.hotspot {\n      dest.write_char(' ')?;\n      x.to_css(dest)?;\n      dest.write_char(' ')?;\n      y.to_css(dest)?;\n    }\n    Ok(())\n  }\n}\n\nenum_property! {\n  /// A pre-defined [cursor](https://www.w3.org/TR/2021/WD-css-ui-4-20210316/#cursor) value,\n  /// used in the `cursor` property.\n  ///\n  /// See [Cursor](Cursor).\n  #[allow(missing_docs)]\n  pub enum CursorKeyword {\n    Auto,\n    Default,\n    None,\n    ContextMenu,\n    Help,\n    Pointer,\n    Progress,\n    Wait,\n    Cell,\n    Crosshair,\n    Text,\n    VerticalText,\n    Alias,\n    Copy,\n    Move,\n    NoDrop,\n    NotAllowed,\n    Grab,\n    Grabbing,\n    EResize,\n    NResize,\n    NeResize,\n    NwResize,\n    SResize,\n    SeResize,\n    SwResize,\n    WResize,\n    EwResize,\n    NsResize,\n    NeswResize,\n    NwseResize,\n    ColResize,\n    RowResize,\n    AllScroll,\n    ZoomIn,\n    ZoomOut,\n  }\n}\n\n/// A value for the [cursor](https://www.w3.org/TR/2021/WD-css-ui-4-20210316/#cursor) property.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct Cursor<'i> {\n  /// A list of cursor images.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  pub images: SmallVec<[CursorImage<'i>; 1]>,\n  /// A pre-defined cursor.\n  pub keyword: CursorKeyword,\n}\n\nimpl<'i> Parse<'i> for Cursor<'i> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let mut images = SmallVec::new();\n    loop {\n      match input.try_parse(CursorImage::parse) {\n        Ok(image) => images.push(image),\n        Err(_) => break,\n      }\n      input.expect_comma()?;\n    }\n\n    Ok(Cursor {\n      images,\n      keyword: CursorKeyword::parse(input)?,\n    })\n  }\n}\n\nimpl<'i> ToCss for Cursor<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    for image in &self.images {\n      image.to_css(dest)?;\n      dest.delim(',', false)?;\n    }\n    self.keyword.to_css(dest)\n  }\n}\n\n/// A value for the [caret-color](https://www.w3.org/TR/2021/WD-css-ui-4-20210316/#caret-color) property.\n#[derive(Debug, Clone, PartialEq, Parse, ToCss)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum ColorOrAuto {\n  /// The `currentColor`, adjusted by the UA to ensure contrast against the background.\n  Auto,\n  /// A color.\n  Color(CssColor),\n}\n\nimpl Default for ColorOrAuto {\n  fn default() -> ColorOrAuto {\n    ColorOrAuto::Auto\n  }\n}\n\nimpl FallbackValues for ColorOrAuto {\n  fn get_fallbacks(&mut self, targets: Targets) -> Vec<Self> {\n    match self {\n      ColorOrAuto::Color(color) => color\n        .get_fallbacks(targets)\n        .into_iter()\n        .map(|color| ColorOrAuto::Color(color))\n        .collect(),\n      ColorOrAuto::Auto => Vec::new(),\n    }\n  }\n}\n\nimpl IsCompatible for ColorOrAuto {\n  fn is_compatible(&self, browsers: Browsers) -> bool {\n    match self {\n      ColorOrAuto::Color(color) => color.is_compatible(browsers),\n      ColorOrAuto::Auto => true,\n    }\n  }\n}\n\nenum_property! {\n  /// A value for the [caret-shape](https://www.w3.org/TR/2021/WD-css-ui-4-20210316/#caret-shape) property.\n  pub enum CaretShape {\n    /// The UA determines the caret shape.\n    Auto,\n    /// A thin bar caret.\n    Bar,\n    /// A rectangle caret.\n    Block,\n    /// An underscore caret.\n    Underscore,\n  }\n}\n\nimpl Default for CaretShape {\n  fn default() -> CaretShape {\n    CaretShape::Auto\n  }\n}\n\nshorthand_property! {\n  /// A value for the [caret](https://www.w3.org/TR/2021/WD-css-ui-4-20210316/#caret) shorthand property.\n  pub struct Caret {\n    /// The caret color.\n    color: CaretColor(ColorOrAuto),\n    /// The caret shape.\n    shape: CaretShape(CaretShape),\n  }\n}\n\nimpl FallbackValues for Caret {\n  fn get_fallbacks(&mut self, targets: Targets) -> Vec<Self> {\n    self\n      .color\n      .get_fallbacks(targets)\n      .into_iter()\n      .map(|color| Caret {\n        color,\n        shape: self.shape.clone(),\n      })\n      .collect()\n  }\n}\n\nimpl IsCompatible for Caret {\n  fn is_compatible(&self, browsers: Browsers) -> bool {\n    self.color.is_compatible(browsers)\n  }\n}\n\nenum_property! {\n  /// A value for the [user-select](https://www.w3.org/TR/2021/WD-css-ui-4-20210316/#content-selection) property.\n  pub enum UserSelect {\n    /// The UA determines whether text is selectable.\n    Auto,\n    /// Text is selectable.\n    Text,\n    /// Text is not selectable.\n    None,\n    /// Text selection is contained to the element.\n    Contain,\n    /// Only the entire element is selectable.\n    All,\n  }\n}\n\n/// A value for the [appearance](https://www.w3.org/TR/2021/WD-css-ui-4-20210316/#appearance-switching) property.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[allow(missing_docs)]\npub enum Appearance<'i> {\n  None,\n  Auto,\n  Textfield,\n  MenulistButton,\n  Button,\n  Checkbox,\n  Listbox,\n  Menulist,\n  Meter,\n  ProgressBar,\n  PushButton,\n  Radio,\n  Searchfield,\n  SliderHorizontal,\n  SquareButton,\n  Textarea,\n  NonStandard(CowArcStr<'i>),\n}\n\nimpl<'i> Appearance<'i> {\n  fn from_str(name: &str) -> Option<Self> {\n    Some(match_ignore_ascii_case! { &name,\n      \"none\" => Appearance::None,\n      \"auto\" => Appearance::Auto,\n      \"textfield\" => Appearance::Textfield,\n      \"menulist-button\" => Appearance::MenulistButton,\n      \"button\" => Appearance::Button,\n      \"checkbox\" => Appearance::Checkbox,\n      \"listbox\" => Appearance::Listbox,\n      \"menulist\" => Appearance::Menulist,\n      \"meter\" => Appearance::Meter,\n      \"progress-bar\" => Appearance::ProgressBar,\n      \"push-button\" => Appearance::PushButton,\n      \"radio\" => Appearance::Radio,\n      \"searchfield\" => Appearance::Searchfield,\n      \"slider-horizontal\" => Appearance::SliderHorizontal,\n      \"square-button\" => Appearance::SquareButton,\n      \"textarea\" => Appearance::Textarea,\n      _ => return None\n    })\n  }\n\n  fn to_str(&self) -> &str {\n    match self {\n      Appearance::None => \"none\",\n      Appearance::Auto => \"auto\",\n      Appearance::Textfield => \"textfield\",\n      Appearance::MenulistButton => \"menulist-button\",\n      Appearance::Button => \"button\",\n      Appearance::Checkbox => \"checkbox\",\n      Appearance::Listbox => \"listbox\",\n      Appearance::Menulist => \"menulist\",\n      Appearance::Meter => \"meter\",\n      Appearance::ProgressBar => \"progress-bar\",\n      Appearance::PushButton => \"push-button\",\n      Appearance::Radio => \"radio\",\n      Appearance::Searchfield => \"searchfield\",\n      Appearance::SliderHorizontal => \"slider-horizontal\",\n      Appearance::SquareButton => \"square-button\",\n      Appearance::Textarea => \"textarea\",\n      Appearance::NonStandard(s) => s.as_ref(),\n    }\n  }\n}\n\nimpl<'i> Parse<'i> for Appearance<'i> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let ident = input.expect_ident()?;\n    Ok(Self::from_str(ident.as_ref()).unwrap_or_else(|| Appearance::NonStandard(ident.into())))\n  }\n}\n\nimpl<'i> ToCss for Appearance<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    dest.write_str(self.to_str())\n  }\n}\n\n#[cfg(feature = \"serde\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"serde\")))]\nimpl<'i> serde::Serialize for Appearance<'i> {\n  fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>\n  where\n    S: serde::Serializer,\n  {\n    serializer.serialize_str(self.to_str())\n  }\n}\n\n#[cfg(feature = \"serde\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"serde\")))]\nimpl<'i, 'de: 'i> serde::Deserialize<'de> for Appearance<'i> {\n  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n  where\n    D: serde::Deserializer<'de>,\n  {\n    let s = CowArcStr::deserialize(deserializer)?;\n    Ok(Self::from_str(s.as_ref()).unwrap_or_else(|| Appearance::NonStandard(s)))\n  }\n}\n\n#[cfg(feature = \"jsonschema\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"jsonschema\")))]\nimpl<'a> schemars::JsonSchema for Appearance<'a> {\n  fn is_referenceable() -> bool {\n    true\n  }\n\n  fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {\n    str::json_schema(gen)\n  }\n\n  fn schema_name() -> String {\n    \"Appearance\".into()\n  }\n}\n\nbitflags! {\n  /// A value for the [color-scheme](https://drafts.csswg.org/css-color-adjust/#color-scheme-prop) property.\n  #[cfg_attr(feature = \"visitor\", derive(Visit))]\n  #[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize), serde(from = \"SerializedColorScheme\", into = \"SerializedColorScheme\"))]\n  #[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n  #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]\n  pub struct ColorScheme: u8 {\n    /// Indicates that the element supports a light color scheme.\n    const Light    = 0b01;\n    /// Indicates that the element supports a dark color scheme.\n    const Dark     = 0b10;\n    /// Forbids the user agent from overriding the color scheme for the element.\n    const Only     = 0b100;\n  }\n}\n\nimpl<'i> Parse<'i> for ColorScheme {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let mut res = ColorScheme::empty();\n    let mut has_any = false;\n\n    if input.try_parse(|input| input.expect_ident_matching(\"normal\")).is_ok() {\n      return Ok(res);\n    }\n\n    if input.try_parse(|input| input.expect_ident_matching(\"only\")).is_ok() {\n      res |= ColorScheme::Only;\n      has_any = true;\n    }\n\n    loop {\n      if input.try_parse(|input| input.expect_ident_matching(\"light\")).is_ok() {\n        res |= ColorScheme::Light;\n        has_any = true;\n        continue;\n      }\n\n      if input.try_parse(|input| input.expect_ident_matching(\"dark\")).is_ok() {\n        res |= ColorScheme::Dark;\n        has_any = true;\n        continue;\n      }\n\n      break;\n    }\n\n    // Only is allowed at the start or the end.\n    if !res.contains(ColorScheme::Only) && input.try_parse(|input| input.expect_ident_matching(\"only\")).is_ok() {\n      res |= ColorScheme::Only;\n      has_any = true;\n    }\n\n    if has_any {\n      return Ok(res);\n    }\n\n    Err(input.new_custom_error(ParserError::InvalidValue))\n  }\n}\n\nimpl ToCss for ColorScheme {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    if self.is_empty() {\n      return dest.write_str(\"normal\");\n    }\n\n    if self.contains(ColorScheme::Light) {\n      dest.write_str(\"light\")?;\n      if self.contains(ColorScheme::Dark) {\n        dest.write_char(' ')?;\n      }\n    }\n\n    if self.contains(ColorScheme::Dark) {\n      dest.write_str(\"dark\")?;\n    }\n\n    if self.contains(ColorScheme::Only) {\n      // Avoid parsing `color-scheme: only` as `color-scheme:  only`\n      if !self.intersects(ColorScheme::Light | ColorScheme::Dark) {\n        return dest.write_str(\"only\");\n      }\n      dest.write_str(\" only\")?;\n    }\n\n    Ok(())\n  }\n}\n\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\nstruct SerializedColorScheme {\n  light: bool,\n  dark: bool,\n  only: bool,\n}\n\nimpl From<ColorScheme> for SerializedColorScheme {\n  fn from(color_scheme: ColorScheme) -> Self {\n    Self {\n      light: color_scheme.contains(ColorScheme::Light),\n      dark: color_scheme.contains(ColorScheme::Dark),\n      only: color_scheme.contains(ColorScheme::Only),\n    }\n  }\n}\n\nimpl From<SerializedColorScheme> for ColorScheme {\n  fn from(s: SerializedColorScheme) -> ColorScheme {\n    let mut color_scheme = ColorScheme::empty();\n    color_scheme.set(ColorScheme::Light, s.light);\n    color_scheme.set(ColorScheme::Dark, s.dark);\n    color_scheme.set(ColorScheme::Only, s.only);\n    color_scheme\n  }\n}\n\n#[cfg(feature = \"jsonschema\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"jsonschema\")))]\nimpl<'a> schemars::JsonSchema for ColorScheme {\n  fn is_referenceable() -> bool {\n    true\n  }\n\n  fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {\n    SerializedColorScheme::json_schema(gen)\n  }\n\n  fn schema_name() -> String {\n    \"ColorScheme\".into()\n  }\n}\n\n#[derive(Default)]\npub(crate) struct ColorSchemeHandler;\n\nimpl<'i> PropertyHandler<'i> for ColorSchemeHandler {\n  fn handle_property(\n    &mut self,\n    property: &Property<'i>,\n    dest: &mut DeclarationList<'i>,\n    context: &mut PropertyHandlerContext<'i, '_>,\n  ) -> bool {\n    match property {\n      Property::ColorScheme(color_scheme) => {\n        if should_compile!(context.targets, LightDark) {\n          if color_scheme.contains(ColorScheme::Light) {\n            dest.push(define_var(\"--lightningcss-light\", Token::Ident(\"initial\".into())));\n            dest.push(define_var(\"--lightningcss-dark\", Token::WhiteSpace(\" \".into())));\n\n            if color_scheme.contains(ColorScheme::Dark) {\n              context.add_dark_rule(define_var(\"--lightningcss-light\", Token::WhiteSpace(\" \".into())));\n              context.add_dark_rule(define_var(\"--lightningcss-dark\", Token::Ident(\"initial\".into())));\n            }\n          } else if color_scheme.contains(ColorScheme::Dark) {\n            dest.push(define_var(\"--lightningcss-light\", Token::WhiteSpace(\" \".into())));\n            dest.push(define_var(\"--lightningcss-dark\", Token::Ident(\"initial\".into())));\n          }\n        }\n        dest.push(property.clone());\n        true\n      }\n      _ => false,\n    }\n  }\n\n  fn finalize(&mut self, _: &mut DeclarationList<'i>, _: &mut PropertyHandlerContext<'i, '_>) {}\n}\n\nenum_property! {\n  /// A value for the [print-color-adjust](https://drafts.csswg.org/css-color-adjust/#propdef-print-color-adjust) property.\n  pub enum PrintColorAdjust {\n    /// The user agent is allowed to make adjustments to the element as it deems appropriate.\n    Economy,\n    /// The user agent is not allowed to make adjustments to the element.\n    Exact,\n  }\n}\n\n#[inline]\nfn define_var<'i>(name: &'static str, value: Token<'static>) -> Property<'i> {\n  Property::Custom(CustomProperty {\n    name: CustomPropertyName::Custom(name.into()),\n    value: TokenList(vec![TokenOrValue::Token(value)]),\n  })\n}\n"
  },
  {
    "path": "src/rules/container.rs",
    "content": "//! The `@container` rule.\n\nuse cssparser::*;\n\nuse super::Location;\nuse super::{CssRuleList, MinifyContext};\nuse crate::error::{MinifyError, ParserError, PrinterError};\nuse crate::media_query::{\n  define_query_features, operation_to_css, parse_query_condition, to_css_with_parens_if_needed, FeatureToCss,\n  MediaFeatureType, Operator, QueryCondition, QueryConditionFlags, QueryFeature, ValueType,\n};\nuse crate::parser::{DefaultAtRule, ParserOptions};\nuse crate::printer::Printer;\nuse crate::properties::custom::TokenList;\nuse crate::properties::{Property, PropertyId};\n#[cfg(feature = \"serde\")]\nuse crate::serialization::ValueWrapper;\nuse crate::targets::{Features, Targets};\nuse crate::traits::{Parse, ParseWithOptions, ToCss};\nuse crate::values::ident::CustomIdent;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\n\n/// A [@container](https://drafts.csswg.org/css-contain-3/#container-rule) rule.\n#[derive(Debug, PartialEq, Clone)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub struct ContainerRule<'i, R = DefaultAtRule> {\n  /// The name of the container.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  pub name: Option<ContainerName<'i>>,\n  /// The container condition.\n  pub condition: Option<ContainerCondition<'i>>,\n  /// The rules within the `@container` rule.\n  pub rules: CssRuleList<'i, R>,\n  /// The location of the rule in the source file.\n  #[cfg_attr(feature = \"visitor\", skip_visit)]\n  pub loc: Location,\n}\n\n/// Represents a container condition.\n#[derive(Clone, Debug, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub enum ContainerCondition<'i> {\n  /// A size container feature, implicitly parenthesized.\n  #[cfg_attr(feature = \"serde\", serde(borrow, with = \"ValueWrapper::<ContainerSizeFeature>\"))]\n  Feature(ContainerSizeFeature<'i>),\n  /// A negation of a condition.\n  #[cfg_attr(feature = \"visitor\", skip_type)]\n  #[cfg_attr(feature = \"serde\", serde(with = \"ValueWrapper::<Box<ContainerCondition>>\"))]\n  Not(Box<ContainerCondition<'i>>),\n  /// A set of joint operations.\n  #[cfg_attr(feature = \"visitor\", skip_type)]\n  Operation {\n    /// The operator for the conditions.\n    operator: Operator,\n    /// The conditions for the operator.\n    conditions: Vec<ContainerCondition<'i>>,\n  },\n  /// A style query.\n  #[cfg_attr(feature = \"serde\", serde(borrow, with = \"ValueWrapper::<StyleQuery>\"))]\n  Style(StyleQuery<'i>),\n  /// A scroll state query.\n  #[cfg_attr(feature = \"serde\", serde(borrow, with = \"ValueWrapper::<ScrollStateQuery>\"))]\n  ScrollState(ScrollStateQuery<'i>),\n  /// Unknown tokens.\n  #[cfg_attr(feature = \"serde\", serde(borrow, with = \"ValueWrapper::<TokenList>\"))]\n  Unknown(TokenList<'i>),\n}\n\n/// A container query size feature.\npub type ContainerSizeFeature<'i> = QueryFeature<'i, ContainerSizeFeatureId>;\n\ndefine_query_features! {\n  /// A container query size feature identifier.\n  pub enum ContainerSizeFeatureId {\n    /// The [width](https://w3c.github.io/csswg-drafts/css-contain-3/#width) size container feature.\n    \"width\": Width = Length,\n    /// The [height](https://w3c.github.io/csswg-drafts/css-contain-3/#height) size container feature.\n    \"height\": Height = Length,\n    /// The [inline-size](https://w3c.github.io/csswg-drafts/css-contain-3/#inline-size) size container feature.\n    \"inline-size\": InlineSize = Length,\n    /// The [block-size](https://w3c.github.io/csswg-drafts/css-contain-3/#block-size) size container feature.\n    \"block-size\": BlockSize = Length,\n    /// The [aspect-ratio](https://w3c.github.io/csswg-drafts/css-contain-3/#aspect-ratio) size container feature.\n    \"aspect-ratio\": AspectRatio = Ratio,\n    /// The [orientation](https://w3c.github.io/csswg-drafts/css-contain-3/#orientation) size container feature.\n    \"orientation\": Orientation = Ident,\n  }\n}\n\nimpl FeatureToCss for ContainerSizeFeatureId {\n  fn to_css_with_prefix<W>(&self, prefix: &str, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    dest.write_str(prefix)?;\n    self.to_css(dest)\n  }\n}\n\n/// Represents a style query within a container condition.\n#[derive(Clone, Debug, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub enum StyleQuery<'i> {\n  /// A property declaration.\n  #[cfg_attr(feature = \"serde\", serde(borrow, with = \"ValueWrapper::<Property>\"))]\n  Declaration(Property<'i>),\n  /// A property name, without a value.\n  /// This matches if the property value is different from the initial value.\n  #[cfg_attr(feature = \"serde\", serde(with = \"ValueWrapper::<PropertyId>\"))]\n  Property(PropertyId<'i>),\n  /// A negation of a condition.\n  #[cfg_attr(feature = \"visitor\", skip_type)]\n  #[cfg_attr(feature = \"serde\", serde(with = \"ValueWrapper::<Box<StyleQuery>>\"))]\n  Not(Box<StyleQuery<'i>>),\n  /// A set of joint operations.\n  #[cfg_attr(feature = \"visitor\", skip_type)]\n  Operation {\n    /// The operator for the conditions.\n    operator: Operator,\n    /// The conditions for the operator.\n    conditions: Vec<StyleQuery<'i>>,\n  },\n}\n\n/// Represents a scroll state query within a container condition.\n#[derive(Clone, Debug, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub enum ScrollStateQuery<'i> {\n  /// A size container feature, implicitly parenthesized.\n  #[cfg_attr(feature = \"serde\", serde(borrow, with = \"ValueWrapper::<ScrollStateFeature>\"))]\n  Feature(ScrollStateFeature<'i>),\n  /// A negation of a condition.\n  #[cfg_attr(feature = \"visitor\", skip_type)]\n  #[cfg_attr(feature = \"serde\", serde(with = \"ValueWrapper::<Box<ScrollStateQuery>>\"))]\n  Not(Box<ScrollStateQuery<'i>>),\n  /// A set of joint operations.\n  #[cfg_attr(feature = \"visitor\", skip_type)]\n  Operation {\n    /// The operator for the conditions.\n    operator: Operator,\n    /// The conditions for the operator.\n    conditions: Vec<ScrollStateQuery<'i>>,\n  },\n}\n\n/// A container query size feature.\npub type ScrollStateFeature<'i> = QueryFeature<'i, ScrollStateFeatureId>;\n\ndefine_query_features! {\n  /// A container query scroll state feature identifier.\n  pub enum ScrollStateFeatureId {\n    /// The [stuck](https://drafts.csswg.org/css-conditional-5/#stuck) scroll state feature.\n    \"stuck\": Stuck = Ident,\n    /// The [snapped](https://drafts.csswg.org/css-conditional-5/#snapped) scroll state feature.\n    \"snapped\": Snapped = Ident,\n    /// The [scrollable](https://drafts.csswg.org/css-conditional-5/#scrollable) scroll state feature.\n    \"scrollable\": Scrollable = Ident,\n    /// The [scrolled](https://drafts.csswg.org/css-conditional-5/#scrolled) scroll state feature.\n    \"scrolled\": Scrolled = Ident,\n  }\n}\n\nimpl FeatureToCss for ScrollStateFeatureId {\n  fn to_css_with_prefix<W>(&self, prefix: &str, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    dest.write_str(prefix)?;\n    self.to_css(dest)\n  }\n}\n\nimpl<'i> QueryCondition<'i> for ContainerCondition<'i> {\n  #[inline]\n  fn parse_feature<'t>(\n    input: &mut Parser<'i, 't>,\n    options: &ParserOptions<'_, 'i>,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let feature = QueryFeature::parse_with_options(input, options)?;\n    Ok(Self::Feature(feature))\n  }\n\n  #[inline]\n  fn create_negation(condition: Box<ContainerCondition<'i>>) -> Self {\n    Self::Not(condition)\n  }\n\n  #[inline]\n  fn create_operation(operator: Operator, conditions: Vec<Self>) -> Self {\n    Self::Operation { operator, conditions }\n  }\n\n  fn parse_style_query<'t>(\n    input: &mut Parser<'i, 't>,\n    options: &ParserOptions<'_, 'i>,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    input.parse_nested_block(|input| {\n      if let Ok(res) =\n        input.try_parse(|input| parse_query_condition(input, QueryConditionFlags::ALLOW_OR, options))\n      {\n        return Ok(Self::Style(res));\n      }\n\n      Ok(Self::Style(StyleQuery::parse_feature(input, options)?))\n    })\n  }\n\n  fn parse_scroll_state_query<'t>(\n    input: &mut Parser<'i, 't>,\n    options: &ParserOptions<'_, 'i>,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    input.parse_nested_block(|input| {\n      if let Ok(res) =\n        input.try_parse(|input| parse_query_condition(input, QueryConditionFlags::ALLOW_OR, options))\n      {\n        return Ok(Self::ScrollState(res));\n      }\n\n      Ok(Self::ScrollState(ScrollStateQuery::parse_feature(input, options)?))\n    })\n  }\n\n  fn needs_parens(&self, parent_operator: Option<Operator>, targets: &Targets) -> bool {\n    match self {\n      ContainerCondition::Not(_) => true,\n      ContainerCondition::Operation { operator, .. } => Some(*operator) != parent_operator,\n      ContainerCondition::Feature(f) => f.needs_parens(parent_operator, targets),\n      ContainerCondition::Style(_) => false,\n      ContainerCondition::ScrollState(_) => false,\n      ContainerCondition::Unknown(_) => false,\n    }\n  }\n}\n\nimpl<'i> QueryCondition<'i> for ScrollStateQuery<'i> {\n  #[inline]\n  fn parse_feature<'t>(\n    input: &mut Parser<'i, 't>,\n    options: &ParserOptions<'_, 'i>,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let feature = QueryFeature::parse_with_options(input, options)?;\n    Ok(Self::Feature(feature))\n  }\n\n  #[inline]\n  fn create_negation(condition: Box<Self>) -> Self {\n    Self::Not(condition)\n  }\n\n  #[inline]\n  fn create_operation(operator: Operator, conditions: Vec<Self>) -> Self {\n    Self::Operation { operator, conditions }\n  }\n\n  fn needs_parens(&self, parent_operator: Option<Operator>, targets: &Targets) -> bool {\n    match self {\n      ScrollStateQuery::Not(_) => true,\n      ScrollStateQuery::Operation { operator, .. } => Some(*operator) != parent_operator,\n      ScrollStateQuery::Feature(f) => f.needs_parens(parent_operator, targets),\n    }\n  }\n}\n\nimpl<'i> QueryCondition<'i> for StyleQuery<'i> {\n  #[inline]\n  fn parse_feature<'t>(\n    input: &mut Parser<'i, 't>,\n    options: &ParserOptions<'_, 'i>,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let property_id = PropertyId::parse(input)?;\n    if input.try_parse(|input| input.expect_colon()).is_ok() {\n      input.skip_whitespace();\n      let feature = Self::Declaration(Property::parse(property_id, input, options)?);\n      let _ = input.try_parse(|input| parse_important(input));\n      Ok(feature)\n    } else {\n      Ok(Self::Property(property_id))\n    }\n  }\n\n  #[inline]\n  fn create_negation(condition: Box<Self>) -> Self {\n    Self::Not(condition)\n  }\n\n  #[inline]\n  fn create_operation(operator: Operator, conditions: Vec<Self>) -> Self {\n    Self::Operation { operator, conditions }\n  }\n\n  fn needs_parens(&self, parent_operator: Option<Operator>, _targets: &Targets) -> bool {\n    match self {\n      StyleQuery::Not(_) => true,\n      StyleQuery::Operation { operator, .. } => Some(*operator) != parent_operator,\n      StyleQuery::Declaration(_) | StyleQuery::Property(_) => true,\n    }\n  }\n}\n\nimpl<'i> ParseWithOptions<'i> for ContainerCondition<'i> {\n  fn parse_with_options<'t>(\n    input: &mut Parser<'i, 't>,\n    options: &ParserOptions<'_, 'i>,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    input\n      .try_parse(|input| {\n        parse_query_condition(\n          input,\n          QueryConditionFlags::ALLOW_OR\n            | QueryConditionFlags::ALLOW_STYLE\n            | QueryConditionFlags::ALLOW_SCROLL_STATE,\n          options,\n        )\n      })\n      .or_else(|e| {\n        if options.error_recovery {\n          options.warn(e);\n          Ok(ContainerCondition::Unknown(TokenList::parse(input, options, 0)?))\n        } else {\n          Err(e)\n        }\n      })\n  }\n}\n\nimpl<'i> ToCss for ContainerCondition<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match *self {\n      ContainerCondition::Feature(ref f) => f.to_css(dest),\n      ContainerCondition::Not(ref c) => {\n        dest.write_str(\"not \")?;\n        to_css_with_parens_if_needed(&**c, dest, c.needs_parens(None, &dest.targets.current))\n      }\n      ContainerCondition::Operation {\n        ref conditions,\n        operator,\n      } => operation_to_css(operator, conditions, dest),\n      ContainerCondition::Style(ref query) => {\n        dest.write_str(\"style(\")?;\n        query.to_css(dest)?;\n        dest.write_char(')')\n      }\n      ContainerCondition::ScrollState(ref query) => {\n        let needs_parens = !matches!(query, ScrollStateQuery::Feature(_));\n        dest.write_str(\"scroll-state\")?;\n        if needs_parens {\n          dest.write_char('(')?;\n        }\n        query.to_css(dest)?;\n        if needs_parens {\n          dest.write_char(')')?;\n        }\n        Ok(())\n      }\n      ContainerCondition::Unknown(ref tokens) => tokens.to_css(dest, false),\n    }\n  }\n}\n\nimpl<'i> ToCss for StyleQuery<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match *self {\n      StyleQuery::Declaration(ref f) => f.to_css(dest, false),\n      StyleQuery::Property(ref f) => f.to_css(dest),\n      StyleQuery::Not(ref c) => {\n        dest.write_str(\"not \")?;\n        to_css_with_parens_if_needed(&**c, dest, c.needs_parens(None, &dest.targets.current))\n      }\n      StyleQuery::Operation {\n        ref conditions,\n        operator,\n      } => operation_to_css(operator, conditions, dest),\n    }\n  }\n}\n\nimpl<'i> ToCss for ScrollStateQuery<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match *self {\n      ScrollStateQuery::Feature(ref f) => f.to_css(dest),\n      ScrollStateQuery::Not(ref c) => {\n        dest.write_str(\"not \")?;\n        to_css_with_parens_if_needed(&**c, dest, c.needs_parens(None, &dest.targets.current))\n      }\n      ScrollStateQuery::Operation {\n        ref conditions,\n        operator,\n      } => operation_to_css(operator, conditions, dest),\n    }\n  }\n}\n\n/// A [`<container-name>`](https://drafts.csswg.org/css-contain-3/#typedef-container-name) in a `@container` rule.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize), serde(transparent))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct ContainerName<'i>(#[cfg_attr(feature = \"serde\", serde(borrow))] pub CustomIdent<'i>);\n\nimpl<'i> Parse<'i> for ContainerName<'i> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let ident = CustomIdent::parse(input)?;\n    match_ignore_ascii_case! { &*ident.0,\n      \"none\" | \"and\" | \"not\" | \"or\" => Err(input.new_unexpected_token_error(Token::Ident(ident.0.as_ref().to_owned().into()))),\n      _ => Ok(ContainerName(ident))\n    }\n  }\n}\n\nimpl<'i> ToCss for ContainerName<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    // Container name should not be hashed\n    // https://github.com/vercel/next.js/issues/71233\n    self.0.to_css_with_options(\n      dest,\n      match &dest.css_module {\n        Some(css_module) => css_module.config.container,\n        None => false,\n      },\n    )\n  }\n}\n\nimpl<'i, T: Clone> ContainerRule<'i, T> {\n  pub(crate) fn minify(\n    &mut self,\n    context: &mut MinifyContext<'_, 'i>,\n    parent_is_unused: bool,\n  ) -> Result<bool, MinifyError> {\n    self.rules.minify(context, parent_is_unused)?;\n    Ok(self.rules.0.is_empty())\n  }\n}\n\nimpl<'a, 'i, T: ToCss> ToCss for ContainerRule<'i, T> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    #[cfg(feature = \"sourcemap\")]\n    dest.add_mapping(self.loc);\n    dest.write_str(\"@container \")?;\n    let has_condition = self.condition.is_some();\n\n    if let Some(name) = &self.name {\n      name.to_css(dest)?;\n      if has_condition {\n        dest.write_char(' ')?;\n      }\n    }\n\n    if let Some(condition) = &self.condition {\n      // Don't downlevel range syntax in container queries.\n      let exclude = dest.targets.current.exclude;\n      dest.targets.current.exclude.insert(Features::MediaQueries);\n      condition.to_css(dest)?;\n      dest.targets.current.exclude = exclude;\n    }\n\n    dest.whitespace()?;\n    dest.write_char('{')?;\n    dest.indent();\n    dest.newline()?;\n    self.rules.to_css(dest)?;\n    dest.dedent();\n    dest.newline()?;\n    dest.write_char('}')\n  }\n}\n"
  },
  {
    "path": "src/rules/counter_style.rs",
    "content": "//! The `@counter-style` rule.\n\nuse super::Location;\nuse crate::declaration::DeclarationBlock;\nuse crate::error::PrinterError;\nuse crate::printer::Printer;\nuse crate::traits::ToCss;\nuse crate::values::ident::CustomIdent;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\n\n/// A [@counter-style](https://drafts.csswg.org/css-counter-styles/#the-counter-style-rule) rule.\n#[derive(Debug, PartialEq, Clone)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct CounterStyleRule<'i> {\n  /// The name of the counter style to declare.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  pub name: CustomIdent<'i>,\n  // TODO: eventually parse these properties\n  /// Declarations in the `@counter-style` rule.\n  pub declarations: DeclarationBlock<'i>,\n  /// The location of the rule in the source file.\n  #[cfg_attr(feature = \"visitor\", skip_visit)]\n  pub loc: Location,\n}\n\nimpl<'i> ToCss for CounterStyleRule<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    #[cfg(feature = \"sourcemap\")]\n    dest.add_mapping(self.loc);\n    dest.write_str(\"@counter-style \")?;\n    self.name.to_css(dest)?;\n    self.declarations.to_css_block(dest)\n  }\n}\n"
  },
  {
    "path": "src/rules/custom_media.rs",
    "content": "//! The `@custom-media` rule.\n\nuse super::Location;\nuse crate::error::PrinterError;\nuse crate::media_query::MediaList;\nuse crate::printer::Printer;\nuse crate::traits::ToCss;\nuse crate::values::ident::DashedIdent;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\n\n/// A [@custom-media](https://drafts.csswg.org/mediaqueries-5/#custom-mq) rule.\n#[derive(Debug, PartialEq, Clone)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct CustomMediaRule<'i> {\n  /// The name of the declared media query.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  pub name: DashedIdent<'i>,\n  /// The media query to declare.\n  pub query: MediaList<'i>,\n  /// The location of the rule in the source file.\n  #[cfg_attr(feature = \"visitor\", skip_visit)]\n  pub loc: Location,\n}\n\nimpl<'i> ToCss for CustomMediaRule<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    #[cfg(feature = \"sourcemap\")]\n    dest.add_mapping(self.loc);\n    dest.write_str(\"@custom-media \")?;\n    self.name.to_css(dest)?;\n    dest.write_char(' ')?;\n    self.query.to_css(dest)?;\n    dest.write_char(';')\n  }\n}\n"
  },
  {
    "path": "src/rules/document.rs",
    "content": "//! The `@-moz-document` rule.\n\nuse super::Location;\nuse super::{CssRuleList, MinifyContext};\nuse crate::error::{MinifyError, PrinterError};\nuse crate::parser::DefaultAtRule;\nuse crate::printer::Printer;\nuse crate::traits::ToCss;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\n\n/// A [@-moz-document](https://www.w3.org/TR/2012/WD-css3-conditional-20120911/#at-document) rule.\n///\n/// Note that only the `url-prefix()` function with no arguments is supported, and only the `-moz` prefix\n/// is allowed since Firefox was the only browser that ever implemented this rule.\n#[derive(Debug, PartialEq, Clone)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub struct MozDocumentRule<'i, R = DefaultAtRule> {\n  /// Nested rules within the `@-moz-document` rule.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  pub rules: CssRuleList<'i, R>,\n  /// The location of the rule in the source file.\n  #[cfg_attr(feature = \"visitor\", skip_visit)]\n  pub loc: Location,\n}\n\nimpl<'i, T: Clone> MozDocumentRule<'i, T> {\n  pub(crate) fn minify(&mut self, context: &mut MinifyContext<'_, 'i>) -> Result<(), MinifyError> {\n    self.rules.minify(context, false)\n  }\n}\n\nimpl<'i, T: ToCss> ToCss for MozDocumentRule<'i, T> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    #[cfg(feature = \"sourcemap\")]\n    dest.add_mapping(self.loc);\n    dest.write_str(\"@-moz-document url-prefix()\")?;\n    dest.whitespace()?;\n    dest.write_char('{')?;\n    dest.indent();\n    dest.newline()?;\n    self.rules.to_css(dest)?;\n    dest.dedent();\n    dest.newline()?;\n    dest.write_char('}')\n  }\n}\n"
  },
  {
    "path": "src/rules/font_face.rs",
    "content": "//! The `@font-face` rule.\n\nuse super::Location;\nuse crate::error::{ParserError, PrinterError};\nuse crate::macros::enum_property;\nuse crate::printer::Printer;\nuse crate::properties::custom::CustomProperty;\nuse crate::properties::font::{FontFamily, FontStretch, FontStyle as FontStyleProperty, FontWeight};\nuse crate::stylesheet::ParserOptions;\nuse crate::traits::{Parse, ToCss};\nuse crate::values::angle::Angle;\nuse crate::values::size::Size2D;\nuse crate::values::string::CowArcStr;\nuse crate::values::url::Url;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse cssparser::*;\nuse std::fmt::Write;\n\n/// A [@font-face](https://drafts.csswg.org/css-fonts/#font-face-rule) rule.\n#[derive(Debug, PartialEq, Clone)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct FontFaceRule<'i> {\n  /// Declarations in the `@font-face` rule.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  pub properties: Vec<FontFaceProperty<'i>>,\n  /// The location of the rule in the source file.\n  #[cfg_attr(feature = \"visitor\", skip_visit)]\n  pub loc: Location,\n}\n\n/// A property within an `@font-face` rule.\n///\n/// See [FontFaceRule](FontFaceRule).\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub enum FontFaceProperty<'i> {\n  /// The `src` property.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  Source(Vec<Source<'i>>),\n  /// The `font-family` property.\n  FontFamily(FontFamily<'i>),\n  /// The `font-style` property.\n  FontStyle(FontStyle),\n  /// The `font-weight` property.\n  FontWeight(Size2D<FontWeight>),\n  /// The `font-stretch` property.\n  FontStretch(Size2D<FontStretch>),\n  /// The `unicode-range` property.\n  UnicodeRange(Vec<UnicodeRange>),\n  /// An unknown or unsupported property.\n  Custom(CustomProperty<'i>),\n}\n\n/// A value for the [src](https://drafts.csswg.org/css-fonts/#src-desc)\n/// property in an `@font-face` rule.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub enum Source<'i> {\n  /// A `url()` with optional format metadata.\n  Url(UrlSource<'i>),\n  /// The `local()` function.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  Local(FontFamily<'i>),\n}\n\nimpl<'i> Parse<'i> for Source<'i> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    match input.try_parse(UrlSource::parse) {\n      Ok(url) => return Ok(Source::Url(url)),\n      e @ Err(ParseError {\n        kind: ParseErrorKind::Basic(BasicParseErrorKind::AtRuleBodyInvalid),\n        ..\n      }) => {\n        return Err(e.err().unwrap());\n      }\n      _ => {}\n    }\n\n    input.expect_function_matching(\"local\")?;\n    let local = input.parse_nested_block(FontFamily::parse)?;\n    Ok(Source::Local(local))\n  }\n}\n\nimpl<'i> ToCss for Source<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      Source::Url(url) => url.to_css(dest),\n      Source::Local(local) => {\n        dest.write_str(\"local(\")?;\n        local.to_css(dest)?;\n        dest.write_char(')')\n      }\n    }\n  }\n}\n\n/// A `url()` value for the [src](https://drafts.csswg.org/css-fonts/#src-desc)\n/// property in an `@font-face` rule.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct UrlSource<'i> {\n  /// The URL.\n  pub url: Url<'i>,\n  /// Optional `format()` function.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  pub format: Option<FontFormat<'i>>,\n  /// Optional `tech()` function.\n  pub tech: Vec<FontTechnology>,\n}\n\nimpl<'i> Parse<'i> for UrlSource<'i> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let url = Url::parse(input)?;\n\n    let format = if input.try_parse(|input| input.expect_function_matching(\"format\")).is_ok() {\n      Some(input.parse_nested_block(FontFormat::parse)?)\n    } else {\n      None\n    };\n\n    let tech = if input.try_parse(|input| input.expect_function_matching(\"tech\")).is_ok() {\n      input.parse_nested_block(Vec::<FontTechnology>::parse)?\n    } else {\n      vec![]\n    };\n\n    Ok(UrlSource { url, format, tech })\n  }\n}\n\nimpl<'i> ToCss for UrlSource<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    self.url.to_css(dest)?;\n    if let Some(format) = &self.format {\n      dest.whitespace()?;\n      dest.write_str(\"format(\")?;\n      format.to_css(dest)?;\n      dest.write_char(')')?;\n    }\n\n    if !self.tech.is_empty() {\n      dest.whitespace()?;\n      dest.write_str(\"tech(\")?;\n      self.tech.to_css(dest)?;\n      dest.write_char(')')?;\n    }\n    Ok(())\n  }\n}\n\n/// A font format keyword in the `format()` function of the the\n/// [src](https://drafts.csswg.org/css-fonts/#src-desc)\n/// property of an `@font-face` rule.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"lowercase\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub enum FontFormat<'i> {\n  /// [src](https://drafts.csswg.org/css-fonts/#font-format-definitions)\n  /// A WOFF 1.0 font.\n  WOFF,\n  /// A WOFF 2.0 font.\n  WOFF2,\n  /// A TrueType font.\n  TrueType,\n  /// An OpenType font.\n  OpenType,\n  /// An Embedded OpenType (.eot) font.\n  #[cfg_attr(feature = \"serde\", serde(rename = \"embedded-opentype\"))]\n  EmbeddedOpenType,\n  /// OpenType Collection.\n  Collection,\n  /// An SVG font.\n  SVG,\n  /// An unknown format.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  String(CowArcStr<'i>),\n}\n\nimpl<'i> Parse<'i> for FontFormat<'i> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let s = input.expect_ident_or_string()?;\n    match_ignore_ascii_case! { &s,\n      \"woff\" => Ok(FontFormat::WOFF),\n      \"woff2\" => Ok(FontFormat::WOFF2),\n      \"truetype\" => Ok(FontFormat::TrueType),\n      \"opentype\" => Ok(FontFormat::OpenType),\n      \"embedded-opentype\" => Ok(FontFormat::EmbeddedOpenType),\n      \"collection\" => Ok(FontFormat::Collection),\n      \"svg\" => Ok(FontFormat::SVG),\n      _ => Ok(FontFormat::String(s.into()))\n    }\n  }\n}\n\nimpl<'i> ToCss for FontFormat<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    use FontFormat::*;\n    let s = match self {\n      WOFF => \"woff\",\n      WOFF2 => \"woff2\",\n      TrueType => \"truetype\",\n      OpenType => \"opentype\",\n      EmbeddedOpenType => \"embedded-opentype\",\n      Collection => \"collection\",\n      SVG => \"svg\",\n      String(s) => &s,\n    };\n    // Browser support for keywords rather than strings is very limited.\n    // https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/src\n    serialize_string(&s, dest)?;\n    Ok(())\n  }\n}\n\nenum_property! {\n  /// A font format keyword in the `format()` function of the the\n  /// [src](https://drafts.csswg.org/css-fonts/#src-desc)\n  /// property of an `@font-face` rule.\n  pub enum FontTechnology {\n    /// A font features tech descriptor in the `tech()`function of the\n    /// [src](https://drafts.csswg.org/css-fonts/#font-features-tech-values)\n    /// property of an `@font-face` rule.\n    /// Supports OpenType Features.\n    /// https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist\n    \"features-opentype\": FeaturesOpentype,\n    /// Supports Apple Advanced Typography Font Features.\n    /// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM09/AppendixF.html\n    \"features-aat\": FeaturesAat,\n    /// Supports Graphite Table Format.\n    /// https://scripts.sil.org/cms/scripts/render_download.php?site_id=nrsi&format=file&media_id=GraphiteBinaryFormat_3_0&filename=GraphiteBinaryFormat_3_0.pdf\n    \"features-graphite\": FeaturesGraphite,\n\n    /// A color font tech descriptor in the `tech()`function of the\n    /// [src](https://drafts.csswg.org/css-fonts/#src-desc)\n    /// property of an `@font-face` rule.\n    /// Supports the `COLR` v0 table.\n    \"color-colrv0\": ColorCOLRv0,\n    /// Supports the `COLR` v1 table.\n    \"color-colrv1\": ColorCOLRv1,\n    /// Supports the `SVG` table.\n    \"color-svg\": ColorSVG,\n    /// Supports the `sbix` table.\n    \"color-sbix\": ColorSbix,\n    /// Supports the `CBDT` table.\n    \"color-cbdt\": ColorCBDT,\n\n    /// Supports Variations\n    /// The variations tech refers to the support of font variations\n    \"variations\": Variations,\n    /// Supports Palettes\n    /// The palettes tech refers to support for font palettes\n    \"palettes\": Palettes,\n    /// Supports Incremental\n    /// The incremental tech refers to client support for incremental font loading, using either the range-request or the patch-subset method\n    \"incremental\": Incremental,\n  }\n}\n\n/// A contiguous range of Unicode code points.\n///\n/// Cannot be empty. Can represent a single code point when start == end.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub struct UnicodeRange {\n  /// Inclusive start of the range. In [0, end].\n  pub start: u32,\n  /// Inclusive end of the range. In [0, 0x10FFFF].\n  pub end: u32,\n}\n\nimpl<'i> Parse<'i> for UnicodeRange {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let range = cssparser::UnicodeRange::parse(input)?;\n    Ok(UnicodeRange {\n      start: range.start,\n      end: range.end,\n    })\n  }\n}\n\nimpl ToCss for UnicodeRange {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    // Attempt to optimize the range to use question mark syntax.\n    if self.start != self.end {\n      // Find the first hex digit that differs between the start and end values.\n      let mut shift = 24;\n      let mut mask = 0xf << shift;\n      while shift > 0 {\n        let c1 = self.start & mask;\n        let c2 = self.end & mask;\n        if c1 != c2 {\n          break;\n        }\n\n        mask = mask >> 4;\n        shift -= 4;\n      }\n\n      // Get the remainder of the value. This must be 0x0 to 0xf for the rest\n      // of the value to use the question mark syntax.\n      shift += 4;\n      let remainder_mask = (1 << shift) - 1;\n      let start_remainder = self.start & remainder_mask;\n      let end_remainder = self.end & remainder_mask;\n\n      if start_remainder == 0 && end_remainder == remainder_mask {\n        let start = (self.start & !remainder_mask) >> shift;\n        if start != 0 {\n          write!(dest, \"U+{:X}\", start)?;\n        } else {\n          dest.write_str(\"U+\")?;\n        }\n\n        while shift > 0 {\n          dest.write_char('?')?;\n          shift -= 4;\n        }\n\n        return Ok(());\n      }\n    }\n\n    write!(dest, \"U+{:X}\", self.start)?;\n    if self.end != self.start {\n      write!(dest, \"-{:X}\", self.end)?;\n    }\n    Ok(())\n  }\n}\n\n/// A value for the [font-style](https://w3c.github.io/csswg-drafts/css-fonts/#descdef-font-face-font-style) descriptor in an `@font-face` rule.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum FontStyle {\n  /// Normal font style.\n  Normal,\n  /// Italic font style.\n  Italic,\n  /// Oblique font style, with a custom angle.\n  Oblique(#[cfg_attr(feature = \"serde\", serde(default = \"FontStyle::default_oblique_angle\"))] Size2D<Angle>),\n}\n\nimpl Default for FontStyle {\n  fn default() -> FontStyle {\n    FontStyle::Normal\n  }\n}\n\nimpl FontStyle {\n  #[inline]\n  fn default_oblique_angle() -> Size2D<Angle> {\n    Size2D(\n      FontStyleProperty::default_oblique_angle(),\n      FontStyleProperty::default_oblique_angle(),\n    )\n  }\n}\n\nimpl<'i> Parse<'i> for FontStyle {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    Ok(match FontStyleProperty::parse(input)? {\n      FontStyleProperty::Normal => FontStyle::Normal,\n      FontStyleProperty::Italic => FontStyle::Italic,\n      FontStyleProperty::Oblique(angle) => {\n        let second_angle = input.try_parse(Angle::parse).unwrap_or_else(|_| angle.clone());\n        FontStyle::Oblique(Size2D(angle, second_angle))\n      }\n    })\n  }\n}\n\nimpl ToCss for FontStyle {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      FontStyle::Normal => dest.write_str(\"normal\"),\n      FontStyle::Italic => dest.write_str(\"italic\"),\n      FontStyle::Oblique(angle) => {\n        dest.write_str(\"oblique\")?;\n        if *angle != FontStyle::default_oblique_angle() {\n          dest.write_char(' ')?;\n          angle.to_css(dest)?;\n        }\n        Ok(())\n      }\n    }\n  }\n}\n\npub(crate) struct FontFaceDeclarationParser;\n\n/// Parse a declaration within {} block: `color: blue`\nimpl<'i> cssparser::DeclarationParser<'i> for FontFaceDeclarationParser {\n  type Declaration = FontFaceProperty<'i>;\n  type Error = ParserError<'i>;\n\n  fn parse_value<'t>(\n    &mut self,\n    name: CowRcStr<'i>,\n    input: &mut cssparser::Parser<'i, 't>,\n  ) -> Result<Self::Declaration, cssparser::ParseError<'i, Self::Error>> {\n    macro_rules! property {\n      ($property: ident, $type: ty) => {\n        if let Ok(c) = <$type>::parse(input) {\n          if input.expect_exhausted().is_ok() {\n            return Ok(FontFaceProperty::$property(c));\n          }\n        }\n      };\n    }\n\n    let state = input.state();\n    match_ignore_ascii_case! { &name,\n      \"src\" => {\n        if let Ok(sources) = input.parse_comma_separated(Source::parse) {\n          return Ok(FontFaceProperty::Source(sources))\n        }\n      },\n      \"font-family\" => property!(FontFamily, FontFamily),\n      \"font-weight\" => property!(FontWeight, Size2D<FontWeight>),\n      \"font-style\" => property!(FontStyle, FontStyle),\n      \"font-stretch\" => property!(FontStretch, Size2D<FontStretch>),\n      \"unicode-range\" => property!(UnicodeRange, Vec<UnicodeRange>),\n      _ => {}\n    }\n\n    input.reset(&state);\n    return Ok(FontFaceProperty::Custom(CustomProperty::parse(\n      name.into(),\n      input,\n      &ParserOptions::default(),\n    )?));\n  }\n}\n\n/// Default methods reject all at rules.\nimpl<'i> AtRuleParser<'i> for FontFaceDeclarationParser {\n  type Prelude = ();\n  type AtRule = FontFaceProperty<'i>;\n  type Error = ParserError<'i>;\n}\n\nimpl<'i> QualifiedRuleParser<'i> for FontFaceDeclarationParser {\n  type Prelude = ();\n  type QualifiedRule = FontFaceProperty<'i>;\n  type Error = ParserError<'i>;\n}\n\nimpl<'i> RuleBodyItemParser<'i, FontFaceProperty<'i>, ParserError<'i>> for FontFaceDeclarationParser {\n  fn parse_qualified(&self) -> bool {\n    false\n  }\n\n  fn parse_declarations(&self) -> bool {\n    true\n  }\n}\n\nimpl<'i> ToCss for FontFaceRule<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    #[cfg(feature = \"sourcemap\")]\n    dest.add_mapping(self.loc);\n    dest.write_str(\"@font-face\")?;\n    dest.whitespace()?;\n    dest.write_char('{')?;\n    dest.indent();\n    let len = self.properties.len();\n    for (i, prop) in self.properties.iter().enumerate() {\n      dest.newline()?;\n      prop.to_css(dest)?;\n      if i != len - 1 || !dest.minify {\n        dest.write_char(';')?;\n      }\n    }\n    dest.dedent();\n    dest.newline()?;\n    dest.write_char('}')\n  }\n}\n\nimpl<'i> ToCss for FontFaceProperty<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    use FontFaceProperty::*;\n    macro_rules! property {\n      ($prop: literal, $value: expr) => {{\n        dest.write_str($prop)?;\n        dest.delim(':', false)?;\n        $value.to_css(dest)\n      }};\n      ($prop: literal, $value: expr, $multi: expr) => {{\n        dest.write_str($prop)?;\n        dest.delim(':', false)?;\n        let len = $value.len();\n        for (idx, val) in $value.iter().enumerate() {\n          val.to_css(dest)?;\n          if idx < len - 1 {\n            dest.delim(',', false)?;\n          }\n        }\n        Ok(())\n      }};\n    }\n\n    match self {\n      Source(value) => property!(\"src\", value, true),\n      FontFamily(value) => property!(\"font-family\", value),\n      FontStyle(value) => property!(\"font-style\", value),\n      FontWeight(value) => property!(\"font-weight\", value),\n      FontStretch(value) => property!(\"font-stretch\", value),\n      UnicodeRange(value) => property!(\"unicode-range\", value),\n      Custom(custom) => {\n        dest.write_str(custom.name.as_ref())?;\n        dest.delim(':', false)?;\n        custom.value.to_css(dest, true)\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/rules/font_feature_values.rs",
    "content": "//! The `@font-feature-values` rule.\n\nuse super::Location;\nuse crate::error::{ParserError, PrinterError};\nuse crate::parser::ParserOptions;\nuse crate::printer::Printer;\nuse crate::properties::font::FamilyName;\nuse crate::traits::{Parse, ToCss};\nuse crate::values::ident::Ident;\nuse crate::values::number::CSSInteger;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse cssparser::*;\nuse indexmap::IndexMap;\nuse smallvec::SmallVec;\nuse std::fmt::Write;\n\n/// A [@font-feature-values](https://drafts.csswg.org/css-fonts/#font-feature-values) rule.\n#[derive(Debug, PartialEq, Clone)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub struct FontFeatureValuesRule<'i> {\n  /// The name of the font feature values.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  pub name: Vec<FamilyName<'i>>,\n  /// The rules within the `@font-feature-values` rule.\n  pub rules: IndexMap<FontFeatureSubruleType, FontFeatureSubrule<'i>>,\n  /// The location of the rule in the source file.\n  #[cfg_attr(feature = \"visitor\", skip_visit)]\n  pub loc: Location,\n}\n\nimpl<'i> FontFeatureValuesRule<'i> {\n  pub(crate) fn parse<'t, 'o>(\n    family_names: Vec<FamilyName<'i>>,\n    input: &mut Parser<'i, 't>,\n    loc: Location,\n    options: &ParserOptions<'o, 'i>,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let mut rules = IndexMap::new();\n    let mut rule_parser = FontFeatureValuesRuleParser {\n      rules: &mut rules,\n      options,\n    };\n    let mut parser = RuleBodyParser::new(input, &mut rule_parser);\n\n    while let Some(decl_or_rule) = parser.next() {\n      if let Err((err, _)) = decl_or_rule {\n        if parser.parser.options.error_recovery {\n          parser.parser.options.warn(err);\n          continue;\n        }\n        return Err(err);\n      }\n    }\n\n    Ok(FontFeatureValuesRule {\n      name: family_names,\n      rules,\n      loc,\n    })\n  }\n}\n\nstruct FontFeatureValuesRuleParser<'a, 'o, 'i> {\n  rules: &'a mut IndexMap<FontFeatureSubruleType, FontFeatureSubrule<'i>>,\n  options: &'a ParserOptions<'o, 'i>,\n}\n\nimpl<'a, 'o, 'i> cssparser::DeclarationParser<'i> for FontFeatureValuesRuleParser<'a, 'o, 'i> {\n  type Declaration = ();\n  type Error = ParserError<'i>;\n}\n\nimpl<'a, 'o, 'i> cssparser::AtRuleParser<'i> for FontFeatureValuesRuleParser<'a, 'o, 'i> {\n  type Prelude = FontFeatureSubruleType;\n  type AtRule = ();\n  type Error = ParserError<'i>;\n\n  fn parse_prelude<'t>(\n    &mut self,\n    name: CowRcStr<'i>,\n    input: &mut Parser<'i, 't>,\n  ) -> Result<Self::Prelude, ParseError<'i, Self::Error>> {\n    let loc = input.current_source_location();\n    FontFeatureSubruleType::parse_string(&name)\n      .map_err(|_| loc.new_custom_error(ParserError::AtRuleInvalid(name.clone().into())))\n  }\n\n  fn parse_block<'t>(\n    &mut self,\n    prelude: Self::Prelude,\n    start: &ParserState,\n    input: &mut Parser<'i, 't>,\n  ) -> Result<Self::AtRule, ParseError<'i, Self::Error>> {\n    let loc = start.source_location();\n    let mut decls = IndexMap::new();\n    let mut has_existing = false;\n    let declarations = if let Some(rule) = self.rules.get_mut(&prelude) {\n      has_existing = true;\n      &mut rule.declarations\n    } else {\n      &mut decls\n    };\n    let mut decl_parser = FontFeatureDeclarationParser { declarations };\n    let mut parser = RuleBodyParser::new(input, &mut decl_parser);\n    while let Some(decl) = parser.next() {\n      if let Err((err, _)) = decl {\n        if self.options.error_recovery {\n          self.options.warn(err);\n          continue;\n        }\n        return Err(err);\n      }\n    }\n\n    if !has_existing {\n      self.rules.insert(\n        prelude,\n        FontFeatureSubrule {\n          name: prelude,\n          declarations: decls,\n          loc: Location {\n            source_index: self.options.source_index,\n            line: loc.line,\n            column: loc.column,\n          },\n        },\n      );\n    }\n\n    Ok(())\n  }\n}\n\nimpl<'a, 'o, 'i> QualifiedRuleParser<'i> for FontFeatureValuesRuleParser<'a, 'o, 'i> {\n  type Prelude = ();\n  type QualifiedRule = ();\n  type Error = ParserError<'i>;\n}\n\nimpl<'a, 'o, 'i> RuleBodyItemParser<'i, (), ParserError<'i>> for FontFeatureValuesRuleParser<'a, 'o, 'i> {\n  fn parse_declarations(&self) -> bool {\n    false\n  }\n\n  fn parse_qualified(&self) -> bool {\n    false\n  }\n}\n\nimpl<'i> ToCss for FontFeatureValuesRule<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    #[cfg(feature = \"sourcemap\")]\n    dest.add_mapping(self.loc);\n    dest.write_str(\"@font-feature-values \")?;\n    self.name.to_css(dest)?;\n    dest.whitespace()?;\n    dest.write_char('{')?;\n    if !self.rules.is_empty() {\n      dest.newline()?;\n      for rule in self.rules.values() {\n        rule.to_css(dest)?;\n        dest.newline()?;\n      }\n    }\n    dest.write_char('}')\n  }\n}\n\nimpl<'i> FontFeatureValuesRule<'i> {\n  pub(crate) fn merge(&mut self, other: &FontFeatureValuesRule<'i>) {\n    debug_assert_eq!(self.name, other.name);\n    for (prelude, rule) in &other.rules {\n      if let Some(existing) = self.rules.get_mut(prelude) {\n        existing\n          .declarations\n          .extend(rule.declarations.iter().map(|(k, v)| (k.clone(), v.clone())));\n      } else {\n        self.rules.insert(*prelude, rule.clone());\n      }\n    }\n  }\n}\n\n/// The name of the `@font-feature-values` sub-rule.\n/// font-feature-value-type = <@stylistic> | <@historical-forms> | <@styleset> | <@character-variant>\n///   | <@swash> | <@ornaments> | <@annotation>\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Parse, ToCss)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum FontFeatureSubruleType {\n  /// @stylistic = @stylistic { <declaration-list> }\n  Stylistic,\n  /// @historical-forms = @historical-forms { <declaration-list> }\n  HistoricalForms,\n  /// @styleset = @styleset { <declaration-list> }\n  Styleset,\n  /// @character-variant = @character-variant { <declaration-list> }\n  CharacterVariant,\n  /// @swash = @swash { <declaration-list> }\n  Swash,\n  /// @ornaments = @ornaments { <declaration-list> }\n  Ornaments,\n  /// @annotation = @annotation { <declaration-list> }\n  Annotation,\n}\n\n/// A sub-rule of `@font-feature-values`\n/// https://drafts.csswg.org/css-fonts/#font-feature-values-syntax\n#[derive(Debug, PartialEq, Clone)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"camelCase\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct FontFeatureSubrule<'i> {\n  /// The name of the `@font-feature-values` sub-rule.\n  pub name: FontFeatureSubruleType,\n  /// The declarations within the `@font-feature-values` sub-rules.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  pub declarations: IndexMap<Ident<'i>, SmallVec<[CSSInteger; 1]>>,\n  /// The location of the rule in the source file.\n  #[cfg_attr(feature = \"visitor\", skip_visit)]\n  pub loc: Location,\n}\n\nimpl<'i> ToCss for FontFeatureSubrule<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: Write,\n  {\n    #[cfg(feature = \"sourcemap\")]\n    dest.add_mapping(self.loc);\n    dest.write_char('@')?;\n    self.name.to_css(dest)?;\n    dest.write_char('{')?;\n    dest.indent();\n    let len = self.declarations.len();\n    for (i, (name, value)) in self.declarations.iter().enumerate() {\n      dest.newline()?;\n      name.to_css(dest)?;\n      dest.delim(':', false)?;\n\n      let mut first = true;\n      for index in value {\n        if first {\n          first = false;\n        } else {\n          dest.write_char(' ')?;\n        }\n        index.to_css(dest)?;\n      }\n\n      if i != len - 1 || !dest.minify {\n        dest.write_char(';')?;\n      }\n    }\n    dest.dedent();\n    dest.newline()?;\n    dest.write_char('}')\n  }\n}\n\nstruct FontFeatureDeclarationParser<'a, 'i> {\n  declarations: &'a mut IndexMap<Ident<'i>, SmallVec<[CSSInteger; 1]>>,\n}\n\nimpl<'a, 'i> cssparser::DeclarationParser<'i> for FontFeatureDeclarationParser<'a, 'i> {\n  type Declaration = ();\n  type Error = ParserError<'i>;\n\n  fn parse_value<'t>(\n    &mut self,\n    name: CowRcStr<'i>,\n    input: &mut cssparser::Parser<'i, 't>,\n  ) -> Result<Self::Declaration, cssparser::ParseError<'i, Self::Error>> {\n    let mut indices = SmallVec::new();\n    loop {\n      if let Ok(value) = CSSInteger::parse(input) {\n        indices.push(value);\n      } else {\n        break;\n      }\n    }\n\n    if indices.is_empty() {\n      return Err(input.new_custom_error(ParserError::InvalidValue));\n    }\n\n    self.declarations.insert(Ident(name.into()), indices);\n    Ok(())\n  }\n}\n\n/// Default methods reject all at rules.\nimpl<'a, 'i> AtRuleParser<'i> for FontFeatureDeclarationParser<'a, 'i> {\n  type Prelude = ();\n  type AtRule = ();\n  type Error = ParserError<'i>;\n}\n\nimpl<'a, 'i> QualifiedRuleParser<'i> for FontFeatureDeclarationParser<'a, 'i> {\n  type Prelude = ();\n  type QualifiedRule = ();\n  type Error = ParserError<'i>;\n}\n\nimpl<'a, 'i> RuleBodyItemParser<'i, (), ParserError<'i>> for FontFeatureDeclarationParser<'a, 'i> {\n  fn parse_qualified(&self) -> bool {\n    false\n  }\n\n  fn parse_declarations(&self) -> bool {\n    true\n  }\n}\n"
  },
  {
    "path": "src/rules/font_palette_values.rs",
    "content": "//! The `@font-palette-values` rule.\n\nuse super::supports::SupportsRule;\nuse super::{CssRule, CssRuleList, Location, MinifyContext};\nuse crate::error::{ParserError, PrinterError};\nuse crate::printer::Printer;\nuse crate::properties::custom::CustomProperty;\nuse crate::properties::font::FontFamily;\nuse crate::stylesheet::ParserOptions;\nuse crate::targets::Targets;\nuse crate::traits::{Parse, ToCss};\nuse crate::values::color::{ColorFallbackKind, CssColor};\nuse crate::values::ident::DashedIdent;\nuse crate::values::number::CSSInteger;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse cssparser::*;\n\n/// A [@font-palette-values](https://drafts.csswg.org/css-fonts-4/#font-palette-values) rule.\n#[derive(Debug, PartialEq, Clone)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct FontPaletteValuesRule<'i> {\n  /// The name of the font palette.\n  pub name: DashedIdent<'i>,\n  /// Declarations in the `@font-palette-values` rule.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  pub properties: Vec<FontPaletteValuesProperty<'i>>,\n  /// The location of the rule in the source file.\n  #[cfg_attr(feature = \"visitor\", skip_visit)]\n  pub loc: Location,\n}\n\n/// A property within an `@font-palette-values` rule.\n///\n///  See [FontPaletteValuesRule](FontPaletteValuesRule).\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub enum FontPaletteValuesProperty<'i> {\n  /// The `font-family` property.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  FontFamily(FontFamily<'i>),\n  /// The `base-palette` property.\n  BasePalette(BasePalette),\n  /// The `override-colors` property.\n  OverrideColors(Vec<OverrideColors>),\n  /// An unknown or unsupported property.\n  Custom(CustomProperty<'i>),\n}\n\n/// A value for the [base-palette](https://drafts.csswg.org/css-fonts-4/#base-palette-desc)\n/// property in an `@font-palette-values` rule.\n#[derive(Debug, PartialEq, Clone)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum BasePalette {\n  /// A light color palette as defined within the font.\n  Light,\n  /// A dark color palette as defined within the font.\n  Dark,\n  /// A palette index within the font.\n  Integer(u16),\n}\n\n/// A value for the [override-colors](https://drafts.csswg.org/css-fonts-4/#override-color)\n/// property in an `@font-palette-values` rule.\n#[derive(Debug, PartialEq, Clone)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub struct OverrideColors {\n  /// The index of the color within the palette to override.\n  index: u16,\n  /// The replacement color.\n  color: CssColor,\n}\n\npub(crate) struct FontPaletteValuesDeclarationParser;\n\nimpl<'i> cssparser::DeclarationParser<'i> for FontPaletteValuesDeclarationParser {\n  type Declaration = FontPaletteValuesProperty<'i>;\n  type Error = ParserError<'i>;\n\n  fn parse_value<'t>(\n    &mut self,\n    name: CowRcStr<'i>,\n    input: &mut cssparser::Parser<'i, 't>,\n  ) -> Result<Self::Declaration, cssparser::ParseError<'i, Self::Error>> {\n    let state = input.state();\n    match_ignore_ascii_case! { &name,\n      \"font-family\" => {\n        // https://drafts.csswg.org/css-fonts-4/#font-family-2-desc\n        if let Ok(font_family) = FontFamily::parse(input) {\n          return match font_family {\n            FontFamily::Generic(_) => Err(input.new_custom_error(ParserError::InvalidDeclaration)),\n            _ => Ok(FontPaletteValuesProperty::FontFamily(font_family))\n          }\n        }\n      },\n      \"base-palette\" => {\n        // https://drafts.csswg.org/css-fonts-4/#base-palette-desc\n        if let Ok(base_palette) = BasePalette::parse(input) {\n          return Ok(FontPaletteValuesProperty::BasePalette(base_palette))\n        }\n      },\n      \"override-colors\" => {\n        // https://drafts.csswg.org/css-fonts-4/#override-color\n        if let Ok(override_colors) = input.parse_comma_separated(OverrideColors::parse) {\n          return Ok(FontPaletteValuesProperty::OverrideColors(override_colors))\n        }\n      },\n      _ => return Err(input.new_custom_error(ParserError::InvalidDeclaration))\n    }\n\n    input.reset(&state);\n    return Ok(FontPaletteValuesProperty::Custom(CustomProperty::parse(\n      name.into(),\n      input,\n      &ParserOptions::default(),\n    )?));\n  }\n}\n\n/// Default methods reject all at rules.\nimpl<'i> AtRuleParser<'i> for FontPaletteValuesDeclarationParser {\n  type Prelude = ();\n  type AtRule = FontPaletteValuesProperty<'i>;\n  type Error = ParserError<'i>;\n}\n\nimpl<'i> QualifiedRuleParser<'i> for FontPaletteValuesDeclarationParser {\n  type Prelude = ();\n  type QualifiedRule = FontPaletteValuesProperty<'i>;\n  type Error = ParserError<'i>;\n}\n\nimpl<'i> RuleBodyItemParser<'i, FontPaletteValuesProperty<'i>, ParserError<'i>>\n  for FontPaletteValuesDeclarationParser\n{\n  fn parse_qualified(&self) -> bool {\n    false\n  }\n\n  fn parse_declarations(&self) -> bool {\n    true\n  }\n}\n\nimpl<'i> FontPaletteValuesRule<'i> {\n  pub(crate) fn parse<'t>(\n    name: DashedIdent<'i>,\n    input: &mut Parser<'i, 't>,\n    loc: Location,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let mut decl_parser = FontPaletteValuesDeclarationParser;\n    let mut parser = RuleBodyParser::new(input, &mut decl_parser);\n    let mut properties = vec![];\n    while let Some(decl) = parser.next() {\n      if let Ok(decl) = decl {\n        properties.push(decl);\n      }\n    }\n\n    Ok(FontPaletteValuesRule { name, properties, loc })\n  }\n}\n\nimpl<'i> Parse<'i> for BasePalette {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    if let Ok(i) = input.try_parse(CSSInteger::parse) {\n      if i.is_negative() {\n        return Err(input.new_custom_error(ParserError::InvalidValue));\n      }\n      return Ok(BasePalette::Integer(i as u16));\n    }\n\n    let location = input.current_source_location();\n    let ident = input.expect_ident()?;\n    match_ignore_ascii_case! { &*ident,\n      \"light\" => Ok(BasePalette::Light),\n      \"dark\" => Ok(BasePalette::Dark),\n      _ => Err(location.new_unexpected_token_error(Token::Ident(ident.clone())))\n    }\n  }\n}\n\nimpl ToCss for BasePalette {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      BasePalette::Light => dest.write_str(\"light\"),\n      BasePalette::Dark => dest.write_str(\"dark\"),\n      BasePalette::Integer(i) => (*i as CSSInteger).to_css(dest),\n    }\n  }\n}\n\nimpl<'i> Parse<'i> for OverrideColors {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let index = CSSInteger::parse(input)?;\n    if index.is_negative() {\n      return Err(input.new_custom_error(ParserError::InvalidValue));\n    }\n\n    let color = CssColor::parse(input)?;\n    if matches!(color, CssColor::CurrentColor) {\n      return Err(input.new_custom_error(ParserError::InvalidValue));\n    }\n\n    Ok(OverrideColors {\n      index: index as u16,\n      color,\n    })\n  }\n}\n\nimpl ToCss for OverrideColors {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    (self.index as CSSInteger).to_css(dest)?;\n    dest.write_char(' ')?;\n    self.color.to_css(dest)\n  }\n}\n\nimpl OverrideColors {\n  fn get_fallback(&self, kind: ColorFallbackKind) -> OverrideColors {\n    OverrideColors {\n      index: self.index,\n      color: self.color.get_fallback(kind),\n    }\n  }\n}\n\nimpl<'i> FontPaletteValuesRule<'i> {\n  pub(crate) fn minify(&mut self, context: &mut MinifyContext<'_, 'i>, _: bool) {\n    let mut properties = Vec::with_capacity(self.properties.len());\n    for property in &self.properties {\n      match property {\n        FontPaletteValuesProperty::OverrideColors(override_colors) => {\n          // Generate color fallbacks.\n          let mut fallbacks = ColorFallbackKind::empty();\n          for o in override_colors {\n            fallbacks |= o.color.get_necessary_fallbacks(context.targets.current);\n          }\n\n          if fallbacks.contains(ColorFallbackKind::RGB) {\n            properties.push(FontPaletteValuesProperty::OverrideColors(\n              override_colors.iter().map(|o| o.get_fallback(ColorFallbackKind::RGB)).collect(),\n            ));\n          }\n\n          if fallbacks.contains(ColorFallbackKind::P3) {\n            properties.push(FontPaletteValuesProperty::OverrideColors(\n              override_colors.iter().map(|o| o.get_fallback(ColorFallbackKind::P3)).collect(),\n            ));\n          }\n\n          let override_colors = if fallbacks.contains(ColorFallbackKind::LAB) {\n            override_colors.iter().map(|o| o.get_fallback(ColorFallbackKind::P3)).collect()\n          } else {\n            override_colors.clone()\n          };\n\n          properties.push(FontPaletteValuesProperty::OverrideColors(override_colors));\n        }\n        _ => properties.push(property.clone()),\n      }\n    }\n\n    self.properties = properties;\n  }\n\n  pub(crate) fn get_fallbacks<T>(&mut self, targets: Targets) -> Vec<CssRule<'i, T>> {\n    // Get fallbacks for unparsed properties. These will generate @supports rules\n    // containing duplicate @font-palette-values rules.\n    let mut fallbacks = ColorFallbackKind::empty();\n    for property in &self.properties {\n      match property {\n        FontPaletteValuesProperty::Custom(CustomProperty { value, .. }) => {\n          fallbacks |= value.get_necessary_fallbacks(targets);\n        }\n        _ => {}\n      }\n    }\n\n    let mut res = Vec::new();\n    let lowest_fallback = fallbacks.lowest();\n    fallbacks.remove(lowest_fallback);\n\n    if fallbacks.contains(ColorFallbackKind::P3) {\n      res.push(self.get_fallback(ColorFallbackKind::P3));\n    }\n\n    if fallbacks.contains(ColorFallbackKind::LAB)\n      || (!lowest_fallback.is_empty() && lowest_fallback != ColorFallbackKind::LAB)\n    {\n      res.push(self.get_fallback(ColorFallbackKind::LAB));\n    }\n\n    if !lowest_fallback.is_empty() {\n      for property in &mut self.properties {\n        match property {\n          FontPaletteValuesProperty::Custom(CustomProperty { value, .. }) => {\n            *value = value.get_fallback(lowest_fallback);\n          }\n          _ => {}\n        }\n      }\n    }\n\n    res\n  }\n\n  fn get_fallback<T>(&self, kind: ColorFallbackKind) -> CssRule<'i, T> {\n    let properties = self\n      .properties\n      .iter()\n      .map(|property| match property {\n        FontPaletteValuesProperty::Custom(custom) => FontPaletteValuesProperty::Custom(CustomProperty {\n          name: custom.name.clone(),\n          value: custom.value.get_fallback(kind),\n        }),\n        _ => property.clone(),\n      })\n      .collect();\n    CssRule::Supports(SupportsRule {\n      condition: kind.supports_condition(),\n      rules: CssRuleList(vec![CssRule::FontPaletteValues(FontPaletteValuesRule {\n        name: self.name.clone(),\n        properties,\n        loc: self.loc.clone(),\n      })]),\n      loc: self.loc.clone(),\n    })\n  }\n}\n\nimpl<'i> ToCss for FontPaletteValuesRule<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    #[cfg(feature = \"sourcemap\")]\n    dest.add_mapping(self.loc);\n    dest.write_str(\"@font-palette-values \")?;\n    self.name.to_css(dest)?;\n    dest.whitespace()?;\n    dest.write_char('{')?;\n    dest.indent();\n    let len = self.properties.len();\n    for (i, prop) in self.properties.iter().enumerate() {\n      dest.newline()?;\n      prop.to_css(dest)?;\n      if i != len - 1 || !dest.minify {\n        dest.write_char(';')?;\n      }\n    }\n    dest.dedent();\n    dest.newline()?;\n    dest.write_char('}')\n  }\n}\n\nimpl<'i> ToCss for FontPaletteValuesProperty<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    macro_rules! property {\n      ($prop: literal, $value: expr) => {{\n        dest.write_str($prop)?;\n        dest.delim(':', false)?;\n        $value.to_css(dest)\n      }};\n    }\n\n    match self {\n      FontPaletteValuesProperty::FontFamily(f) => property!(\"font-family\", f),\n      FontPaletteValuesProperty::BasePalette(b) => property!(\"base-palette\", b),\n      FontPaletteValuesProperty::OverrideColors(o) => property!(\"override-colors\", o),\n      FontPaletteValuesProperty::Custom(custom) => {\n        dest.write_str(custom.name.as_ref())?;\n        dest.delim(':', false)?;\n        custom.value.to_css(dest, true)\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/rules/import.rs",
    "content": "//! The `@import` rule.\n\nuse super::layer::LayerName;\nuse super::supports::SupportsCondition;\nuse super::Location;\nuse crate::dependencies::{Dependency, ImportDependency};\nuse crate::error::PrinterError;\nuse crate::media_query::MediaList;\nuse crate::printer::Printer;\nuse crate::traits::ToCss;\nuse crate::values::string::CowArcStr;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse cssparser::*;\n\n/// A [@import](https://drafts.csswg.org/css-cascade/#at-import) rule.\n#[derive(Debug, PartialEq, Clone)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct ImportRule<'i> {\n  /// The url to import.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  #[cfg_attr(feature = \"visitor\", skip_visit)]\n  pub url: CowArcStr<'i>,\n  /// An optional cascade layer name, or `None` for an anonymous layer.\n  #[cfg_attr(feature = \"visitor\", skip_visit)]\n  pub layer: Option<Option<LayerName<'i>>>,\n  /// An optional `supports()` condition.\n  pub supports: Option<SupportsCondition<'i>>,\n  /// A media query.\n  #[cfg_attr(feature = \"serde\", serde(default))]\n  pub media: MediaList<'i>,\n  /// The location of the rule in the source file.\n  #[cfg_attr(feature = \"visitor\", skip_visit)]\n  pub loc: Location,\n}\n\nimpl<'i> ToCss for ImportRule<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    let dep = if dest.dependencies.is_some() {\n      Some(ImportDependency::new(self, dest.filename()))\n    } else {\n      None\n    };\n\n    #[cfg(feature = \"sourcemap\")]\n    dest.add_mapping(self.loc);\n    dest.write_str(\"@import \")?;\n    if let Some(dep) = dep {\n      serialize_string(&dep.placeholder, dest)?;\n\n      if let Some(dependencies) = &mut dest.dependencies {\n        dependencies.push(Dependency::Import(dep))\n      }\n    } else {\n      serialize_string(&self.url, dest)?;\n    }\n\n    if let Some(layer) = &self.layer {\n      dest.write_str(\" layer\")?;\n      if let Some(name) = layer {\n        dest.write_char('(')?;\n        name.to_css(dest)?;\n        dest.write_char(')')?;\n      }\n    }\n\n    if let Some(supports) = &self.supports {\n      dest.write_str(\" supports\")?;\n      if matches!(supports, SupportsCondition::Declaration { .. }) {\n        supports.to_css(dest)?;\n      } else {\n        dest.write_char('(')?;\n        supports.to_css(dest)?;\n        dest.write_char(')')?;\n      }\n    }\n    if !self.media.media_queries.is_empty() {\n      dest.write_char(' ')?;\n      self.media.to_css(dest)?;\n    }\n    dest.write_str(\";\")\n  }\n}\n"
  },
  {
    "path": "src/rules/keyframes.rs",
    "content": "//! The `@keyframes` rule.\n\nuse super::supports::SupportsRule;\nuse super::MinifyContext;\nuse super::{CssRule, CssRuleList, Location};\nuse crate::context::DeclarationContext;\nuse crate::declaration::DeclarationBlock;\nuse crate::error::{ParserError, PrinterError};\nuse crate::parser::ParserOptions;\nuse crate::printer::Printer;\nuse crate::properties::animation::TimelineRangeName;\nuse crate::properties::custom::{CustomProperty, UnparsedProperty};\nuse crate::properties::Property;\nuse crate::targets::Targets;\nuse crate::traits::{Parse, ToCss};\nuse crate::values::color::ColorFallbackKind;\nuse crate::values::ident::CustomIdent;\nuse crate::values::percentage::Percentage;\nuse crate::values::string::CowArcStr;\nuse crate::vendor_prefix::VendorPrefix;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse cssparser::*;\n\n/// A [@keyframes](https://drafts.csswg.org/css-animations/#keyframes) rule.\n#[derive(Debug, PartialEq, Clone)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"camelCase\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct KeyframesRule<'i> {\n  /// The animation name.\n  /// <keyframes-name> = <custom-ident> | <string>\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  pub name: KeyframesName<'i>,\n  /// A list of keyframes in the animation.\n  pub keyframes: Vec<Keyframe<'i>>,\n  /// A vendor prefix for the rule, e.g. `@-webkit-keyframes`.\n  #[cfg_attr(feature = \"visitor\", skip_visit)]\n  pub vendor_prefix: VendorPrefix,\n  /// The location of the rule in the source file.\n  #[cfg_attr(feature = \"visitor\", skip_visit)]\n  pub loc: Location,\n}\n\n/// KeyframesName\n#[derive(Debug, Clone, PartialEq, Eq, Hash)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub enum KeyframesName<'i> {\n  /// `<custom-ident>` of a `@keyframes` name.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  Ident(CustomIdent<'i>),\n\n  /// `<string>` of a `@keyframes` name.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  Custom(CowArcStr<'i>),\n}\n\nimpl<'i> Parse<'i> for KeyframesName<'i> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    match input.next()?.clone() {\n      Token::Ident(ref s) => {\n        // CSS-wide keywords without quotes throws an error.\n        match_ignore_ascii_case! { &*s,\n          \"none\" | \"initial\" | \"inherit\" | \"unset\" | \"default\" | \"revert\" | \"revert-layer\" => {\n            Err(input.new_unexpected_token_error(Token::Ident(s.clone())))\n          },\n          _ => {\n            Ok(KeyframesName::Ident(CustomIdent(s.into())))\n          }\n        }\n      }\n\n      Token::QuotedString(ref s) => Ok(KeyframesName::Custom(s.into())),\n      t => return Err(input.new_unexpected_token_error(t.clone())),\n    }\n  }\n}\n\nimpl<'i> ToCss for KeyframesName<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    let css_module_animation_enabled =\n      dest.css_module.as_ref().map_or(false, |css_module| css_module.config.animation);\n\n    match self {\n      KeyframesName::Ident(ident) => {\n        dest.write_ident(ident.0.as_ref(), css_module_animation_enabled)?;\n      }\n      KeyframesName::Custom(s) => {\n        // CSS-wide keywords and `none` cannot remove quotes.\n        match_ignore_ascii_case! { &*s,\n          \"none\" | \"initial\" | \"inherit\" | \"unset\" | \"default\" | \"revert\" | \"revert-layer\" => {\n            serialize_string(&s, dest)?;\n          },\n          _ => {\n            dest.write_ident(s.as_ref(), css_module_animation_enabled)?;\n          }\n        }\n      }\n    }\n    Ok(())\n  }\n}\n\nimpl<'i> KeyframesRule<'i> {\n  pub(crate) fn minify(&mut self, context: &mut MinifyContext<'_, 'i>) {\n    context.handler_context.context = DeclarationContext::Keyframes;\n\n    for keyframe in &mut self.keyframes {\n      keyframe\n        .declarations\n        .minify(context.handler, context.important_handler, &mut context.handler_context)\n    }\n\n    context.handler_context.context = DeclarationContext::None;\n  }\n\n  pub(crate) fn get_fallbacks<T>(&mut self, targets: &Targets) -> Vec<CssRule<'i, T>> {\n    let mut fallbacks = ColorFallbackKind::empty();\n    for keyframe in &self.keyframes {\n      for property in &keyframe.declarations.declarations {\n        match property {\n          Property::Custom(CustomProperty { value, .. }) | Property::Unparsed(UnparsedProperty { value, .. }) => {\n            fallbacks |= value.get_necessary_fallbacks(*targets);\n          }\n          _ => {}\n        }\n      }\n    }\n\n    let mut res = Vec::new();\n    let lowest_fallback = fallbacks.lowest();\n    fallbacks.remove(lowest_fallback);\n\n    if fallbacks.contains(ColorFallbackKind::P3) {\n      res.push(self.get_fallback(ColorFallbackKind::P3));\n    }\n\n    if fallbacks.contains(ColorFallbackKind::LAB)\n      || (!lowest_fallback.is_empty() && lowest_fallback != ColorFallbackKind::LAB)\n    {\n      res.push(self.get_fallback(ColorFallbackKind::LAB));\n    }\n\n    if !lowest_fallback.is_empty() {\n      for keyframe in &mut self.keyframes {\n        for property in &mut keyframe.declarations.declarations {\n          match property {\n            Property::Custom(CustomProperty { value, .. })\n            | Property::Unparsed(UnparsedProperty { value, .. }) => {\n              *value = value.get_fallback(lowest_fallback);\n            }\n            _ => {}\n          }\n        }\n      }\n    }\n\n    res\n  }\n\n  fn get_fallback<T>(&self, kind: ColorFallbackKind) -> CssRule<'i, T> {\n    let keyframes = self\n      .keyframes\n      .iter()\n      .map(|keyframe| Keyframe {\n        selectors: keyframe.selectors.clone(),\n        declarations: DeclarationBlock {\n          important_declarations: vec![],\n          declarations: keyframe\n            .declarations\n            .declarations\n            .iter()\n            .map(|property| match property {\n              Property::Custom(custom) => Property::Custom(CustomProperty {\n                name: custom.name.clone(),\n                value: custom.value.get_fallback(kind),\n              }),\n              Property::Unparsed(unparsed) => Property::Unparsed(UnparsedProperty {\n                property_id: unparsed.property_id.clone(),\n                value: unparsed.value.get_fallback(kind),\n              }),\n              _ => property.clone(),\n            })\n            .collect(),\n        },\n      })\n      .collect();\n\n    CssRule::Supports(SupportsRule {\n      condition: kind.supports_condition(),\n      rules: CssRuleList(vec![CssRule::Keyframes(KeyframesRule {\n        name: self.name.clone(),\n        keyframes,\n        vendor_prefix: self.vendor_prefix,\n        loc: self.loc.clone(),\n      })]),\n      loc: self.loc.clone(),\n    })\n  }\n}\n\nimpl<'i> ToCss for KeyframesRule<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    #[cfg(feature = \"sourcemap\")]\n    dest.add_mapping(self.loc);\n    let mut first_rule = true;\n    macro_rules! write_prefix {\n      ($prefix: ident) => {\n        if self.vendor_prefix.contains(VendorPrefix::$prefix) {\n          #[allow(unused_assignments)]\n          if first_rule {\n            first_rule = false;\n          } else {\n            if !dest.minify {\n              dest.write_char('\\n')?; // no indent\n            }\n            dest.newline()?;\n          }\n          dest.write_char('@')?;\n          VendorPrefix::$prefix.to_css(dest)?;\n          dest.write_str(\"keyframes \")?;\n          self.name.to_css(dest)?;\n          dest.whitespace()?;\n          dest.write_char('{')?;\n          dest.indent();\n          let mut first = true;\n          for keyframe in &self.keyframes {\n            if first {\n              first = false;\n            } else if !dest.minify {\n              dest.write_char('\\n')?; // no indent\n            }\n            dest.newline()?;\n            keyframe.to_css(dest)?;\n          }\n          dest.dedent();\n          dest.newline()?;\n          dest.write_char('}')?;\n        }\n      };\n    }\n\n    write_prefix!(WebKit);\n    write_prefix!(Moz);\n    write_prefix!(O);\n    write_prefix!(None);\n    Ok(())\n  }\n}\n\n/// A percentage of a given timeline range\n#[derive(Debug, PartialEq, Clone)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", rename_all = \"camelCase\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub struct TimelineRangePercentage {\n  /// The name of the timeline range.\n  name: TimelineRangeName,\n  /// The percentage progress between the start and end of the range.\n  percentage: Percentage,\n}\n\nimpl<'i> Parse<'i> for TimelineRangePercentage {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let name = TimelineRangeName::parse(input)?;\n    let percentage = Percentage::parse(input)?;\n    Ok(TimelineRangePercentage { name, percentage })\n  }\n}\n\n/// A [keyframe selector](https://drafts.csswg.org/css-animations/#typedef-keyframe-selector)\n/// within an `@keyframes` rule.\n#[derive(Debug, PartialEq, Clone, Parse)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum KeyframeSelector {\n  /// An explicit percentage.\n  Percentage(Percentage),\n  /// The `from` keyword. Equivalent to 0%.\n  From,\n  /// The `to` keyword. Equivalent to 100%.\n  To,\n  /// A [named timeline range selector](https://drafts.csswg.org/scroll-animations-1/#named-range-keyframes)\n  TimelineRangePercentage(TimelineRangePercentage),\n}\n\nimpl ToCss for KeyframeSelector {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      KeyframeSelector::Percentage(p) => {\n        if dest.minify && *p == Percentage(1.0) {\n          dest.write_str(\"to\")\n        } else {\n          p.to_css(dest)\n        }\n      }\n      KeyframeSelector::From => {\n        if dest.minify {\n          dest.write_str(\"0%\")\n        } else {\n          dest.write_str(\"from\")\n        }\n      }\n      KeyframeSelector::To => dest.write_str(\"to\"),\n      KeyframeSelector::TimelineRangePercentage(TimelineRangePercentage {\n        name: timeline_range_name,\n        percentage,\n      }) => {\n        timeline_range_name.to_css(dest)?;\n        dest.write_char(' ')?;\n        percentage.to_css(dest)\n      }\n    }\n  }\n}\n\n/// An individual keyframe within an `@keyframes` rule.\n///\n/// See [KeyframesRule](KeyframesRule).\n#[derive(Debug, PartialEq, Clone)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct Keyframe<'i> {\n  /// A list of keyframe selectors to associate with the declarations in this keyframe.\n  pub selectors: Vec<KeyframeSelector>,\n  /// The declarations for this keyframe.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  pub declarations: DeclarationBlock<'i>,\n}\n\nimpl<'i> ToCss for Keyframe<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    let mut first = true;\n    for selector in &self.selectors {\n      if !first {\n        dest.delim(',', false)?;\n      }\n      first = false;\n      selector.to_css(dest)?;\n    }\n\n    self.declarations.to_css_block(dest)\n  }\n}\n\npub(crate) struct KeyframeListParser;\n\nimpl<'a, 'i> AtRuleParser<'i> for KeyframeListParser {\n  type Prelude = ();\n  type AtRule = Keyframe<'i>;\n  type Error = ParserError<'i>;\n}\n\nimpl<'a, 'i> QualifiedRuleParser<'i> for KeyframeListParser {\n  type Prelude = Vec<KeyframeSelector>;\n  type QualifiedRule = Keyframe<'i>;\n  type Error = ParserError<'i>;\n\n  fn parse_prelude<'t>(\n    &mut self,\n    input: &mut Parser<'i, 't>,\n  ) -> Result<Self::Prelude, ParseError<'i, ParserError<'i>>> {\n    input.parse_comma_separated(KeyframeSelector::parse)\n  }\n\n  fn parse_block<'t>(\n    &mut self,\n    selectors: Self::Prelude,\n    _: &ParserState,\n    input: &mut Parser<'i, 't>,\n  ) -> Result<Self::QualifiedRule, ParseError<'i, ParserError<'i>>> {\n    // For now there are no options that apply within @keyframes\n    let options = ParserOptions::default();\n    Ok(Keyframe {\n      selectors,\n      declarations: DeclarationBlock::parse(input, &options)?,\n    })\n  }\n}\n\nimpl<'i> DeclarationParser<'i> for KeyframeListParser {\n  type Declaration = Keyframe<'i>;\n  type Error = ParserError<'i>;\n}\n\nimpl<'i> RuleBodyItemParser<'i, Keyframe<'i>, ParserError<'i>> for KeyframeListParser {\n  fn parse_qualified(&self) -> bool {\n    true\n  }\n\n  fn parse_declarations(&self) -> bool {\n    false\n  }\n}\n"
  },
  {
    "path": "src/rules/layer.rs",
    "content": "//! The `@layer` rule.\n\nuse super::{CssRuleList, Location, MinifyContext};\nuse crate::error::{MinifyError, ParserError, PrinterError};\nuse crate::parser::DefaultAtRule;\nuse crate::printer::Printer;\nuse crate::traits::{Parse, ToCss};\nuse crate::values::string::CowArcStr;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse cssparser::*;\nuse smallvec::SmallVec;\n\n/// A [`<layer-name>`](https://drafts.csswg.org/css-cascade-5/#typedef-layer-name) within\n/// a `@layer` or `@import` rule.\n///\n/// Nested layers are represented using a list of identifiers. In CSS syntax, these are dot-separated.\n#[derive(Debug, Clone, PartialEq, Eq, Hash)]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize), serde(transparent))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct LayerName<'i>(#[cfg_attr(feature = \"serde\", serde(borrow))] pub SmallVec<[CowArcStr<'i>; 1]>);\n\nmacro_rules! expect_non_whitespace {\n  ($parser: ident, $($branches: tt)+) => {{\n    let start_location = $parser.current_source_location();\n    match *$parser.next_including_whitespace()? {\n      $($branches)+\n      ref token => {\n        return Err(start_location.new_basic_unexpected_token_error(token.clone()))\n      }\n    }\n  }}\n}\n\nimpl<'i> Parse<'i> for LayerName<'i> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let mut parts = SmallVec::new();\n    let ident = input.expect_ident()?;\n    parts.push(ident.into());\n\n    loop {\n      let name = input.try_parse(|input| {\n        expect_non_whitespace! {input,\n          Token::Delim('.') => Ok(()),\n        }?;\n\n        expect_non_whitespace! {input,\n          Token::Ident(ref id) => Ok(id.into()),\n        }\n      });\n\n      match name {\n        Ok(name) => parts.push(name),\n        Err(_) => break,\n      }\n    }\n\n    Ok(LayerName(parts))\n  }\n}\n\nimpl<'i> ToCss for LayerName<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    let mut first = true;\n    for name in &self.0 {\n      if first {\n        first = false;\n      } else {\n        dest.write_char('.')?;\n      }\n\n      serialize_identifier(name, dest)?;\n    }\n\n    Ok(())\n  }\n}\n\n/// A [@layer statement](https://drafts.csswg.org/css-cascade-5/#layer-empty) rule.\n///\n/// See also [LayerBlockRule](LayerBlockRule).\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct LayerStatementRule<'i> {\n  /// The layer names to declare.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  #[cfg_attr(feature = \"visitor\", skip_visit)]\n  pub names: Vec<LayerName<'i>>,\n  /// The location of the rule in the source file.\n  #[cfg_attr(feature = \"visitor\", skip_visit)]\n  pub loc: Location,\n}\n\nimpl<'i> ToCss for LayerStatementRule<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    #[cfg(feature = \"sourcemap\")]\n    dest.add_mapping(self.loc);\n    dest.write_str(\"@layer \")?;\n    self.names.to_css(dest)?;\n    dest.write_char(';')\n  }\n}\n\n/// A [@layer block](https://drafts.csswg.org/css-cascade-5/#layer-block) rule.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub struct LayerBlockRule<'i, R = DefaultAtRule> {\n  /// The name of the layer to declare, or `None` to declare an anonymous layer.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  #[cfg_attr(feature = \"visitor\", skip_visit)]\n  pub name: Option<LayerName<'i>>,\n  /// The rules within the `@layer` rule.\n  pub rules: CssRuleList<'i, R>,\n  /// The location of the rule in the source file.\n  #[cfg_attr(feature = \"visitor\", skip_visit)]\n  pub loc: Location,\n}\n\nimpl<'i, T: Clone> LayerBlockRule<'i, T> {\n  pub(crate) fn minify(\n    &mut self,\n    context: &mut MinifyContext<'_, 'i>,\n    parent_is_unused: bool,\n  ) -> Result<bool, MinifyError> {\n    self.rules.minify(context, parent_is_unused)?;\n\n    Ok(self.rules.0.is_empty())\n  }\n}\n\nimpl<'a, 'i, T: ToCss> ToCss for LayerBlockRule<'i, T> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    #[cfg(feature = \"sourcemap\")]\n    dest.add_mapping(self.loc);\n    dest.write_str(\"@layer\")?;\n    if let Some(name) = &self.name {\n      dest.write_char(' ')?;\n      name.to_css(dest)?;\n    }\n\n    dest.whitespace()?;\n    dest.write_char('{')?;\n    dest.indent();\n    dest.newline()?;\n    self.rules.to_css(dest)?;\n    dest.dedent();\n    dest.newline()?;\n    dest.write_char('}')\n  }\n}\n"
  },
  {
    "path": "src/rules/media.rs",
    "content": "//! The `@media` rule.\n\nuse super::Location;\nuse super::{CssRuleList, MinifyContext};\nuse crate::error::{MinifyError, PrinterError};\nuse crate::media_query::MediaList;\nuse crate::parser::DefaultAtRule;\nuse crate::printer::Printer;\nuse crate::traits::ToCss;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\n\n/// A [@media](https://drafts.csswg.org/css-conditional-3/#at-media) rule.\n#[derive(Debug, PartialEq, Clone)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub struct MediaRule<'i, R = DefaultAtRule> {\n  /// The media query list.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  pub query: MediaList<'i>,\n  /// The rules within the `@media` rule.\n  pub rules: CssRuleList<'i, R>,\n  /// The location of the rule in the source file.\n  #[cfg_attr(feature = \"visitor\", skip_visit)]\n  pub loc: Location,\n}\n\nimpl<'i, T: Clone> MediaRule<'i, T> {\n  pub(crate) fn minify(\n    &mut self,\n    context: &mut MinifyContext<'_, 'i>,\n    parent_is_unused: bool,\n  ) -> Result<bool, MinifyError> {\n    self.rules.minify(context, parent_is_unused)?;\n\n    if let Some(custom_media) = &context.custom_media {\n      self.query.transform_custom_media(self.loc, custom_media)?;\n    }\n\n    self.query.transform_resolution(context.targets.current);\n    Ok(self.rules.0.is_empty() || self.query.never_matches())\n  }\n}\n\nimpl<'a, 'i, T: ToCss> ToCss for MediaRule<'i, T> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    // If the media query always matches, we can just output the nested rules.\n    if dest.minify && self.query.always_matches() {\n      self.rules.to_css(dest)?;\n      return Ok(());\n    }\n\n    #[cfg(feature = \"sourcemap\")]\n    dest.add_mapping(self.loc);\n    dest.write_str(\"@media \")?;\n    self.query.to_css(dest)?;\n    dest.whitespace()?;\n    dest.write_char('{')?;\n    dest.indent();\n    dest.newline()?;\n    self.rules.to_css(dest)?;\n    dest.dedent();\n    dest.newline()?;\n    dest.write_char('}')\n  }\n}\n"
  },
  {
    "path": "src/rules/mod.rs",
    "content": "//! CSS rules.\n//!\n//! The [CssRule](CssRule) enum includes all supported rules, and can be used to parse\n//! and serialize rules from CSS. Lists of rules (i.e. within a stylesheet, or inside\n//! another rule such as `@media`) are represented by [CssRuleList](CssRuleList).\n//!\n//! Each rule includes a source location, which indicates the line and column within\n//! the source file where it was parsed. This is used when generating source maps.\n//!\n//! # Example\n//!\n//! This example shows how you could parse a single CSS rule, and serialize it to a string.\n//!\n//! ```\n//! use lightningcss::{\n//!   rules::CssRule,\n//!   traits::ToCss,\n//!   stylesheet::{ParserOptions, PrinterOptions}\n//! };\n//!\n//! let rule = CssRule::parse_string(\n//!   \".foo { color: red; }\",\n//!   ParserOptions::default()\n//! ).unwrap();\n//!\n//! assert_eq!(\n//!   rule.to_css_string(PrinterOptions::default()).unwrap(),\n//!   \".foo {\\n  color: red;\\n}\"\n//! );\n//! ```\n//!\n//! If you have a [cssparser::Parser](cssparser::Parser) already, you can also use the `parse` and `to_css`\n//! methods instead, rather than parsing from a string.\n//!\n//! See [StyleSheet](super::stylesheet::StyleSheet) to parse an entire file of multiple rules.\n\n#![deny(missing_docs)]\n\npub mod container;\npub mod counter_style;\npub mod custom_media;\npub mod document;\npub mod font_face;\npub mod font_feature_values;\npub mod font_palette_values;\npub mod import;\npub mod keyframes;\npub mod layer;\npub mod media;\npub mod namespace;\npub mod nesting;\npub mod page;\npub mod property;\npub mod scope;\npub mod starting_style;\npub mod style;\npub mod supports;\npub mod unknown;\npub mod view_transition;\npub mod viewport;\n\nuse self::font_feature_values::FontFeatureValuesRule;\nuse self::font_palette_values::FontPaletteValuesRule;\nuse self::layer::{LayerBlockRule, LayerStatementRule};\nuse self::property::PropertyRule;\nuse crate::context::PropertyHandlerContext;\nuse crate::declaration::{DeclarationBlock, DeclarationHandler};\nuse crate::dependencies::{Dependency, ImportDependency};\nuse crate::error::{MinifyError, ParserError, PrinterError, PrinterErrorKind};\nuse crate::parser::{parse_rule_list, parse_style_block, DefaultAtRule, DefaultAtRuleParser, TopLevelRuleParser};\nuse crate::prefixes::Feature;\nuse crate::printer::Printer;\nuse crate::rules::keyframes::KeyframesName;\nuse crate::selector::{is_compatible, is_equivalent, Component, Selector, SelectorList};\nuse crate::stylesheet::ParserOptions;\nuse crate::targets::{should_compile, TargetsWithSupportsScope};\nuse crate::traits::{AtRuleParser, ToCss};\nuse crate::values::string::CowArcStr;\nuse crate::vendor_prefix::VendorPrefix;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::{Visit, VisitTypes, Visitor};\nuse container::ContainerRule;\nuse counter_style::CounterStyleRule;\nuse cssparser::{parse_one_rule, ParseError, Parser, ParserInput};\nuse custom_media::CustomMediaRule;\nuse document::MozDocumentRule;\nuse font_face::FontFaceRule;\nuse import::ImportRule;\nuse itertools::Itertools;\nuse keyframes::KeyframesRule;\nuse media::MediaRule;\nuse namespace::NamespaceRule;\nuse nesting::{NestedDeclarationsRule, NestingRule};\nuse page::PageRule;\nuse scope::ScopeRule;\nuse smallvec::{smallvec, SmallVec};\nuse starting_style::StartingStyleRule;\nuse std::collections::{HashMap, HashSet};\nuse std::hash::{BuildHasherDefault, Hasher};\nuse style::StyleRule;\nuse supports::SupportsRule;\nuse unknown::UnknownAtRule;\nuse view_transition::ViewTransitionRule;\nuse viewport::ViewportRule;\n\n#[derive(Clone)]\npub(crate) struct StyleContext<'a, 'i> {\n  pub selectors: &'a SelectorList<'i>,\n  pub parent: Option<&'a StyleContext<'a, 'i>>,\n}\n\n/// A source location.\n#[derive(PartialEq, Eq, Debug, Clone, Copy)]\n#[cfg_attr(any(feature = \"serde\", feature = \"nodejs\"), derive(serde::Serialize))]\n#[cfg_attr(feature = \"serde\", derive(serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub struct Location {\n  /// The index of the source file within the source map.\n  pub source_index: u32,\n  /// The line number, starting at 0.\n  pub line: u32,\n  /// The column number within a line, starting at 1 for first the character of the line.\n  /// Column numbers are counted in UTF-16 code units.\n  pub column: u32,\n}\n\n/// A CSS rule.\n#[derive(Debug, PartialEq, Clone)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"visitor\", visit(visit_rule, RULES))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema), schemars(rename = \"Rule\"))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum CssRule<'i, R = DefaultAtRule> {\n  /// A `@media` rule.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  Media(MediaRule<'i, R>),\n  /// An `@import` rule.\n  Import(ImportRule<'i>),\n  /// A style rule.\n  Style(StyleRule<'i, R>),\n  /// A `@keyframes` rule.\n  Keyframes(KeyframesRule<'i>),\n  /// A `@font-face` rule.\n  FontFace(FontFaceRule<'i>),\n  /// A `@font-palette-values` rule.\n  FontPaletteValues(FontPaletteValuesRule<'i>),\n  /// A `@font-feature-values` rule.\n  FontFeatureValues(FontFeatureValuesRule<'i>),\n  /// A `@page` rule.\n  Page(PageRule<'i>),\n  /// A `@supports` rule.\n  Supports(SupportsRule<'i, R>),\n  /// A `@counter-style` rule.\n  CounterStyle(CounterStyleRule<'i>),\n  /// A `@namespace` rule.\n  Namespace(NamespaceRule<'i>),\n  /// A `@-moz-document` rule.\n  MozDocument(MozDocumentRule<'i, R>),\n  /// A `@nest` rule.\n  Nesting(NestingRule<'i, R>),\n  /// A nested declarations rule.\n  NestedDeclarations(NestedDeclarationsRule<'i>),\n  /// A `@viewport` rule.\n  Viewport(ViewportRule<'i>),\n  /// A `@custom-media` rule.\n  CustomMedia(CustomMediaRule<'i>),\n  /// A `@layer` statement rule.\n  LayerStatement(LayerStatementRule<'i>),\n  /// A `@layer` block rule.\n  LayerBlock(LayerBlockRule<'i, R>),\n  /// A `@property` rule.\n  Property(PropertyRule<'i>),\n  /// A `@container` rule.\n  Container(ContainerRule<'i, R>),\n  /// A `@scope` rule.\n  Scope(ScopeRule<'i, R>),\n  /// A `@starting-style` rule.\n  StartingStyle(StartingStyleRule<'i, R>),\n  /// A `@view-transition` rule.\n  ViewTransition(ViewTransitionRule<'i>),\n  /// A placeholder for a rule that was removed.\n  Ignored,\n  /// An unknown at-rule.\n  Unknown(UnknownAtRule<'i>),\n  /// A custom at-rule.\n  Custom(R),\n}\n\n// Manually implemented deserialize to reduce binary size.\n#[cfg(feature = \"serde\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"serde\")))]\nimpl<'i, 'de: 'i, R: serde::Deserialize<'de>> serde::Deserialize<'de> for CssRule<'i, R> {\n  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n  where\n    D: serde::Deserializer<'de>,\n  {\n    #[derive(serde::Deserialize)]\n    #[serde(field_identifier, rename_all = \"snake_case\")]\n    enum Field {\n      Type,\n      Value,\n    }\n\n    struct PartialRule<'de> {\n      rule_type: CowArcStr<'de>,\n      content: serde_content::Value<'de>,\n    }\n\n    struct CssRuleVisitor;\n\n    impl<'de> serde::de::Visitor<'de> for CssRuleVisitor {\n      type Value = PartialRule<'de>;\n\n      fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {\n        formatter.write_str(\"a CssRule\")\n      }\n\n      fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>\n      where\n        A: serde::de::MapAccess<'de>,\n      {\n        let mut rule_type: Option<CowArcStr<'de>> = None;\n        let mut value: Option<serde_content::Value> = None;\n        while let Some(key) = map.next_key()? {\n          match key {\n            Field::Type => {\n              rule_type = Some(map.next_value()?);\n            }\n            Field::Value => {\n              value = Some(map.next_value()?);\n            }\n          }\n        }\n\n        let rule_type = rule_type.ok_or_else(|| serde::de::Error::missing_field(\"type\"))?;\n        let content = value.ok_or_else(|| serde::de::Error::missing_field(\"value\"))?;\n        Ok(PartialRule { rule_type, content })\n      }\n    }\n\n    let partial = deserializer.deserialize_map(CssRuleVisitor)?;\n    let deserializer = serde_content::Deserializer::new(partial.content).coerce_numbers();\n\n    match partial.rule_type.as_ref() {\n      \"media\" => {\n        let rule = MediaRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;\n        Ok(CssRule::Media(rule))\n      }\n      \"import\" => {\n        let rule = ImportRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;\n        Ok(CssRule::Import(rule))\n      }\n      \"style\" => {\n        let rule = StyleRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;\n        Ok(CssRule::Style(rule))\n      }\n      \"keyframes\" => {\n        let rule =\n          KeyframesRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;\n        Ok(CssRule::Keyframes(rule))\n      }\n      \"font-face\" => {\n        let rule = FontFaceRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;\n        Ok(CssRule::FontFace(rule))\n      }\n      \"font-palette-values\" => {\n        let rule =\n          FontPaletteValuesRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;\n        Ok(CssRule::FontPaletteValues(rule))\n      }\n      \"font-feature-values\" => {\n        let rule =\n          FontFeatureValuesRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;\n        Ok(CssRule::FontFeatureValues(rule))\n      }\n      \"page\" => {\n        let rule = PageRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;\n        Ok(CssRule::Page(rule))\n      }\n      \"supports\" => {\n        let rule = SupportsRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;\n        Ok(CssRule::Supports(rule))\n      }\n      \"counter-style\" => {\n        let rule =\n          CounterStyleRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;\n        Ok(CssRule::CounterStyle(rule))\n      }\n      \"namespace\" => {\n        let rule =\n          NamespaceRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;\n        Ok(CssRule::Namespace(rule))\n      }\n      \"moz-document\" => {\n        let rule =\n          MozDocumentRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;\n        Ok(CssRule::MozDocument(rule))\n      }\n      \"nesting\" => {\n        let rule = NestingRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;\n        Ok(CssRule::Nesting(rule))\n      }\n      \"nested-declarations\" => {\n        let rule = NestedDeclarationsRule::deserialize(deserializer)\n          .map_err(|e| serde::de::Error::custom(e.to_string()))?;\n        Ok(CssRule::NestedDeclarations(rule))\n      }\n      \"viewport\" => {\n        let rule = ViewportRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;\n        Ok(CssRule::Viewport(rule))\n      }\n      \"custom-media\" => {\n        let rule =\n          CustomMediaRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;\n        Ok(CssRule::CustomMedia(rule))\n      }\n      \"layer-statement\" => {\n        let rule =\n          LayerStatementRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;\n        Ok(CssRule::LayerStatement(rule))\n      }\n      \"layer-block\" => {\n        let rule =\n          LayerBlockRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;\n        Ok(CssRule::LayerBlock(rule))\n      }\n      \"property\" => {\n        let rule = PropertyRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;\n        Ok(CssRule::Property(rule))\n      }\n      \"container\" => {\n        let rule =\n          ContainerRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;\n        Ok(CssRule::Container(rule))\n      }\n      \"scope\" => {\n        let rule = ScopeRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;\n        Ok(CssRule::Scope(rule))\n      }\n      \"starting-style\" => {\n        let rule =\n          StartingStyleRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;\n        Ok(CssRule::StartingStyle(rule))\n      }\n      \"view-transition\" => {\n        let rule =\n          ViewTransitionRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;\n        Ok(CssRule::ViewTransition(rule))\n      }\n      \"ignored\" => Ok(CssRule::Ignored),\n      \"unknown\" => {\n        let rule =\n          UnknownAtRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;\n        Ok(CssRule::Unknown(rule))\n      }\n      \"custom\" => {\n        let rule = R::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;\n        Ok(CssRule::Custom(rule))\n      }\n      t => Err(serde::de::Error::unknown_variant(t, &[])),\n    }\n  }\n}\n\nimpl<'a, 'i, T: ToCss> ToCss for CssRule<'i, T> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      CssRule::Media(media) => media.to_css(dest),\n      CssRule::Import(import) => import.to_css(dest),\n      CssRule::Style(style) => style.to_css(dest),\n      CssRule::Keyframes(keyframes) => keyframes.to_css(dest),\n      CssRule::FontFace(font_face) => font_face.to_css(dest),\n      CssRule::FontPaletteValues(f) => f.to_css(dest),\n      CssRule::FontFeatureValues(font_feature_values) => font_feature_values.to_css(dest),\n      CssRule::Page(font_face) => font_face.to_css(dest),\n      CssRule::Supports(supports) => supports.to_css(dest),\n      CssRule::CounterStyle(counter_style) => counter_style.to_css(dest),\n      CssRule::Namespace(namespace) => namespace.to_css(dest),\n      CssRule::MozDocument(document) => document.to_css(dest),\n      CssRule::Nesting(nesting) => nesting.to_css(dest),\n      CssRule::NestedDeclarations(nested) => nested.to_css(dest),\n      CssRule::Viewport(viewport) => viewport.to_css(dest),\n      CssRule::CustomMedia(custom_media) => custom_media.to_css(dest),\n      CssRule::LayerStatement(layer) => layer.to_css(dest),\n      CssRule::LayerBlock(layer) => layer.to_css(dest),\n      CssRule::Property(property) => property.to_css(dest),\n      CssRule::StartingStyle(rule) => rule.to_css(dest),\n      CssRule::Container(container) => container.to_css(dest),\n      CssRule::Scope(scope) => scope.to_css(dest),\n      CssRule::ViewTransition(rule) => rule.to_css(dest),\n      CssRule::Unknown(unknown) => unknown.to_css(dest),\n      CssRule::Custom(rule) => rule.to_css(dest).map_err(|_| PrinterError {\n        kind: PrinterErrorKind::FmtError,\n        loc: None,\n      }),\n      CssRule::Ignored => Ok(()),\n    }\n  }\n}\n\nimpl<'i> CssRule<'i, DefaultAtRule> {\n  /// Parse a single rule.\n  pub fn parse<'t>(\n    input: &mut Parser<'i, 't>,\n    options: &ParserOptions<'_, 'i>,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    Self::parse_with(input, options, &mut DefaultAtRuleParser)\n  }\n\n  /// Parse a single rule from a string.\n  pub fn parse_string(\n    input: &'i str,\n    options: ParserOptions<'_, 'i>,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    Self::parse_string_with(input, options, &mut DefaultAtRuleParser)\n  }\n}\n\nimpl<'i, T> CssRule<'i, T> {\n  /// Parse a single rule.\n  pub fn parse_with<'t, P: AtRuleParser<'i, AtRule = T>>(\n    input: &mut Parser<'i, 't>,\n    options: &ParserOptions<'_, 'i>,\n    at_rule_parser: &mut P,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let mut rules = CssRuleList(Vec::new());\n    parse_one_rule(input, &mut TopLevelRuleParser::new(options, at_rule_parser, &mut rules))?;\n    Ok(rules.0.pop().unwrap())\n  }\n\n  /// Parse a single rule from a string.\n  pub fn parse_string_with<P: AtRuleParser<'i, AtRule = T>>(\n    input: &'i str,\n    options: ParserOptions<'_, 'i>,\n    at_rule_parser: &mut P,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let mut input = ParserInput::new(input);\n    let mut parser = Parser::new(&mut input);\n    Self::parse_with(&mut parser, &options, at_rule_parser)\n  }\n}\n\n/// A list of CSS rules.\n#[derive(Debug, PartialEq, Clone, Default)]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize), serde(transparent))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub struct CssRuleList<'i, R = DefaultAtRule>(\n  #[cfg_attr(feature = \"serde\", serde(borrow))] pub Vec<CssRule<'i, R>>,\n);\n\nimpl<'i> CssRuleList<'i, DefaultAtRule> {\n  /// Parse a rule list.\n  pub fn parse<'t>(\n    input: &mut Parser<'i, 't>,\n    options: &ParserOptions<'_, 'i>,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    Self::parse_with(input, options, &mut DefaultAtRuleParser)\n  }\n\n  /// Parse a style block, with both declarations and rules.\n  /// Resulting declarations are returned in a nested style rule.\n  pub fn parse_style_block<'t>(\n    input: &mut Parser<'i, 't>,\n    options: &ParserOptions<'_, 'i>,\n    is_nested: bool,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    Self::parse_style_block_with(input, options, &mut DefaultAtRuleParser, is_nested)\n  }\n}\n\nimpl<'i, T> CssRuleList<'i, T> {\n  /// Parse a rule list with a custom at rule parser.\n  pub fn parse_with<'t, P: AtRuleParser<'i, AtRule = T>>(\n    input: &mut Parser<'i, 't>,\n    options: &ParserOptions<'_, 'i>,\n    at_rule_parser: &mut P,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    parse_rule_list(input, options, at_rule_parser)\n  }\n\n  /// Parse a style block, with both declarations and rules.\n  /// Resulting declarations are returned in a nested style rule.\n  pub fn parse_style_block_with<'t, P: AtRuleParser<'i, AtRule = T>>(\n    input: &mut Parser<'i, 't>,\n    options: &ParserOptions<'_, 'i>,\n    at_rule_parser: &mut P,\n    is_nested: bool,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    parse_style_block(input, options, at_rule_parser, is_nested)\n  }\n}\n\n// Manually implemented to avoid circular child types.\n#[cfg(feature = \"visitor\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"visitor\")))]\nimpl<'i, T: Visit<'i, T, V>, V: ?Sized + Visitor<'i, T>> Visit<'i, T, V> for CssRuleList<'i, T> {\n  const CHILD_TYPES: VisitTypes = VisitTypes::all();\n\n  fn visit(&mut self, visitor: &mut V) -> Result<(), V::Error> {\n    if visitor.visit_types().contains(VisitTypes::RULES) {\n      visitor.visit_rule_list(self)\n    } else {\n      self.0.visit(visitor)\n    }\n  }\n\n  fn visit_children(&mut self, visitor: &mut V) -> Result<(), V::Error> {\n    self.0.visit(visitor)\n  }\n}\n\npub(crate) struct MinifyContext<'a, 'i> {\n  pub targets: TargetsWithSupportsScope,\n  pub handler: &'a mut DeclarationHandler<'i>,\n  pub important_handler: &'a mut DeclarationHandler<'i>,\n  pub handler_context: PropertyHandlerContext<'i, 'a>,\n  pub unused_symbols: &'a HashSet<String>,\n  pub custom_media: Option<HashMap<CowArcStr<'i>, CustomMediaRule<'i>>>,\n  pub css_modules: bool,\n  pub pure_css_modules: bool,\n}\n\nimpl<'i, T: Clone> CssRuleList<'i, T> {\n  pub(crate) fn minify(\n    &mut self,\n    context: &mut MinifyContext<'_, 'i>,\n    parent_is_unused: bool,\n  ) -> Result<(), MinifyError> {\n    let mut keyframe_rules = HashMap::new();\n    let mut layer_rules = HashMap::new();\n    let mut has_layers = false;\n    let mut property_rules = HashMap::new();\n    let mut font_feature_values_rules = Vec::new();\n    let mut style_rules =\n      HashMap::with_capacity_and_hasher(self.0.len(), BuildHasherDefault::<PrecomputedHasher>::default());\n    let mut rules = Vec::new();\n    for mut rule in self.0.drain(..) {\n      match &mut rule {\n        CssRule::Keyframes(keyframes) => {\n          if context.unused_symbols.contains(match &keyframes.name {\n            KeyframesName::Ident(ident) => ident.0.as_ref(),\n            KeyframesName::Custom(string) => string.as_ref(),\n          }) {\n            continue;\n          }\n          keyframes.minify(context);\n\n          macro_rules! set_prefix {\n            ($keyframes: ident) => {\n              $keyframes.vendor_prefix =\n                context.targets.current.prefixes($keyframes.vendor_prefix, Feature::AtKeyframes);\n            };\n          }\n\n          // Merge @keyframes rules with the same name.\n          if let Some(existing_idx) = keyframe_rules.get(&keyframes.name) {\n            if let Some(CssRule::Keyframes(existing)) = &mut rules.get_mut(*existing_idx) {\n              // If the existing rule has the same vendor prefixes, replace it with this rule.\n              if existing.vendor_prefix == keyframes.vendor_prefix {\n                *existing = keyframes.clone();\n                continue;\n              }\n              // Otherwise, if the keyframes are identical, merge the prefixes.\n              if existing.keyframes == keyframes.keyframes {\n                existing.vendor_prefix |= keyframes.vendor_prefix;\n                set_prefix!(existing);\n                continue;\n              }\n            }\n          }\n\n          set_prefix!(keyframes);\n          keyframe_rules.insert(keyframes.name.clone(), rules.len());\n\n          let fallbacks = keyframes.get_fallbacks(&context.targets.current);\n          rules.push(rule);\n          rules.extend(fallbacks);\n          continue;\n        }\n        CssRule::CustomMedia(_) => {\n          if context.custom_media.is_some() {\n            continue;\n          }\n        }\n        CssRule::Media(media) => {\n          if let Some(CssRule::Media(last_rule)) = rules.last_mut() {\n            if last_rule.query == media.query {\n              last_rule.rules.0.extend(media.rules.0.drain(..));\n              last_rule.minify(context, parent_is_unused)?;\n              continue;\n            }\n          }\n\n          if media.minify(context, parent_is_unused)? {\n            continue;\n          }\n        }\n        CssRule::Supports(supports) => {\n          if let Some(CssRule::Supports(last_rule)) = rules.last_mut() {\n            if last_rule.condition == supports.condition {\n              last_rule.rules.0.extend(supports.rules.0.drain(..));\n              last_rule.minify(context, parent_is_unused)?;\n              continue;\n            }\n          }\n\n          supports.minify(context, parent_is_unused)?;\n          if supports.rules.0.is_empty() {\n            continue;\n          }\n        }\n        CssRule::Container(container) => {\n          if let Some(CssRule::Container(last_rule)) = rules.last_mut() {\n            if last_rule.name == container.name && last_rule.condition == container.condition {\n              last_rule.rules.0.extend(container.rules.0.drain(..));\n              last_rule.minify(context, parent_is_unused)?;\n              continue;\n            }\n          }\n\n          if container.minify(context, parent_is_unused)? {\n            continue;\n          }\n        }\n        CssRule::LayerBlock(layer) => {\n          // Merging non-adjacent layer rules is safe because they are applied\n          // in the order they are first defined.\n          if let Some(name) = &layer.name {\n            if let Some(idx) = layer_rules.get(name) {\n              if let Some(CssRule::LayerBlock(last_rule)) = rules.get_mut(*idx) {\n                last_rule.rules.0.extend(layer.rules.0.drain(..));\n                continue;\n              }\n            }\n\n            layer_rules.insert(name.clone(), rules.len());\n            has_layers = true;\n          }\n        }\n        CssRule::LayerStatement(layer) => {\n          // Create @layer block rules for each declared layer name,\n          // so we can merge other blocks into it later on.\n          for name in &layer.names {\n            if !layer_rules.contains_key(name) {\n              layer_rules.insert(name.clone(), rules.len());\n              has_layers = true;\n              rules.push(CssRule::LayerBlock(LayerBlockRule {\n                name: Some(name.clone()),\n                rules: CssRuleList(vec![]),\n                loc: layer.loc.clone(),\n              }));\n            }\n          }\n          continue;\n        }\n        CssRule::MozDocument(document) => document.minify(context)?,\n        CssRule::Style(style) => {\n          if parent_is_unused || style.minify(context, parent_is_unused)? {\n            continue;\n          }\n\n          // If some of the selectors in this rule are not compatible with the targets,\n          // we need to either wrap in :is() or split them into multiple rules.\n          let incompatible = if style.selectors.0.len() > 1\n            && context.targets.current.should_compile_selectors()\n            && !style.is_compatible(context.targets.current)\n          {\n            // The :is() selector accepts a forgiving selector list, so use that if possible.\n            // Note that :is() does not allow pseudo elements, so we need to check for that.\n            // In addition, :is() takes the highest specificity of its arguments, so if the selectors\n            // have different weights, we need to split them into separate rules as well.\n            if context.targets.current.is_compatible(crate::compat::Feature::IsSelector)\n              && !style.selectors.0.iter().any(|selector| selector.has_pseudo_element())\n              && style.selectors.0.iter().map(|selector| selector.specificity()).all_equal()\n            {\n              style.selectors =\n                SelectorList::new(smallvec![\n                  Component::Is(style.selectors.0.clone().into_boxed_slice()).into()\n                ]);\n              smallvec![]\n            } else {\n              // Otherwise, partition the selectors and keep the compatible ones in this rule.\n              // We will generate additional rules for incompatible selectors later.\n              let (compatible, incompatible) = style\n                .selectors\n                .0\n                .iter()\n                .cloned()\n                .partition::<SmallVec<[Selector; 1]>, _>(|selector| {\n                  let list = SelectorList::new(smallvec![selector.clone()]);\n                  is_compatible(&list.0, context.targets.current)\n                });\n              style.selectors = SelectorList::new(compatible);\n              incompatible\n            }\n          } else {\n            smallvec![]\n          };\n\n          style.update_prefix(context);\n\n          // Attempt to merge the new rule with the last rule we added.\n          let mut merged = false;\n          if let Some(CssRule::Style(last_style_rule)) = rules.last_mut() {\n            if merge_style_rules(style, last_style_rule, context) {\n              // If that was successful, then the last rule has been updated to include the\n              // selectors/declarations of the new rule. This might mean that we can merge it\n              // with the previous rule, so continue trying while we have style rules available.\n              while rules.len() >= 2 {\n                let len = rules.len();\n                let (a, b) = rules.split_at_mut(len - 1);\n                if let (CssRule::Style(last), CssRule::Style(prev)) = (&mut b[0], &mut a[len - 2]) {\n                  if merge_style_rules(last, prev, context) {\n                    // If we were able to merge the last rule into the previous one, remove the last.\n                    rules.pop();\n                    continue;\n                  }\n                }\n                // If we didn't see a style rule, or were unable to merge, stop.\n                break;\n              }\n              merged = true;\n            }\n          }\n\n          // Create additional rules for logical properties, @supports overrides, and incompatible selectors.\n          let supports = context.handler_context.get_supports_rules(&style);\n          let logical = context.handler_context.get_additional_rules(&style);\n\n          let incompatible_rules = incompatible\n            .into_iter()\n            .map(|selector| {\n              // Create a clone of the rule with only the one incompatible selector.\n              let list = SelectorList::new(smallvec![selector]);\n              let mut clone = style.clone();\n              clone.selectors = list;\n              clone.update_prefix(context);\n\n              // Also add rules for logical properties and @supports overrides.\n              let supports = context.handler_context.get_supports_rules(&clone);\n              let logical = context.handler_context.get_additional_rules(&clone);\n              (clone, logical, supports)\n            })\n            .collect::<Vec<_>>();\n\n          context.handler_context.reset();\n\n          // If the rule has nested rules, and we have extra rules to insert such as for logical properties,\n          // we need to split the rule in two so we can insert the extra rules in between the declarations from\n          // the main rule and the nested rules.\n          let nested_rule = if !style.rules.0.is_empty()\n            // can happen if there are no compatible rules, above.\n            && !style.selectors.0.is_empty()\n            && (!logical.is_empty() || !supports.is_empty() || !incompatible_rules.is_empty())\n          {\n            let mut rules = CssRuleList(vec![]);\n            std::mem::swap(&mut style.rules, &mut rules);\n            Some(StyleRule {\n              selectors: style.selectors.clone(),\n              declarations: DeclarationBlock::default(),\n              rules,\n              vendor_prefix: style.vendor_prefix,\n              loc: style.loc,\n            })\n          } else {\n            None\n          };\n\n          if !merged && !style.is_empty() {\n            let source_index = style.loc.source_index;\n            let has_no_rules = style.rules.0.is_empty();\n            let idx = rules.len();\n            rules.push(rule);\n\n            // Check if this rule is a duplicate of an earlier rule, meaning it has\n            // the same selectors and defines the same properties. If so, remove the\n            // earlier rule because this one completely overrides it.\n            if has_no_rules {\n              // SAFETY: StyleRuleKeys never live beyond this method.\n              let key = StyleRuleKey::new(unsafe { &*(&rules as *const _) }, idx);\n              if idx > 0 {\n                if let Some(i) = style_rules.remove(&key) {\n                  if let CssRule::Style(other) = &rules[i] {\n                    // Don't remove the rule if this is a CSS module and the other rule came from a different file.\n                    if !context.css_modules || source_index == other.loc.source_index {\n                      // Only mark the rule as ignored so we don't need to change all of the indices.\n                      rules[i] = CssRule::Ignored;\n                    }\n                  }\n                }\n              }\n\n              style_rules.insert(key, idx);\n            }\n          }\n\n          if !logical.is_empty() {\n            let mut logical = CssRuleList(logical);\n            logical.minify(context, parent_is_unused)?;\n            rules.extend(logical.0)\n          }\n\n          rules.extend(supports);\n          for (rule, logical, supports) in incompatible_rules {\n            if !rule.is_empty() {\n              rules.push(CssRule::Style(rule));\n            }\n            if !logical.is_empty() {\n              let mut logical = CssRuleList(logical);\n              logical.minify(context, parent_is_unused)?;\n              rules.extend(logical.0)\n            }\n            rules.extend(supports);\n          }\n\n          if let Some(nested_rule) = nested_rule {\n            rules.push(CssRule::Style(nested_rule));\n          }\n\n          continue;\n        }\n        CssRule::CounterStyle(counter_style) => {\n          if context.unused_symbols.contains(counter_style.name.0.as_ref()) {\n            continue;\n          }\n        }\n        CssRule::Scope(scope) => scope.minify(context)?,\n        CssRule::Nesting(nesting) => {\n          if nesting.minify(context, parent_is_unused)? {\n            continue;\n          }\n        }\n        CssRule::NestedDeclarations(nested) => {\n          if nested.minify(context, parent_is_unused) {\n            continue;\n          }\n        }\n        CssRule::StartingStyle(rule) => {\n          if rule.minify(context, parent_is_unused)? {\n            continue;\n          }\n        }\n        CssRule::FontPaletteValues(f) => {\n          if context.unused_symbols.contains(f.name.0.as_ref()) {\n            continue;\n          }\n\n          f.minify(context, parent_is_unused);\n\n          let fallbacks = f.get_fallbacks(context.targets.current);\n          rules.push(rule);\n          rules.extend(fallbacks);\n          continue;\n        }\n        CssRule::FontFeatureValues(rule) => {\n          if let Some(index) = font_feature_values_rules\n            .iter()\n            .find(|index| matches!(&rules[**index], CssRule::FontFeatureValues(r) if r.name == rule.name))\n          {\n            if let CssRule::FontFeatureValues(existing) = &mut rules[*index] {\n              existing.merge(rule);\n            }\n            continue;\n          } else {\n            font_feature_values_rules.push(rules.len());\n          }\n        }\n        CssRule::Property(property) => {\n          if context.unused_symbols.contains(property.name.0.as_ref()) {\n            continue;\n          }\n\n          if let Some(index) = property_rules.get(&property.name) {\n            rules[*index] = rule;\n            continue;\n          } else {\n            property_rules.insert(property.name.clone(), rules.len());\n          }\n        }\n        CssRule::Import(_) => {\n          // @layer blocks can't be inlined into layers declared before imports.\n          layer_rules.clear();\n        }\n        _ => {}\n      }\n\n      rules.push(rule)\n    }\n\n    // Optimize @layer rules. Combine subsequent empty layer blocks into a single @layer statement\n    // so that layers are declared in the correct order.\n    if has_layers {\n      let mut declared_layers = HashSet::new();\n      let mut layer_statement = None;\n      for index in 0..rules.len() {\n        match &mut rules[index] {\n          CssRule::LayerBlock(layer) => {\n            if layer.minify(context, parent_is_unused)? {\n              if let Some(name) = &layer.name {\n                if declared_layers.contains(name) {\n                  // Remove empty layer that has already been declared.\n                  rules[index] = CssRule::Ignored;\n                  continue;\n                }\n\n                let name = name.clone();\n                declared_layers.insert(name.clone());\n\n                if let Some(layer_index) = layer_statement {\n                  if let CssRule::LayerStatement(layer) = &mut rules[layer_index] {\n                    // Add name to previous layer statement rule and remove this one.\n                    layer.names.push(name);\n                    rules[index] = CssRule::Ignored;\n                  }\n                } else {\n                  // Create a new layer statement rule to declare the name.\n                  rules[index] = CssRule::LayerStatement(LayerStatementRule {\n                    names: vec![name],\n                    loc: layer.loc,\n                  });\n                  layer_statement = Some(index);\n                }\n              } else {\n                // Remove empty anonymous layer.\n                rules[index] = CssRule::Ignored;\n              }\n            } else {\n              // Non-empty @layer block. Start a new statement.\n              layer_statement = None;\n            }\n          }\n          CssRule::Import(import) => {\n            if let Some(layer) = &import.layer {\n              // Start a new @layer statement so the import layer is in the right order.\n              layer_statement = None;\n              if let Some(name) = layer {\n                declared_layers.insert(name.clone());\n              }\n            }\n          }\n          _ => {}\n        }\n      }\n    }\n\n    self.0 = rules;\n    Ok(())\n  }\n}\n\nfn merge_style_rules<'i, T>(\n  style: &mut StyleRule<'i, T>,\n  last_style_rule: &mut StyleRule<'i, T>,\n  context: &mut MinifyContext<'_, 'i>,\n) -> bool {\n  // Merge declarations if the selectors are equivalent, and both are compatible with all targets.\n  if style.selectors == last_style_rule.selectors\n    && style.is_compatible(context.targets.current)\n    && last_style_rule.is_compatible(context.targets.current)\n    && style.rules.0.is_empty()\n    && last_style_rule.rules.0.is_empty()\n    && (!context.css_modules || style.loc.source_index == last_style_rule.loc.source_index)\n  {\n    last_style_rule\n      .declarations\n      .declarations\n      .extend(style.declarations.declarations.drain(..));\n    last_style_rule\n      .declarations\n      .important_declarations\n      .extend(style.declarations.important_declarations.drain(..));\n    last_style_rule\n      .declarations\n      .minify(context.handler, context.important_handler, &mut context.handler_context);\n    return true;\n  } else if style.declarations == last_style_rule.declarations\n    && style.rules.0.is_empty()\n    && last_style_rule.rules.0.is_empty()\n  {\n    // If both selectors are potentially vendor prefixable, and they are\n    // equivalent minus prefixes, add the prefix to the last rule.\n    if !style.vendor_prefix.is_empty()\n      && !last_style_rule.vendor_prefix.is_empty()\n      && is_equivalent(&style.selectors.0, &last_style_rule.selectors.0)\n    {\n      // If the new rule is unprefixed, replace the prefixes of the last rule.\n      // Otherwise, add the new prefix.\n      if style.vendor_prefix.contains(VendorPrefix::None) && context.targets.current.should_compile_selectors() {\n        last_style_rule.vendor_prefix = style.vendor_prefix;\n      } else {\n        last_style_rule.vendor_prefix |= style.vendor_prefix;\n      }\n      return true;\n    }\n\n    // Append the selectors to the last rule if the declarations are the same, and all selectors are compatible.\n    if style.is_compatible(context.targets.current) && last_style_rule.is_compatible(context.targets.current) {\n      last_style_rule.selectors.0.extend(style.selectors.0.drain(..));\n      if style.vendor_prefix.contains(VendorPrefix::None) && context.targets.current.should_compile_selectors() {\n        last_style_rule.vendor_prefix = style.vendor_prefix;\n      } else {\n        last_style_rule.vendor_prefix |= style.vendor_prefix;\n      }\n      return true;\n    }\n  }\n  false\n}\n\nimpl<'a, 'i, T: ToCss> ToCss for CssRuleList<'i, T> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    let mut first = true;\n    let mut last_without_block = false;\n\n    for (i, rule) in self.0.iter().enumerate() {\n      if let CssRule::Ignored = &rule {\n        continue;\n      }\n\n      // Skip @import rules if collecting dependencies.\n      if let CssRule::Import(rule) = &rule {\n        if dest.remove_imports {\n          let dep = if dest.dependencies.is_some() {\n            Some(Dependency::Import(ImportDependency::new(&rule, dest.filename())))\n          } else {\n            None\n          };\n\n          if let Some(dependencies) = &mut dest.dependencies {\n            dependencies.push(dep.unwrap());\n            continue;\n          }\n        }\n      }\n\n      if first {\n        first = false;\n      } else {\n        if !dest.minify\n          && !(last_without_block\n            && matches!(\n              rule,\n              CssRule::Import(..) | CssRule::Namespace(..) | CssRule::LayerStatement(..)\n            ))\n        {\n          dest.write_char('\\n')?;\n        }\n        dest.newline()?;\n      }\n      rule.to_css(dest)?;\n\n      // If this is an invisible nested declarations rule, and not the last rule in the block, add a semicolon.\n      if dest.minify\n        && !should_compile!(dest.targets.current, Nesting)\n        && matches!(rule, CssRule::NestedDeclarations(_))\n        && i != self.0.len() - 1\n      {\n        dest.write_char(';')?;\n      }\n\n      last_without_block = matches!(\n        rule,\n        CssRule::Import(..) | CssRule::Namespace(..) | CssRule::LayerStatement(..)\n      );\n    }\n\n    Ok(())\n  }\n}\n\nimpl<'i, T> std::ops::Index<usize> for CssRuleList<'i, T> {\n  type Output = CssRule<'i, T>;\n\n  fn index(&self, index: usize) -> &Self::Output {\n    &self.0[index]\n  }\n}\n\nimpl<'i, T> std::ops::IndexMut<usize> for CssRuleList<'i, T> {\n  fn index_mut(&mut self, index: usize) -> &mut Self::Output {\n    &mut self.0[index]\n  }\n}\n\n/// A hasher that expects to be called with a single u64, which is already a hash.\n#[derive(Default)]\nstruct PrecomputedHasher {\n  hash: Option<u64>,\n}\n\nimpl Hasher for PrecomputedHasher {\n  #[inline]\n  fn write(&mut self, _: &[u8]) {\n    unreachable!()\n  }\n\n  #[inline]\n  fn write_u64(&mut self, i: u64) {\n    debug_assert!(self.hash.is_none());\n    self.hash = Some(i);\n  }\n\n  #[inline]\n  fn finish(&self) -> u64 {\n    self.hash.unwrap()\n  }\n}\n\n/// A key to a StyleRule meant for use in a HashMap for quickly detecting duplicates.\n/// It stores a reference to a list and an index so it can access items without cloning\n/// even when the list is reallocated. A hash is also pre-computed for fast lookups.\n#[derive(Clone)]\npub(crate) struct StyleRuleKey<'a, 'i, R> {\n  list: &'a Vec<CssRule<'i, R>>,\n  index: usize,\n  hash: u64,\n}\n\nimpl<'a, 'i, R> StyleRuleKey<'a, 'i, R> {\n  fn new(list: &'a Vec<CssRule<'i, R>>, index: usize) -> Self {\n    let rule = match &list[index] {\n      CssRule::Style(style) => style,\n      _ => unreachable!(),\n    };\n\n    Self {\n      list,\n      index,\n      hash: rule.hash_key(),\n    }\n  }\n}\n\nimpl<'a, 'i, R> PartialEq for StyleRuleKey<'a, 'i, R> {\n  fn eq(&self, other: &Self) -> bool {\n    let rule = match self.list.get(self.index) {\n      Some(CssRule::Style(style)) => style,\n      _ => return false,\n    };\n\n    let other_rule = match other.list.get(other.index) {\n      Some(CssRule::Style(style)) => style,\n      _ => return false,\n    };\n\n    rule.is_duplicate(other_rule)\n  }\n}\n\nimpl<'a, 'i, R> Eq for StyleRuleKey<'a, 'i, R> {}\n\nimpl<'a, 'i, R> std::hash::Hash for StyleRuleKey<'a, 'i, R> {\n  fn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n    state.write_u64(self.hash);\n  }\n}\n"
  },
  {
    "path": "src/rules/namespace.rs",
    "content": "//! The `@namespace` rule.\n\nuse super::Location;\nuse crate::error::PrinterError;\nuse crate::printer::Printer;\nuse crate::traits::ToCss;\nuse crate::values::ident::Ident;\nuse crate::values::string::CSSString;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\n\n/// A [@namespace](https://drafts.csswg.org/css-namespaces/#declaration) rule.\n#[derive(Debug, PartialEq, Clone)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct NamespaceRule<'i> {\n  /// An optional namespace prefix to declare, or `None` to declare the default namespace.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  #[cfg_attr(feature = \"visitor\", skip_visit)]\n  pub prefix: Option<Ident<'i>>,\n  /// The url of the namespace.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  #[cfg_attr(feature = \"visitor\", skip_visit)]\n  pub url: CSSString<'i>,\n  /// The location of the rule in the source file.\n  #[cfg_attr(feature = \"visitor\", skip_visit)]\n  pub loc: Location,\n}\n\nimpl<'i> ToCss for NamespaceRule<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    #[cfg(feature = \"sourcemap\")]\n    dest.add_mapping(self.loc);\n    dest.write_str(\"@namespace \")?;\n    if let Some(prefix) = &self.prefix {\n      prefix.to_css(dest)?;\n      dest.write_char(' ')?;\n    }\n\n    self.url.to_css(dest)?;\n    dest.write_char(';')\n  }\n}\n"
  },
  {
    "path": "src/rules/nesting.rs",
    "content": "//! The `@nest` rule.\n\nuse smallvec::SmallVec;\n\nuse super::style::StyleRule;\nuse super::Location;\nuse super::MinifyContext;\nuse crate::context::DeclarationContext;\nuse crate::declaration::DeclarationBlock;\nuse crate::error::{MinifyError, PrinterError};\nuse crate::parser::DefaultAtRule;\nuse crate::printer::Printer;\nuse crate::targets::should_compile;\nuse crate::traits::ToCss;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\n\n/// A [@nest](https://www.w3.org/TR/css-nesting-1/#at-nest) rule.\n#[derive(Debug, PartialEq, Clone)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub struct NestingRule<'i, R = DefaultAtRule> {\n  /// The style rule that defines the selector and declarations for the `@nest` rule.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  pub style: StyleRule<'i, R>,\n  /// The location of the rule in the source file.\n  #[cfg_attr(feature = \"visitor\", skip_visit)]\n  pub loc: Location,\n}\n\nimpl<'i, T: Clone> NestingRule<'i, T> {\n  pub(crate) fn minify(\n    &mut self,\n    context: &mut MinifyContext<'_, 'i>,\n    parent_is_unused: bool,\n  ) -> Result<bool, MinifyError> {\n    self.style.minify(context, parent_is_unused)\n  }\n}\n\nimpl<'a, 'i, T: ToCss> ToCss for NestingRule<'i, T> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    #[cfg(feature = \"sourcemap\")]\n    dest.add_mapping(self.loc);\n    if dest.context().is_none() {\n      dest.write_str(\"@nest \")?;\n    }\n    self.style.to_css(dest)\n  }\n}\n\n/// A [nested declarations](https://drafts.csswg.org/css-nesting/#nested-declarations-rule) rule.\n#[derive(Debug, PartialEq, Clone)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub struct NestedDeclarationsRule<'i> {\n  /// The style rule that defines the selector and declarations for the `@nest` rule.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  pub declarations: DeclarationBlock<'i>,\n  /// The location of the rule in the source file.\n  #[cfg_attr(feature = \"visitor\", skip_visit)]\n  pub loc: Location,\n}\n\nimpl<'i> NestedDeclarationsRule<'i> {\n  pub(crate) fn minify(&mut self, context: &mut MinifyContext<'_, 'i>, parent_is_unused: bool) -> bool {\n    if parent_is_unused {\n      return true;\n    }\n\n    context.handler_context.context = DeclarationContext::StyleRule;\n    self\n      .declarations\n      .minify(context.handler, context.important_handler, &mut context.handler_context);\n    context.handler_context.context = DeclarationContext::None;\n    return false;\n  }\n}\n\nimpl<'i> ToCss for NestedDeclarationsRule<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    #[cfg(feature = \"sourcemap\")]\n    dest.add_mapping(self.loc);\n\n    if should_compile!(dest.targets.current, Nesting) {\n      if let Some(context) = dest.context() {\n        let has_printable_declarations = self.declarations.has_printable_declarations();\n        if has_printable_declarations {\n          dest.with_parent_context(|dest| context.selectors.to_css(dest))?;\n          dest.whitespace()?;\n          dest.write_char('{')?;\n          dest.indent();\n          dest.newline()?;\n        }\n\n        self\n          .declarations\n          .to_css_declarations(dest, false, &context.selectors, self.loc.source_index)?;\n\n        if has_printable_declarations {\n          dest.dedent();\n          dest.newline()?;\n          dest.write_char('}')?;\n        }\n        return Ok(());\n      }\n    }\n\n    self\n      .declarations\n      .to_css_declarations(dest, false, &parcel_selectors::SelectorList(SmallVec::new()), 0)\n  }\n}\n"
  },
  {
    "path": "src/rules/page.rs",
    "content": "//! The `@page` rule.\n\nuse super::Location;\nuse crate::declaration::{parse_declaration, DeclarationBlock};\nuse crate::error::{ParserError, PrinterError};\nuse crate::macros::enum_property;\nuse crate::printer::Printer;\nuse crate::stylesheet::ParserOptions;\nuse crate::traits::{Parse, ToCss};\nuse crate::values::string::CowArcStr;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse cssparser::*;\n\n/// A [page selector](https://www.w3.org/TR/css-page-3/#typedef-page-selector)\n/// within a `@page` rule.\n///\n/// Either a name or at least one pseudo class is required.\n#[derive(Debug, PartialEq, Clone)]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"camelCase\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct PageSelector<'i> {\n  /// An optional named page type.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  pub name: Option<CowArcStr<'i>>,\n  /// A list of page pseudo classes.\n  pub pseudo_classes: Vec<PagePseudoClass>,\n}\n\nenum_property! {\n  /// A page pseudo class within an `@page` selector.\n  ///\n  /// See [PageSelector](PageSelector).\n  pub enum PagePseudoClass {\n    /// The `:left` pseudo class.\n    Left,\n    /// The `:right` pseudo class.\n    Right,\n    /// The `:first` pseudo class.\n    First,\n    /// The `:last` pseudo class.\n    Last,\n    /// The `:blank` pseudo class.\n    Blank,\n  }\n}\n\nimpl<'i> Parse<'i> for PageSelector<'i> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let name = input.try_parse(|input| input.expect_ident().map(|x| x.into())).ok();\n    let mut pseudo_classes = vec![];\n\n    loop {\n      // Whitespace is not allowed between pseudo classes\n      let state = input.state();\n      match input.next_including_whitespace() {\n        Ok(Token::Colon) => {\n          pseudo_classes.push(PagePseudoClass::parse(input)?);\n        }\n        _ => {\n          input.reset(&state);\n          break;\n        }\n      }\n    }\n\n    if name.is_none() && pseudo_classes.is_empty() {\n      return Err(input.new_custom_error(ParserError::InvalidPageSelector));\n    }\n\n    Ok(PageSelector { name, pseudo_classes })\n  }\n}\n\nenum_property! {\n  /// A [page margin box](https://www.w3.org/TR/css-page-3/#margin-boxes).\n  pub enum PageMarginBox {\n    /// A fixed-size box defined by the intersection of the top and left margins of the page box.\n    TopLeftCorner,\n    /// A variable-width box filling the top page margin between the top-left-corner and top-center page-margin boxes.\n    TopLeft,\n    /// A variable-width box centered horizontally between the page’s left and right border edges and filling the\n    /// page top margin between the top-left and top-right page-margin boxes.\n    TopCenter,\n    /// A variable-width box filling the top page margin between the top-center and top-right-corner page-margin boxes.\n    TopRight,\n    /// A fixed-size box defined by the intersection of the top and right margins of the page box.\n    TopRightCorner,\n    /// A variable-height box filling the left page margin between the top-left-corner and left-middle page-margin boxes.\n    LeftTop,\n    /// A variable-height box centered vertically between the page’s top and bottom border edges and filling the\n    /// left page margin between the left-top and left-bottom page-margin boxes.\n    LeftMiddle,\n    /// A variable-height box filling the left page margin between the left-middle and bottom-left-corner page-margin boxes.\n    LeftBottom,\n    /// A variable-height box filling the right page margin between the top-right-corner and right-middle page-margin boxes.\n    RightTop,\n    /// A variable-height box centered vertically between the page’s top and bottom border edges and filling the right\n    /// page margin between the right-top and right-bottom page-margin boxes.\n    RightMiddle,\n    /// A variable-height box filling the right page margin between the right-middle and bottom-right-corner page-margin boxes.\n    RightBottom,\n    /// A fixed-size box defined by the intersection of the bottom and left margins of the page box.\n    BottomLeftCorner,\n    /// A variable-width box filling the bottom page margin between the bottom-left-corner and bottom-center page-margin boxes.\n    BottomLeft,\n    /// A variable-width box centered horizontally between the page’s left and right border edges and filling the bottom\n    /// page margin between the bottom-left and bottom-right page-margin boxes.\n    BottomCenter,\n    /// A variable-width box filling the bottom page margin between the bottom-center and bottom-right-corner page-margin boxes.\n    BottomRight,\n    /// A fixed-size box defined by the intersection of the bottom and right margins of the page box.\n    BottomRightCorner,\n  }\n}\n\n/// A [page margin rule](https://www.w3.org/TR/css-page-3/#margin-at-rules) rule.\n#[derive(Debug, PartialEq, Clone)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"camelCase\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct PageMarginRule<'i> {\n  /// The margin box identifier for this rule.\n  pub margin_box: PageMarginBox,\n  /// The declarations within the rule.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  pub declarations: DeclarationBlock<'i>,\n  /// The location of the rule in the source file.\n  #[cfg_attr(feature = \"visitor\", skip_visit)]\n  pub loc: Location,\n}\n\nimpl<'i> ToCss for PageMarginRule<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    #[cfg(feature = \"sourcemap\")]\n    dest.add_mapping(self.loc);\n    dest.write_char('@')?;\n    self.margin_box.to_css(dest)?;\n    self.declarations.to_css_block(dest)\n  }\n}\n\n/// A [@page](https://www.w3.org/TR/css-page-3/#at-page-rule) rule.\n#[derive(Debug, PartialEq, Clone)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct PageRule<'i> {\n  /// A list of page selectors.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  #[cfg_attr(feature = \"visitor\", skip_visit)]\n  pub selectors: Vec<PageSelector<'i>>,\n  /// The declarations within the `@page` rule.\n  pub declarations: DeclarationBlock<'i>,\n  /// The nested margin rules.\n  pub rules: Vec<PageMarginRule<'i>>,\n  /// The location of the rule in the source file.\n  #[cfg_attr(feature = \"visitor\", skip_visit)]\n  pub loc: Location,\n}\n\nimpl<'i> PageRule<'i> {\n  pub(crate) fn parse<'t, 'o>(\n    selectors: Vec<PageSelector<'i>>,\n    input: &mut Parser<'i, 't>,\n    loc: Location,\n    options: &ParserOptions<'o, 'i>,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let mut declarations = DeclarationBlock::new();\n    let mut rules = Vec::new();\n    let mut rule_parser = PageRuleParser {\n      declarations: &mut declarations,\n      rules: &mut rules,\n      options: &options,\n    };\n    let mut parser = RuleBodyParser::new(input, &mut rule_parser);\n\n    while let Some(decl) = parser.next() {\n      if let Err((err, _)) = decl {\n        if parser.parser.options.error_recovery {\n          parser.parser.options.warn(err);\n          continue;\n        }\n        return Err(err);\n      }\n    }\n\n    Ok(PageRule {\n      selectors,\n      declarations,\n      rules,\n      loc,\n    })\n  }\n}\n\nimpl<'i> ToCss for PageRule<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    #[cfg(feature = \"sourcemap\")]\n    dest.add_mapping(self.loc);\n    dest.write_str(\"@page\")?;\n    if let Some(first) = self.selectors.first() {\n      // Space is only required if the first selector has a name.\n      if !dest.minify || first.name.is_some() {\n        dest.write_char(' ')?;\n      }\n      let mut first = true;\n      for selector in &self.selectors {\n        if first {\n          first = false;\n        } else {\n          dest.delim(',', false)?;\n        }\n        selector.to_css(dest)?;\n      }\n    }\n\n    dest.whitespace()?;\n    dest.write_char('{')?;\n    dest.indent();\n\n    let mut i = 0;\n    let len = self.declarations.len() + self.rules.len();\n\n    macro_rules! write {\n      ($decls: expr, $important: literal) => {\n        for decl in &$decls {\n          dest.newline()?;\n          decl.to_css(dest, $important)?;\n          if i != len - 1 || !dest.minify {\n            dest.write_char(';')?;\n          }\n          i += 1;\n        }\n      };\n    }\n\n    write!(self.declarations.declarations, false);\n    write!(self.declarations.important_declarations, true);\n\n    if !self.rules.is_empty() {\n      if !dest.minify && self.declarations.len() > 0 {\n        dest.write_char('\\n')?;\n      }\n      dest.newline()?;\n\n      let mut first = true;\n      for rule in &self.rules {\n        if first {\n          first = false;\n        } else {\n          if !dest.minify {\n            dest.write_char('\\n')?;\n          }\n          dest.newline()?;\n        }\n        rule.to_css(dest)?;\n      }\n    }\n\n    dest.dedent();\n    dest.newline()?;\n    dest.write_char('}')\n  }\n}\n\nimpl<'i> ToCss for PageSelector<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    if let Some(name) = &self.name {\n      dest.write_str(&name)?;\n    }\n\n    for pseudo in &self.pseudo_classes {\n      dest.write_char(':')?;\n      pseudo.to_css(dest)?;\n    }\n\n    Ok(())\n  }\n}\n\nstruct PageRuleParser<'a, 'o, 'i> {\n  declarations: &'a mut DeclarationBlock<'i>,\n  rules: &'a mut Vec<PageMarginRule<'i>>,\n  options: &'a ParserOptions<'o, 'i>,\n}\n\nimpl<'a, 'o, 'i> cssparser::DeclarationParser<'i> for PageRuleParser<'a, 'o, 'i> {\n  type Declaration = ();\n  type Error = ParserError<'i>;\n\n  fn parse_value<'t>(\n    &mut self,\n    name: CowRcStr<'i>,\n    input: &mut cssparser::Parser<'i, 't>,\n  ) -> Result<Self::Declaration, cssparser::ParseError<'i, Self::Error>> {\n    parse_declaration(\n      name,\n      input,\n      &mut self.declarations.declarations,\n      &mut self.declarations.important_declarations,\n      &self.options,\n    )\n  }\n}\n\nimpl<'a, 'o, 'i> AtRuleParser<'i> for PageRuleParser<'a, 'o, 'i> {\n  type Prelude = PageMarginBox;\n  type AtRule = ();\n  type Error = ParserError<'i>;\n\n  fn parse_prelude<'t>(\n    &mut self,\n    name: CowRcStr<'i>,\n    input: &mut Parser<'i, 't>,\n  ) -> Result<Self::Prelude, ParseError<'i, Self::Error>> {\n    let loc = input.current_source_location();\n    PageMarginBox::parse_string(&name)\n      .map_err(|_| loc.new_custom_error(ParserError::AtRuleInvalid(name.clone().into())))\n  }\n\n  fn parse_block<'t>(\n    &mut self,\n    prelude: Self::Prelude,\n    start: &ParserState,\n    input: &mut Parser<'i, 't>,\n  ) -> Result<Self::AtRule, ParseError<'i, Self::Error>> {\n    let loc = start.source_location();\n    let declarations = DeclarationBlock::parse(input, self.options)?;\n    self.rules.push(PageMarginRule {\n      margin_box: prelude,\n      declarations,\n      loc: Location {\n        source_index: self.options.source_index,\n        line: loc.line,\n        column: loc.column,\n      },\n    });\n    Ok(())\n  }\n}\n\nimpl<'a, 'o, 'i> QualifiedRuleParser<'i> for PageRuleParser<'a, 'o, 'i> {\n  type Prelude = ();\n  type QualifiedRule = ();\n  type Error = ParserError<'i>;\n}\n\nimpl<'a, 'o, 'i> RuleBodyItemParser<'i, (), ParserError<'i>> for PageRuleParser<'a, 'o, 'i> {\n  fn parse_qualified(&self) -> bool {\n    false\n  }\n\n  fn parse_declarations(&self) -> bool {\n    true\n  }\n}\n"
  },
  {
    "path": "src/rules/property.rs",
    "content": "//! The `@property` rule.\n\nuse super::Location;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse crate::{\n  error::{ParserError, PrinterError},\n  printer::Printer,\n  properties::custom::TokenList,\n  traits::{Parse, ToCss},\n  values::{\n    ident::DashedIdent,\n    syntax::{ParsedComponent, SyntaxString},\n  },\n};\nuse cssparser::*;\n\n/// A [@property](https://drafts.css-houdini.org/css-properties-values-api/#at-property-rule) rule.\n#[derive(Debug, PartialEq, Clone)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"camelCase\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct PropertyRule<'i> {\n  /// The name of the custom property to declare.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  pub name: DashedIdent<'i>,\n  /// A syntax string to specify the grammar for the custom property.\n  #[cfg_attr(feature = \"visitor\", skip_visit)]\n  pub syntax: SyntaxString,\n  /// Whether the custom property is inherited.\n  #[cfg_attr(feature = \"visitor\", skip_visit)]\n  pub inherits: bool,\n  /// An optional initial value for the custom property.\n  #[cfg_attr(feature = \"visitor\", skip_visit)]\n  pub initial_value: Option<ParsedComponent<'i>>,\n  /// The location of the rule in the source file.\n  #[cfg_attr(feature = \"visitor\", skip_visit)]\n  pub loc: Location,\n}\n\nimpl<'i> PropertyRule<'i> {\n  pub(crate) fn parse<'t>(\n    name: DashedIdent<'i>,\n    input: &mut Parser<'i, 't>,\n    loc: Location,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let mut parser = PropertyRuleDeclarationParser {\n      syntax: None,\n      inherits: None,\n      initial_value: None,\n    };\n\n    let mut decl_parser = RuleBodyParser::new(input, &mut parser);\n    while let Some(decl) = decl_parser.next() {\n      match decl {\n        Ok(()) => {}\n        Err((e, _)) => return Err(e),\n      }\n    }\n\n    // `syntax` and `inherits` are always required.\n    let parser = decl_parser.parser;\n    let syntax = parser\n      .syntax\n      .clone()\n      .ok_or(decl_parser.input.new_custom_error(ParserError::AtRuleBodyInvalid))?;\n    let inherits = parser\n      .inherits\n      .clone()\n      .ok_or(decl_parser.input.new_custom_error(ParserError::AtRuleBodyInvalid))?;\n\n    // `initial-value` is required unless the syntax is a universal definition.\n    let initial_value = match syntax {\n      SyntaxString::Universal => match parser.initial_value {\n        None => None,\n        Some(val) => {\n          let mut input = ParserInput::new(val);\n          let mut parser = Parser::new(&mut input);\n\n          if parser.is_exhausted() {\n            Some(ParsedComponent::TokenList(TokenList(vec![])))\n          } else {\n            Some(syntax.parse_value(&mut parser)?)\n          }\n        }\n      },\n      _ => {\n        let val = parser\n          .initial_value\n          .ok_or(input.new_custom_error(ParserError::AtRuleBodyInvalid))?;\n        let mut input = ParserInput::new(val);\n        let mut parser = Parser::new(&mut input);\n        Some(syntax.parse_value(&mut parser)?)\n      }\n    };\n\n    return Ok(PropertyRule {\n      name,\n      syntax,\n      inherits,\n      initial_value,\n      loc,\n    });\n  }\n}\n\nimpl<'i> ToCss for PropertyRule<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    #[cfg(feature = \"sourcemap\")]\n    dest.add_mapping(self.loc);\n    dest.write_str(\"@property \")?;\n    self.name.to_css(dest)?;\n    dest.whitespace()?;\n    dest.write_char('{')?;\n    dest.indent();\n    dest.newline()?;\n\n    dest.write_str(\"syntax:\")?;\n    dest.whitespace()?;\n    self.syntax.to_css(dest)?;\n    dest.write_char(';')?;\n    dest.newline()?;\n\n    dest.write_str(\"inherits:\")?;\n    dest.whitespace()?;\n    match self.inherits {\n      true => dest.write_str(\"true\")?,\n      false => dest.write_str(\"false\")?,\n    }\n\n    if let Some(initial_value) = &self.initial_value {\n      dest.write_char(';')?;\n      dest.newline()?;\n\n      dest.write_str(\"initial-value:\")?;\n      dest.whitespace()?;\n      initial_value.to_css(dest)?;\n\n      if !dest.minify {\n        dest.write_char(';')?;\n      }\n    }\n\n    dest.dedent();\n    dest.newline()?;\n    dest.write_char('}')\n  }\n}\n\npub(crate) struct PropertyRuleDeclarationParser<'i> {\n  syntax: Option<SyntaxString>,\n  inherits: Option<bool>,\n  initial_value: Option<&'i str>,\n}\n\nimpl<'i> cssparser::DeclarationParser<'i> for PropertyRuleDeclarationParser<'i> {\n  type Declaration = ();\n  type Error = ParserError<'i>;\n\n  fn parse_value<'t>(\n    &mut self,\n    name: CowRcStr<'i>,\n    input: &mut cssparser::Parser<'i, 't>,\n  ) -> Result<Self::Declaration, cssparser::ParseError<'i, Self::Error>> {\n    match_ignore_ascii_case! { &name,\n      \"syntax\" => {\n        let syntax = SyntaxString::parse(input)?;\n        self.syntax = Some(syntax);\n      },\n      \"inherits\" => {\n        let location = input.current_source_location();\n        let ident = input.expect_ident()?;\n        let inherits = match_ignore_ascii_case! {&*ident,\n          \"true\" => true,\n          \"false\" => false,\n          _ => return Err(location.new_unexpected_token_error(\n            cssparser::Token::Ident(ident.clone())\n          ))\n        };\n        self.inherits = Some(inherits);\n      },\n      \"initial-value\" => {\n        // Buffer the value into a string. We will parse it later.\n        let start = input.position();\n        while input.next().is_ok() {}\n        let initial_value = input.slice_from(start);\n        self.initial_value = Some(initial_value);\n      },\n      _ => return Err(input.new_custom_error(ParserError::InvalidDeclaration))\n    }\n\n    return Ok(());\n  }\n}\n\n/// Default methods reject all at rules.\nimpl<'i> AtRuleParser<'i> for PropertyRuleDeclarationParser<'i> {\n  type Prelude = ();\n  type AtRule = ();\n  type Error = ParserError<'i>;\n}\n\nimpl<'i> QualifiedRuleParser<'i> for PropertyRuleDeclarationParser<'i> {\n  type Prelude = ();\n  type QualifiedRule = ();\n  type Error = ParserError<'i>;\n}\n\nimpl<'i> RuleBodyItemParser<'i, (), ParserError<'i>> for PropertyRuleDeclarationParser<'i> {\n  fn parse_qualified(&self) -> bool {\n    false\n  }\n\n  fn parse_declarations(&self) -> bool {\n    true\n  }\n}\n"
  },
  {
    "path": "src/rules/scope.rs",
    "content": "//! The `@scope` rule.\n\nuse super::Location;\nuse super::{CssRuleList, MinifyContext};\nuse crate::error::{MinifyError, PrinterError};\nuse crate::parser::DefaultAtRule;\nuse crate::printer::Printer;\nuse crate::selector::{is_pure_css_modules_selector, SelectorList};\nuse crate::traits::ToCss;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\n\n/// A [@scope](https://drafts.csswg.org/css-cascade-6/#scope-atrule) rule.\n///\n/// @scope (<scope-start>) [to (<scope-end>)]? {\n///  <stylesheet>\n/// }\n#[derive(Debug, PartialEq, Clone)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"camelCase\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub struct ScopeRule<'i, R = DefaultAtRule> {\n  /// A selector list used to identify the scoping root(s).\n  pub scope_start: Option<SelectorList<'i>>,\n  /// A selector list used to identify any scoping limits.\n  pub scope_end: Option<SelectorList<'i>>,\n  /// Nested rules within the `@scope` rule.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  pub rules: CssRuleList<'i, R>,\n  /// The location of the rule in the source file.\n  #[cfg_attr(feature = \"visitor\", skip_visit)]\n  pub loc: Location,\n}\n\nimpl<'i, T: Clone> ScopeRule<'i, T> {\n  pub(crate) fn minify(&mut self, context: &mut MinifyContext<'_, 'i>) -> Result<(), MinifyError> {\n    if context.pure_css_modules {\n      if let Some(scope_start) = &self.scope_start {\n        if !scope_start.0.iter().all(is_pure_css_modules_selector) {\n          return Err(MinifyError {\n            kind: crate::error::MinifyErrorKind::ImpureCSSModuleSelector,\n            loc: self.loc,\n          });\n        }\n      }\n\n      if let Some(scope_end) = &self.scope_end {\n        if !scope_end.0.iter().all(is_pure_css_modules_selector) {\n          return Err(MinifyError {\n            kind: crate::error::MinifyErrorKind::ImpureCSSModuleSelector,\n            loc: self.loc,\n          });\n        }\n      }\n    }\n\n    self.rules.minify(context, false)\n  }\n}\n\nimpl<'i, T: ToCss> ToCss for ScopeRule<'i, T> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    #[cfg(feature = \"sourcemap\")]\n    dest.add_mapping(self.loc);\n    dest.write_str(\"@scope\")?;\n    dest.whitespace()?;\n    if let Some(scope_start) = &self.scope_start {\n      dest.write_char('(')?;\n      scope_start.to_css(dest)?;\n      dest.write_char(')')?;\n      dest.whitespace()?;\n    }\n    if let Some(scope_end) = &self.scope_end {\n      if dest.minify {\n        dest.write_char(' ')?;\n      }\n      dest.write_str(\"to (\")?;\n      // <scope-start> is treated as an ancestor of scope end.\n      // https://drafts.csswg.org/css-nesting/#nesting-at-scope\n      if let Some(scope_start) = &self.scope_start {\n        dest.with_context(scope_start, |dest| scope_end.to_css(dest))?;\n      } else {\n        scope_end.to_css(dest)?;\n      }\n      dest.write_char(')')?;\n      dest.whitespace()?;\n    }\n    dest.write_char('{')?;\n    dest.indent();\n    dest.newline()?;\n    // Nested style rules within @scope are implicitly relative to the <scope-start>\n    // so clear our style context while printing them to avoid replacing & ourselves.\n    // https://drafts.csswg.org/css-cascade-6/#scoped-rules\n    dest.with_cleared_context(|dest| self.rules.to_css(dest))?;\n    dest.dedent();\n    dest.newline()?;\n    dest.write_char('}')\n  }\n}\n"
  },
  {
    "path": "src/rules/starting_style.rs",
    "content": "//! The `@starting-style` rule.\n\nuse super::Location;\nuse super::{CssRuleList, MinifyContext};\nuse crate::error::{MinifyError, PrinterError};\nuse crate::parser::DefaultAtRule;\nuse crate::printer::Printer;\nuse crate::traits::ToCss;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\n\n/// A [@starting-style](https://drafts.csswg.org/css-transitions-2/#defining-before-change-style-the-starting-style-rule) rule.\n#[derive(Debug, PartialEq, Clone)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub struct StartingStyleRule<'i, R = DefaultAtRule> {\n  /// Nested rules within the `@starting-style` rule.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  pub rules: CssRuleList<'i, R>,\n  /// The location of the rule in the source file.\n  #[cfg_attr(feature = \"visitor\", skip_visit)]\n  pub loc: Location,\n}\n\nimpl<'i, T: Clone> StartingStyleRule<'i, T> {\n  pub(crate) fn minify(\n    &mut self,\n    context: &mut MinifyContext<'_, 'i>,\n    parent_is_unused: bool,\n  ) -> Result<bool, MinifyError> {\n    self.rules.minify(context, parent_is_unused)?;\n    Ok(self.rules.0.is_empty())\n  }\n}\n\nimpl<'i, T: ToCss> ToCss for StartingStyleRule<'i, T> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    #[cfg(feature = \"sourcemap\")]\n    dest.add_mapping(self.loc);\n    dest.write_str(\"@starting-style\")?;\n    dest.whitespace()?;\n    dest.write_char('{')?;\n    dest.indent();\n    dest.newline()?;\n    self.rules.to_css(dest)?;\n    dest.dedent();\n    dest.newline()?;\n    dest.write_char('}')\n  }\n}\n"
  },
  {
    "path": "src/rules/style.rs",
    "content": "//! Style rules.\n\nuse std::hash::{Hash, Hasher};\nuse std::ops::Range;\n\nuse super::Location;\nuse super::MinifyContext;\nuse crate::context::DeclarationContext;\nuse crate::declaration::DeclarationBlock;\nuse crate::error::ParserError;\nuse crate::error::{MinifyError, PrinterError};\nuse crate::parser::DefaultAtRule;\nuse crate::printer::Printer;\nuse crate::rules::CssRuleList;\nuse crate::selector::{\n  downlevel_selectors, get_prefix, is_compatible, is_pure_css_modules_selector, is_unused, SelectorList,\n};\nuse crate::targets::{should_compile, Targets};\nuse crate::traits::ToCss;\nuse crate::vendor_prefix::VendorPrefix;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse cssparser::*;\n\n/// A CSS [style rule](https://drafts.csswg.org/css-syntax/#style-rules).\n#[derive(Debug, PartialEq, Clone)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub struct StyleRule<'i, R = DefaultAtRule> {\n  /// The selectors for the style rule.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  pub selectors: SelectorList<'i>,\n  /// A vendor prefix override, used during selector printing.\n  #[cfg_attr(feature = \"serde\", serde(skip, default = \"VendorPrefix::empty\"))]\n  #[cfg_attr(feature = \"visitor\", skip_visit)]\n  pub vendor_prefix: VendorPrefix,\n  /// The declarations within the style rule.\n  #[cfg_attr(feature = \"serde\", serde(default))]\n  pub declarations: DeclarationBlock<'i>,\n  /// Nested rules within the style rule.\n  #[cfg_attr(feature = \"serde\", serde(default = \"default_rule_list::<R>\"))]\n  pub rules: CssRuleList<'i, R>,\n  /// The location of the rule in the source file.\n  #[cfg_attr(feature = \"visitor\", skip_visit)]\n  pub loc: Location,\n}\n\n#[cfg(feature = \"serde\")]\nfn default_rule_list<'i, R>() -> CssRuleList<'i, R> {\n  CssRuleList(Vec::new())\n}\n\nimpl<'i, T: Clone> StyleRule<'i, T> {\n  pub(crate) fn minify(\n    &mut self,\n    context: &mut MinifyContext<'_, 'i>,\n    parent_is_unused: bool,\n  ) -> Result<bool, MinifyError> {\n    let mut unused = false;\n    if !context.unused_symbols.is_empty() {\n      if is_unused(&mut self.selectors.0.iter(), &context.unused_symbols, parent_is_unused) {\n        if self.rules.0.is_empty() {\n          return Ok(true);\n        }\n\n        self.declarations.declarations.clear();\n        self.declarations.important_declarations.clear();\n        unused = true;\n      }\n    }\n\n    let pure_css_modules = context.pure_css_modules;\n    if context.pure_css_modules {\n      if !self.selectors.0.iter().all(is_pure_css_modules_selector) {\n        return Err(MinifyError {\n          kind: crate::error::MinifyErrorKind::ImpureCSSModuleSelector,\n          loc: self.loc,\n        });\n      }\n\n      // Parent rule contained id or class, so child rules don't need to.\n      context.pure_css_modules = false;\n    }\n\n    context.handler_context.context = DeclarationContext::StyleRule;\n    self\n      .declarations\n      .minify(context.handler, context.important_handler, &mut context.handler_context);\n    context.handler_context.context = DeclarationContext::None;\n\n    if !self.rules.0.is_empty() {\n      let mut handler_context = context.handler_context.child(DeclarationContext::StyleRule);\n      std::mem::swap(&mut context.handler_context, &mut handler_context);\n      self.rules.minify(context, unused)?;\n      context.handler_context = handler_context;\n      if unused && self.rules.0.is_empty() {\n        return Ok(true);\n      }\n    }\n\n    context.pure_css_modules = pure_css_modules;\n    Ok(false)\n  }\n}\n\nimpl<'i, T> StyleRule<'i, T> {\n  /// Returns whether the rule is empty.\n  pub fn is_empty(&self) -> bool {\n    self.selectors.0.is_empty() || (self.declarations.is_empty() && self.rules.0.is_empty())\n  }\n\n  /// Returns whether the selectors in the rule are compatible\n  /// with all of the given browser targets.\n  pub fn is_compatible(&self, targets: Targets) -> bool {\n    is_compatible(&self.selectors.0, targets)\n  }\n\n  /// Returns the line and column range of the property key and value at the given index in this style rule.\n  ///\n  /// For performance and memory efficiency in non-error cases, source locations are not stored during parsing.\n  /// Instead, they are computed lazily using the original source string that was used to parse the stylesheet/rule.\n  pub fn property_location<'t>(\n    &self,\n    code: &'i str,\n    index: usize,\n  ) -> Result<(Range<SourceLocation>, Range<SourceLocation>), ParseError<'i, ParserError<'i>>> {\n    let mut input = ParserInput::new(code);\n    let mut parser = Parser::new(&mut input);\n\n    // advance until start location of this rule.\n    parse_at(&mut parser, self.loc, |parser| {\n      // skip selector\n      parser.parse_until_before(Delimiter::CurlyBracketBlock, |parser| {\n        while parser.next().is_ok() {}\n        Ok(())\n      })?;\n\n      parser.expect_curly_bracket_block()?;\n      parser.parse_nested_block(|parser| {\n        let loc = self.declarations.property_location(parser, index);\n        while parser.next().is_ok() {}\n        loc\n      })\n    })\n  }\n\n  /// Returns a hash of this rule for use when deduplicating.\n  /// Includes the selectors and properties.\n  #[inline]\n  pub(crate) fn hash_key(&self) -> u64 {\n    let mut hasher = ahash::AHasher::default();\n    self.selectors.hash(&mut hasher);\n    for (property, _) in self.declarations.iter() {\n      property.property_id().hash(&mut hasher);\n    }\n    hasher.finish()\n  }\n\n  /// Returns whether this rule is a duplicate of another rule.\n  /// This means it has the same selectors and properties.\n  #[inline]\n  pub(crate) fn is_duplicate(&self, other_rule: &StyleRule<'i, T>) -> bool {\n    self.declarations.len() == other_rule.declarations.len()\n      && self.selectors == other_rule.selectors\n      && self\n        .declarations\n        .iter()\n        .zip(other_rule.declarations.iter())\n        .all(|((a, _), (b, _))| a.property_id() == b.property_id())\n  }\n\n  pub(crate) fn update_prefix(&mut self, context: &mut MinifyContext<'_, 'i>) {\n    self.vendor_prefix = get_prefix(&self.selectors);\n    if self.vendor_prefix.contains(VendorPrefix::None) && context.targets.current.should_compile_selectors() {\n      self.vendor_prefix = downlevel_selectors(self.selectors.0.as_mut_slice(), context.targets.current);\n    }\n  }\n}\n\nfn parse_at<'i, 't, T, F>(\n  parser: &mut Parser<'i, 't>,\n  dest: Location,\n  parse: F,\n) -> Result<T, ParseError<'i, ParserError<'i>>>\nwhere\n  F: Copy + for<'tt> FnOnce(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i, ParserError<'i>>>,\n{\n  loop {\n    let loc = parser.current_source_location();\n    if loc.line >= dest.line || (loc.line == dest.line && loc.column >= dest.column) {\n      return parse(parser);\n    }\n\n    match parser.next()? {\n      Token::CurlyBracketBlock => {\n        // Recursively parse nested blocks.\n        let res = parser.parse_nested_block(|parser| {\n          let res = parse_at(parser, dest, parse);\n          while parser.next().is_ok() {}\n          res\n        });\n\n        if let Ok(v) = res {\n          return Ok(v);\n        }\n      }\n      _ => {}\n    }\n  }\n}\n\nimpl<'a, 'i, T: ToCss> ToCss for StyleRule<'i, T> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    if self.vendor_prefix.is_empty() {\n      self.to_css_base(dest)\n    } else {\n      let mut first_rule = true;\n      for prefix in self.vendor_prefix {\n        if first_rule {\n          first_rule = false;\n        } else {\n          if !dest.minify {\n            dest.write_char('\\n')?; // no indent\n          }\n          dest.newline()?;\n        }\n        dest.vendor_prefix = prefix;\n        self.to_css_base(dest)?;\n      }\n\n      dest.vendor_prefix = VendorPrefix::empty();\n      Ok(())\n    }\n  }\n}\n\nimpl<'a, 'i, T: ToCss> StyleRule<'i, T> {\n  fn to_css_base<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    // If supported, or there are no targets, preserve nesting. Otherwise, write nested rules after parent.\n    let supports_nesting = self.rules.0.is_empty() || !should_compile!(dest.targets.current, Nesting);\n    let len = self.declarations.declarations.len() + self.declarations.important_declarations.len();\n    let has_declarations = supports_nesting || len > 0 || self.rules.0.is_empty();\n\n    if has_declarations {\n      #[cfg(feature = \"sourcemap\")]\n      dest.add_mapping(self.loc);\n      self.selectors.to_css(dest)?;\n      dest.whitespace()?;\n      dest.write_char('{')?;\n      dest.indent();\n      if len > 0 {\n        dest.newline()?;\n      }\n\n      self.declarations.to_css_declarations(\n        dest,\n        supports_nesting && !self.rules.0.is_empty(),\n        &self.selectors,\n        self.loc.source_index,\n      )?;\n    }\n\n    macro_rules! newline {\n      () => {\n        if !dest.minify && (supports_nesting || len > 0) && !self.rules.0.is_empty() {\n          if len > 0 {\n            dest.write_char('\\n')?;\n          }\n          dest.newline()?;\n        }\n      };\n    }\n\n    macro_rules! end {\n      () => {\n        if has_declarations {\n          dest.dedent();\n          dest.newline()?;\n          dest.write_char('}')?;\n        }\n      };\n    }\n\n    // Write nested rules after the parent.\n    if supports_nesting {\n      newline!();\n      self.rules.to_css(dest)?;\n      end!();\n    } else {\n      end!();\n      newline!();\n      dest.with_context(&self.selectors, |dest| self.rules.to_css(dest))?;\n    }\n\n    Ok(())\n  }\n}\n"
  },
  {
    "path": "src/rules/supports.rs",
    "content": "//! The `@supports` rule.\n\nuse std::collections::HashMap;\n\nuse super::Location;\nuse super::{CssRuleList, MinifyContext};\nuse crate::error::{MinifyError, ParserError, PrinterError};\nuse crate::parser::DefaultAtRule;\nuse crate::printer::Printer;\nuse crate::properties::custom::TokenList;\nuse crate::properties::PropertyId;\nuse crate::targets::{Features, FeaturesIterator, Targets};\nuse crate::traits::{Parse, ToCss};\nuse crate::values::string::CowArcStr;\nuse crate::vendor_prefix::VendorPrefix;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse cssparser::*;\n\n#[cfg(feature = \"serde\")]\nuse crate::serialization::ValueWrapper;\n\n/// A [@supports](https://drafts.csswg.org/css-conditional-3/#at-supports) rule.\n#[derive(Debug, PartialEq, Clone)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub struct SupportsRule<'i, R = DefaultAtRule> {\n  /// The supports condition.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  pub condition: SupportsCondition<'i>,\n  /// The rules within the `@supports` rule.\n  pub rules: CssRuleList<'i, R>,\n  /// The location of the rule in the source file.\n  #[cfg_attr(feature = \"visitor\", skip_visit)]\n  pub loc: Location,\n}\n\nimpl<'i, T: Clone> SupportsRule<'i, T> {\n  pub(crate) fn minify(\n    &mut self,\n    context: &mut MinifyContext<'_, 'i>,\n    parent_is_unused: bool,\n  ) -> Result<(), MinifyError> {\n    let inserted = context.targets.enter_supports(self.condition.get_supported_features());\n    if inserted {\n      context.handler_context.targets = context.targets.current;\n    }\n\n    self.condition.set_prefixes_for_targets(&context.targets.current);\n    let result = self.rules.minify(context, parent_is_unused);\n\n    if inserted {\n      context.targets.exit_supports();\n      context.handler_context.targets = context.targets.current;\n    }\n    result\n  }\n}\n\nimpl<'a, 'i, T: ToCss> ToCss for SupportsRule<'i, T> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    #[cfg(feature = \"sourcemap\")]\n    dest.add_mapping(self.loc);\n    dest.write_str(\"@supports \")?;\n    self.condition.to_css(dest)?;\n    dest.whitespace()?;\n    dest.write_char('{')?;\n    dest.indent();\n    dest.newline()?;\n\n    let inserted = dest.targets.enter_supports(self.condition.get_supported_features());\n    self.rules.to_css(dest)?;\n    if inserted {\n      dest.targets.exit_supports();\n    }\n\n    dest.dedent();\n    dest.newline()?;\n    dest.write_char('}')\n  }\n}\n\n/// A [`<supports-condition>`](https://drafts.csswg.org/css-conditional-3/#typedef-supports-condition),\n/// as used in the `@supports` and `@import` rules.\n#[derive(Debug, PartialEq, Clone)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(feature = \"visitor\", visit(visit_supports_condition, SUPPORTS_CONDITIONS))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub enum SupportsCondition<'i> {\n  /// A `not` expression.\n  #[cfg_attr(feature = \"visitor\", skip_type)]\n  #[cfg_attr(feature = \"serde\", serde(with = \"ValueWrapper::<Box<SupportsCondition>>\"))]\n  Not(Box<SupportsCondition<'i>>),\n  /// An `and` expression.\n  #[cfg_attr(feature = \"visitor\", skip_type)]\n  #[cfg_attr(feature = \"serde\", serde(with = \"ValueWrapper::<Vec<SupportsCondition>>\"))]\n  And(Vec<SupportsCondition<'i>>),\n  /// An `or` expression.\n  #[cfg_attr(feature = \"visitor\", skip_type)]\n  #[cfg_attr(feature = \"serde\", serde(with = \"ValueWrapper::<Vec<SupportsCondition>>\"))]\n  Or(Vec<SupportsCondition<'i>>),\n  /// A declaration to evaluate.\n  Declaration {\n    /// The property id for the declaration.\n    #[cfg_attr(feature = \"serde\", serde(borrow, rename = \"propertyId\"))]\n    property_id: PropertyId<'i>,\n    /// The raw value of the declaration.\n    value: CowArcStr<'i>,\n  },\n  /// A selector to evaluate.\n  #[cfg_attr(feature = \"serde\", serde(with = \"ValueWrapper::<CowArcStr>\"))]\n  Selector(CowArcStr<'i>),\n  // FontTechnology()\n  /// An unknown condition.\n  #[cfg_attr(feature = \"serde\", serde(with = \"ValueWrapper::<CowArcStr>\"))]\n  Unknown(CowArcStr<'i>),\n}\n\nimpl<'i> SupportsCondition<'i> {\n  /// Combines the given supports condition into this one with an `and` expression.\n  pub fn and(&mut self, b: &SupportsCondition<'i>) {\n    if let SupportsCondition::And(a) = self {\n      if !a.contains(&b) {\n        a.push(b.clone());\n      }\n    } else if self != b {\n      *self = SupportsCondition::And(vec![self.clone(), b.clone()])\n    }\n  }\n\n  /// Combines the given supports condition into this one with an `or` expression.\n  pub fn or(&mut self, b: &SupportsCondition<'i>) {\n    if let SupportsCondition::Or(a) = self {\n      if !a.contains(&b) {\n        a.push(b.clone());\n      }\n    } else if self != b {\n      *self = SupportsCondition::Or(vec![self.clone(), b.clone()])\n    }\n  }\n\n  fn set_prefixes_for_targets(&mut self, targets: &Targets) {\n    match self {\n      SupportsCondition::Not(cond) => cond.set_prefixes_for_targets(targets),\n      SupportsCondition::And(items) | SupportsCondition::Or(items) => {\n        for item in items {\n          item.set_prefixes_for_targets(targets);\n        }\n      }\n      SupportsCondition::Declaration { property_id, .. } => {\n        let prefix = property_id.prefix();\n        if prefix.is_empty() || prefix.contains(VendorPrefix::None) {\n          property_id.set_prefixes_for_targets(*targets);\n        }\n      }\n      _ => {}\n    }\n  }\n\n  fn get_supported_features(&self) -> Features {\n    fn get_supported_features_internal(value: &SupportsCondition) -> Option<Features> {\n      match value {\n        SupportsCondition::And(list) => list.iter().map(|c| get_supported_features_internal(c)).try_union_all(),\n        SupportsCondition::Declaration { value, .. } => {\n          let mut input = ParserInput::new(&value);\n          let mut parser = Parser::new(&mut input);\n          if let Ok(tokens) = TokenList::parse(&mut parser, &Default::default(), 0) {\n            Some(tokens.get_features())\n          } else {\n            Some(Features::empty())\n          }\n        }\n        // bail out if \"not\" or \"or\" exists for now\n        SupportsCondition::Not(_) | SupportsCondition::Or(_) => None,\n        SupportsCondition::Selector(_) | SupportsCondition::Unknown(_) => Some(Features::empty()),\n      }\n    }\n\n    get_supported_features_internal(self).unwrap_or(Features::empty())\n  }\n}\n\nimpl<'i> Parse<'i> for SupportsCondition<'i> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    if input.try_parse(|input| input.expect_ident_matching(\"not\")).is_ok() {\n      let in_parens = Self::parse_in_parens(input)?;\n      return Ok(SupportsCondition::Not(Box::new(in_parens)));\n    }\n\n    let in_parens = Self::parse_in_parens(input)?;\n    let mut expected_type = None;\n    let mut conditions = Vec::new();\n    let mut seen_declarations = HashMap::new();\n\n    loop {\n      let condition = input.try_parse(|input| {\n        let location = input.current_source_location();\n        let s = input.expect_ident()?;\n        let found_type = match_ignore_ascii_case! { &s,\n          \"and\" => 1,\n          \"or\" => 2,\n          _ => return Err(location.new_unexpected_token_error(\n            cssparser::Token::Ident(s.clone())\n          ))\n        };\n\n        if let Some(expected) = expected_type {\n          if found_type != expected {\n            return Err(location.new_unexpected_token_error(cssparser::Token::Ident(s.clone())));\n          }\n        } else {\n          expected_type = Some(found_type);\n        }\n\n        Self::parse_in_parens(input)\n      });\n\n      if let Ok(condition) = condition {\n        if conditions.is_empty() {\n          conditions.push(in_parens.clone());\n          if let SupportsCondition::Declaration { property_id, value } = &in_parens {\n            seen_declarations.insert((property_id.with_prefix(VendorPrefix::None), value.clone()), 0);\n          }\n        }\n\n        if let SupportsCondition::Declaration { property_id, value } = condition {\n          // Merge multiple declarations with the same property id (minus prefix) and value together.\n          let property_id = property_id.with_prefix(VendorPrefix::None);\n          let key = (property_id.clone(), value.clone());\n          if let Some(index) = seen_declarations.get(&key) {\n            if let SupportsCondition::Declaration {\n              property_id: cur_property,\n              ..\n            } = &mut conditions[*index]\n            {\n              cur_property.add_prefix(property_id.prefix());\n            }\n          } else {\n            seen_declarations.insert(key, conditions.len());\n            conditions.push(SupportsCondition::Declaration { property_id, value });\n          }\n        } else {\n          conditions.push(condition);\n        }\n      } else {\n        break;\n      }\n    }\n\n    if conditions.len() == 1 {\n      return Ok(conditions.pop().unwrap());\n    }\n\n    match expected_type {\n      Some(1) => Ok(SupportsCondition::And(conditions)),\n      Some(2) => Ok(SupportsCondition::Or(conditions)),\n      _ => Ok(in_parens),\n    }\n  }\n}\n\nimpl<'i> SupportsCondition<'i> {\n  fn parse_in_parens<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    input.skip_whitespace();\n    let location = input.current_source_location();\n    let pos = input.position();\n    match input.next()? {\n      Token::Function(ref f) => {\n        match_ignore_ascii_case! { &*f,\n          \"selector\" => {\n            let res = input.try_parse(|input| {\n              input.parse_nested_block(|input| {\n                let pos = input.position();\n                input.expect_no_error_token()?;\n                Ok(SupportsCondition::Selector(input.slice_from(pos).into()))\n              })\n            });\n            if res.is_ok() {\n              return res\n            }\n          },\n          _ => {}\n        }\n      }\n      Token::ParenthesisBlock => {\n        let res = input.try_parse(|input| {\n          input.parse_nested_block(|input| {\n            if let Ok(condition) = input.try_parse(SupportsCondition::parse) {\n              return Ok(condition);\n            }\n\n            Self::parse_declaration(input)\n          })\n        });\n        if res.is_ok() {\n          return res;\n        }\n      }\n      t => return Err(location.new_unexpected_token_error(t.clone())),\n    };\n\n    input.parse_nested_block(|input| input.expect_no_error_token().map_err(|err| err.into()))?;\n    Ok(SupportsCondition::Unknown(input.slice_from(pos).into()))\n  }\n\n  pub(crate) fn parse_declaration<'t>(\n    input: &mut Parser<'i, 't>,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let property_id = PropertyId::parse(input)?;\n    input.expect_colon()?;\n    input.skip_whitespace();\n    let pos = input.position();\n    input.expect_no_error_token()?;\n    Ok(SupportsCondition::Declaration {\n      property_id,\n      value: input.slice_from(pos).into(),\n    })\n  }\n\n  fn needs_parens(&self, parent: &SupportsCondition) -> bool {\n    match self {\n      SupportsCondition::Not(_) => true,\n      SupportsCondition::And(_) => !matches!(parent, SupportsCondition::And(_)),\n      SupportsCondition::Or(_) => !matches!(parent, SupportsCondition::Or(_)),\n      _ => false,\n    }\n  }\n\n  fn to_css_with_parens_if_needed<W>(&self, dest: &mut Printer<W>, needs_parens: bool) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    if needs_parens {\n      dest.write_char('(')?;\n    }\n    self.to_css(dest)?;\n    if needs_parens {\n      dest.write_char(')')?;\n    }\n    Ok(())\n  }\n}\n\nimpl<'i> ToCss for SupportsCondition<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      SupportsCondition::Not(condition) => {\n        dest.write_str(\"not \")?;\n        condition.to_css_with_parens_if_needed(dest, condition.needs_parens(self))\n      }\n      SupportsCondition::And(conditions) => {\n        let mut first = true;\n        for condition in conditions {\n          if first {\n            first = false;\n          } else {\n            dest.write_str(\" and \")?;\n          }\n          condition.to_css_with_parens_if_needed(dest, condition.needs_parens(self))?;\n        }\n        Ok(())\n      }\n      SupportsCondition::Or(conditions) => {\n        let mut first = true;\n        for condition in conditions {\n          if first {\n            first = false;\n          } else {\n            dest.write_str(\" or \")?;\n          }\n          condition.to_css_with_parens_if_needed(dest, condition.needs_parens(self))?;\n        }\n        Ok(())\n      }\n      SupportsCondition::Declaration { property_id, value } => {\n        dest.write_char('(')?;\n\n        let prefix = property_id.prefix().or_none();\n        if prefix != VendorPrefix::None {\n          dest.write_char('(')?;\n        }\n\n        let name = property_id.name();\n        let mut first = true;\n        for p in prefix {\n          if first {\n            first = false;\n          } else {\n            dest.write_str(\") or (\")?;\n          }\n\n          p.to_css(dest)?;\n          serialize_name(name, dest)?;\n          dest.delim(':', false)?;\n          dest.write_str(value)?;\n        }\n\n        if prefix != VendorPrefix::None {\n          dest.write_char(')')?;\n        }\n\n        dest.write_char(')')\n      }\n      SupportsCondition::Selector(sel) => {\n        dest.write_str(\"selector(\")?;\n        dest.write_str(sel)?;\n        dest.write_char(')')\n      }\n      SupportsCondition::Unknown(unknown) => dest.write_str(&unknown),\n    }\n  }\n}\n"
  },
  {
    "path": "src/rules/unknown.rs",
    "content": "//! An unknown at-rule.\n\nuse super::Location;\nuse crate::error::PrinterError;\nuse crate::printer::Printer;\nuse crate::properties::custom::TokenList;\nuse crate::traits::ToCss;\nuse crate::values::string::CowArcStr;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\n\n/// An unknown at-rule, stored as raw tokens.\n#[derive(Debug, PartialEq, Clone)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct UnknownAtRule<'i> {\n  /// The name of the at-rule (without the @).\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  #[cfg_attr(feature = \"visitor\", skip_visit)]\n  pub name: CowArcStr<'i>,\n  /// The prelude of the rule.\n  pub prelude: TokenList<'i>,\n  /// The contents of the block, if any.\n  pub block: Option<TokenList<'i>>,\n  /// The location of the rule in the source file.\n  #[cfg_attr(feature = \"visitor\", skip_visit)]\n  pub loc: Location,\n}\n\nimpl<'i> ToCss for UnknownAtRule<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    #[cfg(feature = \"sourcemap\")]\n    dest.add_mapping(self.loc);\n    dest.write_char('@')?;\n    dest.write_str(&self.name)?;\n\n    if !self.prelude.0.is_empty() {\n      dest.write_char(' ')?;\n      self.prelude.to_css(dest, false)?;\n    }\n\n    if let Some(block) = &self.block {\n      dest.whitespace()?;\n      dest.write_char('{')?;\n      dest.indent();\n      dest.newline()?;\n      block.to_css(dest, false)?;\n      dest.dedent();\n      dest.newline()?;\n      dest.write_char('}')\n    } else {\n      dest.write_char(';')\n    }\n  }\n}\n"
  },
  {
    "path": "src/rules/view_transition.rs",
    "content": "//! The `@view-transition` rule.\n\nuse super::Location;\nuse crate::error::{ParserError, PrinterError};\nuse crate::printer::Printer;\nuse crate::properties::custom::CustomProperty;\nuse crate::stylesheet::ParserOptions;\nuse crate::traits::{Parse, ToCss};\nuse crate::values::ident::NoneOrCustomIdentList;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse cssparser::*;\n\n/// A [@view-transition](https://drafts.csswg.org/css-view-transitions-2/#view-transition-rule) rule.\n#[derive(Debug, PartialEq, Clone)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct ViewTransitionRule<'i> {\n  /// Declarations in the `@view-transition` rule.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  pub properties: Vec<ViewTransitionProperty<'i>>,\n  /// The location of the rule in the source file.\n  #[cfg_attr(feature = \"visitor\", skip_visit)]\n  pub loc: Location,\n}\n\n/// A property within a `@view-transition` rule.\n///\n///  See [ViewTransitionRule](ViewTransitionRule).\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"property\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub enum ViewTransitionProperty<'i> {\n  /// The `navigation` property.\n  Navigation(Navigation),\n  /// The `types` property.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  Types(NoneOrCustomIdentList<'i>),\n  /// An unknown or unsupported property.\n  Custom(CustomProperty<'i>),\n}\n\n/// A value for the [navigation](https://drafts.csswg.org/css-view-transitions-2/#view-transition-navigation-descriptor)\n/// property in a `@view-transition` rule.\n#[derive(Debug, Clone, PartialEq, Default, Parse, ToCss)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub enum Navigation {\n  /// There will be no transition.\n  #[default]\n  None,\n  /// The transition will be enabled if the navigation is same-origin.\n  Auto,\n}\n\npub(crate) struct ViewTransitionDeclarationParser;\n\nimpl<'i> cssparser::DeclarationParser<'i> for ViewTransitionDeclarationParser {\n  type Declaration = ViewTransitionProperty<'i>;\n  type Error = ParserError<'i>;\n\n  fn parse_value<'t>(\n    &mut self,\n    name: CowRcStr<'i>,\n    input: &mut cssparser::Parser<'i, 't>,\n  ) -> Result<Self::Declaration, cssparser::ParseError<'i, Self::Error>> {\n    let state = input.state();\n    match_ignore_ascii_case! { &name,\n      \"navigation\" => {\n        // https://drafts.csswg.org/css-view-transitions-2/#view-transition-navigation-descriptor\n        if let Ok(navigation) = Navigation::parse(input) {\n          return Ok(ViewTransitionProperty::Navigation(navigation));\n        }\n      },\n      \"types\" => {\n        // https://drafts.csswg.org/css-view-transitions-2/#types-cross-doc\n        if let Ok(types) = NoneOrCustomIdentList::parse(input) {\n          return Ok(ViewTransitionProperty::Types(types));\n        }\n      },\n      _ => return Err(input.new_custom_error(ParserError::InvalidDeclaration))\n    }\n\n    input.reset(&state);\n    return Ok(ViewTransitionProperty::Custom(CustomProperty::parse(\n      name.into(),\n      input,\n      &ParserOptions::default(),\n    )?));\n  }\n}\n\n/// Default methods reject all at rules.\nimpl<'i> AtRuleParser<'i> for ViewTransitionDeclarationParser {\n  type Prelude = ();\n  type AtRule = ViewTransitionProperty<'i>;\n  type Error = ParserError<'i>;\n}\n\nimpl<'i> QualifiedRuleParser<'i> for ViewTransitionDeclarationParser {\n  type Prelude = ();\n  type QualifiedRule = ViewTransitionProperty<'i>;\n  type Error = ParserError<'i>;\n}\n\nimpl<'i> RuleBodyItemParser<'i, ViewTransitionProperty<'i>, ParserError<'i>> for ViewTransitionDeclarationParser {\n  fn parse_qualified(&self) -> bool {\n    false\n  }\n\n  fn parse_declarations(&self) -> bool {\n    true\n  }\n}\n\nimpl<'i> ViewTransitionRule<'i> {\n  pub(crate) fn parse<'t>(\n    input: &mut Parser<'i, 't>,\n    loc: Location,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let mut decl_parser = ViewTransitionDeclarationParser;\n    let mut parser = RuleBodyParser::new(input, &mut decl_parser);\n    let mut properties = vec![];\n    while let Some(decl) = parser.next() {\n      if let Ok(decl) = decl {\n        properties.push(decl);\n      }\n    }\n\n    Ok(ViewTransitionRule { properties, loc })\n  }\n}\n\nimpl<'i> ToCss for ViewTransitionRule<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    #[cfg(feature = \"sourcemap\")]\n    dest.add_mapping(self.loc);\n    dest.write_str(\"@view-transition\")?;\n    dest.whitespace()?;\n    dest.write_char('{')?;\n    dest.indent();\n    let len = self.properties.len();\n    for (i, prop) in self.properties.iter().enumerate() {\n      dest.newline()?;\n      prop.to_css(dest)?;\n      if i != len - 1 || !dest.minify {\n        dest.write_char(';')?;\n      }\n    }\n    dest.dedent();\n    dest.newline()?;\n    dest.write_char('}')\n  }\n}\n\nimpl<'i> ToCss for ViewTransitionProperty<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    macro_rules! property {\n      ($prop: literal, $value: expr) => {{\n        dest.write_str($prop)?;\n        dest.delim(':', false)?;\n        $value.to_css(dest)\n      }};\n    }\n\n    match self {\n      ViewTransitionProperty::Navigation(f) => property!(\"navigation\", f),\n      ViewTransitionProperty::Types(t) => property!(\"types\", t),\n      ViewTransitionProperty::Custom(custom) => {\n        dest.write_str(custom.name.as_ref())?;\n        dest.delim(':', false)?;\n        custom.value.to_css(dest, true)\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/rules/viewport.rs",
    "content": "//! The `@viewport` rule.\n\nuse super::Location;\nuse crate::declaration::DeclarationBlock;\nuse crate::error::PrinterError;\nuse crate::printer::Printer;\nuse crate::traits::ToCss;\nuse crate::vendor_prefix::VendorPrefix;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\n\n/// A [@viewport](https://drafts.csswg.org/css-device-adapt/#atviewport-rule) rule.\n#[derive(Debug, PartialEq, Clone)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"camelCase\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct ViewportRule<'i> {\n  /// The vendor prefix for this rule, e.g. `@-ms-viewport`.\n  #[cfg_attr(feature = \"visitor\", skip_visit)]\n  pub vendor_prefix: VendorPrefix,\n  /// The declarations within the `@viewport` rule.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  pub declarations: DeclarationBlock<'i>,\n  /// The location of the rule in the source file.\n  #[cfg_attr(feature = \"visitor\", skip_visit)]\n  pub loc: Location,\n}\n\nimpl<'i> ToCss for ViewportRule<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    #[cfg(feature = \"sourcemap\")]\n    dest.add_mapping(self.loc);\n    dest.write_char('@')?;\n    self.vendor_prefix.to_css(dest)?;\n    dest.write_str(\"viewport\")?;\n    self.declarations.to_css_block(dest)\n  }\n}\n"
  },
  {
    "path": "src/selector.rs",
    "content": "//! CSS selectors.\n\nuse crate::compat::Feature;\nuse crate::error::{ParserError, PrinterError, SelectorError};\nuse crate::parser::ParserFlags;\nuse crate::printer::Printer;\nuse crate::properties::custom::TokenList;\nuse crate::rules::StyleContext;\nuse crate::stylesheet::{ParserOptions, PrinterOptions};\nuse crate::targets::{should_compile, Targets};\nuse crate::traits::{Parse, ParseWithOptions, ToCss};\nuse crate::values::ident::{CustomIdent, Ident};\nuse crate::values::string::CSSString;\nuse crate::vendor_prefix::VendorPrefix;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::{Visit, VisitTypes, Visitor};\nuse crate::{macros::enum_property, values::string::CowArcStr};\nuse cssparser::*;\nuse parcel_selectors::parser::{NthType, SelectorParseErrorKind};\nuse parcel_selectors::{\n  attr::{AttrSelectorOperator, ParsedAttrSelectorOperation, ParsedCaseSensitivity},\n  parser::SelectorImpl,\n};\nuse smallvec::SmallVec;\nuse std::collections::HashSet;\nuse std::fmt;\n\n#[cfg(feature = \"serde\")]\nuse crate::serialization::*;\n\nmod private {\n  #[derive(Debug, Clone, PartialEq, Eq, Hash)]\n  #[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n  pub struct Selectors;\n\n  #[cfg(feature = \"into_owned\")]\n  impl<'any> ::static_self::IntoOwned<'any> for Selectors {\n    type Owned = Self;\n\n    fn into_owned(self) -> Self::Owned {\n      self\n    }\n  }\n}\n\n#[cfg(feature = \"into_owned\")]\nfn _assert_into_owned() {\n  // Ensure that we provide into_owned\n\n  use static_self::IntoOwned;\n  fn _assert<'any, T: IntoOwned<'any>>() {}\n\n  _assert::<SelectorList>();\n}\n\nuse private::Selectors;\n\n/// A list of selectors.\npub type SelectorList<'i> = parcel_selectors::SelectorList<'i, Selectors>;\n/// A CSS selector, including a list of components.\npub type Selector<'i> = parcel_selectors::parser::Selector<'i, Selectors>;\n/// An individual component within a selector.\npub type Component<'i> = parcel_selectors::parser::Component<'i, Selectors>;\n/// A combinator.\npub use parcel_selectors::parser::Combinator;\n\nimpl<'i> SelectorImpl<'i> for Selectors {\n  type AttrValue = CSSString<'i>;\n  type Identifier = Ident<'i>;\n  type LocalName = Ident<'i>;\n  type NamespacePrefix = Ident<'i>;\n  type NamespaceUrl = CowArcStr<'i>;\n  type BorrowedNamespaceUrl = CowArcStr<'i>;\n  type BorrowedLocalName = Ident<'i>;\n\n  type NonTSPseudoClass = PseudoClass<'i>;\n  type PseudoElement = PseudoElement<'i>;\n  type VendorPrefix = VendorPrefix;\n\n  type ExtraMatchingData = ();\n\n  fn to_css<W: fmt::Write>(selectors: &SelectorList<'i>, dest: &mut W) -> std::fmt::Result {\n    let mut printer = Printer::new(dest, PrinterOptions::default());\n    serialize_selector_list(selectors.0.iter(), &mut printer, None, false).map_err(|_| std::fmt::Error)\n  }\n}\n\npub(crate) struct SelectorParser<'a, 'o, 'i> {\n  pub is_nesting_allowed: bool,\n  pub options: &'a ParserOptions<'o, 'i>,\n}\n\nimpl<'a, 'o, 'i> parcel_selectors::parser::Parser<'i> for SelectorParser<'a, 'o, 'i> {\n  type Impl = Selectors;\n  type Error = ParserError<'i>;\n\n  fn parse_non_ts_pseudo_class(\n    &self,\n    loc: SourceLocation,\n    name: CowRcStr<'i>,\n  ) -> Result<PseudoClass<'i>, ParseError<'i, Self::Error>> {\n    use PseudoClass::*;\n    let pseudo_class = match_ignore_ascii_case! { &name,\n      // https://drafts.csswg.org/selectors-4/#useraction-pseudos\n      \"hover\" => Hover,\n      \"active\" => Active,\n      \"focus\" => Focus,\n      \"focus-visible\" => FocusVisible,\n      \"focus-within\" => FocusWithin,\n\n      // https://drafts.csswg.org/selectors-4/#time-pseudos\n      \"current\" => Current,\n      \"past\" => Past,\n      \"future\" => Future,\n\n      // https://drafts.csswg.org/selectors-4/#resource-pseudos\n      \"playing\" => Playing,\n      \"paused\" => Paused,\n      \"seeking\" => Seeking,\n      \"buffering\" => Buffering,\n      \"stalled\" => Stalled,\n      \"muted\" => Muted,\n      \"volume-locked\" => VolumeLocked,\n\n      // https://fullscreen.spec.whatwg.org/#:fullscreen-pseudo-class\n      \"fullscreen\" => Fullscreen(VendorPrefix::None),\n      \"-webkit-full-screen\" => Fullscreen(VendorPrefix::WebKit),\n      \"-moz-full-screen\" => Fullscreen(VendorPrefix::Moz),\n      \"-ms-fullscreen\" => Fullscreen(VendorPrefix::Ms),\n\n      // https://drafts.csswg.org/selectors/#display-state-pseudos\n      \"open\" => Open,\n      \"closed\" => Closed,\n      \"modal\" => Modal,\n      \"picture-in-picture\" => PictureInPicture,\n\n      // https://html.spec.whatwg.org/multipage/semantics-other.html#selector-popover-open\n      \"popover-open\" => PopoverOpen,\n\n      // https://drafts.csswg.org/selectors-4/#the-defined-pseudo\n      \"defined\" => Defined,\n\n      // https://drafts.csswg.org/selectors-4/#location\n      \"any-link\" => AnyLink(VendorPrefix::None),\n      \"-webkit-any-link\" => AnyLink(VendorPrefix::WebKit),\n      \"-moz-any-link\" => AnyLink(VendorPrefix::Moz),\n      \"link\" => Link,\n      \"local-link\" => LocalLink,\n      \"target\" => Target,\n      \"target-within\" => TargetWithin,\n      \"visited\" => Visited,\n\n      // https://drafts.csswg.org/selectors-4/#input-pseudos\n      \"enabled\" => Enabled,\n      \"disabled\" => Disabled,\n      \"read-only\" => ReadOnly(VendorPrefix::None),\n      \"-moz-read-only\" => ReadOnly(VendorPrefix::Moz),\n      \"read-write\" => ReadWrite(VendorPrefix::None),\n      \"-moz-read-write\" => ReadWrite(VendorPrefix::Moz),\n      \"placeholder-shown\" => PlaceholderShown(VendorPrefix::None),\n      \"-moz-placeholder\" => PlaceholderShown(VendorPrefix::Moz),\n      \"-ms-input-placeholder\" => PlaceholderShown(VendorPrefix::Ms),\n      \"default\" => Default,\n      \"checked\" => Checked,\n      \"indeterminate\" => Indeterminate,\n      \"blank\" => Blank,\n      \"valid\" => Valid,\n      \"invalid\" => Invalid,\n      \"in-range\" => InRange,\n      \"out-of-range\" => OutOfRange,\n      \"required\" => Required,\n      \"optional\" => Optional,\n      \"user-valid\" => UserValid,\n      \"user-invalid\" => UserInvalid,\n\n      // https://html.spec.whatwg.org/multipage/semantics-other.html#selector-autofill\n      \"autofill\" => Autofill(VendorPrefix::None),\n      \"-webkit-autofill\" => Autofill(VendorPrefix::WebKit),\n      \"-o-autofill\" => Autofill(VendorPrefix::O),\n\n      // https://drafts.csswg.org/css-view-transitions-2/#pseudo-classes-for-selective-vt\n      \"active-view-transition\" => ActiveViewTransition,\n\n      // https://webkit.org/blog/363/styling-scrollbars/\n      \"horizontal\" => WebKitScrollbar(WebKitScrollbarPseudoClass::Horizontal),\n      \"vertical\" => WebKitScrollbar(WebKitScrollbarPseudoClass::Vertical),\n      \"decrement\" => WebKitScrollbar(WebKitScrollbarPseudoClass::Decrement),\n      \"increment\" => WebKitScrollbar(WebKitScrollbarPseudoClass::Increment),\n      \"start\" => WebKitScrollbar(WebKitScrollbarPseudoClass::Start),\n      \"end\" => WebKitScrollbar(WebKitScrollbarPseudoClass::End),\n      \"double-button\" => WebKitScrollbar(WebKitScrollbarPseudoClass::DoubleButton),\n      \"single-button\" => WebKitScrollbar(WebKitScrollbarPseudoClass::SingleButton),\n      \"no-button\" => WebKitScrollbar(WebKitScrollbarPseudoClass::NoButton),\n      \"corner-present\" => WebKitScrollbar(WebKitScrollbarPseudoClass::CornerPresent),\n      \"window-inactive\" => WebKitScrollbar(WebKitScrollbarPseudoClass::WindowInactive),\n\n      \"local\" | \"global\" if self.options.css_modules.is_some() => {\n        return Err(loc.new_custom_error(SelectorParseErrorKind::AmbiguousCssModuleClass(name.clone())))\n      },\n\n      _ => {\n        if !name.starts_with('-') {\n          self.options.warn(loc.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClass(name.clone())));\n        }\n        Custom { name: name.into() }\n      }\n    };\n\n    Ok(pseudo_class)\n  }\n\n  fn parse_non_ts_functional_pseudo_class<'t>(\n    &self,\n    name: CowRcStr<'i>,\n    parser: &mut cssparser::Parser<'i, 't>,\n  ) -> Result<PseudoClass<'i>, ParseError<'i, Self::Error>> {\n    use PseudoClass::*;\n    let pseudo_class = match_ignore_ascii_case! { &name,\n      \"lang\" => {\n        let languages = parser.parse_comma_separated(|parser| {\n          parser.expect_ident_or_string()\n            .map(|s| s.into())\n            .map_err(|e| e.into())\n        })?;\n        Lang { languages }\n      },\n      \"dir\" => Dir { direction: Direction::parse(parser)? },\n      // https://drafts.csswg.org/css-view-transitions-2/#the-active-view-transition-type-pseudo\n      \"active-view-transition-type\" => {\n        let kind = Parse::parse(parser)?;\n        ActiveViewTransitionType { kind }\n      },\n      \"state\" => {\n        let state = CustomIdent::parse(parser)?;\n        State { state }\n      },\n      \"local\" if self.options.css_modules.is_some() => Local { selector: Box::new(Selector::parse(self, parser)?) },\n      \"global\" if self.options.css_modules.is_some() => Global { selector: Box::new(Selector::parse(self, parser)?) },\n      _ => {\n        if !name.starts_with('-') {\n          self.options.warn(parser.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClass(name.clone())));\n        }\n        let mut args = Vec::new();\n        TokenList::parse_raw(parser, &mut args, &self.options, 0)?;\n        CustomFunction {\n          name: name.into(),\n          arguments: TokenList(args)\n        }\n      },\n    };\n\n    Ok(pseudo_class)\n  }\n\n  fn parse_any_prefix<'t>(&self, name: &str) -> Option<VendorPrefix> {\n    match_ignore_ascii_case! { &name,\n      \"-webkit-any\" => Some(VendorPrefix::WebKit),\n      \"-moz-any\" => Some(VendorPrefix::Moz),\n      _ => None\n    }\n  }\n\n  fn parse_pseudo_element(\n    &self,\n    loc: SourceLocation,\n    name: CowRcStr<'i>,\n  ) -> Result<PseudoElement<'i>, ParseError<'i, Self::Error>> {\n    use PseudoElement::*;\n    let pseudo_element = match_ignore_ascii_case! { &name,\n      \"before\" => Before,\n      \"after\" => After,\n      \"first-line\" => FirstLine,\n      \"first-letter\" => FirstLetter,\n      \"details-content\" => DetailsContent,\n      \"target-text\" => TargetText,\n      \"cue\" => Cue,\n      \"cue-region\" => CueRegion,\n      \"selection\" => Selection(VendorPrefix::None),\n      \"-moz-selection\" => Selection(VendorPrefix::Moz),\n      \"placeholder\" => Placeholder(VendorPrefix::None),\n      \"-webkit-input-placeholder\" => Placeholder(VendorPrefix::WebKit),\n      \"-moz-placeholder\" => Placeholder(VendorPrefix::Moz),\n      \"-ms-input-placeholder\" => Placeholder(VendorPrefix::Moz),\n      \"marker\" => Marker,\n      \"backdrop\" => Backdrop(VendorPrefix::None),\n      \"-webkit-backdrop\" => Backdrop(VendorPrefix::WebKit),\n      \"file-selector-button\" => FileSelectorButton(VendorPrefix::None),\n      \"-webkit-file-upload-button\" => FileSelectorButton(VendorPrefix::WebKit),\n      \"-ms-browse\" => FileSelectorButton(VendorPrefix::Ms),\n\n      \"-webkit-scrollbar\" => WebKitScrollbar(WebKitScrollbarPseudoElement::Scrollbar),\n      \"-webkit-scrollbar-button\" => WebKitScrollbar(WebKitScrollbarPseudoElement::Button),\n      \"-webkit-scrollbar-track\" => WebKitScrollbar(WebKitScrollbarPseudoElement::Track),\n      \"-webkit-scrollbar-track-piece\" => WebKitScrollbar(WebKitScrollbarPseudoElement::TrackPiece),\n      \"-webkit-scrollbar-thumb\" => WebKitScrollbar(WebKitScrollbarPseudoElement::Thumb),\n      \"-webkit-scrollbar-corner\" => WebKitScrollbar(WebKitScrollbarPseudoElement::Corner),\n      \"-webkit-resizer\" => WebKitScrollbar(WebKitScrollbarPseudoElement::Resizer),\n\n      \"picker-icon\" => PickerIcon,\n      \"checkmark\" => Checkmark,\n\n      \"view-transition\" => ViewTransition,\n\n      \"grammar-error\" => GrammarError,\n      \"spelling-error\" => SpellingError,\n\n      _ => {\n        if !name.starts_with('-') {\n          self.options.warn(loc.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoElement(name.clone())));\n        }\n        Custom { name: name.into() }\n      }\n    };\n\n    Ok(pseudo_element)\n  }\n\n  fn parse_functional_pseudo_element<'t>(\n    &self,\n    name: CowRcStr<'i>,\n    arguments: &mut Parser<'i, 't>,\n  ) -> Result<<Self::Impl as SelectorImpl<'i>>::PseudoElement, ParseError<'i, Self::Error>> {\n    use PseudoElement::*;\n    let pseudo_element = match_ignore_ascii_case! { &name,\n      \"cue\" => CueFunction { selector: Box::new(Selector::parse(self, arguments)?) },\n      \"cue-region\" => CueRegionFunction { selector: Box::new(Selector::parse(self, arguments)?) },\n      \"highlight\" => HighlightFunction { name: CustomIdent::parse(arguments)? },\n      \"picker\" => PickerFunction { identifier: Ident::parse(arguments)? },\n      \"view-transition-group\" => ViewTransitionGroup { part: ViewTransitionPartSelector::parse(arguments)? },\n      \"view-transition-image-pair\" => ViewTransitionImagePair { part: ViewTransitionPartSelector::parse(arguments)? },\n      \"view-transition-old\" => ViewTransitionOld { part: ViewTransitionPartSelector::parse(arguments)? },\n      \"view-transition-new\" => ViewTransitionNew { part: ViewTransitionPartSelector::parse(arguments)? },\n      _ => {\n        if !name.starts_with('-') {\n          self.options.warn(arguments.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoElement(name.clone())));\n        }\n        let mut args = Vec::new();\n        TokenList::parse_raw(arguments, &mut args, &self.options, 0)?;\n        CustomFunction { name: name.into(), arguments: TokenList(args) }\n      }\n    };\n\n    Ok(pseudo_element)\n  }\n\n  #[inline]\n  fn parse_slotted(&self) -> bool {\n    true\n  }\n\n  #[inline]\n  fn parse_host(&self) -> bool {\n    true\n  }\n\n  #[inline]\n  fn parse_is_and_where(&self) -> bool {\n    true\n  }\n\n  #[inline]\n  fn parse_part(&self) -> bool {\n    true\n  }\n\n  fn default_namespace(&self) -> Option<CowArcStr<'i>> {\n    None\n  }\n\n  fn namespace_for_prefix(&self, prefix: &Ident<'i>) -> Option<CowArcStr<'i>> {\n    Some(prefix.0.clone())\n  }\n\n  #[inline]\n  fn is_nesting_allowed(&self) -> bool {\n    self.is_nesting_allowed\n  }\n\n  fn deep_combinator_enabled(&self) -> bool {\n    self.options.flags.contains(ParserFlags::DEEP_SELECTOR_COMBINATOR)\n  }\n}\n\nenum_property! {\n  /// The [:dir()](https://drafts.csswg.org/selectors-4/#the-dir-pseudo) pseudo class.\n  #[derive(Eq, Hash)]\n  pub enum Direction {\n    /// Left to right\n    Ltr,\n    /// Right to left\n    Rtl,\n  }\n}\n\n/// A pseudo class.\n#[derive(Clone, PartialEq, Eq, Hash)]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"kind\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum PseudoClass<'i> {\n  // https://drafts.csswg.org/selectors-4/#linguistic-pseudos\n  /// The [:lang()](https://drafts.csswg.org/selectors-4/#the-lang-pseudo) pseudo class.\n  Lang {\n    /// A list of language codes.\n    #[cfg_attr(feature = \"serde\", serde(borrow))]\n    languages: Vec<CowArcStr<'i>>,\n  },\n  /// The [:dir()](https://drafts.csswg.org/selectors-4/#the-dir-pseudo) pseudo class.\n  Dir {\n    /// A direction.\n    direction: Direction,\n  },\n\n  // https://drafts.csswg.org/selectors-4/#useraction-pseudos\n  /// The [:hover](https://drafts.csswg.org/selectors-4/#the-hover-pseudo) pseudo class.\n  Hover,\n  /// The [:active](https://drafts.csswg.org/selectors-4/#the-active-pseudo) pseudo class.\n  Active,\n  /// The [:focus](https://drafts.csswg.org/selectors-4/#the-focus-pseudo) pseudo class.\n  Focus,\n  /// The [:focus-visible](https://drafts.csswg.org/selectors-4/#the-focus-visible-pseudo) pseudo class.\n  FocusVisible,\n  /// The [:focus-within](https://drafts.csswg.org/selectors-4/#the-focus-within-pseudo) pseudo class.\n  FocusWithin,\n\n  // https://drafts.csswg.org/selectors-4/#time-pseudos\n  /// The [:current](https://drafts.csswg.org/selectors-4/#the-current-pseudo) pseudo class.\n  Current,\n  /// The [:past](https://drafts.csswg.org/selectors-4/#the-past-pseudo) pseudo class.\n  Past,\n  /// The [:future](https://drafts.csswg.org/selectors-4/#the-future-pseudo) pseudo class.\n  Future,\n\n  // https://drafts.csswg.org/selectors-4/#resource-pseudos\n  /// The [:playing](https://drafts.csswg.org/selectors-4/#selectordef-playing) pseudo class.\n  Playing,\n  /// The [:paused](https://drafts.csswg.org/selectors-4/#selectordef-paused) pseudo class.\n  Paused,\n  /// The [:seeking](https://drafts.csswg.org/selectors-4/#selectordef-seeking) pseudo class.\n  Seeking,\n  /// The [:buffering](https://drafts.csswg.org/selectors-4/#selectordef-buffering) pseudo class.\n  Buffering,\n  /// The [:stalled](https://drafts.csswg.org/selectors-4/#selectordef-stalled) pseudo class.\n  Stalled,\n  /// The [:muted](https://drafts.csswg.org/selectors-4/#selectordef-muted) pseudo class.\n  Muted,\n  /// The [:volume-locked](https://drafts.csswg.org/selectors-4/#selectordef-volume-locked) pseudo class.\n  VolumeLocked,\n\n  /// The [:fullscreen](https://fullscreen.spec.whatwg.org/#:fullscreen-pseudo-class) pseudo class.\n  #[cfg_attr(feature = \"serde\", serde(with = \"PrefixWrapper\"))]\n  Fullscreen(VendorPrefix),\n\n  // https://drafts.csswg.org/selectors/#display-state-pseudos\n  /// The [:open](https://drafts.csswg.org/selectors/#selectordef-open) pseudo class.\n  Open,\n  /// The [:closed](https://drafts.csswg.org/selectors/#selectordef-closed) pseudo class.\n  Closed,\n  /// The [:modal](https://drafts.csswg.org/selectors/#modal-state) pseudo class.\n  Modal,\n  /// The [:picture-in-picture](https://drafts.csswg.org/selectors/#pip-state) pseudo class.\n  PictureInPicture,\n\n  // https://html.spec.whatwg.org/multipage/semantics-other.html#selector-popover-open\n  /// The [:popover-open](https://html.spec.whatwg.org/multipage/semantics-other.html#selector-popover-open) pseudo class.\n  PopoverOpen,\n\n  /// The [:defined](https://drafts.csswg.org/selectors-4/#the-defined-pseudo) pseudo class.\n  Defined,\n\n  // https://drafts.csswg.org/selectors-4/#location\n  /// The [:any-link](https://drafts.csswg.org/selectors-4/#the-any-link-pseudo) pseudo class.\n  #[cfg_attr(feature = \"serde\", serde(with = \"PrefixWrapper\"))]\n  AnyLink(VendorPrefix),\n  /// The [:link](https://drafts.csswg.org/selectors-4/#link-pseudo) pseudo class.\n  Link,\n  /// The [:local-link](https://drafts.csswg.org/selectors-4/#the-local-link-pseudo) pseudo class.\n  LocalLink,\n  /// The [:target](https://drafts.csswg.org/selectors-4/#the-target-pseudo) pseudo class.\n  Target,\n  /// The [:target-within](https://drafts.csswg.org/selectors-4/#the-target-within-pseudo) pseudo class.\n  TargetWithin,\n  /// The [:visited](https://drafts.csswg.org/selectors-4/#visited-pseudo) pseudo class.\n  Visited,\n\n  // https://drafts.csswg.org/selectors-4/#input-pseudos\n  /// The [:enabled](https://drafts.csswg.org/selectors-4/#enabled-pseudo) pseudo class.\n  Enabled,\n  /// The [:disabled](https://drafts.csswg.org/selectors-4/#disabled-pseudo) pseudo class.\n  Disabled,\n  /// The [:read-only](https://drafts.csswg.org/selectors-4/#read-only-pseudo) pseudo class.\n  #[cfg_attr(feature = \"serde\", serde(with = \"PrefixWrapper\"))]\n  ReadOnly(VendorPrefix),\n  /// The [:read-write](https://drafts.csswg.org/selectors-4/#read-write-pseudo) pseudo class.\n  #[cfg_attr(feature = \"serde\", serde(with = \"PrefixWrapper\"))]\n  ReadWrite(VendorPrefix),\n  /// The [:placeholder-shown](https://drafts.csswg.org/selectors-4/#placeholder) pseudo class.\n  #[cfg_attr(feature = \"serde\", serde(with = \"PrefixWrapper\"))]\n  PlaceholderShown(VendorPrefix),\n  /// The [:default](https://drafts.csswg.org/selectors-4/#the-default-pseudo) pseudo class.\n  Default,\n  /// The [:checked](https://drafts.csswg.org/selectors-4/#checked) pseudo class.\n  Checked,\n  /// The [:indeterminate](https://drafts.csswg.org/selectors-4/#indeterminate) pseudo class.\n  Indeterminate,\n  /// The [:blank](https://drafts.csswg.org/selectors-4/#blank) pseudo class.\n  Blank,\n  /// The [:valid](https://drafts.csswg.org/selectors-4/#valid-pseudo) pseudo class.\n  Valid,\n  /// The [:invalid](https://drafts.csswg.org/selectors-4/#invalid-pseudo) pseudo class.\n  Invalid,\n  /// The [:in-range](https://drafts.csswg.org/selectors-4/#in-range-pseudo) pseudo class.\n  InRange,\n  /// The [:out-of-range](https://drafts.csswg.org/selectors-4/#out-of-range-pseudo) pseudo class.\n  OutOfRange,\n  /// The [:required](https://drafts.csswg.org/selectors-4/#required-pseudo) pseudo class.\n  Required,\n  /// The [:optional](https://drafts.csswg.org/selectors-4/#optional-pseudo) pseudo class.\n  Optional,\n  /// The [:user-valid](https://drafts.csswg.org/selectors-4/#user-valid-pseudo) pseudo class.\n  UserValid,\n  /// The [:used-invalid](https://drafts.csswg.org/selectors-4/#user-invalid-pseudo) pseudo class.\n  UserInvalid,\n\n  /// The [:autofill](https://html.spec.whatwg.org/multipage/semantics-other.html#selector-autofill) pseudo class.\n  #[cfg_attr(feature = \"serde\", serde(with = \"PrefixWrapper\"))]\n  Autofill(VendorPrefix),\n\n  /// The [:active-view-transition](https://drafts.csswg.org/css-view-transitions-2/#the-active-view-transition-pseudo) pseudo class.\n  ActiveViewTransition,\n  /// The [:active-view-transition-type()](https://drafts.csswg.org/css-view-transitions-2/#the-active-view-transition-type-pseudo) pseudo class.\n  ActiveViewTransitionType {\n    /// A view transition type.\n    #[cfg_attr(feature = \"serde\", serde(rename = \"type\"))]\n    kind: SmallVec<[CustomIdent<'i>; 1]>,\n  },\n\n  /// The [:state()](https://developer.mozilla.org/en-US/docs/Web/CSS/:state) pseudo class for custom element states.\n  State {\n    /// The custom state identifier.\n    state: CustomIdent<'i>,\n  },\n\n  // CSS modules\n  /// The CSS modules :local() pseudo class.\n  Local {\n    /// A local selector.\n    selector: Box<Selector<'i>>,\n  },\n  /// The CSS modules :global() pseudo class.\n  Global {\n    /// A global selector.\n    selector: Box<Selector<'i>>,\n  },\n\n  /// A [webkit scrollbar](https://webkit.org/blog/363/styling-scrollbars/) pseudo class.\n  // https://webkit.org/blog/363/styling-scrollbars/\n  #[cfg_attr(\n    feature = \"serde\",\n    serde(rename = \"webkit-scrollbar\", with = \"ValueWrapper::<WebKitScrollbarPseudoClass>\")\n  )]\n  WebKitScrollbar(WebKitScrollbarPseudoClass),\n  /// An unknown pseudo class.\n  Custom {\n    /// The pseudo class name.\n    name: CowArcStr<'i>,\n  },\n  /// An unknown functional pseudo class.\n  CustomFunction {\n    /// The pseudo class name.\n    name: CowArcStr<'i>,\n    /// The arguments of the pseudo class function.\n    arguments: TokenList<'i>,\n  },\n}\n\n/// A [webkit scrollbar](https://webkit.org/blog/363/styling-scrollbars/) pseudo class.\n#[derive(Clone, Eq, PartialEq, Hash)]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum WebKitScrollbarPseudoClass {\n  /// :horizontal\n  Horizontal,\n  /// :vertical\n  Vertical,\n  /// :decrement\n  Decrement,\n  /// :increment\n  Increment,\n  /// :start\n  Start,\n  /// :end\n  End,\n  /// :double-button\n  DoubleButton,\n  /// :single-button\n  SingleButton,\n  /// :no-button\n  NoButton,\n  /// :corner-present\n  CornerPresent,\n  /// :window-inactive\n  WindowInactive,\n}\n\nimpl<'i> parcel_selectors::parser::NonTSPseudoClass<'i> for PseudoClass<'i> {\n  type Impl = Selectors;\n\n  fn is_active_or_hover(&self) -> bool {\n    matches!(*self, PseudoClass::Active | PseudoClass::Hover)\n  }\n\n  fn is_user_action_state(&self) -> bool {\n    matches!(\n      *self,\n      PseudoClass::Active\n        | PseudoClass::Hover\n        | PseudoClass::Focus\n        | PseudoClass::FocusWithin\n        | PseudoClass::FocusVisible\n    )\n  }\n\n  fn is_valid_before_webkit_scrollbar(&self) -> bool {\n    !matches!(*self, PseudoClass::WebKitScrollbar(..))\n  }\n\n  fn is_valid_after_webkit_scrollbar(&self) -> bool {\n    // https://github.com/WebKit/WebKit/blob/02fbf9b7aa435edb96cbf563a8d4dcf1aa73b4b3/Source/WebCore/css/parser/CSSSelectorParser.cpp#L285\n    matches!(\n      *self,\n      PseudoClass::WebKitScrollbar(..)\n        | PseudoClass::Enabled\n        | PseudoClass::Disabled\n        | PseudoClass::Hover\n        | PseudoClass::Active\n    )\n  }\n}\n\nimpl<'i> cssparser::ToCss for PseudoClass<'i> {\n  fn to_css<W>(&self, dest: &mut W) -> std::fmt::Result\n  where\n    W: fmt::Write,\n  {\n    let mut s = String::new();\n    serialize_pseudo_class(self, &mut Printer::new(&mut s, Default::default()), None)\n      .map_err(|_| std::fmt::Error)?;\n    write!(dest, \"{}\", s)\n  }\n}\n\nfn serialize_pseudo_class<'a, 'i, W>(\n  pseudo_class: &PseudoClass<'i>,\n  dest: &mut Printer<W>,\n  context: Option<&StyleContext>,\n) -> Result<(), PrinterError>\nwhere\n  W: fmt::Write,\n{\n  use PseudoClass::*;\n  match pseudo_class {\n    Lang { languages: lang } => {\n      dest.write_str(\":lang(\")?;\n      let mut first = true;\n      for lang in lang {\n        if first {\n          first = false;\n        } else {\n          dest.delim(',', false)?;\n        }\n        serialize_identifier(lang, dest)?;\n      }\n      return dest.write_str(\")\");\n    }\n    Dir { direction: dir } => {\n      dest.write_str(\":dir(\")?;\n      dir.to_css(dest)?;\n      return dest.write_str(\")\");\n    }\n    State { state } => {\n      dest.write_str(\":state(\")?;\n      state.to_css(dest)?;\n      return dest.write_str(\")\");\n    }\n    _ => {}\n  }\n\n  macro_rules! write_prefixed {\n    ($prefix: ident, $val: expr) => {{\n      dest.write_char(':')?;\n      // If the printer has a vendor prefix override, use that.\n      let vp = if !dest.vendor_prefix.is_empty() {\n        (dest.vendor_prefix & *$prefix).or_none()\n      } else {\n        *$prefix\n      };\n      vp.to_css(dest)?;\n      dest.write_str($val)\n    }};\n  }\n\n  macro_rules! pseudo {\n    ($key: ident, $s: literal) => {{\n      let class = if let Some(pseudo_classes) = &dest.pseudo_classes {\n        pseudo_classes.$key\n      } else {\n        None\n      };\n\n      if let Some(class) = class {\n        dest.write_char('.')?;\n        dest.write_ident(class, true)\n      } else {\n        dest.write_str($s)\n      }\n    }};\n  }\n\n  match pseudo_class {\n    // https://drafts.csswg.org/selectors-4/#useraction-pseudos\n    Hover => pseudo!(hover, \":hover\"),\n    Active => pseudo!(active, \":active\"),\n    Focus => pseudo!(focus, \":focus\"),\n    FocusVisible => pseudo!(focus_visible, \":focus-visible\"),\n    FocusWithin => pseudo!(focus_within, \":focus-within\"),\n\n    // https://drafts.csswg.org/selectors-4/#time-pseudos\n    Current => dest.write_str(\":current\"),\n    Past => dest.write_str(\":past\"),\n    Future => dest.write_str(\":future\"),\n\n    // https://drafts.csswg.org/selectors-4/#resource-pseudos\n    Playing => dest.write_str(\":playing\"),\n    Paused => dest.write_str(\":paused\"),\n    Seeking => dest.write_str(\":seeking\"),\n    Buffering => dest.write_str(\":buffering\"),\n    Stalled => dest.write_str(\":stalled\"),\n    Muted => dest.write_str(\":muted\"),\n    VolumeLocked => dest.write_str(\":volume-locked\"),\n\n    // https://fullscreen.spec.whatwg.org/#:fullscreen-pseudo-class\n    Fullscreen(prefix) => {\n      dest.write_char(':')?;\n      let vp = if !dest.vendor_prefix.is_empty() {\n        (dest.vendor_prefix & *prefix).or_none()\n      } else {\n        *prefix\n      };\n      vp.to_css(dest)?;\n      if vp == VendorPrefix::WebKit || vp == VendorPrefix::Moz {\n        dest.write_str(\"full-screen\")\n      } else {\n        dest.write_str(\"fullscreen\")\n      }\n    }\n\n    // https://drafts.csswg.org/selectors/#display-state-pseudos\n    Open => dest.write_str(\":open\"),\n    Closed => dest.write_str(\":closed\"),\n    Modal => dest.write_str(\":modal\"),\n    PictureInPicture => dest.write_str(\":picture-in-picture\"),\n\n    // https://html.spec.whatwg.org/multipage/semantics-other.html#selector-popover-open\n    PopoverOpen => dest.write_str(\":popover-open\"),\n\n    // https://drafts.csswg.org/selectors-4/#the-defined-pseudo\n    Defined => dest.write_str(\":defined\"),\n\n    // https://drafts.csswg.org/selectors-4/#location\n    AnyLink(prefix) => write_prefixed!(prefix, \"any-link\"),\n    Link => dest.write_str(\":link\"),\n    LocalLink => dest.write_str(\":local-link\"),\n    Target => dest.write_str(\":target\"),\n    TargetWithin => dest.write_str(\":target-within\"),\n    Visited => dest.write_str(\":visited\"),\n\n    // https://drafts.csswg.org/selectors-4/#input-pseudos\n    Enabled => dest.write_str(\":enabled\"),\n    Disabled => dest.write_str(\":disabled\"),\n    ReadOnly(prefix) => write_prefixed!(prefix, \"read-only\"),\n    ReadWrite(prefix) => write_prefixed!(prefix, \"read-write\"),\n    PlaceholderShown(prefix) => write_prefixed!(prefix, \"placeholder-shown\"),\n    Default => dest.write_str(\":default\"),\n    Checked => dest.write_str(\":checked\"),\n    Indeterminate => dest.write_str(\":indeterminate\"),\n    Blank => dest.write_str(\":blank\"),\n    Valid => dest.write_str(\":valid\"),\n    Invalid => dest.write_str(\":invalid\"),\n    InRange => dest.write_str(\":in-range\"),\n    OutOfRange => dest.write_str(\":out-of-range\"),\n    Required => dest.write_str(\":required\"),\n    Optional => dest.write_str(\":optional\"),\n    UserValid => dest.write_str(\":user-valid\"),\n    UserInvalid => dest.write_str(\":user-invalid\"),\n\n    // https://html.spec.whatwg.org/multipage/semantics-other.html#selector-autofill\n    Autofill(prefix) => write_prefixed!(prefix, \"autofill\"),\n\n    ActiveViewTransition => dest.write_str(\":active-view-transition\"),\n    ActiveViewTransitionType { kind } => {\n      dest.write_str(\":active-view-transition-type(\")?;\n      kind.to_css(dest)?;\n      dest.write_char(')')\n    }\n\n    Local { selector } => serialize_selector(selector, dest, context, false),\n    Global { selector } => {\n      let css_module = std::mem::take(&mut dest.css_module);\n      serialize_selector(selector, dest, context, false)?;\n      dest.css_module = css_module;\n      Ok(())\n    }\n\n    // https://webkit.org/blog/363/styling-scrollbars/\n    WebKitScrollbar(s) => {\n      use WebKitScrollbarPseudoClass::*;\n      dest.write_str(match s {\n        Horizontal => \":horizontal\",\n        Vertical => \":vertical\",\n        Decrement => \":decrement\",\n        Increment => \":increment\",\n        Start => \":start\",\n        End => \":end\",\n        DoubleButton => \":double-button\",\n        SingleButton => \":single-button\",\n        NoButton => \":no-button\",\n        CornerPresent => \":corner-present\",\n        WindowInactive => \":window-inactive\",\n      })\n    }\n\n    Lang { languages: _ } | Dir { direction: _ } | State { .. } => unreachable!(),\n    Custom { name } => {\n      dest.write_char(':')?;\n      return dest.write_str(&name);\n    }\n    CustomFunction { name, arguments: args } => {\n      dest.write_char(':')?;\n      dest.write_str(name)?;\n      dest.write_char('(')?;\n      args.to_css_raw(dest)?;\n      dest.write_char(')')\n    }\n  }\n}\n\nimpl<'i> PseudoClass<'i> {\n  pub(crate) fn is_equivalent(&self, other: &PseudoClass<'i>) -> bool {\n    use PseudoClass::*;\n    match (self, other) {\n      (Fullscreen(_), Fullscreen(_))\n      | (AnyLink(_), AnyLink(_))\n      | (ReadOnly(_), ReadOnly(_))\n      | (ReadWrite(_), ReadWrite(_))\n      | (PlaceholderShown(_), PlaceholderShown(_))\n      | (Autofill(_), Autofill(_)) => true,\n      (a, b) => a == b,\n    }\n  }\n\n  pub(crate) fn get_prefix(&self) -> VendorPrefix {\n    use PseudoClass::*;\n    match self {\n      Fullscreen(p) | AnyLink(p) | ReadOnly(p) | ReadWrite(p) | PlaceholderShown(p) | Autofill(p) => *p,\n      _ => VendorPrefix::empty(),\n    }\n  }\n\n  pub(crate) fn get_necessary_prefixes(&mut self, targets: Targets) -> VendorPrefix {\n    use crate::prefixes::Feature;\n    use PseudoClass::*;\n    let (p, feature) = match self {\n      Fullscreen(p) => (p, Feature::PseudoClassFullscreen),\n      AnyLink(p) => (p, Feature::PseudoClassAnyLink),\n      ReadOnly(p) => (p, Feature::PseudoClassReadOnly),\n      ReadWrite(p) => (p, Feature::PseudoClassReadWrite),\n      PlaceholderShown(p) => (p, Feature::PseudoClassPlaceholderShown),\n      Autofill(p) => (p, Feature::PseudoClassAutofill),\n      _ => return VendorPrefix::empty(),\n    };\n\n    *p = targets.prefixes(*p, feature);\n    *p\n  }\n}\n\n/// A pseudo element.\n#[derive(PartialEq, Eq, Hash, Clone, Debug)]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"kind\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum PseudoElement<'i> {\n  /// The [::after](https://drafts.csswg.org/css-pseudo-4/#selectordef-after) pseudo element.\n  After,\n  /// The [::before](https://drafts.csswg.org/css-pseudo-4/#selectordef-before) pseudo element.\n  Before,\n  /// The [::first-line](https://drafts.csswg.org/css-pseudo-4/#first-line-pseudo) pseudo element.\n  FirstLine,\n  /// The [::first-letter](https://drafts.csswg.org/css-pseudo-4/#first-letter-pseudo) pseudo element.\n  FirstLetter,\n  /// The [::details-content](https://drafts.csswg.org/css-pseudo-4/#details-content-pseudo)\n  DetailsContent,\n  /// The [::target-text](https://drafts.csswg.org/css-pseudo-4/#selectordef-target-text)\n  TargetText,\n  /// The [::selection](https://drafts.csswg.org/css-pseudo-4/#selectordef-selection) pseudo element.\n  #[cfg_attr(feature = \"serde\", serde(with = \"PrefixWrapper\"))]\n  Selection(VendorPrefix),\n  /// The [::placeholder](https://drafts.csswg.org/css-pseudo-4/#placeholder-pseudo) pseudo element.\n  #[cfg_attr(feature = \"serde\", serde(with = \"PrefixWrapper\"))]\n  Placeholder(VendorPrefix),\n  /// The [::highlight()](https://drafts.csswg.org/css-highlight-api/#custom-highlight-pseudo) functional pseudo element.\n  HighlightFunction {\n    /// A custom highlight name.\n    name: CustomIdent<'i>,\n  },\n  ///  The [::marker](https://drafts.csswg.org/css-pseudo-4/#marker-pseudo) pseudo element.\n  Marker,\n  /// The [::backdrop](https://fullscreen.spec.whatwg.org/#::backdrop-pseudo-element) pseudo element.\n  #[cfg_attr(feature = \"serde\", serde(with = \"PrefixWrapper\"))]\n  Backdrop(VendorPrefix),\n  /// The [::file-selector-button](https://drafts.csswg.org/css-pseudo-4/#file-selector-button-pseudo) pseudo element.\n  #[cfg_attr(feature = \"serde\", serde(with = \"PrefixWrapper\"))]\n  FileSelectorButton(VendorPrefix),\n  /// A [webkit scrollbar](https://webkit.org/blog/363/styling-scrollbars/) pseudo element.\n  #[cfg_attr(\n    feature = \"serde\",\n    serde(rename = \"webkit-scrollbar\", with = \"ValueWrapper::<WebKitScrollbarPseudoElement>\")\n  )]\n  WebKitScrollbar(WebKitScrollbarPseudoElement),\n  /// The [::cue](https://w3c.github.io/webvtt/#the-cue-pseudo-element) pseudo element.\n  Cue,\n  /// The [::cue-region](https://w3c.github.io/webvtt/#the-cue-region-pseudo-element) pseudo element.\n  CueRegion,\n  /// The [::cue()](https://w3c.github.io/webvtt/#cue-selector) functional pseudo element.\n  CueFunction {\n    /// The selector argument.\n    selector: Box<Selector<'i>>,\n  },\n  /// The [::cue-region()](https://w3c.github.io/webvtt/#cue-region-selector) functional pseudo element.\n  CueRegionFunction {\n    /// The selector argument.\n    selector: Box<Selector<'i>>,\n  },\n  /// The [::view-transition](https://w3c.github.io/csswg-drafts/css-view-transitions-1/#view-transition) pseudo element.\n  ViewTransition,\n  /// The [::view-transition-group()](https://w3c.github.io/csswg-drafts/css-view-transitions-1/#view-transition-group-pt-name-selector) functional pseudo element.\n  #[cfg_attr(feature = \"serde\", serde(rename_all = \"camelCase\"))]\n  ViewTransitionGroup {\n    /// A part name selector.\n    part: ViewTransitionPartSelector<'i>,\n  },\n  /// The [::view-transition-image-pair()](https://w3c.github.io/csswg-drafts/css-view-transitions-1/#view-transition-image-pair-pt-name-selector) functional pseudo element.\n  #[cfg_attr(feature = \"serde\", serde(rename_all = \"camelCase\"))]\n  ViewTransitionImagePair {\n    /// A part name selector.\n    part: ViewTransitionPartSelector<'i>,\n  },\n  /// The [::view-transition-old()](https://w3c.github.io/csswg-drafts/css-view-transitions-1/#view-transition-old-pt-name-selector) functional pseudo element.\n  #[cfg_attr(feature = \"serde\", serde(rename_all = \"camelCase\"))]\n  ViewTransitionOld {\n    /// A part name selector.\n    part: ViewTransitionPartSelector<'i>,\n  },\n  /// The [::view-transition-new()](https://w3c.github.io/csswg-drafts/css-view-transitions-1/#view-transition-new-pt-name-selector) functional pseudo element.\n  #[cfg_attr(feature = \"serde\", serde(rename_all = \"camelCase\"))]\n  ViewTransitionNew {\n    /// A part name selector.\n    part: ViewTransitionPartSelector<'i>,\n  },\n  /// The [::picker()](https://drafts.csswg.org/css-forms-1/#the-picker-pseudo-element) functional pseudo element.\n  PickerFunction {\n    /// A form control identifier.\n    identifier: Ident<'i>,\n  },\n  /// The [::picker-icon](https://drafts.csswg.org/css-forms-1/#picker-opener-icon-the-picker-icon-pseudo-element) pseudo element.\n  PickerIcon,\n  /// The [::checkmark](https://drafts.csswg.org/css-forms-1/#styling-checkmarks-the-checkmark-pseudo-element) pseudo element.\n  Checkmark,\n  /// The [::grammar-error](https://drafts.csswg.org/css-pseudo/#selectordef-grammar-error) pseudo element.\n  GrammarError,\n  /// The [::spelling-error](https://drafts.csswg.org/css-pseudo/#selectordef-spelling-error) pseudo element.\n  SpellingError,\n  /// An unknown pseudo element.\n  Custom {\n    /// The name of the pseudo element.\n    #[cfg_attr(feature = \"serde\", serde(borrow))]\n    name: CowArcStr<'i>,\n  },\n  /// An unknown functional pseudo element.\n  CustomFunction {\n    ///The name of the pseudo element.\n    name: CowArcStr<'i>,\n    /// The arguments of the pseudo element function.\n    arguments: TokenList<'i>,\n  },\n}\n\n/// A [webkit scrollbar](https://webkit.org/blog/363/styling-scrollbars/) pseudo element.\n#[derive(PartialEq, Eq, Clone, Debug, Hash)]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum WebKitScrollbarPseudoElement {\n  /// ::-webkit-scrollbar\n  Scrollbar,\n  /// ::-webkit-scrollbar-button\n  Button,\n  /// ::-webkit-scrollbar-track\n  Track,\n  /// ::-webkit-scrollbar-track-piece\n  TrackPiece,\n  /// ::-webkit-scrollbar-thumb\n  Thumb,\n  /// ::-webkit-scrollbar-corner\n  Corner,\n  /// ::-webkit-resizer\n  Resizer,\n}\n\n/// A [view transition part name](https://w3c.github.io/csswg-drafts/css-view-transitions-1/#typedef-pt-name-selector).\n#[derive(PartialEq, Eq, Clone, Debug, Hash)]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum ViewTransitionPartName<'i> {\n  /// *\n  #[cfg_attr(feature = \"serde\", serde(rename = \"*\"))]\n  All,\n  /// <custom-ident>\n  #[cfg_attr(feature = \"serde\", serde(borrow, untagged))]\n  Name(CustomIdent<'i>),\n}\n\n#[cfg(feature = \"jsonschema\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"jsonschema\")))]\nimpl<'a> schemars::JsonSchema for ViewTransitionPartName<'a> {\n  fn is_referenceable() -> bool {\n    true\n  }\n\n  fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {\n    str::json_schema(gen)\n  }\n\n  fn schema_name() -> String {\n    \"ViewTransitionPartName\".into()\n  }\n}\n\nimpl<'i> Parse<'i> for ViewTransitionPartName<'i> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    if input.try_parse(|input| input.expect_delim('*')).is_ok() {\n      return Ok(ViewTransitionPartName::All);\n    }\n\n    Ok(ViewTransitionPartName::Name(CustomIdent::parse(input)?))\n  }\n}\n\nimpl<'i> ToCss for ViewTransitionPartName<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      ViewTransitionPartName::All => dest.write_char('*'),\n      ViewTransitionPartName::Name(name) => name.to_css(dest),\n    }\n  }\n}\n\n/// A [view transition part selector](https://w3c.github.io/csswg-drafts/css-view-transitions-1/#typedef-pt-name-selector).\n#[derive(PartialEq, Eq, Clone, Debug, Hash)]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub struct ViewTransitionPartSelector<'i> {\n  /// The view transition part name.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  name: Option<ViewTransitionPartName<'i>>,\n  /// A list of view transition classes.\n  classes: Vec<CustomIdent<'i>>,\n}\n\nimpl<'i> Parse<'i> for ViewTransitionPartSelector<'i> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    input.skip_whitespace();\n    let name = input.try_parse(ViewTransitionPartName::parse).ok();\n    let mut classes = Vec::new();\n    while let Ok(token) = input.next_including_whitespace() {\n      if matches!(token, Token::Delim('.')) {\n        match input.next_including_whitespace() {\n          Ok(Token::Ident(id)) => classes.push(CustomIdent(id.into())),\n          _ => return Err(input.new_custom_error(ParserError::SelectorError(SelectorError::InvalidState))),\n        }\n      } else {\n        break;\n      }\n    }\n\n    if !input.is_exhausted() || (name.is_none() && classes.is_empty()) {\n      return Err(input.new_custom_error(ParserError::SelectorError(SelectorError::InvalidState)));\n    }\n\n    Ok(ViewTransitionPartSelector { name, classes })\n  }\n}\n\nimpl<'i> ToCss for ViewTransitionPartSelector<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    if let Some(name) = &self.name {\n      name.to_css(dest)?;\n    }\n    for class in &self.classes {\n      dest.write_char('.')?;\n      class.to_css(dest)?;\n    }\n    Ok(())\n  }\n}\n\nimpl<'i> cssparser::ToCss for PseudoElement<'i> {\n  fn to_css<W>(&self, dest: &mut W) -> std::fmt::Result\n  where\n    W: fmt::Write,\n  {\n    let mut s = String::new();\n    serialize_pseudo_element(self, &mut Printer::new(&mut s, Default::default()), None)\n      .map_err(|_| std::fmt::Error)?;\n    write!(dest, \"{}\", s)\n  }\n}\n\nfn serialize_pseudo_element<'a, 'i, W>(\n  pseudo_element: &PseudoElement,\n  dest: &mut Printer<W>,\n  context: Option<&StyleContext>,\n) -> Result<(), PrinterError>\nwhere\n  W: fmt::Write,\n{\n  use PseudoElement::*;\n\n  macro_rules! write_prefix {\n    ($prefix: ident) => {{\n      dest.write_str(\"::\")?;\n      // If the printer has a vendor prefix override, use that.\n      let vp = if !dest.vendor_prefix.is_empty() {\n        (dest.vendor_prefix & *$prefix).or_none()\n      } else {\n        *$prefix\n      };\n      vp.to_css(dest)?;\n      vp\n    }};\n  }\n\n  macro_rules! write_prefixed {\n    ($prefix: ident, $val: expr) => {{\n      write_prefix!($prefix);\n      dest.write_str($val)\n    }};\n  }\n\n  match pseudo_element {\n    // CSS2 pseudo elements support a single colon syntax in addition\n    // to the more correct double colon for other pseudo elements.\n    // We use that here because it's supported everywhere and is shorter.\n    After => dest.write_str(\":after\"),\n    Before => dest.write_str(\":before\"),\n    FirstLine => dest.write_str(\":first-line\"),\n    FirstLetter => dest.write_str(\":first-letter\"),\n    DetailsContent => dest.write_str(\"::details-content\"),\n    TargetText => dest.write_str(\"::target-text\"),\n    HighlightFunction { name } => {\n      dest.write_str(\"::highlight(\")?;\n      name.to_css(dest)?;\n      dest.write_char(')')\n    }\n    Marker => dest.write_str(\"::marker\"),\n    Selection(prefix) => write_prefixed!(prefix, \"selection\"),\n    Cue => dest.write_str(\"::cue\"),\n    CueRegion => dest.write_str(\"::cue-region\"),\n    CueFunction { selector } => {\n      dest.write_str(\"::cue(\")?;\n      serialize_selector(selector, dest, context, false)?;\n      dest.write_char(')')\n    }\n    CueRegionFunction { selector } => {\n      dest.write_str(\"::cue-region(\")?;\n      serialize_selector(selector, dest, context, false)?;\n      dest.write_char(')')\n    }\n    Placeholder(prefix) => {\n      let vp = write_prefix!(prefix);\n      if vp == VendorPrefix::WebKit || vp == VendorPrefix::Ms {\n        dest.write_str(\"input-placeholder\")\n      } else {\n        dest.write_str(\"placeholder\")\n      }\n    }\n    Backdrop(prefix) => write_prefixed!(prefix, \"backdrop\"),\n    FileSelectorButton(prefix) => {\n      let vp = write_prefix!(prefix);\n      if vp == VendorPrefix::WebKit {\n        dest.write_str(\"file-upload-button\")\n      } else if vp == VendorPrefix::Ms {\n        dest.write_str(\"browse\")\n      } else {\n        dest.write_str(\"file-selector-button\")\n      }\n    }\n    WebKitScrollbar(s) => {\n      use WebKitScrollbarPseudoElement::*;\n      dest.write_str(match s {\n        Scrollbar => \"::-webkit-scrollbar\",\n        Button => \"::-webkit-scrollbar-button\",\n        Track => \"::-webkit-scrollbar-track\",\n        TrackPiece => \"::-webkit-scrollbar-track-piece\",\n        Thumb => \"::-webkit-scrollbar-thumb\",\n        Corner => \"::-webkit-scrollbar-corner\",\n        Resizer => \"::-webkit-resizer\",\n      })\n    }\n    ViewTransition => dest.write_str(\"::view-transition\"),\n    ViewTransitionGroup { part } => {\n      dest.write_str(\"::view-transition-group(\")?;\n      part.to_css(dest)?;\n      dest.write_char(')')\n    }\n    ViewTransitionImagePair { part } => {\n      dest.write_str(\"::view-transition-image-pair(\")?;\n      part.to_css(dest)?;\n      dest.write_char(')')\n    }\n    ViewTransitionOld { part } => {\n      dest.write_str(\"::view-transition-old(\")?;\n      part.to_css(dest)?;\n      dest.write_char(')')\n    }\n    ViewTransitionNew { part } => {\n      dest.write_str(\"::view-transition-new(\")?;\n      part.to_css(dest)?;\n      dest.write_char(')')\n    }\n    PickerFunction { identifier } => {\n      dest.write_str(\"::picker(\")?;\n      identifier.to_css(dest)?;\n      dest.write_char(')')\n    }\n    PickerIcon => dest.write_str(\"::picker-icon\"),\n    Checkmark => dest.write_str(\"::checkmark\"),\n    GrammarError => dest.write_str(\"::grammar-error\"),\n    SpellingError => dest.write_str(\"::spelling-error\"),\n    Custom { name: val } => {\n      dest.write_str(\"::\")?;\n      return dest.write_str(val);\n    }\n    CustomFunction { name, arguments: args } => {\n      dest.write_str(\"::\")?;\n      dest.write_str(name)?;\n      dest.write_char('(')?;\n      args.to_css_raw(dest)?;\n      dest.write_char(')')\n    }\n  }\n}\n\nimpl<'i> parcel_selectors::parser::PseudoElement<'i> for PseudoElement<'i> {\n  type Impl = Selectors;\n\n  fn accepts_state_pseudo_classes(&self) -> bool {\n    // Be lenient.\n    true\n  }\n\n  fn valid_after_slotted(&self) -> bool {\n    // ::slotted() should support all tree-abiding pseudo-elements, see\n    // https://drafts.csswg.org/css-scoping/#slotted-pseudo\n    // https://drafts.csswg.org/css-pseudo-4/#treelike\n    matches!(\n      *self,\n      PseudoElement::Before\n        | PseudoElement::After\n        | PseudoElement::Marker\n        | PseudoElement::Placeholder(_)\n        | PseudoElement::FileSelectorButton(_)\n    )\n  }\n\n  fn is_webkit_scrollbar(&self) -> bool {\n    matches!(*self, PseudoElement::WebKitScrollbar(..))\n  }\n\n  fn is_view_transition(&self) -> bool {\n    matches!(\n      *self,\n      PseudoElement::ViewTransitionGroup { .. }\n        | PseudoElement::ViewTransitionImagePair { .. }\n        | PseudoElement::ViewTransitionNew { .. }\n        | PseudoElement::ViewTransitionOld { .. }\n    )\n  }\n\n  fn is_unknown(&self) -> bool {\n    matches!(\n      *self,\n      PseudoElement::Custom { .. } | PseudoElement::CustomFunction { .. },\n    )\n  }\n}\n\nimpl<'i> PseudoElement<'i> {\n  pub(crate) fn is_equivalent(&self, other: &PseudoElement<'i>) -> bool {\n    use PseudoElement::*;\n    match (self, other) {\n      (Selection(_), Selection(_))\n      | (Placeholder(_), Placeholder(_))\n      | (Backdrop(_), Backdrop(_))\n      | (FileSelectorButton(_), FileSelectorButton(_)) => true,\n      (a, b) => a == b,\n    }\n  }\n\n  pub(crate) fn get_prefix(&self) -> VendorPrefix {\n    use PseudoElement::*;\n    match self {\n      Selection(p) | Placeholder(p) | Backdrop(p) | FileSelectorButton(p) => *p,\n      _ => VendorPrefix::empty(),\n    }\n  }\n\n  pub(crate) fn get_necessary_prefixes(&mut self, targets: Targets) -> VendorPrefix {\n    use crate::prefixes::Feature;\n    use PseudoElement::*;\n    let (p, feature) = match self {\n      Selection(p) => (p, Feature::PseudoElementSelection),\n      Placeholder(p) => (p, Feature::PseudoElementPlaceholder),\n      Backdrop(p) => (p, Feature::PseudoElementBackdrop),\n      FileSelectorButton(p) => (p, Feature::PseudoElementFileSelectorButton),\n      _ => return VendorPrefix::empty(),\n    };\n\n    *p = targets.prefixes(*p, feature);\n    *p\n  }\n}\n\nimpl<'a, 'i> ToCss for SelectorList<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: fmt::Write,\n  {\n    serialize_selector_list(self.0.iter(), dest, dest.context(), false)\n  }\n}\n\nimpl ToCss for Combinator {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: fmt::Write,\n  {\n    match *self {\n      Combinator::Child => dest.delim('>', true),\n      Combinator::Descendant => dest.write_str(\" \"),\n      Combinator::NextSibling => dest.delim('+', true),\n      Combinator::LaterSibling => dest.delim('~', true),\n      Combinator::Deep => dest.write_str(\" /deep/ \"),\n      Combinator::DeepDescendant => {\n        dest.whitespace()?;\n        dest.write_str(\">>>\")?;\n        dest.whitespace()\n      }\n      Combinator::PseudoElement | Combinator::Part | Combinator::SlotAssignment => Ok(()),\n    }\n  }\n}\n\n// Copied from the selectors crate and modified to override to_css implementation.\nimpl<'a, 'i> ToCss for Selector<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: fmt::Write,\n  {\n    serialize_selector(self, dest, dest.context(), false)\n  }\n}\n\nfn serialize_selector<'a, 'i, W>(\n  selector: &Selector<'i>,\n  dest: &mut Printer<W>,\n  context: Option<&StyleContext>,\n  mut is_relative: bool,\n) -> Result<(), PrinterError>\nwhere\n  W: fmt::Write,\n{\n  use parcel_selectors::parser::*;\n  // Compound selectors invert the order of their contents, so we need to\n  // undo that during serialization.\n  //\n  // This two-iterator strategy involves walking over the selector twice.\n  // We could do something more clever, but selector serialization probably\n  // isn't hot enough to justify it, and the stringification likely\n  // dominates anyway.\n  //\n  // NB: A parse-order iterator is a Rev<>, which doesn't expose as_slice(),\n  // which we need for |split|. So we split by combinators on a match-order\n  // sequence and then reverse.\n\n  let mut combinators = selector.iter_raw_match_order().rev().filter_map(|x| x.as_combinator());\n  let compound_selectors = selector.iter_raw_match_order().as_slice().split(|x| x.is_combinator()).rev();\n  let should_compile_nesting = should_compile!(dest.targets.current, Nesting);\n\n  let mut first = true;\n  let mut combinators_exhausted = false;\n  for mut compound in compound_selectors {\n    debug_assert!(!combinators_exhausted);\n\n    // Skip implicit :scope in relative selectors (e.g. :has(:scope > foo) -> :has(> foo))\n    if is_relative && matches!(compound.get(0), Some(Component::Scope)) {\n      if let Some(combinator) = combinators.next() {\n        combinator.to_css(dest)?;\n      }\n      compound = &compound[1..];\n      is_relative = false;\n    }\n\n    // https://drafts.csswg.org/cssom/#serializing-selectors\n    if compound.is_empty() {\n      continue;\n    }\n\n    let has_leading_nesting = first && matches!(compound[0], Component::Nesting);\n    let first_index = if has_leading_nesting { 1 } else { 0 };\n    first = false;\n\n    // 1. If there is only one simple selector in the compound selectors\n    //    which is a universal selector, append the result of\n    //    serializing the universal selector to s.\n    //\n    // Check if `!compound.empty()` first--this can happen if we have\n    // something like `... > ::before`, because we store `>` and `::`\n    // both as combinators internally.\n    //\n    // If we are in this case, after we have serialized the universal\n    // selector, we skip Step 2 and continue with the algorithm.\n    let (can_elide_namespace, first_non_namespace) = match compound.get(first_index) {\n      Some(Component::ExplicitAnyNamespace)\n      | Some(Component::ExplicitNoNamespace)\n      | Some(Component::Namespace(..)) => (false, first_index + 1),\n      Some(Component::DefaultNamespace(..)) => (true, first_index + 1),\n      _ => (true, first_index),\n    };\n    let mut perform_step_2 = true;\n    let next_combinator = combinators.next();\n    if first_non_namespace == compound.len() - 1 {\n      match (next_combinator, &compound[first_non_namespace]) {\n        // We have to be careful here, because if there is a\n        // pseudo element \"combinator\" there isn't really just\n        // the one simple selector. Technically this compound\n        // selector contains the pseudo element selector as well\n        // -- Combinator::PseudoElement, just like\n        // Combinator::SlotAssignment, don't exist in the\n        // spec.\n        (Some(Combinator::PseudoElement), _) | (Some(Combinator::SlotAssignment), _) => (),\n        (_, &Component::ExplicitUniversalType) => {\n          // Iterate over everything so we serialize the namespace\n          // too.\n          let mut iter = compound.iter();\n          let swap_nesting = has_leading_nesting && should_compile_nesting;\n          if swap_nesting {\n            // Swap nesting and type selector (e.g. &div -> div&).\n            iter.next();\n          }\n\n          for simple in iter {\n            serialize_component(simple, dest, context)?;\n          }\n\n          if swap_nesting {\n            serialize_nesting(dest, context, false)?;\n          }\n\n          // Skip step 2, which is an \"otherwise\".\n          perform_step_2 = false;\n        }\n        _ => (),\n      }\n    }\n\n    // 2. Otherwise, for each simple selector in the compound selectors\n    //    that is not a universal selector of which the namespace prefix\n    //    maps to a namespace that is not the default namespace\n    //    serialize the simple selector and append the result to s.\n    //\n    // See https://github.com/w3c/csswg-drafts/issues/1606, which is\n    // proposing to change this to match up with the behavior asserted\n    // in cssom/serialize-namespaced-type-selectors.html, which the\n    // following code tries to match.\n    if perform_step_2 {\n      let mut iter = compound.iter();\n      if has_leading_nesting && should_compile_nesting && is_type_selector(compound.get(first_non_namespace)) {\n        // Swap nesting and type selector (e.g. &div -> div&).\n        // This ensures that the compiled selector is valid. e.g. (div.foo is valid, .foodiv is not).\n        let nesting = iter.next().unwrap();\n        let local = iter.next().unwrap();\n        serialize_component(local, dest, context)?;\n\n        // Also check the next item in case of namespaces.\n        if first_non_namespace > first_index {\n          let local = iter.next().unwrap();\n          serialize_component(local, dest, context)?;\n        }\n\n        serialize_component(nesting, dest, context)?;\n      } else if has_leading_nesting && should_compile_nesting {\n        // Nesting selector may serialize differently if it is leading, due to type selectors.\n        iter.next();\n        serialize_nesting(dest, context, true)?;\n      }\n\n      for simple in iter {\n        if let Component::ExplicitUniversalType = *simple {\n          // Can't have a namespace followed by a pseudo-element\n          // selector followed by a universal selector in the same\n          // compound selector, so we don't have to worry about the\n          // real namespace being in a different `compound`.\n          if can_elide_namespace {\n            continue;\n          }\n        }\n        serialize_component(simple, dest, context)?;\n      }\n    }\n\n    // 3. If this is not the last part of the chain of the selector\n    //    append a single SPACE (U+0020), followed by the combinator\n    //    \">\", \"+\", \"~\", \">>\", \"||\", as appropriate, followed by another\n    //    single SPACE (U+0020) if the combinator was not whitespace, to\n    //    s.\n    match next_combinator {\n      Some(c) => c.to_css(dest)?,\n      None => combinators_exhausted = true,\n    };\n\n    // 4. If this is the last part of the chain of the selector and\n    //    there is a pseudo-element, append \"::\" followed by the name of\n    //    the pseudo-element, to s.\n    //\n    // (we handle this above)\n  }\n\n  Ok(())\n}\n\nfn serialize_component<'a, 'i, W>(\n  component: &Component,\n  dest: &mut Printer<W>,\n  context: Option<&StyleContext>,\n) -> Result<(), PrinterError>\nwhere\n  W: fmt::Write,\n{\n  match component {\n    Component::Combinator(ref c) => c.to_css(dest),\n    Component::AttributeInNoNamespace {\n      ref local_name,\n      operator,\n      ref value,\n      case_sensitivity,\n      ..\n    } => {\n      dest.write_char('[')?;\n      cssparser::ToCss::to_css(local_name, dest)?;\n      cssparser::ToCss::to_css(operator, dest)?;\n\n      if dest.minify {\n        // Serialize as both an identifier and a string and choose the shorter one.\n        let mut id = String::new();\n        serialize_identifier(&value, &mut id)?;\n\n        let s = value.to_css_string(Default::default())?;\n\n        if id.len() > 0 && id.len() < s.len() {\n          dest.write_str(&id)?;\n        } else {\n          dest.write_str(&s)?;\n        }\n      } else {\n        value.to_css(dest)?;\n      }\n\n      match case_sensitivity {\n        parcel_selectors::attr::ParsedCaseSensitivity::CaseSensitive\n        | parcel_selectors::attr::ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument => {}\n        parcel_selectors::attr::ParsedCaseSensitivity::AsciiCaseInsensitive => dest.write_str(\" i\")?,\n        parcel_selectors::attr::ParsedCaseSensitivity::ExplicitCaseSensitive => dest.write_str(\" s\")?,\n      }\n      dest.write_char(']')\n    }\n    Component::Is(ref list)\n    | Component::Where(ref list)\n    | Component::Negation(ref list)\n    | Component::Any(_, ref list) => {\n      match *component {\n        Component::Where(..) => dest.write_str(\":where(\")?,\n        Component::Is(ref selectors) => {\n          // If there's only one simple selector, serialize it directly.\n          if should_unwrap_is(selectors) {\n            serialize_selector(selectors.first().unwrap(), dest, context, false)?;\n            return Ok(());\n          }\n\n          let vp = dest.vendor_prefix;\n          if vp.intersects(VendorPrefix::WebKit | VendorPrefix::Moz) {\n            dest.write_char(':')?;\n            vp.to_css(dest)?;\n            dest.write_str(\"any(\")?;\n          } else {\n            dest.write_str(\":is(\")?;\n          }\n        }\n        Component::Negation(_) => {\n          dest.write_str(\":not(\")?;\n        }\n        Component::Any(prefix, ..) => {\n          let vp = dest.vendor_prefix.or(prefix);\n          if vp.intersects(VendorPrefix::WebKit | VendorPrefix::Moz) {\n            dest.write_char(':')?;\n            vp.to_css(dest)?;\n            dest.write_str(\"any(\")?;\n          } else {\n            dest.write_str(\":is(\")?;\n          }\n        }\n        _ => unreachable!(),\n      }\n      serialize_selector_list(list.iter(), dest, context, false)?;\n      dest.write_str(\")\")\n    }\n    Component::Has(ref list) => {\n      dest.write_str(\":has(\")?;\n      serialize_selector_list(list.iter(), dest, context, true)?;\n      dest.write_str(\")\")\n    }\n    Component::NonTSPseudoClass(pseudo) => serialize_pseudo_class(pseudo, dest, context),\n    Component::PseudoElement(pseudo) => serialize_pseudo_element(pseudo, dest, context),\n    Component::Nesting => serialize_nesting(dest, context, false),\n    Component::Class(ref class) => {\n      dest.write_char('.')?;\n      dest.write_ident(&class.0, true)\n    }\n    Component::ID(ref id) => {\n      dest.write_char('#')?;\n      dest.write_ident(&id.0, true)\n    }\n    Component::Host(selector) => {\n      dest.write_str(\":host\")?;\n      if let Some(ref selector) = *selector {\n        dest.write_char('(')?;\n        selector.to_css(dest)?;\n        dest.write_char(')')?;\n      }\n      Ok(())\n    }\n    Component::Slotted(ref selector) => {\n      dest.write_str(\"::slotted(\")?;\n      selector.to_css(dest)?;\n      dest.write_char(')')\n    }\n    Component::NthOf(ref nth_of_data) => {\n      let nth_data = nth_of_data.nth_data();\n      nth_data.write_start(dest, true)?;\n      nth_data.write_affine(dest)?;\n      dest.write_str(\" of \")?;\n      serialize_selector_list(nth_of_data.selectors().iter(), dest, context, true)?;\n      dest.write_char(')')\n    }\n    _ => {\n      cssparser::ToCss::to_css(component, dest)?;\n      Ok(())\n    }\n  }\n}\n\nfn should_unwrap_is<'i>(selectors: &Box<[Selector<'i>]>) -> bool {\n  if selectors.len() == 1 {\n    let first = selectors.first().unwrap();\n    if !has_type_selector(first) && is_simple(first) {\n      return true;\n    }\n  }\n\n  false\n}\n\nfn serialize_nesting<W>(\n  dest: &mut Printer<W>,\n  context: Option<&StyleContext>,\n  first: bool,\n) -> Result<(), PrinterError>\nwhere\n  W: fmt::Write,\n{\n  if let Some(ctx) = context {\n    // If there's only one simple selector, just serialize it directly.\n    // Otherwise, use an :is() pseudo class.\n    // Type selectors are only allowed at the start of a compound selector,\n    // so use :is() if that is not the case.\n    if ctx.selectors.0.len() == 1\n      && (first || (!has_type_selector(&ctx.selectors.0[0]) && is_simple(&ctx.selectors.0[0])))\n    {\n      serialize_selector(ctx.selectors.0.first().unwrap(), dest, ctx.parent, false)\n    } else {\n      dest.write_str(\":is(\")?;\n      serialize_selector_list(ctx.selectors.0.iter(), dest, ctx.parent, false)?;\n      dest.write_char(')')\n    }\n  } else {\n    // If there is no context, we are at the root if nesting is supported. This is equivalent to :scope.\n    // Otherwise, if nesting is supported, serialize the nesting selector directly.\n    if should_compile!(dest.targets.current, Nesting) {\n      dest.write_str(\":scope\")\n    } else {\n      dest.write_char('&')\n    }\n  }\n}\n\n#[inline]\nfn has_type_selector(selector: &Selector) -> bool {\n  // For input:checked the component vector is\n  // [input, :checked] so we have to check it using matching order.\n  //\n  // This both happens for input:checked and is(input:checked)\n  let mut iter = selector.iter_raw_match_order();\n  let first = iter.next();\n\n  if is_namespace(first) {\n    is_type_selector(iter.next())\n  } else {\n    is_type_selector(first)\n  }\n}\n\n#[inline]\nfn is_simple(selector: &Selector) -> bool {\n  !selector.iter_raw_match_order().any(|component| component.is_combinator())\n}\n\n#[inline]\nfn is_type_selector(component: Option<&Component>) -> bool {\n  matches!(\n    component,\n    Some(Component::LocalName(_)) | Some(Component::ExplicitUniversalType)\n  )\n}\n\n#[inline]\nfn is_namespace(component: Option<&Component>) -> bool {\n  matches!(\n    component,\n    Some(Component::ExplicitAnyNamespace)\n      | Some(Component::ExplicitNoNamespace)\n      | Some(Component::Namespace(..))\n      | Some(Component::DefaultNamespace(_))\n  )\n}\n\nfn serialize_selector_list<'a, 'i: 'a, I, W>(\n  iter: I,\n  dest: &mut Printer<W>,\n  context: Option<&StyleContext>,\n  is_relative: bool,\n) -> Result<(), PrinterError>\nwhere\n  I: Iterator<Item = &'a Selector<'i>>,\n  W: fmt::Write,\n{\n  let mut first = true;\n  for selector in iter {\n    if !first {\n      dest.delim(',', false)?;\n    }\n    first = false;\n    serialize_selector(selector, dest, context, is_relative)?;\n  }\n  Ok(())\n}\n\npub(crate) fn is_compatible(selectors: &[Selector], targets: Targets) -> bool {\n  for selector in selectors {\n    let iter = selector.iter_raw_match_order();\n    for component in iter {\n      let feature = match component {\n        Component::ID(_) | Component::Class(_) | Component::LocalName(_) => continue,\n\n        Component::ExplicitAnyNamespace\n        | Component::ExplicitNoNamespace\n        | Component::DefaultNamespace(_)\n        | Component::Namespace(_, _) => Feature::Namespaces,\n\n        Component::ExplicitUniversalType => Feature::Selectors2,\n\n        Component::AttributeInNoNamespaceExists { .. } => Feature::Selectors2,\n        Component::AttributeInNoNamespace {\n          operator,\n          case_sensitivity,\n          ..\n        } => {\n          if *case_sensitivity != ParsedCaseSensitivity::CaseSensitive {\n            Feature::CaseInsensitive\n          } else {\n            match operator {\n              AttrSelectorOperator::Equal | AttrSelectorOperator::Includes | AttrSelectorOperator::DashMatch => {\n                Feature::Selectors2\n              }\n              AttrSelectorOperator::Prefix | AttrSelectorOperator::Substring | AttrSelectorOperator::Suffix => {\n                Feature::Selectors3\n              }\n            }\n          }\n        }\n        Component::AttributeOther(attr) => match attr.operation {\n          ParsedAttrSelectorOperation::Exists => Feature::Selectors2,\n          ParsedAttrSelectorOperation::WithValue {\n            operator,\n            case_sensitivity,\n            ..\n          } => {\n            if case_sensitivity != ParsedCaseSensitivity::CaseSensitive {\n              Feature::CaseInsensitive\n            } else {\n              match operator {\n                AttrSelectorOperator::Equal | AttrSelectorOperator::Includes | AttrSelectorOperator::DashMatch => {\n                  Feature::Selectors2\n                }\n                AttrSelectorOperator::Prefix | AttrSelectorOperator::Substring | AttrSelectorOperator::Suffix => {\n                  Feature::Selectors3\n                }\n              }\n            }\n          }\n        },\n\n        Component::Empty | Component::Root => Feature::Selectors3,\n        Component::Negation(selectors) => {\n          // :not() selector list is not forgiving.\n          if !targets.is_compatible(Feature::Selectors3) || !is_compatible(&*selectors, targets) {\n            return false;\n          }\n          continue;\n        }\n\n        Component::Nth(data) => match data.ty {\n          NthType::Child if data.a == 0 && data.b == 1 => Feature::Selectors2,\n          NthType::Col | NthType::LastCol => return false,\n          _ => Feature::Selectors3,\n        },\n        Component::NthOf(n) => {\n          if !targets.is_compatible(Feature::NthChildOf) || !is_compatible(n.selectors(), targets) {\n            return false;\n          }\n          continue;\n        }\n\n        // These support forgiving selector lists, so no need to check nested selectors.\n        Component::Is(selectors) => {\n          // ... except if we are going to unwrap them.\n          if should_unwrap_is(selectors) && is_compatible(selectors, targets) {\n            continue;\n          }\n\n          Feature::IsSelector\n        }\n        Component::Where(_) | Component::Nesting => Feature::IsSelector,\n        Component::Any(..) => return false,\n        Component::Has(selectors) => {\n          if !targets.is_compatible(Feature::HasSelector) || !is_compatible(&*selectors, targets) {\n            return false;\n          }\n          continue;\n        }\n\n        Component::Scope | Component::Host(_) | Component::Slotted(_) => Feature::Shadowdomv1,\n\n        Component::Part(_) => Feature::PartPseudo,\n\n        Component::NonTSPseudoClass(pseudo) => {\n          match pseudo {\n            PseudoClass::Link\n            | PseudoClass::Visited\n            | PseudoClass::Active\n            | PseudoClass::Hover\n            | PseudoClass::Focus\n            | PseudoClass::Lang { languages: _ } => Feature::Selectors2,\n\n            PseudoClass::Checked | PseudoClass::Disabled | PseudoClass::Enabled | PseudoClass::Target => {\n              Feature::Selectors3\n            }\n\n            PseudoClass::AnyLink(prefix) if *prefix == VendorPrefix::None => Feature::AnyLink,\n            PseudoClass::Indeterminate => Feature::IndeterminatePseudo,\n\n            PseudoClass::Fullscreen(prefix) if *prefix == VendorPrefix::None => Feature::Fullscreen,\n\n            PseudoClass::FocusVisible => Feature::FocusVisible,\n            PseudoClass::FocusWithin => Feature::FocusWithin,\n            PseudoClass::Default => Feature::DefaultPseudo,\n            PseudoClass::Dir { direction: _ } => Feature::DirSelector,\n            PseudoClass::Optional => Feature::OptionalPseudo,\n            PseudoClass::PlaceholderShown(prefix) if *prefix == VendorPrefix::None => Feature::PlaceholderShown,\n\n            PseudoClass::ReadOnly(prefix) | PseudoClass::ReadWrite(prefix) if *prefix == VendorPrefix::None => {\n              Feature::ReadOnlyWrite\n            }\n\n            PseudoClass::Valid | PseudoClass::Invalid | PseudoClass::Required => Feature::FormValidation,\n\n            PseudoClass::InRange | PseudoClass::OutOfRange => Feature::InOutOfRange,\n\n            PseudoClass::Autofill(prefix) if *prefix == VendorPrefix::None => Feature::Autofill,\n\n            PseudoClass::State { .. } => Feature::StatePseudoClass,\n\n            // Experimental, no browser support.\n            PseudoClass::Current\n            | PseudoClass::Past\n            | PseudoClass::Future\n            | PseudoClass::Playing\n            | PseudoClass::Paused\n            | PseudoClass::Seeking\n            | PseudoClass::Stalled\n            | PseudoClass::Buffering\n            | PseudoClass::Muted\n            | PseudoClass::VolumeLocked\n            | PseudoClass::TargetWithin\n            | PseudoClass::LocalLink\n            | PseudoClass::Blank\n            | PseudoClass::UserInvalid\n            | PseudoClass::UserValid\n            | PseudoClass::Defined\n            | PseudoClass::ActiveViewTransition\n            | PseudoClass::ActiveViewTransitionType { .. } => return false,\n\n            PseudoClass::Custom { .. } | _ => return false,\n          }\n        }\n\n        Component::PseudoElement(pseudo) => match pseudo {\n          PseudoElement::After | PseudoElement::Before => Feature::Gencontent,\n          PseudoElement::FirstLine => Feature::FirstLine,\n          PseudoElement::FirstLetter => Feature::FirstLetter,\n          PseudoElement::DetailsContent => Feature::DetailsContent,\n          PseudoElement::TargetText => Feature::TargetText,\n          PseudoElement::Selection(prefix) if *prefix == VendorPrefix::None => Feature::Selection,\n          PseudoElement::Placeholder(prefix) if *prefix == VendorPrefix::None => Feature::Placeholder,\n          PseudoElement::HighlightFunction { name: _ } => Feature::Highlight,\n          PseudoElement::Marker => Feature::MarkerPseudo,\n          PseudoElement::Backdrop(prefix) if *prefix == VendorPrefix::None => Feature::Dialog,\n          PseudoElement::Cue => Feature::Cue,\n          PseudoElement::CueFunction { selector: _ } => Feature::CueFunction,\n          PseudoElement::ViewTransition\n          | PseudoElement::ViewTransitionNew { .. }\n          | PseudoElement::ViewTransitionOld { .. }\n          | PseudoElement::ViewTransitionGroup { .. }\n          | PseudoElement::ViewTransitionImagePair { .. } => Feature::ViewTransition,\n          PseudoElement::PickerFunction { identifier: _ } => Feature::Picker,\n          PseudoElement::PickerIcon => Feature::PickerIcon,\n          PseudoElement::Checkmark => Feature::Checkmark,\n          PseudoElement::GrammarError => Feature::GrammarError,\n          PseudoElement::SpellingError => Feature::SpellingError,\n          PseudoElement::Custom { name: _ } | _ => return false,\n        },\n\n        Component::Combinator(combinator) => match combinator {\n          Combinator::Child | Combinator::NextSibling => Feature::Selectors2,\n          Combinator::LaterSibling => Feature::Selectors3,\n          _ => continue,\n        },\n      };\n\n      if !targets.is_compatible(feature) {\n        return false;\n      }\n    }\n  }\n\n  true\n}\n\n/// Returns whether two selector lists are equivalent, i.e. the same minus any vendor prefix differences.\npub(crate) fn is_equivalent<'i>(selectors: &[Selector<'i>], other: &[Selector<'i>]) -> bool {\n  if selectors.len() != other.len() {\n    return false;\n  }\n\n  for (i, a) in selectors.iter().enumerate() {\n    let b = &other[i];\n    if a.len() != b.len() {\n      return false;\n    }\n\n    for (a, b) in a.iter_raw_match_order().zip(b.iter_raw_match_order()) {\n      let is_equivalent = match (a, b) {\n        (Component::NonTSPseudoClass(a_ps), Component::NonTSPseudoClass(b_ps)) => a_ps.is_equivalent(b_ps),\n        (Component::PseudoElement(a_pe), Component::PseudoElement(b_pe)) => a_pe.is_equivalent(b_pe),\n        (Component::Any(_, a), Component::Is(b))\n        | (Component::Is(a), Component::Any(_, b))\n        | (Component::Any(_, a), Component::Any(_, b))\n        | (Component::Is(a), Component::Is(b)) => is_equivalent(&*a, &*b),\n        (a, b) => a == b,\n      };\n\n      if !is_equivalent {\n        return false;\n      }\n    }\n  }\n\n  true\n}\n\n/// Returns the vendor prefix (if any) used in the given selector list.\n/// If multiple vendor prefixes are seen, this is invalid, and an empty result is returned.\npub(crate) fn get_prefix(selectors: &SelectorList) -> VendorPrefix {\n  let mut prefix = VendorPrefix::empty();\n  for selector in &selectors.0 {\n    for component in selector.iter_raw_match_order() {\n      let p = match component {\n        // Return none rather than empty for these so that we call downlevel_selectors.\n        Component::NonTSPseudoClass(PseudoClass::Lang { .. })\n        | Component::NonTSPseudoClass(PseudoClass::Dir { .. })\n        | Component::Is(..)\n        | Component::Where(..)\n        | Component::Has(..)\n        | Component::Negation(..) => VendorPrefix::None,\n        Component::Any(prefix, _) => *prefix,\n        Component::NonTSPseudoClass(pc) => pc.get_prefix(),\n        Component::PseudoElement(pe) => pe.get_prefix(),\n        _ => VendorPrefix::empty(),\n      };\n\n      if !p.is_empty() {\n        // Allow none to be mixed with a prefix.\n        let prefix_without_none = prefix - VendorPrefix::None;\n        if prefix_without_none.is_empty() || prefix_without_none == p {\n          prefix |= p;\n        } else {\n          return VendorPrefix::empty();\n        }\n      }\n    }\n  }\n\n  prefix\n}\n\nconst RTL_LANGS: &[&str] = &[\n  \"ae\", \"ar\", \"arc\", \"bcc\", \"bqi\", \"ckb\", \"dv\", \"fa\", \"glk\", \"he\", \"ku\", \"mzn\", \"nqo\", \"pnb\", \"ps\", \"sd\", \"ug\",\n  \"ur\", \"yi\",\n];\n\n/// Downlevels the given selectors to be compatible with the given browser targets.\n/// Returns the necessary vendor prefixes.\npub(crate) fn downlevel_selectors(selectors: &mut [Selector], targets: Targets) -> VendorPrefix {\n  let mut necessary_prefixes = VendorPrefix::empty();\n  for selector in selectors {\n    for component in selector.iter_mut_raw_match_order() {\n      necessary_prefixes |= downlevel_component(component, targets);\n    }\n  }\n\n  necessary_prefixes\n}\n\nfn downlevel_component<'i>(component: &mut Component<'i>, targets: Targets) -> VendorPrefix {\n  match component {\n    Component::NonTSPseudoClass(pc) => {\n      match pc {\n        PseudoClass::Dir { direction: dir } => {\n          if should_compile!(targets, DirSelector) {\n            *component = downlevel_dir(*dir, targets);\n            downlevel_component(component, targets)\n          } else {\n            VendorPrefix::empty()\n          }\n        }\n        PseudoClass::Lang { languages: langs } => {\n          // :lang() with multiple languages is not supported everywhere.\n          // compile this to :is(:lang(a), :lang(b)) etc.\n          if langs.len() > 1 && should_compile!(targets, LangSelectorList) {\n            *component = Component::Is(lang_list_to_selectors(&langs));\n            downlevel_component(component, targets)\n          } else {\n            VendorPrefix::empty()\n          }\n        }\n        _ => pc.get_necessary_prefixes(targets),\n      }\n    }\n    Component::PseudoElement(pe) => pe.get_necessary_prefixes(targets),\n    Component::Is(selectors) => {\n      let mut necessary_prefixes = downlevel_selectors(&mut **selectors, targets);\n\n      // Convert :is to :-webkit-any/:-moz-any if needed.\n      // All selectors must be simple, no combinators are supported.\n      if should_compile!(targets, IsSelector)\n        && !should_unwrap_is(selectors)\n        && selectors.iter().all(|selector| !selector.has_combinator())\n      {\n        necessary_prefixes |= targets.prefixes(VendorPrefix::None, crate::prefixes::Feature::AnyPseudo)\n      } else {\n        necessary_prefixes |= VendorPrefix::None\n      }\n\n      necessary_prefixes\n    }\n    Component::Negation(selectors) => {\n      let mut necessary_prefixes = downlevel_selectors(&mut **selectors, targets);\n\n      // Downlevel :not(.a, .b) -> :not(:is(.a, .b)) if not list is unsupported.\n      // We need to use :is() / :-webkit-any() rather than :not(.a):not(.b) to ensure the specificity is equivalent.\n      // https://drafts.csswg.org/selectors/#specificity-rules\n      if selectors.len() > 1 && should_compile!(targets, NotSelectorList) {\n        *component =\n          Component::Negation(vec![Selector::from(Component::Is(selectors.clone()))].into_boxed_slice());\n\n        if should_compile!(targets, IsSelector) {\n          necessary_prefixes |= targets.prefixes(VendorPrefix::None, crate::prefixes::Feature::AnyPseudo)\n        } else {\n          necessary_prefixes |= VendorPrefix::None\n        }\n      }\n\n      necessary_prefixes\n    }\n    Component::Where(selectors) | Component::Any(_, selectors) | Component::Has(selectors) => {\n      downlevel_selectors(&mut **selectors, targets)\n    }\n    _ => VendorPrefix::empty(),\n  }\n}\n\nfn lang_list_to_selectors<'i>(langs: &Vec<CowArcStr<'i>>) -> Box<[Selector<'i>]> {\n  langs\n    .iter()\n    .map(|lang| {\n      Selector::from(Component::NonTSPseudoClass(PseudoClass::Lang {\n        languages: vec![lang.clone()],\n      }))\n    })\n    .collect::<Vec<Selector>>()\n    .into_boxed_slice()\n}\n\nfn downlevel_dir<'i>(dir: Direction, targets: Targets) -> Component<'i> {\n  // Convert :dir to :lang. If supported, use a list of languages in a single :lang,\n  // otherwise, use :is/:not, which may be further downleveled to e.g. :-webkit-any.\n  let langs = RTL_LANGS.iter().map(|lang| (*lang).into()).collect();\n  if !should_compile!(targets, LangSelectorList) {\n    let c = Component::NonTSPseudoClass(PseudoClass::Lang { languages: langs });\n    if dir == Direction::Ltr {\n      Component::Negation(vec![Selector::from(c)].into_boxed_slice())\n    } else {\n      c\n    }\n  } else {\n    if dir == Direction::Ltr {\n      Component::Negation(lang_list_to_selectors(&langs))\n    } else {\n      Component::Is(lang_list_to_selectors(&langs))\n    }\n  }\n}\n\n/// Determines whether a selector list contains only unused selectors.\n/// A selector is considered unused if it contains a class or id component that exists in the set of unused symbols.\npub(crate) fn is_unused(\n  selectors: &mut std::slice::Iter<Selector>,\n  unused_symbols: &HashSet<String>,\n  parent_is_unused: bool,\n) -> bool {\n  if unused_symbols.is_empty() {\n    return false;\n  }\n\n  selectors.all(|selector| {\n    for component in selector.iter_raw_match_order() {\n      match component {\n        Component::Class(name) | Component::ID(name) => {\n          if unused_symbols.contains(&name.0.to_string()) {\n            return true;\n          }\n        }\n        Component::Is(is) | Component::Where(is) | Component::Any(_, is) => {\n          if is_unused(&mut is.iter(), unused_symbols, parent_is_unused) {\n            return true;\n          }\n        }\n        Component::Nesting => {\n          if parent_is_unused {\n            return true;\n          }\n        }\n        _ => {}\n      }\n    }\n\n    false\n  })\n}\n\n/// Returns whether the selector has any class or id components.\npub(crate) fn is_pure_css_modules_selector(selector: &Selector) -> bool {\n  use parcel_selectors::parser::Component;\n  selector.iter_raw_match_order().any(|c| match c {\n    Component::Class(_) | Component::ID(_) => true,\n    Component::Is(s) | Component::Where(s) | Component::Has(s) | Component::Any(_, s) | Component::Negation(s) => {\n      s.iter().any(is_pure_css_modules_selector)\n    }\n    Component::NthOf(nth) => nth.selectors().iter().any(is_pure_css_modules_selector),\n    Component::Slotted(s) => is_pure_css_modules_selector(&s),\n    Component::Host(s) => s.as_ref().map(is_pure_css_modules_selector).unwrap_or(false),\n    Component::NonTSPseudoClass(pc) => match pc {\n      PseudoClass::Local { selector } => is_pure_css_modules_selector(&*selector),\n      _ => false,\n    },\n    _ => false,\n  })\n}\n\n#[cfg(feature = \"visitor\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"visitor\")))]\nimpl<'i, T: Visit<'i, T, V>, V: ?Sized + Visitor<'i, T>> Visit<'i, T, V> for SelectorList<'i> {\n  const CHILD_TYPES: VisitTypes = VisitTypes::SELECTORS;\n\n  fn visit(&mut self, visitor: &mut V) -> Result<(), V::Error> {\n    if visitor.visit_types().contains(VisitTypes::SELECTORS) {\n      visitor.visit_selector_list(self)\n    } else {\n      self.visit_children(visitor)\n    }\n  }\n\n  fn visit_children(&mut self, visitor: &mut V) -> Result<(), V::Error> {\n    self.0.iter_mut().try_for_each(|selector| Visit::visit(selector, visitor))\n  }\n}\n\n#[cfg(feature = \"visitor\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"visitor\")))]\nimpl<'i, T: Visit<'i, T, V>, V: ?Sized + Visitor<'i, T>> Visit<'i, T, V> for Selector<'i> {\n  const CHILD_TYPES: VisitTypes = VisitTypes::SELECTORS;\n\n  fn visit(&mut self, visitor: &mut V) -> Result<(), V::Error> {\n    visitor.visit_selector(self)\n  }\n\n  fn visit_children(&mut self, _visitor: &mut V) -> Result<(), V::Error> {\n    Ok(())\n  }\n}\n\nimpl<'i> ParseWithOptions<'i> for Selector<'i> {\n  fn parse_with_options<'t>(\n    input: &mut Parser<'i, 't>,\n    options: &ParserOptions<'_, 'i>,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    Selector::parse(\n      &SelectorParser {\n        is_nesting_allowed: true,\n        options: &options,\n      },\n      input,\n    )\n  }\n}\n\nimpl<'i> ParseWithOptions<'i> for SelectorList<'i> {\n  fn parse_with_options<'t>(\n    input: &mut Parser<'i, 't>,\n    options: &ParserOptions<'_, 'i>,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    SelectorList::parse(\n      &SelectorParser {\n        is_nesting_allowed: true,\n        options: &options,\n      },\n      input,\n      parcel_selectors::parser::ParseErrorRecovery::DiscardList,\n      parcel_selectors::parser::NestingRequirement::None,\n    )\n  }\n}\n"
  },
  {
    "path": "src/serialization.rs",
    "content": "#![allow(non_snake_case)]\n\nmacro_rules! wrapper {\n  ($name: ident, $value: ident $(, $t: ty)?) => {\n    #[derive(serde::Serialize, serde::Deserialize)]\n    #[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n    pub struct $name<T $(= $t)?> {\n      $value: T,\n    }\n\n    impl<'de, T> $name<T> {\n      pub fn serialize<S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>\n      where\n        S: serde::Serializer,\n        T: serde::Serialize,\n      {\n        let wrapper = $name { $value: value };\n        serde::Serialize::serialize(&wrapper, serializer)\n      }\n\n      pub fn deserialize<D>(deserializer: D) -> Result<T, D::Error>\n      where\n        D: serde::Deserializer<'de>,\n        T: serde::Deserialize<'de>,\n      {\n        let v: $name<T> = serde::Deserialize::deserialize(deserializer)?;\n        Ok(v.$value)\n      }\n    }\n  };\n}\n\nwrapper!(ValueWrapper, value);\nwrapper!(PrefixWrapper, vendorPrefix, crate::vendor_prefix::VendorPrefix);\n"
  },
  {
    "path": "src/stylesheet.rs",
    "content": "//! CSS style sheets and style attributes.\n//!\n//! A [StyleSheet](StyleSheet) represents a `.css` file or `<style>` element in HTML.\n//! A [StyleAttribute](StyleAttribute) represents an inline `style` attribute in HTML.\n\nuse crate::context::{DeclarationContext, PropertyHandlerContext};\nuse crate::css_modules::{hash, CssModule, CssModuleExports, CssModuleReferences};\nuse crate::declaration::{DeclarationBlock, DeclarationHandler};\nuse crate::dependencies::Dependency;\nuse crate::error::{Error, ErrorLocation, MinifyErrorKind, ParserError, PrinterError, PrinterErrorKind};\nuse crate::parser::{DefaultAtRule, DefaultAtRuleParser, TopLevelRuleParser};\nuse crate::printer::Printer;\nuse crate::rules::{CssRule, CssRuleList, MinifyContext};\nuse crate::targets::{should_compile, Targets, TargetsWithSupportsScope};\nuse crate::traits::{AtRuleParser, ToCss};\nuse crate::values::string::CowArcStr;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::{Visit, VisitTypes, Visitor};\nuse cssparser::{Parser, ParserInput, StyleSheetParser};\n#[cfg(feature = \"sourcemap\")]\nuse parcel_sourcemap::SourceMap;\nuse std::collections::{HashMap, HashSet};\n\npub use crate::parser::{ParserFlags, ParserOptions};\npub use crate::printer::PrinterOptions;\npub use crate::printer::PseudoClasses;\n\n/// A CSS style sheet, representing a `.css` file or inline `<style>` element.\n///\n/// Style sheets can be parsed from a string, constructed from scratch,\n/// or created using a [Bundler](super::bundler::Bundler). Then, they can be\n/// minified and transformed for a set of target browsers, and serialied to a string.\n///\n/// # Example\n///\n/// ```\n/// use lightningcss::stylesheet::{\n///   StyleSheet, ParserOptions, MinifyOptions, PrinterOptions\n/// };\n///\n/// // Parse a style sheet from a string.\n/// let mut stylesheet = StyleSheet::parse(\n///   r#\"\n///   .foo {\n///     color: red;\n///   }\n///\n///   .bar {\n///     color: red;\n///   }\n///   \"#,\n///   ParserOptions::default()\n/// ).unwrap();\n///\n/// // Minify the stylesheet.\n/// stylesheet.minify(MinifyOptions::default()).unwrap();\n///\n/// // Serialize it to a string.\n/// let res = stylesheet.to_css(PrinterOptions::default()).unwrap();\n/// assert_eq!(res.code, \".foo, .bar {\\n  color: red;\\n}\\n\");\n/// ```\n#[derive(Debug)]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"camelCase\")\n)]\n#[cfg_attr(\n  feature = \"jsonschema\",\n  derive(schemars::JsonSchema),\n  schemars(rename = \"StyleSheet\", bound = \"T: schemars::JsonSchema\")\n)]\npub struct StyleSheet<'i, 'o, T = DefaultAtRule> {\n  /// A list of top-level rules within the style sheet.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  pub rules: CssRuleList<'i, T>,\n  /// A list of file names for all source files included within the style sheet.\n  /// Sources are referenced by index in the `loc` property of each rule.\n  pub sources: Vec<String>,\n  /// The source map URL extracted from the original style sheet.\n  pub(crate) source_map_urls: Vec<Option<String>>,\n  /// The license comments that appeared at the start of the file.\n  pub license_comments: Vec<CowArcStr<'i>>,\n  /// A list of content hashes for all source files included within the style sheet.\n  /// This is only set if CSS modules are enabled and the pattern includes [content-hash].\n  #[cfg_attr(feature = \"serde\", serde(skip))]\n  pub(crate) content_hashes: Option<Vec<String>>,\n  #[cfg_attr(feature = \"serde\", serde(skip))]\n  /// The options the style sheet was originally parsed with.\n  options: ParserOptions<'o, 'i>,\n}\n\n/// Options for the `minify` function of a [StyleSheet](StyleSheet)\n/// or [StyleAttribute](StyleAttribute).\n#[derive(Default)]\npub struct MinifyOptions {\n  /// Targets to compile the CSS for.\n  pub targets: Targets,\n  /// A list of known unused symbols, including CSS class names,\n  /// ids, and `@keyframe` names. The declarations of these will be removed.\n  pub unused_symbols: HashSet<String>,\n}\n\n/// A result returned from `to_css`, including the serialize CSS\n/// and other metadata depending on the input options.\n#[derive(Debug)]\npub struct ToCssResult {\n  /// Serialized CSS code.\n  pub code: String,\n  /// A map of CSS module exports, if the `css_modules` option was\n  /// enabled during parsing.\n  pub exports: Option<CssModuleExports>,\n  /// A map of CSS module references, if the `css_modules` config\n  /// had `dashed_idents` enabled.\n  pub references: Option<CssModuleReferences>,\n  /// A list of dependencies (e.g. `@import` or `url()`) found in\n  /// the style sheet, if the `analyze_dependencies` option is enabled.\n  pub dependencies: Option<Vec<Dependency>>,\n}\n\nimpl<'i, 'o> StyleSheet<'i, 'o, DefaultAtRule> {\n  /// Parse a style sheet from a string.\n  pub fn parse(code: &'i str, options: ParserOptions<'o, 'i>) -> Result<Self, Error<ParserError<'i>>> {\n    Self::parse_with(code, options, &mut DefaultAtRuleParser)\n  }\n}\n\nimpl<'i, 'o, T> StyleSheet<'i, 'o, T>\nwhere\n  T: ToCss + Clone,\n{\n  /// Creates a new style sheet with the given source filenames and rules.\n  pub fn new(\n    sources: Vec<String>,\n    rules: CssRuleList<'i, T>,\n    options: ParserOptions<'o, 'i>,\n  ) -> StyleSheet<'i, 'o, T> {\n    StyleSheet {\n      sources,\n      source_map_urls: Vec::new(),\n      license_comments: Vec::new(),\n      content_hashes: None,\n      rules,\n      options,\n    }\n  }\n\n  /// Parse a style sheet from a string.\n  pub fn parse_with<P: AtRuleParser<'i, AtRule = T>>(\n    code: &'i str,\n    mut options: ParserOptions<'o, 'i>,\n    at_rule_parser: &mut P,\n  ) -> Result<Self, Error<ParserError<'i>>> {\n    let mut input = ParserInput::new(&code);\n    let mut parser = Parser::new(&mut input);\n    let mut license_comments = Vec::new();\n\n    let mut content_hashes = None;\n    if let Some(config) = &options.css_modules {\n      if config.pattern.has_content_hash() {\n        content_hashes = Some(vec![hash(\n          &code,\n          matches!(config.pattern.segments[0], crate::css_modules::Segment::ContentHash),\n        )]);\n      }\n    }\n\n    let mut state = parser.state();\n    while let Ok(token) = parser.next_including_whitespace_and_comments() {\n      match token {\n        cssparser::Token::WhiteSpace(..) => {}\n        cssparser::Token::Comment(comment) if comment.starts_with('!') => {\n          license_comments.push((*comment).into());\n        }\n        cssparser::Token::Comment(comment) if comment.contains(\"cssmodules-pure-no-check\") => {\n          if let Some(css_modules) = &mut options.css_modules {\n            css_modules.pure = false;\n          }\n        }\n        _ => break,\n      }\n      state = parser.state();\n    }\n    parser.reset(&state);\n\n    let mut rules = CssRuleList(vec![]);\n    let mut rule_parser = TopLevelRuleParser::new(&mut options, at_rule_parser, &mut rules);\n    let mut rule_list_parser = StyleSheetParser::new(&mut parser, &mut rule_parser);\n\n    while let Some(rule) = rule_list_parser.next() {\n      match rule {\n        Ok(()) => {}\n        Err((e, _)) => {\n          let options = &mut rule_list_parser.parser.options;\n          if options.error_recovery {\n            options.warn(e);\n            continue;\n          }\n\n          return Err(Error::from(e, options.filename.clone()));\n        }\n      }\n    }\n\n    Ok(StyleSheet {\n      sources: vec![options.filename.clone()],\n      source_map_urls: vec![parser.current_source_map_url().map(|s| s.to_owned())],\n      content_hashes,\n      rules,\n      license_comments,\n      options,\n    })\n  }\n\n  /// Returns the source map URL for the source at the given index.\n  pub fn source_map_url(&self, source_index: usize) -> Option<&String> {\n    self.source_map_urls.get(source_index)?.as_ref()\n  }\n\n  /// Returns the inline source map associated with the source at the given index.\n  #[cfg(feature = \"sourcemap\")]\n  #[cfg_attr(docsrs, doc(cfg(feature = \"sourcemap\")))]\n  pub fn source_map(&self, source_index: usize) -> Option<SourceMap> {\n    SourceMap::from_data_url(\"/\", self.source_map_url(source_index)?).ok()\n  }\n\n  /// Minify and transform the style sheet for the provided browser targets.\n  pub fn minify(&mut self, options: MinifyOptions) -> Result<(), Error<MinifyErrorKind>> {\n    let context = PropertyHandlerContext::new(options.targets, &options.unused_symbols);\n    let mut handler = DeclarationHandler::default();\n    let mut important_handler = DeclarationHandler::default();\n\n    // @custom-media rules may be defined after they are referenced, but may only be defined at the top level\n    // of a stylesheet. Do a pre-scan here and create a lookup table by name.\n    let custom_media = if self.options.flags.contains(ParserFlags::CUSTOM_MEDIA)\n      && should_compile!(options.targets, CustomMediaQueries)\n    {\n      let mut custom_media = HashMap::new();\n      for rule in &self.rules.0 {\n        if let CssRule::CustomMedia(rule) = rule {\n          custom_media.insert(rule.name.0.clone(), rule.clone());\n        }\n      }\n      Some(custom_media)\n    } else {\n      None\n    };\n\n    let mut ctx = MinifyContext {\n      targets: TargetsWithSupportsScope::new(options.targets),\n      handler: &mut handler,\n      important_handler: &mut important_handler,\n      handler_context: context,\n      unused_symbols: &options.unused_symbols,\n      custom_media,\n      css_modules: self.options.css_modules.is_some(),\n      pure_css_modules: self.options.css_modules.as_ref().map(|c| c.pure).unwrap_or_default(),\n    };\n\n    self.rules.minify(&mut ctx, false).map_err(|e| Error {\n      kind: e.kind,\n      loc: Some(ErrorLocation::new(\n        e.loc,\n        self.sources[e.loc.source_index as usize].clone(),\n      )),\n    })?;\n\n    Ok(())\n  }\n\n  /// Serialize the style sheet to a CSS string.\n  pub fn to_css(&self, options: PrinterOptions) -> Result<ToCssResult, Error<PrinterErrorKind>> {\n    // Make sure we always have capacity > 0: https://github.com/napi-rs/napi-rs/issues/1124.\n    let mut dest = String::with_capacity(1);\n    let project_root = options.project_root.clone();\n    let mut printer = Printer::new(&mut dest, options);\n\n    #[cfg(feature = \"sourcemap\")]\n    {\n      printer.sources = Some(&self.sources);\n    }\n\n    #[cfg(feature = \"sourcemap\")]\n    if printer.source_map.is_some() {\n      printer.source_maps = self.sources.iter().enumerate().map(|(i, _)| self.source_map(i)).collect();\n    }\n\n    for comment in &self.license_comments {\n      printer.write_str(\"/*\")?;\n      printer.write_str_with_newlines(comment)?;\n      printer.write_str_with_newlines(\"*/\\n\")?;\n    }\n\n    if let Some(config) = &self.options.css_modules {\n      let mut references = HashMap::new();\n      printer.css_module = Some(CssModule::new(\n        config,\n        &self.sources,\n        project_root,\n        &mut references,\n        &self.content_hashes,\n      ));\n\n      self.rules.to_css(&mut printer)?;\n      printer.newline()?;\n\n      Ok(ToCssResult {\n        dependencies: printer.dependencies,\n        exports: Some(std::mem::take(\n          &mut printer.css_module.unwrap().exports_by_source_index[0],\n        )),\n        code: dest,\n        references: Some(references),\n      })\n    } else {\n      self.rules.to_css(&mut printer)?;\n      printer.newline()?;\n\n      Ok(ToCssResult {\n        dependencies: printer.dependencies,\n        code: dest,\n        exports: None,\n        references: None,\n      })\n    }\n  }\n}\n\n#[cfg(feature = \"visitor\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"visitor\")))]\nimpl<'i, 'o, T, V> Visit<'i, T, V> for StyleSheet<'i, 'o, T>\nwhere\n  T: Visit<'i, T, V>,\n  V: ?Sized + Visitor<'i, T>,\n{\n  const CHILD_TYPES: VisitTypes = VisitTypes::all();\n\n  fn visit(&mut self, visitor: &mut V) -> Result<(), V::Error> {\n    visitor.visit_stylesheet(self)\n  }\n\n  fn visit_children(&mut self, visitor: &mut V) -> Result<(), V::Error> {\n    self.rules.visit(visitor)\n  }\n}\n\n/// An inline style attribute, as in HTML or SVG.\n///\n/// Style attributes can be parsed from a string, minified and transformed\n/// for a set of target browsers, and serialied to a string.\n///\n/// # Example\n///\n/// ```\n/// use lightningcss::stylesheet::{\n///   StyleAttribute, ParserOptions, MinifyOptions, PrinterOptions\n/// };\n///\n/// // Parse a style sheet from a string.\n/// let mut style = StyleAttribute::parse(\n///   \"color: yellow; font-family: 'Helvetica';\",\n///   ParserOptions::default()\n/// ).unwrap();\n///\n/// // Minify the stylesheet.\n/// style.minify(MinifyOptions::default());\n///\n/// // Serialize it to a string.\n/// let res = style.to_css(PrinterOptions::default()).unwrap();\n/// assert_eq!(res.code, \"color: #ff0; font-family: Helvetica\");\n/// ```\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub struct StyleAttribute<'i> {\n  /// The declarations in the style attribute.\n  pub declarations: DeclarationBlock<'i>,\n  #[cfg_attr(feature = \"visitor\", skip_visit)]\n  sources: Vec<String>,\n}\n\nimpl<'i> StyleAttribute<'i> {\n  /// Parses a style attribute from a string.\n  pub fn parse(\n    code: &'i str,\n    options: ParserOptions<'_, 'i>,\n  ) -> Result<StyleAttribute<'i>, Error<ParserError<'i>>> {\n    let mut input = ParserInput::new(&code);\n    let mut parser = Parser::new(&mut input);\n    Ok(StyleAttribute {\n      declarations: DeclarationBlock::parse(&mut parser, &options).map_err(|e| Error::from(e, \"\".into()))?,\n      sources: vec![options.filename],\n    })\n  }\n\n  /// Minify and transform the style attribute for the provided browser targets.\n  pub fn minify(&mut self, options: MinifyOptions) {\n    let mut context = PropertyHandlerContext::new(options.targets, &options.unused_symbols);\n    let mut handler = DeclarationHandler::default();\n    let mut important_handler = DeclarationHandler::default();\n    context.context = DeclarationContext::StyleAttribute;\n    self.declarations.minify(&mut handler, &mut important_handler, &mut context);\n  }\n\n  /// Serializes the style attribute to a CSS string.\n  pub fn to_css(&self, options: PrinterOptions) -> Result<ToCssResult, PrinterError> {\n    #[cfg(feature = \"sourcemap\")]\n    assert!(\n      options.source_map.is_none(),\n      \"Source maps are not supported for style attributes\"\n    );\n\n    // Make sure we always have capacity > 0: https://github.com/napi-rs/napi-rs/issues/1124.\n    let mut dest = String::with_capacity(1);\n    let mut printer = Printer::new(&mut dest, options);\n    printer.sources = Some(&self.sources);\n\n    self.declarations.to_css(&mut printer)?;\n\n    Ok(ToCssResult {\n      dependencies: printer.dependencies,\n      code: dest,\n      exports: None,\n      references: None,\n    })\n  }\n}\n"
  },
  {
    "path": "src/targets.rs",
    "content": "//! Browser target options.\n\n#![allow(missing_docs)]\n\nuse std::borrow::Borrow;\n\nuse crate::vendor_prefix::VendorPrefix;\nuse bitflags::bitflags;\n#[cfg(any(feature = \"serde\", feature = \"nodejs\"))]\nuse serde::{Deserialize, Serialize};\n\n/// Browser versions to compile CSS for.\n///\n/// Versions are represented as a single 24-bit integer, with one byte\n/// per `major.minor.patch` component.\n///\n/// # Example\n///\n/// This example represents a target of Safari 13.2.0.\n///\n/// ```\n/// use lightningcss::targets::Browsers;\n///\n/// let targets = Browsers {\n///   safari: Some((13 << 16) | (2 << 8)),\n///   ..Browsers::default()\n/// };\n/// ```\n#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]\n#[cfg_attr(any(feature = \"serde\", feature = \"nodejs\"), derive(Serialize, Deserialize))]\n#[allow(missing_docs)]\npub struct Browsers {\n  pub android: Option<u32>,\n  pub chrome: Option<u32>,\n  pub edge: Option<u32>,\n  pub firefox: Option<u32>,\n  pub ie: Option<u32>,\n  pub ios_saf: Option<u32>,\n  pub opera: Option<u32>,\n  pub safari: Option<u32>,\n  pub samsung: Option<u32>,\n}\n\n#[cfg(feature = \"browserslist\")]\npub use browserslist::Opts as BrowserslistConfig;\n\n#[cfg(feature = \"browserslist\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"browserslist\")))]\nimpl Browsers {\n  /// Parses a list of browserslist queries into Lightning CSS targets.\n  pub fn from_browserslist<S: AsRef<str>, I: IntoIterator<Item = S>>(\n    query: I,\n  ) -> Result<Option<Browsers>, browserslist::Error> {\n    Self::from_browserslist_with_config(query, BrowserslistConfig::default())\n  }\n\n  /// Parses a list of browserslist queries into Lightning CSS targets.\n  pub fn from_browserslist_with_config<S: AsRef<str>, I: IntoIterator<Item = S>>(\n    query: I,\n    config: BrowserslistConfig,\n  ) -> Result<Option<Browsers>, browserslist::Error> {\n    use browserslist::resolve;\n\n    Self::from_distribs(resolve(query, &config)?)\n  }\n\n  #[cfg(not(target_arch = \"wasm32\"))]\n  /// Finds browserslist configuration, selects queries by environment and loads the resulting queries into LightningCSS targets.\n  ///\n  /// Configuration resolution is modeled after the original `browserslist` nodeJS package.\n  /// The configuration is resolved in the following order:\n  ///\n  /// - If a `BROWSERSLIST` environment variable is present, then load targets from its value. This is analog to the `--targets` CLI option.\n  ///   Example: `BROWSERSLIST=\"firefox ESR\" lightningcss [OPTIONS] <INPUT_FILE>`\n  /// - If a `BROWSERSLIST_CONFIG` environment variable is present, then resolve the file at the provided path.\n  ///   Then parse and use targets from `package.json` or any browserslist configuration file pointed to by the environment variable.\n  ///   Example: `BROWSERSLIST_CONFIG=\"../config/browserslist\" lightningcss [OPTIONS] <INPUT_FILE>`\n  /// - If none of the above apply, then find, parse and use targets from the first `browserslist`, `.browserslistrc`\n  ///   or `package.json` configuration file in any parent directory.\n  ///\n  /// When using parsed configuration from `browserslist`, `.browserslistrc` or `package.json` configuration files,\n  /// the environment determined by:\n  ///\n  /// - the `BROWSERSLIST_ENV` environment variable if present,\n  /// - otherwise the `NODE_ENV` environment varialbe if present,\n  /// - otherwise `production` is used.\n  ///\n  /// If no targets are found for the resulting environment, then the `defaults` configuration section is used.\n  pub fn load_browserslist() -> Result<Option<Browsers>, browserslist::Error> {\n    use browserslist::{execute, Opts};\n\n    Self::from_distribs(execute(&Opts::default())?)\n  }\n\n  fn from_distribs(distribs: Vec<browserslist::Distrib>) -> Result<Option<Browsers>, browserslist::Error> {\n    let mut browsers = Browsers::default();\n    let mut has_any = false;\n    for distrib in distribs {\n      macro_rules! browser {\n        ($browser: ident) => {{\n          if let Some(v) = parse_version(distrib.version()) {\n            if browsers.$browser.is_none() || v < browsers.$browser.unwrap() {\n              browsers.$browser = Some(v);\n              has_any = true;\n            }\n          }\n        }};\n      }\n\n      match distrib.name() {\n        \"android\" => browser!(android),\n        \"chrome\" | \"and_chr\" => browser!(chrome),\n        \"edge\" => browser!(edge),\n        \"firefox\" | \"and_ff\" => browser!(firefox),\n        \"ie\" => browser!(ie),\n        \"ios_saf\" => browser!(ios_saf),\n        \"opera\" | \"op_mob\" => browser!(opera),\n        \"safari\" => browser!(safari),\n        \"samsung\" => browser!(samsung),\n        _ => {}\n      }\n    }\n\n    if !has_any {\n      return Ok(None);\n    }\n\n    Ok(Some(browsers))\n  }\n}\n\n#[cfg(feature = \"browserslist\")]\nfn parse_version(version: &str) -> Option<u32> {\n  let version = version.split('-').next();\n  if version.is_none() {\n    return None;\n  }\n\n  let mut version = version.unwrap().split('.');\n  let major = version.next().and_then(|v| v.parse::<u32>().ok());\n  if let Some(major) = major {\n    let minor = version.next().and_then(|v| v.parse::<u32>().ok()).unwrap_or(0);\n    let patch = version.next().and_then(|v| v.parse::<u32>().ok()).unwrap_or(0);\n    let v: u32 = (major & 0xff) << 16 | (minor & 0xff) << 8 | (patch & 0xff);\n    return Some(v);\n  }\n\n  None\n}\n\nbitflags! {\n  /// Features to explicitly enable or disable.\n  #[derive(Debug, Default, Clone, Copy, Hash, Eq, PartialEq)]\n  #[cfg_attr(feature = \"serde\", derive(Serialize, Deserialize), serde(transparent))]\n  pub struct Features: u32 {\n    const Nesting = 1 << 0;\n    const NotSelectorList = 1 << 1;\n    const DirSelector = 1 << 2;\n    const LangSelectorList = 1 << 3;\n    const IsSelector = 1 << 4;\n    const TextDecorationThicknessPercent = 1 << 5;\n    const MediaIntervalSyntax = 1 << 6;\n    const MediaRangeSyntax = 1 << 7;\n    const CustomMediaQueries = 1 << 8;\n    const ClampFunction = 1 << 9;\n    const ColorFunction = 1 << 10;\n    const OklabColors = 1 << 11;\n    const LabColors = 1 << 12;\n    const P3Colors = 1 << 13;\n    const HexAlphaColors = 1 << 14;\n    const SpaceSeparatedColorNotation = 1 << 15;\n    const FontFamilySystemUi = 1 << 16;\n    const DoublePositionGradients = 1 << 17;\n    const VendorPrefixes = 1 << 18;\n    const LogicalProperties = 1 << 19;\n    const LightDark = 1 << 20;\n    const Selectors = Self::Nesting.bits() | Self::NotSelectorList.bits() | Self::DirSelector.bits() | Self::LangSelectorList.bits() | Self::IsSelector.bits();\n    const MediaQueries = Self::MediaIntervalSyntax.bits() | Self::MediaRangeSyntax.bits() | Self::CustomMediaQueries.bits();\n    const Colors = Self::ColorFunction.bits() | Self::OklabColors.bits() | Self::LabColors.bits() | Self::P3Colors.bits() | Self::HexAlphaColors.bits() | Self::SpaceSeparatedColorNotation.bits() | Self::LightDark.bits();\n  }\n}\n\npub(crate) trait FeaturesIterator: Sized + Iterator {\n  fn try_union_all<T>(&mut self) -> Option<Features>\n  where\n    Self: Iterator<Item = Option<T>>,\n    T: Borrow<Features>,\n  {\n    self.try_fold(Features::empty(), |a, b| b.map(|b| a | *b.borrow()))\n  }\n}\n\nimpl<I> FeaturesIterator for I where I: Iterator {}\n\n/// Target browsers and features to compile.\n#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]\n#[cfg_attr(feature = \"serde\", derive(Serialize, Deserialize))]\npub struct Targets {\n  /// Browser targets to compile the CSS for.\n  pub browsers: Option<Browsers>,\n  /// Features that should always be compiled, even when supported by targets.\n  pub include: Features,\n  /// Features that should never be compiled, even when unsupported by targets.\n  pub exclude: Features,\n}\n\nimpl From<Browsers> for Targets {\n  fn from(browsers: Browsers) -> Self {\n    Self {\n      browsers: Some(browsers),\n      ..Default::default()\n    }\n  }\n}\n\nimpl From<Option<Browsers>> for Targets {\n  fn from(browsers: Option<Browsers>) -> Self {\n    Self {\n      browsers,\n      ..Default::default()\n    }\n  }\n}\n\nimpl Targets {\n  pub(crate) fn is_compatible(&self, feature: crate::compat::Feature) -> bool {\n    self.browsers.map(|targets| feature.is_compatible(targets)).unwrap_or(true)\n  }\n\n  pub(crate) fn should_compile(&self, feature: crate::compat::Feature, flag: Features) -> bool {\n    self.include.contains(flag) || (!self.exclude.contains(flag) && !self.is_compatible(feature))\n  }\n\n  pub(crate) fn should_compile_logical(&self, feature: crate::compat::Feature) -> bool {\n    self.should_compile(feature, Features::LogicalProperties)\n  }\n\n  pub(crate) fn should_compile_selectors(&self) -> bool {\n    self.include.intersects(Features::Selectors)\n      || (!self.exclude.intersects(Features::Selectors) && self.browsers.is_some())\n  }\n\n  pub(crate) fn prefixes(&self, prefix: VendorPrefix, feature: crate::prefixes::Feature) -> VendorPrefix {\n    if prefix.contains(VendorPrefix::None) && !self.exclude.contains(Features::VendorPrefixes) {\n      if self.include.contains(Features::VendorPrefixes) {\n        VendorPrefix::all()\n      } else {\n        self.browsers.map(|browsers| feature.prefixes_for(browsers)).unwrap_or(prefix)\n      }\n    } else {\n      prefix\n    }\n  }\n}\n\n#[derive(Debug)]\npub(crate) struct TargetsWithSupportsScope {\n  stack: Vec<Features>,\n  pub(crate) current: Targets,\n}\n\nimpl TargetsWithSupportsScope {\n  pub fn new(targets: Targets) -> Self {\n    Self {\n      stack: Vec::new(),\n      current: targets,\n    }\n  }\n\n  /// Returns true if inserted\n  pub fn enter_supports(&mut self, features: Features) -> bool {\n    if features.is_empty() || self.current.exclude.contains(features) {\n      // Already excluding all features\n      return false;\n    }\n\n    let newly_excluded = features - self.current.exclude;\n    self.stack.push(newly_excluded);\n    self.current.exclude.insert(newly_excluded);\n    true\n  }\n\n  /// Should be only called if inserted\n  pub fn exit_supports(&mut self) {\n    if let Some(last) = self.stack.pop() {\n      self.current.exclude.remove(last);\n    }\n  }\n}\n\n#[test]\nfn supports_scope_correctly() {\n  let mut targets = TargetsWithSupportsScope::new(Targets::default());\n  assert!(!targets.current.exclude.contains(Features::OklabColors));\n  assert!(!targets.current.exclude.contains(Features::LabColors));\n  assert!(!targets.current.exclude.contains(Features::P3Colors));\n\n  targets.enter_supports(Features::OklabColors | Features::LabColors);\n  assert!(targets.current.exclude.contains(Features::OklabColors));\n  assert!(targets.current.exclude.contains(Features::LabColors));\n\n  targets.enter_supports(Features::P3Colors | Features::LabColors);\n  assert!(targets.current.exclude.contains(Features::OklabColors));\n  assert!(targets.current.exclude.contains(Features::LabColors));\n  assert!(targets.current.exclude.contains(Features::P3Colors));\n\n  targets.exit_supports();\n  assert!(targets.current.exclude.contains(Features::OklabColors));\n  assert!(targets.current.exclude.contains(Features::LabColors));\n  assert!(!targets.current.exclude.contains(Features::P3Colors));\n\n  targets.exit_supports();\n  assert!(!targets.current.exclude.contains(Features::OklabColors));\n  assert!(!targets.current.exclude.contains(Features::LabColors));\n}\n\nmacro_rules! should_compile {\n  ($targets: expr, $feature: ident) => {\n    $targets.should_compile(\n      crate::compat::Feature::$feature,\n      crate::targets::Features::$feature,\n    )\n  };\n}\n\npub(crate) use should_compile;\n"
  },
  {
    "path": "src/test_helpers.rs",
    "content": "use crate::error::{Error, ErrorLocation};\n\npub(crate) fn format_source_location_context(source: &str, loc: &ErrorLocation) -> String {\n  let lines: Vec<&str> = source.lines().collect();\n  let line_idx = loc.line as usize;\n  let display_line = line_idx + 1;\n  let display_column = if loc.column == 0 { 1 } else { loc.column as usize };\n\n  let mut output = format!(\"css location: line {display_line}, column {display_column}\");\n\n  if lines.is_empty() {\n    output.push_str(\"\\n  (source is empty)\");\n    return output;\n  }\n\n  if line_idx >= lines.len() {\n    output.push_str(&format!(\"\\n  (line is out of range; source has {} line(s))\", lines.len()));\n    return output;\n  }\n\n  let start = line_idx.saturating_sub(1);\n  let end = usize::min(line_idx + 1, lines.len() - 1);\n  output.push_str(\"\\ncontext:\");\n\n  for i in start..=end {\n    let line = lines[i];\n    output.push_str(&format!(\"\\n{:>6} | {}\", i + 1, line));\n    if i == line_idx {\n      let caret_pos = display_column.saturating_sub(1);\n      let line_char_count = line.chars().count();\n      let mut marker = String::with_capacity(caret_pos.max(line_char_count));\n      for ch in line.chars().take(caret_pos) {\n        marker.push(if ch == '\\t' { '\\t' } else { ' ' });\n      }\n      if caret_pos > line_char_count {\n        marker.push_str(&\" \".repeat(caret_pos - line_char_count));\n      }\n      output.push_str(&format!(\"\\n       | {}^\", marker));\n    }\n  }\n\n  output\n}\n\n#[track_caller]\npub(crate) fn panic_with_test_error<T: std::fmt::Debug + std::fmt::Display>(\n  helper: &str,\n  stage: &str,\n  source: &str,\n  error: Error<T>,\n) -> ! {\n  let caller = std::panic::Location::caller();\n  let location = match error.loc.as_ref() {\n    Some(loc) => format_source_location_context(source, loc),\n    None => \"css location: <none>\".to_string(),\n  };\n  panic!(\n    \"{helper}: {stage} failed\\ncaller: {}:{}\\nerror: {error}\\nerror(debug): {error:?}\\n{location}\\nsource:\\n{source}\",\n    caller.file(),\n    caller.line(),\n  );\n}\n"
  },
  {
    "path": "src/traits.rs",
    "content": "//! Traits for parsing and serializing CSS.\n\nuse crate::context::PropertyHandlerContext;\nuse crate::declaration::{DeclarationBlock, DeclarationList};\nuse crate::error::{ParserError, PrinterError};\nuse crate::printer::Printer;\nuse crate::properties::{Property, PropertyId};\nuse crate::stylesheet::{ParserOptions, PrinterOptions};\nuse crate::targets::{Browsers, Targets};\nuse crate::vendor_prefix::VendorPrefix;\nuse cssparser::*;\n\n#[cfg(feature = \"into_owned\")]\npub use static_self::IntoOwned;\n\n/// Trait for things that can be parsed from CSS syntax.\npub trait Parse<'i>: Sized {\n  /// Parse a value of this type using an existing parser.\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>>;\n\n  /// Parse a value from a string.\n  ///\n  /// (This is a convenience wrapper for `parse` and probably should not be overridden.)\n  fn parse_string(input: &'i str) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let mut input = ParserInput::new(input);\n    let mut parser = Parser::new(&mut input);\n    let result = Self::parse(&mut parser)?;\n    parser.expect_exhausted()?;\n    Ok(result)\n  }\n}\n\npub(crate) use lightningcss_derive::Parse;\n\nimpl<'i, T: Parse<'i>> Parse<'i> for Option<T> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    Ok(input.try_parse(T::parse).ok())\n  }\n}\n\nimpl<'i, T: Parse<'i>> Parse<'i> for Box<T> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    Ok(Box::new(T::parse(input)?))\n  }\n}\n\n/// Trait for things that can be parsed from CSS syntax and require ParserOptions.\npub trait ParseWithOptions<'i>: Sized {\n  /// Parse a value of this type with the given options.\n  fn parse_with_options<'t>(\n    input: &mut Parser<'i, 't>,\n    options: &ParserOptions<'_, 'i>,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>>;\n\n  /// Parse a value from a string with the given options.\n  fn parse_string_with_options(\n    input: &'i str,\n    options: ParserOptions<'_, 'i>,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let mut input = ParserInput::new(input);\n    let mut parser = Parser::new(&mut input);\n    Self::parse_with_options(&mut parser, &options)\n  }\n}\n\nimpl<'i, T: Parse<'i>> ParseWithOptions<'i> for T {\n  #[inline]\n  fn parse_with_options<'t>(\n    input: &mut Parser<'i, 't>,\n    _options: &ParserOptions,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    T::parse(input)\n  }\n}\n\n/// Trait for things the can serialize themselves in CSS syntax.\npub trait ToCss {\n  /// Serialize `self` in CSS syntax, writing to `dest`.\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write;\n\n  /// Serialize `self` in CSS syntax and return a string.\n  ///\n  /// (This is a convenience wrapper for `to_css` and probably should not be overridden.)\n  #[inline]\n  fn to_css_string(&self, options: PrinterOptions) -> Result<String, PrinterError> {\n    let mut s = String::new();\n    let mut printer = Printer::new(&mut s, options);\n    self.to_css(&mut printer)?;\n    Ok(s)\n  }\n}\n\npub(crate) use lightningcss_derive::ToCss;\n\nimpl<'a, T> ToCss for &'a T\nwhere\n  T: ToCss + ?Sized,\n{\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    (*self).to_css(dest)\n  }\n}\n\nimpl<T: ToCss> ToCss for Box<T> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    (**self).to_css(dest)\n  }\n}\n\nimpl<T: ToCss> ToCss for Option<T> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    if let Some(v) = self {\n      v.to_css(dest)?;\n    }\n    Ok(())\n  }\n}\n\npub(crate) trait PropertyHandler<'i>: Sized {\n  fn handle_property(\n    &mut self,\n    property: &Property<'i>,\n    dest: &mut DeclarationList<'i>,\n    context: &mut PropertyHandlerContext<'i, '_>,\n  ) -> bool;\n  fn finalize(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>);\n}\n\npub(crate) mod private {\n  pub trait TryAdd<T> {\n    fn try_add(&self, other: &T) -> Option<T>;\n  }\n\n  pub trait AddInternal {\n    fn add(self, other: Self) -> Self;\n  }\n}\n\npub(crate) trait FromStandard<T>: Sized {\n  fn from_standard(val: &T) -> Option<Self>;\n}\n\npub(crate) trait FallbackValues: Sized {\n  fn get_fallbacks(&mut self, targets: Targets) -> Vec<Self>;\n}\n\n/// Trait for shorthand properties.\npub(crate) trait Shorthand<'i>: Sized {\n  /// Returns a shorthand from the longhand properties defined in the given declaration block.\n  fn from_longhands(decls: &DeclarationBlock<'i>, vendor_prefix: VendorPrefix) -> Option<(Self, bool)>;\n\n  /// Returns a list of longhand property ids for this shorthand.\n  fn longhands(vendor_prefix: VendorPrefix) -> Vec<PropertyId<'static>>;\n\n  /// Returns a longhand property for this shorthand.\n  fn longhand(&self, property_id: &PropertyId) -> Option<Property<'i>>;\n\n  /// Updates this shorthand from a longhand property.\n  fn set_longhand(&mut self, property: &Property<'i>) -> Result<(), ()>;\n}\n\n/// A trait for values that support binary operations.\npub trait Op {\n  /// Returns the result of the operation in the same type.\n  fn op<F: FnOnce(f32, f32) -> f32>(&self, rhs: &Self, op: F) -> Self;\n  /// Returns the result of the operation in a different type.\n  fn op_to<T, F: FnOnce(f32, f32) -> T>(&self, rhs: &Self, op: F) -> T;\n}\n\nmacro_rules! impl_op {\n  ($t: ty, $trait: ident $(:: $x: ident)*, $op: ident) => {\n    impl $trait$(::$x)* for $t {\n      type Output = $t;\n\n      fn $op(self, rhs: Self) -> Self::Output {\n        self.op(&rhs, $trait$(::$x)*::$op)\n      }\n    }\n  };\n}\n\npub(crate) use impl_op;\nuse smallvec::SmallVec;\n\n/// A trait for values that potentially support a binary operation (e.g. if they have the same unit).\npub trait TryOp: Sized {\n  /// Returns the result of the operation in the same type, if possible.\n  fn try_op<F: FnOnce(f32, f32) -> f32>(&self, rhs: &Self, op: F) -> Option<Self>;\n  /// Returns the result of the operation in a different type, if possible.\n  fn try_op_to<T, F: FnOnce(f32, f32) -> T>(&self, rhs: &Self, op: F) -> Option<T>;\n}\n\nimpl<T: Op> TryOp for T {\n  fn try_op<F: FnOnce(f32, f32) -> f32>(&self, rhs: &Self, op: F) -> Option<Self> {\n    Some(self.op(rhs, op))\n  }\n\n  fn try_op_to<U, F: FnOnce(f32, f32) -> U>(&self, rhs: &Self, op: F) -> Option<U> {\n    Some(self.op_to(rhs, op))\n  }\n}\n\n/// A trait for values that can be mapped by applying a function.\npub trait Map {\n  /// Returns the result of the operation.\n  fn map<F: FnOnce(f32) -> f32>(&self, op: F) -> Self;\n}\n\n/// A trait for values that can potentially be mapped.\npub trait TryMap: Sized {\n  /// Returns the result of the operation, if possible.\n  fn try_map<F: FnOnce(f32) -> f32>(&self, op: F) -> Option<Self>;\n}\n\nimpl<T: Map> TryMap for T {\n  fn try_map<F: FnOnce(f32) -> f32>(&self, op: F) -> Option<Self> {\n    Some(self.map(op))\n  }\n}\n\n/// A trait for values that can return a sign.\npub trait Sign {\n  /// Returns the sign of the value.\n  fn sign(&self) -> f32;\n\n  /// Returns whether the value is positive.\n  fn is_sign_positive(&self) -> bool {\n    f32::is_sign_positive(self.sign())\n  }\n\n  /// Returns whether the value is negative.\n  fn is_sign_negative(&self) -> bool {\n    f32::is_sign_negative(self.sign())\n  }\n}\n\n/// A trait for values that can potentially return a sign.\npub trait TrySign {\n  /// Returns the sign of the value, if possible.\n  fn try_sign(&self) -> Option<f32>;\n\n  /// Returns whether the value is positive. If not possible, returns false.\n  fn is_sign_positive(&self) -> bool {\n    self.try_sign().map_or(false, |s| f32::is_sign_positive(s))\n  }\n\n  /// Returns whether the value is negative. If not possible, returns false.\n  fn is_sign_negative(&self) -> bool {\n    self.try_sign().map_or(false, |s| f32::is_sign_negative(s))\n  }\n}\n\nimpl<T: Sign> TrySign for T {\n  fn try_sign(&self) -> Option<f32> {\n    Some(self.sign())\n  }\n}\n\n/// A trait for values that can be zero.\npub trait Zero {\n  /// Returns the zero value.\n  fn zero() -> Self;\n\n  /// Returns whether the value is zero.\n  fn is_zero(&self) -> bool;\n}\n\n/// A trait for values that can check if they are compatible with browser targets.\npub trait IsCompatible {\n  /// Returns whether the value is compatible with all of the given browser targets.\n  fn is_compatible(&self, browsers: Browsers) -> bool;\n}\n\nimpl<T: IsCompatible> IsCompatible for SmallVec<[T; 1]> {\n  fn is_compatible(&self, browsers: Browsers) -> bool {\n    self.iter().all(|v| v.is_compatible(browsers))\n  }\n}\n\nimpl<T: IsCompatible> IsCompatible for Vec<T> {\n  fn is_compatible(&self, browsers: Browsers) -> bool {\n    self.iter().all(|v| v.is_compatible(browsers))\n  }\n}\n\n/// A trait to provide parsing of custom at-rules.\n///\n/// For example, there could be different implementations for top-level at-rules\n/// (`@media`, `@font-face`, …)\n/// and for page-margin rules inside `@page`.\n///\n/// Default implementations that reject all at-rules are provided,\n/// so that `impl AtRuleParser<(), ()> for ... {}` can be used\n/// for using `DeclarationListParser` to parse a declarations list with only qualified rules.\n///\n/// Note: this trait is copied from cssparser and modified to provide parser options.\npub trait AtRuleParser<'i>: Sized {\n  /// The intermediate representation of prelude of an at-rule.\n  type Prelude;\n\n  /// The finished representation of an at-rule.\n  type AtRule;\n\n  /// The error type that is included in the ParseError value that can be returned.\n  type Error: 'i;\n\n  /// Parse the prelude of an at-rule with the given `name`.\n  ///\n  /// Return the representation of the prelude and the type of at-rule,\n  /// or `Err(())` to ignore the entire at-rule as invalid.\n  ///\n  /// The prelude is the part after the at-keyword\n  /// and before the `;` semicolon or `{ /* ... */ }` block.\n  ///\n  /// At-rule name matching should be case-insensitive in the ASCII range.\n  /// This can be done with `std::ascii::Ascii::eq_ignore_ascii_case`,\n  /// or with the `match_ignore_ascii_case!` macro.\n  ///\n  /// The given `input` is a \"delimited\" parser\n  /// that ends wherever the prelude should end.\n  /// (Before the next semicolon, the next `{`, or the end of the current block.)\n  fn parse_prelude<'t>(\n    &mut self,\n    name: CowRcStr<'i>,\n    input: &mut Parser<'i, 't>,\n    options: &ParserOptions<'_, 'i>,\n  ) -> Result<Self::Prelude, ParseError<'i, Self::Error>> {\n    let _ = name;\n    let _ = input;\n    let _ = options;\n    Err(input.new_error(BasicParseErrorKind::AtRuleInvalid(name)))\n  }\n\n  /// End an at-rule which doesn't have block. Return the finished\n  /// representation of the at-rule.\n  ///\n  /// The location passed in is source location of the start of the prelude.\n  /// `is_nested` indicates whether the rule is nested inside a style rule.\n  ///\n  /// This is only called when either the `;` semicolon indeed follows the prelude,\n  /// or parser is at the end of the input.\n  fn rule_without_block(\n    &mut self,\n    prelude: Self::Prelude,\n    start: &ParserState,\n    options: &ParserOptions<'_, 'i>,\n    is_nested: bool,\n  ) -> Result<Self::AtRule, ()> {\n    let _ = prelude;\n    let _ = start;\n    let _ = options;\n    let _ = is_nested;\n    Err(())\n  }\n\n  /// Parse the content of a `{ /* ... */ }` block for the body of the at-rule.\n  ///\n  /// The location passed in is source location of the start of the prelude.\n  /// `is_nested` indicates whether the rule is nested inside a style rule.\n  ///\n  /// Return the finished representation of the at-rule\n  /// as returned by `RuleListParser::next` or `DeclarationListParser::next`,\n  /// or `Err(())` to ignore the entire at-rule as invalid.\n  ///\n  /// This is only called when a block was found following the prelude.\n  fn parse_block<'t>(\n    &mut self,\n    prelude: Self::Prelude,\n    start: &ParserState,\n    input: &mut Parser<'i, 't>,\n    options: &ParserOptions<'_, 'i>,\n    is_nested: bool,\n  ) -> Result<Self::AtRule, ParseError<'i, Self::Error>> {\n    let _ = prelude;\n    let _ = start;\n    let _ = input;\n    let _ = options;\n    let _ = is_nested;\n    Err(input.new_error(BasicParseErrorKind::AtRuleBodyInvalid))\n  }\n}\n"
  },
  {
    "path": "src/values/alpha.rs",
    "content": "//! CSS alpha values, used to represent opacity.\n\nuse super::percentage::NumberOrPercentage;\nuse crate::error::{ParserError, PrinterError};\nuse crate::printer::Printer;\nuse crate::traits::{Parse, ToCss};\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse cssparser::*;\n\n/// A CSS [`<alpha-value>`](https://www.w3.org/TR/css-color-4/#typedef-alpha-value),\n/// used to represent opacity.\n///\n/// Parses either a `<number>` or `<percentage>`, but is always stored and serialized as a number.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize), serde(transparent))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub struct AlphaValue(pub f32);\n\nimpl<'i> Parse<'i> for AlphaValue {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    match NumberOrPercentage::parse(input)? {\n      NumberOrPercentage::Percentage(percent) => Ok(AlphaValue(percent.0)),\n      NumberOrPercentage::Number(number) => Ok(AlphaValue(number)),\n    }\n  }\n}\n\nimpl ToCss for AlphaValue {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    self.0.to_css(dest)\n  }\n}\n"
  },
  {
    "path": "src/values/angle.rs",
    "content": "//! CSS angle values.\n\nuse super::calc::Calc;\nuse super::length::serialize_dimension;\nuse super::number::CSSNumber;\nuse super::percentage::DimensionPercentage;\nuse crate::error::{ParserError, PrinterError};\nuse crate::printer::Printer;\nuse crate::traits::{\n  impl_op,\n  private::{AddInternal, TryAdd},\n  Map, Op, Parse, Sign, ToCss, Zero,\n};\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse cssparser::*;\nuse std::f32::consts::PI;\n\n/// A CSS [`<angle>`](https://www.w3.org/TR/css-values-4/#angles) value.\n///\n/// Angles may be explicit or computed by `calc()`, but are always stored and serialized\n/// as their computed value.\n#[derive(Debug, Clone)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"visitor\", visit(visit_angle, ANGLES))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum Angle {\n  /// An angle in degrees. There are 360 degrees in a full circle.\n  Deg(CSSNumber),\n  /// An angle in radians. There are 2π radians in a full circle.\n  Rad(CSSNumber),\n  /// An angle in gradians. There are 400 gradians in a full circle.\n  Grad(CSSNumber),\n  /// An angle in turns. There is 1 turn in a full circle.\n  Turn(CSSNumber),\n}\n\nimpl<'i> Parse<'i> for Angle {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    Self::parse_internal(input, false)\n  }\n}\n\nimpl Angle {\n  /// Parses an angle, allowing unitless zero values.\n  pub fn parse_with_unitless_zero<'i, 't>(\n    input: &mut Parser<'i, 't>,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    Self::parse_internal(input, true)\n  }\n\n  fn parse_internal<'i, 't>(\n    input: &mut Parser<'i, 't>,\n    allow_unitless_zero: bool,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    match input.try_parse(Calc::parse) {\n      Ok(Calc::Value(v)) => return Ok(*v),\n      // Angles are always compatible, so they will always compute to a value.\n      Ok(_) => return Err(input.new_custom_error(ParserError::InvalidValue)),\n      _ => {}\n    }\n\n    let location = input.current_source_location();\n    let token = input.next()?;\n    match *token {\n      Token::Dimension { value, ref unit, .. } => {\n        match_ignore_ascii_case! { unit,\n          \"deg\" => Ok(Angle::Deg(value)),\n          \"grad\" => Ok(Angle::Grad(value)),\n          \"turn\" => Ok(Angle::Turn(value)),\n          \"rad\" => Ok(Angle::Rad(value)),\n          _ => return Err(location.new_unexpected_token_error(token.clone())),\n        }\n      }\n      Token::Number { value, .. } if value == 0.0 && allow_unitless_zero => Ok(Angle::zero()),\n      ref token => return Err(location.new_unexpected_token_error(token.clone())),\n    }\n  }\n}\n\nimpl<'i> TryFrom<&Token<'i>> for Angle {\n  type Error = ();\n\n  fn try_from(token: &Token) -> Result<Self, Self::Error> {\n    match token {\n      Token::Dimension { value, ref unit, .. } => match_ignore_ascii_case! { unit,\n        \"deg\" => Ok(Angle::Deg(*value)),\n        \"grad\" => Ok(Angle::Grad(*value)),\n        \"turn\" => Ok(Angle::Turn(*value)),\n        \"rad\" => Ok(Angle::Rad(*value)),\n        _ => Err(()),\n      },\n      _ => Err(()),\n    }\n  }\n}\n\nimpl ToCss for Angle {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    let (value, unit) = match self {\n      Angle::Deg(val) => (*val, \"deg\"),\n      Angle::Grad(val) => (*val, \"grad\"),\n      Angle::Rad(val) => {\n        let deg = self.to_degrees();\n        // We print 5 digits of precision by default.\n        // Switch to degrees if there are an even number of them.\n        if (deg * 100000.0).round().fract() == 0.0 {\n          (deg, \"deg\")\n        } else {\n          (*val, \"rad\")\n        }\n      }\n      Angle::Turn(val) => (*val, \"turn\"),\n    };\n    // Canonicalize negative zero so serialization is stable (`0deg` instead of `-0deg`).\n    let value = if value == 0.0 { 0.0 } else { value };\n\n    serialize_dimension(value, unit, dest)\n  }\n}\n\nimpl Angle {\n  /// Prints the angle, allowing unitless zero values.\n  pub fn to_css_with_unitless_zero<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    if self.is_zero() {\n      (0.0).to_css(dest)\n    } else {\n      self.to_css(dest)\n    }\n  }\n}\n\nimpl Angle {\n  /// Returns the angle in radians.\n  pub fn to_radians(&self) -> CSSNumber {\n    const RAD_PER_DEG: f32 = PI / 180.0;\n    match self {\n      Angle::Deg(deg) => deg * RAD_PER_DEG,\n      Angle::Rad(rad) => *rad,\n      Angle::Grad(grad) => grad * 180.0 / 200.0 * RAD_PER_DEG,\n      Angle::Turn(turn) => turn * 360.0 * RAD_PER_DEG,\n    }\n  }\n\n  /// Returns the angle in degrees.\n  pub fn to_degrees(&self) -> CSSNumber {\n    const DEG_PER_RAD: f32 = 180.0 / PI;\n    match self {\n      Angle::Deg(deg) => *deg,\n      Angle::Rad(rad) => rad * DEG_PER_RAD,\n      Angle::Grad(grad) => grad * 180.0 / 200.0,\n      Angle::Turn(turn) => turn * 360.0,\n    }\n  }\n}\n\nimpl Zero for Angle {\n  fn is_zero(&self) -> bool {\n    use Angle::*;\n    match self {\n      Deg(v) | Rad(v) | Grad(v) | Turn(v) => *v == 0.0,\n    }\n  }\n\n  fn zero() -> Self {\n    Angle::Deg(0.0)\n  }\n}\n\nimpl Into<Calc<Angle>> for Angle {\n  fn into(self) -> Calc<Angle> {\n    Calc::Value(Box::new(self))\n  }\n}\n\nimpl TryFrom<Calc<Angle>> for Angle {\n  type Error = ();\n\n  fn try_from(calc: Calc<Angle>) -> Result<Angle, ()> {\n    match calc {\n      Calc::Value(v) => Ok(*v),\n      _ => Err(()),\n    }\n  }\n}\n\nimpl std::ops::Mul<CSSNumber> for Angle {\n  type Output = Self;\n\n  fn mul(self, other: CSSNumber) -> Angle {\n    match self {\n      Angle::Deg(v) => Angle::Deg(v * other),\n      Angle::Rad(v) => Angle::Rad(v * other),\n      Angle::Grad(v) => Angle::Grad(v * other),\n      Angle::Turn(v) => Angle::Turn(v * other),\n    }\n  }\n}\n\nimpl AddInternal for Angle {\n  fn add(self, other: Self) -> Self {\n    self + other\n  }\n}\n\nimpl TryAdd<Angle> for Angle {\n  fn try_add(&self, other: &Angle) -> Option<Angle> {\n    Some(Angle::Deg(self.to_degrees() + other.to_degrees()))\n  }\n}\n\nimpl std::cmp::PartialEq<Angle> for Angle {\n  fn eq(&self, other: &Angle) -> bool {\n    self.to_degrees() == other.to_degrees()\n  }\n}\n\nimpl std::cmp::PartialOrd<Angle> for Angle {\n  fn partial_cmp(&self, other: &Angle) -> Option<std::cmp::Ordering> {\n    self.to_degrees().partial_cmp(&other.to_degrees())\n  }\n}\n\nimpl Op for Angle {\n  fn op<F: FnOnce(f32, f32) -> f32>(&self, other: &Self, op: F) -> Self {\n    match (self, other) {\n      (Angle::Deg(a), Angle::Deg(b)) => Angle::Deg(op(*a, *b)),\n      (Angle::Rad(a), Angle::Rad(b)) => Angle::Rad(op(*a, *b)),\n      (Angle::Grad(a), Angle::Grad(b)) => Angle::Grad(op(*a, *b)),\n      (Angle::Turn(a), Angle::Turn(b)) => Angle::Turn(op(*a, *b)),\n      (a, b) => Angle::Deg(op(a.to_degrees(), b.to_degrees())),\n    }\n  }\n\n  fn op_to<T, F: FnOnce(f32, f32) -> T>(&self, other: &Self, op: F) -> T {\n    match (self, other) {\n      (Angle::Deg(a), Angle::Deg(b)) => op(*a, *b),\n      (Angle::Rad(a), Angle::Rad(b)) => op(*a, *b),\n      (Angle::Grad(a), Angle::Grad(b)) => op(*a, *b),\n      (Angle::Turn(a), Angle::Turn(b)) => op(*a, *b),\n      (a, b) => op(a.to_degrees(), b.to_degrees()),\n    }\n  }\n}\n\nimpl Map for Angle {\n  fn map<F: FnOnce(f32) -> f32>(&self, op: F) -> Self {\n    match self {\n      Angle::Deg(deg) => Angle::Deg(op(*deg)),\n      Angle::Rad(rad) => Angle::Rad(op(*rad)),\n      Angle::Grad(grad) => Angle::Grad(op(*grad)),\n      Angle::Turn(turn) => Angle::Turn(op(*turn)),\n    }\n  }\n}\n\nimpl Sign for Angle {\n  fn sign(&self) -> f32 {\n    match self {\n      Angle::Deg(v) | Angle::Rad(v) | Angle::Grad(v) | Angle::Turn(v) => v.sign(),\n    }\n  }\n}\n\nimpl_op!(Angle, std::ops::Rem, rem);\nimpl_op!(Angle, std::ops::Add, add);\n\n/// A CSS [`<angle-percentage>`](https://www.w3.org/TR/css-values-4/#typedef-angle-percentage) value.\n/// May be specified as either an angle or a percentage that resolves to an angle.\npub type AnglePercentage = DimensionPercentage<Angle>;\n\nmacro_rules! impl_try_from_angle {\n  ($t: ty) => {\n    impl TryFrom<crate::values::angle::Angle> for $t {\n      type Error = ();\n      fn try_from(_: crate::values::angle::Angle) -> Result<Self, Self::Error> {\n        Err(())\n      }\n    }\n\n    impl TryInto<crate::values::angle::Angle> for $t {\n      type Error = ();\n      fn try_into(self) -> Result<crate::values::angle::Angle, Self::Error> {\n        Err(())\n      }\n    }\n  };\n}\n\npub(crate) use impl_try_from_angle;\n"
  },
  {
    "path": "src/values/calc.rs",
    "content": "//! Mathematical calculation functions and expressions.\n\nuse crate::compat::Feature;\nuse crate::error::{ParserError, PrinterError};\nuse crate::macros::enum_property;\nuse crate::printer::Printer;\nuse crate::targets::{should_compile, Browsers};\nuse crate::traits::private::AddInternal;\nuse crate::traits::{IsCompatible, Parse, Sign, ToCss, TryMap, TryOp, TrySign};\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse cssparser::*;\n\nuse super::angle::Angle;\nuse super::length::Length;\nuse super::number::CSSNumber;\nuse super::percentage::Percentage;\nuse super::time::Time;\n\n/// A CSS [math function](https://www.w3.org/TR/css-values-4/#math-function).\n///\n/// Math functions may be used in most properties and values that accept numeric\n/// values, including lengths, percentages, angles, times, etc.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum MathFunction<V> {\n  /// The [`calc()`](https://www.w3.org/TR/css-values-4/#calc-func) function.\n  Calc(Calc<V>),\n  /// The [`min()`](https://www.w3.org/TR/css-values-4/#funcdef-min) function.\n  Min(Vec<Calc<V>>),\n  /// The [`max()`](https://www.w3.org/TR/css-values-4/#funcdef-max) function.\n  Max(Vec<Calc<V>>),\n  /// The [`clamp()`](https://www.w3.org/TR/css-values-4/#funcdef-clamp) function.\n  Clamp(Calc<V>, Calc<V>, Calc<V>),\n  /// The [`round()`](https://www.w3.org/TR/css-values-4/#funcdef-round) function.\n  Round(RoundingStrategy, Calc<V>, Calc<V>),\n  /// The [`rem()`](https://www.w3.org/TR/css-values-4/#funcdef-rem) function.\n  Rem(Calc<V>, Calc<V>),\n  /// The [`mod()`](https://www.w3.org/TR/css-values-4/#funcdef-mod) function.\n  Mod(Calc<V>, Calc<V>),\n  /// The [`abs()`](https://drafts.csswg.org/css-values-4/#funcdef-abs) function.\n  Abs(Calc<V>),\n  /// The [`sign()`](https://drafts.csswg.org/css-values-4/#funcdef-sign) function.\n  Sign(Calc<V>),\n  /// The [`hypot()`](https://drafts.csswg.org/css-values-4/#funcdef-hypot) function.\n  Hypot(Vec<Calc<V>>),\n}\n\nimpl<V: IsCompatible> IsCompatible for MathFunction<V> {\n  fn is_compatible(&self, browsers: Browsers) -> bool {\n    match self {\n      MathFunction::Calc(v) => Feature::CalcFunction.is_compatible(browsers) && v.is_compatible(browsers),\n      MathFunction::Min(v) => {\n        Feature::MinFunction.is_compatible(browsers) && v.iter().all(|v| v.is_compatible(browsers))\n      }\n      MathFunction::Max(v) => {\n        Feature::MaxFunction.is_compatible(browsers) && v.iter().all(|v| v.is_compatible(browsers))\n      }\n      MathFunction::Clamp(a, b, c) => {\n        Feature::ClampFunction.is_compatible(browsers)\n          && a.is_compatible(browsers)\n          && b.is_compatible(browsers)\n          && c.is_compatible(browsers)\n      }\n      MathFunction::Round(_, a, b) => {\n        Feature::RoundFunction.is_compatible(browsers) && a.is_compatible(browsers) && b.is_compatible(browsers)\n      }\n      MathFunction::Rem(a, b) => {\n        Feature::RemFunction.is_compatible(browsers) && a.is_compatible(browsers) && b.is_compatible(browsers)\n      }\n      MathFunction::Mod(a, b) => {\n        Feature::ModFunction.is_compatible(browsers) && a.is_compatible(browsers) && b.is_compatible(browsers)\n      }\n      MathFunction::Abs(v) => Feature::AbsFunction.is_compatible(browsers) && v.is_compatible(browsers),\n      MathFunction::Sign(v) => Feature::SignFunction.is_compatible(browsers) && v.is_compatible(browsers),\n      MathFunction::Hypot(v) => {\n        Feature::HypotFunction.is_compatible(browsers) && v.iter().all(|v| v.is_compatible(browsers))\n      }\n    }\n  }\n}\n\nenum_property! {\n  /// A [rounding strategy](https://www.w3.org/TR/css-values-4/#typedef-rounding-strategy),\n  /// as used in the `round()` function.\n  pub enum RoundingStrategy {\n    /// Round to the nearest integer.\n    Nearest,\n    /// Round up (ceil).\n    Up,\n    /// Round down (floor).\n    Down,\n    /// Round toward zero (truncate).\n    ToZero,\n  }\n}\n\nimpl Default for RoundingStrategy {\n  fn default() -> Self {\n    RoundingStrategy::Nearest\n  }\n}\n\nfn round(value: f32, to: f32, strategy: RoundingStrategy) -> f32 {\n  let v = value / to;\n  match strategy {\n    RoundingStrategy::Down => v.floor() * to,\n    RoundingStrategy::Up => v.ceil() * to,\n    RoundingStrategy::Nearest => v.round() * to,\n    RoundingStrategy::ToZero => v.trunc() * to,\n  }\n}\n\nfn modulo(a: f32, b: f32) -> f32 {\n  ((a % b) + b) % b\n}\n\nimpl<V: ToCss + std::ops::Mul<f32, Output = V> + TrySign + Clone + std::fmt::Debug> ToCss for MathFunction<V> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      MathFunction::Calc(calc) => {\n        dest.write_str(\"calc(\")?;\n        calc.to_css(dest)?;\n        dest.write_char(')')\n      }\n      MathFunction::Min(args) => {\n        dest.write_str(\"min(\")?;\n        let mut first = true;\n        for arg in args {\n          if first {\n            first = false;\n          } else {\n            dest.delim(',', false)?;\n          }\n          arg.to_css(dest)?;\n        }\n        dest.write_char(')')\n      }\n      MathFunction::Max(args) => {\n        dest.write_str(\"max(\")?;\n        let mut first = true;\n        for arg in args {\n          if first {\n            first = false;\n          } else {\n            dest.delim(',', false)?;\n          }\n          arg.to_css(dest)?;\n        }\n        dest.write_char(')')\n      }\n      MathFunction::Clamp(a, b, c) => {\n        // If clamp() is unsupported by targets, output min()/max()\n        if should_compile!(dest.targets.current, ClampFunction) {\n          dest.write_str(\"max(\")?;\n          a.to_css(dest)?;\n          dest.delim(',', false)?;\n          dest.write_str(\"min(\")?;\n          b.to_css(dest)?;\n          dest.delim(',', false)?;\n          c.to_css(dest)?;\n          dest.write_str(\"))\")?;\n          return Ok(());\n        }\n\n        dest.write_str(\"clamp(\")?;\n        a.to_css(dest)?;\n        dest.delim(',', false)?;\n        b.to_css(dest)?;\n        dest.delim(',', false)?;\n        c.to_css(dest)?;\n        dest.write_char(')')\n      }\n      MathFunction::Round(strategy, a, b) => {\n        dest.write_str(\"round(\")?;\n        if *strategy != RoundingStrategy::default() {\n          strategy.to_css(dest)?;\n          dest.delim(',', false)?;\n        }\n        a.to_css(dest)?;\n        dest.delim(',', false)?;\n        b.to_css(dest)?;\n        dest.write_char(')')\n      }\n      MathFunction::Rem(a, b) => {\n        dest.write_str(\"rem(\")?;\n        a.to_css(dest)?;\n        dest.delim(',', false)?;\n        b.to_css(dest)?;\n        dest.write_char(')')\n      }\n      MathFunction::Mod(a, b) => {\n        dest.write_str(\"mod(\")?;\n        a.to_css(dest)?;\n        dest.delim(',', false)?;\n        b.to_css(dest)?;\n        dest.write_char(')')\n      }\n      MathFunction::Abs(v) => {\n        dest.write_str(\"abs(\")?;\n        v.to_css(dest)?;\n        dest.write_char(')')\n      }\n      MathFunction::Sign(v) => {\n        dest.write_str(\"sign(\")?;\n        v.to_css(dest)?;\n        dest.write_char(')')\n      }\n      MathFunction::Hypot(args) => {\n        dest.write_str(\"hypot(\")?;\n        let mut first = true;\n        for arg in args {\n          if first {\n            first = false;\n          } else {\n            dest.delim(',', false)?;\n          }\n          arg.to_css(dest)?;\n        }\n        dest.write_char(')')\n      }\n    }\n  }\n}\n\n/// A mathematical expression used within the [`calc()`](https://www.w3.org/TR/css-values-4/#calc-func) function.\n///\n/// This type supports generic value types. Values such as [Length](super::length::Length), [Percentage](super::percentage::Percentage),\n/// [Time](super::time::Time), and [Angle](super::angle::Angle) support `calc()` expressions.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum Calc<V> {\n  /// A literal value.\n  Value(Box<V>),\n  /// A literal number.\n  Number(CSSNumber),\n  /// A sum of two calc expressions.\n  #[cfg_attr(feature = \"visitor\", skip_type)]\n  Sum(Box<Calc<V>>, Box<Calc<V>>),\n  /// A product of a number and another calc expression.\n  #[cfg_attr(feature = \"visitor\", skip_type)]\n  Product(CSSNumber, Box<Calc<V>>),\n  /// A math function, such as `calc()`, `min()`, or `max()`.\n  #[cfg_attr(feature = \"visitor\", skip_type)]\n  Function(Box<MathFunction<V>>),\n}\n\nimpl<V: IsCompatible> IsCompatible for Calc<V> {\n  fn is_compatible(&self, browsers: Browsers) -> bool {\n    match self {\n      Calc::Sum(a, b) => a.is_compatible(browsers) && b.is_compatible(browsers),\n      Calc::Product(_, v) => v.is_compatible(browsers),\n      Calc::Function(f) => f.is_compatible(browsers),\n      Calc::Value(v) => v.is_compatible(browsers),\n      Calc::Number(..) => true,\n    }\n  }\n}\n\nenum_property! {\n  /// A mathematical constant.\n  pub enum Constant {\n    /// The base of the natural logarithm\n    \"e\": E,\n    /// The ratio of a circle’s circumference to its diameter\n    \"pi\": Pi,\n    /// infinity\n    \"infinity\": Infinity,\n    /// -infinity\n    \"-infinity\": NegativeInfinity,\n    /// Not a number.\n    \"nan\": Nan,\n  }\n}\n\nimpl Into<f32> for Constant {\n  fn into(self) -> f32 {\n    use std::f32::consts;\n    use Constant::*;\n    match self {\n      E => consts::E,\n      Pi => consts::PI,\n      Infinity => f32::INFINITY,\n      NegativeInfinity => -f32::INFINITY,\n      Nan => f32::NAN,\n    }\n  }\n}\n\nimpl<\n    'i,\n    V: Parse<'i>\n      + std::ops::Mul<f32, Output = V>\n      + AddInternal\n      + TryOp\n      + TryMap\n      + TrySign\n      + std::cmp::PartialOrd<V>\n      + Into<Calc<V>>\n      + TryFrom<Calc<V>>\n      + TryFrom<Angle>\n      + TryInto<Angle>\n      + Clone\n      + std::fmt::Debug,\n  > Parse<'i> for Calc<V>\n{\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    Self::parse_with(input, |_| None)\n  }\n}\n\nimpl<\n    'i,\n    V: Parse<'i>\n      + std::ops::Mul<f32, Output = V>\n      + AddInternal\n      + TryOp\n      + TryMap\n      + TrySign\n      + std::cmp::PartialOrd<V>\n      + Into<Calc<V>>\n      + TryFrom<Calc<V>>\n      + TryFrom<Angle>\n      + TryInto<Angle>\n      + Clone\n      + std::fmt::Debug,\n  > Calc<V>\n{\n  pub(crate) fn parse_with<'t, Parse: Copy + Fn(&str) -> Option<Calc<V>>>(\n    input: &mut Parser<'i, 't>,\n    parse_ident: Parse,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let location = input.current_source_location();\n    let f = input.expect_function()?;\n    match_ignore_ascii_case! { &f,\n      \"calc\" => {\n        let calc = input.parse_nested_block(|input| Calc::parse_sum(input, parse_ident))?;\n        match calc {\n          Calc::Value(_) | Calc::Number(_) => Ok(calc),\n          _ => Ok(Calc::Function(Box::new(MathFunction::Calc(calc))))\n        }\n      },\n      \"min\" => {\n        let mut args = input.parse_nested_block(|input| input.parse_comma_separated(|input| Calc::parse_sum(input, parse_ident)))?;\n        let mut reduced = Calc::reduce_args(&mut args, std::cmp::Ordering::Less);\n        if reduced.len() == 1 {\n          return Ok(reduced.remove(0))\n        }\n        Ok(Calc::Function(Box::new(MathFunction::Min(reduced))))\n      },\n      \"max\" => {\n        let mut args = input.parse_nested_block(|input| input.parse_comma_separated(|input| Calc::parse_sum(input, parse_ident)))?;\n        let mut reduced = Calc::reduce_args(&mut args, std::cmp::Ordering::Greater);\n        if reduced.len() == 1 {\n          return Ok(reduced.remove(0))\n        }\n        Ok(Calc::Function(Box::new(MathFunction::Max(reduced))))\n      },\n      \"clamp\" => {\n        let (mut min, mut center, mut max) = input.parse_nested_block(|input| {\n          let min = Some(Calc::parse_sum(input, parse_ident)?);\n          input.expect_comma()?;\n          let center: Calc<V> = Calc::parse_sum(input, parse_ident)?;\n          input.expect_comma()?;\n          let max = Some(Calc::parse_sum(input, parse_ident)?);\n          Ok((min, center, max))\n        })?;\n\n        // According to the spec, the minimum should \"win\" over the maximum if they are in the wrong order.\n        let cmp = match (&max, &center) {\n          (Some(Calc::Value(max_val)), Calc::Value(center_val)) => center_val.partial_cmp(&max_val),\n          (Some(Calc::Number(max_val)), Calc::Number(center_val)) => center_val.partial_cmp(max_val),\n          _ => None,\n        };\n\n        // If center is known to be greater than the maximum, replace it with maximum and remove the max argument.\n        // Otherwise, if center is known to be less than the maximum, remove the max argument.\n        match cmp {\n          Some(std::cmp::Ordering::Greater) => {\n            center = std::mem::take(&mut max).unwrap();\n          }\n          Some(_) => {\n            max = None;\n          }\n          None => {}\n        }\n\n        if cmp.is_some() {\n          let cmp = match (&min, &center) {\n            (Some(Calc::Value(min_val)), Calc::Value(center_val)) => center_val.partial_cmp(&min_val),\n            (Some(Calc::Number(min_val)), Calc::Number(center_val)) => center_val.partial_cmp(min_val),\n            _ => None,\n          };\n\n          // If center is known to be less than the minimum, replace it with minimum and remove the min argument.\n          // Otherwise, if center is known to be greater than the minimum, remove the min argument.\n          match cmp {\n            Some(std::cmp::Ordering::Less) => {\n              center = std::mem::take(&mut min).unwrap();\n            }\n            Some(_) => {\n              min = None;\n            }\n            None => {}\n          }\n        }\n\n        // Generate clamp(), min(), max(), or value depending on which arguments are left.\n        match (min, max) {\n          (None, None) => Ok(center),\n          (Some(min), None) => Ok(Calc::Function(Box::new(MathFunction::Max(vec![min, center])))),\n          (None, Some(max)) => Ok(Calc::Function(Box::new(MathFunction::Min(vec![center, max])))),\n          (Some(min), Some(max)) => Ok(Calc::Function(Box::new(MathFunction::Clamp(min, center, max))))\n        }\n      },\n      \"round\" => {\n        input.parse_nested_block(|input| {\n          let strategy = if let Ok(s) = input.try_parse(RoundingStrategy::parse) {\n            input.expect_comma()?;\n            s\n          } else {\n            RoundingStrategy::default()\n          };\n\n          Self::parse_math_fn(\n            input,\n            |a, b| round(a, b, strategy),\n            |a, b| MathFunction::Round(strategy, a, b),\n            parse_ident\n          )\n        })\n      },\n      \"rem\" => {\n        input.parse_nested_block(|input| {\n          Self::parse_math_fn(input, std::ops::Rem::rem, MathFunction::Rem, parse_ident)\n        })\n      },\n      \"mod\" => {\n        input.parse_nested_block(|input| {\n          Self::parse_math_fn(input, modulo, MathFunction::Mod, parse_ident)\n        })\n      },\n      \"sin\" => Self::parse_trig(input, f32::sin, false, parse_ident),\n      \"cos\" => Self::parse_trig(input, f32::cos, false, parse_ident),\n      \"tan\" => Self::parse_trig(input, f32::tan, false, parse_ident),\n      \"asin\" => Self::parse_trig(input, f32::asin, true, parse_ident),\n      \"acos\" => Self::parse_trig(input, f32::acos, true, parse_ident),\n      \"atan\" => Self::parse_trig(input, f32::atan, true, parse_ident),\n      \"atan2\" => {\n        input.parse_nested_block(|input| {\n          let res = Self::parse_atan2(input, parse_ident)?;\n          if let Ok(v) = V::try_from(res) {\n            return Ok(Calc::Value(Box::new(v)))\n          }\n\n          Err(input.new_custom_error(ParserError::InvalidValue))\n        })\n      },\n      \"pow\" => {\n        input.parse_nested_block(|input| {\n          let a = Self::parse_numeric(input, parse_ident)?;\n          input.expect_comma()?;\n          let b = Self::parse_numeric(input, parse_ident)?;\n          Ok(Calc::Number(a.powf(b)))\n        })\n      },\n      \"log\" => {\n        input.parse_nested_block(|input| {\n          let value = Self::parse_numeric(input, parse_ident)?;\n          if input.try_parse(|input| input.expect_comma()).is_ok() {\n            let base = Self::parse_numeric(input, parse_ident)?;\n            Ok(Calc::Number(value.log(base)))\n          } else {\n            Ok(Calc::Number(value.ln()))\n          }\n        })\n      },\n      \"sqrt\" => Self::parse_numeric_fn(input, f32::sqrt, parse_ident),\n      \"exp\" => Self::parse_numeric_fn(input, f32::exp, parse_ident),\n      \"hypot\" => {\n        input.parse_nested_block(|input| {\n          let args: Vec<Self> = input.parse_comma_separated(|input| Calc::parse_sum(input, parse_ident))?;\n          Self::parse_hypot(&args)?\n            .map_or_else(\n              || Ok(Calc::Function(Box::new(MathFunction::Hypot(args)))),\n              |v| Ok(v)\n            )\n        })\n      },\n      \"abs\" => {\n        input.parse_nested_block(|input| {\n          let v: Calc<V> = Self::parse_sum(input, parse_ident)?;\n          Self::apply_map(&v, f32::abs)\n            .map_or_else(\n              || Ok(Calc::Function(Box::new(MathFunction::Abs(v)))),\n              |v| Ok(v)\n            )\n        })\n      },\n      \"sign\" => {\n        input.parse_nested_block(|input| {\n          let v: Calc<V> = Self::parse_sum(input, parse_ident)?;\n          match &v {\n            Calc::Number(n) => return Ok(Calc::Number(n.sign())),\n            Calc::Value(v) => {\n              // First map so we ignore percentages, which must be resolved to their\n              // computed value in order to determine the sign.\n              if let Some(v) = v.try_map(|s| s.sign()) {\n                // sign() always resolves to a number.\n                return Ok(Calc::Number(v.try_sign().unwrap()));\n              }\n            }\n            _ => {}\n          }\n\n          Ok(Calc::Function(Box::new(MathFunction::Sign(v))))\n        })\n      },\n       _ => Err(location.new_unexpected_token_error(Token::Ident(f.clone()))),\n    }\n  }\n\n  fn parse_sum<'t, Parse: Copy + Fn(&str) -> Option<Calc<V>>>(\n    input: &mut Parser<'i, 't>,\n    parse_ident: Parse,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let mut cur: Calc<V> = Calc::parse_product(input, parse_ident)?;\n    loop {\n      let start = input.state();\n      match input.next_including_whitespace() {\n        Ok(&Token::WhiteSpace(_)) => {\n          if input.is_exhausted() {\n            break; // allow trailing whitespace\n          }\n          match *input.next()? {\n            Token::Delim('+') => {\n              let next = Calc::parse_product(input, parse_ident)?;\n              cur = cur.add(next).map_err(|_| input.new_custom_error(ParserError::InvalidValue))?;\n            }\n            Token::Delim('-') => {\n              let mut rhs = Calc::parse_product(input, parse_ident)?;\n              rhs = rhs * -1.0;\n              cur = cur.add(rhs).map_err(|_| input.new_custom_error(ParserError::InvalidValue))?;\n            }\n            ref t => {\n              let t = t.clone();\n              return Err(input.new_unexpected_token_error(t));\n            }\n          }\n        }\n        _ => {\n          input.reset(&start);\n          break;\n        }\n      }\n    }\n    Ok(cur)\n  }\n\n  fn parse_product<'t, Parse: Copy + Fn(&str) -> Option<Calc<V>>>(\n    input: &mut Parser<'i, 't>,\n    parse_ident: Parse,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let mut node = Calc::parse_value(input, parse_ident)?;\n    loop {\n      let start = input.state();\n      match input.next() {\n        Ok(&Token::Delim('*')) => {\n          // At least one of the operands must be a number.\n          let rhs = Self::parse_value(input, parse_ident)?;\n          if let Calc::Number(val) = rhs {\n            node = node * val;\n          } else if let Calc::Number(val) = node {\n            node = rhs;\n            node = node * val;\n          } else {\n            return Err(input.new_unexpected_token_error(Token::Delim('*')));\n          }\n        }\n        Ok(&Token::Delim('/')) => {\n          let rhs = Self::parse_value(input, parse_ident)?;\n          if let Calc::Number(val) = rhs {\n            if val != 0.0 {\n              node = node * (1.0 / val);\n              continue;\n            }\n          }\n          return Err(input.new_custom_error(ParserError::InvalidValue));\n        }\n        _ => {\n          input.reset(&start);\n          break;\n        }\n      }\n    }\n    Ok(node)\n  }\n\n  fn parse_value<'t, Parse: Copy + Fn(&str) -> Option<Calc<V>>>(\n    input: &mut Parser<'i, 't>,\n    parse_ident: Parse,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    // Parse nested calc() and other math functions.\n    if let Ok(calc) = input.try_parse(Self::parse) {\n      match calc {\n        Calc::Function(f) => {\n          return Ok(match *f {\n            MathFunction::Calc(c) => c,\n            _ => Calc::Function(f),\n          })\n        }\n        c => return Ok(c),\n      }\n    }\n\n    if input.try_parse(|input| input.expect_parenthesis_block()).is_ok() {\n      return input.parse_nested_block(|input| Calc::parse_sum(input, parse_ident));\n    }\n\n    if let Ok(num) = input.try_parse(|input| input.expect_number()) {\n      return Ok(Calc::Number(num));\n    }\n\n    if let Ok(constant) = input.try_parse(Constant::parse) {\n      return Ok(Calc::Number(constant.into()));\n    }\n\n    let location = input.current_source_location();\n    if let Ok(ident) = input.try_parse(|input| input.expect_ident_cloned()) {\n      if let Some(v) = parse_ident(ident.as_ref()) {\n        return Ok(v);\n      }\n\n      return Err(location.new_unexpected_token_error(Token::Ident(ident.clone())));\n    }\n\n    let value = input.try_parse(V::parse)?;\n    Ok(Calc::Value(Box::new(value)))\n  }\n\n  fn reduce_args(args: &mut Vec<Calc<V>>, cmp: std::cmp::Ordering) -> Vec<Calc<V>> {\n    // Reduces the arguments of a min() or max() expression, combining compatible values.\n    // e.g. min(1px, 1em, 2px, 3in) => min(1px, 1em)\n    // Also handles plain numbers: min(1, 2, 3) => min(1, 2)\n    let mut reduced: Vec<Calc<V>> = vec![];\n    for arg in args.drain(..) {\n      let mut found = None;\n      match &arg {\n        Calc::Value(val) => {\n          for b in reduced.iter_mut() {\n            if let Calc::Value(v) = b {\n              match val.partial_cmp(v) {\n                Some(ord) if ord == cmp => {\n                  found = Some(Some(b));\n                  break;\n                }\n                Some(_) => {\n                  found = Some(None);\n                  break;\n                }\n                None => {}\n              }\n            }\n          }\n        }\n        Calc::Number(val) => {\n          for b in reduced.iter_mut() {\n            if let Calc::Number(v) = b {\n              match val.partial_cmp(v) {\n                Some(ord) if ord == cmp => {\n                  found = Some(Some(b));\n                  break;\n                }\n                Some(_) => {\n                  found = Some(None);\n                  break;\n                }\n                None => {}\n              }\n            }\n          }\n        }\n        _ => {}\n      }\n      if let Some(r) = found {\n        if let Some(r) = r {\n          *r = arg\n        }\n      } else {\n        reduced.push(arg)\n      }\n    }\n    reduced\n  }\n\n  fn parse_math_fn<\n    't,\n    O: FnOnce(f32, f32) -> f32,\n    F: FnOnce(Calc<V>, Calc<V>) -> MathFunction<V>,\n    Parse: Copy + Fn(&str) -> Option<Calc<V>>,\n  >(\n    input: &mut Parser<'i, 't>,\n    op: O,\n    fallback: F,\n    parse_ident: Parse,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let a: Calc<V> = Calc::parse_sum(input, parse_ident)?;\n    input.expect_comma()?;\n    let b: Calc<V> = Calc::parse_sum(input, parse_ident)?;\n\n    Ok(Self::apply_op(&a, &b, op).unwrap_or_else(|| Calc::Function(Box::new(fallback(a, b)))))\n  }\n\n  fn apply_op<'t, O: FnOnce(f32, f32) -> f32>(a: &Calc<V>, b: &Calc<V>, op: O) -> Option<Self> {\n    match (a, b) {\n      (Calc::Value(a), Calc::Value(b)) => {\n        if let Some(v) = a.try_op(&**b, op) {\n          return Some(Calc::Value(Box::new(v)));\n        }\n      }\n      (Calc::Number(a), Calc::Number(b)) => return Some(Calc::Number(op(*a, *b))),\n      _ => {}\n    }\n\n    None\n  }\n\n  fn apply_map<'t, O: FnOnce(f32) -> f32>(v: &Calc<V>, op: O) -> Option<Self> {\n    match v {\n      Calc::Number(n) => return Some(Calc::Number(op(*n))),\n      Calc::Value(v) => {\n        if let Some(v) = v.try_map(op) {\n          return Some(Calc::Value(Box::new(v)));\n        }\n      }\n      _ => {}\n    }\n\n    None\n  }\n\n  fn parse_trig<'t, F: FnOnce(f32) -> f32, Parse: Copy + Fn(&str) -> Option<Calc<V>>>(\n    input: &mut Parser<'i, 't>,\n    f: F,\n    to_angle: bool,\n    parse_ident: Parse,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    input.parse_nested_block(|input| {\n      let v: Calc<Angle> = Calc::parse_sum(input, |v| {\n        parse_ident(v).and_then(|v| -> Option<Calc<Angle>> {\n          match v {\n            Calc::Number(v) => Some(Calc::Number(v)),\n            Calc::Value(v) => (*v).try_into().ok().map(|v| Calc::Value(Box::new(v))),\n            _ => None,\n          }\n        })\n      })?;\n      let rad = match v {\n        Calc::Value(angle) if !to_angle => f(angle.to_radians()),\n        Calc::Number(v) => f(v),\n        _ => return Err(input.new_custom_error(ParserError::InvalidValue)),\n      };\n\n      if to_angle && !rad.is_nan() {\n        if let Ok(v) = V::try_from(Angle::Rad(rad)) {\n          return Ok(Calc::Value(Box::new(v)));\n        } else {\n          return Err(input.new_custom_error(ParserError::InvalidValue));\n        }\n      } else {\n        Ok(Calc::Number(rad))\n      }\n    })\n  }\n\n  fn parse_numeric<'t, Parse: Copy + Fn(&str) -> Option<Calc<V>>>(\n    input: &mut Parser<'i, 't>,\n    parse_ident: Parse,\n  ) -> Result<f32, ParseError<'i, ParserError<'i>>> {\n    let v: Calc<CSSNumber> = Calc::parse_sum(input, |v| {\n      parse_ident(v).and_then(|v| match v {\n        Calc::Number(v) => Some(Calc::Number(v)),\n        _ => None,\n      })\n    })?;\n    match v {\n      Calc::Number(n) => Ok(n),\n      Calc::Value(v) => Ok(*v),\n      _ => Err(input.new_custom_error(ParserError::InvalidValue)),\n    }\n  }\n\n  fn parse_numeric_fn<'t, F: FnOnce(f32) -> f32, Parse: Copy + Fn(&str) -> Option<Calc<V>>>(\n    input: &mut Parser<'i, 't>,\n    f: F,\n    parse_ident: Parse,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    input.parse_nested_block(|input| {\n      let v = Self::parse_numeric(input, parse_ident)?;\n      Ok(Calc::Number(f(v)))\n    })\n  }\n\n  fn parse_atan2<'t, Parse: Copy + Fn(&str) -> Option<Calc<V>>>(\n    input: &mut Parser<'i, 't>,\n    parse_ident: Parse,\n  ) -> Result<Angle, ParseError<'i, ParserError<'i>>> {\n    // atan2 supports arguments of any <number>, <dimension>, or <percentage>, even ones that wouldn't\n    // normally be supported by V. The only requirement is that the arguments be of the same type.\n    // Try parsing with each type, and return the first one that parses successfully.\n    if let Ok(v) = input.try_parse(|input| Calc::<Length>::parse_atan2_args(input, |_| None)) {\n      return Ok(v);\n    }\n\n    if let Ok(v) = input.try_parse(|input| Calc::<Percentage>::parse_atan2_args(input, |_| None)) {\n      return Ok(v);\n    }\n\n    if let Ok(v) = input.try_parse(|input| Calc::<Angle>::parse_atan2_args(input, |_| None)) {\n      return Ok(v);\n    }\n\n    if let Ok(v) = input.try_parse(|input| Calc::<Time>::parse_atan2_args(input, |_| None)) {\n      return Ok(v);\n    }\n\n    Calc::<CSSNumber>::parse_atan2_args(input, |v| {\n      parse_ident(v).and_then(|v| match v {\n        Calc::Number(v) => Some(Calc::Number(v)),\n        _ => None,\n      })\n    })\n  }\n\n  fn parse_atan2_args<'t, Parse: Copy + Fn(&str) -> Option<Calc<V>>>(\n    input: &mut Parser<'i, 't>,\n    parse_ident: Parse,\n  ) -> Result<Angle, ParseError<'i, ParserError<'i>>> {\n    let a = Calc::<V>::parse_sum(input, parse_ident)?;\n    input.expect_comma()?;\n    let b = Calc::<V>::parse_sum(input, parse_ident)?;\n\n    match (&a, &b) {\n      (Calc::Value(a), Calc::Value(b)) => {\n        if let Some(v) = a.try_op_to(&**b, |a, b| Angle::Rad(a.atan2(b))) {\n          return Ok(v);\n        }\n      }\n      (Calc::Number(a), Calc::Number(b)) => return Ok(Angle::Rad(a.atan2(*b))),\n      _ => {}\n    }\n\n    // We don't have a way to represent arguments that aren't angles, so just error.\n    // This will fall back to an unparsed property, leaving the atan2() function intact.\n    Err(input.new_custom_error(ParserError::InvalidValue))\n  }\n\n  fn parse_hypot<'t>(args: &Vec<Self>) -> Result<Option<Self>, ParseError<'i, ParserError<'i>>> {\n    if args.len() == 1 {\n      return Ok(Some(args[0].clone()));\n    }\n\n    if args.len() == 2 {\n      return Ok(Self::apply_op(&args[0], &args[1], f32::hypot));\n    }\n\n    let mut iter = args.iter();\n    let first = match Self::apply_map(&iter.next().unwrap(), |v| v.powi(2)) {\n      Some(v) => v,\n      None => return Ok(None),\n    };\n    let sum = iter.try_fold(first, |acc, arg| {\n      Self::apply_op(&acc, &arg, |a, b| a + b.powi(2)).map_or_else(|| Err(()), |v| Ok(v))\n    });\n\n    let sum = match sum {\n      Ok(s) => s,\n      Err(_) => return Ok(None),\n    };\n\n    Ok(Self::apply_map(&sum, f32::sqrt))\n  }\n}\n\nimpl<V: std::ops::Mul<f32, Output = V>> std::ops::Mul<f32> for Calc<V> {\n  type Output = Self;\n\n  fn mul(self, other: f32) -> Self {\n    if other == 1.0 {\n      return self;\n    }\n\n    match self {\n      Calc::Value(v) => Calc::Value(Box::new(*v * other)),\n      Calc::Number(n) => Calc::Number(n * other),\n      Calc::Sum(a, b) => Calc::Sum(Box::new(*a * other), Box::new(*b * other)),\n      Calc::Product(num, calc) => {\n        let num = num * other;\n        if num == 1.0 {\n          return *calc;\n        }\n        Calc::Product(num, calc)\n      }\n      Calc::Function(f) => match *f {\n        MathFunction::Calc(c) => Calc::Function(Box::new(MathFunction::Calc(c * other))),\n        _ => Calc::Product(other, Box::new(Calc::Function(f))),\n      },\n    }\n  }\n}\n\nimpl<V: AddInternal + std::convert::Into<Calc<V>> + std::convert::TryFrom<Calc<V>> + std::fmt::Debug> Calc<V> {\n  pub(crate) fn add(self, other: Calc<V>) -> Result<Calc<V>, <V as TryFrom<Calc<V>>>::Error> {\n    Ok(match (self, other) {\n      (Calc::Value(a), Calc::Value(b)) => (a.add(*b)).into(),\n      (Calc::Number(a), Calc::Number(b)) => Calc::Number(a + b),\n      (Calc::Sum(a, b), Calc::Number(c)) => {\n        if let Calc::Number(a) = *a {\n          Calc::Sum(Box::new(Calc::Number(a + c)), b)\n        } else if let Calc::Number(b) = *b {\n          Calc::Sum(a, Box::new(Calc::Number(b + c)))\n        } else {\n          Calc::Sum(Box::new(Calc::Sum(a, b)), Box::new(Calc::Number(c)))\n        }\n      }\n      (Calc::Number(a), Calc::Sum(b, c)) => {\n        if let Calc::Number(b) = *b {\n          Calc::Sum(Box::new(Calc::Number(a + b)), c)\n        } else if let Calc::Number(c) = *c {\n          Calc::Sum(Box::new(Calc::Number(a + c)), b)\n        } else {\n          Calc::Sum(Box::new(Calc::Number(a)), Box::new(Calc::Sum(b, c)))\n        }\n      }\n      (a @ Calc::Number(_), b)\n      | (a, b @ Calc::Number(_))\n      | (a @ Calc::Product(..), b)\n      | (a, b @ Calc::Product(..)) => Calc::Sum(Box::new(a), Box::new(b)),\n      (Calc::Function(a), b) => Calc::Sum(Box::new(Calc::Function(a)), Box::new(b)),\n      (a, Calc::Function(b)) => Calc::Sum(Box::new(a), Box::new(Calc::Function(b))),\n      (Calc::Value(a), b) => (a.add(V::try_from(b)?)).into(),\n      (a, Calc::Value(b)) => (V::try_from(a)?.add(*b)).into(),\n      (a @ Calc::Sum(..), b @ Calc::Sum(..)) => V::try_from(a)?.add(V::try_from(b)?).into(),\n    })\n  }\n}\n\nimpl<V: ToCss + std::ops::Mul<f32, Output = V> + TrySign + Clone + std::fmt::Debug> ToCss for Calc<V> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    let was_in_calc = dest.in_calc;\n    dest.in_calc = true;\n\n    let res = match self {\n      Calc::Value(v) => v.to_css(dest),\n      Calc::Number(n) => n.to_css(dest),\n      Calc::Sum(a, b) => {\n        a.to_css(dest)?;\n        // Whitespace is always required.\n        let b = &**b;\n        if b.is_sign_negative() {\n          dest.write_str(\" - \")?;\n          let b = b.clone() * -1.0;\n          b.to_css(dest)\n        } else {\n          dest.write_str(\" + \")?;\n          b.to_css(dest)\n        }\n      }\n      Calc::Product(num, calc) => {\n        if num.abs() < 1.0 {\n          let div = 1.0 / num;\n          calc.to_css(dest)?;\n          dest.delim('/', true)?;\n          div.to_css(dest)\n        } else {\n          num.to_css(dest)?;\n          dest.delim('*', true)?;\n          calc.to_css(dest)\n        }\n      }\n      Calc::Function(f) => f.to_css(dest),\n    };\n\n    dest.in_calc = was_in_calc;\n    res\n  }\n}\n\nimpl<V: TrySign> TrySign for Calc<V> {\n  fn try_sign(&self) -> Option<f32> {\n    match self {\n      Calc::Number(v) => v.try_sign(),\n      Calc::Value(v) => v.try_sign(),\n      Calc::Product(c, v) => v.try_sign().map(|s| s * c.sign()),\n      Calc::Function(f) => f.try_sign(),\n      _ => None,\n    }\n  }\n}\n\nimpl<V: TrySign> TrySign for MathFunction<V> {\n  fn try_sign(&self) -> Option<f32> {\n    match self {\n      MathFunction::Abs(_) => Some(1.0),\n      MathFunction::Max(values) | MathFunction::Min(values) => {\n        let mut iter = values.iter();\n        if let Some(sign) = iter.next().and_then(|f| f.try_sign()) {\n          for value in iter {\n            if let Some(s) = value.try_sign() {\n              if s != sign {\n                return None;\n              }\n            } else {\n              return None;\n            }\n          }\n          return Some(sign);\n        } else {\n          return None;\n        }\n      }\n      MathFunction::Clamp(a, b, c) => {\n        if let (Some(a), Some(b), Some(c)) = (a.try_sign(), b.try_sign(), c.try_sign()) {\n          if a == b && b == c {\n            return Some(a);\n          }\n        }\n        return None;\n      }\n      MathFunction::Round(_, a, b) => {\n        if let (Some(a), Some(b)) = (a.try_sign(), b.try_sign()) {\n          if a == b {\n            return Some(a);\n          }\n        }\n        return None;\n      }\n      MathFunction::Sign(v) => v.try_sign(),\n      MathFunction::Calc(v) => v.try_sign(),\n      _ => None,\n    }\n  }\n}\n"
  },
  {
    "path": "src/values/color.rs",
    "content": "//! CSS color values.\n\n#![allow(non_upper_case_globals)]\n\nuse super::angle::Angle;\nuse super::calc::Calc;\nuse super::number::CSSNumber;\nuse super::percentage::Percentage;\nuse crate::compat::Feature;\nuse crate::error::{ParserError, PrinterError};\nuse crate::macros::enum_property;\nuse crate::printer::Printer;\nuse crate::properties::PropertyId;\nuse crate::rules::supports::SupportsCondition;\nuse crate::targets::{should_compile, Browsers, Features, Targets};\nuse crate::traits::{FallbackValues, IsCompatible, Parse, ToCss};\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::{Visit, VisitTypes, Visitor};\nuse bitflags::bitflags;\nuse cssparser::color::{parse_hash_color, parse_named_color};\nuse cssparser::*;\nuse cssparser_color::{hsl_to_rgb, AngleOrNumber, ColorParser, NumberOrPercentage};\nuse std::any::TypeId;\nuse std::f32::consts::PI;\nuse std::fmt::Write;\n\n/// A CSS [`<color>`](https://www.w3.org/TR/css-color-4/#color-type) value.\n///\n/// CSS supports many different color spaces to represent colors. The most common values\n/// are stored as RGBA using a single byte per component. Less common values are stored\n/// using a `Box` to reduce the amount of memory used per color.\n///\n/// Each color space is represented as a struct that implements the `From` and `Into` traits\n/// for all other color spaces, so it is possible to convert between color spaces easily.\n/// In addition, colors support [interpolation](#method.interpolate) as in the `color-mix()` function.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"visitor\", visit(visit_color, COLORS))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(untagged, rename_all = \"lowercase\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum CssColor {\n  /// The [`currentColor`](https://www.w3.org/TR/css-color-4/#currentcolor-color) keyword.\n  #[cfg_attr(feature = \"serde\", serde(with = \"CurrentColor\"))]\n  CurrentColor,\n  /// An value in the RGB color space, including values parsed as hex colors, or the `rgb()`, `hsl()`, and `hwb()` functions.\n  #[cfg_attr(\n    feature = \"serde\",\n    serde(serialize_with = \"serialize_rgba\", deserialize_with = \"deserialize_rgba\")\n  )]\n  #[cfg_attr(feature = \"jsonschema\", schemars(with = \"RGBColor\"))]\n  RGBA(RGBA),\n  /// A value in a LAB color space, including the `lab()`, `lch()`, `oklab()`, and `oklch()` functions.\n  LAB(Box<LABColor>),\n  /// A value in a predefined color space, e.g. `display-p3`.\n  Predefined(Box<PredefinedColor>),\n  /// A floating point representation of an RGB, HSL, or HWB color when it contains `none` components.\n  Float(Box<FloatColor>),\n  /// The [`light-dark()`](https://drafts.csswg.org/css-color-5/#light-dark) function.\n  #[cfg_attr(feature = \"visitor\", skip_type)]\n  #[cfg_attr(feature = \"serde\", serde(with = \"LightDark\"))]\n  LightDark(Box<CssColor>, Box<CssColor>),\n  /// A [system color](https://drafts.csswg.org/css-color/#css-system-colors) keyword.\n  System(SystemColor),\n}\n\n#[cfg(feature = \"serde\")]\n#[derive(serde::Serialize, serde::Deserialize)]\n#[serde(tag = \"type\", rename_all = \"lowercase\")]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\nenum CurrentColor {\n  CurrentColor,\n}\n\n#[cfg(feature = \"serde\")]\nimpl CurrentColor {\n  fn serialize<S>(serializer: S) -> Result<S::Ok, S::Error>\n  where\n    S: serde::Serializer,\n  {\n    serde::Serialize::serialize(&CurrentColor::CurrentColor, serializer)\n  }\n\n  fn deserialize<'de, D>(deserializer: D) -> Result<(), D::Error>\n  where\n    D: serde::Deserializer<'de>,\n  {\n    use serde::Deserialize;\n    let _: CurrentColor = Deserialize::deserialize(deserializer)?;\n    Ok(())\n  }\n}\n\n// Convert RGBA to SRGB to serialize so we get a tagged struct.\n#[cfg(feature = \"serde\")]\n#[derive(serde::Serialize, serde::Deserialize)]\n#[serde(tag = \"type\", rename_all = \"lowercase\")]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\nenum RGBColor {\n  RGB(RGB),\n}\n\n#[cfg(feature = \"serde\")]\nfn serialize_rgba<S>(rgba: &RGBA, serializer: S) -> Result<S::Ok, S::Error>\nwhere\n  S: serde::Serializer,\n{\n  use serde::Serialize;\n  RGBColor::RGB(rgba.into()).serialize(serializer)\n}\n\n#[cfg(feature = \"serde\")]\nfn deserialize_rgba<'de, D>(deserializer: D) -> Result<RGBA, D::Error>\nwhere\n  D: serde::Deserializer<'de>,\n{\n  use serde::Deserialize;\n  match RGBColor::deserialize(deserializer)? {\n    RGBColor::RGB(srgb) => Ok(srgb.into()),\n  }\n}\n\n// For AST serialization.\n#[cfg(feature = \"serde\")]\n#[derive(serde::Serialize, serde::Deserialize)]\n#[serde(tag = \"type\", rename_all = \"kebab-case\")]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\nenum LightDark {\n  LightDark { light: CssColor, dark: CssColor },\n}\n\n#[cfg(feature = \"serde\")]\nimpl<'de> LightDark {\n  pub fn serialize<S>(light: &Box<CssColor>, dark: &Box<CssColor>, serializer: S) -> Result<S::Ok, S::Error>\n  where\n    S: serde::Serializer,\n  {\n    let wrapper = LightDark::LightDark {\n      light: (**light).clone(),\n      dark: (**dark).clone(),\n    };\n    serde::Serialize::serialize(&wrapper, serializer)\n  }\n\n  pub fn deserialize<D>(deserializer: D) -> Result<(Box<CssColor>, Box<CssColor>), D::Error>\n  where\n    D: serde::Deserializer<'de>,\n  {\n    let v: LightDark = serde::Deserialize::deserialize(deserializer)?;\n    match v {\n      LightDark::LightDark { light, dark } => Ok((Box::new(light), Box::new(dark))),\n    }\n  }\n}\n\n/// A color in a LAB color space, including the `lab()`, `lch()`, `oklab()`, and `oklch()` functions.\n#[derive(Debug, Clone, Copy, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", rename_all = \"lowercase\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub enum LABColor {\n  /// A `lab()` color.\n  LAB(LAB),\n  /// An `lch()` color.\n  LCH(LCH),\n  /// An `oklab()` color.\n  OKLAB(OKLAB),\n  /// An `oklch()` color.\n  OKLCH(OKLCH),\n}\n\n/// A color in a predefined color space, e.g. `display-p3`.\n#[derive(Debug, Clone, Copy, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize), serde(tag = \"type\"))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub enum PredefinedColor {\n  /// A color in the `srgb` color space.\n  #[cfg_attr(feature = \"serde\", serde(rename = \"srgb\"))]\n  SRGB(SRGB),\n  /// A color in the `srgb-linear` color space.\n  #[cfg_attr(feature = \"serde\", serde(rename = \"srgb-linear\"))]\n  SRGBLinear(SRGBLinear),\n  /// A color in the `display-p3` color space.\n  #[cfg_attr(feature = \"serde\", serde(rename = \"display-p3\"))]\n  DisplayP3(P3),\n  /// A color in the `a98-rgb` color space.\n  #[cfg_attr(feature = \"serde\", serde(rename = \"a98-rgb\"))]\n  A98(A98),\n  /// A color in the `prophoto-rgb` color space.\n  #[cfg_attr(feature = \"serde\", serde(rename = \"prophoto-rgb\"))]\n  ProPhoto(ProPhoto),\n  /// A color in the `rec2020` color space.\n  #[cfg_attr(feature = \"serde\", serde(rename = \"rec2020\"))]\n  Rec2020(Rec2020),\n  /// A color in the `xyz-d50` color space.\n  #[cfg_attr(feature = \"serde\", serde(rename = \"xyz-d50\"))]\n  XYZd50(XYZd50),\n  /// A color in the `xyz-d65` color space.\n  #[cfg_attr(feature = \"serde\", serde(rename = \"xyz-d65\"))]\n  XYZd65(XYZd65),\n}\n\n/// A floating point representation of color types that\n/// are usually stored as RGBA. These are used when there\n/// are any `none` components, which are represented as NaN.\n#[derive(Debug, Clone, Copy, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", rename_all = \"lowercase\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub enum FloatColor {\n  /// An RGB color.\n  RGB(RGB),\n  /// An HSL color.\n  HSL(HSL),\n  /// An HWB color.\n  HWB(HWB),\n}\n\nbitflags! {\n  /// A color type that is used as a fallback when compiling colors for older browsers.\n  #[derive(PartialEq, Eq, Clone, Copy)]\n  pub struct ColorFallbackKind: u8 {\n    /// An RGB color fallback.\n    const RGB    = 0b01;\n    /// A P3 color fallback.\n    const P3     = 0b10;\n    /// A LAB color fallback.\n    const LAB    = 0b100;\n    /// An OKLAB color fallback.\n    const OKLAB  = 0b1000;\n  }\n}\n\nenum_property! {\n  /// A [color space](https://www.w3.org/TR/css-color-4/#interpolation-space) keyword\n  /// used in interpolation functions such as `color-mix()`.\n  enum ColorSpaceName {\n    \"srgb\": SRGB,\n    \"srgb-linear\": SRGBLinear,\n    \"lab\": LAB,\n    \"oklab\": OKLAB,\n    \"xyz\": XYZ,\n    \"xyz-d50\": XYZd50,\n    \"xyz-d65\": XYZd65,\n    \"hsl\": Hsl,\n    \"hwb\": Hwb,\n    \"lch\": LCH,\n    \"oklch\": OKLCH,\n  }\n}\n\nenum_property! {\n  /// A hue [interpolation method](https://www.w3.org/TR/css-color-4/#typedef-hue-interpolation-method)\n  /// used in interpolation functions such as `color-mix()`.\n  pub enum HueInterpolationMethod {\n    /// Angles are adjusted so that θ₂ - θ₁ ∈ [-180, 180].\n    Shorter,\n    /// Angles are adjusted so that θ₂ - θ₁ ∈ {0, [180, 360)}.\n    Longer,\n    /// Angles are adjusted so that θ₂ - θ₁ ∈ [0, 360).\n    Increasing,\n    /// Angles are adjusted so that θ₂ - θ₁ ∈ (-360, 0].\n    Decreasing,\n    /// No fixup is performed. Angles are interpolated in the same way as every other component.\n    Specified,\n  }\n}\n\nimpl ColorFallbackKind {\n  pub(crate) fn lowest(&self) -> ColorFallbackKind {\n    // This finds the lowest set bit.\n    *self & ColorFallbackKind::from_bits_truncate(self.bits().wrapping_neg())\n  }\n\n  pub(crate) fn highest(&self) -> ColorFallbackKind {\n    // This finds the highest set bit.\n    if self.is_empty() {\n      return ColorFallbackKind::empty();\n    }\n\n    let zeros = 7 - self.bits().leading_zeros();\n    ColorFallbackKind::from_bits_truncate(1 << zeros)\n  }\n\n  pub(crate) fn and_below(&self) -> ColorFallbackKind {\n    if self.is_empty() {\n      return ColorFallbackKind::empty();\n    }\n\n    *self | ColorFallbackKind::from_bits_truncate(self.bits() - 1)\n  }\n\n  pub(crate) fn supports_condition<'i>(&self) -> SupportsCondition<'i> {\n    let s = match *self {\n      ColorFallbackKind::P3 => \"color(display-p3 0 0 0)\",\n      ColorFallbackKind::LAB => \"lab(0% 0 0)\",\n      _ => unreachable!(),\n    };\n\n    SupportsCondition::Declaration {\n      property_id: PropertyId::Color,\n      value: s.into(),\n    }\n  }\n}\n\nimpl CssColor {\n  /// Returns the `currentColor` keyword.\n  pub fn current_color() -> CssColor {\n    CssColor::CurrentColor\n  }\n\n  /// Returns the `transparent` keyword.\n  pub fn transparent() -> CssColor {\n    CssColor::RGBA(RGBA::transparent())\n  }\n\n  /// Converts the color to RGBA.\n  pub fn to_rgb(&self) -> Result<CssColor, ()> {\n    match self {\n      CssColor::LightDark(light, dark) => {\n        Ok(CssColor::LightDark(Box::new(light.to_rgb()?), Box::new(dark.to_rgb()?)))\n      }\n      _ => Ok(RGBA::try_from(self)?.into()),\n    }\n  }\n\n  /// Converts the color to the LAB color space.\n  pub fn to_lab(&self) -> Result<CssColor, ()> {\n    match self {\n      CssColor::LightDark(light, dark) => {\n        Ok(CssColor::LightDark(Box::new(light.to_lab()?), Box::new(dark.to_lab()?)))\n      }\n      _ => Ok(LAB::try_from(self)?.into()),\n    }\n  }\n\n  /// Converts the color to the P3 color space.\n  pub fn to_p3(&self) -> Result<CssColor, ()> {\n    match self {\n      CssColor::LightDark(light, dark) => {\n        Ok(CssColor::LightDark(Box::new(light.to_p3()?), Box::new(dark.to_p3()?)))\n      }\n      _ => Ok(P3::try_from(self)?.into()),\n    }\n  }\n\n  pub(crate) fn get_possible_fallbacks(&self, targets: Targets) -> ColorFallbackKind {\n    // Fallbacks occur in levels: Oklab -> Lab -> P3 -> RGB. We start with all levels\n    // below and including the authored color space, and remove the ones that aren't\n    // compatible with our browser targets.\n    let mut fallbacks = match self {\n      CssColor::CurrentColor | CssColor::RGBA(_) | CssColor::Float(..) | CssColor::System(..) => {\n        return ColorFallbackKind::empty()\n      }\n      CssColor::LAB(lab) => match &**lab {\n        LABColor::LAB(..) | LABColor::LCH(..) if should_compile!(targets, LabColors) => {\n          ColorFallbackKind::LAB.and_below()\n        }\n        LABColor::OKLAB(..) | LABColor::OKLCH(..) if should_compile!(targets, OklabColors) => {\n          ColorFallbackKind::OKLAB.and_below()\n        }\n        _ => return ColorFallbackKind::empty(),\n      },\n      CssColor::Predefined(predefined) => match &**predefined {\n        PredefinedColor::DisplayP3(..) if should_compile!(targets, P3Colors) => ColorFallbackKind::P3.and_below(),\n        _ if should_compile!(targets, ColorFunction) => ColorFallbackKind::LAB.and_below(),\n        _ => return ColorFallbackKind::empty(),\n      },\n      CssColor::LightDark(light, dark) => {\n        return light.get_possible_fallbacks(targets) | dark.get_possible_fallbacks(targets);\n      }\n    };\n\n    if fallbacks.contains(ColorFallbackKind::OKLAB) {\n      if !should_compile!(targets, OklabColors) {\n        fallbacks.remove(ColorFallbackKind::LAB.and_below());\n      }\n    }\n\n    if fallbacks.contains(ColorFallbackKind::LAB) {\n      if !should_compile!(targets, LabColors) {\n        fallbacks.remove(ColorFallbackKind::P3.and_below());\n      } else if targets\n        .browsers\n        .map(|targets| Feature::LabColors.is_partially_compatible(targets))\n        .unwrap_or(false)\n      {\n        // We don't need P3 if Lab is supported by some of our targets.\n        // No browser implements Lab but not P3.\n        fallbacks.remove(ColorFallbackKind::P3);\n      }\n    }\n\n    if fallbacks.contains(ColorFallbackKind::P3) {\n      if !should_compile!(targets, P3Colors) {\n        fallbacks.remove(ColorFallbackKind::RGB);\n      } else if fallbacks.highest() != ColorFallbackKind::P3\n        && !targets\n          .browsers\n          .map(|targets| Feature::P3Colors.is_partially_compatible(targets))\n          .unwrap_or(false)\n      {\n        // Remove P3 if it isn't supported by any targets, and wasn't the\n        // original authored color.\n        fallbacks.remove(ColorFallbackKind::P3);\n      }\n    }\n\n    fallbacks\n  }\n\n  /// Returns the color fallback types needed for the given browser targets.\n  pub fn get_necessary_fallbacks(&self, targets: Targets) -> ColorFallbackKind {\n    // Get the full set of possible fallbacks, and remove the highest one, which\n    // will replace the original declaration. The remaining fallbacks need to be added.\n    let fallbacks = self.get_possible_fallbacks(targets);\n    fallbacks - fallbacks.highest()\n  }\n\n  /// Returns a fallback color for the given fallback type.\n  pub fn get_fallback(&self, kind: ColorFallbackKind) -> CssColor {\n    if matches!(self, CssColor::RGBA(_)) {\n      return self.clone();\n    }\n\n    match kind {\n      ColorFallbackKind::RGB => self.to_rgb().unwrap(),\n      ColorFallbackKind::P3 => self.to_p3().unwrap(),\n      ColorFallbackKind::LAB => self.to_lab().unwrap(),\n      _ => unreachable!(),\n    }\n  }\n\n  pub(crate) fn get_features(&self) -> Features {\n    let mut features = Features::empty();\n    match self {\n      CssColor::LAB(labcolor) => match &**labcolor {\n        LABColor::LAB(_) | LABColor::LCH(_) => {\n          features |= Features::LabColors;\n        }\n        LABColor::OKLAB(_) | LABColor::OKLCH(_) => {\n          features |= Features::OklabColors;\n        }\n      },\n      CssColor::Predefined(predefined_color) => {\n        features |= Features::ColorFunction;\n        match &**predefined_color {\n          PredefinedColor::DisplayP3(_) => {\n            features |= Features::P3Colors;\n          }\n          _ => {}\n        }\n      }\n      CssColor::Float(_) => {\n        features |= Features::SpaceSeparatedColorNotation;\n      }\n      CssColor::LightDark(light, dark) => {\n        features |= Features::LightDark;\n        features |= light.get_features();\n        features |= dark.get_features();\n      }\n      _ => {}\n    }\n\n    features\n  }\n}\n\nimpl IsCompatible for CssColor {\n  fn is_compatible(&self, browsers: Browsers) -> bool {\n    match self {\n      CssColor::CurrentColor | CssColor::RGBA(_) | CssColor::Float(..) => true,\n      CssColor::LAB(lab) => match &**lab {\n        LABColor::LAB(..) | LABColor::LCH(..) => Feature::LabColors.is_compatible(browsers),\n        LABColor::OKLAB(..) | LABColor::OKLCH(..) => Feature::OklabColors.is_compatible(browsers),\n      },\n      CssColor::Predefined(predefined) => match &**predefined {\n        PredefinedColor::DisplayP3(..) => Feature::P3Colors.is_compatible(browsers),\n        _ => Feature::ColorFunction.is_compatible(browsers),\n      },\n      CssColor::LightDark(light, dark) => {\n        Feature::LightDark.is_compatible(browsers) && light.is_compatible(browsers) && dark.is_compatible(browsers)\n      }\n      CssColor::System(system) => system.is_compatible(browsers),\n    }\n  }\n}\n\nimpl FallbackValues for CssColor {\n  fn get_fallbacks(&mut self, targets: Targets) -> Vec<CssColor> {\n    let fallbacks = self.get_necessary_fallbacks(targets);\n\n    let mut res = Vec::new();\n    if fallbacks.contains(ColorFallbackKind::RGB) {\n      res.push(self.to_rgb().unwrap());\n    }\n\n    if fallbacks.contains(ColorFallbackKind::P3) {\n      res.push(self.to_p3().unwrap());\n    }\n\n    if fallbacks.contains(ColorFallbackKind::LAB) {\n      *self = self.to_lab().unwrap();\n    }\n\n    res\n  }\n}\n\nimpl Default for CssColor {\n  fn default() -> CssColor {\n    CssColor::transparent()\n  }\n}\n\nimpl<'i> Parse<'i> for CssColor {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let location = input.current_source_location();\n    let token = input.next()?;\n    match *token {\n      Token::Hash(ref value) | Token::IDHash(ref value) => parse_hash_color(value.as_bytes())\n        .map(|(r, g, b, a)| CssColor::RGBA(RGBA::new(r, g, b, a)))\n        .map_err(|_| location.new_unexpected_token_error(token.clone())),\n      Token::Ident(ref value) => Ok(match_ignore_ascii_case! { value,\n        \"currentcolor\" => CssColor::CurrentColor,\n        \"transparent\" => CssColor::RGBA(RGBA::transparent()),\n        _ => {\n          if let Ok((r, g, b)) = parse_named_color(value) {\n            CssColor::RGBA(RGBA { red: r, green: g, blue: b, alpha: 255 })\n          } else if let Ok(system_color) = SystemColor::parse_string(&value) {\n            CssColor::System(system_color)\n          } else {\n            return Err(location.new_unexpected_token_error(token.clone()))\n          }\n        }\n      }),\n      Token::Function(ref name) => parse_color_function(location, name.clone(), input),\n      _ => Err(location.new_unexpected_token_error(token.clone())),\n    }\n  }\n}\n\nimpl ToCss for CssColor {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      CssColor::CurrentColor => dest.write_str(\"currentColor\"),\n      CssColor::RGBA(color) => {\n        if color.alpha == 255 {\n          let hex: u32 = ((color.red as u32) << 16) | ((color.green as u32) << 8) | (color.blue as u32);\n          if let Some(name) = short_color_name(hex) {\n            return dest.write_str(name);\n          }\n\n          let compact = compact_hex(hex);\n          if hex == expand_hex(compact) {\n            write!(dest, \"#{:03x}\", compact)?;\n          } else {\n            write!(dest, \"#{:06x}\", hex)?;\n          }\n        } else {\n          // If the #rrggbbaa syntax is not supported by the browser targets, output rgba()\n          if should_compile!(dest.targets.current, HexAlphaColors) {\n            // If the browser doesn't support `#rrggbbaa` color syntax, it is converted to `transparent` when compressed(minify = true).\n            // https://www.w3.org/TR/css-color-4/#transparent-black\n            if dest.minify && color.red == 0 && color.green == 0 && color.blue == 0 && color.alpha == 0 {\n              return dest.write_str(\"transparent\");\n            } else {\n              dest.write_str(\"rgba(\")?;\n              write!(dest, \"{}\", color.red)?;\n              dest.delim(',', false)?;\n              write!(dest, \"{}\", color.green)?;\n              dest.delim(',', false)?;\n              write!(dest, \"{}\", color.blue)?;\n              dest.delim(',', false)?;\n\n              // Try first with two decimal places, then with three.\n              let mut rounded_alpha = (color.alpha_f32() * 100.0).round() / 100.0;\n              let clamped = (rounded_alpha * 255.0).round().max(0.).min(255.0) as u8;\n              if clamped != color.alpha {\n                rounded_alpha = (color.alpha_f32() * 1000.).round() / 1000.;\n              }\n\n              rounded_alpha.to_css(dest)?;\n              dest.write_char(')')?;\n              return Ok(());\n            }\n          }\n\n          let hex: u32 = ((color.red as u32) << 24)\n            | ((color.green as u32) << 16)\n            | ((color.blue as u32) << 8)\n            | (color.alpha as u32);\n          let compact = compact_hex(hex);\n          if hex == expand_hex(compact) {\n            write!(dest, \"#{:04x}\", compact)?;\n          } else {\n            write!(dest, \"#{:08x}\", hex)?;\n          }\n        }\n        Ok(())\n      }\n      CssColor::LAB(lab) => match &**lab {\n        LABColor::LAB(lab) => write_components(\"lab\", lab.l / 100.0, lab.a, lab.b, lab.alpha, dest),\n        LABColor::LCH(lch) => write_components(\"lch\", lch.l / 100.0, lch.c, lch.h, lch.alpha, dest),\n        LABColor::OKLAB(lab) => write_components(\"oklab\", lab.l, lab.a, lab.b, lab.alpha, dest),\n        LABColor::OKLCH(lch) => write_components(\"oklch\", lch.l, lch.c, lch.h, lch.alpha, dest),\n      },\n      CssColor::Predefined(predefined) => write_predefined(predefined, dest),\n      CssColor::Float(float) => {\n        // Serialize as hex.\n        let rgb = RGB::from(**float);\n        CssColor::from(rgb).to_css(dest)\n      }\n      CssColor::LightDark(light, dark) => {\n        if should_compile!(dest.targets.current, LightDark) {\n          dest.write_str(\"var(--lightningcss-light\")?;\n          dest.delim(',', false)?;\n          light.to_css(dest)?;\n          dest.write_char(')')?;\n          dest.whitespace()?;\n          dest.write_str(\"var(--lightningcss-dark\")?;\n          dest.delim(',', false)?;\n          dark.to_css(dest)?;\n          return dest.write_char(')');\n        }\n\n        dest.write_str(\"light-dark(\")?;\n        light.to_css(dest)?;\n        dest.delim(',', false)?;\n        dark.to_css(dest)?;\n        dest.write_char(')')\n      }\n      CssColor::System(system) => system.to_css(dest),\n    }\n  }\n}\n\n// From esbuild: https://github.com/evanw/esbuild/blob/18e13bdfdca5cd3c7a2fae1a8bd739f8f891572c/internal/css_parser/css_decls_color.go#L218\n// 0xAABBCCDD => 0xABCD\nfn compact_hex(v: u32) -> u32 {\n  return ((v & 0x0FF00000) >> 12) | ((v & 0x00000FF0) >> 4);\n}\n\n// 0xABCD => 0xAABBCCDD\nfn expand_hex(v: u32) -> u32 {\n  return ((v & 0xF000) << 16) | ((v & 0xFF00) << 12) | ((v & 0x0FF0) << 8) | ((v & 0x00FF) << 4) | (v & 0x000F);\n}\n\nfn short_color_name(v: u32) -> Option<&'static str> {\n  // These names are shorter than their hex codes\n  let s = match v {\n    0x000080 => \"navy\",\n    0x008000 => \"green\",\n    0x008080 => \"teal\",\n    0x4b0082 => \"indigo\",\n    0x800000 => \"maroon\",\n    0x800080 => \"purple\",\n    0x808000 => \"olive\",\n    0x808080 => \"gray\",\n    0xa0522d => \"sienna\",\n    0xa52a2a => \"brown\",\n    0xc0c0c0 => \"silver\",\n    0xcd853f => \"peru\",\n    0xd2b48c => \"tan\",\n    0xda70d6 => \"orchid\",\n    0xdda0dd => \"plum\",\n    0xee82ee => \"violet\",\n    0xf0e68c => \"khaki\",\n    0xf0ffff => \"azure\",\n    0xf5deb3 => \"wheat\",\n    0xf5f5dc => \"beige\",\n    0xfa8072 => \"salmon\",\n    0xfaf0e6 => \"linen\",\n    0xff0000 => \"red\",\n    0xff6347 => \"tomato\",\n    0xff7f50 => \"coral\",\n    0xffa500 => \"orange\",\n    0xffc0cb => \"pink\",\n    0xffd700 => \"gold\",\n    0xffe4c4 => \"bisque\",\n    0xfffafa => \"snow\",\n    0xfffff0 => \"ivory\",\n    _ => return None,\n  };\n\n  Some(s)\n}\n\nstruct RelativeComponentParser {\n  names: (&'static str, &'static str, &'static str),\n  components: (f32, f32, f32, f32),\n  types: (ChannelType, ChannelType, ChannelType),\n}\n\nimpl RelativeComponentParser {\n  fn new<T: ColorSpace>(color: &T) -> Self {\n    Self {\n      names: color.channels(),\n      components: color.components(),\n      types: color.types(),\n    }\n  }\n\n  fn get_ident(&self, ident: &str, allowed_types: ChannelType) -> Option<(f32, ChannelType)> {\n    if ident.eq_ignore_ascii_case(self.names.0) && allowed_types.intersects(self.types.0) {\n      return Some((self.components.0, self.types.0));\n    }\n\n    if ident.eq_ignore_ascii_case(self.names.1) && allowed_types.intersects(self.types.1) {\n      return Some((self.components.1, self.types.1));\n    }\n\n    if ident.eq_ignore_ascii_case(self.names.2) && allowed_types.intersects(self.types.2) {\n      return Some((self.components.2, self.types.2));\n    }\n\n    if ident.eq_ignore_ascii_case(\"alpha\")\n      && allowed_types.intersects(ChannelType::Number | ChannelType::Percentage)\n    {\n      return Some((self.components.3, ChannelType::Number));\n    }\n\n    None\n  }\n\n  fn parse_ident<'i, 't>(\n    &self,\n    input: &mut Parser<'i, 't>,\n    allowed_types: ChannelType,\n  ) -> Result<(f32, ChannelType), ParseError<'i, ParserError<'i>>> {\n    match self.get_ident(input.expect_ident()?.as_ref(), allowed_types) {\n      Some(v) => Ok(v),\n      None => Err(input.new_error_for_next_token()),\n    }\n  }\n}\n\nimpl<'i> ColorParser<'i> for RelativeComponentParser {\n  type Output = cssparser_color::Color;\n  type Error = ParserError<'i>;\n\n  fn parse_angle_or_number<'t>(\n    &self,\n    input: &mut Parser<'i, 't>,\n  ) -> Result<AngleOrNumber, ParseError<'i, Self::Error>> {\n    if let Ok((value, ty)) =\n      input.try_parse(|input| self.parse_ident(input, ChannelType::Angle | ChannelType::Number))\n    {\n      return Ok(match ty {\n        ChannelType::Angle => AngleOrNumber::Angle { degrees: value },\n        ChannelType::Number => AngleOrNumber::Number { value },\n        _ => unreachable!(),\n      });\n    }\n\n    if let Ok(value) = input.try_parse(|input| -> Result<AngleOrNumber, ParseError<'i, ParserError<'i>>> {\n      match Calc::parse_with(input, |ident| {\n        self\n          .get_ident(ident, ChannelType::Angle | ChannelType::Number)\n          .map(|(value, ty)| match ty {\n            ChannelType::Angle => Calc::Value(Box::new(Angle::Deg(value))),\n            ChannelType::Number => Calc::Number(value),\n            _ => unreachable!(),\n          })\n      }) {\n        Ok(Calc::Value(v)) => Ok(AngleOrNumber::Angle {\n          degrees: v.to_degrees(),\n        }),\n        Ok(Calc::Number(v)) => Ok(AngleOrNumber::Number { value: v }),\n        _ => Err(input.new_custom_error(ParserError::InvalidValue)),\n      }\n    }) {\n      return Ok(value);\n    }\n\n    Err(input.new_error_for_next_token())\n  }\n\n  fn parse_number<'t>(&self, input: &mut Parser<'i, 't>) -> Result<f32, ParseError<'i, Self::Error>> {\n    if let Ok((value, _)) = input.try_parse(|input| self.parse_ident(input, ChannelType::Number)) {\n      return Ok(value);\n    }\n\n    match Calc::parse_with(input, |ident| {\n      self.get_ident(ident, ChannelType::Number).map(|(v, _)| Calc::Number(v))\n    }) {\n      Ok(Calc::Value(v)) => Ok(*v),\n      Ok(Calc::Number(n)) => Ok(n),\n      _ => Err(input.new_error_for_next_token()),\n    }\n  }\n\n  fn parse_percentage<'t>(&self, input: &mut Parser<'i, 't>) -> Result<f32, ParseError<'i, Self::Error>> {\n    if let Ok((value, _)) = input.try_parse(|input| self.parse_ident(input, ChannelType::Percentage)) {\n      return Ok(value);\n    }\n\n    if let Ok(value) = input.try_parse(|input| -> Result<Percentage, ParseError<'i, ParserError<'i>>> {\n      match Calc::parse_with(input, |ident| {\n        self\n          .get_ident(ident, ChannelType::Percentage)\n          .map(|(v, _)| Calc::Value(Box::new(Percentage(v))))\n      }) {\n        Ok(Calc::Value(v)) => Ok(*v),\n        _ => Err(input.new_custom_error(ParserError::InvalidValue)),\n      }\n    }) {\n      return Ok(value.0);\n    }\n\n    Err(input.new_error_for_next_token())\n  }\n\n  fn parse_number_or_percentage<'t>(\n    &self,\n    input: &mut Parser<'i, 't>,\n  ) -> Result<NumberOrPercentage, ParseError<'i, Self::Error>> {\n    if let Ok((value, ty)) =\n      input.try_parse(|input| self.parse_ident(input, ChannelType::Percentage | ChannelType::Number))\n    {\n      return Ok(match ty {\n        ChannelType::Percentage => NumberOrPercentage::Percentage { unit_value: value },\n        ChannelType::Number => NumberOrPercentage::Number { value },\n        _ => unreachable!(),\n      });\n    }\n\n    if let Ok(value) = input.try_parse(|input| -> Result<NumberOrPercentage, ParseError<'i, ParserError<'i>>> {\n      match Calc::parse_with(input, |ident| {\n        self\n          .get_ident(ident, ChannelType::Percentage | ChannelType::Number)\n          .map(|(value, ty)| match ty {\n            ChannelType::Percentage => Calc::Value(Box::new(Percentage(value))),\n            ChannelType::Number => Calc::Number(value),\n            _ => unreachable!(),\n          })\n      }) {\n        Ok(Calc::Value(v)) => Ok(NumberOrPercentage::Percentage { unit_value: v.0 }),\n        Ok(Calc::Number(v)) => Ok(NumberOrPercentage::Number { value: v }),\n        _ => Err(input.new_custom_error(ParserError::InvalidValue)),\n      }\n    }) {\n      return Ok(value);\n    }\n\n    Err(input.new_error_for_next_token())\n  }\n}\n\npub(crate) trait LightDarkColor {\n  fn light_dark(light: Self, dark: Self) -> Self;\n}\n\nimpl LightDarkColor for CssColor {\n  #[inline]\n  fn light_dark(light: Self, dark: Self) -> Self {\n    CssColor::LightDark(Box::new(light), Box::new(dark))\n  }\n}\n\npub(crate) struct ComponentParser {\n  pub allow_none: bool,\n  from: Option<RelativeComponentParser>,\n}\n\nimpl ComponentParser {\n  pub fn new(allow_none: bool) -> Self {\n    Self { allow_none, from: None }\n  }\n\n  pub fn parse_relative<\n    'i,\n    't,\n    T: TryFrom<CssColor> + ColorSpace,\n    C: LightDarkColor,\n    P: Fn(&mut Parser<'i, 't>, &mut Self) -> Result<C, ParseError<'i, ParserError<'i>>>,\n  >(\n    &mut self,\n    input: &mut Parser<'i, 't>,\n    parse: P,\n  ) -> Result<C, ParseError<'i, ParserError<'i>>> {\n    if input.try_parse(|input| input.expect_ident_matching(\"from\")).is_ok() {\n      let from = CssColor::parse(input)?;\n      return self.parse_from::<T, C, P>(from, input, &parse);\n    }\n\n    parse(input, self)\n  }\n\n  fn parse_from<\n    'i,\n    't,\n    T: TryFrom<CssColor> + ColorSpace,\n    C: LightDarkColor,\n    P: Fn(&mut Parser<'i, 't>, &mut Self) -> Result<C, ParseError<'i, ParserError<'i>>>,\n  >(\n    &mut self,\n    from: CssColor,\n    input: &mut Parser<'i, 't>,\n    parse: &P,\n  ) -> Result<C, ParseError<'i, ParserError<'i>>> {\n    if let CssColor::LightDark(light, dark) = from {\n      let state = input.state();\n      let light = self.parse_from::<T, C, P>(*light, input, parse)?;\n      input.reset(&state);\n      let dark = self.parse_from::<T, C, P>(*dark, input, parse)?;\n      return Ok(C::light_dark(light, dark));\n    }\n\n    let from = T::try_from(from)\n      .map_err(|_| input.new_custom_error(ParserError::InvalidValue))?\n      .resolve();\n    self.from = Some(RelativeComponentParser::new(&from));\n\n    parse(input, self)\n  }\n}\n\nimpl<'i> ColorParser<'i> for ComponentParser {\n  type Output = cssparser_color::Color;\n  type Error = ParserError<'i>;\n\n  fn parse_angle_or_number<'t>(\n    &self,\n    input: &mut Parser<'i, 't>,\n  ) -> Result<AngleOrNumber, ParseError<'i, Self::Error>> {\n    if let Some(from) = &self.from {\n      if let Ok(res) = input.try_parse(|input| from.parse_angle_or_number(input)) {\n        return Ok(res);\n      }\n    }\n\n    if let Ok(angle) = input.try_parse(Angle::parse) {\n      Ok(AngleOrNumber::Angle {\n        degrees: angle.to_degrees(),\n      })\n    } else if let Ok(value) = input.try_parse(CSSNumber::parse) {\n      Ok(AngleOrNumber::Number { value })\n    } else if self.allow_none {\n      input.expect_ident_matching(\"none\")?;\n      Ok(AngleOrNumber::Number { value: f32::NAN })\n    } else {\n      Err(input.new_custom_error(ParserError::InvalidValue))\n    }\n  }\n\n  fn parse_number<'t>(&self, input: &mut Parser<'i, 't>) -> Result<f32, ParseError<'i, Self::Error>> {\n    if let Some(from) = &self.from {\n      if let Ok(res) = input.try_parse(|input| from.parse_number(input)) {\n        return Ok(res);\n      }\n    }\n\n    if let Ok(val) = input.try_parse(CSSNumber::parse) {\n      return Ok(val);\n    } else if self.allow_none {\n      input.expect_ident_matching(\"none\")?;\n      Ok(f32::NAN)\n    } else {\n      Err(input.new_custom_error(ParserError::InvalidValue))\n    }\n  }\n\n  fn parse_percentage<'t>(&self, input: &mut Parser<'i, 't>) -> Result<f32, ParseError<'i, Self::Error>> {\n    if let Some(from) = &self.from {\n      if let Ok(res) = input.try_parse(|input| from.parse_percentage(input)) {\n        return Ok(res);\n      }\n    }\n\n    if let Ok(val) = input.try_parse(Percentage::parse) {\n      return Ok(val.0);\n    } else if self.allow_none {\n      input.expect_ident_matching(\"none\")?;\n      Ok(f32::NAN)\n    } else {\n      Err(input.new_custom_error(ParserError::InvalidValue))\n    }\n  }\n\n  fn parse_number_or_percentage<'t>(\n    &self,\n    input: &mut Parser<'i, 't>,\n  ) -> Result<NumberOrPercentage, ParseError<'i, Self::Error>> {\n    if let Some(from) = &self.from {\n      if let Ok(res) = input.try_parse(|input| from.parse_number_or_percentage(input)) {\n        return Ok(res);\n      }\n    }\n\n    if let Ok(value) = input.try_parse(CSSNumber::parse) {\n      Ok(NumberOrPercentage::Number { value })\n    } else if let Ok(value) = input.try_parse(Percentage::parse) {\n      Ok(NumberOrPercentage::Percentage { unit_value: value.0 })\n    } else if self.allow_none {\n      input.expect_ident_matching(\"none\")?;\n      Ok(NumberOrPercentage::Number { value: f32::NAN })\n    } else {\n      Err(input.new_custom_error(ParserError::InvalidValue))\n    }\n  }\n}\n\n// https://www.w3.org/TR/css-color-4/#lab-colors\nfn parse_color_function<'i, 't>(\n  location: SourceLocation,\n  function: CowRcStr<'i>,\n  input: &mut Parser<'i, 't>,\n) -> Result<CssColor, ParseError<'i, ParserError<'i>>> {\n  let mut parser = ComponentParser::new(true);\n\n  match_ignore_ascii_case! {&*function,\n    \"lab\" => {\n      parse_lab::<LAB, _>(input, &mut parser, 100.0, 125.0, |l, a, b, alpha| {\n        LABColor::LAB(LAB { l, a, b, alpha })\n      })\n    },\n    \"oklab\" => {\n      parse_lab::<OKLAB, _>(input, &mut parser, 1.0, 0.4, |l, a, b, alpha| {\n        LABColor::OKLAB(OKLAB { l, a, b, alpha })\n      })\n    },\n    \"lch\" => {\n      parse_lch::<LCH, _>(input, &mut parser, 100.0, 150.0, |l, c, h, alpha| {\n        LABColor::LCH(LCH { l, c, h, alpha })\n      })\n    },\n    \"oklch\" => {\n      parse_lch::<OKLCH, _>(input, &mut parser, 1.0, 0.4, |l, c, h, alpha| {\n        LABColor::OKLCH(OKLCH { l, c, h, alpha })\n      })\n    },\n    \"color\" => {\n      let predefined = parse_predefined(input, &mut parser)?;\n      Ok(predefined)\n    },\n    \"hsl\" | \"hsla\" => {\n      parse_hsl_hwb::<HSL, _>(input, &mut parser, true, |h, s, l, a| {\n        let hsl = HSL { h, s, l, alpha: a };\n        if !h.is_nan() && !s.is_nan() && !l.is_nan() && !a.is_nan() {\n          CssColor::RGBA(hsl.into())\n        } else {\n          CssColor::Float(Box::new(FloatColor::HSL(hsl)))\n        }\n      })\n    },\n    \"hwb\" => {\n      parse_hsl_hwb::<HWB, _>(input, &mut parser, false, |h, w, b, a| {\n        let hwb = HWB { h, w, b, alpha: a };\n        if !h.is_nan() && !w.is_nan() && !b.is_nan() && !a.is_nan() {\n          CssColor::RGBA(hwb.into())\n        } else {\n          CssColor::Float(Box::new(FloatColor::HWB(hwb)))\n        }\n      })\n    },\n    \"rgb\" | \"rgba\" => {\n       parse_rgb(input, &mut parser)\n    },\n    \"color-mix\" => {\n      input.parse_nested_block(parse_color_mix)\n    },\n    \"light-dark\" => {\n      input.parse_nested_block(|input| {\n        let light = match CssColor::parse(input)? {\n          CssColor::LightDark(light, _) => light,\n          light => Box::new(light)\n        };\n        input.expect_comma()?;\n        let dark = match CssColor::parse(input)? {\n          CssColor::LightDark(_, dark) => dark,\n          dark => Box::new(dark)\n        };\n        Ok(CssColor::LightDark(light, dark))\n      })\n    },\n    _ => Err(location.new_unexpected_token_error(\n      cssparser::Token::Ident(function.clone())\n    ))\n  }\n}\n\n/// Parses the lab() and oklab() functions.\n#[inline]\nfn parse_lab<'i, 't, T: TryFrom<CssColor> + ColorSpace, F: Fn(f32, f32, f32, f32) -> LABColor>(\n  input: &mut Parser<'i, 't>,\n  parser: &mut ComponentParser,\n  l_basis: f32,\n  ab_basis: f32,\n  f: F,\n) -> Result<CssColor, ParseError<'i, ParserError<'i>>> {\n  // https://www.w3.org/TR/css-color-4/#funcdef-lab\n  input.parse_nested_block(|input| {\n    parser.parse_relative::<T, _, _>(input, |input, parser| {\n      // f32::max() does not propagate NaN, so use clamp for now until f32::maximum() is stable.\n      let l = parse_number_or_percentage(input, parser, l_basis)?.clamp(0.0, f32::MAX);\n      let a = parse_number_or_percentage(input, parser, ab_basis)?;\n      let b = parse_number_or_percentage(input, parser, ab_basis)?;\n      let alpha = parse_alpha(input, parser)?;\n      let lab = f(l, a, b, alpha);\n\n      Ok(CssColor::LAB(Box::new(lab)))\n    })\n  })\n}\n\n/// Parses the lch() and oklch() functions.\n#[inline]\nfn parse_lch<'i, 't, T: TryFrom<CssColor> + ColorSpace, F: Fn(f32, f32, f32, f32) -> LABColor>(\n  input: &mut Parser<'i, 't>,\n  parser: &mut ComponentParser,\n  l_basis: f32,\n  c_basis: f32,\n  f: F,\n) -> Result<CssColor, ParseError<'i, ParserError<'i>>> {\n  // https://www.w3.org/TR/css-color-4/#funcdef-lch\n  input.parse_nested_block(|input| {\n    parser.parse_relative::<T, _, _>(input, |input, parser| {\n      if let Some(from) = &mut parser.from {\n        // Relative angles should be normalized.\n        // https://www.w3.org/TR/css-color-5/#relative-LCH\n        from.components.2 %= 360.0;\n        if from.components.2 < 0.0 {\n          from.components.2 += 360.0;\n        }\n      }\n\n      let l = parse_number_or_percentage(input, parser, l_basis)?.clamp(0.0, f32::MAX);\n      let c = parse_number_or_percentage(input, parser, c_basis)?.clamp(0.0, f32::MAX);\n      let h = parse_angle_or_number(input, parser)?;\n      let alpha = parse_alpha(input, parser)?;\n      let lab = f(l, c, h, alpha);\n\n      Ok(CssColor::LAB(Box::new(lab)))\n    })\n  })\n}\n\n#[inline]\nfn parse_predefined<'i, 't>(\n  input: &mut Parser<'i, 't>,\n  parser: &mut ComponentParser,\n) -> Result<CssColor, ParseError<'i, ParserError<'i>>> {\n  // https://www.w3.org/TR/css-color-4/#color-function\n  let res = input.parse_nested_block(|input| {\n    let from = if input.try_parse(|input| input.expect_ident_matching(\"from\")).is_ok() {\n      Some(CssColor::parse(input)?)\n    } else {\n      None\n    };\n\n    let colorspace = input.expect_ident_cloned()?;\n\n    if let Some(CssColor::LightDark(light, dark)) = from {\n      let state = input.state();\n      let light = parse_predefined_relative(input, parser, &colorspace, Some(&*light))?;\n      input.reset(&state);\n      let dark = parse_predefined_relative(input, parser, &colorspace, Some(&*dark))?;\n      return Ok(CssColor::LightDark(Box::new(light), Box::new(dark)));\n    }\n\n    parse_predefined_relative(input, parser, &colorspace, from.as_ref())\n  })?;\n\n  Ok(res)\n}\n\n#[inline]\nfn parse_predefined_relative<'i, 't>(\n  input: &mut Parser<'i, 't>,\n  parser: &mut ComponentParser,\n  colorspace: &CowRcStr<'i>,\n  from: Option<&CssColor>,\n) -> Result<CssColor, ParseError<'i, ParserError<'i>>> {\n  let location = input.current_source_location();\n  if let Some(from) = from {\n    let handle_error = |_| input.new_custom_error(ParserError::InvalidValue);\n    parser.from = Some(match_ignore_ascii_case! { &*&colorspace,\n      \"srgb\" => RelativeComponentParser::new(&SRGB::try_from(from).map_err(handle_error)?.resolve_missing()),\n      \"srgb-linear\" => RelativeComponentParser::new(&SRGBLinear::try_from(from).map_err(handle_error)?.resolve_missing()),\n      \"display-p3\" => RelativeComponentParser::new(&P3::try_from(from).map_err(handle_error)?.resolve_missing()),\n      \"a98-rgb\" => RelativeComponentParser::new(&A98::try_from(from).map_err(handle_error)?.resolve_missing()),\n      \"prophoto-rgb\" => RelativeComponentParser::new(&ProPhoto::try_from(from).map_err(handle_error)?.resolve_missing()),\n      \"rec2020\" => RelativeComponentParser::new(&Rec2020::try_from(from).map_err(handle_error)?.resolve_missing()),\n      \"xyz-d50\" => RelativeComponentParser::new(&XYZd50::try_from(from).map_err(handle_error)?.resolve_missing()),\n      \"xyz\" | \"xyz-d65\" => RelativeComponentParser::new(&XYZd65::try_from(from).map_err(handle_error)?.resolve_missing()),\n      _ => return Err(location.new_unexpected_token_error(\n        cssparser::Token::Ident(colorspace.clone())\n      ))\n    });\n  }\n\n  // Out of gamut values should not be clamped, i.e. values < 0 or > 1 should be preserved.\n  // The browser will gamut-map the color for the target device that it is rendered on.\n  let a = input.try_parse(|input| parse_number_or_percentage(input, parser, 1.0))?;\n  let b = input.try_parse(|input| parse_number_or_percentage(input, parser, 1.0))?;\n  let c = input.try_parse(|input| parse_number_or_percentage(input, parser, 1.0))?;\n  let alpha = parse_alpha(input, parser)?;\n\n  let res = match_ignore_ascii_case! { &*&colorspace,\n    \"srgb\" => PredefinedColor::SRGB(SRGB { r: a, g: b, b: c, alpha }),\n    \"srgb-linear\" => PredefinedColor::SRGBLinear(SRGBLinear { r: a, g: b, b: c, alpha }),\n    \"display-p3\" => PredefinedColor::DisplayP3(P3 { r: a, g: b, b: c, alpha }),\n    \"a98-rgb\" => PredefinedColor::A98(A98 { r: a, g: b, b: c, alpha }),\n    \"prophoto-rgb\" => PredefinedColor::ProPhoto(ProPhoto { r: a, g: b, b: c, alpha }),\n    \"rec2020\" => PredefinedColor::Rec2020(Rec2020 { r: a, g: b, b: c, alpha }),\n    \"xyz-d50\" => PredefinedColor::XYZd50(XYZd50 { x: a, y: b, z: c, alpha}),\n    \"xyz\" | \"xyz-d65\" => PredefinedColor::XYZd65(XYZd65 { x: a, y: b, z: c, alpha }),\n    _ => return Err(location.new_unexpected_token_error(\n      cssparser::Token::Ident(colorspace.clone())\n    ))\n  };\n\n  Ok(CssColor::Predefined(Box::new(res)))\n}\n\n/// Parses the hsl() and hwb() functions.\n/// The results of this function are stored as floating point if there are any `none` components.\n#[inline]\nfn parse_hsl_hwb<'i, 't, T: TryFrom<CssColor> + ColorSpace, F: Fn(f32, f32, f32, f32) -> CssColor>(\n  input: &mut Parser<'i, 't>,\n  parser: &mut ComponentParser,\n  allows_legacy: bool,\n  f: F,\n) -> Result<CssColor, ParseError<'i, ParserError<'i>>> {\n  // https://drafts.csswg.org/css-color-4/#the-hsl-notation\n  input.parse_nested_block(|input| {\n    parser.parse_relative::<T, _, _>(input, |input, parser| {\n      let (h, a, b, is_legacy) = parse_hsl_hwb_components::<T>(input, parser, allows_legacy)?;\n      let alpha = if is_legacy {\n        parse_legacy_alpha(input, parser)?\n      } else {\n        parse_alpha(input, parser)?\n      };\n\n      Ok(f(h, a, b, alpha))\n    })\n  })\n}\n\n#[inline]\npub(crate) fn parse_hsl_hwb_components<'i, 't, T: TryFrom<CssColor> + ColorSpace>(\n  input: &mut Parser<'i, 't>,\n  parser: &mut ComponentParser,\n  allows_legacy: bool,\n) -> Result<(f32, f32, f32, bool), ParseError<'i, ParserError<'i>>> {\n  let h = parse_angle_or_number(input, parser)?;\n  let is_legacy_syntax =\n    allows_legacy && parser.from.is_none() && !h.is_nan() && input.try_parse(|p| p.expect_comma()).is_ok();\n  let a = parse_number_or_percentage(input, parser, 100.0)?.clamp(0.0, 100.0);\n  if is_legacy_syntax {\n    input.expect_comma()?;\n  }\n  let b = parse_number_or_percentage(input, parser, 100.0)?.clamp(0.0, 100.0);\n  if is_legacy_syntax && (a.is_nan() || b.is_nan()) {\n    return Err(input.new_custom_error(ParserError::InvalidValue));\n  }\n  Ok((h, a, b, is_legacy_syntax))\n}\n\n#[inline]\nfn parse_rgb<'i, 't>(\n  input: &mut Parser<'i, 't>,\n  parser: &mut ComponentParser,\n) -> Result<CssColor, ParseError<'i, ParserError<'i>>> {\n  // https://drafts.csswg.org/css-color-4/#rgb-functions\n  input.parse_nested_block(|input| {\n    parser.parse_relative::<RGB, _, _>(input, |input, parser| {\n      let (r, g, b, is_legacy) = parse_rgb_components(input, parser)?;\n      let alpha = if is_legacy {\n        parse_legacy_alpha(input, parser)?\n      } else {\n        parse_alpha(input, parser)?\n      };\n\n      if !r.is_nan() && !g.is_nan() && !b.is_nan() && !alpha.is_nan() {\n        if is_legacy {\n          Ok(CssColor::RGBA(RGBA::new(r as u8, g as u8, b as u8, alpha)))\n        } else {\n          Ok(CssColor::RGBA(RGBA::from_floats(\n            r / 255.0,\n            g / 255.0,\n            b / 255.0,\n            alpha,\n          )))\n        }\n      } else {\n        Ok(CssColor::Float(Box::new(FloatColor::RGB(RGB { r, g, b, alpha }))))\n      }\n    })\n  })\n}\n\n#[inline]\npub(crate) fn parse_rgb_components<'i, 't>(\n  input: &mut Parser<'i, 't>,\n  parser: &mut ComponentParser,\n) -> Result<(f32, f32, f32, bool), ParseError<'i, ParserError<'i>>> {\n  let red = parser.parse_number_or_percentage(input)?;\n  let is_legacy_syntax =\n    parser.from.is_none() && !red.unit_value().is_nan() && input.try_parse(|p| p.expect_comma()).is_ok();\n  let (r, g, b) = if is_legacy_syntax {\n    match red {\n      NumberOrPercentage::Number { value } => {\n        let r = value.round().clamp(0.0, 255.0);\n        let g = parser.parse_number(input)?.round().clamp(0.0, 255.0);\n        input.expect_comma()?;\n        let b = parser.parse_number(input)?.round().clamp(0.0, 255.0);\n        (r, g, b)\n      }\n      NumberOrPercentage::Percentage { unit_value } => {\n        let r = (unit_value * 255.0).round().clamp(0.0, 255.0);\n        let g = (parser.parse_percentage(input)? * 255.0).round().clamp(0.0, 255.0);\n        input.expect_comma()?;\n        let b = (parser.parse_percentage(input)? * 255.0).round().clamp(0.0, 255.0);\n        (r, g, b)\n      }\n    }\n  } else {\n    #[inline]\n    fn get_component<'i, 't>(value: NumberOrPercentage) -> f32 {\n      match value {\n        NumberOrPercentage::Number { value } if value.is_nan() => value,\n        NumberOrPercentage::Number { value } => value.round().clamp(0.0, 255.0),\n        NumberOrPercentage::Percentage { unit_value } => (unit_value * 255.0).round().clamp(0.0, 255.0),\n      }\n    }\n\n    let r = get_component(red);\n    let g = get_component(parser.parse_number_or_percentage(input)?);\n    let b = get_component(parser.parse_number_or_percentage(input)?);\n    (r, g, b)\n  };\n\n  if is_legacy_syntax && (g.is_nan() || b.is_nan()) {\n    return Err(input.new_custom_error(ParserError::InvalidValue));\n  }\n  Ok((r, g, b, is_legacy_syntax))\n}\n\n#[inline]\nfn parse_angle_or_number<'i, 't>(\n  input: &mut Parser<'i, 't>,\n  parser: &ComponentParser,\n) -> Result<f32, ParseError<'i, ParserError<'i>>> {\n  Ok(match parser.parse_angle_or_number(input)? {\n    AngleOrNumber::Number { value } => value,\n    AngleOrNumber::Angle { degrees } => degrees,\n  })\n}\n\n#[inline]\nfn parse_number_or_percentage<'i, 't>(\n  input: &mut Parser<'i, 't>,\n  parser: &ComponentParser,\n  percent_basis: f32,\n) -> Result<f32, ParseError<'i, ParserError<'i>>> {\n  Ok(match parser.parse_number_or_percentage(input)? {\n    NumberOrPercentage::Number { value } => value,\n    NumberOrPercentage::Percentage { unit_value } => unit_value * percent_basis,\n  })\n}\n\n#[inline]\nfn parse_alpha<'i, 't>(\n  input: &mut Parser<'i, 't>,\n  parser: &ComponentParser,\n) -> Result<f32, ParseError<'i, ParserError<'i>>> {\n  let res = if input.try_parse(|input| input.expect_delim('/')).is_ok() {\n    parse_number_or_percentage(input, parser, 1.0)?.clamp(0.0, 1.0)\n  } else {\n    1.0\n  };\n  Ok(res)\n}\n\n#[inline]\nfn parse_legacy_alpha<'i, 't>(\n  input: &mut Parser<'i, 't>,\n  parser: &ComponentParser,\n) -> Result<f32, ParseError<'i, ParserError<'i>>> {\n  Ok(if !input.is_exhausted() {\n    input.expect_comma()?;\n    parse_number_or_percentage(input, parser, 1.0)?.clamp(0.0, 1.0)\n  } else {\n    1.0\n  })\n}\n\n#[inline]\nfn write_components<W>(\n  name: &str,\n  a: f32,\n  b: f32,\n  c: f32,\n  alpha: f32,\n  dest: &mut Printer<W>,\n) -> Result<(), PrinterError>\nwhere\n  W: std::fmt::Write,\n{\n  dest.write_str(name)?;\n  dest.write_char('(')?;\n  if a.is_nan() {\n    dest.write_str(\"none\")?;\n  } else {\n    Percentage(a).to_css(dest)?;\n  }\n  dest.write_char(' ')?;\n  write_component(b, dest)?;\n  dest.write_char(' ')?;\n  write_component(c, dest)?;\n  if alpha.is_nan() || (alpha - 1.0).abs() > f32::EPSILON {\n    dest.delim('/', true)?;\n    write_component(alpha, dest)?;\n  }\n\n  dest.write_char(')')\n}\n\n#[inline]\nfn write_component<W>(c: f32, dest: &mut Printer<W>) -> Result<(), PrinterError>\nwhere\n  W: std::fmt::Write,\n{\n  if c.is_nan() {\n    dest.write_str(\"none\")?;\n  } else {\n    c.to_css(dest)?;\n  }\n  Ok(())\n}\n\n#[inline]\nfn write_predefined<W>(predefined: &PredefinedColor, dest: &mut Printer<W>) -> Result<(), PrinterError>\nwhere\n  W: std::fmt::Write,\n{\n  use PredefinedColor::*;\n\n  let (name, a, b, c, alpha) = match predefined {\n    SRGB(rgb) => (\"srgb\", rgb.r, rgb.g, rgb.b, rgb.alpha),\n    SRGBLinear(rgb) => (\"srgb-linear\", rgb.r, rgb.g, rgb.b, rgb.alpha),\n    DisplayP3(rgb) => (\"display-p3\", rgb.r, rgb.g, rgb.b, rgb.alpha),\n    A98(rgb) => (\"a98-rgb\", rgb.r, rgb.g, rgb.b, rgb.alpha),\n    ProPhoto(rgb) => (\"prophoto-rgb\", rgb.r, rgb.g, rgb.b, rgb.alpha),\n    Rec2020(rgb) => (\"rec2020\", rgb.r, rgb.g, rgb.b, rgb.alpha),\n    XYZd50(xyz) => (\"xyz-d50\", xyz.x, xyz.y, xyz.z, xyz.alpha),\n    // \"xyz\" has better compatibility (Safari 15) than \"xyz-d65\", and it is shorter.\n    XYZd65(xyz) => (\"xyz\", xyz.x, xyz.y, xyz.z, xyz.alpha),\n  };\n\n  dest.write_str(\"color(\")?;\n  dest.write_str(name)?;\n  dest.write_char(' ')?;\n  write_component(a, dest)?;\n  dest.write_char(' ')?;\n  write_component(b, dest)?;\n  dest.write_char(' ')?;\n  write_component(c, dest)?;\n\n  if alpha.is_nan() || (alpha - 1.0).abs() > f32::EPSILON {\n    dest.delim('/', true)?;\n    write_component(alpha, dest)?;\n  }\n\n  dest.write_char(')')\n}\n\nbitflags! {\n  /// A channel type for a color space.\n  #[derive(PartialEq, Eq, Clone, Copy)]\n  pub struct ChannelType: u8 {\n    /// Channel represents a percentage.\n    const Percentage = 0b001;\n    /// Channel represents an angle.\n    const Angle = 0b010;\n    /// Channel represents a number.\n    const Number = 0b100;\n  }\n}\n\n/// A trait for color spaces.\npub trait ColorSpace {\n  /// Returns the raw color component values.\n  fn components(&self) -> (f32, f32, f32, f32);\n  /// Returns the channel names for this color space.\n  fn channels(&self) -> (&'static str, &'static str, &'static str);\n  /// Returns the channel types for this color space.\n  fn types(&self) -> (ChannelType, ChannelType, ChannelType);\n  /// Resolves missing color components (e.g. `none` keywords) in the color.\n  fn resolve_missing(&self) -> Self;\n  /// Returns a resolved color by replacing missing (i.e. `none`) components with zero,\n  /// and performing gamut mapping to ensure the color can be represented within the color space.\n  fn resolve(&self) -> Self;\n}\n\nmacro_rules! define_colorspace {\n  (\n    $(#[$outer:meta])*\n    $vis:vis struct $name:ident {\n      $(#[$a_meta: meta])*\n      $a: ident: $at: ident,\n      $(#[$b_meta: meta])*\n      $b: ident: $bt: ident,\n      $(#[$c_meta: meta])*\n      $c: ident: $ct: ident\n    }\n  ) => {\n    $(#[$outer])*\n    #[derive(Debug, Clone, Copy, PartialEq)] #[cfg_attr(feature = \"visitor\", derive(Visit))]\n    #[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n    #[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n    pub struct $name {\n      $(#[$a_meta])*\n      pub $a: f32,\n      $(#[$b_meta])*\n      pub $b: f32,\n      $(#[$c_meta])*\n      pub $c: f32,\n      /// The alpha component.\n      pub alpha: f32,\n    }\n\n    impl ColorSpace for $name {\n      fn components(&self) -> (f32, f32, f32, f32) {\n        (self.$a, self.$b, self.$c, self.alpha)\n      }\n\n      fn channels(&self) -> (&'static str, &'static str, &'static str) {\n        (stringify!($a), stringify!($b), stringify!($c))\n      }\n\n      fn types(&self) -> (ChannelType, ChannelType, ChannelType) {\n        (ChannelType::$at, ChannelType::$bt, ChannelType::$ct)\n      }\n\n      #[inline]\n      fn resolve_missing(&self) -> Self {\n        Self {\n          $a: if self.$a.is_nan() { 0.0 } else { self.$a },\n          $b: if self.$b.is_nan() { 0.0 } else { self.$b },\n          $c: if self.$c.is_nan() { 0.0 } else { self.$c },\n          alpha: if self.alpha.is_nan() { 0.0 } else { self.alpha },\n        }\n      }\n\n      #[inline]\n      fn resolve(&self) -> Self {\n        let mut resolved = self.resolve_missing();\n        if !resolved.in_gamut() {\n          resolved = map_gamut(resolved);\n        }\n        resolved\n      }\n    }\n  };\n}\n\ndefine_colorspace! {\n  /// A color in the [`sRGB`](https://www.w3.org/TR/css-color-4/#predefined-sRGB) color space.\n  pub struct SRGB {\n    /// The red component.\n    r: Number,\n    /// The green component.\n    g: Number,\n    /// The blue component.\n    b: Number\n  }\n}\n\n// Copied from an older version of cssparser.\n/// A color with red, green, blue, and alpha components, in a byte each.\n#[derive(Clone, Copy, PartialEq, Debug)]\npub struct RGBA {\n  /// The red component.\n  pub red: u8,\n  /// The green component.\n  pub green: u8,\n  /// The blue component.\n  pub blue: u8,\n  /// The alpha component.\n  pub alpha: u8,\n}\n\nimpl RGBA {\n  /// Constructs a new RGBA value from float components. It expects the red,\n  /// green, blue and alpha channels in that order, and all values will be\n  /// clamped to the 0.0 ... 1.0 range.\n  #[inline]\n  pub fn from_floats(red: f32, green: f32, blue: f32, alpha: f32) -> Self {\n    Self::new(clamp_unit_f32(red), clamp_unit_f32(green), clamp_unit_f32(blue), alpha)\n  }\n\n  /// Returns a transparent color.\n  #[inline]\n  pub fn transparent() -> Self {\n    Self::new(0, 0, 0, 0.0)\n  }\n\n  /// Same thing, but with `u8` values instead of floats in the 0 to 1 range.\n  #[inline]\n  pub fn new(red: u8, green: u8, blue: u8, alpha: f32) -> Self {\n    RGBA {\n      red,\n      green,\n      blue,\n      alpha: clamp_unit_f32(alpha),\n    }\n  }\n\n  /// Returns the red channel in a floating point number form, from 0 to 1.\n  #[inline]\n  pub fn red_f32(&self) -> f32 {\n    self.red as f32 / 255.0\n  }\n\n  /// Returns the green channel in a floating point number form, from 0 to 1.\n  #[inline]\n  pub fn green_f32(&self) -> f32 {\n    self.green as f32 / 255.0\n  }\n\n  /// Returns the blue channel in a floating point number form, from 0 to 1.\n  #[inline]\n  pub fn blue_f32(&self) -> f32 {\n    self.blue as f32 / 255.0\n  }\n\n  /// Returns the alpha channel in a floating point number form, from 0 to 1.\n  #[inline]\n  pub fn alpha_f32(&self) -> f32 {\n    self.alpha as f32 / 255.0\n  }\n}\n\nfn clamp_unit_f32(val: f32) -> u8 {\n  // Whilst scaling by 256 and flooring would provide\n  // an equal distribution of integers to percentage inputs,\n  // this is not what Gecko does so we instead multiply by 255\n  // and round (adding 0.5 and flooring is equivalent to rounding)\n  //\n  // Chrome does something similar for the alpha value, but not\n  // the rgb values.\n  //\n  // See https://bugzilla.mozilla.org/show_bug.cgi?id=1340484\n  //\n  // Clamping to 256 and rounding after would let 1.0 map to 256, and\n  // `256.0_f32 as u8` is undefined behavior:\n  //\n  // https://github.com/rust-lang/rust/issues/10184\n  clamp_floor_256_f32(val * 255.)\n}\n\nfn clamp_floor_256_f32(val: f32) -> u8 {\n  val.round().max(0.).min(255.) as u8\n}\n\ndefine_colorspace! {\n  /// A color in the [`RGB`](https://w3c.github.io/csswg-drafts/css-color-4/#rgb-functions) color space.\n  /// Components are in the 0-255 range.\n  pub struct RGB {\n    /// The red component.\n    r: Number,\n    /// The green component.\n    g: Number,\n    /// The blue component.\n    b: Number\n  }\n}\n\ndefine_colorspace! {\n  /// A color in the [`sRGB-linear`](https://www.w3.org/TR/css-color-4/#predefined-sRGB-linear) color space.\n  pub struct SRGBLinear {\n    /// The red component.\n    r: Number,\n    /// The green component.\n    g: Number,\n    /// The blue component.\n    b: Number\n  }\n}\n\ndefine_colorspace! {\n  /// A color in the [`display-p3`](https://www.w3.org/TR/css-color-4/#predefined-display-p3) color space.\n  pub struct P3 {\n    /// The red component.\n    r: Number,\n    /// The green component.\n    g: Number,\n    /// The blue component.\n    b: Number\n  }\n}\n\ndefine_colorspace! {\n  /// A color in the [`a98-rgb`](https://www.w3.org/TR/css-color-4/#predefined-a98-rgb) color space.\n  pub struct A98 {\n    /// The red component.\n    r: Number,\n    /// The green component.\n    g: Number,\n    /// The blue component.\n    b: Number\n  }\n}\n\ndefine_colorspace! {\n  /// A color in the [`prophoto-rgb`](https://www.w3.org/TR/css-color-4/#predefined-prophoto-rgb) color space.\n  pub struct ProPhoto {\n    /// The red component.\n    r: Number,\n    /// The green component.\n    g: Number,\n    /// The blue component.\n    b: Number\n  }\n}\n\ndefine_colorspace! {\n  /// A color in the [`rec2020`](https://www.w3.org/TR/css-color-4/#predefined-rec2020) color space.\n  pub struct Rec2020 {\n    /// The red component.\n    r: Number,\n    /// The green component.\n    g: Number,\n    /// The blue component.\n    b: Number\n  }\n}\n\ndefine_colorspace! {\n  /// A color in the [CIE Lab](https://www.w3.org/TR/css-color-4/#cie-lab) color space.\n  pub struct LAB {\n    /// The lightness component.\n    l: Number,\n    /// The a component.\n    a: Number,\n    /// The b component.\n    b: Number\n  }\n}\n\ndefine_colorspace! {\n  /// A color in the [CIE LCH](https://www.w3.org/TR/css-color-4/#cie-lab) color space.\n  pub struct LCH {\n    /// The lightness component.\n    l: Number,\n    /// The chroma component.\n    c: Number,\n    /// The hue component.\n    h: Angle\n  }\n}\n\ndefine_colorspace! {\n  /// A color in the [OKLab](https://www.w3.org/TR/css-color-4/#ok-lab) color space.\n  pub struct OKLAB {\n    /// The lightness component.\n    l: Number,\n    /// The a component.\n    a: Number,\n    /// The b component.\n    b: Number\n  }\n}\n\ndefine_colorspace! {\n  /// A color in the [OKLCH](https://www.w3.org/TR/css-color-4/#ok-lab) color space.\n  pub struct OKLCH {\n    /// The lightness component.\n    l: Number,\n    /// The chroma component.\n    c: Number,\n    /// The hue component.\n    h: Angle\n  }\n}\n\ndefine_colorspace! {\n  /// A color in the [`xyz-d50`](https://www.w3.org/TR/css-color-4/#predefined-xyz) color space.\n  pub struct XYZd50 {\n    /// The x component.\n    x: Number,\n    /// The y component.\n    y: Number,\n    /// The z component.\n    z: Number\n  }\n}\n\ndefine_colorspace! {\n  /// A color in the [`xyz-d65`](https://www.w3.org/TR/css-color-4/#predefined-xyz) color space.\n  pub struct XYZd65 {\n    /// The x component.\n    x: Number,\n    /// The y component.\n    y: Number,\n    /// The z component.\n    z: Number\n  }\n}\n\ndefine_colorspace! {\n  /// A color in the [`hsl`](https://www.w3.org/TR/css-color-4/#the-hsl-notation) color space.\n  pub struct HSL {\n    /// The hue component.\n    h: Angle,\n    /// The saturation component.\n    s: Number,\n    /// The lightness component.\n    l: Number\n  }\n}\n\ndefine_colorspace! {\n  /// A color in the [`hwb`](https://www.w3.org/TR/css-color-4/#the-hwb-notation) color space.\n  pub struct HWB {\n    /// The hue component.\n    h: Angle,\n    /// The whiteness component.\n    w: Number,\n    /// The blackness component.\n    b: Number\n  }\n}\n\nmacro_rules! via {\n  ($t: ident -> $u: ident -> $v: ident) => {\n    impl From<$t> for $v {\n      #[inline]\n      fn from(t: $t) -> $v {\n        let xyz: $u = t.into();\n        xyz.into()\n      }\n    }\n\n    impl From<$v> for $t {\n      #[inline]\n      fn from(t: $v) -> $t {\n        let xyz: $u = t.into();\n        xyz.into()\n      }\n    }\n  };\n}\n\n#[inline]\nfn rectangular_to_polar(l: f32, a: f32, b: f32) -> (f32, f32, f32) {\n  // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L375\n  let mut h = b.atan2(a) * 180.0 / PI;\n  if h < 0.0 {\n    h += 360.0;\n  }\n  let c = (a.powi(2) + b.powi(2)).sqrt();\n  h = h % 360.0;\n  (l, c, h)\n}\n\n#[inline]\nfn polar_to_rectangular(l: f32, c: f32, h: f32) -> (f32, f32, f32) {\n  // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L385\n  let a = c * (h * PI / 180.0).cos();\n  let b = c * (h * PI / 180.0).sin();\n  (l, a, b)\n}\n\nimpl From<LCH> for LAB {\n  fn from(lch: LCH) -> LAB {\n    let lch = lch.resolve_missing();\n    let (l, a, b) = polar_to_rectangular(lch.l, lch.c, lch.h);\n    LAB {\n      l,\n      a,\n      b,\n      alpha: lch.alpha,\n    }\n  }\n}\n\nimpl From<LAB> for LCH {\n  fn from(lab: LAB) -> LCH {\n    let lab = lab.resolve_missing();\n    let (l, c, h) = rectangular_to_polar(lab.l, lab.a, lab.b);\n    LCH {\n      l,\n      c,\n      h,\n      alpha: lab.alpha,\n    }\n  }\n}\n\nimpl From<OKLCH> for OKLAB {\n  fn from(lch: OKLCH) -> OKLAB {\n    let lch = lch.resolve_missing();\n    let (l, a, b) = polar_to_rectangular(lch.l, lch.c, lch.h);\n    OKLAB {\n      l,\n      a,\n      b,\n      alpha: lch.alpha,\n    }\n  }\n}\n\nimpl From<OKLAB> for OKLCH {\n  fn from(lab: OKLAB) -> OKLCH {\n    let lab = lab.resolve_missing();\n    let (l, c, h) = rectangular_to_polar(lab.l, lab.a, lab.b);\n    OKLCH {\n      l,\n      c,\n      h,\n      alpha: lab.alpha,\n    }\n  }\n}\n\nconst D50: &[f32] = &[0.3457 / 0.3585, 1.00000, (1.0 - 0.3457 - 0.3585) / 0.3585];\n\nimpl From<LAB> for XYZd50 {\n  fn from(lab: LAB) -> XYZd50 {\n    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L352\n    const K: f32 = 24389.0 / 27.0; // 29^3/3^3\n    const E: f32 = 216.0 / 24389.0; // 6^3/29^3\n\n    let lab = lab.resolve_missing();\n    let l = lab.l;\n    let a = lab.a;\n    let b = lab.b;\n\n    // compute f, starting with the luminance-related term\n    let f1 = (l + 16.0) / 116.0;\n    let f0 = a / 500.0 + f1;\n    let f2 = f1 - b / 200.0;\n\n    // compute xyz\n    let x = if f0.powi(3) > E {\n      f0.powi(3)\n    } else {\n      (116.0 * f0 - 16.0) / K\n    };\n\n    let y = if l > K * E { ((l + 16.0) / 116.0).powi(3) } else { l / K };\n\n    let z = if f2.powi(3) > E {\n      f2.powi(3)\n    } else {\n      (116.0 * f2 - 16.0) / K\n    };\n\n    // Compute XYZ by scaling xyz by reference white\n    XYZd50 {\n      x: x * D50[0],\n      y: y * D50[1],\n      z: z * D50[2],\n      alpha: lab.alpha,\n    }\n  }\n}\n\nimpl From<XYZd50> for XYZd65 {\n  fn from(xyz: XYZd50) -> XYZd65 {\n    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L319\n    const MATRIX: &[f32] = &[\n      0.9554734527042182,\n      -0.023098536874261423,\n      0.0632593086610217,\n      -0.028369706963208136,\n      1.0099954580058226,\n      0.021041398966943008,\n      0.012314001688319899,\n      -0.020507696433477912,\n      1.3303659366080753,\n    ];\n\n    let xyz = xyz.resolve_missing();\n    let (x, y, z) = multiply_matrix(MATRIX, xyz.x, xyz.y, xyz.z);\n    XYZd65 {\n      x,\n      y,\n      z,\n      alpha: xyz.alpha,\n    }\n  }\n}\n\nimpl From<XYZd65> for XYZd50 {\n  fn from(xyz: XYZd65) -> XYZd50 {\n    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L319\n    const MATRIX: &[f32] = &[\n      1.0479298208405488,\n      0.022946793341019088,\n      -0.05019222954313557,\n      0.029627815688159344,\n      0.990434484573249,\n      -0.01707382502938514,\n      -0.009243058152591178,\n      0.015055144896577895,\n      0.7518742899580008,\n    ];\n\n    let xyz = xyz.resolve_missing();\n    let (x, y, z) = multiply_matrix(MATRIX, xyz.x, xyz.y, xyz.z);\n    XYZd50 {\n      x,\n      y,\n      z,\n      alpha: xyz.alpha,\n    }\n  }\n}\n\nimpl From<XYZd65> for SRGBLinear {\n  fn from(xyz: XYZd65) -> SRGBLinear {\n    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L62\n    const MATRIX: &[f32] = &[\n      3.2409699419045226,\n      -1.537383177570094,\n      -0.4986107602930034,\n      -0.9692436362808796,\n      1.8759675015077202,\n      0.04155505740717559,\n      0.05563007969699366,\n      -0.20397695888897652,\n      1.0569715142428786,\n    ];\n\n    let xyz = xyz.resolve_missing();\n    let (r, g, b) = multiply_matrix(MATRIX, xyz.x, xyz.y, xyz.z);\n    SRGBLinear {\n      r,\n      g,\n      b,\n      alpha: xyz.alpha,\n    }\n  }\n}\n\n#[inline]\nfn multiply_matrix(m: &[f32], x: f32, y: f32, z: f32) -> (f32, f32, f32) {\n  let a = m[0] * x + m[1] * y + m[2] * z;\n  let b = m[3] * x + m[4] * y + m[5] * z;\n  let c = m[6] * x + m[7] * y + m[8] * z;\n  (a, b, c)\n}\n\nimpl From<SRGBLinear> for SRGB {\n  #[inline]\n  fn from(rgb: SRGBLinear) -> SRGB {\n    let rgb = rgb.resolve_missing();\n    let (r, g, b) = gam_srgb(rgb.r, rgb.g, rgb.b);\n    SRGB {\n      r,\n      g,\n      b,\n      alpha: rgb.alpha,\n    }\n  }\n}\n\nfn gam_srgb(r: f32, g: f32, b: f32) -> (f32, f32, f32) {\n  // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L31\n  // convert an array of linear-light sRGB values in the range 0.0-1.0\n  // to gamma corrected form\n  // https://en.wikipedia.org/wiki/SRGB\n  // Extended transfer function:\n  // For negative values, linear portion extends on reflection\n  // of axis, then uses reflected pow below that\n\n  #[inline]\n  fn gam_srgb_component(c: f32) -> f32 {\n    let abs = c.abs();\n    if abs > 0.0031308 {\n      let sign = if c < 0.0 { -1.0 } else { 1.0 };\n      return sign * (1.055 * abs.powf(1.0 / 2.4) - 0.055);\n    }\n\n    return 12.92 * c;\n  }\n\n  let r = gam_srgb_component(r);\n  let g = gam_srgb_component(g);\n  let b = gam_srgb_component(b);\n  (r, g, b)\n}\n\nimpl From<OKLAB> for XYZd65 {\n  fn from(lab: OKLAB) -> XYZd65 {\n    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L418\n    const LMS_TO_XYZ: &[f32] = &[\n      1.2268798733741557,\n      -0.5578149965554813,\n      0.28139105017721583,\n      -0.04057576262431372,\n      1.1122868293970594,\n      -0.07171106666151701,\n      -0.07637294974672142,\n      -0.4214933239627914,\n      1.5869240244272418,\n    ];\n\n    const OKLAB_TO_LMS: &[f32] = &[\n      0.99999999845051981432,\n      0.39633779217376785678,\n      0.21580375806075880339,\n      1.0000000088817607767,\n      -0.1055613423236563494,\n      -0.063854174771705903402,\n      1.0000000546724109177,\n      -0.089484182094965759684,\n      -1.2914855378640917399,\n    ];\n\n    let lab = lab.resolve_missing();\n    let (a, b, c) = multiply_matrix(OKLAB_TO_LMS, lab.l, lab.a, lab.b);\n    let (x, y, z) = multiply_matrix(LMS_TO_XYZ, a.powi(3), b.powi(3), c.powi(3));\n    XYZd65 {\n      x,\n      y,\n      z,\n      alpha: lab.alpha,\n    }\n  }\n}\n\nimpl From<XYZd65> for OKLAB {\n  fn from(xyz: XYZd65) -> OKLAB {\n    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L400\n    const XYZ_TO_LMS: &[f32] = &[\n      0.8190224432164319,\n      0.3619062562801221,\n      -0.12887378261216414,\n      0.0329836671980271,\n      0.9292868468965546,\n      0.03614466816999844,\n      0.048177199566046255,\n      0.26423952494422764,\n      0.6335478258136937,\n    ];\n\n    const LMS_TO_OKLAB: &[f32] = &[\n      0.2104542553,\n      0.7936177850,\n      -0.0040720468,\n      1.9779984951,\n      -2.4285922050,\n      0.4505937099,\n      0.0259040371,\n      0.7827717662,\n      -0.8086757660,\n    ];\n\n    let xyz = xyz.resolve_missing();\n    let (a, b, c) = multiply_matrix(XYZ_TO_LMS, xyz.x, xyz.y, xyz.z);\n    let (l, a, b) = multiply_matrix(LMS_TO_OKLAB, a.cbrt(), b.cbrt(), c.cbrt());\n    OKLAB {\n      l,\n      a,\n      b,\n      alpha: xyz.alpha,\n    }\n  }\n}\n\nimpl From<XYZd50> for LAB {\n  fn from(xyz: XYZd50) -> LAB {\n    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L332\n    // Assuming XYZ is relative to D50, convert to CIE LAB\n    // from CIE standard, which now defines these as a rational fraction\n    const E: f32 = 216.0 / 24389.0; // 6^3/29^3\n    const K: f32 = 24389.0 / 27.0; // 29^3/3^3\n\n    // compute xyz, which is XYZ scaled relative to reference white\n    let xyz = xyz.resolve_missing();\n    let x = xyz.x / D50[0];\n    let y = xyz.y / D50[1];\n    let z = xyz.z / D50[2];\n\n    // now compute f\n    let f0 = if x > E { x.cbrt() } else { (K * x + 16.0) / 116.0 };\n\n    let f1 = if y > E { y.cbrt() } else { (K * y + 16.0) / 116.0 };\n\n    let f2 = if z > E { z.cbrt() } else { (K * z + 16.0) / 116.0 };\n\n    let l = (116.0 * f1) - 16.0;\n    let a = 500.0 * (f0 - f1);\n    let b = 200.0 * (f1 - f2);\n    LAB {\n      l,\n      a,\n      b,\n      alpha: xyz.alpha,\n    }\n  }\n}\n\nimpl From<SRGB> for SRGBLinear {\n  fn from(rgb: SRGB) -> SRGBLinear {\n    let rgb = rgb.resolve_missing();\n    let (r, g, b) = lin_srgb(rgb.r, rgb.g, rgb.b);\n    SRGBLinear {\n      r,\n      g,\n      b,\n      alpha: rgb.alpha,\n    }\n  }\n}\n\nfn lin_srgb(r: f32, g: f32, b: f32) -> (f32, f32, f32) {\n  // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L11\n  // convert sRGB values where in-gamut values are in the range [0 - 1]\n  // to linear light (un-companded) form.\n  // https://en.wikipedia.org/wiki/SRGB\n  // Extended transfer function:\n  // for negative values, linear portion is extended on reflection of axis,\n  // then reflected power function is used.\n\n  #[inline]\n  fn lin_srgb_component(c: f32) -> f32 {\n    let abs = c.abs();\n    if abs < 0.04045 {\n      return c / 12.92;\n    }\n\n    let sign = if c < 0.0 { -1.0 } else { 1.0 };\n    sign * ((abs + 0.055) / 1.055).powf(2.4)\n  }\n\n  let r = lin_srgb_component(r);\n  let g = lin_srgb_component(g);\n  let b = lin_srgb_component(b);\n  (r, g, b)\n}\n\nimpl From<SRGBLinear> for XYZd65 {\n  fn from(rgb: SRGBLinear) -> XYZd65 {\n    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L50\n    // convert an array of linear-light sRGB values to CIE XYZ\n    // using sRGB's own white, D65 (no chromatic adaptation)\n    const MATRIX: &[f32] = &[\n      0.41239079926595934,\n      0.357584339383878,\n      0.1804807884018343,\n      0.21263900587151027,\n      0.715168678767756,\n      0.07219231536073371,\n      0.01933081871559182,\n      0.11919477979462598,\n      0.9505321522496607,\n    ];\n\n    let rgb = rgb.resolve_missing();\n    let (x, y, z) = multiply_matrix(MATRIX, rgb.r, rgb.g, rgb.b);\n    XYZd65 {\n      x,\n      y,\n      z,\n      alpha: rgb.alpha,\n    }\n  }\n}\n\nimpl From<XYZd65> for P3 {\n  fn from(xyz: XYZd65) -> P3 {\n    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L105\n    const MATRIX: &[f32] = &[\n      2.493496911941425,\n      -0.9313836179191239,\n      -0.40271078445071684,\n      -0.8294889695615747,\n      1.7626640603183463,\n      0.023624685841943577,\n      0.03584583024378447,\n      -0.07617238926804182,\n      0.9568845240076872,\n    ];\n\n    let xyz = xyz.resolve_missing();\n    let (r, g, b) = multiply_matrix(MATRIX, xyz.x, xyz.y, xyz.z);\n    let (r, g, b) = gam_srgb(r, g, b); // same as sRGB\n    P3 {\n      r,\n      g,\n      b,\n      alpha: xyz.alpha,\n    }\n  }\n}\n\nimpl From<P3> for XYZd65 {\n  fn from(p3: P3) -> XYZd65 {\n    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L91\n    // convert linear-light display-p3 values to CIE XYZ\n    // using D65 (no chromatic adaptation)\n    // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html\n    const MATRIX: &[f32] = &[\n      0.4865709486482162,\n      0.26566769316909306,\n      0.1982172852343625,\n      0.2289745640697488,\n      0.6917385218365064,\n      0.079286914093745,\n      0.0000000000000000,\n      0.04511338185890264,\n      1.043944368900976,\n    ];\n\n    let p3 = p3.resolve_missing();\n    let (r, g, b) = lin_srgb(p3.r, p3.g, p3.b);\n    let (x, y, z) = multiply_matrix(MATRIX, r, g, b);\n    XYZd65 {\n      x,\n      y,\n      z,\n      alpha: p3.alpha,\n    }\n  }\n}\n\nimpl From<A98> for XYZd65 {\n  fn from(a98: A98) -> XYZd65 {\n    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L181\n    #[inline]\n    fn lin_a98rgb_component(c: f32) -> f32 {\n      let sign = if c < 0.0 { -1.0 } else { 1.0 };\n      sign * c.abs().powf(563.0 / 256.0)\n    }\n\n    // convert an array of a98-rgb values in the range 0.0 - 1.0\n    // to linear light (un-companded) form.\n    // negative values are also now accepted\n    let a98 = a98.resolve_missing();\n    let r = lin_a98rgb_component(a98.r);\n    let g = lin_a98rgb_component(a98.g);\n    let b = lin_a98rgb_component(a98.b);\n\n    // convert an array of linear-light a98-rgb values to CIE XYZ\n    // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html\n    // has greater numerical precision than section 4.3.5.3 of\n    // https://www.adobe.com/digitalimag/pdfs/AdobeRGB1998.pdf\n    // but the values below were calculated from first principles\n    // from the chromaticity coordinates of R G B W\n    // see matrixmaker.html\n    const MATRIX: &[f32] = &[\n      0.5766690429101305,\n      0.1855582379065463,\n      0.1882286462349947,\n      0.29734497525053605,\n      0.6273635662554661,\n      0.07529145849399788,\n      0.02703136138641234,\n      0.07068885253582723,\n      0.9913375368376388,\n    ];\n\n    let (x, y, z) = multiply_matrix(MATRIX, r, g, b);\n    XYZd65 {\n      x,\n      y,\n      z,\n      alpha: a98.alpha,\n    }\n  }\n}\n\nimpl From<XYZd65> for A98 {\n  fn from(xyz: XYZd65) -> A98 {\n    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L222\n    // convert XYZ to linear-light a98-rgb\n    const MATRIX: &[f32] = &[\n      2.0415879038107465,\n      -0.5650069742788596,\n      -0.34473135077832956,\n      -0.9692436362808795,\n      1.8759675015077202,\n      0.04155505740717557,\n      0.013444280632031142,\n      -0.11836239223101838,\n      1.0151749943912054,\n    ];\n\n    #[inline]\n    fn gam_a98_component(c: f32) -> f32 {\n      // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L193\n      // convert linear-light a98-rgb  in the range 0.0-1.0\n      // to gamma corrected form\n      // negative values are also now accepted\n      let sign = if c < 0.0 { -1.0 } else { 1.0 };\n      sign * c.abs().powf(256.0 / 563.0)\n    }\n\n    let xyz = xyz.resolve_missing();\n    let (r, g, b) = multiply_matrix(MATRIX, xyz.x, xyz.y, xyz.z);\n    let r = gam_a98_component(r);\n    let g = gam_a98_component(g);\n    let b = gam_a98_component(b);\n    A98 {\n      r,\n      g,\n      b,\n      alpha: xyz.alpha,\n    }\n  }\n}\n\nimpl From<ProPhoto> for XYZd50 {\n  fn from(prophoto: ProPhoto) -> XYZd50 {\n    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L118\n    // convert an array of prophoto-rgb values\n    // where in-gamut colors are in the range [0.0 - 1.0]\n    // to linear light (un-companded) form.\n    // Transfer curve is gamma 1.8 with a small linear portion\n    // Extended transfer function\n\n    #[inline]\n    fn lin_prophoto_component(c: f32) -> f32 {\n      const ET2: f32 = 16.0 / 512.0;\n      let abs = c.abs();\n      if abs <= ET2 {\n        return c / 16.0;\n      }\n\n      let sign = if c < 0.0 { -1.0 } else { 1.0 };\n      sign * c.powf(1.8)\n    }\n\n    let prophoto = prophoto.resolve_missing();\n    let r = lin_prophoto_component(prophoto.r);\n    let g = lin_prophoto_component(prophoto.g);\n    let b = lin_prophoto_component(prophoto.b);\n\n    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L155\n    // convert an array of linear-light prophoto-rgb values to CIE XYZ\n    // using  D50 (so no chromatic adaptation needed afterwards)\n    // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html\n    const MATRIX: &[f32] = &[\n      0.7977604896723027,\n      0.13518583717574031,\n      0.0313493495815248,\n      0.2880711282292934,\n      0.7118432178101014,\n      0.00008565396060525902,\n      0.0,\n      0.0,\n      0.8251046025104601,\n    ];\n\n    let (x, y, z) = multiply_matrix(MATRIX, r, g, b);\n    XYZd50 {\n      x,\n      y,\n      z,\n      alpha: prophoto.alpha,\n    }\n  }\n}\n\nimpl From<XYZd50> for ProPhoto {\n  fn from(xyz: XYZd50) -> ProPhoto {\n    // convert XYZ to linear-light prophoto-rgb\n    const MATRIX: &[f32] = &[\n      1.3457989731028281,\n      -0.25558010007997534,\n      -0.05110628506753401,\n      -0.5446224939028347,\n      1.5082327413132781,\n      0.02053603239147973,\n      0.0,\n      0.0,\n      1.2119675456389454,\n    ];\n\n    #[inline]\n    fn gam_prophoto_component(c: f32) -> f32 {\n      // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L137\n      // convert linear-light prophoto-rgb  in the range 0.0-1.0\n      // to gamma corrected form\n      // Transfer curve is gamma 1.8 with a small linear portion\n      // TODO for negative values, extend linear portion on reflection of axis, then add pow below that\n      const ET: f32 = 1.0 / 512.0;\n      let abs = c.abs();\n      if abs >= ET {\n        let sign = if c < 0.0 { -1.0 } else { 1.0 };\n        return sign * abs.powf(1.0 / 1.8);\n      }\n\n      16.0 * c\n    }\n\n    let xyz = xyz.resolve_missing();\n    let (r, g, b) = multiply_matrix(MATRIX, xyz.x, xyz.y, xyz.z);\n    let r = gam_prophoto_component(r);\n    let g = gam_prophoto_component(g);\n    let b = gam_prophoto_component(b);\n    ProPhoto {\n      r,\n      g,\n      b,\n      alpha: xyz.alpha,\n    }\n  }\n}\n\nimpl From<Rec2020> for XYZd65 {\n  fn from(rec2020: Rec2020) -> XYZd65 {\n    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L235\n    // convert an array of rec2020 RGB values in the range 0.0 - 1.0\n    // to linear light (un-companded) form.\n    // ITU-R BT.2020-2 p.4\n\n    #[inline]\n    fn lin_rec2020_component(c: f32) -> f32 {\n      const A: f32 = 1.09929682680944;\n      const B: f32 = 0.018053968510807;\n\n      let abs = c.abs();\n      if abs < B * 4.5 {\n        return c / 4.5;\n      }\n\n      let sign = if c < 0.0 { -1.0 } else { 1.0 };\n      sign * ((abs + A - 1.0) / A).powf(1.0 / 0.45)\n    }\n\n    let rec2020 = rec2020.resolve_missing();\n    let r = lin_rec2020_component(rec2020.r);\n    let g = lin_rec2020_component(rec2020.g);\n    let b = lin_rec2020_component(rec2020.b);\n\n    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L276\n    // convert an array of linear-light rec2020 values to CIE XYZ\n    // using  D65 (no chromatic adaptation)\n    // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html\n    const MATRIX: &[f32] = &[\n      0.6369580483012914,\n      0.14461690358620832,\n      0.1688809751641721,\n      0.2627002120112671,\n      0.6779980715188708,\n      0.05930171646986196,\n      0.000000000000000,\n      0.028072693049087428,\n      1.060985057710791,\n    ];\n\n    let (x, y, z) = multiply_matrix(MATRIX, r, g, b);\n    XYZd65 {\n      x,\n      y,\n      z,\n      alpha: rec2020.alpha,\n    }\n  }\n}\n\nimpl From<XYZd65> for Rec2020 {\n  fn from(xyz: XYZd65) -> Rec2020 {\n    // convert XYZ to linear-light rec2020\n    const MATRIX: &[f32] = &[\n      1.7166511879712674,\n      -0.35567078377639233,\n      -0.25336628137365974,\n      -0.6666843518324892,\n      1.6164812366349395,\n      0.01576854581391113,\n      0.017639857445310783,\n      -0.042770613257808524,\n      0.9421031212354738,\n    ];\n\n    #[inline]\n    fn gam_rec2020_component(c: f32) -> f32 {\n      // convert linear-light rec2020 RGB  in the range 0.0-1.0\n      // to gamma corrected form\n      // ITU-R BT.2020-2 p.4\n\n      const A: f32 = 1.09929682680944;\n      const B: f32 = 0.018053968510807;\n\n      let abs = c.abs();\n      if abs > B {\n        let sign = if c < 0.0 { -1.0 } else { 1.0 };\n        return sign * (A * abs.powf(0.45) - (A - 1.0));\n      }\n\n      4.5 * c\n    }\n\n    let xyz = xyz.resolve_missing();\n    let (r, g, b) = multiply_matrix(MATRIX, xyz.x, xyz.y, xyz.z);\n    let r = gam_rec2020_component(r);\n    let g = gam_rec2020_component(g);\n    let b = gam_rec2020_component(b);\n    Rec2020 {\n      r,\n      g,\n      b,\n      alpha: xyz.alpha,\n    }\n  }\n}\n\nimpl From<SRGB> for HSL {\n  fn from(rgb: SRGB) -> HSL {\n    // https://drafts.csswg.org/css-color/#rgb-to-hsl\n    let rgb = rgb.resolve();\n    let r = rgb.r;\n    let g = rgb.g;\n    let b = rgb.b;\n    let max = r.max(g).max(b);\n    let min = r.min(g).min(b);\n    let mut h = f32::NAN;\n    let mut s: f32 = 0.0;\n    let l = (min + max) / 2.0;\n    let d = max - min;\n\n    if d != 0.0 {\n      s = if l == 0.0 || l == 1.0 {\n        0.0\n      } else {\n        (max - l) / l.min(1.0 - l)\n      };\n\n      if max == r {\n        h = (g - b) / d + (if g < b { 6.0 } else { 0.0 });\n      } else if max == g {\n        h = (b - r) / d + 2.0;\n      } else if max == b {\n        h = (r - g) / d + 4.0;\n      }\n\n      h = h * 60.0;\n    }\n\n    HSL {\n      h,\n      s: s * 100.0,\n      l: l * 100.0,\n      alpha: rgb.alpha,\n    }\n  }\n}\n\nimpl From<HSL> for SRGB {\n  fn from(hsl: HSL) -> SRGB {\n    // https://drafts.csswg.org/css-color/#hsl-to-rgb\n    let hsl = hsl.resolve_missing();\n    let h = (hsl.h - 360.0 * (hsl.h / 360.0).floor()) / 360.0;\n    let (r, g, b) = hsl_to_rgb(h, hsl.s / 100.0, hsl.l / 100.0);\n    SRGB {\n      r,\n      g,\n      b,\n      alpha: hsl.alpha,\n    }\n  }\n}\n\nimpl From<SRGB> for HWB {\n  fn from(rgb: SRGB) -> HWB {\n    let rgb = rgb.resolve();\n    let hsl = HSL::from(rgb);\n    let r = rgb.r;\n    let g = rgb.g;\n    let b = rgb.b;\n    let w = r.min(g).min(b);\n    let b = 1.0 - r.max(g).max(b);\n    HWB {\n      h: hsl.h,\n      w: w * 100.0,\n      b: b * 100.0,\n      alpha: rgb.alpha,\n    }\n  }\n}\n\nimpl From<HWB> for SRGB {\n  fn from(hwb: HWB) -> SRGB {\n    // https://drafts.csswg.org/css-color/#hwb-to-rgb\n    let hwb = hwb.resolve_missing();\n    let h = hwb.h;\n    let w = hwb.w / 100.0;\n    let b = hwb.b / 100.0;\n\n    if w + b >= 1.0 {\n      let gray = w / (w + b);\n      return SRGB {\n        r: gray,\n        g: gray,\n        b: gray,\n        alpha: hwb.alpha,\n      };\n    }\n\n    let mut rgba = SRGB::from(HSL {\n      h,\n      s: 100.0,\n      l: 50.0,\n      alpha: hwb.alpha,\n    });\n    let x = 1.0 - w - b;\n    rgba.r = rgba.r * x + w;\n    rgba.g = rgba.g * x + w;\n    rgba.b = rgba.b * x + w;\n    rgba\n  }\n}\n\nimpl From<RGBA> for SRGB {\n  fn from(rgb: RGBA) -> SRGB {\n    SRGB {\n      r: rgb.red_f32(),\n      g: rgb.green_f32(),\n      b: rgb.blue_f32(),\n      alpha: rgb.alpha_f32(),\n    }\n  }\n}\n\nimpl From<SRGB> for RGBA {\n  fn from(rgb: SRGB) -> RGBA {\n    let rgb = rgb.resolve();\n    RGBA::from_floats(rgb.r, rgb.g, rgb.b, rgb.alpha)\n  }\n}\n\nimpl From<SRGB> for RGB {\n  fn from(rgb: SRGB) -> Self {\n    RGB {\n      r: rgb.r * 255.0,\n      g: rgb.g * 255.0,\n      b: rgb.b * 255.0,\n      alpha: rgb.alpha,\n    }\n  }\n}\n\nimpl From<RGB> for SRGB {\n  fn from(rgb: RGB) -> Self {\n    SRGB {\n      r: rgb.r / 255.0,\n      g: rgb.g / 255.0,\n      b: rgb.b / 255.0,\n      alpha: rgb.alpha,\n    }\n  }\n}\n\nimpl From<RGBA> for RGB {\n  fn from(rgb: RGBA) -> Self {\n    RGB::from(&rgb)\n  }\n}\n\nimpl From<&RGBA> for RGB {\n  fn from(rgb: &RGBA) -> Self {\n    RGB {\n      r: rgb.red as f32,\n      g: rgb.green as f32,\n      b: rgb.blue as f32,\n      alpha: rgb.alpha_f32(),\n    }\n  }\n}\n\nimpl From<RGB> for RGBA {\n  fn from(rgb: RGB) -> Self {\n    let rgb = rgb.resolve();\n    RGBA::new(\n      clamp_floor_256_f32(rgb.r),\n      clamp_floor_256_f32(rgb.g),\n      clamp_floor_256_f32(rgb.b),\n      rgb.alpha,\n    )\n  }\n}\n\n// Once Rust specialization is stable, this could be simplified.\nvia!(LAB -> XYZd50 -> XYZd65);\nvia!(ProPhoto -> XYZd50 -> XYZd65);\nvia!(OKLCH -> OKLAB -> XYZd65);\n\nvia!(LAB -> XYZd65 -> OKLAB);\nvia!(LAB -> XYZd65 -> OKLCH);\nvia!(LAB -> XYZd65 -> SRGB);\nvia!(LAB -> XYZd65 -> SRGBLinear);\nvia!(LAB -> XYZd65 -> P3);\nvia!(LAB -> XYZd65 -> A98);\nvia!(LAB -> XYZd65 -> ProPhoto);\nvia!(LAB -> XYZd65 -> Rec2020);\nvia!(LAB -> XYZd65 -> HSL);\nvia!(LAB -> XYZd65 -> HWB);\n\nvia!(LCH -> LAB -> XYZd65);\nvia!(LCH -> XYZd65 -> OKLAB);\nvia!(LCH -> XYZd65 -> OKLCH);\nvia!(LCH -> XYZd65 -> SRGB);\nvia!(LCH -> XYZd65 -> SRGBLinear);\nvia!(LCH -> XYZd65 -> P3);\nvia!(LCH -> XYZd65 -> A98);\nvia!(LCH -> XYZd65 -> ProPhoto);\nvia!(LCH -> XYZd65 -> Rec2020);\nvia!(LCH -> XYZd65 -> XYZd50);\nvia!(LCH -> XYZd65 -> HSL);\nvia!(LCH -> XYZd65 -> HWB);\n\nvia!(SRGB -> SRGBLinear -> XYZd65);\nvia!(SRGB -> XYZd65 -> OKLAB);\nvia!(SRGB -> XYZd65 -> OKLCH);\nvia!(SRGB -> XYZd65 -> P3);\nvia!(SRGB -> XYZd65 -> A98);\nvia!(SRGB -> XYZd65 -> ProPhoto);\nvia!(SRGB -> XYZd65 -> Rec2020);\nvia!(SRGB -> XYZd65 -> XYZd50);\n\nvia!(P3 -> XYZd65 -> SRGBLinear);\nvia!(P3 -> XYZd65 -> OKLAB);\nvia!(P3 -> XYZd65 -> OKLCH);\nvia!(P3 -> XYZd65 -> A98);\nvia!(P3 -> XYZd65 -> ProPhoto);\nvia!(P3 -> XYZd65 -> Rec2020);\nvia!(P3 -> XYZd65 -> XYZd50);\nvia!(P3 -> XYZd65 -> HSL);\nvia!(P3 -> XYZd65 -> HWB);\n\nvia!(SRGBLinear -> XYZd65 -> OKLAB);\nvia!(SRGBLinear -> XYZd65 -> OKLCH);\nvia!(SRGBLinear -> XYZd65 -> A98);\nvia!(SRGBLinear -> XYZd65 -> ProPhoto);\nvia!(SRGBLinear -> XYZd65 -> Rec2020);\nvia!(SRGBLinear -> XYZd65 -> XYZd50);\nvia!(SRGBLinear -> XYZd65 -> HSL);\nvia!(SRGBLinear -> XYZd65 -> HWB);\n\nvia!(A98 -> XYZd65 -> OKLAB);\nvia!(A98 -> XYZd65 -> OKLCH);\nvia!(A98 -> XYZd65 -> ProPhoto);\nvia!(A98 -> XYZd65 -> Rec2020);\nvia!(A98 -> XYZd65 -> XYZd50);\nvia!(A98 -> XYZd65 -> HSL);\nvia!(A98 -> XYZd65 -> HWB);\n\nvia!(ProPhoto -> XYZd65 -> OKLAB);\nvia!(ProPhoto -> XYZd65 -> OKLCH);\nvia!(ProPhoto -> XYZd65 -> Rec2020);\nvia!(ProPhoto -> XYZd65 -> HSL);\nvia!(ProPhoto -> XYZd65 -> HWB);\n\nvia!(XYZd50 -> XYZd65 -> OKLAB);\nvia!(XYZd50 -> XYZd65 -> OKLCH);\nvia!(XYZd50 -> XYZd65 -> Rec2020);\nvia!(XYZd50 -> XYZd65 -> HSL);\nvia!(XYZd50 -> XYZd65 -> HWB);\n\nvia!(Rec2020 -> XYZd65 -> OKLAB);\nvia!(Rec2020 -> XYZd65 -> OKLCH);\nvia!(Rec2020 -> XYZd65 -> HSL);\nvia!(Rec2020 -> XYZd65 -> HWB);\n\nvia!(HSL -> XYZd65 -> OKLAB);\nvia!(HSL -> XYZd65 -> OKLCH);\nvia!(HSL -> SRGB -> XYZd65);\nvia!(HSL -> SRGB -> HWB);\n\nvia!(HWB -> SRGB -> XYZd65);\nvia!(HWB -> XYZd65 -> OKLAB);\nvia!(HWB -> XYZd65 -> OKLCH);\n\nvia!(RGB -> SRGB -> LAB);\nvia!(RGB -> SRGB -> LCH);\nvia!(RGB -> SRGB -> OKLAB);\nvia!(RGB -> SRGB -> OKLCH);\nvia!(RGB -> SRGB -> P3);\nvia!(RGB -> SRGB -> SRGBLinear);\nvia!(RGB -> SRGB -> A98);\nvia!(RGB -> SRGB -> ProPhoto);\nvia!(RGB -> SRGB -> XYZd50);\nvia!(RGB -> SRGB -> XYZd65);\nvia!(RGB -> SRGB -> Rec2020);\nvia!(RGB -> SRGB -> HSL);\nvia!(RGB -> SRGB -> HWB);\n\n// RGBA is an 8-bit version. Convert to SRGB, which is a\n// more accurate floating point representation for all operations.\nvia!(RGBA -> SRGB -> LAB);\nvia!(RGBA -> SRGB -> LCH);\nvia!(RGBA -> SRGB -> OKLAB);\nvia!(RGBA -> SRGB -> OKLCH);\nvia!(RGBA -> SRGB -> P3);\nvia!(RGBA -> SRGB -> SRGBLinear);\nvia!(RGBA -> SRGB -> A98);\nvia!(RGBA -> SRGB -> ProPhoto);\nvia!(RGBA -> SRGB -> XYZd50);\nvia!(RGBA -> SRGB -> XYZd65);\nvia!(RGBA -> SRGB -> Rec2020);\nvia!(RGBA -> SRGB -> HSL);\nvia!(RGBA -> SRGB -> HWB);\n\nmacro_rules! color_space {\n  ($space: ty) => {\n    impl From<LABColor> for $space {\n      fn from(color: LABColor) -> $space {\n        use LABColor::*;\n\n        match color {\n          LAB(v) => v.into(),\n          LCH(v) => v.into(),\n          OKLAB(v) => v.into(),\n          OKLCH(v) => v.into(),\n        }\n      }\n    }\n\n    impl From<PredefinedColor> for $space {\n      fn from(color: PredefinedColor) -> $space {\n        use PredefinedColor::*;\n\n        match color {\n          SRGB(v) => v.into(),\n          SRGBLinear(v) => v.into(),\n          DisplayP3(v) => v.into(),\n          A98(v) => v.into(),\n          ProPhoto(v) => v.into(),\n          Rec2020(v) => v.into(),\n          XYZd50(v) => v.into(),\n          XYZd65(v) => v.into(),\n        }\n      }\n    }\n\n    impl From<FloatColor> for $space {\n      fn from(color: FloatColor) -> $space {\n        use FloatColor::*;\n\n        match color {\n          RGB(v) => v.into(),\n          HSL(v) => v.into(),\n          HWB(v) => v.into(),\n        }\n      }\n    }\n\n    impl TryFrom<&CssColor> for $space {\n      type Error = ();\n      fn try_from(color: &CssColor) -> Result<$space, ()> {\n        Ok(match color {\n          CssColor::RGBA(rgba) => (*rgba).into(),\n          CssColor::LAB(lab) => (**lab).into(),\n          CssColor::Predefined(predefined) => (**predefined).into(),\n          CssColor::Float(float) => (**float).into(),\n          CssColor::CurrentColor => return Err(()),\n          CssColor::LightDark(..) => return Err(()),\n          CssColor::System(..) => return Err(()),\n        })\n      }\n    }\n\n    impl TryFrom<CssColor> for $space {\n      type Error = ();\n      fn try_from(color: CssColor) -> Result<$space, ()> {\n        Ok(match color {\n          CssColor::RGBA(rgba) => rgba.into(),\n          CssColor::LAB(lab) => (*lab).into(),\n          CssColor::Predefined(predefined) => (*predefined).into(),\n          CssColor::Float(float) => (*float).into(),\n          CssColor::CurrentColor => return Err(()),\n          CssColor::LightDark(..) => return Err(()),\n          CssColor::System(..) => return Err(()),\n        })\n      }\n    }\n  };\n}\n\ncolor_space!(LAB);\ncolor_space!(LCH);\ncolor_space!(OKLAB);\ncolor_space!(OKLCH);\ncolor_space!(SRGB);\ncolor_space!(SRGBLinear);\ncolor_space!(XYZd50);\ncolor_space!(XYZd65);\ncolor_space!(P3);\ncolor_space!(A98);\ncolor_space!(ProPhoto);\ncolor_space!(Rec2020);\ncolor_space!(HSL);\ncolor_space!(HWB);\ncolor_space!(RGB);\ncolor_space!(RGBA);\n\nmacro_rules! predefined {\n  ($key: ident, $t: ty) => {\n    impl From<$t> for PredefinedColor {\n      fn from(color: $t) -> PredefinedColor {\n        PredefinedColor::$key(color)\n      }\n    }\n\n    impl From<$t> for CssColor {\n      fn from(color: $t) -> CssColor {\n        CssColor::Predefined(Box::new(PredefinedColor::$key(color)))\n      }\n    }\n  };\n}\n\npredefined!(SRGBLinear, SRGBLinear);\npredefined!(XYZd50, XYZd50);\npredefined!(XYZd65, XYZd65);\npredefined!(DisplayP3, P3);\npredefined!(A98, A98);\npredefined!(ProPhoto, ProPhoto);\npredefined!(Rec2020, Rec2020);\n\nmacro_rules! lab {\n  ($key: ident, $t: ty) => {\n    impl From<$t> for LABColor {\n      fn from(color: $t) -> LABColor {\n        LABColor::$key(color)\n      }\n    }\n\n    impl From<$t> for CssColor {\n      fn from(color: $t) -> CssColor {\n        CssColor::LAB(Box::new(LABColor::$key(color)))\n      }\n    }\n  };\n}\n\nlab!(LAB, LAB);\nlab!(LCH, LCH);\nlab!(OKLAB, OKLAB);\nlab!(OKLCH, OKLCH);\n\nmacro_rules! rgb {\n  ($t: ty) => {\n    impl From<$t> for CssColor {\n      fn from(color: $t) -> CssColor {\n        // TODO: should we serialize as color(srgb, ...)?\n        // would be more precise than 8-bit color.\n        CssColor::RGBA(color.into())\n      }\n    }\n  };\n}\n\nrgb!(SRGB);\nrgb!(HSL);\nrgb!(HWB);\nrgb!(RGB);\n\nimpl From<RGBA> for CssColor {\n  fn from(color: RGBA) -> CssColor {\n    CssColor::RGBA(color)\n  }\n}\n\n/// A trait that colors implement to support [gamut mapping](https://www.w3.org/TR/css-color-4/#gamut-mapping).\npub trait ColorGamut {\n  /// Returns whether the color is within the gamut of the color space.\n  fn in_gamut(&self) -> bool;\n  /// Clips the color so that it is within the gamut of the color space.\n  fn clip(&self) -> Self;\n}\n\nmacro_rules! bounded_color_gamut {\n  ($t: ty, $a: ident, $b: ident, $c: ident) => {\n    impl ColorGamut for $t {\n      #[inline]\n      fn in_gamut(&self) -> bool {\n        self.$a >= 0.0 && self.$a <= 1.0 && self.$b >= 0.0 && self.$b <= 1.0 && self.$c >= 0.0 && self.$c <= 1.0\n      }\n\n      #[inline]\n      fn clip(&self) -> Self {\n        Self {\n          $a: self.$a.clamp(0.0, 1.0),\n          $b: self.$b.clamp(0.0, 1.0),\n          $c: self.$c.clamp(0.0, 1.0),\n          alpha: self.alpha.clamp(0.0, 1.0),\n        }\n      }\n    }\n  };\n}\n\nmacro_rules! unbounded_color_gamut {\n  ($t: ty, $a: ident, $b: ident, $c: ident) => {\n    impl ColorGamut for $t {\n      #[inline]\n      fn in_gamut(&self) -> bool {\n        true\n      }\n\n      #[inline]\n      fn clip(&self) -> Self {\n        *self\n      }\n    }\n  };\n}\n\nmacro_rules! hsl_hwb_color_gamut {\n  ($t: ty, $a: ident, $b: ident) => {\n    impl ColorGamut for $t {\n      #[inline]\n      fn in_gamut(&self) -> bool {\n        self.$a >= 0.0 && self.$a <= 100.0 && self.$b >= 0.0 && self.$b <= 100.0\n      }\n\n      #[inline]\n      fn clip(&self) -> Self {\n        Self {\n          h: self.h % 360.0,\n          $a: self.$a.clamp(0.0, 100.0),\n          $b: self.$b.clamp(0.0, 100.0),\n          alpha: self.alpha.clamp(0.0, 1.0),\n        }\n      }\n    }\n  };\n}\n\nbounded_color_gamut!(SRGB, r, g, b);\nbounded_color_gamut!(SRGBLinear, r, g, b);\nbounded_color_gamut!(P3, r, g, b);\nbounded_color_gamut!(A98, r, g, b);\nbounded_color_gamut!(ProPhoto, r, g, b);\nbounded_color_gamut!(Rec2020, r, g, b);\nunbounded_color_gamut!(LAB, l, a, b);\nunbounded_color_gamut!(OKLAB, l, a, b);\nunbounded_color_gamut!(XYZd50, x, y, z);\nunbounded_color_gamut!(XYZd65, x, y, z);\nunbounded_color_gamut!(LCH, l, c, h);\nunbounded_color_gamut!(OKLCH, l, c, h);\nhsl_hwb_color_gamut!(HSL, s, l);\nhsl_hwb_color_gamut!(HWB, w, b);\n\nimpl ColorGamut for RGB {\n  #[inline]\n  fn in_gamut(&self) -> bool {\n    self.r >= 0.0 && self.r <= 255.0 && self.g >= 0.0 && self.g <= 255.0 && self.b >= 0.0 && self.b <= 255.0\n  }\n\n  #[inline]\n  fn clip(&self) -> Self {\n    Self {\n      r: self.r.clamp(0.0, 255.0),\n      g: self.g.clamp(0.0, 255.0),\n      b: self.b.clamp(0.0, 255.0),\n      alpha: self.alpha.clamp(0.0, 1.0),\n    }\n  }\n}\n\nfn delta_eok<T: Into<OKLAB>>(a: T, b: OKLCH) -> f32 {\n  // https://www.w3.org/TR/css-color-4/#color-difference-OK\n  let a: OKLAB = a.into();\n  let b: OKLAB = b.into();\n  let delta_l = a.l - b.l;\n  let delta_a = a.a - b.a;\n  let delta_b = a.b - b.b;\n\n  (delta_l.powi(2) + delta_a.powi(2) + delta_b.powi(2)).sqrt()\n}\n\nfn map_gamut<T>(color: T) -> T\nwhere\n  T: Into<OKLCH> + ColorGamut + Into<OKLAB> + From<OKLCH> + Copy,\n{\n  const JND: f32 = 0.02;\n  const EPSILON: f32 = 0.00001;\n\n  // https://www.w3.org/TR/css-color-4/#binsearch\n  let mut current: OKLCH = color.into();\n\n  // If lightness is >= 100%, return pure white.\n  if (current.l - 1.0).abs() < EPSILON || current.l > 1.0 {\n    return OKLCH {\n      l: 1.0,\n      c: 0.0,\n      h: 0.0,\n      alpha: current.alpha,\n    }\n    .into();\n  }\n\n  // If lightness <= 0%, return pure black.\n  if current.l < EPSILON {\n    return OKLCH {\n      l: 0.0,\n      c: 0.0,\n      h: 0.0,\n      alpha: current.alpha,\n    }\n    .into();\n  }\n\n  let mut min = 0.0;\n  let mut max = current.c;\n\n  while (max - min) > EPSILON {\n    let chroma = (min + max) / 2.0;\n    current.c = chroma;\n\n    let converted = T::from(current);\n    if converted.in_gamut() {\n      min = chroma;\n      continue;\n    }\n\n    let clipped = converted.clip();\n    let delta_e = delta_eok(clipped, current);\n    if delta_e < JND {\n      return clipped;\n    }\n\n    max = chroma;\n  }\n\n  current.into()\n}\n\nfn parse_color_mix<'i, 't>(input: &mut Parser<'i, 't>) -> Result<CssColor, ParseError<'i, ParserError<'i>>> {\n  input.expect_ident_matching(\"in\")?;\n  let method = ColorSpaceName::parse(input)?;\n\n  let hue_method = if matches!(\n    method,\n    ColorSpaceName::Hsl | ColorSpaceName::Hwb | ColorSpaceName::LCH | ColorSpaceName::OKLCH\n  ) {\n    let hue_method = input.try_parse(HueInterpolationMethod::parse);\n    if hue_method.is_ok() {\n      input.expect_ident_matching(\"hue\")?;\n    }\n    hue_method\n  } else {\n    Ok(HueInterpolationMethod::Shorter)\n  };\n\n  let hue_method = hue_method.unwrap_or(HueInterpolationMethod::Shorter);\n  input.expect_comma()?;\n\n  let first_percent = input.try_parse(|input| input.expect_percentage());\n  let first_color = CssColor::parse(input)?;\n  let first_percent = first_percent\n    .or_else(|_| input.try_parse(|input| input.expect_percentage()))\n    .ok();\n  input.expect_comma()?;\n\n  let second_percent = input.try_parse(|input| input.expect_percentage());\n  let second_color = CssColor::parse(input)?;\n  let second_percent = second_percent\n    .or_else(|_| input.try_parse(|input| input.expect_percentage()))\n    .ok();\n\n  // https://drafts.csswg.org/css-color-5/#color-mix-percent-norm\n  let (p1, p2) = if first_percent.is_none() && second_percent.is_none() {\n    (0.5, 0.5)\n  } else {\n    let p2 = second_percent.unwrap_or_else(|| 1.0 - first_percent.unwrap());\n    let p1 = first_percent.unwrap_or_else(|| 1.0 - second_percent.unwrap());\n    (p1, p2)\n  };\n\n  if (p1 + p2) == 0.0 {\n    return Err(input.new_custom_error(ParserError::InvalidValue));\n  }\n\n  match method {\n    ColorSpaceName::SRGB => first_color.interpolate::<SRGB>(p1, &second_color, p2, hue_method),\n    ColorSpaceName::SRGBLinear => first_color.interpolate::<SRGBLinear>(p1, &second_color, p2, hue_method),\n    ColorSpaceName::Hsl => first_color.interpolate::<HSL>(p1, &second_color, p2, hue_method),\n    ColorSpaceName::Hwb => first_color.interpolate::<HWB>(p1, &second_color, p2, hue_method),\n    ColorSpaceName::LAB => first_color.interpolate::<LAB>(p1, &second_color, p2, hue_method),\n    ColorSpaceName::LCH => first_color.interpolate::<LCH>(p1, &second_color, p2, hue_method),\n    ColorSpaceName::OKLAB => first_color.interpolate::<OKLAB>(p1, &second_color, p2, hue_method),\n    ColorSpaceName::OKLCH => first_color.interpolate::<OKLCH>(p1, &second_color, p2, hue_method),\n    ColorSpaceName::XYZ | ColorSpaceName::XYZd65 => {\n      first_color.interpolate::<XYZd65>(p1, &second_color, p2, hue_method)\n    }\n    ColorSpaceName::XYZd50 => first_color.interpolate::<XYZd50>(p1, &second_color, p2, hue_method),\n  }\n  .map_err(|_| input.new_custom_error(ParserError::InvalidValue))\n}\n\nimpl CssColor {\n  fn get_type_id(&self) -> TypeId {\n    match self {\n      CssColor::RGBA(..) => TypeId::of::<SRGB>(),\n      CssColor::LAB(lab) => match &**lab {\n        LABColor::LAB(..) => TypeId::of::<LAB>(),\n        LABColor::LCH(..) => TypeId::of::<LCH>(),\n        LABColor::OKLAB(..) => TypeId::of::<OKLAB>(),\n        LABColor::OKLCH(..) => TypeId::of::<OKLCH>(),\n      },\n      CssColor::Predefined(predefined) => match &**predefined {\n        PredefinedColor::SRGB(..) => TypeId::of::<SRGB>(),\n        PredefinedColor::SRGBLinear(..) => TypeId::of::<SRGBLinear>(),\n        PredefinedColor::DisplayP3(..) => TypeId::of::<P3>(),\n        PredefinedColor::A98(..) => TypeId::of::<A98>(),\n        PredefinedColor::ProPhoto(..) => TypeId::of::<ProPhoto>(),\n        PredefinedColor::Rec2020(..) => TypeId::of::<Rec2020>(),\n        PredefinedColor::XYZd50(..) => TypeId::of::<XYZd50>(),\n        PredefinedColor::XYZd65(..) => TypeId::of::<XYZd65>(),\n      },\n      CssColor::Float(float) => match &**float {\n        FloatColor::RGB(..) => TypeId::of::<SRGB>(),\n        FloatColor::HSL(..) => TypeId::of::<HSL>(),\n        FloatColor::HWB(..) => TypeId::of::<HWB>(),\n      },\n      _ => unreachable!(),\n    }\n  }\n\n  fn to_light_dark(&self) -> CssColor {\n    match self {\n      CssColor::LightDark(..) => self.clone(),\n      _ => CssColor::LightDark(Box::new(self.clone()), Box::new(self.clone())),\n    }\n  }\n\n  /// Mixes this color with another color, including the specified amount of each.\n  /// Implemented according to the [`color-mix()`](https://www.w3.org/TR/css-color-5/#color-mix) function.\n  pub fn interpolate<T>(\n    &self,\n    mut p1: f32,\n    other: &CssColor,\n    mut p2: f32,\n    method: HueInterpolationMethod,\n  ) -> Result<CssColor, ()>\n  where\n    for<'a> T: 'static\n      + TryFrom<&'a CssColor>\n      + Interpolate\n      + Into<CssColor>\n      + Into<OKLCH>\n      + ColorGamut\n      + Into<OKLAB>\n      + From<OKLCH>\n      + Copy,\n  {\n    if matches!(self, CssColor::CurrentColor | CssColor::System(..))\n      || matches!(other, CssColor::CurrentColor | CssColor::System(..))\n    {\n      return Err(());\n    }\n\n    if matches!(self, CssColor::LightDark(..)) || matches!(other, CssColor::LightDark(..)) {\n      if let (CssColor::LightDark(al, ad), CssColor::LightDark(bl, bd)) =\n        (self.to_light_dark(), other.to_light_dark())\n      {\n        return Ok(CssColor::LightDark(\n          Box::new(al.interpolate::<T>(p1, &bl, p2, method)?),\n          Box::new(ad.interpolate::<T>(p1, &bd, p2, method)?),\n        ));\n      }\n    }\n\n    let type_id = TypeId::of::<T>();\n    let converted_first = self.get_type_id() != type_id;\n    let converted_second = other.get_type_id() != type_id;\n\n    // https://drafts.csswg.org/css-color-5/#color-mix-result\n    let mut first_color = T::try_from(self).map_err(|_| ())?;\n    let mut second_color = T::try_from(other).map_err(|_| ())?;\n\n    if converted_first && !first_color.in_gamut() {\n      first_color = map_gamut(first_color);\n    }\n\n    if converted_second && !second_color.in_gamut() {\n      second_color = map_gamut(second_color);\n    }\n\n    // https://www.w3.org/TR/css-color-4/#powerless\n    if converted_first {\n      first_color.adjust_powerless_components();\n    }\n\n    if converted_second {\n      second_color.adjust_powerless_components();\n    }\n\n    // https://drafts.csswg.org/css-color-4/#interpolation-missing\n    first_color.fill_missing_components(&second_color);\n    second_color.fill_missing_components(&first_color);\n\n    // https://www.w3.org/TR/css-color-4/#hue-interpolation\n    first_color.adjust_hue(&mut second_color, method);\n\n    // https://www.w3.org/TR/css-color-4/#interpolation-alpha\n    first_color.premultiply();\n    second_color.premultiply();\n\n    // https://drafts.csswg.org/css-color-5/#color-mix-percent-norm\n    let mut alpha_multiplier = p1 + p2;\n    if alpha_multiplier != 1.0 {\n      p1 = p1 / alpha_multiplier;\n      p2 = p2 / alpha_multiplier;\n      if alpha_multiplier > 1.0 {\n        alpha_multiplier = 1.0;\n      }\n    }\n\n    let mut result_color = first_color.interpolate(p1, &second_color, p2);\n    result_color.unpremultiply(alpha_multiplier);\n\n    Ok(result_color.into())\n  }\n}\n\n/// A trait that colors implement to support interpolation.\npub trait Interpolate {\n  /// Adjusts components that are powerless to be NaN.\n  fn adjust_powerless_components(&mut self) {}\n  /// Fills missing components (represented as NaN) to match the other color to interpolate with.\n  fn fill_missing_components(&mut self, other: &Self);\n  /// Adjusts the color hue according to the given hue interpolation method.\n  fn adjust_hue(&mut self, _: &mut Self, _: HueInterpolationMethod) {}\n  /// Premultiplies the color by its alpha value.\n  fn premultiply(&mut self);\n  /// Un-premultiplies the color by the given alpha multiplier.\n  fn unpremultiply(&mut self, alpha_multiplier: f32);\n  /// Interpolates the color with another using the given amounts of each.\n  fn interpolate(&self, p1: f32, other: &Self, p2: f32) -> Self;\n}\n\nmacro_rules! interpolate {\n  ($a: ident, $b: ident, $c: ident) => {\n    fn fill_missing_components(&mut self, other: &Self) {\n      if self.$a.is_nan() {\n        self.$a = other.$a;\n      }\n\n      if self.$b.is_nan() {\n        self.$b = other.$b;\n      }\n\n      if self.$c.is_nan() {\n        self.$c = other.$c;\n      }\n\n      if self.alpha.is_nan() {\n        self.alpha = other.alpha;\n      }\n    }\n\n    fn interpolate(&self, p1: f32, other: &Self, p2: f32) -> Self {\n      Self {\n        $a: self.$a * p1 + other.$a * p2,\n        $b: self.$b * p1 + other.$b * p2,\n        $c: self.$c * p1 + other.$c * p2,\n        alpha: self.alpha * p1 + other.alpha * p2,\n      }\n    }\n  };\n}\n\nmacro_rules! rectangular_premultiply {\n  ($a: ident, $b: ident, $c: ident) => {\n    fn premultiply(&mut self) {\n      if !self.alpha.is_nan() {\n        self.$a *= self.alpha;\n        self.$b *= self.alpha;\n        self.$c *= self.alpha;\n      }\n    }\n\n    fn unpremultiply(&mut self, alpha_multiplier: f32) {\n      if !self.alpha.is_nan() && self.alpha != 0.0 {\n        self.$a /= self.alpha;\n        self.$b /= self.alpha;\n        self.$c /= self.alpha;\n        self.alpha *= alpha_multiplier;\n      }\n    }\n  };\n}\n\nmacro_rules! polar_premultiply {\n  ($a: ident, $b: ident) => {\n    fn premultiply(&mut self) {\n      if !self.alpha.is_nan() {\n        self.$a *= self.alpha;\n        self.$b *= self.alpha;\n      }\n    }\n\n    fn unpremultiply(&mut self, alpha_multiplier: f32) {\n      self.h %= 360.0;\n      if !self.alpha.is_nan() {\n        self.$a /= self.alpha;\n        self.$b /= self.alpha;\n        self.alpha *= alpha_multiplier;\n      }\n    }\n  };\n}\n\nmacro_rules! adjust_powerless_lab {\n  () => {\n    fn adjust_powerless_components(&mut self) {\n      // If the lightness of a LAB color is 0%, both the a and b components are powerless.\n      if self.l.abs() < f32::EPSILON {\n        self.a = f32::NAN;\n        self.b = f32::NAN;\n      }\n    }\n  };\n}\n\nmacro_rules! adjust_powerless_lch {\n  () => {\n    fn adjust_powerless_components(&mut self) {\n      // If the chroma of an LCH color is 0%, the hue component is powerless.\n      // If the lightness of an LCH color is 0%, both the hue and chroma components are powerless.\n      if self.c.abs() < f32::EPSILON {\n        self.h = f32::NAN;\n      }\n\n      if self.l.abs() < f32::EPSILON {\n        self.c = f32::NAN;\n        self.h = f32::NAN;\n      }\n    }\n\n    fn adjust_hue(&mut self, other: &mut Self, method: HueInterpolationMethod) {\n      method.interpolate(&mut self.h, &mut other.h);\n    }\n  };\n}\n\nimpl Interpolate for SRGB {\n  rectangular_premultiply!(r, g, b);\n  interpolate!(r, g, b);\n}\n\nimpl Interpolate for SRGBLinear {\n  rectangular_premultiply!(r, g, b);\n  interpolate!(r, g, b);\n}\n\nimpl Interpolate for XYZd50 {\n  rectangular_premultiply!(x, y, z);\n  interpolate!(x, y, z);\n}\n\nimpl Interpolate for XYZd65 {\n  rectangular_premultiply!(x, y, z);\n  interpolate!(x, y, z);\n}\n\nimpl Interpolate for LAB {\n  adjust_powerless_lab!();\n  rectangular_premultiply!(l, a, b);\n  interpolate!(l, a, b);\n}\n\nimpl Interpolate for OKLAB {\n  adjust_powerless_lab!();\n  rectangular_premultiply!(l, a, b);\n  interpolate!(l, a, b);\n}\n\nimpl Interpolate for LCH {\n  adjust_powerless_lch!();\n  polar_premultiply!(l, c);\n  interpolate!(l, c, h);\n}\n\nimpl Interpolate for OKLCH {\n  adjust_powerless_lch!();\n  polar_premultiply!(l, c);\n  interpolate!(l, c, h);\n}\n\nimpl Interpolate for HSL {\n  polar_premultiply!(s, l);\n\n  fn adjust_powerless_components(&mut self) {\n    // If the saturation of an HSL color is 0%, then the hue component is powerless.\n    // If the lightness of an HSL color is 0% or 100%, both the saturation and hue components are powerless.\n    if self.s.abs() < f32::EPSILON {\n      self.h = f32::NAN;\n    }\n\n    if self.l.abs() < f32::EPSILON || (self.l - 100.0).abs() < f32::EPSILON {\n      self.h = f32::NAN;\n      self.s = f32::NAN;\n    }\n  }\n\n  fn adjust_hue(&mut self, other: &mut Self, method: HueInterpolationMethod) {\n    method.interpolate(&mut self.h, &mut other.h);\n  }\n\n  interpolate!(h, s, l);\n}\n\nimpl Interpolate for HWB {\n  polar_premultiply!(w, b);\n\n  fn adjust_powerless_components(&mut self) {\n    // If white+black is equal to 100% (after normalization), it defines an achromatic color,\n    // i.e. some shade of gray, without any hint of the chosen hue. In this case, the hue component is powerless.\n    if (self.w + self.b - 100.0).abs() < f32::EPSILON {\n      self.h = f32::NAN;\n    }\n  }\n\n  fn adjust_hue(&mut self, other: &mut Self, method: HueInterpolationMethod) {\n    method.interpolate(&mut self.h, &mut other.h);\n  }\n\n  interpolate!(h, w, b);\n}\n\nimpl HueInterpolationMethod {\n  fn interpolate(&self, a: &mut f32, b: &mut f32) {\n    // https://drafts.csswg.org/css-color/#hue-interpolation\n    if *self != HueInterpolationMethod::Specified {\n      *a = ((*a % 360.0) + 360.0) % 360.0;\n      *b = ((*b % 360.0) + 360.0) % 360.0;\n    }\n\n    match self {\n      HueInterpolationMethod::Shorter => {\n        // https://www.w3.org/TR/css-color-4/#hue-shorter\n        let delta = *b - *a;\n        if delta > 180.0 {\n          *a += 360.0;\n        } else if delta < -180.0 {\n          *b += 360.0;\n        }\n      }\n      HueInterpolationMethod::Longer => {\n        // https://www.w3.org/TR/css-color-4/#hue-longer\n        let delta = *b - *a;\n        if 0.0 < delta && delta < 180.0 {\n          *a += 360.0;\n        } else if -180.0 < delta && delta < 0.0 {\n          *b += 360.0;\n        }\n      }\n      HueInterpolationMethod::Increasing => {\n        // https://www.w3.org/TR/css-color-4/#hue-increasing\n        if *b < *a {\n          *b += 360.0;\n        }\n      }\n      HueInterpolationMethod::Decreasing => {\n        // https://www.w3.org/TR/css-color-4/#hue-decreasing\n        if *a < *b {\n          *a += 360.0;\n        }\n      }\n      HueInterpolationMethod::Specified => {}\n    }\n  }\n}\n\n#[cfg(feature = \"visitor\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"visitor\")))]\nimpl<'i, V: ?Sized + Visitor<'i, T>, T: Visit<'i, T, V>> Visit<'i, T, V> for RGBA {\n  const CHILD_TYPES: VisitTypes = VisitTypes::empty();\n  fn visit_children(&mut self, _: &mut V) -> Result<(), V::Error> {\n    Ok(())\n  }\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Parse, ToCss)]\n#[css(case = lower)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"lowercase\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n/// A CSS [system color](https://drafts.csswg.org/css-color/#css-system-colors) keyword.\npub enum SystemColor {\n  /// Background of accented user interface controls.\n  AccentColor,\n  /// Text of accented user interface controls.\n  AccentColorText,\n  /// Text in active links. For light backgrounds, traditionally red.\n  ActiveText,\n  /// The base border color for push buttons.\n  ButtonBorder,\n  /// The face background color for push buttons.\n  ButtonFace,\n  /// Text on push buttons.\n  ButtonText,\n  /// Background of application content or documents.\n  Canvas,\n  /// Text in application content or documents.\n  CanvasText,\n  /// Background of input fields.\n  Field,\n  /// Text in input fields.\n  FieldText,\n  /// Disabled text. (Often, but not necessarily, gray.)\n  GrayText,\n  /// Background of selected text, for example from ::selection.\n  Highlight,\n  /// Text of selected text.\n  HighlightText,\n  /// Text in non-active, non-visited links. For light backgrounds, traditionally blue.\n  LinkText,\n  /// Background of text that has been specially marked (such as by the HTML mark element).\n  Mark,\n  /// Text that has been specially marked (such as by the HTML mark element).\n  MarkText,\n  /// Background of selected items, for example a selected checkbox.\n  SelectedItem,\n  /// Text of selected items.\n  SelectedItemText,\n  /// Text in visited links. For light backgrounds, traditionally purple.\n  VisitedText,\n\n  // Deprecated colors: https://drafts.csswg.org/css-color/#deprecated-system-colors\n  /// Active window border. Same as ButtonBorder.\n  ActiveBorder,\n  /// Active window caption. Same as Canvas.\n  ActiveCaption,\n  /// Background color of multiple document interface. Same as Canvas.\n  AppWorkspace,\n  /// Desktop background. Same as Canvas.\n  Background,\n  /// The color of the border facing the light source for 3-D elements that appear 3-D due to one layer of surrounding border. Same as ButtonFace.\n  ButtonHighlight,\n  /// The color of the border away from the light source for 3-D elements that appear 3-D due to one layer of surrounding border. Same as ButtonFace.\n  ButtonShadow,\n  /// Text in caption, size box, and scrollbar arrow box. Same as CanvasText.\n  CaptionText,\n  /// Inactive window border. Same as ButtonBorder.\n  InactiveBorder,\n  /// Inactive window caption. Same as Canvas.\n  InactiveCaption,\n  /// Color of text in an inactive caption. Same as GrayText.\n  InactiveCaptionText,\n  /// Background color for tooltip controls. Same as Canvas.\n  InfoBackground,\n  /// Text color for tooltip controls. Same as CanvasText.\n  InfoText,\n  /// Menu background. Same as Canvas.\n  Menu,\n  /// Text in menus. Same as CanvasText.\n  MenuText,\n  /// Scroll bar gray area. Same as Canvas.\n  Scrollbar,\n  /// The color of the darker (generally outer) of the two borders away from the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border. Same as ButtonBorder.\n  ThreeDDarkShadow,\n  /// The face background color for 3-D elements that appear 3-D due to two concentric layers of surrounding border. Same as ButtonFace.\n  ThreeDFace,\n  /// The color of the lighter (generally outer) of the two borders facing the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border. Same as ButtonBorder.\n  ThreeDHighlight,\n  /// The color of the darker (generally inner) of the two borders facing the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border. Same as ButtonBorder.\n  ThreeDLightShadow,\n  /// The color of the lighter (generally inner) of the two borders away from the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border. Same as ButtonBorder.\n  ThreeDShadow,\n  /// Window background. Same as Canvas.\n  Window,\n  /// Window frame. Same as ButtonBorder.\n  WindowFrame,\n  /// Text in windows. Same as CanvasText.\n  WindowText,\n}\n\nimpl IsCompatible for SystemColor {\n  fn is_compatible(&self, browsers: Browsers) -> bool {\n    use SystemColor::*;\n    match self {\n      AccentColor | AccentColorText => Feature::AccentSystemColor.is_compatible(browsers),\n      _ => true,\n    }\n  }\n}\n"
  },
  {
    "path": "src/values/easing.rs",
    "content": "//! CSS easing functions.\n\nuse crate::error::{ParserError, PrinterError};\nuse crate::printer::Printer;\nuse crate::traits::{Parse, ToCss};\nuse crate::values::number::{CSSInteger, CSSNumber};\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse cssparser::*;\nuse std::fmt::Write;\n\n/// A CSS [easing function](https://www.w3.org/TR/css-easing-1/#easing-functions).\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum EasingFunction {\n  /// A linear easing function.\n  Linear,\n  /// Equivalent to `cubic-bezier(0.25, 0.1, 0.25, 1)`.\n  Ease,\n  /// Equivalent to `cubic-bezier(0.42, 0, 1, 1)`.\n  EaseIn,\n  /// Equivalent to `cubic-bezier(0, 0, 0.58, 1)`.\n  EaseOut,\n  /// Equivalent to `cubic-bezier(0.42, 0, 0.58, 1)`.\n  EaseInOut,\n  /// A custom cubic Bézier easing function.\n  CubicBezier {\n    /// The x-position of the first point in the curve.\n    x1: CSSNumber,\n    /// The y-position of the first point in the curve.\n    y1: CSSNumber,\n    /// The x-position of the second point in the curve.\n    x2: CSSNumber,\n    /// The y-position of the second point in the curve.\n    y2: CSSNumber,\n  },\n  /// A step easing function.\n  Steps {\n    /// The number of intervals in the function.\n    count: CSSInteger,\n    /// The step position.\n    #[cfg_attr(feature = \"serde\", serde(default))]\n    position: StepPosition,\n  },\n}\n\nimpl EasingFunction {\n  /// Returns whether the easing function is equivalent to the `ease` keyword.\n  pub fn is_ease(&self) -> bool {\n    *self == EasingFunction::Ease\n      || *self\n        == EasingFunction::CubicBezier {\n          x1: 0.25,\n          y1: 0.1,\n          x2: 0.25,\n          y2: 1.0,\n        }\n  }\n}\n\nimpl<'i> Parse<'i> for EasingFunction {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let location = input.current_source_location();\n    if let Ok(ident) = input.try_parse(|i| i.expect_ident_cloned()) {\n      let keyword = match_ignore_ascii_case! { &ident,\n        \"linear\" => EasingFunction::Linear,\n        \"ease\" => EasingFunction::Ease,\n        \"ease-in\" => EasingFunction::EaseIn,\n        \"ease-out\" => EasingFunction::EaseOut,\n        \"ease-in-out\" => EasingFunction::EaseInOut,\n        \"step-start\" => EasingFunction::Steps { count: 1, position: StepPosition::Start },\n        \"step-end\" => EasingFunction::Steps { count: 1, position: StepPosition::End },\n        _ => return Err(location.new_unexpected_token_error(Token::Ident(ident.clone())))\n      };\n      return Ok(keyword);\n    }\n\n    let function = input.expect_function()?.clone();\n    input.parse_nested_block(|input| {\n      match_ignore_ascii_case! { &function,\n        \"cubic-bezier\" => {\n          let x1 = CSSNumber::parse(input)?;\n          input.expect_comma()?;\n          let y1 = CSSNumber::parse(input)?;\n          input.expect_comma()?;\n          let x2 = CSSNumber::parse(input)?;\n          input.expect_comma()?;\n          let y2 = CSSNumber::parse(input)?;\n          Ok(EasingFunction::CubicBezier { x1, y1, x2, y2 })\n        },\n        \"steps\" => {\n          let count = CSSInteger::parse(input)?;\n          let position = input.try_parse(|input| {\n            input.expect_comma()?;\n            StepPosition::parse(input)\n          }).unwrap_or_default();\n          Ok(EasingFunction::Steps { count, position })\n        },\n        _ => return Err(location.new_unexpected_token_error(Token::Ident(function.clone())))\n      }\n    })\n  }\n}\n\nimpl ToCss for EasingFunction {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      EasingFunction::Linear => dest.write_str(\"linear\"),\n      EasingFunction::Ease => dest.write_str(\"ease\"),\n      EasingFunction::EaseIn => dest.write_str(\"ease-in\"),\n      EasingFunction::EaseOut => dest.write_str(\"ease-out\"),\n      EasingFunction::EaseInOut => dest.write_str(\"ease-in-out\"),\n      _ if self.is_ease() => dest.write_str(\"ease\"),\n      x if *x\n        == EasingFunction::CubicBezier {\n          x1: 0.42,\n          y1: 0.0,\n          x2: 1.0,\n          y2: 1.0,\n        } =>\n      {\n        dest.write_str(\"ease-in\")\n      }\n      x if *x\n        == EasingFunction::CubicBezier {\n          x1: 0.0,\n          y1: 0.0,\n          x2: 0.58,\n          y2: 1.0,\n        } =>\n      {\n        dest.write_str(\"ease-out\")\n      }\n      x if *x\n        == EasingFunction::CubicBezier {\n          x1: 0.42,\n          y1: 0.0,\n          x2: 0.58,\n          y2: 1.0,\n        } =>\n      {\n        dest.write_str(\"ease-in-out\")\n      }\n      EasingFunction::CubicBezier { x1, y1, x2, y2 } => {\n        dest.write_str(\"cubic-bezier(\")?;\n        x1.to_css(dest)?;\n        dest.delim(',', false)?;\n        y1.to_css(dest)?;\n        dest.delim(',', false)?;\n        x2.to_css(dest)?;\n        dest.delim(',', false)?;\n        y2.to_css(dest)?;\n        dest.write_char(')')\n      }\n      EasingFunction::Steps {\n        count: 1,\n        position: StepPosition::Start,\n      } => dest.write_str(\"step-start\"),\n      EasingFunction::Steps {\n        count: 1,\n        position: StepPosition::End,\n      } => dest.write_str(\"step-end\"),\n      EasingFunction::Steps { count, position } => {\n        dest.write_str(\"steps(\")?;\n        write!(dest, \"{}\", count)?;\n        dest.delim(',', false)?;\n        position.to_css(dest)?;\n        dest.write_char(')')\n      }\n    }\n  }\n}\n\nimpl EasingFunction {\n  /// Returns whether the given string is a valid easing function name.\n  pub fn is_ident(s: &str) -> bool {\n    match s {\n      \"linear\" | \"ease\" | \"ease-in\" | \"ease-out\" | \"ease-in-out\" | \"step-start\" | \"step-end\" => true,\n      _ => false,\n    }\n  }\n}\n\n/// A [step position](https://www.w3.org/TR/css-easing-1/#step-position), used within the `steps()` function.\n#[derive(Debug, Clone, PartialEq, ToCss)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub enum StepPosition {\n  /// The first rise occurs at input progress value of 0.\n  Start,\n  /// The last rise occurs at input progress value of 1.\n  End,\n  /// All rises occur within the range (0, 1).\n  JumpNone,\n  /// The first rise occurs at input progress value of 0 and the last rise occurs at input progress value of 1.\n  JumpBoth,\n}\n\nimpl Default for StepPosition {\n  fn default() -> Self {\n    StepPosition::End\n  }\n}\n\nimpl<'i> Parse<'i> for StepPosition {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let location = input.current_source_location();\n    let ident = input.expect_ident()?;\n    let keyword = match_ignore_ascii_case! { &ident,\n      \"start\" => StepPosition::Start,\n      \"end\" => StepPosition::End,\n      \"jump-start\" => StepPosition::Start,\n      \"jump-end\" => StepPosition::End,\n      \"jump-none\" => StepPosition::JumpNone,\n      \"jump-both\" => StepPosition::JumpBoth,\n      _ => return Err(location.new_unexpected_token_error(Token::Ident(ident.clone())))\n    };\n    Ok(keyword)\n  }\n}\n"
  },
  {
    "path": "src/values/gradient.rs",
    "content": "//! CSS gradient values.\n\nuse super::angle::{Angle, AnglePercentage};\nuse super::color::{ColorFallbackKind, CssColor};\nuse super::length::{Length, LengthPercentage};\nuse super::number::CSSNumber;\nuse super::percentage::{DimensionPercentage, NumberOrPercentage, Percentage};\nuse super::position::{HorizontalPositionKeyword, VerticalPositionKeyword};\nuse super::position::{Position, PositionComponent};\nuse crate::compat;\nuse crate::error::{ParserError, PrinterError};\nuse crate::macros::enum_property;\nuse crate::prefixes::Feature;\nuse crate::printer::Printer;\nuse crate::targets::{should_compile, Browsers, Targets};\nuse crate::traits::{IsCompatible, Parse, ToCss, TrySign, Zero};\nuse crate::vendor_prefix::VendorPrefix;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse cssparser::*;\nuse std::f32::consts::PI;\n\n#[cfg(feature = \"serde\")]\nuse crate::serialization::ValueWrapper;\n\n/// A CSS [`<gradient>`](https://www.w3.org/TR/css-images-3/#gradients) value.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum Gradient {\n  /// A `linear-gradient()`, and its vendor prefix.\n  Linear(LinearGradient),\n  /// A `repeating-linear-gradient()`, and its vendor prefix.\n  RepeatingLinear(LinearGradient),\n  /// A `radial-gradient()`, and its vendor prefix.\n  Radial(RadialGradient),\n  /// A `repeating-radial-gradient`, and its vendor prefix.\n  RepeatingRadial(RadialGradient),\n  /// A `conic-gradient()`.\n  Conic(ConicGradient),\n  /// A `repeating-conic-gradient()`.\n  RepeatingConic(ConicGradient),\n  /// A legacy `-webkit-gradient()`.\n  #[cfg_attr(feature = \"serde\", serde(rename = \"webkit-gradient\"))]\n  WebKitGradient(WebKitGradient),\n}\n\nimpl Gradient {\n  /// Returns the vendor prefix of the gradient.\n  pub fn get_vendor_prefix(&self) -> VendorPrefix {\n    match self {\n      Gradient::Linear(LinearGradient { vendor_prefix, .. })\n      | Gradient::RepeatingLinear(LinearGradient { vendor_prefix, .. })\n      | Gradient::Radial(RadialGradient { vendor_prefix, .. })\n      | Gradient::RepeatingRadial(RadialGradient { vendor_prefix, .. }) => *vendor_prefix,\n      Gradient::WebKitGradient(_) => VendorPrefix::WebKit,\n      _ => VendorPrefix::None,\n    }\n  }\n\n  /// Returns the vendor prefixes needed for the given browser targets.\n  pub fn get_necessary_prefixes(&self, targets: Targets) -> VendorPrefix {\n    macro_rules! get_prefixes {\n      ($feature: ident, $prefix: expr) => {\n        targets.prefixes($prefix, Feature::$feature)\n      };\n    }\n\n    match self {\n      Gradient::Linear(linear) => get_prefixes!(LinearGradient, linear.vendor_prefix),\n      Gradient::RepeatingLinear(linear) => get_prefixes!(RepeatingLinearGradient, linear.vendor_prefix),\n      Gradient::Radial(radial) => get_prefixes!(RadialGradient, radial.vendor_prefix),\n      Gradient::RepeatingRadial(radial) => get_prefixes!(RepeatingRadialGradient, radial.vendor_prefix),\n      _ => VendorPrefix::None,\n    }\n  }\n\n  /// Returns a copy of the gradient with the given vendor prefix.\n  pub fn get_prefixed(&self, prefix: VendorPrefix) -> Gradient {\n    match self {\n      Gradient::Linear(linear) => {\n        let mut new_linear = linear.clone();\n        let needs_legacy_direction = linear.vendor_prefix == VendorPrefix::None && prefix != VendorPrefix::None;\n        if needs_legacy_direction {\n          new_linear.direction = convert_to_legacy_direction(&new_linear.direction);\n        }\n        new_linear.vendor_prefix = prefix;\n        Gradient::Linear(new_linear)\n      }\n      Gradient::RepeatingLinear(linear) => {\n        let mut new_linear = linear.clone();\n        let needs_legacy_direction = linear.vendor_prefix == VendorPrefix::None && prefix != VendorPrefix::None;\n        if needs_legacy_direction {\n          new_linear.direction = convert_to_legacy_direction(&new_linear.direction);\n        }\n        new_linear.vendor_prefix = prefix;\n        Gradient::RepeatingLinear(new_linear)\n      }\n      Gradient::Radial(radial) => Gradient::Radial(RadialGradient {\n        vendor_prefix: prefix,\n        ..radial.clone()\n      }),\n      Gradient::RepeatingRadial(radial) => Gradient::RepeatingRadial(RadialGradient {\n        vendor_prefix: prefix,\n        ..radial.clone()\n      }),\n      _ => self.clone(),\n    }\n  }\n\n  /// Attempts to convert the gradient to the legacy `-webkit-gradient()` syntax.\n  ///\n  /// Returns an error in case the conversion is not possible.\n  pub fn get_legacy_webkit(&self) -> Result<Gradient, ()> {\n    Ok(Gradient::WebKitGradient(WebKitGradient::from_standard(self)?))\n  }\n\n  /// Returns the color fallback types needed for the given browser targets.\n  pub fn get_necessary_fallbacks(&self, targets: Targets) -> ColorFallbackKind {\n    match self {\n      Gradient::Linear(LinearGradient { items, .. })\n      | Gradient::Radial(RadialGradient { items, .. })\n      | Gradient::RepeatingLinear(LinearGradient { items, .. })\n      | Gradient::RepeatingRadial(RadialGradient { items, .. }) => {\n        let mut fallbacks = ColorFallbackKind::empty();\n        for item in items {\n          fallbacks |= item.get_necessary_fallbacks(targets)\n        }\n        fallbacks\n      }\n      Gradient::Conic(ConicGradient { items, .. }) | Gradient::RepeatingConic(ConicGradient { items, .. }) => {\n        let mut fallbacks = ColorFallbackKind::empty();\n        for item in items {\n          fallbacks |= item.get_necessary_fallbacks(targets)\n        }\n        fallbacks\n      }\n      Gradient::WebKitGradient(..) => ColorFallbackKind::empty(),\n    }\n  }\n\n  /// Returns a fallback gradient for the given color fallback type.\n  pub fn get_fallback(&self, kind: ColorFallbackKind) -> Gradient {\n    match self {\n      Gradient::Linear(g) => Gradient::Linear(g.get_fallback(kind)),\n      Gradient::RepeatingLinear(g) => Gradient::RepeatingLinear(g.get_fallback(kind)),\n      Gradient::Radial(g) => Gradient::Radial(g.get_fallback(kind)),\n      Gradient::RepeatingRadial(g) => Gradient::RepeatingRadial(g.get_fallback(kind)),\n      Gradient::Conic(g) => Gradient::Conic(g.get_fallback(kind)),\n      Gradient::RepeatingConic(g) => Gradient::RepeatingConic(g.get_fallback(kind)),\n      Gradient::WebKitGradient(g) => Gradient::WebKitGradient(g.get_fallback(kind)),\n    }\n  }\n}\n\nimpl<'i> Parse<'i> for Gradient {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let location = input.current_source_location();\n    let func = input.expect_function()?.clone();\n    input.parse_nested_block(|input| {\n      match_ignore_ascii_case! { &func,\n        \"linear-gradient\" => Ok(Gradient::Linear(LinearGradient::parse(input, VendorPrefix::None)?)),\n        \"repeating-linear-gradient\" => Ok(Gradient::RepeatingLinear(LinearGradient::parse(input, VendorPrefix::None)?)),\n        \"radial-gradient\" => Ok(Gradient::Radial(RadialGradient::parse(input, VendorPrefix::None)?)),\n        \"repeating-radial-gradient\" => Ok(Gradient::RepeatingRadial(RadialGradient::parse(input, VendorPrefix::None)?)),\n        \"conic-gradient\" => Ok(Gradient::Conic(ConicGradient::parse(input)?)),\n        \"repeating-conic-gradient\" => Ok(Gradient::RepeatingConic(ConicGradient::parse(input)?)),\n        \"-webkit-linear-gradient\" => Ok(Gradient::Linear(LinearGradient::parse(input, VendorPrefix::WebKit)?)),\n        \"-webkit-repeating-linear-gradient\" => Ok(Gradient::RepeatingLinear(LinearGradient::parse(input, VendorPrefix::WebKit)?)),\n        \"-webkit-radial-gradient\" => Ok(Gradient::Radial(RadialGradient::parse(input, VendorPrefix::WebKit)?)),\n        \"-webkit-repeating-radial-gradient\" => Ok(Gradient::RepeatingRadial(RadialGradient::parse(input, VendorPrefix::WebKit)?)),\n        \"-moz-linear-gradient\" => Ok(Gradient::Linear(LinearGradient::parse(input, VendorPrefix::Moz)?)),\n        \"-moz-repeating-linear-gradient\" => Ok(Gradient::RepeatingLinear(LinearGradient::parse(input, VendorPrefix::Moz)?)),\n        \"-moz-radial-gradient\" => Ok(Gradient::Radial(RadialGradient::parse(input, VendorPrefix::Moz)?)),\n        \"-moz-repeating-radial-gradient\" => Ok(Gradient::RepeatingRadial(RadialGradient::parse(input, VendorPrefix::Moz)?)),\n        \"-o-linear-gradient\" => Ok(Gradient::Linear(LinearGradient::parse(input, VendorPrefix::O)?)),\n        \"-o-repeating-linear-gradient\" => Ok(Gradient::RepeatingLinear(LinearGradient::parse(input, VendorPrefix::O)?)),\n        \"-o-radial-gradient\" => Ok(Gradient::Radial(RadialGradient::parse(input, VendorPrefix::O)?)),\n        \"-o-repeating-radial-gradient\" => Ok(Gradient::RepeatingRadial(RadialGradient::parse(input, VendorPrefix::O)?)),\n        \"-webkit-gradient\" => Ok(Gradient::WebKitGradient(WebKitGradient::parse(input)?)),\n        _ => Err(location.new_unexpected_token_error(cssparser::Token::Ident(func.clone())))\n      }\n    })\n  }\n}\n\nimpl ToCss for Gradient {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    let (f, prefix) = match self {\n      Gradient::Linear(g) => (\"linear-gradient(\", Some(g.vendor_prefix)),\n      Gradient::RepeatingLinear(g) => (\"repeating-linear-gradient(\", Some(g.vendor_prefix)),\n      Gradient::Radial(g) => (\"radial-gradient(\", Some(g.vendor_prefix)),\n      Gradient::RepeatingRadial(g) => (\"repeating-radial-gradient(\", Some(g.vendor_prefix)),\n      Gradient::Conic(_) => (\"conic-gradient(\", None),\n      Gradient::RepeatingConic(_) => (\"repeating-conic-gradient(\", None),\n      Gradient::WebKitGradient(_) => (\"-webkit-gradient(\", None),\n    };\n\n    if let Some(prefix) = prefix {\n      prefix.to_css(dest)?;\n    }\n\n    dest.write_str(f)?;\n\n    match self {\n      Gradient::Linear(linear) | Gradient::RepeatingLinear(linear) => {\n        linear.to_css(dest, linear.vendor_prefix != VendorPrefix::None)?\n      }\n      Gradient::Radial(radial) | Gradient::RepeatingRadial(radial) => radial.to_css(dest)?,\n      Gradient::Conic(conic) | Gradient::RepeatingConic(conic) => conic.to_css(dest)?,\n      Gradient::WebKitGradient(g) => g.to_css(dest)?,\n    }\n\n    dest.write_char(')')\n  }\n}\n\n/// A CSS [`linear-gradient()`](https://www.w3.org/TR/css-images-3/#linear-gradients) or `repeating-linear-gradient()`.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"camelCase\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub struct LinearGradient {\n  /// The vendor prefixes for the gradient.\n  pub vendor_prefix: VendorPrefix,\n  /// The direction of the gradient.\n  pub direction: LineDirection,\n  /// The color stops and transition hints for the gradient.\n  pub items: Vec<GradientItem<LengthPercentage>>,\n}\n\nimpl LinearGradient {\n  fn parse<'i, 't>(\n    input: &mut Parser<'i, 't>,\n    vendor_prefix: VendorPrefix,\n  ) -> Result<LinearGradient, ParseError<'i, ParserError<'i>>> {\n    let direction = if let Ok(direction) =\n      input.try_parse(|input| LineDirection::parse(input, vendor_prefix != VendorPrefix::None))\n    {\n      input.expect_comma()?;\n      direction\n    } else {\n      LineDirection::Vertical(VerticalPositionKeyword::Bottom)\n    };\n    let items = parse_items(input)?;\n    Ok(LinearGradient {\n      direction,\n      items,\n      vendor_prefix,\n    })\n  }\n\n  fn to_css<W>(&self, dest: &mut Printer<W>, is_prefixed: bool) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    let angle = match &self.direction {\n      LineDirection::Vertical(VerticalPositionKeyword::Bottom) => 180.0,\n      LineDirection::Vertical(VerticalPositionKeyword::Top) => 0.0,\n      LineDirection::Angle(angle) => angle.to_degrees(),\n      _ => -1.0,\n    };\n\n    // We can omit `to bottom` or `180deg` because it is the default.\n    if angle == 180.0 {\n      serialize_items(&self.items, dest)\n\n    // If we have `to top` or `0deg`, and all of the positions and hints are percentages,\n    // we can flip the gradient the other direction and omit the direction.\n    } else if angle == 0.0\n      && dest.minify\n      && self.items.iter().all(|item| {\n        matches!(\n          item,\n          GradientItem::Hint(LengthPercentage::Percentage(_))\n            | GradientItem::ColorStop(ColorStop {\n              position: None | Some(LengthPercentage::Percentage(_)),\n              ..\n            })\n        )\n      })\n    {\n      let items: Vec<GradientItem<LengthPercentage>> = self\n        .items\n        .iter()\n        .rev()\n        .map(|item| {\n          // Flip percentages.\n          match item {\n            GradientItem::Hint(LengthPercentage::Percentage(p)) => {\n              GradientItem::Hint(LengthPercentage::Percentage(Percentage(1.0 - p.0)))\n            }\n            GradientItem::ColorStop(ColorStop { color, position }) => GradientItem::ColorStop(ColorStop {\n              color: color.clone(),\n              position: position.clone().map(|p| match p {\n                LengthPercentage::Percentage(p) => LengthPercentage::Percentage(Percentage(1.0 - p.0)),\n                _ => unreachable!(),\n              }),\n            }),\n            _ => unreachable!(),\n          }\n        })\n        .collect();\n      serialize_items(&items, dest)\n    } else {\n      if self.direction != LineDirection::Vertical(VerticalPositionKeyword::Bottom)\n        && self.direction != LineDirection::Angle(Angle::Deg(180.0))\n      {\n        self.direction.to_css(dest, is_prefixed)?;\n        dest.delim(',', false)?;\n      }\n\n      serialize_items(&self.items, dest)\n    }\n  }\n\n  fn get_fallback(&self, kind: ColorFallbackKind) -> LinearGradient {\n    LinearGradient {\n      direction: self.direction.clone(),\n      items: self.items.iter().map(|item| item.get_fallback(kind)).collect(),\n      vendor_prefix: self.vendor_prefix,\n    }\n  }\n}\n\nimpl IsCompatible for LinearGradient {\n  fn is_compatible(&self, browsers: Browsers) -> bool {\n    self.items.iter().all(|item| item.is_compatible(browsers))\n  }\n}\n\n/// A CSS [`radial-gradient()`](https://www.w3.org/TR/css-images-3/#radial-gradients) or `repeating-radial-gradient()`.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"camelCase\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub struct RadialGradient {\n  /// The vendor prefixes for the gradient.\n  pub vendor_prefix: VendorPrefix,\n  /// The shape of the gradient.\n  pub shape: EndingShape,\n  /// The position of the gradient.\n  pub position: Position,\n  /// The color stops and transition hints for the gradient.\n  pub items: Vec<GradientItem<LengthPercentage>>,\n}\n\nimpl<'i> RadialGradient {\n  fn parse<'t>(\n    input: &mut Parser<'i, 't>,\n    vendor_prefix: VendorPrefix,\n  ) -> Result<RadialGradient, ParseError<'i, ParserError<'i>>> {\n    let shape = input.try_parse(EndingShape::parse).ok();\n    let position = input\n      .try_parse(|input| {\n        input.expect_ident_matching(\"at\")?;\n        Position::parse(input)\n      })\n      .ok();\n\n    if shape.is_some() || position.is_some() {\n      input.expect_comma()?;\n    }\n\n    let items = parse_items(input)?;\n    Ok(RadialGradient {\n      shape: shape.unwrap_or_default(),\n      position: position.unwrap_or(Position::center()),\n      items,\n      vendor_prefix,\n    })\n  }\n}\n\nimpl ToCss for RadialGradient {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    if self.shape != EndingShape::default() {\n      self.shape.to_css(dest)?;\n      if self.position.is_center() {\n        dest.delim(',', false)?;\n      } else {\n        dest.write_char(' ')?;\n      }\n    }\n\n    if !self.position.is_center() {\n      dest.write_str(\"at \")?;\n      self.position.to_css(dest)?;\n      dest.delim(',', false)?;\n    }\n\n    serialize_items(&self.items, dest)\n  }\n}\n\nimpl RadialGradient {\n  fn get_fallback(&self, kind: ColorFallbackKind) -> RadialGradient {\n    RadialGradient {\n      shape: self.shape.clone(),\n      position: self.position.clone(),\n      items: self.items.iter().map(|item| item.get_fallback(kind)).collect(),\n      vendor_prefix: self.vendor_prefix,\n    }\n  }\n}\n\nimpl IsCompatible for RadialGradient {\n  fn is_compatible(&self, browsers: Browsers) -> bool {\n    self.items.iter().all(|item| item.is_compatible(browsers))\n  }\n}\n\n/// The direction of a CSS `linear-gradient()`.\n///\n/// See [LinearGradient](LinearGradient).\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub enum LineDirection {\n  /// An angle.\n  #[cfg_attr(feature = \"serde\", serde(with = \"ValueWrapper::<Angle>\"))]\n  Angle(Angle),\n  /// A horizontal position keyword, e.g. `left` or `right.\n  #[cfg_attr(feature = \"serde\", serde(with = \"ValueWrapper::<HorizontalPositionKeyword>\"))]\n  Horizontal(HorizontalPositionKeyword),\n  /// A vertical posision keyword, e.g. `top` or `bottom`.\n  #[cfg_attr(feature = \"serde\", serde(with = \"ValueWrapper::<VerticalPositionKeyword>\"))]\n  Vertical(VerticalPositionKeyword),\n  /// A corner, e.g. `bottom left` or `top right`.\n  Corner {\n    /// A horizontal position keyword, e.g. `left` or `right.\n    horizontal: HorizontalPositionKeyword,\n    /// A vertical posision keyword, e.g. `top` or `bottom`.\n    vertical: VerticalPositionKeyword,\n  },\n}\n\nimpl LineDirection {\n  fn parse<'i, 't>(\n    input: &mut Parser<'i, 't>,\n    is_prefixed: bool,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    // Spec allows unitless zero angles for gradients.\n    // https://w3c.github.io/csswg-drafts/css-images-3/#linear-gradient-syntax\n    if let Ok(angle) = input.try_parse(Angle::parse_with_unitless_zero) {\n      return Ok(LineDirection::Angle(angle));\n    }\n\n    if !is_prefixed {\n      input.expect_ident_matching(\"to\")?;\n    }\n\n    if let Ok(x) = input.try_parse(HorizontalPositionKeyword::parse) {\n      if let Ok(y) = input.try_parse(VerticalPositionKeyword::parse) {\n        return Ok(LineDirection::Corner {\n          horizontal: x,\n          vertical: y,\n        });\n      }\n      return Ok(LineDirection::Horizontal(x));\n    }\n\n    let y = VerticalPositionKeyword::parse(input)?;\n    if let Ok(x) = input.try_parse(HorizontalPositionKeyword::parse) {\n      return Ok(LineDirection::Corner {\n        horizontal: x,\n        vertical: y,\n      });\n    }\n    Ok(LineDirection::Vertical(y))\n  }\n\n  fn to_css<W>(&self, dest: &mut Printer<W>, is_prefixed: bool) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      LineDirection::Angle(angle) => angle.to_css(dest),\n      LineDirection::Horizontal(k) => {\n        if dest.minify {\n          match k {\n            HorizontalPositionKeyword::Left => dest.write_str(\"270deg\"),\n            HorizontalPositionKeyword::Right => dest.write_str(\"90deg\"),\n          }\n        } else {\n          if !is_prefixed {\n            dest.write_str(\"to \")?;\n          }\n          k.to_css(dest)\n        }\n      }\n      LineDirection::Vertical(k) => {\n        if dest.minify {\n          match k {\n            VerticalPositionKeyword::Top => dest.write_str(\"0deg\"),\n            VerticalPositionKeyword::Bottom => dest.write_str(\"180deg\"),\n          }\n        } else {\n          if !is_prefixed {\n            dest.write_str(\"to \")?;\n          }\n          k.to_css(dest)\n        }\n      }\n      LineDirection::Corner { horizontal, vertical } => {\n        if !is_prefixed {\n          dest.write_str(\"to \")?;\n        }\n        vertical.to_css(dest)?;\n        dest.write_char(' ')?;\n        horizontal.to_css(dest)\n      }\n    }\n  }\n}\n\n/// Converts a standard gradient direction to its legacy vendor-prefixed form.\n///\n/// Inverts keyword-based directions (e.g., `to bottom` → `top`) for compatibility\n/// with legacy prefixed syntaxes.\n///\n/// See: https://github.com/parcel-bundler/lightningcss/issues/918\nfn convert_to_legacy_direction(direction: &LineDirection) -> LineDirection {\n  match direction {\n    LineDirection::Horizontal(HorizontalPositionKeyword::Left) => {\n      LineDirection::Horizontal(HorizontalPositionKeyword::Right)\n    }\n    LineDirection::Horizontal(HorizontalPositionKeyword::Right) => {\n      LineDirection::Horizontal(HorizontalPositionKeyword::Left)\n    }\n    LineDirection::Vertical(VerticalPositionKeyword::Top) => {\n      LineDirection::Vertical(VerticalPositionKeyword::Bottom)\n    }\n    LineDirection::Vertical(VerticalPositionKeyword::Bottom) => {\n      LineDirection::Vertical(VerticalPositionKeyword::Top)\n    }\n    LineDirection::Corner { horizontal, vertical } => LineDirection::Corner {\n      horizontal: match horizontal {\n        HorizontalPositionKeyword::Left => HorizontalPositionKeyword::Right,\n        HorizontalPositionKeyword::Right => HorizontalPositionKeyword::Left,\n      },\n      vertical: match vertical {\n        VerticalPositionKeyword::Top => VerticalPositionKeyword::Bottom,\n        VerticalPositionKeyword::Bottom => VerticalPositionKeyword::Top,\n      },\n    },\n    LineDirection::Angle(angle) => {\n      let angle = angle.clone();\n      let deg = match angle {\n        Angle::Deg(n) => convert_to_legacy_degree(n),\n        Angle::Rad(n) => {\n          let n = n / (2.0 * PI) * 360.0;\n          convert_to_legacy_degree(n)\n        }\n        Angle::Grad(n) => {\n          let n = n / 400.0 * 360.0;\n          convert_to_legacy_degree(n)\n        }\n        Angle::Turn(n) => {\n          let n = n * 360.0;\n          convert_to_legacy_degree(n)\n        }\n      };\n      LineDirection::Angle(Angle::Deg(deg))\n    }\n  }\n}\n\nfn convert_to_legacy_degree(degree: f32) -> f32 {\n  // Add 90 degrees\n  let n = (450.0 - degree).abs() % 360.0;\n  // Round the number to 3 decimal places\n  (n * 1000.0).round() / 1000.0\n}\n\n/// A `radial-gradient()` [ending shape](https://www.w3.org/TR/css-images-3/#valdef-radial-gradient-ending-shape).\n///\n/// See [RadialGradient](RadialGradient).\n#[derive(Debug, Clone, PartialEq, Parse, ToCss)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub enum EndingShape {\n  // Note: Ellipse::parse MUST run before Circle::parse for this to be correct.\n  /// An ellipse.\n  Ellipse(Ellipse),\n  /// A circle.\n  Circle(Circle),\n}\n\nimpl Default for EndingShape {\n  fn default() -> EndingShape {\n    EndingShape::Ellipse(Ellipse::Extent(ShapeExtent::FarthestCorner))\n  }\n}\n\n/// A circle ending shape for a `radial-gradient()`.\n///\n/// See [RadialGradient](RadialGradient).\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub enum Circle {\n  /// A circle with a specified radius.\n  Radius(Length),\n  /// A shape extent keyword.\n  Extent(ShapeExtent),\n}\n\nimpl<'i> Parse<'i> for Circle {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    if let Ok(extent) = input.try_parse(ShapeExtent::parse) {\n      // The `circle` keyword is required. If it's not there, then it's an ellipse.\n      input.expect_ident_matching(\"circle\")?;\n      return Ok(Circle::Extent(extent));\n    }\n\n    if let Ok(length) = input.try_parse(Length::parse) {\n      // The `circle` keyword is optional if there is only a single length.\n      // We are assuming here that Ellipse::parse ran first.\n      let _ = input.try_parse(|input| input.expect_ident_matching(\"circle\"));\n      return Ok(Circle::Radius(length));\n    }\n\n    if input.try_parse(|input| input.expect_ident_matching(\"circle\")).is_ok() {\n      if let Ok(extent) = input.try_parse(ShapeExtent::parse) {\n        return Ok(Circle::Extent(extent));\n      }\n\n      if let Ok(length) = input.try_parse(Length::parse) {\n        return Ok(Circle::Radius(length));\n      }\n\n      // If only the `circle` keyword was given, default to `farthest-corner`.\n      return Ok(Circle::Extent(ShapeExtent::FarthestCorner));\n    }\n\n    return Err(input.new_error_for_next_token());\n  }\n}\n\nimpl ToCss for Circle {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      Circle::Radius(r) => r.to_css(dest),\n      Circle::Extent(extent) => {\n        dest.write_str(\"circle\")?;\n        if *extent != ShapeExtent::FarthestCorner {\n          dest.write_char(' ')?;\n          extent.to_css(dest)?;\n        }\n        Ok(())\n      }\n    }\n  }\n}\n\n/// An ellipse ending shape for a `radial-gradient()`.\n///\n/// See [RadialGradient](RadialGradient).\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub enum Ellipse {\n  /// An ellipse with a specified horizontal and vertical radius.\n  Size {\n    /// The x-radius of the ellipse.\n    x: LengthPercentage,\n    /// The y-radius of the ellipse.\n    y: LengthPercentage,\n  },\n  /// A shape extent keyword.\n  #[cfg_attr(feature = \"serde\", serde(with = \"ValueWrapper::<ShapeExtent>\"))]\n  Extent(ShapeExtent),\n}\n\nimpl<'i> Parse<'i> for Ellipse {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    if let Ok(extent) = input.try_parse(ShapeExtent::parse) {\n      // The `ellipse` keyword is optional, but only if the `circle` keyword is not present.\n      // If it is, then we'll re-parse as a circle.\n      if input.try_parse(|input| input.expect_ident_matching(\"circle\")).is_ok() {\n        return Err(input.new_error_for_next_token());\n      }\n      let _ = input.try_parse(|input| input.expect_ident_matching(\"ellipse\"));\n      return Ok(Ellipse::Extent(extent));\n    }\n\n    if let Ok(x) = input.try_parse(LengthPercentage::parse) {\n      let y = LengthPercentage::parse(input)?;\n      // The `ellipse` keyword is optional if there are two lengths.\n      let _ = input.try_parse(|input| input.expect_ident_matching(\"ellipse\"));\n      return Ok(Ellipse::Size { x, y });\n    }\n\n    if input.try_parse(|input| input.expect_ident_matching(\"ellipse\")).is_ok() {\n      if let Ok(extent) = input.try_parse(ShapeExtent::parse) {\n        return Ok(Ellipse::Extent(extent));\n      }\n\n      if let Ok(x) = input.try_parse(LengthPercentage::parse) {\n        let y = LengthPercentage::parse(input)?;\n        return Ok(Ellipse::Size { x, y });\n      }\n\n      // Assume `farthest-corner` if only the `ellipse` keyword is present.\n      return Ok(Ellipse::Extent(ShapeExtent::FarthestCorner));\n    }\n\n    return Err(input.new_error_for_next_token());\n  }\n}\n\nimpl ToCss for Ellipse {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    // The `ellipse` keyword is optional, so we don't emit it.\n    match self {\n      Ellipse::Size { x, y } => {\n        x.to_css(dest)?;\n        dest.write_char(' ')?;\n        y.to_css(dest)\n      }\n      Ellipse::Extent(extent) => extent.to_css(dest),\n    }\n  }\n}\n\nenum_property! {\n  /// A shape extent for a `radial-gradient()`.\n  ///\n  /// See [RadialGradient](RadialGradient).\n  pub enum ShapeExtent {\n    /// The closest side of the box to the gradient's center.\n    ClosestSide,\n    /// The farthest side of the box from the gradient's center.\n    FarthestSide,\n    /// The closest cornder of the box to the gradient's center.\n    ClosestCorner,\n    /// The farthest corner of the box from the gradient's center.\n    FarthestCorner,\n  }\n}\n\n/// A CSS [`conic-gradient()`](https://www.w3.org/TR/css-images-4/#conic-gradients) or `repeating-conic-gradient()`.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub struct ConicGradient {\n  /// The angle of the gradient.\n  pub angle: Angle,\n  /// The position of the gradient.\n  pub position: Position,\n  /// The color stops and transition hints for the gradient.\n  pub items: Vec<GradientItem<AnglePercentage>>,\n}\n\nimpl ConicGradient {\n  fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let angle = input.try_parse(|input| {\n      input.expect_ident_matching(\"from\")?;\n      // Spec allows unitless zero angles for gradients.\n      // https://w3c.github.io/csswg-drafts/css-images-4/#valdef-conic-gradient-angle\n      Angle::parse_with_unitless_zero(input)\n    });\n\n    let position = input.try_parse(|input| {\n      input.expect_ident_matching(\"at\")?;\n      Position::parse(input)\n    });\n\n    if angle.is_ok() || position.is_ok() {\n      input.expect_comma()?;\n    }\n\n    let items = parse_items(input)?;\n    Ok(ConicGradient {\n      angle: angle.unwrap_or(Angle::Deg(0.0)),\n      position: position.unwrap_or(Position::center()),\n      items,\n    })\n  }\n}\n\nimpl ToCss for ConicGradient {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    if !self.angle.is_zero() {\n      dest.write_str(\"from \")?;\n      self.angle.to_css(dest)?;\n\n      if self.position.is_center() {\n        dest.delim(',', false)?;\n      } else {\n        dest.write_char(' ')?;\n      }\n    }\n\n    if !self.position.is_center() {\n      dest.write_str(\"at \")?;\n      self.position.to_css(dest)?;\n      dest.delim(',', false)?;\n    }\n\n    serialize_items(&self.items, dest)\n  }\n}\n\nimpl ConicGradient {\n  fn get_fallback(&self, kind: ColorFallbackKind) -> ConicGradient {\n    ConicGradient {\n      angle: self.angle.clone(),\n      position: self.position.clone(),\n      items: self.items.iter().map(|item| item.get_fallback(kind)).collect(),\n    }\n  }\n}\n\nimpl IsCompatible for ConicGradient {\n  fn is_compatible(&self, browsers: Browsers) -> bool {\n    self.items.iter().all(|item| item.is_compatible(browsers))\n  }\n}\n\n/// A [`<color-stop>`](https://www.w3.org/TR/css-images-4/#color-stop-syntax) within a gradient.\n///\n/// This type is generic, and may be either a [LengthPercentage](super::length::LengthPercentage)\n/// or [Angle](super::angle::Angle) depending on what type of gradient it is within.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct ColorStop<D> {\n  /// The color of the color stop.\n  pub color: CssColor,\n  /// The position of the color stop.\n  pub position: Option<D>,\n}\n\nimpl<'i, D: Parse<'i>> Parse<'i> for ColorStop<D> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let color = CssColor::parse(input)?;\n    let position = input.try_parse(D::parse).ok();\n    Ok(ColorStop { color, position })\n  }\n}\n\nimpl<D: ToCss> ToCss for ColorStop<D> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    self.color.to_css(dest)?;\n    if let Some(position) = &self.position {\n      dest.write_char(' ')?;\n      position.to_css(dest)?;\n    }\n    Ok(())\n  }\n}\n\n/// Either a color stop or interpolation hint within a gradient.\n///\n/// This type is generic, and items may be either a [LengthPercentage](super::length::LengthPercentage)\n/// or [Angle](super::angle::Angle) depending on what type of gradient it is within.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub enum GradientItem<D> {\n  /// A color stop.\n  ColorStop(ColorStop<D>),\n  /// A color interpolation hint.\n  #[cfg_attr(\n    feature = \"serde\",\n    serde(\n      bound(serialize = \"D: serde::Serialize\", deserialize = \"D: serde::Deserialize<'de>\"),\n      with = \"ValueWrapper::<D>\"\n    )\n  )]\n  Hint(D),\n}\n\nimpl<D: ToCss> ToCss for GradientItem<D> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      GradientItem::ColorStop(stop) => stop.to_css(dest),\n      GradientItem::Hint(hint) => hint.to_css(dest),\n    }\n  }\n}\n\nimpl<D: Clone> GradientItem<D> {\n  /// Returns the color fallback types needed for the given browser targets.\n  pub fn get_necessary_fallbacks(&self, targets: Targets) -> ColorFallbackKind {\n    match self {\n      GradientItem::ColorStop(stop) => stop.color.get_necessary_fallbacks(targets),\n      GradientItem::Hint(..) => ColorFallbackKind::empty(),\n    }\n  }\n\n  /// Returns a fallback gradient item for the given color fallback type.\n  pub fn get_fallback(&self, kind: ColorFallbackKind) -> GradientItem<D> {\n    match self {\n      GradientItem::ColorStop(stop) => GradientItem::ColorStop(ColorStop {\n        color: stop.color.get_fallback(kind),\n        position: stop.position.clone(),\n      }),\n      GradientItem::Hint(..) => self.clone(),\n    }\n  }\n}\n\nimpl<D> IsCompatible for GradientItem<D> {\n  fn is_compatible(&self, browsers: Browsers) -> bool {\n    match self {\n      GradientItem::ColorStop(c) => c.color.is_compatible(browsers),\n      GradientItem::Hint(..) => compat::Feature::GradientInterpolationHints.is_compatible(browsers),\n    }\n  }\n}\n\nfn parse_items<'i, 't, D: Parse<'i>>(\n  input: &mut Parser<'i, 't>,\n) -> Result<Vec<GradientItem<D>>, ParseError<'i, ParserError<'i>>> {\n  let mut items = Vec::new();\n  let mut seen_stop = false;\n\n  loop {\n    input.parse_until_before(Delimiter::Comma, |input| {\n      if seen_stop {\n        if let Ok(hint) = input.try_parse(D::parse) {\n          seen_stop = false;\n          items.push(GradientItem::Hint(hint));\n          return Ok(());\n        }\n      }\n\n      let stop = ColorStop::parse(input)?;\n\n      if let Ok(position) = input.try_parse(D::parse) {\n        let color = stop.color.clone();\n        items.push(GradientItem::ColorStop(stop));\n\n        items.push(GradientItem::ColorStop(ColorStop {\n          color,\n          position: Some(position),\n        }))\n      } else {\n        items.push(GradientItem::ColorStop(stop));\n      }\n\n      seen_stop = true;\n      Ok(())\n    })?;\n\n    match input.next() {\n      Err(_) => break,\n      Ok(Token::Comma) => continue,\n      _ => unreachable!(),\n    }\n  }\n\n  Ok(items)\n}\n\nfn serialize_items<\n  D: ToCss + std::cmp::PartialEq<D> + std::ops::Mul<f32, Output = D> + TrySign + Clone + std::fmt::Debug,\n  W,\n>(\n  items: &Vec<GradientItem<DimensionPercentage<D>>>,\n  dest: &mut Printer<W>,\n) -> Result<(), PrinterError>\nwhere\n  W: std::fmt::Write,\n{\n  let mut first = true;\n  let mut last: Option<&GradientItem<DimensionPercentage<D>>> = None;\n  for item in items {\n    // Skip useless hints\n    if *item == GradientItem::Hint(DimensionPercentage::Percentage(Percentage(0.5))) {\n      continue;\n    }\n\n    // Use double position stop if the last stop is the same color and all targets support it.\n    if let Some(prev) = last {\n      if !should_compile!(dest.targets.current, DoublePositionGradients) {\n        match (prev, item) {\n          (\n            GradientItem::ColorStop(ColorStop {\n              position: Some(_),\n              color: ca,\n            }),\n            GradientItem::ColorStop(ColorStop {\n              position: Some(p),\n              color: cb,\n            }),\n          ) if ca == cb => {\n            dest.write_char(' ')?;\n            p.to_css(dest)?;\n            last = None;\n            continue;\n          }\n          _ => {}\n        }\n      }\n    }\n\n    if first {\n      first = false;\n    } else {\n      dest.delim(',', false)?;\n    }\n    item.to_css(dest)?;\n    last = Some(item)\n  }\n  Ok(())\n}\n\n/// A legacy `-webkit-gradient()`.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"kind\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum WebKitGradient {\n  /// A linear `-webkit-gradient()`.\n  Linear {\n    /// The starting point of the gradient.\n    from: WebKitGradientPoint,\n    /// The ending point of the gradient.\n    to: WebKitGradientPoint,\n    /// The color stops in the gradient.\n    stops: Vec<WebKitColorStop>,\n  },\n  /// A radial `-webkit-gradient()`.\n  Radial {\n    /// The starting point of the gradient.\n    from: WebKitGradientPoint,\n    /// The starting radius of the gradient.\n    r0: CSSNumber,\n    /// The ending point of the gradient.\n    to: WebKitGradientPoint,\n    /// The ending radius of the gradient.\n    r1: CSSNumber,\n    /// The color stops in the gradient.\n    stops: Vec<WebKitColorStop>,\n  },\n}\n\nimpl<'i> Parse<'i> for WebKitGradient {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let location = input.current_source_location();\n    let ident = input.expect_ident_cloned()?;\n    input.expect_comma()?;\n\n    match_ignore_ascii_case! { &ident,\n      \"linear\" => {\n        let from = WebKitGradientPoint::parse(input)?;\n        input.expect_comma()?;\n        let to = WebKitGradientPoint::parse(input)?;\n        input.expect_comma()?;\n        let stops = input.parse_comma_separated(WebKitColorStop::parse)?;\n        Ok(WebKitGradient::Linear {\n          from,\n          to,\n          stops\n        })\n      },\n      \"radial\" => {\n        let from = WebKitGradientPoint::parse(input)?;\n        input.expect_comma()?;\n        let r0 = CSSNumber::parse(input)?;\n        input.expect_comma()?;\n        let to = WebKitGradientPoint::parse(input)?;\n        input.expect_comma()?;\n        let r1 = CSSNumber::parse(input)?;\n        input.expect_comma()?;\n        let stops = input.parse_comma_separated(WebKitColorStop::parse)?;\n        Ok(WebKitGradient::Radial {\n          from,\n          r0,\n          to,\n          r1,\n          stops\n        })\n      },\n      _ => Err(location.new_unexpected_token_error(cssparser::Token::Ident(ident.clone())))\n    }\n  }\n}\n\nimpl ToCss for WebKitGradient {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      WebKitGradient::Linear { from, to, stops } => {\n        dest.write_str(\"linear\")?;\n        dest.delim(',', false)?;\n        from.to_css(dest)?;\n        dest.delim(',', false)?;\n        to.to_css(dest)?;\n        for stop in stops {\n          dest.delim(',', false)?;\n          stop.to_css(dest)?;\n        }\n        Ok(())\n      }\n      WebKitGradient::Radial {\n        from,\n        r0,\n        to,\n        r1,\n        stops,\n      } => {\n        dest.write_str(\"radial\")?;\n        dest.delim(',', false)?;\n        from.to_css(dest)?;\n        dest.delim(',', false)?;\n        r0.to_css(dest)?;\n        dest.delim(',', false)?;\n        to.to_css(dest)?;\n        dest.delim(',', false)?;\n        r1.to_css(dest)?;\n        for stop in stops {\n          dest.delim(',', false)?;\n          stop.to_css(dest)?;\n        }\n        Ok(())\n      }\n    }\n  }\n}\n\nimpl WebKitGradient {\n  fn get_fallback(&self, kind: ColorFallbackKind) -> WebKitGradient {\n    let stops = match self {\n      WebKitGradient::Linear { stops, .. } => stops,\n      WebKitGradient::Radial { stops, .. } => stops,\n    };\n\n    let stops = stops.iter().map(|stop| stop.get_fallback(kind)).collect();\n\n    match self {\n      WebKitGradient::Linear { from, to, .. } => WebKitGradient::Linear {\n        from: from.clone(),\n        to: to.clone(),\n        stops,\n      },\n      WebKitGradient::Radial { from, r0, to, r1, .. } => WebKitGradient::Radial {\n        from: from.clone(),\n        r0: *r0,\n        to: to.clone(),\n        r1: *r1,\n        stops,\n      },\n    }\n  }\n}\n\n/// A color stop within a legacy `-webkit-gradient()`.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct WebKitColorStop {\n  /// The color of the color stop.\n  pub color: CssColor,\n  /// The position of the color stop.\n  pub position: CSSNumber,\n}\n\nimpl<'i> Parse<'i> for WebKitColorStop {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let location = input.current_source_location();\n    let function = input.expect_function()?.clone();\n    input.parse_nested_block(|input| {\n      let position = match_ignore_ascii_case! { &function,\n        \"color-stop\" => {\n          let p = NumberOrPercentage::parse(input)?;\n          input.expect_comma()?;\n          (&p).into()\n        },\n        \"from\" => 0.0,\n        \"to\" => 1.0,\n        _ => return Err(location.new_unexpected_token_error(cssparser::Token::Ident(function.clone())))\n      };\n      let color = CssColor::parse(input)?;\n      Ok(WebKitColorStop { color, position })\n    })\n  }\n}\n\nimpl ToCss for WebKitColorStop {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    if self.position == 0.0 {\n      dest.write_str(\"from(\")?;\n      self.color.to_css(dest)?;\n    } else if self.position == 1.0 {\n      dest.write_str(\"to(\")?;\n      self.color.to_css(dest)?;\n    } else {\n      dest.write_str(\"color-stop(\")?;\n      self.position.to_css(dest)?;\n      dest.delim(',', false)?;\n      self.color.to_css(dest)?;\n    }\n    dest.write_char(')')\n  }\n}\n\nimpl WebKitColorStop {\n  fn get_fallback(&self, kind: ColorFallbackKind) -> WebKitColorStop {\n    WebKitColorStop {\n      color: self.color.get_fallback(kind),\n      position: self.position,\n    }\n  }\n}\n\n/// An x/y position within a legacy `-webkit-gradient()`.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub struct WebKitGradientPoint {\n  /// The x-position.\n  pub x: WebKitGradientPointComponent<HorizontalPositionKeyword>,\n  /// The y-position.\n  pub y: WebKitGradientPointComponent<VerticalPositionKeyword>,\n}\n\nimpl<'i> Parse<'i> for WebKitGradientPoint {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let x = WebKitGradientPointComponent::parse(input)?;\n    let y = WebKitGradientPointComponent::parse(input)?;\n    Ok(WebKitGradientPoint { x, y })\n  }\n}\n\nimpl ToCss for WebKitGradientPoint {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    self.x.to_css(dest)?;\n    dest.write_char(' ')?;\n    self.y.to_css(dest)\n  }\n}\n\n/// A keyword or number within a [WebKitGradientPoint](WebKitGradientPoint).\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum WebKitGradientPointComponent<S> {\n  /// The `center` keyword.\n  Center,\n  /// A number or percentage.\n  Number(NumberOrPercentage),\n  /// A side keyword.\n  Side(S),\n}\n\nimpl<'i, S: Parse<'i>> Parse<'i> for WebKitGradientPointComponent<S> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    if input.try_parse(|i| i.expect_ident_matching(\"center\")).is_ok() {\n      return Ok(WebKitGradientPointComponent::Center);\n    }\n\n    if let Ok(lp) = input.try_parse(NumberOrPercentage::parse) {\n      return Ok(WebKitGradientPointComponent::Number(lp));\n    }\n\n    let keyword = S::parse(input)?;\n    Ok(WebKitGradientPointComponent::Side(keyword))\n  }\n}\n\nimpl<S: ToCss + Clone + Into<LengthPercentage>> ToCss for WebKitGradientPointComponent<S> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    use WebKitGradientPointComponent::*;\n    match &self {\n      Center => {\n        if dest.minify {\n          dest.write_str(\"50%\")\n        } else {\n          dest.write_str(\"center\")\n        }\n      }\n      Number(lp) => {\n        if matches!(lp, NumberOrPercentage::Percentage(Percentage(p)) if *p == 0.0) {\n          dest.write_char('0')\n        } else {\n          lp.to_css(dest)\n        }\n      }\n      Side(s) => {\n        if dest.minify {\n          let lp: LengthPercentage = s.clone().into();\n          lp.to_css(dest)?;\n        } else {\n          s.to_css(dest)?;\n        }\n        Ok(())\n      }\n    }\n  }\n}\n\nimpl<S: Clone> WebKitGradientPointComponent<S> {\n  /// Attempts to convert a standard position to a webkit gradient point.\n  fn from_position(pos: &PositionComponent<S>) -> Result<WebKitGradientPointComponent<S>, ()> {\n    match pos {\n      PositionComponent::Center => Ok(WebKitGradientPointComponent::Center),\n      PositionComponent::Length(len) => {\n        Ok(WebKitGradientPointComponent::Number(match len {\n          LengthPercentage::Percentage(p) => NumberOrPercentage::Percentage(p.clone()),\n          LengthPercentage::Dimension(d) => {\n            // Webkit gradient points can only be specified in pixels.\n            if let Some(px) = d.to_px() {\n              NumberOrPercentage::Number(px)\n            } else {\n              return Err(());\n            }\n          }\n          _ => return Err(()),\n        }))\n      }\n      PositionComponent::Side { side, offset } => {\n        if offset.is_some() {\n          return Err(());\n        }\n        Ok(WebKitGradientPointComponent::Side(side.clone()))\n      }\n    }\n  }\n}\n\nimpl WebKitGradient {\n  /// Attempts to convert a standard gradient to a legacy -webkit-gradient()\n  pub fn from_standard(gradient: &Gradient) -> Result<WebKitGradient, ()> {\n    match gradient {\n      Gradient::Linear(linear) => {\n        // Convert from line direction to a from and to point, if possible.\n        let (from, to) = match &linear.direction {\n          LineDirection::Horizontal(horizontal) => match horizontal {\n            HorizontalPositionKeyword::Left => ((1.0, 0.0), (0.0, 0.0)),\n            HorizontalPositionKeyword::Right => ((0.0, 0.0), (1.0, 0.0)),\n          },\n          LineDirection::Vertical(vertical) => match vertical {\n            VerticalPositionKeyword::Top => ((0.0, 1.0), (0.0, 0.0)),\n            VerticalPositionKeyword::Bottom => ((0.0, 0.0), (0.0, 1.0)),\n          },\n          LineDirection::Corner { horizontal, vertical } => match (horizontal, vertical) {\n            (HorizontalPositionKeyword::Left, VerticalPositionKeyword::Top) => ((1.0, 1.0), (0.0, 0.0)),\n            (HorizontalPositionKeyword::Left, VerticalPositionKeyword::Bottom) => ((1.0, 0.0), (0.0, 1.0)),\n            (HorizontalPositionKeyword::Right, VerticalPositionKeyword::Top) => ((0.0, 1.0), (1.0, 0.0)),\n            (HorizontalPositionKeyword::Right, VerticalPositionKeyword::Bottom) => ((0.0, 0.0), (1.0, 1.0)),\n          },\n          LineDirection::Angle(angle) => {\n            let degrees = angle.to_degrees();\n            if degrees == 0.0 {\n              ((0.0, 1.0), (0.0, 0.0))\n            } else if degrees == 90.0 {\n              ((0.0, 0.0), (1.0, 0.0))\n            } else if degrees == 180.0 {\n              ((0.0, 0.0), (0.0, 1.0))\n            } else if degrees == 270.0 {\n              ((1.0, 0.0), (0.0, 0.0))\n            } else {\n              return Err(());\n            }\n          }\n        };\n\n        Ok(WebKitGradient::Linear {\n          from: WebKitGradientPoint {\n            x: WebKitGradientPointComponent::Number(NumberOrPercentage::Percentage(Percentage(from.0))),\n            y: WebKitGradientPointComponent::Number(NumberOrPercentage::Percentage(Percentage(from.1))),\n          },\n          to: WebKitGradientPoint {\n            x: WebKitGradientPointComponent::Number(NumberOrPercentage::Percentage(Percentage(to.0))),\n            y: WebKitGradientPointComponent::Number(NumberOrPercentage::Percentage(Percentage(to.1))),\n          },\n          stops: convert_stops_to_webkit(&linear.items)?,\n        })\n      }\n      Gradient::Radial(radial) => {\n        // Webkit radial gradients are always circles, not ellipses, and must be specified in pixels.\n        let radius = match &radial.shape {\n          EndingShape::Circle(Circle::Radius(radius)) => {\n            if let Some(r) = radius.to_px() {\n              r\n            } else {\n              return Err(());\n            }\n          }\n          _ => return Err(()),\n        };\n\n        let x = WebKitGradientPointComponent::from_position(&radial.position.x)?;\n        let y = WebKitGradientPointComponent::from_position(&radial.position.y)?;\n        let point = WebKitGradientPoint { x, y };\n        Ok(WebKitGradient::Radial {\n          from: point.clone(),\n          r0: 0.0,\n          to: point,\n          r1: radius,\n          stops: convert_stops_to_webkit(&radial.items)?,\n        })\n      }\n      _ => Err(()),\n    }\n  }\n}\n\nfn convert_stops_to_webkit(items: &Vec<GradientItem<LengthPercentage>>) -> Result<Vec<WebKitColorStop>, ()> {\n  let mut stops = Vec::with_capacity(items.len());\n  for (i, item) in items.iter().enumerate() {\n    match item {\n      GradientItem::ColorStop(stop) => {\n        // webkit stops must always be percentage based, not length based.\n        let position = if let Some(pos) = &stop.position {\n          if let LengthPercentage::Percentage(position) = pos {\n            position.0\n          } else {\n            return Err(());\n          }\n        } else if i == 0 {\n          0.0\n        } else if i == items.len() - 1 {\n          1.0\n        } else {\n          return Err(());\n        };\n\n        stops.push(WebKitColorStop {\n          color: stop.color.clone(),\n          position,\n        })\n      }\n      _ => return Err(()),\n    }\n  }\n\n  Ok(stops)\n}\n"
  },
  {
    "path": "src/values/ident.rs",
    "content": "//! CSS identifiers.\n\nuse crate::error::{ParserError, PrinterError};\nuse crate::printer::Printer;\nuse crate::properties::css_modules::Specifier;\nuse crate::traits::{Parse, ParseWithOptions, ToCss};\nuse crate::values::string::CowArcStr;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse cssparser::*;\nuse smallvec::SmallVec;\nuse std::borrow::Borrow;\nuse std::ops::Deref;\n\nuse super::string::impl_string_type;\n\n/// A CSS [`<custom-ident>`](https://www.w3.org/TR/css-values-4/#custom-idents).\n///\n/// Custom idents are author defined, and allow any valid identifier except the\n/// [CSS-wide keywords](https://www.w3.org/TR/css-values-4/#css-wide-keywords).\n/// They may be renamed to include a hash when compiled as part of a CSS module.\n#[derive(Debug, Clone, Eq, Hash)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(feature = \"visitor\", visit(visit_custom_ident, CUSTOM_IDENTS))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize), serde(transparent))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct CustomIdent<'i>(#[cfg_attr(feature = \"serde\", serde(borrow))] pub CowArcStr<'i>);\n\nimpl<'i> Parse<'i> for CustomIdent<'i> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let location = input.current_source_location();\n    let ident = input.expect_ident()?;\n    let valid = match_ignore_ascii_case! { &ident,\n      \"initial\" | \"inherit\" | \"unset\" | \"default\" | \"revert\" | \"revert-layer\" => false,\n      _ => true\n    };\n\n    if !valid {\n      return Err(location.new_unexpected_token_error(Token::Ident(ident.clone())));\n    }\n\n    Ok(CustomIdent(ident.into()))\n  }\n}\n\nimpl<'i> ToCss for CustomIdent<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    self.to_css_with_options(dest, true)\n  }\n}\n\nimpl<'i> CustomIdent<'i> {\n  /// Write the custom ident to CSS.\n  pub(crate) fn to_css_with_options<W>(\n    &self,\n    dest: &mut Printer<W>,\n    enabled_css_modules: bool,\n  ) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    let css_module_custom_idents_enabled = enabled_css_modules\n      && dest\n        .css_module\n        .as_mut()\n        .map_or(false, |css_module| css_module.config.custom_idents);\n    dest.write_ident(&self.0, css_module_custom_idents_enabled)\n  }\n}\n\n/// A list of CSS [`<custom-ident>`](https://www.w3.org/TR/css-values-4/#custom-idents) values.\npub type CustomIdentList<'i> = SmallVec<[CustomIdent<'i>; 1]>;\n\n/// The `none` keyword, or a space-separated list of custom idents.\n#[derive(Debug, Clone, PartialEq, Default)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub enum NoneOrCustomIdentList<'i> {\n  /// None.\n  #[default]\n  None,\n  /// A list of idents.\n  #[cfg_attr(feature = \"serde\", serde(borrow, untagged))]\n  Idents(SmallVec<[CustomIdent<'i>; 1]>),\n}\n\nimpl<'i> Parse<'i> for NoneOrCustomIdentList<'i> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let mut types = SmallVec::new();\n    loop {\n      if let Ok(ident) = input.try_parse(CustomIdent::parse) {\n        if ident == \"none\" {\n          if types.is_empty() {\n            return Ok(NoneOrCustomIdentList::None);\n          } else {\n            return Err(input.new_custom_error(ParserError::InvalidValue));\n          }\n        }\n\n        types.push(ident);\n      } else {\n        return Ok(NoneOrCustomIdentList::Idents(types));\n      }\n    }\n  }\n}\n\nimpl<'i> ToCss for NoneOrCustomIdentList<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      NoneOrCustomIdentList::None => dest.write_str(\"none\"),\n      NoneOrCustomIdentList::Idents(types) => {\n        let mut first = true;\n        for ident in types {\n          if !first {\n            dest.write_char(' ')?;\n          } else {\n            first = false;\n          }\n          ident.to_css(dest)?;\n        }\n        Ok(())\n      }\n    }\n  }\n}\n\n/// A CSS [`<dashed-ident>`](https://www.w3.org/TR/css-values-4/#dashed-idents) declaration.\n///\n/// Dashed idents are used in cases where an identifier can be either author defined _or_ CSS-defined.\n/// Author defined idents must start with two dash characters (\"--\") or parsing will fail.\n#[derive(Debug, Clone, Eq, Hash)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(feature = \"visitor\", visit(visit_dashed_ident, DASHED_IDENTS))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize), serde(transparent))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct DashedIdent<'i>(#[cfg_attr(feature = \"serde\", serde(borrow))] pub CowArcStr<'i>);\n\nimpl<'i> Parse<'i> for DashedIdent<'i> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let location = input.current_source_location();\n    let ident = input.expect_ident()?;\n    if !ident.starts_with(\"--\") {\n      return Err(location.new_unexpected_token_error(Token::Ident(ident.clone())));\n    }\n\n    Ok(DashedIdent(ident.into()))\n  }\n}\n\nimpl<'i> ToCss for DashedIdent<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    dest.write_dashed_ident(&self.0, true)\n  }\n}\n\n#[cfg(feature = \"serde\")]\nimpl<'i, 'de: 'i> serde::Deserialize<'de> for DashedIdent<'i> {\n  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n  where\n    D: serde::Deserializer<'de>,\n  {\n    let ident = CowArcStr::deserialize(deserializer)?;\n    if !ident.starts_with(\"--\") {\n      return Err(serde::de::Error::custom(\"Dashed idents must start with --\"));\n    }\n\n    Ok(DashedIdent(ident))\n  }\n}\n\n/// A CSS [`<dashed-ident>`](https://www.w3.org/TR/css-values-4/#dashed-idents) reference.\n///\n/// Dashed idents are used in cases where an identifier can be either author defined _or_ CSS-defined.\n/// Author defined idents must start with two dash characters (\"--\") or parsing will fail.\n///\n/// In CSS modules, when the `dashed_idents` option is enabled, the identifier may be followed by the\n/// `from` keyword and an argument indicating where the referenced identifier is declared (e.g. a filename).\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct DashedIdentReference<'i> {\n  /// The referenced identifier.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  pub ident: DashedIdent<'i>,\n  /// CSS modules extension: the filename where the variable is defined.\n  /// Only enabled when the CSS modules `dashed_idents` option is turned on.\n  pub from: Option<Specifier<'i>>,\n}\n\nimpl<'i> ParseWithOptions<'i> for DashedIdentReference<'i> {\n  fn parse_with_options<'t>(\n    input: &mut Parser<'i, 't>,\n    options: &crate::stylesheet::ParserOptions,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let ident = DashedIdent::parse(input)?;\n\n    let from = match &options.css_modules {\n      Some(config) if config.dashed_idents => {\n        if input.try_parse(|input| input.expect_ident_matching(\"from\")).is_ok() {\n          Some(Specifier::parse(input)?)\n        } else {\n          None\n        }\n      }\n      _ => None,\n    };\n\n    Ok(DashedIdentReference { ident, from })\n  }\n}\n\nimpl<'i> ToCss for DashedIdentReference<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match &mut dest.css_module {\n      Some(css_module) if css_module.config.dashed_idents => {\n        if let Some(name) = css_module.reference_dashed(&self.ident.0, &self.from, dest.loc.source_index) {\n          dest.write_str(\"--\")?;\n          serialize_name(&name, dest)?;\n          return Ok(());\n        }\n      }\n      _ => {}\n    }\n\n    dest.write_dashed_ident(&self.ident.0, false)\n  }\n}\n\n/// A CSS [`<ident>`](https://www.w3.org/TR/css-values-4/#css-css-identifier).\n#[derive(Debug, Clone, Eq, Hash, Default)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize), serde(transparent))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct Ident<'i>(#[cfg_attr(feature = \"serde\", serde(borrow))] pub CowArcStr<'i>);\n\nimpl<'i> Parse<'i> for Ident<'i> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let ident = input.expect_ident()?;\n    Ok(Ident(ident.into()))\n  }\n}\n\nimpl<'i> ToCss for Ident<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    serialize_identifier(&self.0, dest)?;\n    Ok(())\n  }\n}\n\nimpl<'i> cssparser::ToCss for Ident<'i> {\n  fn to_css<W>(&self, dest: &mut W) -> std::fmt::Result\n  where\n    W: std::fmt::Write,\n  {\n    serialize_identifier(&self.0, dest)\n  }\n}\n\nimpl_string_type!(Ident);\nimpl_string_type!(CustomIdent);\nimpl_string_type!(DashedIdent);\n"
  },
  {
    "path": "src/values/image.rs",
    "content": "//! CSS image values.\n\nuse super::color::ColorFallbackKind;\nuse super::gradient::*;\nuse super::resolution::Resolution;\nuse crate::compat;\nuse crate::dependencies::{Dependency, UrlDependency};\nuse crate::error::{ParserError, PrinterError};\nuse crate::prefixes::{is_webkit_gradient, Feature};\nuse crate::printer::Printer;\nuse crate::targets::{Browsers, Targets};\nuse crate::traits::{FallbackValues, IsCompatible, Parse, ToCss};\nuse crate::values::string::CowArcStr;\nuse crate::values::url::Url;\nuse crate::vendor_prefix::VendorPrefix;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse cssparser::*;\nuse smallvec::SmallVec;\n\n/// A CSS [`<image>`](https://www.w3.org/TR/css-images-3/#image-values) value.\n#[derive(Debug, Clone, PartialEq, Parse, ToCss)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(feature = \"visitor\", visit(visit_image, IMAGES))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub enum Image<'i> {\n  /// The `none` keyword.\n  None,\n  /// A `url()`.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  Url(Url<'i>),\n  /// A gradient.\n  Gradient(Box<Gradient>),\n  /// An `image-set()`.\n  ImageSet(ImageSet<'i>),\n}\n\nimpl<'i> Default for Image<'i> {\n  fn default() -> Image<'i> {\n    Image::None\n  }\n}\n\nimpl<'i> Image<'i> {\n  /// Returns whether the image includes any vendor prefixed values.\n  pub fn has_vendor_prefix(&self) -> bool {\n    let prefix = self.get_vendor_prefix();\n    !prefix.is_empty() && prefix != VendorPrefix::None\n  }\n\n  /// Returns the vendor prefix used in the image value.\n  pub fn get_vendor_prefix(&self) -> VendorPrefix {\n    match self {\n      Image::Gradient(a) => a.get_vendor_prefix(),\n      Image::ImageSet(a) => a.get_vendor_prefix(),\n      _ => VendorPrefix::empty(),\n    }\n  }\n\n  /// Returns the vendor prefixes that are needed for the given browser targets.\n  pub fn get_necessary_prefixes(&self, targets: Targets) -> VendorPrefix {\n    match self {\n      Image::Gradient(grad) => grad.get_necessary_prefixes(targets),\n      Image::ImageSet(image_set) => image_set.get_necessary_prefixes(targets),\n      _ => VendorPrefix::None,\n    }\n  }\n\n  /// Returns a vendor prefixed version of the image for the given vendor prefixes.\n  pub fn get_prefixed(&self, prefix: VendorPrefix) -> Image<'i> {\n    match self {\n      Image::Gradient(grad) => Image::Gradient(Box::new(grad.get_prefixed(prefix))),\n      Image::ImageSet(image_set) => Image::ImageSet(image_set.get_prefixed(prefix)),\n      _ => self.clone(),\n    }\n  }\n\n  /// Returns a legacy `-webkit-gradient()` value for the image.\n  ///\n  /// May return an error in case the gradient cannot be converted.\n  pub fn get_legacy_webkit(&self) -> Result<Image<'i>, ()> {\n    match self {\n      Image::Gradient(grad) => Ok(Image::Gradient(Box::new(grad.get_legacy_webkit()?))),\n      _ => Ok(self.clone()),\n    }\n  }\n\n  /// Returns the color fallbacks that are needed for the given browser targets.\n  pub fn get_necessary_fallbacks(&self, targets: Targets) -> ColorFallbackKind {\n    match self {\n      Image::Gradient(grad) => grad.get_necessary_fallbacks(targets),\n      _ => ColorFallbackKind::empty(),\n    }\n  }\n\n  /// Returns a fallback version of the image for the given color fallback type.\n  pub fn get_fallback(&self, kind: ColorFallbackKind) -> Image<'i> {\n    match self {\n      Image::Gradient(grad) => Image::Gradient(Box::new(grad.get_fallback(kind))),\n      _ => self.clone(),\n    }\n  }\n}\n\nimpl<'i> IsCompatible for Image<'i> {\n  fn is_compatible(&self, browsers: Browsers) -> bool {\n    match self {\n      Image::Gradient(g) => match &**g {\n        Gradient::Linear(g) => {\n          compat::Feature::LinearGradient.is_compatible(browsers) && g.is_compatible(browsers)\n        }\n        Gradient::RepeatingLinear(g) => {\n          compat::Feature::RepeatingLinearGradient.is_compatible(browsers) && g.is_compatible(browsers)\n        }\n        Gradient::Radial(g) => {\n          compat::Feature::RadialGradient.is_compatible(browsers) && g.is_compatible(browsers)\n        }\n        Gradient::RepeatingRadial(g) => {\n          compat::Feature::RepeatingRadialGradient.is_compatible(browsers) && g.is_compatible(browsers)\n        }\n        Gradient::Conic(g) => compat::Feature::ConicGradient.is_compatible(browsers) && g.is_compatible(browsers),\n        Gradient::RepeatingConic(g) => {\n          compat::Feature::RepeatingConicGradient.is_compatible(browsers) && g.is_compatible(browsers)\n        }\n        Gradient::WebKitGradient(..) => is_webkit_gradient(browsers),\n      },\n      Image::ImageSet(i) => i.is_compatible(browsers),\n      Image::Url(..) | Image::None => true,\n    }\n  }\n}\n\npub(crate) trait ImageFallback<'i>: Sized {\n  fn get_image(&self) -> &Image<'i>;\n  fn with_image(&self, image: Image<'i>) -> Self;\n\n  #[inline]\n  fn get_necessary_fallbacks(&self, targets: Targets) -> ColorFallbackKind {\n    self.get_image().get_necessary_fallbacks(targets)\n  }\n\n  #[inline]\n  fn get_fallback(&self, kind: ColorFallbackKind) -> Self {\n    self.with_image(self.get_image().get_fallback(kind))\n  }\n}\n\nimpl<'i> ImageFallback<'i> for Image<'i> {\n  #[inline]\n  fn get_image(&self) -> &Image<'i> {\n    self\n  }\n\n  #[inline]\n  fn with_image(&self, image: Image<'i>) -> Self {\n    image\n  }\n}\n\nimpl<'i> FallbackValues for Image<'i> {\n  fn get_fallbacks(&mut self, targets: Targets) -> Vec<Self> {\n    // Determine which prefixes and color fallbacks are needed.\n    let prefixes = self.get_necessary_prefixes(targets);\n    let fallbacks = self.get_necessary_fallbacks(targets);\n    let mut res = Vec::new();\n\n    // Get RGB fallbacks if needed.\n    let rgb = if fallbacks.contains(ColorFallbackKind::RGB) {\n      Some(self.get_fallback(ColorFallbackKind::RGB))\n    } else {\n      None\n    };\n\n    // Prefixed properties only support RGB.\n    let prefix_image = rgb.as_ref().unwrap_or(self);\n\n    // Legacy -webkit-gradient()\n    if prefixes.contains(VendorPrefix::WebKit)\n      && targets.browsers.map(is_webkit_gradient).unwrap_or(false)\n      && matches!(prefix_image, Image::Gradient(_))\n    {\n      if let Ok(legacy) = prefix_image.get_legacy_webkit() {\n        res.push(legacy);\n      }\n    }\n\n    // Standard syntax, with prefixes.\n    if prefixes.contains(VendorPrefix::WebKit) {\n      res.push(prefix_image.get_prefixed(VendorPrefix::WebKit))\n    }\n\n    if prefixes.contains(VendorPrefix::Moz) {\n      res.push(prefix_image.get_prefixed(VendorPrefix::Moz))\n    }\n\n    if prefixes.contains(VendorPrefix::O) {\n      res.push(prefix_image.get_prefixed(VendorPrefix::O))\n    }\n\n    if prefixes.contains(VendorPrefix::None) {\n      // Unprefixed, rgb fallback.\n      if let Some(rgb) = rgb {\n        res.push(rgb);\n      }\n\n      // P3 fallback.\n      if fallbacks.contains(ColorFallbackKind::P3) {\n        res.push(self.get_fallback(ColorFallbackKind::P3));\n      }\n\n      // Convert original to lab if needed (e.g. if oklab is not supported but lab is).\n      if fallbacks.contains(ColorFallbackKind::LAB) {\n        *self = self.get_fallback(ColorFallbackKind::LAB);\n      }\n    } else if let Some(last) = res.pop() {\n      // Prefixed property with no unprefixed version.\n      // Replace self with the last prefixed version so that it doesn't\n      // get duplicated when the caller pushes the original value.\n      *self = last;\n    }\n\n    res\n  }\n}\n\nimpl<'i, T: ImageFallback<'i>> FallbackValues for SmallVec<[T; 1]> {\n  fn get_fallbacks(&mut self, targets: Targets) -> Vec<Self> {\n    // Determine what vendor prefixes and color fallbacks are needed.\n    let mut prefixes = VendorPrefix::empty();\n    let mut fallbacks = ColorFallbackKind::empty();\n    let mut res = Vec::new();\n    for item in self.iter() {\n      prefixes |= item.get_image().get_necessary_prefixes(targets);\n      fallbacks |= item.get_necessary_fallbacks(targets);\n    }\n\n    // Get RGB fallbacks if needed.\n    let rgb: Option<SmallVec<[T; 1]>> = if fallbacks.contains(ColorFallbackKind::RGB) {\n      Some(self.iter().map(|item| item.get_fallback(ColorFallbackKind::RGB)).collect())\n    } else {\n      None\n    };\n\n    // Prefixed properties only support RGB.\n    let prefix_images = rgb.as_ref().unwrap_or(&self);\n\n    // Legacy -webkit-gradient()\n    if prefixes.contains(VendorPrefix::WebKit) && targets.browsers.map(is_webkit_gradient).unwrap_or(false) {\n      let images: SmallVec<[T; 1]> = prefix_images\n        .iter()\n        .map(|item| item.get_image().get_legacy_webkit().map(|image| item.with_image(image)))\n        .flatten()\n        .collect();\n      if !images.is_empty() {\n        res.push(images)\n      }\n    }\n\n    // Standard syntax, with prefixes.\n    macro_rules! prefix {\n      ($prefix: ident) => {\n        if prefixes.contains(VendorPrefix::$prefix) {\n          let images = prefix_images\n            .iter()\n            .map(|item| {\n              let image = item.get_image().get_prefixed(VendorPrefix::$prefix);\n              item.with_image(image)\n            })\n            .collect();\n          res.push(images)\n        }\n      };\n    }\n\n    prefix!(WebKit);\n    prefix!(Moz);\n    prefix!(O);\n    if prefixes.contains(VendorPrefix::None) {\n      if let Some(rgb) = rgb {\n        res.push(rgb);\n      }\n\n      if fallbacks.contains(ColorFallbackKind::P3) {\n        let p3_images = self.iter().map(|item| item.get_fallback(ColorFallbackKind::P3)).collect();\n\n        res.push(p3_images)\n      }\n\n      // Convert to lab if needed (e.g. if oklab is not supported but lab is).\n      if fallbacks.contains(ColorFallbackKind::LAB) {\n        for item in self.iter_mut() {\n          *item = item.get_fallback(ColorFallbackKind::LAB);\n        }\n      }\n    } else if let Some(last) = res.pop() {\n      // Prefixed property with no unprefixed version.\n      // Replace self with the last prefixed version so that it doesn't\n      // get duplicated when the caller pushes the original value.\n      *self = last;\n    }\n\n    res\n  }\n}\n\n/// A CSS [`image-set()`](https://drafts.csswg.org/css-images-4/#image-set-notation) value.\n///\n/// `image-set()` allows the user agent to choose between multiple versions of an image to\n/// display the most appropriate resolution or file type that it supports.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"camelCase\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct ImageSet<'i> {\n  /// The image options to choose from.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  pub options: Vec<ImageSetOption<'i>>,\n  /// The vendor prefix for the `image-set()` function.\n  pub vendor_prefix: VendorPrefix,\n}\n\nimpl<'i> ImageSet<'i> {\n  /// Returns the vendor prefix for the `image-set()`.\n  pub fn get_vendor_prefix(&self) -> VendorPrefix {\n    self.vendor_prefix\n  }\n\n  /// Returns the vendor prefixes needed for the given browser targets.\n  pub fn get_necessary_prefixes(&self, targets: Targets) -> VendorPrefix {\n    targets.prefixes(self.vendor_prefix, Feature::ImageSet)\n  }\n\n  /// Returns the `image-set()` value with the given vendor prefix.\n  pub fn get_prefixed(&self, prefix: VendorPrefix) -> ImageSet<'i> {\n    ImageSet {\n      options: self.options.clone(),\n      vendor_prefix: prefix,\n    }\n  }\n}\n\nimpl<'i> Parse<'i> for ImageSet<'i> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let location = input.current_source_location();\n    let f = input.expect_function()?;\n    let vendor_prefix = match_ignore_ascii_case! { &*f,\n      \"image-set\" => VendorPrefix::None,\n      \"-webkit-image-set\" => VendorPrefix::WebKit,\n      _ => return Err(location.new_unexpected_token_error(\n        cssparser::Token::Ident(f.clone())\n      ))\n    };\n\n    let options = input.parse_nested_block(|input| input.parse_comma_separated(ImageSetOption::parse))?;\n    Ok(ImageSet { options, vendor_prefix })\n  }\n}\n\nimpl<'i> ToCss for ImageSet<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    self.vendor_prefix.to_css(dest)?;\n    dest.write_str(\"image-set(\")?;\n    let mut first = true;\n    for option in &self.options {\n      if first {\n        first = false;\n      } else {\n        dest.delim(',', false)?;\n      }\n      option.to_css(dest, self.vendor_prefix != VendorPrefix::None)?;\n    }\n    dest.write_char(')')\n  }\n}\n\nimpl<'i> IsCompatible for ImageSet<'i> {\n  fn is_compatible(&self, browsers: Browsers) -> bool {\n    compat::Feature::ImageSet.is_compatible(browsers)\n      && self.options.iter().all(|opt| opt.image.is_compatible(browsers))\n  }\n}\n\n/// An image option within the `image-set()` function. See [ImageSet](ImageSet).\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"camelCase\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct ImageSetOption<'i> {\n  /// The image for this option.\n  #[cfg_attr(feature = \"visitor\", skip_type)]\n  pub image: Image<'i>,\n  /// The resolution of the image.\n  pub resolution: Resolution,\n  /// The mime type of the image.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  pub file_type: Option<CowArcStr<'i>>,\n}\n\nimpl<'i> Parse<'i> for ImageSetOption<'i> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let loc = input.current_source_location();\n    let image = if let Ok(url) = input.try_parse(|input| input.expect_url_or_string()) {\n      Image::Url(Url {\n        url: url.into(),\n        loc: loc.into(),\n      })\n    } else {\n      Image::parse(input)?\n    };\n\n    let (resolution, file_type) = if let Ok(res) = input.try_parse(Resolution::parse) {\n      let file_type = input.try_parse(parse_file_type).ok();\n      (res, file_type)\n    } else {\n      let file_type = input.try_parse(parse_file_type).ok();\n      let resolution = input.try_parse(Resolution::parse).unwrap_or(Resolution::Dppx(1.0));\n      (resolution, file_type)\n    };\n\n    Ok(ImageSetOption {\n      image,\n      resolution,\n      file_type: file_type.map(|x| x.into()),\n    })\n  }\n}\n\nimpl<'i> ImageSetOption<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>, is_prefixed: bool) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match &self.image {\n      // Prefixed syntax didn't allow strings, only url()\n      Image::Url(url) if !is_prefixed => {\n        // Add dependency if needed. Normally this is handled by the Url type.\n        let dep = if dest.dependencies.is_some() {\n          Some(UrlDependency::new(url, dest.filename()))\n        } else {\n          None\n        };\n        if let Some(dep) = dep {\n          serialize_string(&dep.placeholder, dest)?;\n          if let Some(dependencies) = &mut dest.dependencies {\n            dependencies.push(Dependency::Url(dep))\n          }\n        } else {\n          serialize_string(&url.url, dest)?;\n        }\n      }\n      _ => self.image.to_css(dest)?,\n    }\n\n    // TODO: Throwing an error when `self.resolution = Resolution::Dppx(0.0)`\n    // TODO: -webkit-image-set() does not support `<image()> | <image-set()> |\n    // <cross-fade()> | <element()> | <gradient>` and `type(<string>)`.\n    dest.write_char(' ')?;\n\n    // Safari only supports the x resolution unit in image-set().\n    // In other places, x was added as an alias later.\n    // Temporarily ignore the targets while printing here.\n    let targets = std::mem::take(&mut dest.targets.current);\n    self.resolution.to_css(dest)?;\n    dest.targets.current = targets;\n\n    if let Some(file_type) = &self.file_type {\n      dest.write_str(\" type(\")?;\n      serialize_string(&file_type, dest)?;\n      dest.write_char(')')?;\n    }\n\n    Ok(())\n  }\n}\n\nfn parse_file_type<'i, 't>(input: &mut Parser<'i, 't>) -> Result<CowRcStr<'i>, ParseError<'i, ParserError<'i>>> {\n  input.expect_function_matching(\"type\")?;\n  input.parse_nested_block(|input| Ok(input.expect_string_cloned()?))\n}\n"
  },
  {
    "path": "src/values/length.rs",
    "content": "//! CSS length values.\n\nuse super::angle::impl_try_from_angle;\nuse super::calc::{Calc, MathFunction};\nuse super::number::CSSNumber;\nuse super::percentage::DimensionPercentage;\nuse crate::error::{ParserError, PrinterError};\nuse crate::printer::Printer;\nuse crate::targets::Browsers;\nuse crate::traits::{\n  private::{AddInternal, TryAdd},\n  Map, Parse, Sign, ToCss, TryMap, TryOp, Zero,\n};\nuse crate::traits::{IsCompatible, TrySign};\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse const_str;\nuse cssparser::*;\n\n/// A CSS [`<length-percentage>`](https://www.w3.org/TR/css-values-4/#typedef-length-percentage) value.\n/// May be specified as either a length or a percentage that resolves to an length.\npub type LengthPercentage = DimensionPercentage<LengthValue>;\n\nimpl LengthPercentage {\n  /// Constructs a `LengthPercentage` with the given pixel value.\n  pub fn px(val: CSSNumber) -> LengthPercentage {\n    LengthPercentage::Dimension(LengthValue::Px(val))\n  }\n\n  pub(crate) fn to_css_unitless<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      DimensionPercentage::Dimension(d) => d.to_css_unitless(dest),\n      _ => self.to_css(dest),\n    }\n  }\n}\n\nimpl IsCompatible for LengthPercentage {\n  fn is_compatible(&self, browsers: Browsers) -> bool {\n    match self {\n      LengthPercentage::Dimension(d) => d.is_compatible(browsers),\n      LengthPercentage::Calc(c) => c.is_compatible(browsers),\n      LengthPercentage::Percentage(..) => true,\n    }\n  }\n}\n\n/// Either a [`<length-percentage>`](https://www.w3.org/TR/css-values-4/#typedef-length-percentage), or the `auto` keyword.\n#[derive(Debug, Clone, PartialEq, Parse, ToCss)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum LengthPercentageOrAuto {\n  /// The `auto` keyword.\n  Auto,\n  /// A [`<length-percentage>`](https://www.w3.org/TR/css-values-4/#typedef-length-percentage).\n  LengthPercentage(LengthPercentage),\n}\n\nimpl IsCompatible for LengthPercentageOrAuto {\n  fn is_compatible(&self, browsers: Browsers) -> bool {\n    match self {\n      LengthPercentageOrAuto::LengthPercentage(p) => p.is_compatible(browsers),\n      _ => true,\n    }\n  }\n}\n\nconst PX_PER_IN: f32 = 96.0;\nconst PX_PER_CM: f32 = PX_PER_IN / 2.54;\nconst PX_PER_MM: f32 = PX_PER_CM / 10.0;\nconst PX_PER_Q: f32 = PX_PER_CM / 40.0;\nconst PX_PER_PT: f32 = PX_PER_IN / 72.0;\nconst PX_PER_PC: f32 = PX_PER_IN / 6.0;\n\nmacro_rules! define_length_units {\n  (\n    $(\n      $(#[$meta: meta])*\n      $name: ident $(/ $feature: ident)?,\n    )+\n  ) => {\n    /// A CSS [`<length>`](https://www.w3.org/TR/css-values-4/#lengths) value,\n    /// without support for `calc()`. See also: [Length](Length).\n    #[derive(Debug, Clone, PartialEq)]\n    #[cfg_attr(feature = \"visitor\", derive(Visit))]\n    #[cfg_attr(feature = \"visitor\", visit(visit_length, LENGTHS))]\n    #[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize), serde(tag = \"unit\", content = \"value\", rename_all = \"kebab-case\"))]\n    #[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n    pub enum LengthValue {\n      $(\n        $(#[$meta])*\n        $name(CSSNumber),\n      )+\n    }\n\n    impl<'i> Parse<'i> for LengthValue {\n      fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n        let location = input.current_source_location();\n        let token = input.next()?;\n        match *token {\n          Token::Dimension { value, ref unit, .. } => {\n            Ok(match unit {\n              $(\n                s if s.eq_ignore_ascii_case(stringify!($name)) => LengthValue::$name(value),\n              )+\n              _ => return Err(location.new_unexpected_token_error(token.clone())),\n            })\n          },\n          Token::Number { value, .. } => {\n            // TODO: quirks mode only?\n            Ok(LengthValue::Px(value))\n          }\n          ref token => return Err(location.new_unexpected_token_error(token.clone())),\n        }\n      }\n    }\n\n    impl<'i> TryFrom<&Token<'i>> for LengthValue {\n      type Error = ();\n\n      fn try_from(token: &Token) -> Result<Self, Self::Error> {\n        match token {\n          Token::Dimension { value, ref unit, .. } => {\n            Ok(match unit {\n              $(\n                s if s.eq_ignore_ascii_case(stringify!($name)) => LengthValue::$name(*value),\n              )+\n              _ => return Err(()),\n            })\n          },\n          _ => Err(())\n        }\n      }\n    }\n\n    impl LengthValue {\n      /// Returns the numeric value and unit string for the length value.\n      pub fn to_unit_value(&self) -> (CSSNumber, &str) {\n        match self {\n          $(\n            LengthValue::$name(value) => (*value, const_str::convert_ascii_case!(lower, stringify!($name))),\n          )+\n        }\n      }\n    }\n\n    impl IsCompatible for LengthValue {\n      fn is_compatible(&self, browsers: Browsers) -> bool {\n        macro_rules! is_compatible {\n          ($f: ident) => {\n            crate::compat::Feature::$f.is_compatible(browsers)\n          };\n          () => {\n            true\n          };\n        }\n\n        match self {\n          $(\n            LengthValue::$name(_) => {\n              is_compatible!($($feature)?)\n            }\n          )+\n        }\n      }\n    }\n\n    impl TryAdd<LengthValue> for LengthValue {\n      fn try_add(&self, other: &LengthValue) -> Option<LengthValue> {\n        use LengthValue::*;\n        match (self, other) {\n          $(\n            ($name(a), $name(b)) => Some($name(a + b)),\n          )+\n          (a, b) => {\n            if let (Some(a), Some(b)) = (a.to_px(), b.to_px()) {\n              Some(Px(a + b))\n            } else {\n              None\n            }\n          }\n        }\n      }\n    }\n\n    impl std::ops::Mul<CSSNumber> for LengthValue {\n      type Output = Self;\n\n      fn mul(self, other: CSSNumber) -> LengthValue {\n        use LengthValue::*;\n        match self {\n          $(\n            $name(value) => $name(value * other),\n          )+\n        }\n      }\n    }\n\n    impl std::cmp::PartialOrd<LengthValue> for LengthValue {\n      fn partial_cmp(&self, other: &LengthValue) -> Option<std::cmp::Ordering> {\n        use LengthValue::*;\n        match (self, other) {\n          $(\n            ($name(a), $name(b)) => a.partial_cmp(b),\n          )+\n          (a, b) => {\n            if let (Some(a), Some(b)) = (a.to_px(), b.to_px()) {\n              a.partial_cmp(&b)\n            } else {\n              None\n            }\n          }\n        }\n      }\n    }\n\n    impl TryOp for LengthValue {\n      fn try_op<F: FnOnce(f32, f32) -> f32>(&self, rhs: &Self, op: F) -> Option<Self> {\n        use LengthValue::*;\n        match (self, rhs) {\n          $(\n            ($name(a), $name(b)) => Some($name(op(*a, *b))),\n          )+\n          (a, b) => {\n            if let (Some(a), Some(b)) = (a.to_px(), b.to_px()) {\n              Some(Px(op(a, b)))\n            } else {\n              None\n            }\n          }\n        }\n      }\n\n      fn try_op_to<T, F: FnOnce(f32, f32) -> T>(&self, rhs: &Self, op: F) -> Option<T> {\n        use LengthValue::*;\n        match (self, rhs) {\n          $(\n            ($name(a), $name(b)) => Some(op(*a, *b)),\n          )+\n          (a, b) => {\n            if let (Some(a), Some(b)) = (a.to_px(), b.to_px()) {\n              Some(op(a, b))\n            } else {\n              None\n            }\n          }\n        }\n      }\n    }\n\n    impl Map for LengthValue {\n      fn map<F: FnOnce(f32) -> f32>(&self, op: F) -> Self {\n        use LengthValue::*;\n        match self {\n          $(\n            $name(value) => $name(op(*value)),\n          )+\n        }\n      }\n    }\n\n    impl Sign for LengthValue {\n      fn sign(&self) -> f32 {\n        use LengthValue::*;\n        match self {\n          $(\n            $name(value) => value.sign(),\n          )+\n        }\n      }\n    }\n\n    impl Zero for LengthValue {\n      fn zero() -> Self {\n        LengthValue::Px(0.0)\n      }\n\n      fn is_zero(&self) -> bool {\n        use LengthValue::*;\n        match self {\n          $(\n            $name(value) => value.is_zero(),\n          )+\n        }\n      }\n    }\n\n    impl_try_from_angle!(LengthValue);\n\n    #[cfg(feature = \"jsonschema\")]\n    #[cfg_attr(docsrs, doc(cfg(feature = \"jsonschema\")))]\n    impl schemars::JsonSchema for LengthValue {\n      fn is_referenceable() -> bool {\n        true\n      }\n\n      fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {\n        #[derive(schemars::JsonSchema)]\n        #[schemars(rename_all = \"lowercase\")]\n        #[allow(dead_code)]\n        enum LengthUnit {\n          $(\n            $(#[$meta])*\n            $name,\n          )+\n        }\n\n        #[derive(schemars::JsonSchema)]\n        #[allow(dead_code)]\n        struct LengthValue {\n          /// The length unit.\n          unit: LengthUnit,\n          /// The length value.\n          value: CSSNumber\n        }\n\n        LengthValue::json_schema(gen)\n      }\n\n      fn schema_name() -> String {\n        \"LengthValue\".into()\n      }\n    }\n  };\n}\n\ndefine_length_units! {\n  // https://www.w3.org/TR/css-values-4/#absolute-lengths\n  /// A length in pixels.\n  Px,\n  /// A length in inches. 1in = 96px.\n  In,\n  /// A length in centimeters. 1cm = 96px / 2.54.\n  Cm,\n  /// A length in millimeters. 1mm = 1/10th of 1cm.\n  Mm,\n  /// A length in quarter-millimeters. 1Q = 1/40th of 1cm.\n  Q / QUnit,\n  /// A length in points. 1pt = 1/72nd of 1in.\n  Pt,\n  /// A length in picas. 1pc = 1/6th of 1in.\n  Pc,\n\n  // https://www.w3.org/TR/css-values-4/#font-relative-lengths\n  /// A length in the `em` unit. An `em` is equal to the computed value of the\n  /// font-size property of the element on which it is used.\n  Em,\n  /// A length in the `rem` unit. A `rem` is equal to the computed value of the\n  /// `em` unit on the root element.\n  Rem / RemUnit,\n  /// A length in `ex` unit. An `ex` is equal to the x-height of the font.\n  Ex / ExUnit,\n  /// A length in the `rex` unit. A `rex` is equal to the value of the `ex` unit on the root element.\n  Rex,\n  /// A length in the `ch` unit. A `ch` is equal to the width of the zero (\"0\") character in the current font.\n  Ch / ChUnit,\n  /// A length in the `rch` unit. An `rch` is equal to the value of the `ch` unit on the root element.\n  Rch,\n  /// A length in the `cap` unit. A `cap` is equal to the cap-height of the font.\n  Cap / CapUnit,\n  /// A length in the `rcap` unit. An `rcap` is equal to the value of the `cap` unit on the root element.\n  Rcap,\n  /// A length in the `ic` unit. An `ic` is equal to the width of the “水” (CJK water ideograph) character in the current font.\n  Ic / IcUnit,\n  /// A length in the `ric` unit. An `ric` is equal to the value of the `ic` unit on the root element.\n  Ric,\n  /// A length in the `lh` unit. An `lh` is equal to the computed value of the `line-height` property.\n  Lh / LhUnit,\n  /// A length in the `rlh` unit. An `rlh` is equal to the value of the `lh` unit on the root element.\n  Rlh / RlhUnit,\n\n  // https://www.w3.org/TR/css-values-4/#viewport-relative-units\n  /// A length in the `vw` unit. A `vw` is equal to 1% of the [viewport width](https://www.w3.org/TR/css-values-4/#ua-default-viewport-size).\n  Vw / VwUnit,\n  /// A length in the `lvw` unit. An `lvw` is equal to 1% of the [large viewport width](https://www.w3.org/TR/css-values-4/#large-viewport-size).\n  Lvw / ViewportPercentageUnitsLarge,\n  /// A length in the `svw` unit. An `svw` is equal to 1% of the [small viewport width](https://www.w3.org/TR/css-values-4/#small-viewport-size).\n  Svw / ViewportPercentageUnitsSmall,\n  /// A length in the `dvw` unit. An `dvw` is equal to 1% of the [dynamic viewport width](https://www.w3.org/TR/css-values-4/#dynamic-viewport-size).\n  Dvw / ViewportPercentageUnitsDynamic,\n  /// A length in the `cqw` unit. An `cqw` is equal to 1% of the [query container](https://drafts.csswg.org/css-contain-3/#query-container) width.\n  Cqw / ContainerQueryLengthUnits,\n\n  /// A length in the `vh` unit. A `vh` is equal to 1% of the [viewport height](https://www.w3.org/TR/css-values-4/#ua-default-viewport-size).\n  Vh / VhUnit,\n  /// A length in the `lvh` unit. An `lvh` is equal to 1% of the [large viewport height](https://www.w3.org/TR/css-values-4/#large-viewport-size).\n  Lvh / ViewportPercentageUnitsLarge,\n  /// A length in the `svh` unit. An `svh` is equal to 1% of the [small viewport height](https://www.w3.org/TR/css-values-4/#small-viewport-size).\n  Svh / ViewportPercentageUnitsSmall,\n  /// A length in the `dvh` unit. An `dvh` is equal to 1% of the [dynamic viewport height](https://www.w3.org/TR/css-values-4/#dynamic-viewport-size).\n  Dvh / ViewportPercentageUnitsDynamic,\n  /// A length in the `cqh` unit. An `cqh` is equal to 1% of the [query container](https://drafts.csswg.org/css-contain-3/#query-container) height.\n  Cqh / ContainerQueryLengthUnits,\n\n  /// A length in the `vi` unit. A `vi` is equal to 1% of the [viewport size](https://www.w3.org/TR/css-values-4/#ua-default-viewport-size)\n  /// in the box's [inline axis](https://www.w3.org/TR/css-writing-modes-4/#inline-axis).\n  Vi / ViUnit,\n  /// A length in the `svi` unit. A `svi` is equal to 1% of the [small viewport size](https://www.w3.org/TR/css-values-4/#small-viewport-size)\n  /// in the box's [inline axis](https://www.w3.org/TR/css-writing-modes-4/#inline-axis).\n  Svi / ViewportPercentageUnitsSmall,\n  /// A length in the `lvi` unit. A `lvi` is equal to 1% of the [large viewport size](https://www.w3.org/TR/css-values-4/#large-viewport-size)\n  /// in the box's [inline axis](https://www.w3.org/TR/css-writing-modes-4/#inline-axis).\n  Lvi / ViewportPercentageUnitsLarge,\n  /// A length in the `dvi` unit. A `dvi` is equal to 1% of the [dynamic viewport size](https://www.w3.org/TR/css-values-4/#dynamic-viewport-size)\n  /// in the box's [inline axis](https://www.w3.org/TR/css-writing-modes-4/#inline-axis).\n  Dvi / ViewportPercentageUnitsDynamic,\n  /// A length in the `cqi` unit. An `cqi` is equal to 1% of the [query container](https://drafts.csswg.org/css-contain-3/#query-container) inline size.\n  Cqi / ContainerQueryLengthUnits,\n\n  /// A length in the `vb` unit. A `vb` is equal to 1% of the [viewport size](https://www.w3.org/TR/css-values-4/#ua-default-viewport-size)\n  /// in the box's [block axis](https://www.w3.org/TR/css-writing-modes-4/#block-axis).\n  Vb / VbUnit,\n  /// A length in the `svb` unit. A `svb` is equal to 1% of the [small viewport size](https://www.w3.org/TR/css-values-4/#small-viewport-size)\n  /// in the box's [block axis](https://www.w3.org/TR/css-writing-modes-4/#block-axis).\n  Svb / ViewportPercentageUnitsSmall,\n  /// A length in the `lvb` unit. A `lvb` is equal to 1% of the [large viewport size](https://www.w3.org/TR/css-values-4/#large-viewport-size)\n  /// in the box's [block axis](https://www.w3.org/TR/css-writing-modes-4/#block-axis).\n  Lvb / ViewportPercentageUnitsLarge,\n  /// A length in the `dvb` unit. A `dvb` is equal to 1% of the [dynamic viewport size](https://www.w3.org/TR/css-values-4/#dynamic-viewport-size)\n  /// in the box's [block axis](https://www.w3.org/TR/css-writing-modes-4/#block-axis).\n  Dvb / ViewportPercentageUnitsDynamic,\n  /// A length in the `cqb` unit. An `cqb` is equal to 1% of the [query container](https://drafts.csswg.org/css-contain-3/#query-container) block size.\n  Cqb / ContainerQueryLengthUnits,\n\n  /// A length in the `vmin` unit. A `vmin` is equal to the smaller of `vw` and `vh`.\n  Vmin / VminUnit,\n  /// A length in the `svmin` unit. An `svmin` is equal to the smaller of `svw` and `svh`.\n  Svmin / ViewportPercentageUnitsSmall,\n  /// A length in the `lvmin` unit. An `lvmin` is equal to the smaller of `lvw` and `lvh`.\n  Lvmin / ViewportPercentageUnitsLarge,\n  /// A length in the `dvmin` unit. A `dvmin` is equal to the smaller of `dvw` and `dvh`.\n  Dvmin / ViewportPercentageUnitsDynamic,\n  /// A length in the `cqmin` unit. An `cqmin` is equal to the smaller of `cqi` and `cqb`.\n  Cqmin / ContainerQueryLengthUnits,\n\n  /// A length in the `vmax` unit. A `vmax` is equal to the larger of `vw` and `vh`.\n  Vmax / VmaxUnit,\n  /// A length in the `svmax` unit. An `svmax` is equal to the larger of `svw` and `svh`.\n  Svmax / ViewportPercentageUnitsSmall,\n  /// A length in the `lvmax` unit. An `lvmax` is equal to the larger of `lvw` and `lvh`.\n  Lvmax / ViewportPercentageUnitsLarge,\n  /// A length in the `dvmax` unit. An `dvmax` is equal to the larger of `dvw` and `dvh`.\n  Dvmax / ViewportPercentageUnitsDynamic,\n  /// A length in the `cqmax` unit. An `cqmin` is equal to the larger of `cqi` and `cqb`.\n  Cqmax / ContainerQueryLengthUnits,\n}\n\nimpl ToCss for LengthValue {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    let (value, unit) = self.to_unit_value();\n\n    // The unit can be omitted if the value is zero, except inside calc()\n    // expressions, where unitless numbers won't be parsed as dimensions.\n    if !dest.in_calc && value == 0.0 {\n      return dest.write_char('0');\n    }\n\n    serialize_dimension(value, unit, dest)\n  }\n}\n\nimpl LengthValue {\n  pub(crate) fn to_css_unitless<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      LengthValue::Px(value) => value.to_css(dest),\n      _ => self.to_css(dest),\n    }\n  }\n}\n\npub(crate) fn serialize_dimension<W>(value: f32, unit: &str, dest: &mut Printer<W>) -> Result<(), PrinterError>\nwhere\n  W: std::fmt::Write,\n{\n  use cssparser::ToCss;\n  let int_value = if value.fract() == 0.0 { Some(value as i32) } else { None };\n  let token = Token::Dimension {\n    has_sign: value < 0.0,\n    value,\n    int_value,\n    unit: CowRcStr::from(unit),\n  };\n  if value != 0.0 && value.abs() < 1.0 {\n    let mut s = String::new();\n    token.to_css(&mut s)?;\n    if value < 0.0 {\n      dest.write_char('-')?;\n      dest.write_str(s.trim_start_matches(\"-0\"))\n    } else {\n      dest.write_str(s.trim_start_matches('0'))\n    }\n  } else {\n    token.to_css(dest)?;\n    Ok(())\n  }\n}\n\nimpl LengthValue {\n  /// Attempts to convert the value to pixels.\n  /// Returns `None` if the conversion is not possible.\n  pub fn to_px(&self) -> Option<CSSNumber> {\n    use LengthValue::*;\n    match self {\n      Px(value) => Some(*value),\n      In(value) => Some(value * PX_PER_IN),\n      Cm(value) => Some(value * PX_PER_CM),\n      Mm(value) => Some(value * PX_PER_MM),\n      Q(value) => Some(value * PX_PER_Q),\n      Pt(value) => Some(value * PX_PER_PT),\n      Pc(value) => Some(value * PX_PER_PC),\n      _ => None,\n    }\n  }\n}\n\n/// A CSS [`<length>`](https://www.w3.org/TR/css-values-4/#lengths) value, with support for `calc()`.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum Length {\n  /// An explicitly specified length value.\n  Value(LengthValue),\n  /// A computed length value using `calc()`.\n  #[cfg_attr(feature = \"visitor\", skip_type)]\n  Calc(Box<Calc<Length>>),\n}\n\nimpl<'i> Parse<'i> for Length {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    match input.try_parse(Calc::parse) {\n      Ok(Calc::Value(v)) => return Ok(*v),\n      Ok(calc) => return Ok(Length::Calc(Box::new(calc))),\n      _ => {}\n    }\n\n    let len = LengthValue::parse(input)?;\n    Ok(Length::Value(len))\n  }\n}\n\nimpl ToCss for Length {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      Length::Value(a) => a.to_css(dest),\n      Length::Calc(c) => c.to_css(dest),\n    }\n  }\n}\n\nimpl std::ops::Mul<CSSNumber> for Length {\n  type Output = Self;\n\n  fn mul(self, other: CSSNumber) -> Length {\n    match self {\n      Length::Value(a) => Length::Value(a * other),\n      Length::Calc(a) => Length::Calc(Box::new(*a * other)),\n    }\n  }\n}\n\nimpl std::ops::Add<Length> for Length {\n  type Output = Self;\n\n  fn add(self, other: Length) -> Length {\n    // Unwrap calc(...) functions so we can add inside.\n    // Then wrap the result in a calc(...) again if necessary.\n    let a = unwrap_calc(self);\n    let b = unwrap_calc(other);\n    let res = AddInternal::add(a, b);\n    match res {\n      Length::Calc(c) => match *c {\n        Calc::Value(l) => *l,\n        Calc::Function(f) if !matches!(*f, MathFunction::Calc(_)) => Length::Calc(Box::new(Calc::Function(f))),\n        c => Length::Calc(Box::new(Calc::Function(Box::new(MathFunction::Calc(c))))),\n      },\n      _ => res,\n    }\n  }\n}\n\nfn unwrap_calc(length: Length) -> Length {\n  match length {\n    Length::Calc(c) => match *c {\n      Calc::Function(f) => match *f {\n        MathFunction::Calc(c) => Length::Calc(Box::new(c)),\n        c => Length::Calc(Box::new(Calc::Function(Box::new(c)))),\n      },\n      _ => Length::Calc(c),\n    },\n    _ => length,\n  }\n}\n\nimpl AddInternal for Length {\n  fn add(self, other: Self) -> Self {\n    match self.try_add(&other) {\n      Some(r) => r,\n      None => self.add(other),\n    }\n  }\n}\n\nimpl Length {\n  /// Constructs a length with the given pixel value.\n  pub fn px(px: CSSNumber) -> Length {\n    Length::Value(LengthValue::Px(px))\n  }\n\n  /// Attempts to convert the length to pixels.\n  /// Returns `None` if the conversion is not possible.\n  pub fn to_px(&self) -> Option<CSSNumber> {\n    match self {\n      Length::Value(a) => a.to_px(),\n      _ => None,\n    }\n  }\n\n  fn add(self, other: Length) -> Length {\n    let mut a = self;\n    let mut b = other;\n\n    if a.is_zero() {\n      return b;\n    }\n\n    if b.is_zero() {\n      return a;\n    }\n\n    if a.is_sign_negative() && b.is_sign_positive() {\n      std::mem::swap(&mut a, &mut b);\n    }\n\n    match (a, b) {\n      (Length::Calc(a), Length::Calc(b)) => return Length::Calc(Box::new(a.add(*b).unwrap())),\n      (Length::Calc(calc), b) => {\n        if let Calc::Value(a) = *calc {\n          a.add(b)\n        } else {\n          Length::Calc(Box::new(Calc::Sum(Box::new((*calc).into()), Box::new(b.into()))))\n        }\n      }\n      (a, Length::Calc(calc)) => {\n        if let Calc::Value(b) = *calc {\n          a.add(*b)\n        } else {\n          Length::Calc(Box::new(Calc::Sum(Box::new(a.into()), Box::new((*calc).into()))))\n        }\n      }\n      (a, b) => Length::Calc(Box::new(Calc::Sum(Box::new(a.into()), Box::new(b.into())))),\n    }\n  }\n}\n\nimpl IsCompatible for Length {\n  fn is_compatible(&self, browsers: Browsers) -> bool {\n    match self {\n      Length::Value(v) => v.is_compatible(browsers),\n      Length::Calc(calc) => calc.is_compatible(browsers),\n    }\n  }\n}\n\nimpl Zero for Length {\n  fn zero() -> Length {\n    Length::Value(LengthValue::Px(0.0))\n  }\n\n  fn is_zero(&self) -> bool {\n    match self {\n      Length::Value(v) => v.is_zero(),\n      _ => false,\n    }\n  }\n}\n\nimpl TryAdd<Length> for Length {\n  fn try_add(&self, other: &Length) -> Option<Length> {\n    match (self, other) {\n      (Length::Value(a), Length::Value(b)) => {\n        if let Some(res) = a.try_add(b) {\n          Some(Length::Value(res))\n        } else {\n          None\n        }\n      }\n      (Length::Calc(a), other) => match &**a {\n        Calc::Value(v) => v.try_add(other),\n        Calc::Sum(a, b) => {\n          if let Some(res) = Length::Calc(Box::new(*a.clone())).try_add(other) {\n            return Some(res.add(Length::from(*b.clone())));\n          }\n\n          if let Some(res) = Length::Calc(Box::new(*b.clone())).try_add(other) {\n            return Some(Length::from(*a.clone()).add(res));\n          }\n\n          None\n        }\n        _ => None,\n      },\n      (other, Length::Calc(b)) => match &**b {\n        Calc::Value(v) => other.try_add(&*v),\n        Calc::Sum(a, b) => {\n          if let Some(res) = other.try_add(&Length::Calc(Box::new(*a.clone()))) {\n            return Some(res.add(Length::from(*b.clone())));\n          }\n\n          if let Some(res) = other.try_add(&Length::Calc(Box::new(*b.clone()))) {\n            return Some(Length::from(*a.clone()).add(res));\n          }\n\n          None\n        }\n        _ => None,\n      },\n    }\n  }\n}\n\nimpl std::convert::Into<Calc<Length>> for Length {\n  fn into(self) -> Calc<Length> {\n    match self {\n      Length::Calc(c) => *c,\n      b => Calc::Value(Box::new(b)),\n    }\n  }\n}\n\nimpl std::convert::From<Calc<Length>> for Length {\n  fn from(calc: Calc<Length>) -> Length {\n    Length::Calc(Box::new(calc))\n  }\n}\n\nimpl std::cmp::PartialOrd<Length> for Length {\n  fn partial_cmp(&self, other: &Length) -> Option<std::cmp::Ordering> {\n    match (self, other) {\n      (Length::Value(a), Length::Value(b)) => a.partial_cmp(b),\n      _ => None,\n    }\n  }\n}\n\nimpl TryOp for Length {\n  fn try_op<F: FnOnce(f32, f32) -> f32>(&self, rhs: &Self, op: F) -> Option<Self> {\n    match (self, rhs) {\n      (Length::Value(a), Length::Value(b)) => a.try_op(b, op).map(Length::Value),\n      _ => None,\n    }\n  }\n\n  fn try_op_to<T, F: FnOnce(f32, f32) -> T>(&self, rhs: &Self, op: F) -> Option<T> {\n    match (self, rhs) {\n      (Length::Value(a), Length::Value(b)) => a.try_op_to(b, op),\n      _ => None,\n    }\n  }\n}\n\nimpl TryMap for Length {\n  fn try_map<F: FnOnce(f32) -> f32>(&self, op: F) -> Option<Self> {\n    match self {\n      Length::Value(v) => v.try_map(op).map(Length::Value),\n      _ => None,\n    }\n  }\n}\n\nimpl TrySign for Length {\n  fn try_sign(&self) -> Option<f32> {\n    match self {\n      Length::Value(v) => Some(v.sign()),\n      Length::Calc(c) => c.try_sign(),\n    }\n  }\n}\n\nimpl_try_from_angle!(Length);\n\n/// Either a [`<length>`](https://www.w3.org/TR/css-values-4/#lengths) or a [`<number>`](https://www.w3.org/TR/css-values-4/#numbers).\n#[derive(Debug, Clone, PartialEq, Parse, ToCss)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum LengthOrNumber {\n  /// A number.\n  Number(CSSNumber),\n  /// A length.\n  Length(Length),\n}\n\nimpl Default for LengthOrNumber {\n  fn default() -> LengthOrNumber {\n    LengthOrNumber::Number(0.0)\n  }\n}\n\nimpl Zero for LengthOrNumber {\n  fn zero() -> Self {\n    LengthOrNumber::Number(0.0)\n  }\n\n  fn is_zero(&self) -> bool {\n    match self {\n      LengthOrNumber::Length(l) => l.is_zero(),\n      LengthOrNumber::Number(v) => v.is_zero(),\n    }\n  }\n}\n\nimpl IsCompatible for LengthOrNumber {\n  fn is_compatible(&self, browsers: Browsers) -> bool {\n    match self {\n      LengthOrNumber::Length(l) => l.is_compatible(browsers),\n      LengthOrNumber::Number(..) => true,\n    }\n  }\n}\n"
  },
  {
    "path": "src/values/mod.rs",
    "content": "//! Common [CSS values](https://www.w3.org/TR/css3-values/) used across many properties.\n//!\n//! Each value provides parsing and serialization support using the [Parse](super::traits::Parse)\n//! and [ToCss](super::traits::ToCss) traits. In addition, many values support ways of manipulating\n//! them, including converting between representations and units, generating fallbacks for legacy\n//! browsers, minifying them, etc.\n//!\n//! # Example\n//!\n//! This example shows how you could parse a CSS color value, convert it to RGB, and re-serialize it.\n//! Similar patterns for parsing and serializing are possible across all value types.\n//!\n//! ```\n//! use lightningcss::{\n//!   traits::{Parse, ToCss},\n//!   values::color::CssColor,\n//!   printer::PrinterOptions\n//! };\n//!\n//! let color = CssColor::parse_string(\"lch(50% 75 0)\").unwrap();\n//! let rgb = color.to_rgb().unwrap();\n//! assert_eq!(rgb.to_css_string(PrinterOptions::default()).unwrap(), \"#e1157b\");\n//! ```\n//!\n//! If you have a [cssparser::Parser](cssparser::Parser) already, you can also use the `parse` and `to_css`\n//! methods instead, rather than parsing from a string.\n\n#![deny(missing_docs)]\n\npub mod alpha;\npub mod angle;\npub mod calc;\npub mod color;\npub mod easing;\npub mod gradient;\npub mod ident;\npub mod image;\npub mod length;\npub mod number;\npub mod percentage;\npub mod position;\npub mod ratio;\npub mod rect;\npub mod resolution;\npub mod shape;\npub mod size;\npub mod string;\npub mod syntax;\npub mod time;\npub mod url;\n"
  },
  {
    "path": "src/values/number.rs",
    "content": "//! CSS number values.\n\nuse super::angle::impl_try_from_angle;\nuse super::calc::Calc;\nuse crate::error::{ParserError, PrinterError};\nuse crate::printer::Printer;\nuse crate::traits::private::AddInternal;\nuse crate::traits::{Map, Op, Parse, Sign, ToCss, Zero};\nuse cssparser::*;\n\n/// A CSS [`<number>`](https://www.w3.org/TR/css-values-4/#numbers) value.\n///\n/// Numbers may be explicit or computed by `calc()`, but are always stored and serialized\n/// as their computed value.\npub type CSSNumber = f32;\n\nimpl<'i> Parse<'i> for CSSNumber {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    match input.try_parse(Calc::parse) {\n      Ok(Calc::Value(v)) => return Ok(*v),\n      Ok(Calc::Number(n)) => return Ok(n),\n      // Numbers are always compatible, so they will always compute to a value.\n      Ok(_) => return Err(input.new_custom_error(ParserError::InvalidValue)),\n      _ => {}\n    }\n\n    let number = input.expect_number()?;\n    Ok(number)\n  }\n}\n\nimpl ToCss for CSSNumber {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    let number = *self;\n    if number != 0.0 && number.abs() < 1.0 {\n      let mut s = String::new();\n      cssparser::ToCss::to_css(self, &mut s)?;\n      if number < 0.0 {\n        dest.write_char('-')?;\n        dest.write_str(s.trim_start_matches(\"-\").trim_start_matches(\"0\"))\n      } else {\n        dest.write_str(s.trim_start_matches('0'))\n      }\n    } else {\n      cssparser::ToCss::to_css(self, dest)?;\n      Ok(())\n    }\n  }\n}\n\nimpl std::convert::Into<Calc<CSSNumber>> for CSSNumber {\n  fn into(self) -> Calc<CSSNumber> {\n    Calc::Value(Box::new(self))\n  }\n}\n\nimpl std::convert::From<Calc<CSSNumber>> for CSSNumber {\n  fn from(calc: Calc<CSSNumber>) -> CSSNumber {\n    match calc {\n      Calc::Value(v) => *v,\n      Calc::Number(n) => n,\n      _ => unreachable!(),\n    }\n  }\n}\n\nimpl AddInternal for CSSNumber {\n  fn add(self, other: Self) -> Self {\n    self + other\n  }\n}\n\nimpl Op for CSSNumber {\n  fn op<F: FnOnce(f32, f32) -> f32>(&self, to: &Self, op: F) -> Self {\n    op(*self, *to)\n  }\n\n  fn op_to<T, F: FnOnce(f32, f32) -> T>(&self, rhs: &Self, op: F) -> T {\n    op(*self, *rhs)\n  }\n}\n\nimpl Map for CSSNumber {\n  fn map<F: FnOnce(f32) -> f32>(&self, op: F) -> Self {\n    op(*self)\n  }\n}\n\nimpl Sign for CSSNumber {\n  fn sign(&self) -> f32 {\n    if *self == 0.0 {\n      return if f32::is_sign_positive(*self) { 0.0 } else { -0.0 };\n    }\n    self.signum()\n  }\n}\n\nimpl Zero for CSSNumber {\n  fn zero() -> Self {\n    0.0\n  }\n\n  fn is_zero(&self) -> bool {\n    *self == 0.0\n  }\n}\n\nimpl_try_from_angle!(CSSNumber);\n\n/// A CSS [`<integer>`](https://www.w3.org/TR/css-values-4/#integers) value.\npub type CSSInteger = i32;\n\nimpl<'i> Parse<'i> for CSSInteger {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    // TODO: calc??\n    let integer = input.expect_integer()?;\n    Ok(integer)\n  }\n}\n\nimpl ToCss for CSSInteger {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    cssparser::ToCss::to_css(self, dest)?;\n    Ok(())\n  }\n}\n\nimpl Zero for CSSInteger {\n  fn zero() -> Self {\n    0\n  }\n\n  fn is_zero(&self) -> bool {\n    *self == 0\n  }\n}\n"
  },
  {
    "path": "src/values/percentage.rs",
    "content": "//! CSS percentage values.\n\nuse super::angle::{impl_try_from_angle, Angle};\nuse super::calc::{Calc, MathFunction};\nuse super::number::CSSNumber;\nuse crate::error::{ParserError, PrinterError};\nuse crate::printer::Printer;\nuse crate::traits::private::AddInternal;\nuse crate::traits::{impl_op, private::TryAdd, Op, Parse, Sign, ToCss, TryMap, TryOp, TrySign, Zero};\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse cssparser::*;\n\n/// A CSS [`<percentage>`](https://www.w3.org/TR/css-values-4/#percentages) value.\n///\n/// Percentages may be explicit or computed by `calc()`, but are always stored and serialized\n/// as their computed value.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize), serde(transparent))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub struct Percentage(pub CSSNumber);\n\nimpl<'i> Parse<'i> for Percentage {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    match input.try_parse(Calc::parse) {\n      Ok(Calc::Value(v)) => return Ok(*v),\n      // Percentages are always compatible, so they will always compute to a value.\n      Ok(_) => unreachable!(),\n      _ => {}\n    }\n\n    let percent = input.expect_percentage()?;\n    Ok(Percentage(percent))\n  }\n}\n\nimpl ToCss for Percentage {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    use cssparser::ToCss;\n    let int_value = if (self.0 * 100.0).fract() == 0.0 {\n      Some(self.0 as i32)\n    } else {\n      None\n    };\n    let percent = Token::Percentage {\n      has_sign: self.0 < 0.0,\n      unit_value: self.0,\n      int_value,\n    };\n    if self.0 != 0.0 && self.0.abs() < 0.01 {\n      let mut s = String::new();\n      percent.to_css(&mut s)?;\n      if self.0 < 0.0 {\n        dest.write_char('-')?;\n        dest.write_str(s.trim_start_matches(\"-0\"))\n      } else {\n        dest.write_str(s.trim_start_matches('0'))\n      }\n    } else {\n      percent.to_css(dest)?;\n      Ok(())\n    }\n  }\n}\n\nimpl std::convert::Into<Calc<Percentage>> for Percentage {\n  fn into(self) -> Calc<Percentage> {\n    Calc::Value(Box::new(self))\n  }\n}\n\nimpl std::convert::TryFrom<Calc<Percentage>> for Percentage {\n  type Error = ();\n\n  fn try_from(calc: Calc<Percentage>) -> Result<Percentage, Self::Error> {\n    match calc {\n      Calc::Value(v) => Ok(*v),\n      _ => Err(()),\n    }\n  }\n}\n\nimpl std::ops::Mul<CSSNumber> for Percentage {\n  type Output = Self;\n\n  fn mul(self, other: CSSNumber) -> Percentage {\n    Percentage(self.0 * other)\n  }\n}\n\nimpl AddInternal for Percentage {\n  fn add(self, other: Self) -> Self {\n    self + other\n  }\n}\n\nimpl std::cmp::PartialOrd<Percentage> for Percentage {\n  fn partial_cmp(&self, other: &Percentage) -> Option<std::cmp::Ordering> {\n    self.0.partial_cmp(&other.0)\n  }\n}\n\nimpl Op for Percentage {\n  fn op<F: FnOnce(f32, f32) -> f32>(&self, to: &Self, op: F) -> Self {\n    Percentage(op(self.0, to.0))\n  }\n\n  fn op_to<T, F: FnOnce(f32, f32) -> T>(&self, rhs: &Self, op: F) -> T {\n    op(self.0, rhs.0)\n  }\n}\n\nimpl TryMap for Percentage {\n  fn try_map<F: FnOnce(f32) -> f32>(&self, _: F) -> Option<Self> {\n    // Percentages cannot be mapped because we don't know what they will resolve to.\n    // For example, they might be positive or negative depending on what they are a\n    // percentage of, which we don't know.\n    None\n  }\n}\n\nimpl Zero for Percentage {\n  fn zero() -> Self {\n    Percentage(0.0)\n  }\n\n  fn is_zero(&self) -> bool {\n    self.0.is_zero()\n  }\n}\n\nimpl Sign for Percentage {\n  fn sign(&self) -> f32 {\n    self.0.sign()\n  }\n}\n\nimpl_op!(Percentage, std::ops::Rem, rem);\nimpl_op!(Percentage, std::ops::Add, add);\n\nimpl_try_from_angle!(Percentage);\n\n/// Either a `<number>` or `<percentage>`.\n#[derive(Debug, Clone, PartialEq, Parse, ToCss)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum NumberOrPercentage {\n  /// A number.\n  Number(CSSNumber),\n  /// A percentage.\n  Percentage(Percentage),\n}\n\nimpl std::convert::Into<CSSNumber> for &NumberOrPercentage {\n  fn into(self) -> CSSNumber {\n    match self {\n      NumberOrPercentage::Number(a) => *a,\n      NumberOrPercentage::Percentage(a) => a.0,\n    }\n  }\n}\n\n/// A generic type that allows any kind of dimension and percentage to be\n/// used standalone or mixed within a `calc()` expression.\n///\n/// <https://drafts.csswg.org/css-values-4/#mixed-percentages>\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum DimensionPercentage<D> {\n  /// An explicit dimension value.\n  Dimension(D),\n  /// A percentage.\n  Percentage(Percentage),\n  /// A `calc()` expression.\n  #[cfg_attr(feature = \"visitor\", skip_type)]\n  Calc(Box<Calc<DimensionPercentage<D>>>),\n}\n\nimpl<\n    'i,\n    D: Parse<'i>\n      + std::ops::Mul<CSSNumber, Output = D>\n      + TryAdd<D>\n      + Clone\n      + TryOp\n      + TryMap\n      + Zero\n      + TrySign\n      + TryFrom<Angle>\n      + TryInto<Angle>\n      + PartialOrd<D>\n      + std::fmt::Debug,\n  > Parse<'i> for DimensionPercentage<D>\n{\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    match input.try_parse(Calc::parse) {\n      Ok(Calc::Value(v)) => return Ok(*v),\n      Ok(calc) => return Ok(DimensionPercentage::Calc(Box::new(calc))),\n      _ => {}\n    }\n\n    if let Ok(length) = input.try_parse(|input| D::parse(input)) {\n      return Ok(DimensionPercentage::Dimension(length));\n    }\n\n    if let Ok(percent) = input.try_parse(|input| Percentage::parse(input)) {\n      return Ok(DimensionPercentage::Percentage(percent));\n    }\n\n    Err(input.new_error_for_next_token())\n  }\n}\n\nimpl<D: std::ops::Mul<CSSNumber, Output = D>> std::ops::Mul<CSSNumber> for DimensionPercentage<D> {\n  type Output = Self;\n\n  fn mul(self, other: CSSNumber) -> DimensionPercentage<D> {\n    match self {\n      DimensionPercentage::Dimension(l) => DimensionPercentage::Dimension(l * other),\n      DimensionPercentage::Percentage(p) => DimensionPercentage::Percentage(Percentage(p.0 * other)),\n      DimensionPercentage::Calc(c) => DimensionPercentage::Calc(Box::new(*c * other)),\n    }\n  }\n}\n\nimpl<D: TryAdd<D> + Clone + Zero + TrySign + std::fmt::Debug> std::ops::Add<DimensionPercentage<D>>\n  for DimensionPercentage<D>\n{\n  type Output = DimensionPercentage<D>;\n\n  fn add(self, other: DimensionPercentage<D>) -> DimensionPercentage<D> {\n    // Unwrap calc(...) functions so we can add inside.\n    // Then wrap the result in a calc(...) again if necessary.\n    let a = unwrap_calc(self);\n    let b = unwrap_calc(other);\n    let res = AddInternal::add(a, b);\n    match res {\n      DimensionPercentage::Calc(c) => match *c {\n        Calc::Value(l) => *l,\n        Calc::Function(f) if !matches!(*f, MathFunction::Calc(_)) => {\n          DimensionPercentage::Calc(Box::new(Calc::Function(f)))\n        }\n        c => DimensionPercentage::Calc(Box::new(Calc::Function(Box::new(MathFunction::Calc(c))))),\n      },\n      _ => res,\n    }\n  }\n}\n\nfn unwrap_calc<D>(v: DimensionPercentage<D>) -> DimensionPercentage<D> {\n  match v {\n    DimensionPercentage::Calc(c) => match *c {\n      Calc::Function(f) => match *f {\n        MathFunction::Calc(c) => DimensionPercentage::Calc(Box::new(c)),\n        c => DimensionPercentage::Calc(Box::new(Calc::Function(Box::new(c)))),\n      },\n      _ => DimensionPercentage::Calc(c),\n    },\n    _ => v,\n  }\n}\n\nimpl<D: TryAdd<D> + Clone + Zero + TrySign + std::fmt::Debug> AddInternal for DimensionPercentage<D> {\n  fn add(self, other: Self) -> Self {\n    match self.add_recursive(&other) {\n      Some(r) => r,\n      None => self.add(other),\n    }\n  }\n}\n\nimpl<D: TryAdd<D> + Clone + Zero + TrySign + std::fmt::Debug> DimensionPercentage<D> {\n  fn add_recursive(&self, other: &DimensionPercentage<D>) -> Option<DimensionPercentage<D>> {\n    match (self, other) {\n      (DimensionPercentage::Dimension(a), DimensionPercentage::Dimension(b)) => {\n        if let Some(res) = a.try_add(b) {\n          Some(DimensionPercentage::Dimension(res))\n        } else {\n          None\n        }\n      }\n      (DimensionPercentage::Percentage(a), DimensionPercentage::Percentage(b)) => {\n        Some(DimensionPercentage::Percentage(Percentage(a.0 + b.0)))\n      }\n      (DimensionPercentage::Calc(a), other) => match &**a {\n        Calc::Value(v) => v.add_recursive(other),\n        Calc::Sum(a, b) => {\n          if let Some(res) = DimensionPercentage::Calc(Box::new(*a.clone())).add_recursive(other) {\n            return Some(res.add(DimensionPercentage::from(*b.clone())));\n          }\n\n          if let Some(res) = DimensionPercentage::Calc(Box::new(*b.clone())).add_recursive(other) {\n            return Some(DimensionPercentage::from(*a.clone()).add(res));\n          }\n\n          None\n        }\n        _ => None,\n      },\n      (other, DimensionPercentage::Calc(b)) => match &**b {\n        Calc::Value(v) => other.add_recursive(&*v),\n        Calc::Sum(a, b) => {\n          if let Some(res) = other.add_recursive(&DimensionPercentage::Calc(Box::new(*a.clone()))) {\n            return Some(res.add(DimensionPercentage::from(*b.clone())));\n          }\n\n          if let Some(res) = other.add_recursive(&DimensionPercentage::Calc(Box::new(*b.clone()))) {\n            return Some(DimensionPercentage::from(*a.clone()).add(res));\n          }\n\n          None\n        }\n        _ => None,\n      },\n      _ => None,\n    }\n  }\n\n  fn add(self, other: DimensionPercentage<D>) -> DimensionPercentage<D> {\n    let mut a = self;\n    let mut b = other;\n\n    if a.is_zero() {\n      return b;\n    }\n\n    if b.is_zero() {\n      return a;\n    }\n\n    if a.is_sign_negative() && b.is_sign_positive() {\n      std::mem::swap(&mut a, &mut b);\n    }\n\n    match (a, b) {\n      (DimensionPercentage::Calc(a), DimensionPercentage::Calc(b)) => {\n        DimensionPercentage::Calc(Box::new(a.add(*b).unwrap()))\n      }\n      (DimensionPercentage::Calc(calc), b) => {\n        if let Calc::Value(a) = *calc {\n          a.add(b)\n        } else {\n          DimensionPercentage::Calc(Box::new(Calc::Sum(Box::new((*calc).into()), Box::new(b.into()))))\n        }\n      }\n      (a, DimensionPercentage::Calc(calc)) => {\n        if let Calc::Value(b) = *calc {\n          a.add(*b)\n        } else {\n          DimensionPercentage::Calc(Box::new(Calc::Sum(Box::new(a.into()), Box::new((*calc).into()))))\n        }\n      }\n      (a, b) => DimensionPercentage::Calc(Box::new(Calc::Sum(Box::new(a.into()), Box::new(b.into())))),\n    }\n  }\n}\n\nimpl<D> std::convert::Into<Calc<DimensionPercentage<D>>> for DimensionPercentage<D> {\n  fn into(self) -> Calc<DimensionPercentage<D>> {\n    match self {\n      DimensionPercentage::Calc(c) => *c,\n      b => Calc::Value(Box::new(b)),\n    }\n  }\n}\n\nimpl<D> std::convert::From<Calc<DimensionPercentage<D>>> for DimensionPercentage<D> {\n  fn from(calc: Calc<DimensionPercentage<D>>) -> DimensionPercentage<D> {\n    DimensionPercentage::Calc(Box::new(calc))\n  }\n}\n\nimpl<D: std::cmp::PartialOrd<D>> std::cmp::PartialOrd<DimensionPercentage<D>> for DimensionPercentage<D> {\n  fn partial_cmp(&self, other: &DimensionPercentage<D>) -> Option<std::cmp::Ordering> {\n    match (self, other) {\n      (DimensionPercentage::Dimension(a), DimensionPercentage::Dimension(b)) => a.partial_cmp(b),\n      (DimensionPercentage::Percentage(a), DimensionPercentage::Percentage(b)) => a.partial_cmp(b),\n      _ => None,\n    }\n  }\n}\n\nimpl<D: TryOp> TryOp for DimensionPercentage<D> {\n  fn try_op<F: FnOnce(f32, f32) -> f32>(&self, rhs: &Self, op: F) -> Option<Self> {\n    match (self, rhs) {\n      (DimensionPercentage::Dimension(a), DimensionPercentage::Dimension(b)) => {\n        a.try_op(b, op).map(DimensionPercentage::Dimension)\n      }\n      (DimensionPercentage::Percentage(a), DimensionPercentage::Percentage(b)) => {\n        Some(DimensionPercentage::Percentage(Percentage(op(a.0, b.0))))\n      }\n      _ => None,\n    }\n  }\n\n  fn try_op_to<T, F: FnOnce(f32, f32) -> T>(&self, rhs: &Self, op: F) -> Option<T> {\n    match (self, rhs) {\n      (DimensionPercentage::Dimension(a), DimensionPercentage::Dimension(b)) => a.try_op_to(b, op),\n      (DimensionPercentage::Percentage(a), DimensionPercentage::Percentage(b)) => Some(op(a.0, b.0)),\n      _ => None,\n    }\n  }\n}\n\nimpl<D: TryMap> TryMap for DimensionPercentage<D> {\n  fn try_map<F: FnOnce(f32) -> f32>(&self, op: F) -> Option<Self> {\n    match self {\n      DimensionPercentage::Dimension(v) => v.try_map(op).map(DimensionPercentage::Dimension),\n      _ => None,\n    }\n  }\n}\n\nimpl<E, D: TryFrom<Angle, Error = E>> TryFrom<Angle> for DimensionPercentage<D> {\n  type Error = E;\n\n  fn try_from(value: Angle) -> Result<Self, Self::Error> {\n    Ok(DimensionPercentage::Dimension(D::try_from(value)?))\n  }\n}\n\nimpl<E, D: TryInto<Angle, Error = E>> TryInto<Angle> for DimensionPercentage<D> {\n  type Error = ();\n\n  fn try_into(self) -> Result<Angle, Self::Error> {\n    match self {\n      DimensionPercentage::Dimension(d) => d.try_into().map_err(|_| ()),\n      _ => Err(()),\n    }\n  }\n}\n\nimpl<D: Zero> Zero for DimensionPercentage<D> {\n  fn zero() -> Self {\n    DimensionPercentage::Dimension(D::zero())\n  }\n\n  fn is_zero(&self) -> bool {\n    match self {\n      DimensionPercentage::Dimension(d) => d.is_zero(),\n      DimensionPercentage::Percentage(p) => p.is_zero(),\n      _ => false,\n    }\n  }\n}\n\nimpl<D: TrySign> TrySign for DimensionPercentage<D> {\n  fn try_sign(&self) -> Option<f32> {\n    match self {\n      DimensionPercentage::Dimension(d) => d.try_sign(),\n      DimensionPercentage::Percentage(p) => p.try_sign(),\n      DimensionPercentage::Calc(c) => c.try_sign(),\n    }\n  }\n}\n\nimpl<D: ToCss + std::ops::Mul<CSSNumber, Output = D> + TrySign + Clone + std::fmt::Debug> ToCss\n  for DimensionPercentage<D>\n{\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      DimensionPercentage::Dimension(length) => length.to_css(dest),\n      DimensionPercentage::Percentage(percent) => percent.to_css(dest),\n      DimensionPercentage::Calc(calc) => calc.to_css(dest),\n    }\n  }\n}\n"
  },
  {
    "path": "src/values/position.rs",
    "content": "//! CSS position values.\n\nuse super::length::LengthPercentage;\nuse super::percentage::Percentage;\nuse crate::error::{ParserError, PrinterError};\nuse crate::macros::enum_property;\nuse crate::printer::Printer;\nuse crate::targets::Browsers;\nuse crate::traits::{IsCompatible, Parse, ToCss, Zero};\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse cssparser::*;\n\n#[cfg(feature = \"serde\")]\nuse crate::serialization::ValueWrapper;\n\n/// A CSS [`<position>`](https://www.w3.org/TR/css3-values/#position) value,\n/// as used in the `background-position` property, gradients, masks, etc.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub struct Position {\n  /// The x-position.\n  pub x: HorizontalPosition,\n  /// The y-position.\n  pub y: VerticalPosition,\n}\n\nimpl Position {\n  /// Returns a `Position` with both the x and y set to `center`.\n  pub fn center() -> Position {\n    Position {\n      x: HorizontalPosition::Center,\n      y: VerticalPosition::Center,\n    }\n  }\n\n  /// Returns whether both the x and y positions are centered.\n  pub fn is_center(&self) -> bool {\n    self.x.is_center() && self.y.is_center()\n  }\n\n  /// Returns whether both the x and y positions are zero.\n  pub fn is_zero(&self) -> bool {\n    self.x.is_zero() && self.y.is_zero()\n  }\n}\n\nimpl Default for Position {\n  fn default() -> Position {\n    Position {\n      x: HorizontalPosition::Length(LengthPercentage::Percentage(Percentage(0.0))),\n      y: VerticalPosition::Length(LengthPercentage::Percentage(Percentage(0.0))),\n    }\n  }\n}\n\nimpl<'i> Parse<'i> for Position {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    match input.try_parse(HorizontalPosition::parse) {\n      Ok(HorizontalPosition::Center) => {\n        // Try parsing a vertical position next.\n        if let Ok(y) = input.try_parse(VerticalPosition::parse) {\n          return Ok(Position {\n            x: HorizontalPosition::Center,\n            y,\n          });\n        }\n\n        // If it didn't work, assume the first actually represents a y position,\n        // and the next is an x position. e.g. `center left` rather than `left center`.\n        let x = input.try_parse(HorizontalPosition::parse).unwrap_or(HorizontalPosition::Center);\n        let y = VerticalPosition::Center;\n        return Ok(Position { x, y });\n      }\n      Ok(x @ HorizontalPosition::Length(_)) => {\n        // If we got a length as the first component, then the second must\n        // be a keyword or length (not a side offset).\n        if let Ok(y_keyword) = input.try_parse(VerticalPositionKeyword::parse) {\n          let y = VerticalPosition::Side {\n            side: y_keyword,\n            offset: None,\n          };\n          return Ok(Position { x, y });\n        }\n        if let Ok(y_lp) = input.try_parse(LengthPercentage::parse) {\n          let y = VerticalPosition::Length(y_lp);\n          return Ok(Position { x, y });\n        }\n        let y = VerticalPosition::Center;\n        let _ = input.try_parse(|i| i.expect_ident_matching(\"center\"));\n        return Ok(Position { x, y });\n      }\n      Ok(HorizontalPosition::Side {\n        side: x_keyword,\n        offset: lp,\n      }) => {\n        // If we got a horizontal side keyword (and optional offset), expect another for the vertical side.\n        // e.g. `left center` or `left 20px center`\n        if input.try_parse(|i| i.expect_ident_matching(\"center\")).is_ok() {\n          let x = HorizontalPosition::Side {\n            side: x_keyword,\n            offset: lp,\n          };\n          let y = VerticalPosition::Center;\n          return Ok(Position { x, y });\n        }\n\n        // e.g. `left top`, `left top 20px`, `left 20px top`, or `left 20px top 20px`\n        if let Ok(y_keyword) = input.try_parse(VerticalPositionKeyword::parse) {\n          let y_lp = input.try_parse(LengthPercentage::parse).ok();\n          let x = HorizontalPosition::Side {\n            side: x_keyword,\n            offset: lp,\n          };\n          let y = VerticalPosition::Side {\n            side: y_keyword,\n            offset: y_lp,\n          };\n          return Ok(Position { x, y });\n        }\n\n        // If we didn't get a vertical side keyword (e.g. `left 20px`), then apply the offset to the vertical side.\n        let x = HorizontalPosition::Side {\n          side: x_keyword,\n          offset: None,\n        };\n        let y = lp.map_or(VerticalPosition::Center, VerticalPosition::Length);\n        return Ok(Position { x, y });\n      }\n      _ => {}\n    }\n\n    // If the horizontal position didn't parse, then it must be out of order. Try vertical position keyword.\n    let y_keyword = VerticalPositionKeyword::parse(input)?;\n    let lp_and_x_pos: Result<_, ParseError<()>> = input.try_parse(|i| {\n      let y_lp = i.try_parse(LengthPercentage::parse).ok();\n      if let Ok(x_keyword) = i.try_parse(HorizontalPositionKeyword::parse) {\n        let x_lp = i.try_parse(LengthPercentage::parse).ok();\n        let x_pos = HorizontalPosition::Side {\n          side: x_keyword,\n          offset: x_lp,\n        };\n        return Ok((y_lp, x_pos));\n      }\n      i.expect_ident_matching(\"center\")?;\n      let x_pos = HorizontalPosition::Center;\n      Ok((y_lp, x_pos))\n    });\n\n    if let Ok((y_lp, x)) = lp_and_x_pos {\n      let y = VerticalPosition::Side {\n        side: y_keyword,\n        offset: y_lp,\n      };\n      return Ok(Position { x, y });\n    }\n\n    let x = HorizontalPosition::Center;\n    let y = VerticalPosition::Side {\n      side: y_keyword,\n      offset: None,\n    };\n    Ok(Position { x, y })\n  }\n}\n\nimpl ToCss for Position {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match (&self.x, &self.y) {\n      (x_pos @ &HorizontalPosition::Side { side, offset: Some(_) }, &VerticalPosition::Length(ref y_lp))\n        if side != HorizontalPositionKeyword::Left =>\n      {\n        x_pos.to_css(dest)?;\n        dest.write_str(\" top \")?;\n        y_lp.to_css(dest)\n      }\n      (x_pos @ &HorizontalPosition::Side { side, offset: Some(_) }, y)\n        if side != HorizontalPositionKeyword::Left && y.is_center() =>\n      {\n        // If there is a side keyword with an offset, \"center\" must be a keyword not a percentage.\n        x_pos.to_css(dest)?;\n        dest.write_str(\" center\")\n      }\n      (&HorizontalPosition::Length(ref x_lp), y_pos @ &VerticalPosition::Side { side, offset: Some(_) })\n        if side != VerticalPositionKeyword::Top =>\n      {\n        dest.write_str(\"left \")?;\n        x_lp.to_css(dest)?;\n        dest.write_str(\" \")?;\n        y_pos.to_css(dest)\n      }\n      (x, y) if x.is_center() && y.is_center() => {\n        // `center center` => 50%\n        x.to_css(dest)\n      }\n      (&HorizontalPosition::Length(ref x_lp), y) if y.is_center() => {\n        // `center` is assumed if omitted.\n        x_lp.to_css(dest)\n      }\n      (\n        &HorizontalPosition::Side {\n          side: HorizontalPositionKeyword::Left,\n          offset: Some(ref x_lp),\n        },\n        y,\n      ) if y.is_center() => {\n        // `left 10px center` => `10px` (omit Y when center)\n        x_lp.to_css(dest)\n      }\n      (&HorizontalPosition::Side { side, offset: None }, y) if y.is_center() => {\n        let p: LengthPercentage = side.into();\n        p.to_css(dest)\n      }\n      (x, y_pos @ &VerticalPosition::Side { offset: None, .. }) if x.is_center() => y_pos.to_css(dest),\n      (\n        &HorizontalPosition::Center,\n        y_pos @ &VerticalPosition::Side {\n          side: VerticalPositionKeyword::Bottom,\n          offset: Some(_),\n        },\n      ) => {\n        // `center bottom 10px` must keep the keyword form\n        dest.write_str(\"center \")?;\n        y_pos.to_css(dest)\n      }\n      (&HorizontalPosition::Side { side: x, offset: None }, &VerticalPosition::Side { side: y, offset: None }) => {\n        let x: LengthPercentage = x.into();\n        let y: LengthPercentage = y.into();\n        x.to_css(dest)?;\n        dest.write_str(\" \")?;\n        y.to_css(dest)\n      }\n      (x_pos, y_pos) => {\n        let zero = LengthPercentage::zero();\n        let fifty = LengthPercentage::Percentage(Percentage(0.5));\n        let x_len = match &x_pos {\n          HorizontalPosition::Side {\n            side: HorizontalPositionKeyword::Left,\n            offset,\n          } => {\n            if let Some(len) = offset {\n              if len.is_zero() {\n                Some(&zero)\n              } else {\n                Some(len)\n              }\n            } else {\n              Some(&zero)\n            }\n          }\n          HorizontalPosition::Length(len) if len.is_zero() => Some(&zero),\n          HorizontalPosition::Length(len) => Some(len),\n          HorizontalPosition::Center => Some(&fifty),\n          _ => None,\n        };\n\n        let y_len = match &y_pos {\n          VerticalPosition::Side {\n            side: VerticalPositionKeyword::Top,\n            offset,\n          } => {\n            if let Some(len) = offset {\n              if len.is_zero() {\n                Some(&zero)\n              } else {\n                Some(len)\n              }\n            } else {\n              Some(&zero)\n            }\n          }\n          VerticalPosition::Length(len) if len.is_zero() => Some(&zero),\n          VerticalPosition::Length(len) => Some(len),\n          VerticalPosition::Center => Some(&fifty),\n          _ => None,\n        };\n\n        if let (Some(x), Some(y)) = (x_len, y_len) {\n          x.to_css(dest)?;\n          dest.write_str(\" \")?;\n          y.to_css(dest)\n        } else {\n          x_pos.to_css(dest)?;\n          dest.write_str(\" \")?;\n          y_pos.to_css(dest)\n        }\n      }\n    }\n  }\n}\n\nimpl IsCompatible for Position {\n  fn is_compatible(&self, _browsers: Browsers) -> bool {\n    true\n  }\n}\n\n/// A component within a [Position](Position) value, representing a position\n/// along either the horizontal or vertical axis of a box.\n///\n/// This type is generic over side keywords.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum PositionComponent<S> {\n  /// The `center` keyword.\n  Center,\n  /// A length or percentage from the top-left corner of the box.\n  #[cfg_attr(feature = \"serde\", serde(with = \"ValueWrapper::<LengthPercentage>\"))]\n  Length(LengthPercentage),\n  /// A side keyword with an optional offset.\n  Side {\n    /// A side keyword.\n    side: S,\n    /// Offset from the side.\n    offset: Option<LengthPercentage>,\n  },\n}\n\nimpl<S> PositionComponent<S> {\n  fn is_center(&self) -> bool {\n    match self {\n      PositionComponent::Center => true,\n      PositionComponent::Length(LengthPercentage::Percentage(Percentage(p))) => *p == 0.5,\n      _ => false,\n    }\n  }\n\n  fn is_zero(&self) -> bool {\n    matches!(self, PositionComponent::Length(len) if len.is_zero())\n  }\n}\n\nimpl<'i, S: Parse<'i>> Parse<'i> for PositionComponent<S> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    if input.try_parse(|i| i.expect_ident_matching(\"center\")).is_ok() {\n      return Ok(PositionComponent::Center);\n    }\n\n    if let Ok(lp) = input.try_parse(|input| LengthPercentage::parse(input)) {\n      return Ok(PositionComponent::Length(lp));\n    }\n\n    let side = S::parse(input)?;\n    let offset = input.try_parse(|input| LengthPercentage::parse(input)).ok();\n    Ok(PositionComponent::Side { side, offset })\n  }\n}\n\nimpl<S: ToCss> ToCss for PositionComponent<S> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    use PositionComponent::*;\n    match &self {\n      Center => {\n        if dest.minify {\n          dest.write_str(\"50%\")\n        } else {\n          dest.write_str(\"center\")\n        }\n      }\n      Length(lp) => lp.to_css(dest),\n      Side { side, offset } => {\n        side.to_css(dest)?;\n        if let Some(lp) = offset {\n          dest.write_str(\" \")?;\n          lp.to_css(dest)?;\n        }\n        Ok(())\n      }\n    }\n  }\n}\n\nenum_property! {\n  /// A horizontal position keyword.\n  pub enum HorizontalPositionKeyword {\n    /// The `left` keyword.\n    Left,\n    /// The `right` keyword.\n    Right,\n  }\n}\n\nimpl Into<LengthPercentage> for HorizontalPositionKeyword {\n  fn into(self) -> LengthPercentage {\n    match self {\n      HorizontalPositionKeyword::Left => LengthPercentage::zero(),\n      HorizontalPositionKeyword::Right => LengthPercentage::Percentage(Percentage(1.0)),\n    }\n  }\n}\n\nenum_property! {\n  /// A vertical position keyword.\n  pub enum VerticalPositionKeyword {\n    /// The `top` keyword.\n    Top,\n    /// The `bottom` keyword.\n    Bottom,\n  }\n}\n\nimpl Into<LengthPercentage> for VerticalPositionKeyword {\n  fn into(self) -> LengthPercentage {\n    match self {\n      VerticalPositionKeyword::Top => LengthPercentage::zero(),\n      VerticalPositionKeyword::Bottom => LengthPercentage::Percentage(Percentage(1.0)),\n    }\n  }\n}\n\n/// A horizontal position component.\npub type HorizontalPosition = PositionComponent<HorizontalPositionKeyword>;\n\n/// A vertical position component.\npub type VerticalPosition = PositionComponent<VerticalPositionKeyword>;\n"
  },
  {
    "path": "src/values/ratio.rs",
    "content": "//! CSS ratio values.\n\nuse super::number::CSSNumber;\nuse crate::error::{ParserError, PrinterError};\nuse crate::printer::Printer;\nuse crate::traits::{Parse, ToCss};\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse cssparser::*;\n\n/// A CSS [`<ratio>`](https://www.w3.org/TR/css-values-4/#ratios) value,\n/// representing the ratio of two numeric values.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"visitor\", visit(visit_ratio, RATIOS))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub struct Ratio(pub CSSNumber, pub CSSNumber);\n\nimpl<'i> Parse<'i> for Ratio {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let first = CSSNumber::parse(input)?;\n    let second = if input.try_parse(|input| input.expect_delim('/')).is_ok() {\n      CSSNumber::parse(input)?\n    } else {\n      1.0\n    };\n\n    Ok(Ratio(first, second))\n  }\n}\n\nimpl Ratio {\n  /// Parses a ratio where both operands are required.\n  pub fn parse_required<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let first = CSSNumber::parse(input)?;\n    input.expect_delim('/')?;\n    let second = CSSNumber::parse(input)?;\n    Ok(Ratio(first, second))\n  }\n}\n\nimpl ToCss for Ratio {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    self.0.to_css(dest)?;\n    if self.1 != 1.0 {\n      dest.delim('/', true)?;\n      self.1.to_css(dest)?;\n    }\n    Ok(())\n  }\n}\n\nimpl std::ops::Add<CSSNumber> for Ratio {\n  type Output = Self;\n\n  fn add(self, other: CSSNumber) -> Ratio {\n    Ratio(self.0 + other, self.1)\n  }\n}\n"
  },
  {
    "path": "src/values/rect.rs",
    "content": "//! Generic values for four sided properties.\n\nuse crate::error::{ParserError, PrinterError};\nuse crate::printer::Printer;\nuse crate::traits::{IsCompatible, Parse, ToCss};\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse cssparser::*;\n\n/// A generic value that represents a value for four sides of a box,\n/// e.g. border-width, margin, padding, etc.\n///\n/// When serialized, as few components as possible are written when\n/// there are duplicate values.\n#[derive(Clone, Debug, PartialEq, Eq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub struct Rect<T>(\n  /// The top component.\n  pub T,\n  /// The right component.\n  pub T,\n  /// The bottom component.\n  pub T,\n  /// The left component.\n  pub T,\n);\n\nimpl<T> Rect<T> {\n  /// Returns a new `Rect<T>` value.\n  pub fn new(first: T, second: T, third: T, fourth: T) -> Self {\n    Rect(first, second, third, fourth)\n  }\n}\n\nimpl<T: Default + Clone> Default for Rect<T> {\n  fn default() -> Rect<T> {\n    Rect::all(T::default())\n  }\n}\n\nimpl<T> Rect<T>\nwhere\n  T: Clone,\n{\n  /// Returns a rect with all the values equal to `v`.\n  pub fn all(v: T) -> Self {\n    Rect::new(v.clone(), v.clone(), v.clone(), v)\n  }\n\n  /// Parses a new `Rect<T>` value with the given parse function.\n  pub fn parse_with<'i, 't, Parse>(\n    input: &mut Parser<'i, 't>,\n    parse: Parse,\n  ) -> Result<Self, ParseError<'i, ParserError<'i>>>\n  where\n    Parse: Fn(&mut Parser<'i, 't>) -> Result<T, ParseError<'i, ParserError<'i>>>,\n  {\n    let first = parse(input)?;\n    let second = if let Ok(second) = input.try_parse(|i| parse(i)) {\n      second\n    } else {\n      // <first>\n      return Ok(Self::new(first.clone(), first.clone(), first.clone(), first));\n    };\n    let third = if let Ok(third) = input.try_parse(|i| parse(i)) {\n      third\n    } else {\n      // <first> <second>\n      return Ok(Self::new(first.clone(), second.clone(), first, second));\n    };\n    let fourth = if let Ok(fourth) = input.try_parse(|i| parse(i)) {\n      fourth\n    } else {\n      // <first> <second> <third>\n      return Ok(Self::new(first, second.clone(), third, second));\n    };\n    // <first> <second> <third> <fourth>\n    Ok(Self::new(first, second, third, fourth))\n  }\n}\n\nimpl<'i, T> Parse<'i> for Rect<T>\nwhere\n  T: Clone + PartialEq + Parse<'i>,\n{\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    Self::parse_with(input, T::parse)\n  }\n}\n\nimpl<T> ToCss for Rect<T>\nwhere\n  T: PartialEq + ToCss,\n{\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    self.0.to_css(dest)?;\n    let same_vertical = self.0 == self.2;\n    let same_horizontal = self.1 == self.3;\n    if same_vertical && same_horizontal && self.0 == self.1 {\n      return Ok(());\n    }\n    dest.write_str(\" \")?;\n    self.1.to_css(dest)?;\n    if same_vertical && same_horizontal {\n      return Ok(());\n    }\n    dest.write_str(\" \")?;\n    self.2.to_css(dest)?;\n    if same_horizontal {\n      return Ok(());\n    }\n    dest.write_str(\" \")?;\n    self.3.to_css(dest)\n  }\n}\n\nimpl<T: IsCompatible> IsCompatible for Rect<T> {\n  fn is_compatible(&self, browsers: crate::targets::Browsers) -> bool {\n    self.0.is_compatible(browsers)\n      && self.1.is_compatible(browsers)\n      && self.2.is_compatible(browsers)\n      && self.3.is_compatible(browsers)\n  }\n}\n"
  },
  {
    "path": "src/values/resolution.rs",
    "content": "//! CSS resolution values.\n\nuse super::length::serialize_dimension;\nuse super::number::CSSNumber;\nuse crate::compat::Feature;\nuse crate::error::{ParserError, PrinterError};\nuse crate::printer::Printer;\nuse crate::traits::{Parse, ToCss};\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse cssparser::*;\n\n/// A CSS [`<resolution>`](https://www.w3.org/TR/css-values-4/#resolution) value.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"visitor\", visit(visit_resolution, RESOLUTIONS))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum Resolution {\n  /// A resolution in dots per inch.\n  Dpi(CSSNumber),\n  /// A resolution in dots per centimeter.\n  Dpcm(CSSNumber),\n  /// A resolution in dots per px.\n  Dppx(CSSNumber),\n}\n\nimpl<'i> Parse<'i> for Resolution {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    // TODO: calc?\n    let location = input.current_source_location();\n    match *input.next()? {\n      Token::Dimension { value, ref unit, .. } => {\n        match_ignore_ascii_case! { unit,\n          \"dpi\" => Ok(Resolution::Dpi(value)),\n          \"dpcm\" => Ok(Resolution::Dpcm(value)),\n          \"dppx\" | \"x\" => Ok(Resolution::Dppx(value)),\n          _ => Err(location.new_unexpected_token_error(Token::Ident(unit.clone())))\n        }\n      }\n      ref t => Err(location.new_unexpected_token_error(t.clone())),\n    }\n  }\n}\n\nimpl<'i> TryFrom<&Token<'i>> for Resolution {\n  type Error = ();\n\n  fn try_from(token: &Token) -> Result<Self, Self::Error> {\n    match token {\n      Token::Dimension { value, ref unit, .. } => match_ignore_ascii_case! { unit,\n        \"dpi\" => Ok(Resolution::Dpi(*value)),\n        \"dpcm\" => Ok(Resolution::Dpcm(*value)),\n        \"dppx\" | \"x\" => Ok(Resolution::Dppx(*value)),\n        _ => Err(()),\n      },\n      _ => Err(()),\n    }\n  }\n}\n\nimpl ToCss for Resolution {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    let (value, unit) = match self {\n      Resolution::Dpi(dpi) => (*dpi, \"dpi\"),\n      Resolution::Dpcm(dpcm) => (*dpcm, \"dpcm\"),\n      Resolution::Dppx(dppx) => {\n        if dest.targets.current.is_compatible(Feature::XResolutionUnit) {\n          (*dppx, \"x\")\n        } else {\n          (*dppx, \"dppx\")\n        }\n      }\n    };\n\n    serialize_dimension(value, unit, dest)\n  }\n}\n\nimpl std::ops::Add<CSSNumber> for Resolution {\n  type Output = Self;\n\n  fn add(self, other: CSSNumber) -> Resolution {\n    match self {\n      Resolution::Dpi(dpi) => Resolution::Dpi(dpi + other),\n      Resolution::Dpcm(dpcm) => Resolution::Dpcm(dpcm + other),\n      Resolution::Dppx(dppx) => Resolution::Dppx(dppx + other),\n    }\n  }\n}\n"
  },
  {
    "path": "src/values/shape.rs",
    "content": "//! CSS shape values for masking and clipping.\n\nuse super::length::LengthPercentage;\nuse super::position::Position;\nuse super::rect::Rect;\nuse crate::error::{ParserError, PrinterError};\nuse crate::macros::enum_property;\nuse crate::printer::Printer;\nuse crate::properties::border_radius::BorderRadius;\nuse crate::traits::{Parse, ToCss};\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse cssparser::*;\n\n/// A CSS [`<basic-shape>`](https://www.w3.org/TR/css-shapes-1/#basic-shape-functions) value.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum BasicShape {\n  /// An inset rectangle.\n  Inset(InsetRect),\n  /// A circle.\n  Circle(Circle),\n  /// An ellipse.\n  Ellipse(Ellipse),\n  /// A polygon.\n  Polygon(Polygon),\n}\n\n/// An [`inset()`](https://www.w3.org/TR/css-shapes-1/#funcdef-inset) rectangle shape.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct InsetRect {\n  /// The rectangle.\n  pub rect: Rect<LengthPercentage>,\n  /// A corner radius for the rectangle.\n  pub radius: BorderRadius,\n}\n\n/// A [`circle()`](https://www.w3.org/TR/css-shapes-1/#funcdef-circle) shape.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct Circle {\n  /// The radius of the circle.\n  pub radius: ShapeRadius,\n  /// The position of the center of the circle.\n  pub position: Position,\n}\n\n/// A [`<shape-radius>`](https://www.w3.org/TR/css-shapes-1/#typedef-shape-radius) value\n/// that defines the radius of a `circle()` or `ellipse()` shape.\n#[derive(Debug, Clone, PartialEq, Parse, ToCss)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub enum ShapeRadius {\n  /// An explicit length or percentage.\n  LengthPercentage(LengthPercentage),\n  /// The length from the center to the closest side of the box.\n  ClosestSide,\n  /// The length from the center to the farthest side of the box.\n  FarthestSide,\n}\n\n/// An [`ellipse()`](https://www.w3.org/TR/css-shapes-1/#funcdef-ellipse) shape.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"camelCase\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct Ellipse {\n  /// The x-radius of the ellipse.\n  pub radius_x: ShapeRadius,\n  /// The y-radius of the ellipse.\n  pub radius_y: ShapeRadius,\n  /// The position of the center of the ellipse.\n  pub position: Position,\n}\n\n/// A [`polygon()`](https://www.w3.org/TR/css-shapes-1/#funcdef-polygon) shape.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(rename_all = \"camelCase\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct Polygon {\n  /// The fill rule used to determine the interior of the polygon.\n  pub fill_rule: FillRule,\n  /// The points of each vertex of the polygon.\n  pub points: Vec<Point>,\n}\n\n/// A point within a `polygon()` shape.\n///\n/// See [Polygon](Polygon).\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct Point {\n  /// The x position of the point.\n  x: LengthPercentage,\n  /// the y position of the point.\n  y: LengthPercentage,\n}\n\nenum_property! {\n  /// A [`<fill-rule>`](https://www.w3.org/TR/css-shapes-1/#typedef-fill-rule) used to\n  /// determine the interior of a `polygon()` shape.\n  ///\n  /// See [Polygon](Polygon).\n  pub enum FillRule {\n    /// The `nonzero` fill rule.\n    Nonzero,\n    /// The `evenodd` fill rule.\n    Evenodd,\n  }\n}\n\nimpl Default for FillRule {\n  fn default() -> FillRule {\n    FillRule::Nonzero\n  }\n}\n\nimpl<'i> Parse<'i> for BasicShape {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let location = input.current_source_location();\n    let f = input.expect_function()?;\n    match_ignore_ascii_case! { &f,\n      \"inset\" => Ok(BasicShape::Inset(input.parse_nested_block(InsetRect::parse)?)),\n      \"circle\" => Ok(BasicShape::Circle(input.parse_nested_block(Circle::parse)?)),\n      \"ellipse\" => Ok(BasicShape::Ellipse(input.parse_nested_block(Ellipse::parse)?)),\n      \"polygon\" => Ok(BasicShape::Polygon(input.parse_nested_block(Polygon::parse)?)),\n      _ => Err(location.new_unexpected_token_error(Token::Ident(f.clone()))),\n    }\n  }\n}\n\nimpl<'i> Parse<'i> for InsetRect {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let rect = Rect::parse(input)?;\n    let radius = if input.try_parse(|input| input.expect_ident_matching(\"round\")).is_ok() {\n      BorderRadius::parse(input)?\n    } else {\n      BorderRadius::default()\n    };\n    Ok(InsetRect { rect, radius })\n  }\n}\n\nimpl<'i> Parse<'i> for Circle {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let radius = input.try_parse(ShapeRadius::parse).unwrap_or_default();\n    let position = if input.try_parse(|input| input.expect_ident_matching(\"at\")).is_ok() {\n      Position::parse(input)?\n    } else {\n      Position::center()\n    };\n\n    Ok(Circle { radius, position })\n  }\n}\n\nimpl Default for ShapeRadius {\n  fn default() -> ShapeRadius {\n    ShapeRadius::ClosestSide\n  }\n}\n\nimpl<'i> Parse<'i> for Ellipse {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let (x, y) = input\n      .try_parse(|input| -> Result<_, ParseError<'i, ParserError<'i>>> {\n        Ok((ShapeRadius::parse(input)?, ShapeRadius::parse(input)?))\n      })\n      .unwrap_or_default();\n\n    let position = if input.try_parse(|input| input.expect_ident_matching(\"at\")).is_ok() {\n      Position::parse(input)?\n    } else {\n      Position::center()\n    };\n\n    Ok(Ellipse {\n      radius_x: x,\n      radius_y: y,\n      position,\n    })\n  }\n}\n\nimpl<'i> Parse<'i> for Polygon {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let fill_rule = input.try_parse(FillRule::parse);\n    if fill_rule.is_ok() {\n      input.expect_comma()?;\n    }\n\n    let points = input.parse_comma_separated(Point::parse)?;\n    Ok(Polygon {\n      fill_rule: fill_rule.unwrap_or_default(),\n      points,\n    })\n  }\n}\n\nimpl<'i> Parse<'i> for Point {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let x = LengthPercentage::parse(input)?;\n    let y = LengthPercentage::parse(input)?;\n    Ok(Point { x, y })\n  }\n}\n\nimpl ToCss for BasicShape {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    match self {\n      BasicShape::Inset(rect) => {\n        dest.write_str(\"inset(\")?;\n        rect.to_css(dest)?;\n        dest.write_char(')')\n      }\n      BasicShape::Circle(circle) => {\n        dest.write_str(\"circle(\")?;\n        circle.to_css(dest)?;\n        dest.write_char(')')\n      }\n      BasicShape::Ellipse(ellipse) => {\n        dest.write_str(\"ellipse(\")?;\n        ellipse.to_css(dest)?;\n        dest.write_char(')')\n      }\n      BasicShape::Polygon(poly) => {\n        dest.write_str(\"polygon(\")?;\n        poly.to_css(dest)?;\n        dest.write_char(')')\n      }\n    }\n  }\n}\n\nimpl ToCss for InsetRect {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    self.rect.to_css(dest)?;\n    if self.radius != BorderRadius::default() {\n      dest.write_str(\" round \")?;\n      self.radius.to_css(dest)?;\n    }\n    Ok(())\n  }\n}\n\nimpl ToCss for Circle {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    let mut has_output = false;\n    if self.radius != ShapeRadius::default() {\n      self.radius.to_css(dest)?;\n      has_output = true;\n    }\n\n    if !self.position.is_center() {\n      if has_output {\n        dest.write_char(' ')?;\n      }\n      dest.write_str(\"at \")?;\n      self.position.to_css(dest)?;\n    }\n\n    Ok(())\n  }\n}\n\nimpl ToCss for Ellipse {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    let mut has_output = false;\n    if self.radius_x != ShapeRadius::default() || self.radius_y != ShapeRadius::default() {\n      self.radius_x.to_css(dest)?;\n      dest.write_char(' ')?;\n      self.radius_y.to_css(dest)?;\n      has_output = true;\n    }\n\n    if !self.position.is_center() {\n      if has_output {\n        dest.write_char(' ')?;\n      }\n      dest.write_str(\"at \")?;\n      self.position.to_css(dest)?;\n    }\n\n    Ok(())\n  }\n}\n\nimpl ToCss for Polygon {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    if self.fill_rule != FillRule::default() {\n      self.fill_rule.to_css(dest)?;\n      dest.delim(',', false)?;\n    }\n\n    let mut first = true;\n    for point in &self.points {\n      if first {\n        first = false;\n      } else {\n        dest.delim(',', false)?;\n      }\n      point.to_css(dest)?;\n    }\n\n    Ok(())\n  }\n}\n\nimpl ToCss for Point {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    self.x.to_css(dest)?;\n    dest.write_char(' ')?;\n    self.y.to_css(dest)\n  }\n}\n"
  },
  {
    "path": "src/values/size.rs",
    "content": "//! Generic values for two component properties.\n\nuse crate::error::{ParserError, PrinterError};\nuse crate::printer::Printer;\nuse crate::traits::{IsCompatible, Parse, ToCss};\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse cssparser::*;\n\n/// A generic value that represents a value with two components, e.g. a border radius.\n///\n/// When serialized, only a single component will be written if both are equal.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub struct Size2D<T>(pub T, pub T);\n\nimpl<'i, T> Parse<'i> for Size2D<T>\nwhere\n  T: Parse<'i> + Clone,\n{\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let first = T::parse(input)?;\n    let second = input.try_parse(T::parse).unwrap_or_else(|_| first.clone());\n    Ok(Size2D(first, second))\n  }\n}\n\nimpl<T> ToCss for Size2D<T>\nwhere\n  T: ToCss + PartialEq,\n{\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    self.0.to_css(dest)?;\n    if self.1 != self.0 {\n      dest.write_str(\" \")?;\n      self.1.to_css(dest)?;\n    }\n    Ok(())\n  }\n}\n\nimpl<T: IsCompatible> IsCompatible for Size2D<T> {\n  fn is_compatible(&self, browsers: crate::targets::Browsers) -> bool {\n    self.0.is_compatible(browsers) && self.1.is_compatible(browsers)\n  }\n}\n"
  },
  {
    "path": "src/values/string.rs",
    "content": "//! Types used to represent strings.\n\nuse crate::traits::{Parse, ToCss};\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::{Visit, VisitTypes, Visitor};\nuse cssparser::{serialize_string, CowRcStr};\n#[cfg(feature = \"serde\")]\nuse serde::{Deserialize, Deserializer};\n#[cfg(any(feature = \"serde\", feature = \"nodejs\"))]\nuse serde::{Serialize, Serializer};\nuse std::borrow::{Borrow, Cow};\nuse std::cmp;\nuse std::fmt;\nuse std::hash;\nuse std::marker::PhantomData;\nuse std::ops::Deref;\nuse std::rc::Rc;\nuse std::slice;\nuse std::str;\nuse std::sync::Arc;\n\n// We cannot store CowRcStr from cssparser directly because it is not threadsafe (due to Rc).\n// CowArcStr is exactly the same, but uses Arc instead of Rc. We could use Cow<str> instead,\n// but real-world benchmarks show a performance regression, likely due to the larger memory\n// footprint.\n//\n// In order to convert between CowRcStr and CowArcStr without cloning, we use some unsafe\n// tricks to access the internal fields. LocalCowRcStr must be exactly the same as CowRcStr\n// so we can transmutate between them.\nstruct LocalCowRcStr<'a> {\n  ptr: &'static (),\n  borrowed_len_or_max: usize,\n  phantom: PhantomData<Result<&'a str, Rc<String>>>,\n}\n\n/// A string that is either shared (heap-allocated and atomically reference-counted)\n/// or borrowed from the input CSS source code.\npub struct CowArcStr<'a> {\n  ptr: &'static (),\n  borrowed_len_or_max: usize,\n  phantom: PhantomData<Result<&'a str, Arc<String>>>,\n}\n\nimpl<'a> From<CowRcStr<'a>> for CowArcStr<'a> {\n  #[inline]\n  fn from(s: CowRcStr<'a>) -> Self {\n    (&s).into()\n  }\n}\n\nimpl<'a> From<&CowRcStr<'a>> for CowArcStr<'a> {\n  #[inline]\n  fn from(s: &CowRcStr<'a>) -> Self {\n    let local = unsafe { std::mem::transmute::<&CowRcStr<'a>, &LocalCowRcStr<'a>>(&s) };\n    if local.borrowed_len_or_max == usize::MAX {\n      // If the string is owned and not borrowed, we do need to clone.\n      // We could possibly use std::mem::take here, but that would mutate the\n      // original CowRcStr which we borrowed, so might be unexpected. Owned\n      // CowRcStr are very rare in practice though, since most strings are\n      // borrowed directly from the input.\n      let ptr = local.ptr as *const () as *mut String;\n      CowArcStr::from(unsafe { (*ptr).clone() })\n    } else {\n      let s = unsafe {\n        str::from_utf8_unchecked(slice::from_raw_parts(\n          local.ptr as *const () as *const u8,\n          local.borrowed_len_or_max,\n        ))\n      };\n\n      CowArcStr::from(s)\n    }\n  }\n}\n\n// The below implementation is copied and modified from cssparser.\n\nimpl<'a> From<&'a str> for CowArcStr<'a> {\n  #[inline]\n  fn from(s: &'a str) -> Self {\n    let len = s.len();\n    assert!(len < usize::MAX);\n    CowArcStr {\n      ptr: unsafe { &*(s.as_ptr() as *const ()) },\n      borrowed_len_or_max: len,\n      phantom: PhantomData,\n    }\n  }\n}\n\nimpl<'a> From<String> for CowArcStr<'a> {\n  #[inline]\n  fn from(s: String) -> Self {\n    CowArcStr::from_arc(Arc::new(s))\n  }\n}\n\nimpl<'a> From<Cow<'a, str>> for CowArcStr<'a> {\n  #[inline]\n  fn from(s: Cow<'a, str>) -> Self {\n    match s {\n      Cow::Borrowed(s) => s.into(),\n      Cow::Owned(s) => s.into(),\n    }\n  }\n}\n\nimpl<'a> CowArcStr<'a> {\n  #[inline]\n  fn from_arc(s: Arc<String>) -> Self {\n    let ptr = unsafe { &*(Arc::into_raw(s) as *const ()) };\n    CowArcStr {\n      ptr,\n      borrowed_len_or_max: usize::MAX,\n      phantom: PhantomData,\n    }\n  }\n\n  #[inline]\n  fn unpack(&self) -> Result<&'a str, *const String> {\n    if self.borrowed_len_or_max == usize::MAX {\n      Err(self.ptr as *const () as *const String)\n    } else {\n      unsafe {\n        Ok(str::from_utf8_unchecked(slice::from_raw_parts(\n          self.ptr as *const () as *const u8,\n          self.borrowed_len_or_max,\n        )))\n      }\n    }\n  }\n}\n\n#[cfg(feature = \"into_owned\")]\nimpl<'any> static_self::IntoOwned<'any> for CowArcStr<'_> {\n  type Owned = CowArcStr<'any>;\n\n  /// Consumes the value and returns an owned clone.\n  fn into_owned(self) -> Self::Owned {\n    if self.borrowed_len_or_max != usize::MAX {\n      CowArcStr::from(self.as_ref().to_owned())\n    } else {\n      unsafe { std::mem::transmute(self) }\n    }\n  }\n}\n\nimpl<'a> Clone for CowArcStr<'a> {\n  #[inline]\n  fn clone(&self) -> Self {\n    match self.unpack() {\n      Err(ptr) => {\n        let rc = unsafe { Arc::from_raw(ptr) };\n        let new_rc = rc.clone();\n        std::mem::forget(rc); // Don’t actually take ownership of this strong reference\n        CowArcStr::from_arc(new_rc)\n      }\n      Ok(_) => CowArcStr { ..*self },\n    }\n  }\n}\n\nimpl<'a> Drop for CowArcStr<'a> {\n  #[inline]\n  fn drop(&mut self) {\n    if let Err(ptr) = self.unpack() {\n      std::mem::drop(unsafe { Arc::from_raw(ptr) })\n    }\n  }\n}\n\nimpl<'a> Deref for CowArcStr<'a> {\n  type Target = str;\n\n  #[inline]\n  fn deref(&self) -> &str {\n    self.unpack().unwrap_or_else(|ptr| unsafe { &**ptr })\n  }\n}\n\n// Boilerplate / trivial impls below.\n\nimpl<'a> AsRef<str> for CowArcStr<'a> {\n  #[inline]\n  fn as_ref(&self) -> &str {\n    self\n  }\n}\n\nimpl<'a> Borrow<str> for CowArcStr<'a> {\n  #[inline]\n  fn borrow(&self) -> &str {\n    self\n  }\n}\n\nimpl<'a> Default for CowArcStr<'a> {\n  #[inline]\n  fn default() -> Self {\n    Self::from(\"\")\n  }\n}\n\nimpl<'a> hash::Hash for CowArcStr<'a> {\n  #[inline]\n  fn hash<H: hash::Hasher>(&self, hasher: &mut H) {\n    str::hash(self, hasher)\n  }\n}\n\nimpl<'a, T: AsRef<str>> PartialEq<T> for CowArcStr<'a> {\n  #[inline]\n  fn eq(&self, other: &T) -> bool {\n    str::eq(self, other.as_ref())\n  }\n}\n\nimpl<'a, T: AsRef<str>> PartialOrd<T> for CowArcStr<'a> {\n  #[inline]\n  fn partial_cmp(&self, other: &T) -> Option<cmp::Ordering> {\n    str::partial_cmp(self, other.as_ref())\n  }\n}\n\nimpl<'a> Eq for CowArcStr<'a> {}\n\nimpl<'a> Ord for CowArcStr<'a> {\n  #[inline]\n  fn cmp(&self, other: &Self) -> cmp::Ordering {\n    str::cmp(self, other)\n  }\n}\n\nimpl<'a> fmt::Display for CowArcStr<'a> {\n  #[inline]\n  fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {\n    str::fmt(self, formatter)\n  }\n}\n\nimpl<'a> fmt::Debug for CowArcStr<'a> {\n  #[inline]\n  fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {\n    str::fmt(self, formatter)\n  }\n}\n\n#[cfg(any(feature = \"nodejs\", feature = \"serde\"))]\nimpl<'a> Serialize for CowArcStr<'a> {\n  fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {\n    self.as_ref().serialize(serializer)\n  }\n}\n\n#[cfg(feature = \"serde\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"serde\")))]\nimpl<'a, 'de: 'a> Deserialize<'de> for CowArcStr<'a> {\n  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n  where\n    D: Deserializer<'de>,\n  {\n    deserializer.deserialize_str(CowArcStrVisitor)\n  }\n}\n\n#[cfg(feature = \"jsonschema\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"jsonschema\")))]\nimpl<'a> schemars::JsonSchema for CowArcStr<'a> {\n  fn is_referenceable() -> bool {\n    true\n  }\n\n  fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {\n    String::json_schema(gen)\n  }\n\n  fn schema_name() -> String {\n    \"String\".into()\n  }\n}\n\n#[cfg(feature = \"serde\")]\nstruct CowArcStrVisitor;\n\n#[cfg(feature = \"serde\")]\nimpl<'de> serde::de::Visitor<'de> for CowArcStrVisitor {\n  type Value = CowArcStr<'de>;\n\n  fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {\n    formatter.write_str(\"a CowArcStr\")\n  }\n\n  fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>\n  where\n    E: serde::de::Error,\n  {\n    Ok(v.into())\n  }\n\n  fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>\n  where\n    E: serde::de::Error,\n  {\n    Ok(v.to_owned().into())\n  }\n\n  fn visit_string<E>(self, v: String) -> Result<Self::Value, E>\n  where\n    E: serde::de::Error,\n  {\n    Ok(v.into())\n  }\n}\n\n#[cfg(feature = \"visitor\")]\nimpl<'i, V: ?Sized + Visitor<'i, T>, T: Visit<'i, T, V>> Visit<'i, T, V> for CowArcStr<'i> {\n  const CHILD_TYPES: VisitTypes = VisitTypes::empty();\n  fn visit_children(&mut self, _: &mut V) -> Result<(), V::Error> {\n    Ok(())\n  }\n}\n\n/// A quoted CSS string.\n#[derive(Clone, Eq, Ord, Hash, Debug)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize), serde(transparent))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct CSSString<'i>(#[cfg_attr(feature = \"serde\", serde(borrow))] pub CowArcStr<'i>);\n\nimpl<'i> Parse<'i> for CSSString<'i> {\n  fn parse<'t>(\n    input: &mut cssparser::Parser<'i, 't>,\n  ) -> Result<Self, cssparser::ParseError<'i, crate::error::ParserError<'i>>> {\n    let s = input.expect_string()?;\n    Ok(CSSString(s.into()))\n  }\n}\n\nimpl<'i> ToCss for CSSString<'i> {\n  fn to_css<W>(&self, dest: &mut crate::printer::Printer<W>) -> Result<(), crate::error::PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    serialize_string(&self.0, dest)?;\n    Ok(())\n  }\n}\n\nimpl<'i> cssparser::ToCss for CSSString<'i> {\n  fn to_css<W>(&self, dest: &mut W) -> fmt::Result\n  where\n    W: fmt::Write,\n  {\n    serialize_string(&self.0, dest)\n  }\n}\n\nmacro_rules! impl_string_type {\n  ($t: ident) => {\n    impl<'i> From<CowRcStr<'i>> for $t<'i> {\n      fn from(s: CowRcStr<'i>) -> Self {\n        $t(s.into())\n      }\n    }\n\n    impl<'a> From<&CowRcStr<'a>> for $t<'a> {\n      fn from(s: &CowRcStr<'a>) -> Self {\n        $t(s.into())\n      }\n    }\n\n    impl<'i> From<String> for $t<'i> {\n      fn from(s: String) -> Self {\n        $t(s.into())\n      }\n    }\n\n    impl<'i> From<&'i str> for $t<'i> {\n      fn from(s: &'i str) -> Self {\n        $t(s.into())\n      }\n    }\n\n    impl<'a> From<std::borrow::Cow<'a, str>> for $t<'a> {\n      #[inline]\n      fn from(s: std::borrow::Cow<'a, str>) -> Self {\n        match s {\n          std::borrow::Cow::Borrowed(s) => s.into(),\n          std::borrow::Cow::Owned(s) => s.into(),\n        }\n      }\n    }\n\n    impl<'a> Deref for $t<'a> {\n      type Target = str;\n\n      #[inline]\n      fn deref(&self) -> &str {\n        self.0.deref()\n      }\n    }\n\n    impl<'a> AsRef<str> for $t<'a> {\n      #[inline]\n      fn as_ref(&self) -> &str {\n        self\n      }\n    }\n\n    impl<'a> Borrow<str> for $t<'a> {\n      #[inline]\n      fn borrow(&self) -> &str {\n        self\n      }\n    }\n\n    impl<'a> std::fmt::Display for $t<'a> {\n      #[inline]\n      fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {\n        str::fmt(self, formatter)\n      }\n    }\n\n    impl<'a, T: AsRef<str>> PartialEq<T> for $t<'a> {\n      #[inline]\n      fn eq(&self, other: &T) -> bool {\n        str::eq(self, other.as_ref())\n      }\n    }\n\n    impl<'a, T: AsRef<str>> PartialOrd<T> for $t<'a> {\n      #[inline]\n      fn partial_cmp(&self, other: &T) -> Option<std::cmp::Ordering> {\n        str::partial_cmp(self, other.as_ref())\n      }\n    }\n  };\n}\n\nimpl_string_type!(CSSString);\n\npub(crate) use impl_string_type;\n"
  },
  {
    "path": "src/values/syntax.rs",
    "content": "//! CSS syntax strings\n\nuse super::ident::Ident;\nuse super::number::{CSSInteger, CSSNumber};\nuse crate::error::{ParserError, PrinterError};\nuse crate::printer::Printer;\nuse crate::properties::custom::TokenList;\nuse crate::stylesheet::ParserOptions;\nuse crate::traits::{Parse, ToCss};\nuse crate::values;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse cssparser::*;\n\n/// A CSS [syntax string](https://drafts.css-houdini.org/css-properties-values-api/#syntax-strings)\n/// used to define the grammar for a registered custom property.\n#[derive(Debug, PartialEq, Clone)]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum SyntaxString {\n  /// A list of syntax components.\n  Components(Vec<SyntaxComponent>),\n  /// The universal syntax definition.\n  Universal,\n}\n\n/// A [syntax component](https://drafts.css-houdini.org/css-properties-values-api/#syntax-component)\n/// within a [SyntaxString](SyntaxString).\n///\n/// A syntax component consists of a component kind an a multiplier, which indicates how the component\n/// may repeat during parsing.\n#[derive(Debug, PartialEq, Clone)]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub struct SyntaxComponent {\n  /// The kind of component.\n  pub kind: SyntaxComponentKind,\n  /// A multiplier for the component.\n  pub multiplier: Multiplier,\n}\n\n/// A [syntax component component name](https://drafts.css-houdini.org/css-properties-values-api/#supported-names).\n#[derive(Debug, PartialEq, Clone)]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum SyntaxComponentKind {\n  /// A `<length>` component.\n  Length,\n  /// A `<number>` component.\n  Number,\n  /// A `<percentage>` component.\n  Percentage,\n  /// A `<length-percentage>` component.\n  LengthPercentage,\n  /// A `<string>` component.\n  String,\n  /// A `<color>` component.\n  Color,\n  /// An `<image>` component.\n  Image,\n  /// A `<url>` component.\n  Url,\n  /// An `<integer>` component.\n  Integer,\n  /// An `<angle>` component.\n  Angle,\n  /// A `<time>` component.\n  Time,\n  /// A `<resolution>` component.\n  Resolution,\n  /// A `<transform-function>` component.\n  TransformFunction,\n  /// A `<transform-list>` component.\n  TransformList,\n  /// A `<custom-ident>` component.\n  CustomIdent,\n  /// A literal component.\n  Literal(String), // TODO: borrow??\n}\n\n/// A [multiplier](https://drafts.css-houdini.org/css-properties-values-api/#multipliers) for a\n/// [SyntaxComponent](SyntaxComponent). Indicates whether and how the component may be repeated.\n#[derive(Debug, PartialEq, Clone)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum Multiplier {\n  /// The component may not be repeated.\n  None,\n  /// The component may repeat one or more times, separated by spaces.\n  Space,\n  /// The component may repeat one or more times, separated by commas.\n  Comma,\n}\n\n/// A parsed value for a [SyntaxComponent](SyntaxComponent).\n#[derive(Debug, PartialEq, Clone)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub enum ParsedComponent<'i> {\n  /// A `<length>` value.\n  Length(values::length::Length),\n  /// A `<number>` value.\n  Number(CSSNumber),\n  /// A `<percentage>` value.\n  Percentage(values::percentage::Percentage),\n  /// A `<length-percentage>` value.\n  LengthPercentage(values::length::LengthPercentage),\n  /// A `<string>` value.\n  String(values::string::CSSString<'i>),\n  /// A `<color>` value.\n  Color(values::color::CssColor),\n  /// An `<image>` value.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  Image(values::image::Image<'i>),\n  /// A `<url>` value.\n  Url(values::url::Url<'i>),\n  /// An `<integer>` value.\n  Integer(CSSInteger),\n  /// An `<angle>` value.\n  Angle(values::angle::Angle),\n  /// A `<time>` value.\n  Time(values::time::Time),\n  /// A `<resolution>` value.\n  Resolution(values::resolution::Resolution),\n  /// A `<transform-function>` value.\n  TransformFunction(crate::properties::transform::Transform),\n  /// A `<transform-list>` value.\n  TransformList(crate::properties::transform::TransformList),\n  /// A `<custom-ident>` value.\n  CustomIdent(values::ident::CustomIdent<'i>),\n  /// A literal value.\n  Literal(Ident<'i>),\n  /// A repeated component value.\n  Repeated {\n    /// The components to repeat.\n    #[cfg_attr(feature = \"visitor\", skip_type)]\n    components: Vec<ParsedComponent<'i>>,\n    /// A multiplier describing how the components repeat.\n    multiplier: Multiplier,\n  },\n  /// A raw token stream.\n  TokenList(crate::properties::custom::TokenList<'i>),\n}\n\nimpl<'i> SyntaxString {\n  /// Parses a syntax string.\n  pub fn parse_string(input: &'i str) -> Result<SyntaxString, ()> {\n    // https://drafts.css-houdini.org/css-properties-values-api/#parsing-syntax\n    let mut input = input.trim_matches(SPACE_CHARACTERS);\n    if input.is_empty() {\n      return Err(());\n    }\n\n    if input == \"*\" {\n      return Ok(SyntaxString::Universal);\n    }\n\n    let mut components = Vec::new();\n    loop {\n      let component = SyntaxComponent::parse_string(&mut input)?;\n      components.push(component);\n\n      input = input.trim_start_matches(SPACE_CHARACTERS);\n      if input.is_empty() {\n        break;\n      }\n\n      if input.starts_with('|') {\n        input = &input[1..];\n        continue;\n      }\n\n      return Err(());\n    }\n\n    Ok(SyntaxString::Components(components))\n  }\n\n  /// Parses a value according to the syntax grammar.\n  pub fn parse_value<'t>(\n    &self,\n    input: &mut Parser<'i, 't>,\n  ) -> Result<ParsedComponent<'i>, ParseError<'i, ParserError<'i>>> {\n    match self {\n      SyntaxString::Universal => Ok(ParsedComponent::TokenList(TokenList::parse(\n        input,\n        &ParserOptions::default(),\n        0,\n      )?)),\n      SyntaxString::Components(components) => {\n        // Loop through each component, and return the first one that parses successfully.\n        for component in components {\n          let state = input.state();\n          let mut parsed = Vec::new();\n          loop {\n            let value: Result<ParsedComponent<'i>, ParseError<'i, ParserError<'i>>> = input.try_parse(|input| {\n              Ok(match &component.kind {\n                SyntaxComponentKind::Length => ParsedComponent::Length(values::length::Length::parse(input)?),\n                SyntaxComponentKind::Number => ParsedComponent::Number(CSSNumber::parse(input)?),\n                SyntaxComponentKind::Percentage => {\n                  ParsedComponent::Percentage(values::percentage::Percentage::parse(input)?)\n                }\n                SyntaxComponentKind::LengthPercentage => {\n                  ParsedComponent::LengthPercentage(values::length::LengthPercentage::parse(input)?)\n                }\n                SyntaxComponentKind::String => ParsedComponent::String(values::string::CSSString::parse(input)?),\n                SyntaxComponentKind::Color => ParsedComponent::Color(values::color::CssColor::parse(input)?),\n                SyntaxComponentKind::Image => ParsedComponent::Image(values::image::Image::parse(input)?),\n                SyntaxComponentKind::Url => ParsedComponent::Url(values::url::Url::parse(input)?),\n                SyntaxComponentKind::Integer => ParsedComponent::Integer(CSSInteger::parse(input)?),\n                SyntaxComponentKind::Angle => ParsedComponent::Angle(values::angle::Angle::parse(input)?),\n                SyntaxComponentKind::Time => ParsedComponent::Time(values::time::Time::parse(input)?),\n                SyntaxComponentKind::Resolution => {\n                  ParsedComponent::Resolution(values::resolution::Resolution::parse(input)?)\n                }\n                SyntaxComponentKind::TransformFunction => {\n                  ParsedComponent::TransformFunction(crate::properties::transform::Transform::parse(input)?)\n                }\n                SyntaxComponentKind::TransformList => {\n                  ParsedComponent::TransformList(crate::properties::transform::TransformList::parse(input)?)\n                }\n                SyntaxComponentKind::CustomIdent => {\n                  ParsedComponent::CustomIdent(values::ident::CustomIdent::parse(input)?)\n                }\n                SyntaxComponentKind::Literal(value) => {\n                  let location = input.current_source_location();\n                  let ident = input.expect_ident()?;\n                  if *ident != &value {\n                    return Err(location.new_unexpected_token_error(Token::Ident(ident.clone())));\n                  }\n                  ParsedComponent::Literal(ident.into())\n                }\n              })\n            });\n\n            if let Ok(value) = value {\n              match component.multiplier {\n                Multiplier::None => return Ok(value),\n                Multiplier::Space => {\n                  parsed.push(value);\n                  if input.is_exhausted() {\n                    return Ok(ParsedComponent::Repeated {\n                      components: parsed,\n                      multiplier: component.multiplier.clone(),\n                    });\n                  }\n                }\n                Multiplier::Comma => {\n                  parsed.push(value);\n                  match input.next() {\n                    Err(_) => {\n                      return Ok(ParsedComponent::Repeated {\n                        components: parsed,\n                        multiplier: component.multiplier.clone(),\n                      })\n                    }\n                    Ok(&Token::Comma) => continue,\n                    Ok(_) => break,\n                  }\n                }\n              }\n            } else {\n              break;\n            }\n          }\n\n          input.reset(&state);\n        }\n\n        Err(input.new_error_for_next_token())\n      }\n    }\n  }\n\n  /// Parses a value from a string according to the syntax grammar.\n  pub fn parse_value_from_string<'t>(\n    &self,\n    input: &'i str,\n  ) -> Result<ParsedComponent<'i>, ParseError<'i, ParserError<'i>>> {\n    let mut input = ParserInput::new(input);\n    let mut parser = Parser::new(&mut input);\n    self.parse_value(&mut parser)\n  }\n}\n\nimpl SyntaxComponent {\n  fn parse_string(input: &mut &str) -> Result<SyntaxComponent, ()> {\n    let kind = SyntaxComponentKind::parse_string(input)?;\n\n    // Pre-multiplied types cannot have multipliers.\n    if kind == SyntaxComponentKind::TransformList {\n      return Ok(SyntaxComponent {\n        kind,\n        multiplier: Multiplier::None,\n      });\n    }\n\n    let multiplier = if input.starts_with('+') {\n      *input = &input[1..];\n      Multiplier::Space\n    } else if input.starts_with('#') {\n      *input = &input[1..];\n      Multiplier::Comma\n    } else {\n      Multiplier::None\n    };\n\n    Ok(SyntaxComponent { kind, multiplier })\n  }\n}\n\n// https://drafts.csswg.org/css-syntax-3/#whitespace\nstatic SPACE_CHARACTERS: &'static [char] = &['\\u{0020}', '\\u{0009}'];\n\nimpl SyntaxComponentKind {\n  fn parse_string(input: &mut &str) -> Result<SyntaxComponentKind, ()> {\n    // https://drafts.css-houdini.org/css-properties-values-api/#consume-syntax-component\n    *input = input.trim_start_matches(SPACE_CHARACTERS);\n    if input.starts_with('<') {\n      // https://drafts.css-houdini.org/css-properties-values-api/#consume-data-type-name\n      let end_idx = input.find('>').ok_or(())?;\n      let name = &input[1..end_idx];\n      let component = match_ignore_ascii_case! {name,\n        \"length\" => SyntaxComponentKind::Length,\n        \"number\" => SyntaxComponentKind::Number,\n        \"percentage\" => SyntaxComponentKind::Percentage,\n        \"length-percentage\" => SyntaxComponentKind::LengthPercentage,\n        \"string\" => SyntaxComponentKind::String,\n        \"color\" => SyntaxComponentKind::Color,\n        \"image\" => SyntaxComponentKind::Image,\n        \"url\" => SyntaxComponentKind::Url,\n        \"integer\" => SyntaxComponentKind::Integer,\n        \"angle\" => SyntaxComponentKind::Angle,\n        \"time\" => SyntaxComponentKind::Time,\n        \"resolution\" => SyntaxComponentKind::Resolution,\n        \"transform-function\" => SyntaxComponentKind::TransformFunction,\n        \"transform-list\" => SyntaxComponentKind::TransformList,\n        \"custom-ident\" => SyntaxComponentKind::CustomIdent,\n        _ => return Err(())\n      };\n\n      *input = &input[end_idx + 1..];\n      Ok(component)\n    } else if input.starts_with(is_ident_start) {\n      // A literal.\n      let end_idx = input.find(|c| !is_name_code_point(c)).unwrap_or_else(|| input.len());\n      let name = input[0..end_idx].to_owned();\n      *input = &input[end_idx..];\n      Ok(SyntaxComponentKind::Literal(name))\n    } else {\n      return Err(());\n    }\n  }\n}\n\n#[inline]\nfn is_ident_start(c: char) -> bool {\n  // https://drafts.csswg.org/css-syntax-3/#ident-start-code-point\n  c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' || c >= '\\u{80}' || c == '_'\n}\n\n#[inline]\nfn is_name_code_point(c: char) -> bool {\n  // https://drafts.csswg.org/css-syntax-3/#ident-code-point\n  is_ident_start(c) || c >= '0' && c <= '9' || c == '-'\n}\n\nimpl<'i> Parse<'i> for SyntaxString {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let string = input.expect_string_cloned()?;\n    SyntaxString::parse_string(string.as_ref()).map_err(|_| input.new_custom_error(ParserError::InvalidValue))\n  }\n}\n\nimpl ToCss for SyntaxString {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    dest.write_char('\"')?;\n    match self {\n      SyntaxString::Universal => dest.write_char('*')?,\n      SyntaxString::Components(components) => {\n        let mut first = true;\n        for component in components {\n          if first {\n            first = false;\n          } else {\n            dest.delim('|', true)?;\n          }\n\n          component.to_css(dest)?;\n        }\n      }\n    }\n\n    dest.write_char('\"')\n  }\n}\n\nimpl ToCss for SyntaxComponent {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    self.kind.to_css(dest)?;\n    match self.multiplier {\n      Multiplier::None => Ok(()),\n      Multiplier::Comma => dest.write_char('#'),\n      Multiplier::Space => dest.write_char('+'),\n    }\n  }\n}\n\nimpl ToCss for SyntaxComponentKind {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    use SyntaxComponentKind::*;\n    let s = match self {\n      Length => \"<length>\",\n      Number => \"<number>\",\n      Percentage => \"<percentage>\",\n      LengthPercentage => \"<length-percentage>\",\n      String => \"<string>\",\n      Color => \"<color>\",\n      Image => \"<image>\",\n      Url => \"<url>\",\n      Integer => \"<integer>\",\n      Angle => \"<angle>\",\n      Time => \"<time>\",\n      Resolution => \"<resolution>\",\n      TransformFunction => \"<transform-function>\",\n      TransformList => \"<transform-list>\",\n      CustomIdent => \"<custom-ident>\",\n      Literal(l) => l,\n    };\n    dest.write_str(s)\n  }\n}\n\nimpl<'i> ToCss for ParsedComponent<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    use ParsedComponent::*;\n    match self {\n      Length(v) => v.to_css(dest),\n      Number(v) => v.to_css(dest),\n      Percentage(v) => v.to_css(dest),\n      LengthPercentage(v) => v.to_css(dest),\n      String(v) => v.to_css(dest),\n      Color(v) => v.to_css(dest),\n      Image(v) => v.to_css(dest),\n      Url(v) => v.to_css(dest),\n      Integer(v) => v.to_css(dest),\n      Angle(v) => v.to_css(dest),\n      Time(v) => v.to_css(dest),\n      Resolution(v) => v.to_css(dest),\n      TransformFunction(v) => v.to_css(dest),\n      TransformList(v) => v.to_css(dest),\n      CustomIdent(v) => v.to_css(dest),\n      Literal(v) => v.to_css(dest),\n      Repeated { components, multiplier } => {\n        let mut first = true;\n        for component in components {\n          if first {\n            first = false;\n          } else {\n            match multiplier {\n              Multiplier::Comma => dest.delim(',', false)?,\n              Multiplier::Space => dest.write_char(' ')?,\n              Multiplier::None => unreachable!(),\n            }\n          }\n\n          component.to_css(dest)?;\n        }\n        Ok(())\n      }\n      TokenList(t) => t.to_css(dest, false),\n    }\n  }\n}\n\n#[cfg(test)]\nmod tests {\n  use crate::values::color::RGBA;\n\n  use super::*;\n\n  fn test(source: &str, test: &str, expected: ParsedComponent) {\n    let parsed = SyntaxString::parse_string(source).unwrap();\n\n    let mut input = ParserInput::new(test);\n    let mut parser = Parser::new(&mut input);\n    let value = parsed.parse_value(&mut parser).unwrap();\n    assert_eq!(value, expected);\n  }\n\n  fn parse_error_test(source: &str) {\n    let res = SyntaxString::parse_string(source);\n    match res {\n      Ok(_) => unreachable!(),\n      Err(_) => {}\n    }\n  }\n\n  fn error_test(source: &str, test: &str) {\n    let parsed = SyntaxString::parse_string(source).unwrap();\n    let mut input = ParserInput::new(test);\n    let mut parser = Parser::new(&mut input);\n    let res = parsed.parse_value(&mut parser);\n    match res {\n      Ok(_) => unreachable!(),\n      Err(_) => {}\n    }\n  }\n\n  #[test]\n  fn test_syntax() {\n    test(\n      \"foo | <color>+ | <integer>\",\n      \"foo\",\n      ParsedComponent::Literal(\"foo\".into()),\n    );\n\n    test(\"foo|<color>+|<integer>\", \"foo\", ParsedComponent::Literal(\"foo\".into()));\n\n    test(\"foo | <color>+ | <integer>\", \"2\", ParsedComponent::Integer(2));\n\n    test(\n      \"foo | <color>+ | <integer>\",\n      \"red\",\n      ParsedComponent::Repeated {\n        components: vec![ParsedComponent::Color(values::color::CssColor::RGBA(RGBA {\n          red: 255,\n          green: 0,\n          blue: 0,\n          alpha: 255,\n        }))],\n        multiplier: Multiplier::Space,\n      },\n    );\n\n    test(\n      \"foo | <color>+ | <integer>\",\n      \"red blue\",\n      ParsedComponent::Repeated {\n        components: vec![\n          ParsedComponent::Color(values::color::CssColor::RGBA(RGBA {\n            red: 255,\n            green: 0,\n            blue: 0,\n            alpha: 255,\n          })),\n          ParsedComponent::Color(values::color::CssColor::RGBA(RGBA {\n            red: 0,\n            green: 0,\n            blue: 255,\n            alpha: 255,\n          })),\n        ],\n        multiplier: Multiplier::Space,\n      },\n    );\n\n    error_test(\"foo | <color>+ | <integer>\", \"2.5\");\n\n    error_test(\"foo | <color>+ | <integer>\", \"25px\");\n\n    error_test(\"foo | <color>+ | <integer>\", \"red, green\");\n\n    test(\n      \"foo | <color># | <integer>\",\n      \"red, blue\",\n      ParsedComponent::Repeated {\n        components: vec![\n          ParsedComponent::Color(values::color::CssColor::RGBA(RGBA {\n            red: 255,\n            green: 0,\n            blue: 0,\n            alpha: 255,\n          })),\n          ParsedComponent::Color(values::color::CssColor::RGBA(RGBA {\n            red: 0,\n            green: 0,\n            blue: 255,\n            alpha: 255,\n          })),\n        ],\n        multiplier: Multiplier::Comma,\n      },\n    );\n\n    error_test(\"foo | <color># | <integer>\", \"red green\");\n\n    test(\n      \"<length>\",\n      \"25px\",\n      ParsedComponent::Length(values::length::Length::Value(values::length::LengthValue::Px(25.0))),\n    );\n\n    test(\n      \"<length>\",\n      \"calc(25px + 25px)\",\n      ParsedComponent::Length(values::length::Length::Value(values::length::LengthValue::Px(50.0))),\n    );\n\n    test(\n      \"<length> | <percentage>\",\n      \"25px\",\n      ParsedComponent::Length(values::length::Length::Value(values::length::LengthValue::Px(25.0))),\n    );\n\n    test(\n      \"<length> | <percentage>\",\n      \"25%\",\n      ParsedComponent::Percentage(values::percentage::Percentage(0.25)),\n    );\n\n    error_test(\"<length> | <percentage>\", \"calc(100% - 25px)\");\n\n    test(\"foo | bar | baz\", \"bar\", ParsedComponent::Literal(\"bar\".into()));\n\n    test(\n      \"<string>\",\n      \"'foo'\",\n      ParsedComponent::String(values::string::CSSString(\"foo\".into())),\n    );\n\n    test(\n      \"<custom-ident>\",\n      \"hi\",\n      ParsedComponent::CustomIdent(values::ident::CustomIdent(\"hi\".into())),\n    );\n\n    parse_error_test(\"<transform-list>#\");\n    parse_error_test(\"<color\");\n    parse_error_test(\"color>\");\n  }\n}\n"
  },
  {
    "path": "src/values/time.rs",
    "content": "//! CSS time values.\n\nuse super::angle::impl_try_from_angle;\nuse super::calc::Calc;\nuse super::number::CSSNumber;\nuse crate::error::{ParserError, PrinterError};\nuse crate::printer::Printer;\nuse crate::traits::private::AddInternal;\nuse crate::traits::{impl_op, Map, Op, Parse, Sign, ToCss, Zero};\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse cssparser::*;\n\n/// A CSS [`<time>`](https://www.w3.org/TR/css-values-4/#time) value, in either\n/// seconds or milliseconds.\n///\n/// Time values may be explicit or computed by `calc()`, but are always stored and serialized\n/// as their computed value.\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"visitor\", visit(visit_time, TIMES))]\n#[cfg_attr(\n  feature = \"serde\",\n  derive(serde::Serialize, serde::Deserialize),\n  serde(tag = \"type\", content = \"value\", rename_all = \"kebab-case\")\n)]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\npub enum Time {\n  /// A time in seconds.\n  Seconds(CSSNumber),\n  /// A time in milliseconds.\n  Milliseconds(CSSNumber),\n}\n\nimpl Time {\n  /// Returns the time in milliseconds.\n  pub fn to_ms(&self) -> CSSNumber {\n    match self {\n      Time::Seconds(s) => s * 1000.0,\n      Time::Milliseconds(ms) => *ms,\n    }\n  }\n}\n\nimpl Zero for Time {\n  fn zero() -> Self {\n    Time::Milliseconds(0.0)\n  }\n\n  fn is_zero(&self) -> bool {\n    match self {\n      Time::Seconds(s) => s.is_zero(),\n      Time::Milliseconds(s) => s.is_zero(),\n    }\n  }\n}\n\nimpl<'i> Parse<'i> for Time {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    match input.try_parse(Calc::parse) {\n      Ok(Calc::Value(v)) => return Ok(*v),\n      // Time is always compatible, so they will always compute to a value.\n      Ok(_) => return Err(input.new_custom_error(ParserError::InvalidValue)),\n      _ => {}\n    }\n\n    let location = input.current_source_location();\n    match *input.next()? {\n      Token::Dimension { value, ref unit, .. } => {\n        match_ignore_ascii_case! { unit,\n          \"s\" => Ok(Time::Seconds(value)),\n          \"ms\" => Ok(Time::Milliseconds(value)),\n          _ => Err(location.new_unexpected_token_error(Token::Ident(unit.clone())))\n        }\n      }\n      ref t => Err(location.new_unexpected_token_error(t.clone())),\n    }\n  }\n}\n\nimpl<'i> TryFrom<&Token<'i>> for Time {\n  type Error = ();\n\n  fn try_from(token: &Token) -> Result<Self, Self::Error> {\n    match token {\n      Token::Dimension { value, ref unit, .. } => match_ignore_ascii_case! { unit,\n        \"s\" => Ok(Time::Seconds(*value)),\n        \"ms\" => Ok(Time::Milliseconds(*value)),\n        _ => Err(()),\n      },\n      _ => Err(()),\n    }\n  }\n}\n\nimpl ToCss for Time {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    // 0.1s is shorter than 100ms\n    // anything smaller is longer\n    match self {\n      Time::Seconds(s) => {\n        if *s > 0.0 && *s < 0.1 {\n          (*s * 1000.0).to_css(dest)?;\n          dest.write_str(\"ms\")\n        } else {\n          s.to_css(dest)?;\n          dest.write_str(\"s\")\n        }\n      }\n      Time::Milliseconds(ms) => {\n        if *ms == 0.0 || *ms >= 100.0 {\n          (*ms / 1000.0).to_css(dest)?;\n          dest.write_str(\"s\")\n        } else {\n          ms.to_css(dest)?;\n          dest.write_str(\"ms\")\n        }\n      }\n    }\n  }\n}\n\nimpl std::convert::Into<Calc<Time>> for Time {\n  fn into(self) -> Calc<Time> {\n    Calc::Value(Box::new(self))\n  }\n}\n\nimpl std::convert::TryFrom<Calc<Time>> for Time {\n  type Error = ();\n\n  fn try_from(calc: Calc<Time>) -> Result<Time, Self::Error> {\n    match calc {\n      Calc::Value(v) => Ok(*v),\n      _ => Err(()),\n    }\n  }\n}\n\nimpl std::ops::Mul<f32> for Time {\n  type Output = Self;\n\n  fn mul(self, other: f32) -> Time {\n    match self {\n      Time::Seconds(t) => Time::Seconds(t * other),\n      Time::Milliseconds(t) => Time::Milliseconds(t * other),\n    }\n  }\n}\n\nimpl AddInternal for Time {\n  fn add(self, other: Self) -> Self {\n    self + other\n  }\n}\n\nimpl std::cmp::PartialOrd<Time> for Time {\n  fn partial_cmp(&self, other: &Time) -> Option<std::cmp::Ordering> {\n    self.to_ms().partial_cmp(&other.to_ms())\n  }\n}\n\nimpl Op for Time {\n  fn op<F: FnOnce(f32, f32) -> f32>(&self, to: &Self, op: F) -> Self {\n    match (self, to) {\n      (Time::Seconds(a), Time::Seconds(b)) => Time::Seconds(op(*a, *b)),\n      (Time::Milliseconds(a), Time::Milliseconds(b)) => Time::Milliseconds(op(*a, *b)),\n      (Time::Seconds(a), Time::Milliseconds(b)) => Time::Seconds(op(*a, b / 1000.0)),\n      (Time::Milliseconds(a), Time::Seconds(b)) => Time::Milliseconds(op(*a, b * 1000.0)),\n    }\n  }\n\n  fn op_to<T, F: FnOnce(f32, f32) -> T>(&self, rhs: &Self, op: F) -> T {\n    match (self, rhs) {\n      (Time::Seconds(a), Time::Seconds(b)) => op(*a, *b),\n      (Time::Milliseconds(a), Time::Milliseconds(b)) => op(*a, *b),\n      (Time::Seconds(a), Time::Milliseconds(b)) => op(*a, b / 1000.0),\n      (Time::Milliseconds(a), Time::Seconds(b)) => op(*a, b * 1000.0),\n    }\n  }\n}\n\nimpl Map for Time {\n  fn map<F: FnOnce(f32) -> f32>(&self, op: F) -> Self {\n    match self {\n      Time::Seconds(t) => Time::Seconds(op(*t)),\n      Time::Milliseconds(t) => Time::Milliseconds(op(*t)),\n    }\n  }\n}\n\nimpl Sign for Time {\n  fn sign(&self) -> f32 {\n    match self {\n      Time::Seconds(v) | Time::Milliseconds(v) => v.sign(),\n    }\n  }\n}\n\nimpl_op!(Time, std::ops::Rem, rem);\nimpl_op!(Time, std::ops::Add, add);\n\nimpl_try_from_angle!(Time);\n"
  },
  {
    "path": "src/values/url.rs",
    "content": "//! CSS url() values.\n\nuse crate::dependencies::{Dependency, Location, UrlDependency};\nuse crate::error::{ParserError, PrinterError};\nuse crate::printer::Printer;\nuse crate::traits::{Parse, ToCss};\nuse crate::values::string::CowArcStr;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::Visit;\nuse cssparser::*;\n\n/// A CSS [url()](https://www.w3.org/TR/css-values-4/#urls) value and its source location.\n#[derive(Debug, Clone)]\n#[cfg_attr(feature = \"visitor\", derive(Visit))]\n#[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n#[cfg_attr(feature = \"visitor\", visit(visit_url, URLS))]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"jsonschema\", derive(schemars::JsonSchema))]\npub struct Url<'i> {\n  /// The url string.\n  #[cfg_attr(feature = \"serde\", serde(borrow))]\n  pub url: CowArcStr<'i>,\n  /// The location where the `url()` was seen in the CSS source file.\n  pub loc: Location,\n}\n\nimpl<'i> PartialEq for Url<'i> {\n  fn eq(&self, other: &Self) -> bool {\n    self.url == other.url\n  }\n}\n\nimpl<'i> Parse<'i> for Url<'i> {\n  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {\n    let loc = input.current_source_location();\n    let url = input.expect_url()?.into();\n    Ok(Url { url, loc: loc.into() })\n  }\n}\n\nimpl<'i> ToCss for Url<'i> {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    let dep = if dest.dependencies.is_some() {\n      Some(UrlDependency::new(self, dest.filename()))\n    } else {\n      None\n    };\n\n    // If adding dependencies, always write url() with quotes so that the placeholder can\n    // be replaced without escaping more easily. Quotes may be removed later during minification.\n    if let Some(dep) = dep {\n      dest.write_str(\"url(\")?;\n      serialize_string(&dep.placeholder, dest)?;\n      dest.write_char(')')?;\n\n      if let Some(dependencies) = &mut dest.dependencies {\n        dependencies.push(Dependency::Url(dep))\n      }\n\n      return Ok(());\n    }\n\n    use cssparser::ToCss;\n    if dest.minify {\n      let mut buf = String::new();\n      Token::UnquotedUrl(CowRcStr::from(self.url.as_ref())).to_css(&mut buf)?;\n\n      // If the unquoted url is longer than it would be quoted (e.g. `url(\"...\")`)\n      // then serialize as a string and choose the shorter version.\n      if buf.len() > self.url.len() + 7 {\n        let mut buf2 = String::new();\n        serialize_string(&self.url, &mut buf2)?;\n        if buf2.len() + 5 < buf.len() {\n          dest.write_str(\"url(\")?;\n          dest.write_str(&buf2)?;\n          return dest.write_char(')');\n        }\n      }\n\n      dest.write_str(&buf)?;\n    } else {\n      dest.write_str(\"url(\")?;\n      serialize_string(&self.url, dest)?;\n      dest.write_char(')')?;\n    }\n\n    Ok(())\n  }\n}\n\nimpl<'i> Url<'i> {\n  /// Returns whether the URL is absolute, and not relative.\n  pub fn is_absolute(&self) -> bool {\n    let url = self.url.as_ref();\n\n    // Quick checks. If the url starts with '.', it is relative.\n    if url.starts_with('.') {\n      return false;\n    }\n\n    // If the url starts with '/' it is absolute.\n    if url.starts_with('/') {\n      return true;\n    }\n\n    // If the url starts with '#' we have a fragment URL.\n    // These are resolved relative to the document rather than the CSS file.\n    // https://drafts.csswg.org/css-values-4/#local-urls\n    if url.starts_with('#') {\n      return true;\n    }\n\n    // Otherwise, we might have a scheme. These must start with an ascii alpha character.\n    // https://url.spec.whatwg.org/#scheme-start-state\n    if !url.starts_with(|c| matches!(c, 'a'..='z' | 'A'..='Z')) {\n      return false;\n    }\n\n    // https://url.spec.whatwg.org/#scheme-state\n    for b in url.as_bytes() {\n      let c = *b as char;\n      match c {\n        'a'..='z' | 'A'..='Z' | '0'..='9' | '+' | '-' | '.' => {}\n        ':' => return true,\n        _ => break,\n      }\n    }\n\n    false\n  }\n}\n"
  },
  {
    "path": "src/vendor_prefix.rs",
    "content": "//! Vendor prefixes.\n\n#![allow(non_upper_case_globals)]\n\nuse crate::error::PrinterError;\nuse crate::printer::Printer;\nuse crate::traits::ToCss;\n#[cfg(feature = \"visitor\")]\nuse crate::visitor::{Visit, VisitTypes, Visitor};\nuse bitflags::bitflags;\n\nbitflags! {\n  /// Bit flags that represent one or more vendor prefixes, such as\n  /// `-webkit` or `-moz`.\n  ///\n  /// Multiple flags can be combined to represent\n  /// more than one prefix. During printing, the rule or property will\n  /// be duplicated for each prefix flag that is enabled. This enables\n  /// vendor prefixes to be added without increasing memory usage.\n  #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]\n  #[cfg_attr(feature = \"into_owned\", derive(static_self::IntoOwned))]\n  pub struct VendorPrefix: u8 {\n    /// The `-webkit` vendor prefix.\n    const WebKit = 0b00000010;\n    /// The `-moz` vendor prefix.\n    const Moz    = 0b00000100;\n    /// The `-ms` vendor prefix.\n    const Ms     = 0b00001000;\n    /// The `-o` vendor prefix.\n    const O      = 0b00010000;\n    /// No vendor prefixes.\n    const None   = 0b00000001;\n  }\n}\n\nimpl Default for VendorPrefix {\n  fn default() -> VendorPrefix {\n    VendorPrefix::None\n  }\n}\n\nimpl VendorPrefix {\n  /// Returns a vendor prefix flag from a prefix string (without the leading `-`).\n  pub fn from_str(s: &str) -> VendorPrefix {\n    match s {\n      \"webkit\" => VendorPrefix::WebKit,\n      \"moz\" => VendorPrefix::Moz,\n      \"ms\" => VendorPrefix::Ms,\n      \"o\" => VendorPrefix::O,\n      _ => unreachable!(),\n    }\n  }\n\n  /// Returns VendorPrefix::None if empty.\n  #[inline]\n  pub fn or_none(self) -> Self {\n    self.or(VendorPrefix::None)\n  }\n\n  /// Returns `other` if `self` is empty\n  #[inline]\n  pub fn or(self, other: Self) -> Self {\n    if self.is_empty() {\n      other\n    } else {\n      self\n    }\n  }\n}\n\nimpl ToCss for VendorPrefix {\n  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>\n  where\n    W: std::fmt::Write,\n  {\n    cssparser::ToCss::to_css(self, dest)?;\n    Ok(())\n  }\n}\n\nimpl cssparser::ToCss for VendorPrefix {\n  fn to_css<W>(&self, dest: &mut W) -> std::fmt::Result\n  where\n    W: std::fmt::Write,\n  {\n    match *self {\n      VendorPrefix::WebKit => dest.write_str(\"-webkit-\"),\n      VendorPrefix::Moz => dest.write_str(\"-moz-\"),\n      VendorPrefix::Ms => dest.write_str(\"-ms-\"),\n      VendorPrefix::O => dest.write_str(\"-o-\"),\n      _ => Ok(()),\n    }\n  }\n}\n\n#[cfg(feature = \"serde\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"serde\")))]\nimpl serde::Serialize for VendorPrefix {\n  fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>\n  where\n    S: serde::Serializer,\n  {\n    let mut values = Vec::new();\n    if *self != VendorPrefix::None {\n      if self.contains(VendorPrefix::None) {\n        values.push(\"none\");\n      }\n      if self.contains(VendorPrefix::WebKit) {\n        values.push(\"webkit\");\n      }\n      if self.contains(VendorPrefix::Moz) {\n        values.push(\"moz\");\n      }\n      if self.contains(VendorPrefix::Ms) {\n        values.push(\"ms\");\n      }\n      if self.contains(VendorPrefix::O) {\n        values.push(\"o\");\n      }\n    }\n    values.serialize(serializer)\n  }\n}\n\n#[cfg(feature = \"serde\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"serde\")))]\nimpl<'de> serde::Deserialize<'de> for VendorPrefix {\n  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n  where\n    D: serde::Deserializer<'de>,\n  {\n    use crate::values::string::CowArcStr;\n    let values = Vec::<CowArcStr<'de>>::deserialize(deserializer)?;\n    if values.is_empty() {\n      return Ok(VendorPrefix::None);\n    }\n    let mut res = VendorPrefix::empty();\n    for value in values {\n      res |= match value.as_ref() {\n        \"none\" => VendorPrefix::None,\n        \"webkit\" => VendorPrefix::WebKit,\n        \"moz\" => VendorPrefix::Moz,\n        \"ms\" => VendorPrefix::Ms,\n        \"o\" => VendorPrefix::O,\n        _ => continue,\n      };\n    }\n    Ok(res)\n  }\n}\n\n#[cfg(feature = \"visitor\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"visitor\")))]\nimpl<'i, V: ?Sized + Visitor<'i, T>, T: Visit<'i, T, V>> Visit<'i, T, V> for VendorPrefix {\n  const CHILD_TYPES: VisitTypes = VisitTypes::empty();\n  fn visit_children(&mut self, _: &mut V) -> Result<(), V::Error> {\n    Ok(())\n  }\n}\n\n#[cfg(feature = \"jsonschema\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"jsonschema\")))]\nimpl schemars::JsonSchema for VendorPrefix {\n  fn is_referenceable() -> bool {\n    true\n  }\n\n  fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {\n    #[derive(schemars::JsonSchema)]\n    #[schemars(rename_all = \"lowercase\")]\n    #[allow(dead_code)]\n    enum Prefix {\n      None,\n      WebKit,\n      Moz,\n      Ms,\n      O,\n    }\n\n    Vec::<Prefix>::json_schema(gen)\n  }\n\n  fn schema_name() -> String {\n    \"VendorPrefix\".into()\n  }\n}\n"
  },
  {
    "path": "src/visitor.rs",
    "content": "//! Visitors for traversing the values in a StyleSheet.\n//!\n//! The [Visitor](Visitor) trait includes methods for visiting and transforming rules, properties, and values within a StyleSheet.\n//! Each value implements the [Visit](Visit) trait, which knows how to visit the value itself, as well as its children.\n//! A Visitor is configured to only visit specific types of values using [VisitTypes](VisitTypes) flags. This enables\n//! entire branches to be skipped when a type does not contain any relevant values.\n//!\n//! # Example\n//!\n//! This example transforms a stylesheet, adding a prefix to all URLs, and converting pixels to rems.\n//!\n//! ```\n//! use std::convert::Infallible;\n//! use lightningcss::{\n//!   stylesheet::{StyleSheet, ParserOptions, PrinterOptions},\n//!   visitor::{Visitor, Visit, VisitTypes},\n//!   visit_types,\n//!   values::length::LengthValue,\n//!   values::url::Url\n//! };\n//!\n//! let mut stylesheet = StyleSheet::parse(\n//!   r#\"\n//!     .foo {\n//!       background: url(bg.png);\n//!       width: 32px;\n//!     }\n//!   \"#,\n//!   ParserOptions::default()\n//! ).unwrap();\n//!\n//! struct MyVisitor;\n//! impl<'i> Visitor<'i> for MyVisitor {\n//!   type Error = Infallible;\n//!\n//!   fn visit_types(&self) -> VisitTypes {\n//!     visit_types!(URLS | LENGTHS)\n//!   }\n//!\n//!   fn visit_url(&mut self, url: &mut Url<'i>) -> Result<(), Self::Error> {\n//!     url.url = format!(\"https://mywebsite.com/{}\", url.url).into();\n//!     Ok(())\n//!   }\n//!\n//!   fn visit_length(&mut self, length: &mut LengthValue) -> Result<(), Self::Error> {\n//!     match length {\n//!       LengthValue::Px(px) => *length = LengthValue::Rem(*px / 16.0),\n//!       _ => {}\n//!     }\n//!\n//!     Ok(())\n//!   }\n//! }\n//!\n//! stylesheet.visit(&mut MyVisitor).unwrap();\n//!\n//! let res = stylesheet.to_css(PrinterOptions { minify: true, ..Default::default() }).unwrap();\n//! assert_eq!(res.code, \".foo{background:url(https://mywebsite.com/bg.png);width:2rem}\");\n//! ```\n\nuse crate::{\n  declaration::DeclarationBlock,\n  media_query::{MediaFeature, MediaFeatureValue, MediaList, MediaQuery},\n  parser::DefaultAtRule,\n  properties::{\n    custom::{EnvironmentVariable, Function, TokenList, TokenOrValue, Variable},\n    Property,\n  },\n  rules::{supports::SupportsCondition, CssRule, CssRuleList},\n  selector::{Selector, SelectorList},\n  stylesheet::StyleSheet,\n  values::{\n    angle::Angle,\n    color::CssColor,\n    ident::{CustomIdent, DashedIdent},\n    image::Image,\n    length::LengthValue,\n    ratio::Ratio,\n    resolution::Resolution,\n    time::Time,\n    url::Url,\n  },\n};\nuse bitflags::bitflags;\nuse indexmap::IndexMap;\nuse smallvec::SmallVec;\n\npub(crate) use lightningcss_derive::Visit;\n\nbitflags! {\n  /// Describes what a [Visitor](Visitor) will visit when traversing a StyleSheet.\n  ///\n  /// Flags may be combined to visit multiple types. The [visit_types](visit_types) macro allows\n  /// combining flags in a `const` expression.\n  #[derive(PartialEq, Eq, Clone, Copy)]\n  pub struct VisitTypes: u32 {\n    /// Visit rules.\n    const RULES = 1 << 0;\n    /// Visit properties;\n    const PROPERTIES = 1 << 1;\n    /// Visit urls.\n    const URLS = 1 << 2;\n    /// Visit colors.\n    const COLORS = 1 << 3;\n    /// Visit images.\n    const IMAGES = 1 << 4;\n    /// Visit lengths.\n    const LENGTHS = 1 << 5;\n    /// Visit angles.\n    const ANGLES = 1 << 6;\n    /// Visit ratios.\n    const RATIOS = 1 << 7;\n    /// Visit resolutions.\n    const RESOLUTIONS = 1 << 8;\n    /// Visit times.\n    const TIMES = 1 << 9;\n    /// Visit custom identifiers.\n    const CUSTOM_IDENTS = 1 << 10;\n    /// Visit dashed identifiers.\n    const DASHED_IDENTS = 1 << 11;\n    /// Visit variables.\n    const VARIABLES = 1 << 12;\n    /// Visit environment variables.\n    const ENVIRONMENT_VARIABLES = 1 << 13;\n    /// Visit media queries.\n    const MEDIA_QUERIES = 1 << 14;\n    /// Visit supports conditions.\n    const SUPPORTS_CONDITIONS = 1 << 15;\n    /// Visit selectors.\n    const SELECTORS = 1 << 16;\n    /// Visit custom functions.\n    const FUNCTIONS = 1 << 17;\n    /// Visit a token.\n    const TOKENS = 1 << 18;\n  }\n}\n\n/// Constructs a constant [VisitTypes](VisitTypes) from flags.\n#[macro_export]\nmacro_rules! visit_types {\n  ($( $flag: ident )|+) => {\n    $crate::visitor::VisitTypes::from_bits_truncate(0 $(| $crate::visitor::VisitTypes::$flag.bits())+)\n  }\n}\n\n/// A trait for visiting or transforming rules, properties, and values in a StyleSheet.\npub trait Visitor<'i, T: Visit<'i, T, Self> = DefaultAtRule> {\n  /// The `Err` value for `Result`s returned by `visit_*` methods.\n  type Error;\n\n  /// Returns the types of values that this visitor should visit. By default, it returns\n  /// `Self::TYPES`, but this can be overridden to change the value at runtime.\n  fn visit_types(&self) -> VisitTypes;\n\n  /// Visits a stylesheet.\n  #[inline]\n  fn visit_stylesheet<'o>(&mut self, stylesheet: &mut StyleSheet<'i, 'o, T>) -> Result<(), Self::Error> {\n    stylesheet.visit_children(self)\n  }\n\n  /// Visits a rule list.\n  #[inline]\n  fn visit_rule_list(&mut self, rules: &mut CssRuleList<'i, T>) -> Result<(), Self::Error> {\n    rules.visit_children(self)\n  }\n\n  /// Visits a rule.\n  #[inline]\n  fn visit_rule(&mut self, rule: &mut CssRule<'i, T>) -> Result<(), Self::Error> {\n    rule.visit_children(self)\n  }\n\n  /// Visits a declaration block.\n  #[inline]\n  fn visit_declaration_block(&mut self, decls: &mut DeclarationBlock<'i>) -> Result<(), Self::Error> {\n    decls.visit_children(self)\n  }\n\n  /// Visits a property.\n  #[inline]\n  fn visit_property(&mut self, property: &mut Property<'i>) -> Result<(), Self::Error> {\n    property.visit_children(self)\n  }\n\n  /// Visits a url.\n  fn visit_url(&mut self, _url: &mut Url<'i>) -> Result<(), Self::Error> {\n    Ok(())\n  }\n\n  /// Visits a color.\n  #[allow(unused_variables)]\n  fn visit_color(&mut self, color: &mut CssColor) -> Result<(), Self::Error> {\n    Ok(())\n  }\n\n  /// Visits an image.\n  #[inline]\n  fn visit_image(&mut self, image: &mut Image<'i>) -> Result<(), Self::Error> {\n    image.visit_children(self)\n  }\n\n  /// Visits a length.\n  #[allow(unused_variables)]\n  fn visit_length(&mut self, length: &mut LengthValue) -> Result<(), Self::Error> {\n    Ok(())\n  }\n\n  /// Visits an angle.\n  #[allow(unused_variables)]\n  fn visit_angle(&mut self, angle: &mut Angle) -> Result<(), Self::Error> {\n    Ok(())\n  }\n\n  /// Visits a ratio.\n  #[allow(unused_variables)]\n  fn visit_ratio(&mut self, ratio: &mut Ratio) -> Result<(), Self::Error> {\n    Ok(())\n  }\n\n  /// Visits a resolution.\n  #[allow(unused_variables)]\n  fn visit_resolution(&mut self, resolution: &mut Resolution) -> Result<(), Self::Error> {\n    Ok(())\n  }\n\n  /// Visits a time.\n  #[allow(unused_variables)]\n  fn visit_time(&mut self, time: &mut Time) -> Result<(), Self::Error> {\n    Ok(())\n  }\n\n  /// Visits a custom ident.\n  #[allow(unused_variables)]\n  fn visit_custom_ident(&mut self, ident: &mut CustomIdent) -> Result<(), Self::Error> {\n    Ok(())\n  }\n\n  /// Visits a dashed ident.\n  #[allow(unused_variables)]\n  fn visit_dashed_ident(&mut self, ident: &mut DashedIdent) -> Result<(), Self::Error> {\n    Ok(())\n  }\n\n  /// Visits a variable reference.\n  #[inline]\n  fn visit_variable(&mut self, var: &mut Variable<'i>) -> Result<(), Self::Error> {\n    var.visit_children(self)\n  }\n\n  /// Visits an environment variable reference.\n  #[inline]\n  fn visit_environment_variable(&mut self, env: &mut EnvironmentVariable<'i>) -> Result<(), Self::Error> {\n    env.visit_children(self)\n  }\n\n  /// Visits a media query list.\n  #[inline]\n  fn visit_media_list(&mut self, media: &mut MediaList<'i>) -> Result<(), Self::Error> {\n    media.visit_children(self)\n  }\n\n  /// Visits a media query.\n  #[inline]\n  fn visit_media_query(&mut self, query: &mut MediaQuery<'i>) -> Result<(), Self::Error> {\n    query.visit_children(self)\n  }\n\n  /// Visits a media feature.\n  #[inline]\n  fn visit_media_feature(&mut self, feature: &mut MediaFeature<'i>) -> Result<(), Self::Error> {\n    feature.visit_children(self)\n  }\n\n  /// Visits a media feature value.\n  #[inline]\n  fn visit_media_feature_value(&mut self, value: &mut MediaFeatureValue<'i>) -> Result<(), Self::Error> {\n    value.visit_children(self)\n  }\n\n  /// Visits a supports condition.\n  #[inline]\n  fn visit_supports_condition(&mut self, condition: &mut SupportsCondition<'i>) -> Result<(), Self::Error> {\n    condition.visit_children(self)\n  }\n\n  /// Visits a selector list.\n  #[inline]\n  fn visit_selector_list(&mut self, selectors: &mut SelectorList<'i>) -> Result<(), Self::Error> {\n    selectors.visit_children(self)\n  }\n\n  /// Visits a selector.\n  #[allow(unused_variables)]\n  fn visit_selector(&mut self, selector: &mut Selector<'i>) -> Result<(), Self::Error> {\n    Ok(())\n  }\n\n  /// Visits a custom function.\n  #[inline]\n  fn visit_function(&mut self, function: &mut Function<'i>) -> Result<(), Self::Error> {\n    function.visit_children(self)\n  }\n\n  /// Visits a token list.\n  #[inline]\n  fn visit_token_list(&mut self, tokens: &mut TokenList<'i>) -> Result<(), Self::Error> {\n    tokens.visit_children(self)\n  }\n\n  /// Visits a token or value in an unparsed property.\n  #[inline]\n  fn visit_token(&mut self, token: &mut TokenOrValue<'i>) -> Result<(), Self::Error> {\n    token.visit_children(self)\n  }\n}\n\n/// A trait for values that can be visited by a [Visitor](Visitor).\npub trait Visit<'i, T: Visit<'i, T, V>, V: ?Sized + Visitor<'i, T>> {\n  /// The types of values contained within this value and its children.\n  /// This is used to skip branches that don't have any values requested\n  /// by the Visitor.\n  const CHILD_TYPES: VisitTypes;\n\n  /// Visits the value by calling an appropriate method on the Visitor.\n  /// If no corresponding visitor method exists, then the children are visited.\n  #[inline]\n  fn visit(&mut self, visitor: &mut V) -> Result<(), V::Error> {\n    self.visit_children(visitor)\n  }\n\n  /// Visit the children of this value.\n  fn visit_children(&mut self, visitor: &mut V) -> Result<(), V::Error>;\n}\n\nimpl<'i, T: Visit<'i, T, V>, V: ?Sized + Visitor<'i, T>, U: Visit<'i, T, V>> Visit<'i, T, V> for Option<U> {\n  const CHILD_TYPES: VisitTypes = U::CHILD_TYPES;\n\n  fn visit(&mut self, visitor: &mut V) -> Result<(), V::Error> {\n    if let Some(v) = self {\n      v.visit(visitor)\n    } else {\n      Ok(())\n    }\n  }\n\n  fn visit_children(&mut self, visitor: &mut V) -> Result<(), V::Error> {\n    if let Some(v) = self {\n      v.visit_children(visitor)\n    } else {\n      Ok(())\n    }\n  }\n}\n\nimpl<'i, T: Visit<'i, T, V>, V: ?Sized + Visitor<'i, T>, U: Visit<'i, T, V>> Visit<'i, T, V> for Box<U> {\n  const CHILD_TYPES: VisitTypes = U::CHILD_TYPES;\n\n  fn visit(&mut self, visitor: &mut V) -> Result<(), V::Error> {\n    self.as_mut().visit(visitor)\n  }\n\n  fn visit_children(&mut self, visitor: &mut V) -> Result<(), V::Error> {\n    self.as_mut().visit_children(visitor)\n  }\n}\n\nimpl<'i, T: Visit<'i, T, V>, V: ?Sized + Visitor<'i, T>, U: Visit<'i, T, V>> Visit<'i, T, V> for Vec<U> {\n  const CHILD_TYPES: VisitTypes = U::CHILD_TYPES;\n\n  fn visit(&mut self, visitor: &mut V) -> Result<(), V::Error> {\n    self.iter_mut().try_for_each(|v| v.visit(visitor))\n  }\n\n  fn visit_children(&mut self, visitor: &mut V) -> Result<(), V::Error> {\n    self.iter_mut().try_for_each(|v| v.visit_children(visitor))\n  }\n}\n\nimpl<'i, A: smallvec::Array<Item = U>, U: Visit<'i, T, V>, T: Visit<'i, T, V>, V: ?Sized + Visitor<'i, T>>\n  Visit<'i, T, V> for SmallVec<A>\n{\n  const CHILD_TYPES: VisitTypes = U::CHILD_TYPES;\n\n  fn visit(&mut self, visitor: &mut V) -> Result<(), V::Error> {\n    self.iter_mut().try_for_each(|v| v.visit(visitor))\n  }\n\n  fn visit_children(&mut self, visitor: &mut V) -> Result<(), V::Error> {\n    self.iter_mut().try_for_each(|v| v.visit_children(visitor))\n  }\n}\n\nimpl<'i, T, V, U, W> Visit<'i, T, V> for IndexMap<U, W>\nwhere\n  T: Visit<'i, T, V>,\n  V: ?Sized + Visitor<'i, T>,\n  W: Visit<'i, T, V>,\n{\n  const CHILD_TYPES: VisitTypes = W::CHILD_TYPES;\n\n  fn visit(&mut self, visitor: &mut V) -> Result<(), V::Error> {\n    self.iter_mut().try_for_each(|(_k, v)| v.visit(visitor))\n  }\n\n  fn visit_children(&mut self, visitor: &mut V) -> Result<(), V::Error> {\n    self.iter_mut().try_for_each(|(_k, v)| v.visit_children(visitor))\n  }\n}\n\nmacro_rules! impl_visit {\n  ($t: ty) => {\n    impl<'i, V: ?Sized + Visitor<'i, T>, T: Visit<'i, T, V>> Visit<'i, T, V> for $t {\n      const CHILD_TYPES: VisitTypes = VisitTypes::empty();\n\n      fn visit_children(&mut self, _: &mut V) -> Result<(), V::Error> {\n        Ok(())\n      }\n    }\n  };\n}\n\nimpl_visit!(u8);\nimpl_visit!(u16);\nimpl_visit!(u32);\nimpl_visit!(i32);\nimpl_visit!(f32);\nimpl_visit!(bool);\nimpl_visit!(char);\nimpl_visit!(str);\nimpl_visit!(String);\nimpl_visit!((f32, f32));\n"
  },
  {
    "path": "static-self/Cargo.toml",
    "content": "[package]\nname = \"static-self\"\nversion = \"0.1.2\"\nedition = \"2021\"\nauthors = [\"Devon Govett <devongovett@gmail.com>\",\"Donny <kdy1997.dev@gmail.com>\"]\ndescription = \"A trait for values that can be cloned with a static lifetime\"\nlicense = \"MPL-2.0\"\nkeywords = [  ]\nrepository = \"https://github.com/parcel-bundler/lightningcss\"\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\nsmallvec = { version = \"1.11.1\", optional = true }\nstatic-self-derive = { version = \"0.1.1\", path = \"../static-self-derive\" }\nindexmap = { version = \"2.2.6\", optional = true }\n"
  },
  {
    "path": "static-self/src/lib.rs",
    "content": "pub use static_self_derive::IntoOwned;\n\n/// A trait for things that can be cloned with a new lifetime.\n///\n/// `'any` lifeitme means the output should have `'static` lifetime.\npub trait IntoOwned<'any> {\n  /// A variant of `Self` with a new lifetime.\n  type Owned: 'any;\n\n  /// Make lifetime of `self` `'static`.\n  fn into_owned(self) -> Self::Owned;\n}\n\nmacro_rules! impl_into_owned {\n  ($t: ty) => {\n    impl<'a> IntoOwned<'a> for $t {\n      type Owned = Self;\n\n      #[inline]\n      fn into_owned(self) -> Self {\n        self\n      }\n    }\n  };\n  ($($t:ty),*) => {\n    $(impl_into_owned!($t);)*\n  };\n}\n\nimpl_into_owned!(bool, f32, f64, u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, usize, isize, char, String);\n\nmacro_rules! impl_tuple {\n  (\n    $($name:ident),*\n  ) =>{\n    #[allow(non_snake_case)]\n    impl<'any, $($name,)*> IntoOwned<'any> for ($($name,)*)\n    where\n        $($name: IntoOwned<'any>),*\n    {\n      type Owned = ($(<$name as IntoOwned<'any>>::Owned,)*);\n\n      #[inline]\n      fn into_owned(self) -> Self::Owned {\n        let ($($name,)*) = self;\n        ($($name.into_owned(),)*)\n      }\n    }\n  };\n}\n\nmacro_rules! call_impl_tuple {\n  () => {};\n  ($first:ident) => {\n    impl_tuple!($first);\n  };\n  (\n    $first:ident,\n    $($name:ident),*\n  ) => {\n    call_impl_tuple!($($name),*);\n    impl_tuple!($first, $($name),*);\n  };\n}\n\ncall_impl_tuple!(A, B, C, D, E, F, G, H, I, J, K, L);\n\nimpl<'any, T> IntoOwned<'any> for Vec<T>\nwhere\n  T: IntoOwned<'any>,\n{\n  type Owned = Vec<<T as IntoOwned<'any>>::Owned>;\n\n  fn into_owned(self) -> Self::Owned {\n    self.into_iter().map(|v| v.into_owned()).collect()\n  }\n}\nimpl<'any, T> IntoOwned<'any> for Option<T>\nwhere\n  T: IntoOwned<'any>,\n{\n  type Owned = Option<<T as IntoOwned<'any>>::Owned>;\n\n  fn into_owned(self) -> Self::Owned {\n    self.map(|v| v.into_owned())\n  }\n}\n\nimpl<'any, T> IntoOwned<'any> for Box<T>\nwhere\n  T: IntoOwned<'any>,\n{\n  type Owned = Box<<T as IntoOwned<'any>>::Owned>;\n\n  fn into_owned(self) -> Self::Owned {\n    Box::new((*self).into_owned())\n  }\n}\n\nimpl<'any, T> IntoOwned<'any> for Box<[T]>\nwhere\n  T: IntoOwned<'any>,\n{\n  type Owned = Box<[<T as IntoOwned<'any>>::Owned]>;\n\n  fn into_owned(self) -> Self::Owned {\n    self.into_vec().into_owned().into_boxed_slice()\n  }\n}\n\n#[cfg(feature = \"smallvec\")]\nimpl<'any, T, const N: usize> IntoOwned<'any> for smallvec::SmallVec<[T; N]>\nwhere\n  T: IntoOwned<'any>,\n  [T; N]: smallvec::Array<Item = T>,\n  [<T as IntoOwned<'any>>::Owned; N]: smallvec::Array<Item = <T as IntoOwned<'any>>::Owned>,\n{\n  type Owned = smallvec::SmallVec<[<T as IntoOwned<'any>>::Owned; N]>;\n\n  fn into_owned(self) -> Self::Owned {\n    self.into_iter().map(|v| v.into_owned()).collect()\n  }\n}\n\n#[cfg(feature = \"indexmap\")]\nimpl<'any, K, V> IntoOwned<'any> for indexmap::IndexMap<K, V>\nwhere\n  K: IntoOwned<'any>,\n  V: IntoOwned<'any>,\n  <K as IntoOwned<'any>>::Owned: Eq + std::hash::Hash,\n{\n  type Owned = indexmap::IndexMap<<K as IntoOwned<'any>>::Owned, <V as IntoOwned<'any>>::Owned>;\n\n  fn into_owned(self) -> Self::Owned {\n    self.into_iter().map(|(k, v)| (k.into_owned(), v.into_owned())).collect()\n  }\n}\n\nimpl<'any, T, const N: usize> IntoOwned<'any> for [T; N]\nwhere\n  T: IntoOwned<'any>,\n{\n  type Owned = [<T as IntoOwned<'any>>::Owned; N];\n\n  fn into_owned(self) -> Self::Owned {\n    self\n      .into_iter()\n      .map(|v| v.into_owned())\n      .collect::<Vec<_>>()\n      .try_into()\n      .unwrap_or_else(|_| unreachable!(\"Vec<T> with N elements should be able to be converted to [T; N]\"))\n  }\n}\n"
  },
  {
    "path": "static-self-derive/Cargo.toml",
    "content": "[package]\nname = \"static-self-derive\"\nversion = \"0.1.1\"\nedition = \"2021\"\nauthors = [\"Devon Govett <devongovett@gmail.com>\",\"Donny <kdy1997.dev@gmail.com>\"]\ndescription = \"Derive macros for static-self\"\nlicense = \"MPL-2.0\"\nkeywords = [  ]\nrepository = \"https://github.com/parcel-bundler/lightningcss\"\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[lib]\nproc-macro = true\n\n[dependencies]\nsyn = { version = \"1.0\", features = [\"extra-traits\"] }\nquote = \"1.0\"\nproc-macro2 = \"1.0\"\n"
  },
  {
    "path": "static-self-derive/src/into_owned.rs",
    "content": "use proc_macro::{self, TokenStream};\nuse proc_macro2::Span;\nuse quote::quote;\nuse syn::{parse_macro_input, parse_quote, Data, DataEnum, DeriveInput, Field, Fields, Ident, Member};\n\npub(crate) fn derive_into_owned(input: TokenStream) -> TokenStream {\n  let DeriveInput {\n    ident: self_name,\n    data,\n    mut generics,\n    ..\n  } = parse_macro_input!(input);\n\n  let res = match data {\n    Data::Struct(s) => {\n      let fields = s\n        .fields\n        .iter()\n        .enumerate()\n        .map(|(index, Field { ident, .. })| {\n          let name = ident\n            .as_ref()\n            .map_or_else(|| Member::Unnamed(index.into()), |ident| Member::Named(ident.clone()));\n\n          let value = into_owned(quote! { self.#name });\n          if let Some(ident) = ident {\n            quote! { #ident: #value }\n          } else {\n            value\n          }\n        })\n        .collect::<Vec<proc_macro2::TokenStream>>();\n\n      match s.fields {\n        Fields::Unnamed(_) => {\n          quote! {\n            #self_name(#(#fields),*)\n          }\n        }\n        Fields::Named(_) => {\n          quote! {\n            #self_name { #(#fields),* }\n          }\n        }\n        Fields::Unit => quote! {},\n      }\n    }\n    Data::Enum(DataEnum { variants, .. }) => {\n      let variants = variants\n        .iter()\n        .map(|variant| {\n          let name = &variant.ident;\n          let mut field_names = Vec::new();\n          let mut static_fields = Vec::new();\n          for (index, Field { ident, .. }) in variant.fields.iter().enumerate() {\n            let name = ident.as_ref().map_or_else(\n              || Ident::new(&format!(\"_{}\", index), Span::call_site()),\n              |ident| ident.clone(),\n            );\n            field_names.push(name.clone());\n            let value = into_owned(quote! { #name });\n            static_fields.push(if let Some(ident) = ident {\n              quote! { #ident: #value }\n            } else {\n              value\n            })\n          }\n\n          match variant.fields {\n            Fields::Unnamed(_) => {\n              quote! {\n                #self_name::#name(#(#field_names),*) => {\n                  #self_name::#name(#(#static_fields),*)\n                }\n              }\n            }\n            Fields::Named(_) => {\n              quote! {\n                #self_name::#name { #(#field_names),* } => {\n                  #self_name::#name { #(#static_fields),* }\n                }\n              }\n            }\n            Fields::Unit => quote! {\n              #self_name::#name => #self_name::#name,\n            },\n          }\n        })\n        .collect::<proc_macro2::TokenStream>();\n\n      quote! {\n        match self {\n          #variants\n        }\n      }\n    }\n    _ => {\n      panic!(\"can only derive IntoOwned for enums and structs\")\n    }\n  };\n\n  let orig_generics = generics.clone();\n\n  // Add generic bounds for all type parameters.\n  let mut type_param_names = vec![];\n\n  for ty in generics.type_params() {\n    type_param_names.push(ty.ident.clone());\n  }\n\n  for type_param in type_param_names {\n    generics.make_where_clause().predicates.push_value(parse_quote! {\n      #type_param: ::static_self::IntoOwned<'any>\n    })\n  }\n\n  let has_lifetime = generics\n    .params\n    .first()\n    .map_or(false, |v| matches!(v, syn::GenericParam::Lifetime(..)));\n  let has_generic = !generics.params.is_empty();\n\n  // Prepend `'any` to generics\n  let any = syn::GenericParam::Lifetime(syn::LifetimeDef {\n    attrs: Default::default(),\n    lifetime: syn::Lifetime {\n      apostrophe: Span::call_site(),\n      ident: Ident::new(\"any\", Span::call_site()),\n    },\n    colon_token: None,\n    bounds: Default::default(),\n  });\n  generics.params.insert(0, any.clone());\n\n  let (impl_generics, _, where_clause) = generics.split_for_impl();\n  let (_, ty_generics, _) = orig_generics.split_for_impl();\n\n  let into_owned = if !has_generic {\n    quote! {\n      impl #impl_generics ::static_self::IntoOwned<'any> for #self_name #ty_generics #where_clause {\n        type Owned = Self;\n\n        #[inline]\n        fn into_owned(self) -> Self {\n          self\n        }\n      }\n    }\n  } else {\n    let mut generics_without_default = generics.clone();\n\n    let mut params = Vec::new();\n\n    for p in generics_without_default.params.iter_mut() {\n      if let syn::GenericParam::Type(ty) = p {\n        ty.default = None;\n\n        params.push(quote!(<#ty as static_self::IntoOwned<'any>>::Owned));\n      }\n    }\n\n    if has_lifetime {\n      quote! {\n        impl #impl_generics ::static_self::IntoOwned<'any> for #self_name #ty_generics #where_clause {\n          type Owned = #self_name<'any, #(#params),*>;\n          /// Consumes the value and returns an owned clone.\n          fn into_owned(self) -> Self::Owned {\n            use ::static_self::IntoOwned;\n\n            #res\n          }\n        }\n      }\n    } else {\n      quote! {\n        impl #impl_generics ::static_self::IntoOwned<'any> for #self_name #ty_generics #where_clause {\n          type Owned = #self_name<#(#params),*>;\n          /// Consumes the value and returns an owned clone.\n          fn into_owned(self) -> Self::Owned {\n            use ::static_self::IntoOwned;\n\n            #res\n          }\n        }\n      }\n    }\n  };\n\n  into_owned.into()\n}\n\nfn into_owned(name: proc_macro2::TokenStream) -> proc_macro2::TokenStream {\n  quote! { #name.into_owned() }\n}\n"
  },
  {
    "path": "static-self-derive/src/lib.rs",
    "content": "use proc_macro::TokenStream;\n\nmod into_owned;\n\n#[proc_macro_derive(IntoOwned)]\npub fn derive_into_owned(input: TokenStream) -> TokenStream {\n  into_owned::derive_into_owned(input)\n}\n"
  },
  {
    "path": "test-integration.mjs",
    "content": "import puppeteer from 'puppeteer';\nimport fetch from 'node-fetch';\nimport assert from 'assert';\nimport {diff} from 'jest-diff';\nimport * as css from 'lightningcss';\n\nlet urls = [\n  'https://getbootstrap.com/docs/5.1/examples/headers/',\n  'https://getbootstrap.com/docs/5.1/examples/heroes/',\n  'https://getbootstrap.com/docs/5.1/examples/features/',\n  'https://getbootstrap.com/docs/5.1/examples/sidebars/',\n  'https://getbootstrap.com/docs/5.1/examples/footers/',\n  'https://getbootstrap.com/docs/5.1/examples/dropdowns/',\n  'https://getbootstrap.com/docs/5.1/examples/list-groups/',\n  'https://getbootstrap.com/docs/5.1/examples/modals/',\n  'http://csszengarden.com',\n  'http://csszengarden.com/221/',\n  'http://csszengarden.com/219/',\n  'http://csszengarden.com/218/',\n  'http://csszengarden.com/217/',\n  'http://csszengarden.com/216/',\n  'http://csszengarden.com/215/'\n];\n\nlet success = true;\nconst browser = await puppeteer.launch();\nconst page = await browser.newPage();\n\nfor (let url of urls) {\n  await test(url);\n}\n\nasync function test(url) {\n  console.log(`Testing ${url}...`);\n\n  await page.goto(url);\n  await page.waitForNetworkIdle();\n\n  // Snapshot the computed styles of all elements\n  let elements = await page.$$('body *');\n  let computed = [];\n  for (let element of elements) {\n    let style = await element.evaluate(node => {\n      let res = {};\n      let style = window.getComputedStyle(node);\n      for (let i = 0; i < style.length; i++) {\n        res[style.item(i)] = style.getPropertyValue(style.item(i));\n      }\n      return res;\n    });\n\n    for (let key in style) {\n      style[key] = normalize(key, style[key]);\n    }\n\n    computed.push(style);\n  }\n\n  // Find stylesheets, load, and minify.\n  let stylesheets = await page.evaluate(() => {\n    return [...document.styleSheets].map(styleSheet => styleSheet.href).filter(Boolean);\n  });\n\n  let texts = await Promise.all(stylesheets.map(async url => {\n    let res = await fetch(url);\n    return res.text();\n  }));\n\n  let minified = texts.map((code, i) => {\n    let minified = css.transform({\n      filename: 'test.css',\n      code: Buffer.from(code),\n      minify: true,\n      targets: {\n        chrome: 95 << 16\n      }\n    }).code.toString();\n    console.log(new URL(stylesheets[i]).pathname, '–', code.length + ' bytes', '=>', minified.length + ' bytes');\n    return minified;\n  });\n\n  await page.setCacheEnabled(false);\n\n  // Disable the original stylesheets and insert a <style> element containing the minified CSS for each.\n  await page.evaluate(minified => {\n    let i = 0;\n    for (let stylesheet of [...document.styleSheets]) {\n      if (stylesheet.href) {\n        stylesheet.disabled = true;\n\n        let style = document.createElement('style');\n        style.textContent = minified[i++].replace(/url\\((.*?)\\)/g, (orig, url) => {\n          if (/['\"]?data:/.test(url)) {\n            return orig;\n          }\n\n          // Rewrite urls so they are relative to the original stylesheet.\n          return `url(${new URL(url, stylesheet.href)})`;\n        });\n\n        stylesheet.ownerNode.insertAdjacentElement('beforebegin', style);\n      }\n    }\n  }, minified);\n\n  await page.waitForNetworkIdle();\n\n  // Now get the computed style for each element again and compare.\n  let i = 0;\n  for (let element of elements) {\n    let style = await element.evaluate(node => {\n      let res = {};\n      let style = window.getComputedStyle(node);\n      for (let i = 0; i < style.length; i++) {\n        let name = style.item(i);\n        res[name] = style.getPropertyValue(name);\n      }\n      return res;\n    });\n\n    for (let key in style) {\n      style[key] = normalize(key, style[key]);\n\n      // Ignore prefixed properties that were removed during minification.\n      if (key.startsWith('-webkit-box-') && style[key] !== computed[i][key]) {\n        style[key] = computed[i][key];\n      }\n    }\n\n    try {\n      assert.deepEqual(style, computed[i]);\n    } catch (err) {\n      success = false;\n      console.log(diff(computed[i], style));\n      console.log(await element.evaluate(node => node.outerHTML))\n      console.log(minified[0]);\n    }\n\n    i++;\n  }\n\n  console.log('');\n}\n\nfunction normalize(key, value) {\n  if (key === 'background-position') {\n    value = value.replace(/(^|\\s)0(%|px)/g, '$10');\n  }\n\n  if (key === 'background-image') {\n    // Center is implied.\n    value = value.replace('radial-gradient(at center center, ', 'radial-gradient(');\n  }\n\n  return value.split(' ').map(v => {\n    if (/^[\\d\\.]+px$/.test(v)) {\n      let val = parseFloat(v);\n      return Math.round(val) + 'px';\n    }\n\n    return v;\n  }).join(' ');\n}\n\nif (success) {\n  console.log('Pass!');\n  process.exit(0);\n} else {\n  console.log('Fail!');\n  process.exit(1);\n}\n"
  },
  {
    "path": "test.js",
    "content": "const css = require('./');\nconst fs = require('fs');\n\nif (process.argv[process.argv.length - 1] !== __filename) {\n  let opts = {\n    filename: process.argv[process.argv.length - 1],\n    code: fs.readFileSync(process.argv[process.argv.length - 1]),\n    minify: true,\n    sourceMap: true,\n    targets: {\n      chrome: 95 << 16\n    }\n  };\n\n  console.time('optimize');\n  let r = css.transform(opts);\n  console.timeEnd('optimize')\n  // console.log(r.toString());\n  console.log(r);\n  let code = r.code;\n  if (r.map) {\n    code = code.toString() + `\\n/*# sourceMappingURL=out.css.map */\\n`;\n  }\n  fs.writeFileSync('out.css', code);\n  if (r.map) {\n    fs.writeFileSync('out.css.map', r.map);\n  }\n  return;\n}\n\nlet res = css.transform({\n  filename: __filename,\n  code: Buffer.from(`\n  @breakpoints {\n    .foo { color: yellow; }\n  }\n\n  .foo {\n    color: red;\n    @bar {\n      width: 25px;\n    }\n  }\n`),\n  drafts: {\n    nesting: true\n  },\n  targets: {\n    safari: 16 << 16\n  },\n  customAtRules: {\n    breakpoints: {\n      // Syntax string defining the at rule prelude.\n      // https://drafts.css-houdini.org/css-properties-values-api/#syntax-strings\n      prelude: null,\n      // Type of the at rule block.\n      // Can be declaration-list, rule-list, or style-block.\n      // https://www.w3.org/TR/css-syntax-3/#declaration-rule-list\n      body: 'rule-list'\n    },\n    bar: {\n      body: 'style-block'\n    }\n  },\n  visitor: {\n    Rule: {\n      custom(rule) {\n        console.log(rule.body);\n      }\n    },\n    Length(length) {\n      length.value *= 2;\n      return length;\n    }\n  }\n});\n\nconsole.log(res.code.toString());\n"
  },
  {
    "path": "tests/cli_integration_tests.rs",
    "content": "use assert_cmd::prelude::*;\nuse assert_fs::fixture::FixtureError;\nuse assert_fs::prelude::*;\nuse indoc::indoc;\nuse lightningcss::css_modules::CssModuleExport;\nuse predicates::prelude::*;\nuse std::collections::HashMap;\nuse std::fs;\nuse std::process::Command;\n\nfn test_file() -> Result<assert_fs::NamedTempFile, FixtureError> {\n  let file = assert_fs::NamedTempFile::new(\"test.css\")?;\n  file.write_str(\n    r#\"\n      .foo {\n        border: none;\n      }\n    \"#,\n  )?;\n  Ok(file)\n}\n\nfn test_file2() -> Result<assert_fs::NamedTempFile, FixtureError> {\n  let file = assert_fs::NamedTempFile::new(\"test2.css\")?;\n  file.write_str(\n    r#\"\n      .foo {\n        color: yellow;\n      }\n    \"#,\n  )?;\n  Ok(file)\n}\n\nfn css_module_test_vals() -> (String, String, String) {\n  let exports: HashMap<&str, CssModuleExport> = HashMap::from([\n    (\n      \"fade\",\n      CssModuleExport {\n        name: \"EgL3uq_fade\".into(),\n        composes: vec![],\n        is_referenced: false,\n      },\n    ),\n    (\n      \"foo\",\n      CssModuleExport {\n        name: \"EgL3uq_foo\".into(),\n        composes: vec![],\n        is_referenced: false,\n      },\n    ),\n    (\n      \"circles\",\n      CssModuleExport {\n        name: \"EgL3uq_circles\".into(),\n        composes: vec![],\n        is_referenced: true,\n      },\n    ),\n    (\n      \"id\",\n      CssModuleExport {\n        name: \"EgL3uq_id\".into(),\n        composes: vec![],\n        is_referenced: false,\n      },\n    ),\n    (\n      \"test\",\n      CssModuleExport {\n        name: \"EgL3uq_test\".into(),\n        composes: vec![],\n        is_referenced: true,\n      },\n    ),\n  ]);\n  (\n    r#\"\n      .foo {\n        color: red;\n      }\n\n      #id {\n        animation: 2s test;\n      }\n\n      @keyframes test {\n        from { color: red }\n        to { color: yellow }\n      }\n\n      @counter-style circles {\n        symbols: Ⓐ Ⓑ Ⓒ;\n      }\n\n      ul {\n        list-style: circles;\n      }\n\n      @keyframes fade {\n        from { opacity: 0 }\n        to { opacity: 1 }\n      }\n    \"#\n    .into(),\n    indoc! {r#\"\n      .EgL3uq_foo {\n        color: red;\n      }\n\n      #EgL3uq_id {\n        animation: 2s EgL3uq_test;\n      }\n\n      @keyframes EgL3uq_test {\n        from {\n          color: red;\n        }\n\n        to {\n          color: #ff0;\n        }\n      }\n\n      @counter-style EgL3uq_circles {\n        symbols: Ⓐ Ⓑ Ⓒ;\n      }\n\n      ul {\n        list-style: EgL3uq_circles;\n      }\n\n      @keyframes EgL3uq_fade {\n        from {\n          opacity: 0;\n        }\n\n        to {\n          opacity: 1;\n        }\n      }\n    \"#}\n    .into(),\n    serde_json::to_string(&exports).unwrap(),\n  )\n}\n\n#[test]\nfn valid_input_file() -> Result<(), Box<dyn std::error::Error>> {\n  let file = assert_fs::NamedTempFile::new(\"test.css\")?;\n  file.write_str(\n    r#\"\n      .foo {\n        border: none;\n      }\n    \"#,\n  )?;\n\n  let mut cmd = Command::cargo_bin(\"lightningcss\")?;\n  cmd.arg(file.path());\n  cmd.assert().success().stdout(predicate::str::contains(indoc! {r#\"\n        .foo {\n          border: none;\n        }\"#}));\n\n  Ok(())\n}\n\n#[test]\nfn empty_input_file() -> Result<(), Box<dyn std::error::Error>> {\n  let file = assert_fs::NamedTempFile::new(\"test.css\")?;\n  file.write_str(\"\")?;\n\n  let mut cmd = Command::cargo_bin(\"lightningcss\")?;\n  cmd.arg(file.path());\n  cmd.assert().success();\n\n  Ok(())\n}\n\n#[test]\nfn output_file_option() -> Result<(), Box<dyn std::error::Error>> {\n  let infile = test_file()?;\n  let outfile = assert_fs::NamedTempFile::new(\"test.out\")?;\n  let mut cmd = Command::cargo_bin(\"lightningcss\")?;\n  cmd.arg(infile.path());\n  cmd.arg(\"--output-file\").arg(outfile.path());\n  cmd.assert().success();\n  outfile.assert(predicate::str::contains(indoc! {r#\"\n        .foo {\n          border: none;\n        }\"#}));\n\n  Ok(())\n}\n\n#[test]\nfn output_file_option_create_missing_directories() -> Result<(), Box<dyn std::error::Error>> {\n  let infile = test_file()?;\n  let outdir = assert_fs::TempDir::new()?;\n  let outfile = outdir.child(\"out.css\");\n  outdir.close()?;\n  let mut cmd = Command::cargo_bin(\"lightningcss\")?;\n  cmd.arg(infile.path());\n  cmd.arg(\"--output-file\").arg(outfile.path());\n  cmd.assert().success();\n  outfile.assert(predicate::str::contains(indoc! {\n    r#\"\n      .foo {\n        border: none;\n      }\n    \"#\n  }));\n  fs::remove_dir_all(outfile.parent().unwrap())?;\n\n  Ok(())\n}\n\n#[test]\nfn multiple_input_files() -> Result<(), Box<dyn std::error::Error>> {\n  let infile = test_file()?;\n  let infile2 = test_file2()?;\n  let outdir = assert_fs::TempDir::new()?;\n  let mut cmd = Command::cargo_bin(\"lightningcss\")?;\n  cmd.arg(infile.path());\n  cmd.arg(infile2.path());\n  cmd.arg(\"--output-dir\").arg(outdir.path());\n  cmd.assert().success();\n  outdir\n    .child(infile.file_name().unwrap())\n    .assert(predicate::str::contains(indoc! {r#\"\n        .foo {\n          border: none;\n        }\"#}));\n  outdir\n    .child(infile2.file_name().unwrap())\n    .assert(predicate::str::contains(indoc! {r#\"\n        .foo {\n          color: #ff0;\n        }\"#}));\n\n  Ok(())\n}\n\n#[test]\nfn multiple_input_files_out_file() -> Result<(), Box<dyn std::error::Error>> {\n  let infile = test_file()?;\n  let infile2 = test_file2()?;\n  let outdir = assert_fs::TempDir::new()?;\n  let mut cmd = Command::cargo_bin(\"lightningcss\")?;\n  cmd.arg(infile.path());\n  cmd.arg(infile2.path());\n  cmd.arg(\"--output-file\").arg(outdir.path());\n  cmd.assert().failure();\n\n  Ok(())\n}\n\n#[test]\nfn multiple_input_files_stdout() -> Result<(), Box<dyn std::error::Error>> {\n  let infile = test_file()?;\n  let infile2 = test_file2()?;\n  let mut cmd = Command::cargo_bin(\"lightningcss\")?;\n  cmd.arg(infile.path());\n  cmd.arg(infile2.path());\n  cmd.assert().failure();\n\n  Ok(())\n}\n\n#[test]\nfn minify_option() -> Result<(), Box<dyn std::error::Error>> {\n  let infile = test_file()?;\n  let mut cmd = Command::cargo_bin(\"lightningcss\")?;\n  cmd.arg(infile.path());\n  cmd.arg(\"--minify\");\n  cmd\n    .assert()\n    .success()\n    .stdout(predicate::str::contains(indoc! {r#\".foo{border:none}\"#}));\n\n  Ok(())\n}\n\n#[test]\nfn nesting_option() -> Result<(), Box<dyn std::error::Error>> {\n  let infile = assert_fs::NamedTempFile::new(\"test.css\")?;\n  infile.write_str(\n    r#\"\n        .foo {\n          color: blue;\n          & > .bar { color: red; }\n        }\n      \"#,\n  )?;\n\n  let mut cmd = Command::cargo_bin(\"lightningcss\")?;\n  cmd.arg(infile.path());\n  cmd.arg(\"--targets=defaults\");\n  cmd.arg(\"--nesting\");\n  cmd.assert().success().stdout(predicate::str::contains(indoc! {r#\"\n        .foo {\n          color: #00f;\n        }\n\n        .foo > .bar {\n          color: red;\n        }\n      \"#}));\n\n  Ok(())\n}\n\n#[test]\nfn css_modules_infer_output_file() -> Result<(), Box<dyn std::error::Error>> {\n  let (input, _, exports) = css_module_test_vals();\n  let infile = assert_fs::NamedTempFile::new(\"test.css\")?;\n  let outfile = assert_fs::NamedTempFile::new(\"out.css\")?;\n  infile.write_str(&input)?;\n  let mut cmd = Command::cargo_bin(\"lightningcss\")?;\n  cmd.current_dir(infile.path().parent().unwrap());\n  cmd.arg(infile.path());\n  cmd.arg(\"--css-modules\");\n  cmd.arg(\"-o\").arg(outfile.path());\n  cmd.assert().success();\n\n  let expected: serde_json::Value = serde_json::from_str(&exports)?;\n  let actual: serde_json::Value =\n    serde_json::from_str(&std::fs::read_to_string(outfile.path().with_extension(\"json\"))?)?;\n  assert_eq!(expected, actual);\n\n  Ok(())\n}\n\n#[test]\nfn css_modules_output_target_option() -> Result<(), Box<dyn std::error::Error>> {\n  let (input, _, exports) = css_module_test_vals();\n  let infile = assert_fs::NamedTempFile::new(\"test.css\")?;\n  let outfile = assert_fs::NamedTempFile::new(\"out.css\")?;\n  let modules_file = assert_fs::NamedTempFile::new(\"module.json\")?;\n  infile.write_str(&input)?;\n  let mut cmd = Command::cargo_bin(\"lightningcss\")?;\n  cmd.current_dir(infile.path().parent().unwrap());\n  cmd.arg(infile.path());\n  cmd.arg(\"-o\").arg(outfile.path());\n  cmd.arg(\"--css-modules\").arg(modules_file.path());\n  cmd.assert().success();\n\n  let expected: serde_json::Value = serde_json::from_str(&exports)?;\n  let actual: serde_json::Value = serde_json::from_str(&std::fs::read_to_string(modules_file.path())?)?;\n  assert_eq!(expected, actual);\n\n  Ok(())\n}\n\n#[test]\nfn css_modules_stdout() -> Result<(), Box<dyn std::error::Error>> {\n  let (input, out_code, exports) = css_module_test_vals();\n  let infile = assert_fs::NamedTempFile::new(\"test.css\")?;\n  infile.write_str(&input)?;\n  let mut cmd = Command::cargo_bin(\"lightningcss\")?;\n  cmd.current_dir(infile.path().parent().unwrap());\n  cmd.arg(infile.path());\n  cmd.arg(\"--css-modules\");\n  let assert = cmd.assert().success();\n  let output = assert.get_output();\n\n  let expected: serde_json::Value = serde_json::from_str(&exports)?;\n  let actual: serde_json::Value = serde_json::from_slice(&output.stdout)?;\n  assert_eq!(out_code, actual.pointer(\"/code\").unwrap().as_str().unwrap());\n  assert_eq!(&expected, actual.pointer(\"/exports\").unwrap());\n\n  Ok(())\n}\n\n#[test]\nfn css_modules_pattern() -> Result<(), Box<dyn std::error::Error>> {\n  let (input, _, _) = css_module_test_vals();\n  let infile = assert_fs::NamedTempFile::new(\"test.css\")?;\n  infile.write_str(&input)?;\n  let mut cmd = Command::cargo_bin(\"lightningcss\")?;\n  cmd.current_dir(infile.path().parent().unwrap());\n  cmd.arg(infile.path());\n  cmd.arg(\"--css-modules\");\n  cmd.arg(\"--css-modules-pattern\").arg(\"[name]-[hash]-[local]\");\n  cmd.assert().success().stdout(predicate::str::contains(\"test-EgL3uq-foo\"));\n\n  Ok(())\n}\n\n#[test]\nfn css_modules_next_64299() -> Result<(), Box<dyn std::error::Error>> {\n  let file = assert_fs::NamedTempFile::new(\"test.css\")?;\n  file.write_str(\n    \"\n  .blue {\n    background: blue;\n\n    :global {\n      .red {\n        background: red;\n      }\n    }\n\n    &:global {\n      &.green {\n        background: green;\n      }\n    }\n  }\n  \",\n  )?;\n\n  let mut cmd = Command::cargo_bin(\"lightningcss\")?;\n  cmd.arg(file.path());\n  cmd.arg(\"--css-modules\");\n  cmd.assert().failure();\n\n  Ok(())\n}\n\n#[test]\nfn sourcemap() -> Result<(), Box<dyn std::error::Error>> {\n  let (input, _, _) = css_module_test_vals();\n  let infile = assert_fs::NamedTempFile::new(\"test.css\")?;\n  let outdir = assert_fs::TempDir::new()?;\n  let outfile = outdir.child(\"out.css\");\n  infile.write_str(&input)?;\n  let mut cmd = Command::cargo_bin(\"lightningcss\")?;\n  cmd.current_dir(infile.path().parent().unwrap());\n  cmd.arg(infile.path());\n  cmd.arg(\"-o\").arg(outfile.path());\n  cmd.arg(\"--sourcemap\");\n  cmd.assert().success();\n\n  outfile.assert(predicate::str::contains(&format!(\n    \"/*# sourceMappingURL={}.map */\",\n    outfile.path().to_str().unwrap()\n  )));\n  let mapfile = outdir.child(\"out.css.map\");\n  mapfile.assert(predicate::str::contains(r#\"\"version\":3\"#));\n  mapfile.assert(predicate::str::contains(r#\"\"sources\":[\"test.css\"]\"#));\n  mapfile.assert(predicate::str::contains(\n    r#\"\"mappings\":\"AACM;;;;AAIA;;;;AAIA;;;;;;;;;;AAKA;;;;AAIA;;;;AAIA\"\"#,\n  ));\n\n  Ok(())\n}\n\n#[test]\nfn targets() -> Result<(), Box<dyn std::error::Error>> {\n  let file = assert_fs::NamedTempFile::new(\"test.css\")?;\n  file.write_str(\n    r#\"\n      @custom-media --foo print;\n      @media (--foo) {\n        .a { color: red }\n      }\n    \"#,\n  )?;\n\n  let mut cmd = Command::cargo_bin(\"lightningcss\")?;\n  cmd.arg(file.path());\n  cmd.arg(\"--custom-media\");\n  cmd.arg(\"--targets\").arg(\"last 1 Chrome version\");\n  cmd.assert().success().stdout(predicate::str::contains(indoc! {r#\"\n        @media print {\n          .a {\n            color: red;\n          }\n        }\"#}));\n\n  Ok(())\n}\n\n#[test]\nfn preserve_custom_media() -> Result<(), Box<dyn std::error::Error>> {\n  let file = assert_fs::NamedTempFile::new(\"test.css\")?;\n  file.write_str(\n    r#\"\n      @custom-media --foo print;\n    \"#,\n  )?;\n\n  let mut cmd = Command::cargo_bin(\"lightningcss\")?;\n  cmd.arg(file.path());\n  cmd.arg(\"--custom-media\");\n  cmd.assert().success().stdout(predicate::str::contains(indoc! {r#\"\n    @custom-media --foo print;\n  \"#}));\n\n  Ok(())\n}\n\n#[test]\n/// Test command line argument parsing failing when `--targets` is used at the same time as `--browserslist`.\n/// The two options are mutually exclusive.\nfn browserslist_targets_exclusive() -> Result<(), Box<dyn std::error::Error>> {\n  let mut cmd = Command::cargo_bin(\"lightningcss\")?;\n  cmd.arg(\"--targets\").arg(\"defaults\");\n  cmd.arg(\"--browserslist\");\n  cmd.assert().failure().stderr(predicate::str::contains(indoc! {r#\"\n    error: The argument '--targets <TARGETS>' cannot be used with '--browserslist'\n  \"#}));\n\n  Ok(())\n}\n\n#[test]\n/// Test browserslist defaults being applied when no configuration is provided or discovered.\n///\n/// Note: This test might fail in unhygienic environments and should ideally run inside a chroot.\n/// We have no control over the contents of our temp dir's parent dir (e.g. `/tmp`).\n/// If this parent dir or its ancestors contain a `browserslist`, `.browserslistrc` or `package.json`\n/// file, then configuration will be read from there, instead of applying defaults.\nfn browserslist_defaults() -> Result<(), Box<dyn std::error::Error>> {\n  let dir = assert_fs::TempDir::new()?;\n  let file = dir.child(\"test.css\");\n  file.write_str(\n    r#\"\n      * {\n        -webkit-border-radius: 1rem;\n        border-radius: 1rem;\n      }\n    \"#,\n  )?;\n\n  let mut cmd = Command::cargo_bin(\"lightningcss\")?;\n  cmd.current_dir(dir.path());\n  cmd.env_clear();\n  cmd.arg(\"--browserslist\");\n  cmd.arg(file.path());\n  cmd.assert().success().stdout(predicate::str::contains(indoc! {r#\"\n    * {\n      border-radius: 1rem;\n    }\n  \"#}));\n\n  Ok(())\n}\n\n#[test]\n/// Test browserslist configuration being read from the `BROWSERSLIST` environment variable.\nfn browserslist_env_config() -> Result<(), Box<dyn std::error::Error>> {\n  let dir = assert_fs::TempDir::new()?;\n  let file = dir.child(\"test.css\");\n  file.write_str(\n    r#\"\n      * {\n        border-radius: 1rem;\n      }\n    \"#,\n  )?;\n\n  let mut cmd = Command::cargo_bin(\"lightningcss\")?;\n  cmd.current_dir(dir.path());\n  cmd.env_clear();\n  cmd.env(\"BROWSERSLIST\", \"safari 4\");\n  cmd.arg(\"--browserslist\");\n  cmd.arg(file.path());\n  cmd.assert().success().stdout(predicate::str::contains(indoc! {r#\"\n    * {\n      -webkit-border-radius: 1rem;\n      border-radius: 1rem;\n    }\n  \"#}));\n\n  Ok(())\n}\n\n#[test]\n/// Test browserslist configuration being read from the file configured\n/// by setting the `BROWSERSLIST_CONFIG` environment variable.\nfn browserslist_env_config_file() -> Result<(), Box<dyn std::error::Error>> {\n  let dir = assert_fs::TempDir::new()?;\n  let file = dir.child(\"test.css\");\n  file.write_str(\n    r#\"\n      * {\n        border-radius: 1rem;\n      }\n    \"#,\n  )?;\n\n  let config = dir.child(\"config\");\n  config.write_str(\n    r#\"\n      safari 4\n    \"#,\n  )?;\n\n  let mut cmd = Command::cargo_bin(\"lightningcss\")?;\n  cmd.current_dir(dir.path());\n  cmd.env_clear();\n  cmd.env(\"BROWSERSLIST_CONFIG\", config.path());\n  cmd.arg(\"--browserslist\");\n  cmd.arg(file.path());\n  cmd.assert().success().stdout(predicate::str::contains(indoc! {r#\"\n    * {\n      -webkit-border-radius: 1rem;\n      border-radius: 1rem;\n    }\n  \"#}));\n\n  Ok(())\n}\n\n#[test]\n/// Test `browserslist` configuration file being read.\nfn browserslist_config_discovery() -> Result<(), Box<dyn std::error::Error>> {\n  let dir = assert_fs::TempDir::new()?;\n  let file = dir.child(\"test.css\");\n  file.write_str(\n    r#\"\n      * {\n        border-radius: 1rem;\n      }\n    \"#,\n  )?;\n\n  let config = dir.child(\"browserslist\");\n  config.write_str(\n    r#\"\n      safari 4\n    \"#,\n  )?;\n\n  let mut cmd = Command::cargo_bin(\"lightningcss\")?;\n  cmd.current_dir(dir.path());\n  cmd.env_clear();\n  cmd.arg(\"--browserslist\");\n  cmd.arg(file.path());\n  cmd.assert().success().stdout(predicate::str::contains(indoc! {r#\"\n    * {\n      -webkit-border-radius: 1rem;\n      border-radius: 1rem;\n    }\n  \"#}));\n\n  Ok(())\n}\n\n#[test]\n/// Test `.browserslistrc` configuration file being read.\nfn browserslist_rc_discovery() -> Result<(), Box<dyn std::error::Error>> {\n  let dir = assert_fs::TempDir::new()?;\n  let file = dir.child(\"test.css\");\n  file.write_str(\n    r#\"\n      * {\n        border-radius: 1rem;\n      }\n    \"#,\n  )?;\n\n  let config = dir.child(\".browserslistrc\");\n  config.write_str(\n    r#\"\n      safari 4\n    \"#,\n  )?;\n\n  let mut cmd = Command::cargo_bin(\"lightningcss\")?;\n  cmd.current_dir(dir.path());\n  cmd.env_clear();\n  cmd.arg(\"--browserslist\");\n  cmd.arg(file.path());\n  cmd.assert().success().stdout(predicate::str::contains(indoc! {r#\"\n    * {\n      -webkit-border-radius: 1rem;\n      border-radius: 1rem;\n    }\n  \"#}));\n\n  Ok(())\n}\n\n#[test]\n/// Test `package.json` configuration section being read.\nfn browserslist_package_discovery() -> Result<(), Box<dyn std::error::Error>> {\n  let dir = assert_fs::TempDir::new()?;\n  let file = dir.child(\"test.css\");\n  file.write_str(\n    r#\"\n      * {\n        border-radius: 1rem;\n      }\n    \"#,\n  )?;\n\n  let config = dir.child(\"package.json\");\n  config.write_str(\n    r#\"\n      {\n        \"browserslist\": \"safari 4\"\n      }\n    \"#,\n  )?;\n\n  let mut cmd = Command::cargo_bin(\"lightningcss\")?;\n  cmd.current_dir(dir.path());\n  cmd.env_clear();\n  cmd.arg(\"--browserslist\");\n  cmd.arg(file.path());\n  cmd.assert().success().stdout(predicate::str::contains(indoc! {r#\"\n    * {\n      -webkit-border-radius: 1rem;\n      border-radius: 1rem;\n    }\n  \"#}));\n\n  Ok(())\n}\n\n#[test]\n/// Test environment targets being applied from the `NODE_ENV` environment variable.\nfn browserslist_environment_from_node_env() -> Result<(), Box<dyn std::error::Error>> {\n  let dir = assert_fs::TempDir::new()?;\n  let file = dir.child(\"test.css\");\n  file.write_str(\n    r#\"\n      * {\n        border-radius: 1rem;\n      }\n    \"#,\n  )?;\n\n  let config = dir.child(\"browserslist\");\n  config.write_str(\n    r#\"\n      last 1 Chrome version\n\n      [legacy]\n      safari 4\n    \"#,\n  )?;\n\n  let mut cmd = Command::cargo_bin(\"lightningcss\")?;\n  cmd.current_dir(dir.path());\n  cmd.env_clear();\n  cmd.env(\"NODE_ENV\", \"legacy\");\n  cmd.arg(\"--browserslist\");\n  cmd.arg(file.path());\n  cmd.assert().success().stdout(predicate::str::contains(indoc! {r#\"\n    * {\n      -webkit-border-radius: 1rem;\n      border-radius: 1rem;\n    }\n  \"#}));\n\n  Ok(())\n}\n\n#[test]\n/// Test environment targets being applied from the `BROWSERSLIST_ENV` environment variable.\nfn browserslist_environment_from_browserslist_env() -> Result<(), Box<dyn std::error::Error>> {\n  let dir = assert_fs::TempDir::new()?;\n  let file = dir.child(\"test.css\");\n  file.write_str(\n    r#\"\n      * {\n        border-radius: 1rem;\n      }\n    \"#,\n  )?;\n\n  let config = dir.child(\"browserslist\");\n  config.write_str(\n    r#\"\n      last 1 Chrome version\n\n      [legacy]\n      safari 4\n    \"#,\n  )?;\n\n  let mut cmd = Command::cargo_bin(\"lightningcss\")?;\n  cmd.current_dir(dir.path());\n  cmd.env_clear();\n  cmd.env(\"BROWSERSLIST_ENV\", \"legacy\");\n  cmd.arg(\"--browserslist\");\n  cmd.arg(file.path());\n  cmd.assert().success().stdout(predicate::str::contains(indoc! {r#\"\n    * {\n      -webkit-border-radius: 1rem;\n      border-radius: 1rem;\n    }\n  \"#}));\n\n  Ok(())\n}\n\n#[test]\nfn next_66191() -> Result<(), Box<dyn std::error::Error>> {\n  let infile = assert_fs::NamedTempFile::new(\"test.css\")?;\n  infile.write_str(\n    r#\"\n      .cb:is(input:checked) {\n        margin: 3rem;\n      }\n    \"#,\n  )?;\n  let outfile = assert_fs::NamedTempFile::new(\"test.out\")?;\n  let mut cmd = Command::cargo_bin(\"lightningcss\")?;\n  cmd.arg(infile.path());\n  cmd.arg(\"--output-file\").arg(outfile.path());\n  cmd.assert().success();\n  outfile.assert(predicate::str::contains(indoc! {r#\".cb:is(input:checked)\"#}));\n\n  Ok(())\n}\n"
  },
  {
    "path": "tests/test_cssom.rs",
    "content": "use lightningcss::{\n  declaration::DeclarationBlock,\n  properties::{Property, PropertyId},\n  stylesheet::{ParserOptions, PrinterOptions},\n  traits::ToCss,\n  vendor_prefix::VendorPrefix,\n};\n\nfn get_test(decls: &str, property_id: PropertyId, expected: Option<(&str, bool)>) {\n  let decls = DeclarationBlock::parse_string(decls, ParserOptions::default()).unwrap();\n  let v = decls.get(&property_id);\n  if let Some((expected, important)) = expected {\n    let (value, is_important) = v.unwrap();\n    assert_eq!(\n      *value,\n      Property::parse_string(property_id, expected, ParserOptions::default()).unwrap()\n    );\n    assert_eq!(is_important, important);\n  } else {\n    assert_eq!(v, None)\n  }\n}\n\n#[test]\nfn test_get() {\n  get_test(\"color: red\", PropertyId::Color, Some((\"red\", false)));\n  get_test(\"color: red !important\", PropertyId::Color, Some((\"red\", true)));\n  get_test(\"color: green; color: red\", PropertyId::Color, Some((\"red\", false)));\n  get_test(\n    r#\"\n    margin-top: 5px;\n    margin-bottom: 5px;\n    margin-left: 5px;\n    margin-right: 5px;\n    \"#,\n    PropertyId::Margin,\n    Some((\"5px\", false)),\n  );\n  get_test(\n    r#\"\n    margin-top: 5px;\n    margin-bottom: 5px;\n    margin-left: 6px;\n    margin-right: 6px;\n    \"#,\n    PropertyId::Margin,\n    Some((\"5px 6px\", false)),\n  );\n  get_test(\n    r#\"\n    margin-top: 5px;\n    margin-bottom: 5px;\n    margin-left: 6px;\n    margin-right: 6px;\n    \"#,\n    PropertyId::Margin,\n    Some((\"5px 6px\", false)),\n  );\n  get_test(\n    r#\"\n    margin-top: 5px;\n    margin-bottom: 5px;\n    \"#,\n    PropertyId::Margin,\n    None,\n  );\n  get_test(\n    r#\"\n    margin-top: 5px;\n    margin-bottom: 5px;\n    margin-left: 5px !important;\n    margin-right: 5px;\n    \"#,\n    PropertyId::Margin,\n    None,\n  );\n  get_test(\n    r#\"\n    margin-top: 5px !important;\n    margin-bottom: 5px !important;\n    margin-left: 5px !important;\n    margin-right: 5px !important;\n    \"#,\n    PropertyId::Margin,\n    Some((\"5px\", true)),\n  );\n  get_test(\n    \"margin: 5px 6px 7px 8px\",\n    PropertyId::Margin,\n    Some((\"5px 6px 7px 8px\", false)),\n  );\n  get_test(\"margin: 5px 6px 7px 8px\", PropertyId::MarginTop, Some((\"5px\", false)));\n  get_test(\n    r#\"\n    border: 1px solid red;\n    border-color: green;\n    \"#,\n    PropertyId::Border,\n    Some((\"1px solid green\", false)),\n  );\n  get_test(\n    r#\"\n    border: 1px solid red;\n    border-left-color: green;\n    \"#,\n    PropertyId::Border,\n    None,\n  );\n  get_test(\"background: red\", PropertyId::Background, Some((\"red\", false)));\n  get_test(\"background: red\", PropertyId::BackgroundColor, Some((\"red\", false)));\n  get_test(\n    \"background: red url(foo.png)\",\n    PropertyId::BackgroundColor,\n    Some((\"red\", false)),\n  );\n  get_test(\n    \"background: url(foo.png), url(bar.png) red\",\n    PropertyId::BackgroundColor,\n    Some((\"red\", false)),\n  );\n  get_test(\n    \"background: url(foo.png) green, url(bar.png) red\",\n    PropertyId::BackgroundColor,\n    Some((\"red\", false)),\n  );\n  get_test(\n    \"background: linear-gradient(red, green)\",\n    PropertyId::BackgroundImage,\n    Some((\"linear-gradient(red, green)\", false)),\n  );\n  get_test(\n    \"background: linear-gradient(red, green), linear-gradient(#fff, #000)\",\n    PropertyId::BackgroundImage,\n    Some((\"linear-gradient(red, green), linear-gradient(#fff, #000)\", false)),\n  );\n  get_test(\n    \"background: linear-gradient(red, green) repeat-x, linear-gradient(#fff, #000) repeat-y\",\n    PropertyId::BackgroundImage,\n    Some((\"linear-gradient(red, green), linear-gradient(#fff, #000)\", false)),\n  );\n  get_test(\n    \"background: linear-gradient(red, green) repeat-x, linear-gradient(#fff, #000) repeat-y\",\n    PropertyId::BackgroundRepeat,\n    Some((\"repeat-x, repeat-y\", false)),\n  );\n  get_test(\n    r#\"\n    background: linear-gradient(red, green);\n    background-position-x: 20px;\n    background-position-y: 10px;\n    background-size: 50px 100px;\n    background-repeat: repeat no-repeat;\n    \"#,\n    PropertyId::Background,\n    Some((\"linear-gradient(red, green) 20px 10px / 50px 100px repeat-x\", false)),\n  );\n  get_test(\n    r#\"\n    background: linear-gradient(red, green);\n    background-position-x: 20px;\n    background-position-y: 10px !important;\n    background-size: 50px 100px;\n    background-repeat: repeat no-repeat;\n    \"#,\n    PropertyId::Background,\n    None,\n  );\n  get_test(\n    r#\"\n    background: linear-gradient(red, green), linear-gradient(#fff, #000) gray;\n    background-position-x: right 20px, 10px;\n    background-position-y: top 20px, 15px;\n    background-size: 50px 50px, auto;\n    background-repeat: repeat no-repeat, no-repeat;\n    \"#,\n    PropertyId::Background,\n    Some((\"linear-gradient(red, green) right 20px top 20px / 50px 50px repeat-x, gray linear-gradient(#fff, #000) 10px 15px no-repeat\", false)),\n  );\n  get_test(\n    r#\"\n    background: linear-gradient(red, green);\n    background-position-x: right 20px, 10px;\n    background-position-y: top 20px, 15px;\n    background-size: 50px 50px, auto;\n    background-repeat: repeat no-repeat, no-repeat;\n    \"#,\n    PropertyId::Background,\n    None,\n  );\n  get_test(\n    r#\"\n    background: linear-gradient(red, green);\n    background-position: 20px 10px;\n    background-size: 50px 100px;\n    background-repeat: repeat no-repeat;\n    \"#,\n    PropertyId::Background,\n    Some((\"linear-gradient(red, green) 20px 10px / 50px 100px repeat-x\", false)),\n  );\n  get_test(\n    r#\"\n    background-position-x: 20px;\n    background-position-y: 10px;\n    \"#,\n    PropertyId::BackgroundPosition,\n    Some((\"20px 10px\", false)),\n  );\n  get_test(\n    r#\"\n    background: linear-gradient(red, green) 20px 10px;\n    \"#,\n    PropertyId::BackgroundPosition,\n    Some((\"20px 10px\", false)),\n  );\n  get_test(\n    r#\"\n    background: linear-gradient(red, green) 20px 10px;\n    \"#,\n    PropertyId::BackgroundPositionX,\n    Some((\"20px\", false)),\n  );\n  get_test(\n    r#\"\n    background: linear-gradient(red, green) 20px 10px;\n    \"#,\n    PropertyId::BackgroundPositionY,\n    Some((\"10px\", false)),\n  );\n  get_test(\n    \"mask-border: linear-gradient(red, green) 25\",\n    PropertyId::MaskBorderSource,\n    Some((\"linear-gradient(red, green)\", false)),\n  );\n  get_test(\"grid-area: a / b / c / d\", PropertyId::GridRowStart, Some((\"a\", false)));\n  get_test(\"grid-area: a / b / c / d\", PropertyId::GridRowEnd, Some((\"c\", false)));\n  get_test(\"grid-area: a / b / c / d\", PropertyId::GridRow, Some((\"a / c\", false)));\n  get_test(\n    \"grid-area: a / b / c / d\",\n    PropertyId::GridColumn,\n    Some((\"b / d\", false)),\n  );\n  get_test(\n    r#\"\n    grid-template-rows: auto 1fr;\n    grid-template-columns: auto 1fr auto;\n    grid-template-areas: none;\n    \"#,\n    PropertyId::GridTemplate,\n    Some((\"auto 1fr / auto 1fr auto\", false)),\n  );\n  get_test(\n    r#\"\n    grid-template-areas: \". a a .\"\n        \". b b .\";\n    grid-template-rows: auto 1fr;\n    grid-template-columns: 10px 1fr 1fr 10px;\n    \"#,\n    PropertyId::GridTemplate,\n    Some((\n      r#\"\n      \". a a .\"\n      \". b b .\" 1fr\n      / 10px 1fr 1fr 10px\n      \"#,\n      false,\n    )),\n  );\n  get_test(\n    r#\"\n    grid-template-areas: \"a a a\"\n                          \"b b b\";\n    grid-template-columns: repeat(3, 1fr);\n    grid-template-rows: auto 1fr;\n    \"#,\n    PropertyId::GridTemplate,\n    None,\n  );\n  get_test(\n    r#\"\n    grid-template-areas: \"a a a\"\n                         \"b b b\";\n    grid-template-rows: [header-top] auto [header-bottom main-top] 1fr [main-bottom];\n    grid-template-columns: auto 1fr auto;\n    grid-auto-flow: row;\n    grid-auto-rows: auto;\n    grid-auto-columns: auto;\n    \"#,\n    PropertyId::Grid,\n    Some((\n      r#\"\n      [header-top] \"a a a\" [header-bottom]\n      [main-top] \"b b b\" 1fr [main-bottom]\n      / auto 1fr auto\n      \"#,\n      false,\n    )),\n  );\n  get_test(\n    r#\"\n    grid-template-areas: \"a a a\"\n                         \"b b b\";\n    grid-template-rows: [header-top] auto [header-bottom main-top] 1fr [main-bottom];\n    grid-template-columns: auto 1fr auto;\n    grid-auto-flow: column;\n    grid-auto-rows: 1fr;\n    grid-auto-columns: 1fr;\n    \"#,\n    PropertyId::Grid,\n    None,\n  );\n  get_test(\n    r#\"\n    flex-direction: row;\n    flex-wrap: wrap;\n    \"#,\n    PropertyId::FlexFlow(VendorPrefix::None),\n    Some((\"row wrap\", false)),\n  );\n  get_test(\n    r#\"\n    -webkit-flex-direction: row;\n    -webkit-flex-wrap: wrap;\n    \"#,\n    PropertyId::FlexFlow(VendorPrefix::WebKit),\n    Some((\"row wrap\", false)),\n  );\n  get_test(\n    r#\"\n    flex-direction: row;\n    flex-wrap: wrap;\n    \"#,\n    PropertyId::FlexFlow(VendorPrefix::WebKit),\n    None,\n  );\n  get_test(\n    r#\"\n    -webkit-flex-direction: row;\n    flex-wrap: wrap;\n    \"#,\n    PropertyId::FlexFlow(VendorPrefix::WebKit),\n    None,\n  );\n  get_test(\n    r#\"\n    -webkit-flex-direction: row;\n    flex-wrap: wrap;\n    \"#,\n    PropertyId::FlexFlow(VendorPrefix::None),\n    None,\n  );\n  get_test(\n    r#\"\n    -webkit-flex-flow: row;\n    \"#,\n    PropertyId::FlexDirection(VendorPrefix::WebKit),\n    Some((\"row\", false)),\n  );\n  get_test(\n    r#\"\n    -webkit-flex-flow: row;\n    \"#,\n    PropertyId::FlexDirection(VendorPrefix::None),\n    None,\n  );\n}\n\nfn set_test(orig: &str, property: &str, value: &str, important: bool, expected: &str) {\n  let mut decls = DeclarationBlock::parse_string(orig, ParserOptions::default()).unwrap();\n  decls.set(\n    Property::parse_string(property.into(), value, ParserOptions::default()).unwrap(),\n    important,\n  );\n  assert_eq!(decls.to_css_string(PrinterOptions::default()).unwrap(), expected);\n}\n\n#[test]\nfn test_set() {\n  set_test(\"color: red\", \"color\", \"green\", false, \"color: green\");\n  set_test(\"color: red !important\", \"color\", \"green\", false, \"color: green\");\n  set_test(\"color: red\", \"color\", \"green\", true, \"color: green !important\");\n  set_test(\"margin: 5px\", \"margin\", \"10px\", false, \"margin: 10px\");\n  set_test(\"margin: 5px\", \"margin-top\", \"8px\", false, \"margin: 8px 5px 5px\");\n  set_test(\n    \"margin: 5px\",\n    \"margin-inline-start\",\n    \"8px\",\n    false,\n    \"margin: 5px; margin-inline-start: 8px\",\n  );\n  set_test(\n    \"margin-inline-start: 5px; margin-top: 10px\",\n    \"margin-inline-start\",\n    \"8px\",\n    false,\n    \"margin-inline-start: 5px; margin-top: 10px; margin-inline-start: 8px\",\n  );\n  set_test(\n    \"margin: 5px; margin-inline-start: 8px\",\n    \"margin-left\",\n    \"10px\",\n    false,\n    \"margin: 5px; margin-inline-start: 8px; margin-left: 10px\",\n  );\n  set_test(\n    \"border: 1px solid red\",\n    \"border-right\",\n    \"1px solid green\",\n    false,\n    \"border: 1px solid red; border-right: 1px solid green\",\n  );\n  set_test(\n    \"border: 1px solid red\",\n    \"border-right-color\",\n    \"green\",\n    false,\n    \"border: 1px solid red; border-right-color: green\",\n  );\n  set_test(\n    \"animation: foo 2s\",\n    \"animation-name\",\n    \"foo, bar\",\n    false,\n    \"animation: 2s foo; animation-name: foo, bar\",\n  );\n  set_test(\"animation: foo 2s\", \"animation-name\", \"bar\", false, \"animation: 2s bar\");\n  set_test(\n    \"background: linear-gradient(red, green)\",\n    \"background-position-x\",\n    \"20px\",\n    false,\n    \"background: linear-gradient(red, green) 20px 0\",\n  );\n  set_test(\n    \"background: linear-gradient(red, green)\",\n    \"background-position\",\n    \"20px 10px\",\n    false,\n    \"background: linear-gradient(red, green) 20px 10px\",\n  );\n  set_test(\n    \"flex-flow: row wrap\",\n    \"flex-direction\",\n    \"column\",\n    false,\n    \"flex-flow: column wrap\",\n  );\n  set_test(\n    \"-webkit-flex-flow: row wrap\",\n    \"-webkit-flex-direction\",\n    \"column\",\n    false,\n    \"-webkit-flex-flow: column wrap\",\n  );\n  set_test(\n    \"flex-flow: row wrap\",\n    \"-webkit-flex-direction\",\n    \"column\",\n    false,\n    \"flex-flow: wrap; -webkit-flex-direction: column\",\n  );\n}\n\nfn remove_test(orig: &str, property_id: PropertyId, expected: &str) {\n  let mut decls = DeclarationBlock::parse_string(orig, ParserOptions::default()).unwrap();\n  decls.remove(&property_id);\n  assert_eq!(decls.to_css_string(PrinterOptions::default()).unwrap(), expected);\n}\n\n#[test]\nfn test_remove() {\n  remove_test(\"margin-top: 10px\", PropertyId::MarginTop, \"\");\n  remove_test(\n    \"margin-top: 10px; margin-left: 5px\",\n    PropertyId::MarginTop,\n    \"margin-left: 5px\",\n  );\n  remove_test(\n    \"margin-top: 10px !important; margin-left: 5px\",\n    PropertyId::MarginTop,\n    \"margin-left: 5px\",\n  );\n  remove_test(\n    \"margin: 10px\",\n    PropertyId::MarginTop,\n    \"margin-right: 10px; margin-bottom: 10px; margin-left: 10px\",\n  );\n  remove_test(\"margin: 10px\", PropertyId::Margin, \"\");\n  remove_test(\n    \"margin-top: 10px; margin-right: 10px; margin-bottom: 10px; margin-left: 10px\",\n    PropertyId::Margin,\n    \"\",\n  );\n  remove_test(\n    \"flex-flow: column wrap\",\n    PropertyId::FlexDirection(VendorPrefix::None),\n    \"flex-wrap: wrap\",\n  );\n  remove_test(\n    \"flex-flow: column wrap\",\n    PropertyId::FlexDirection(VendorPrefix::WebKit),\n    \"flex-flow: column wrap\",\n  );\n  remove_test(\n    \"-webkit-flex-flow: column wrap\",\n    PropertyId::FlexDirection(VendorPrefix::WebKit),\n    \"-webkit-flex-wrap: wrap\",\n  );\n}\n"
  },
  {
    "path": "tests/test_custom_parser.rs",
    "content": "use cssparser::*;\nuse lightningcss::{\n  declaration::DeclarationBlock,\n  error::{ParserError, PrinterError},\n  printer::Printer,\n  stylesheet::{ParserOptions, PrinterOptions, StyleSheet},\n  traits::{AtRuleParser, Parse, ToCss},\n  values::ident::Ident,\n};\n\nfn minify_test(source: &str, expected: &str) {\n  let mut stylesheet = StyleSheet::parse_with(&source, ParserOptions::default(), &mut TestAtRuleParser).unwrap();\n  stylesheet.minify(Default::default()).unwrap();\n  let res = stylesheet\n    .to_css(PrinterOptions {\n      minify: true,\n      ..PrinterOptions::default()\n    })\n    .unwrap();\n  assert_eq!(res.code, expected);\n}\n\n#[test]\nfn test_block() {\n  minify_test(\n    r#\"\n    @block test {\n      color: yellow;\n    }\n  \"#,\n    \"@block test{color:#ff0}\",\n  )\n}\n\n#[test]\nfn test_inline() {\n  minify_test(\n    r#\"\n    @inline test;\n    .foo {\n      color: yellow;\n    }\n  \"#,\n    \"@inline test;.foo{color:#ff0}\",\n  )\n}\n\nenum Prelude<'i> {\n  Block(Ident<'i>),\n  Inline(Ident<'i>),\n}\n\n#[derive(Debug, Clone)]\nenum AtRule<'i> {\n  Block(BlockRule<'i>),\n  Inline(InlineRule<'i>),\n}\n\n#[derive(Debug, Clone)]\nstruct BlockRule<'i> {\n  name: Ident<'i>,\n  declarations: DeclarationBlock<'i>,\n}\n\n#[derive(Debug, Clone)]\nstruct InlineRule<'i> {\n  name: Ident<'i>,\n}\n\n#[derive(Default)]\nstruct TestAtRuleParser;\nimpl<'i> AtRuleParser<'i> for TestAtRuleParser {\n  type Prelude = Prelude<'i>;\n  type Error = ParserError<'i>;\n  type AtRule = AtRule<'i>;\n\n  fn parse_prelude<'t>(\n    &mut self,\n    name: CowRcStr<'i>,\n    input: &mut Parser<'i, 't>,\n    _options: &ParserOptions<'_, 'i>,\n  ) -> Result<Self::Prelude, ParseError<'i, Self::Error>> {\n    let location = input.current_source_location();\n    match_ignore_ascii_case! {&*name,\n      \"block\" => {\n        let name = Ident::parse(input)?;\n        Ok(Prelude::Block(name))\n      },\n      \"inline\" => {\n        let name = Ident::parse(input)?;\n        Ok(Prelude::Inline(name))\n      },\n      _ => Err(location.new_unexpected_token_error(\n        cssparser::Token::Ident(name.clone())\n      ))\n    }\n  }\n\n  fn rule_without_block(\n    &mut self,\n    prelude: Self::Prelude,\n    _start: &ParserState,\n    _options: &ParserOptions<'_, 'i>,\n    _is_nested: bool,\n  ) -> Result<Self::AtRule, ()> {\n    match prelude {\n      Prelude::Inline(name) => Ok(AtRule::Inline(InlineRule { name })),\n      _ => unreachable!(),\n    }\n  }\n\n  fn parse_block<'t>(\n    &mut self,\n    prelude: Self::Prelude,\n    _start: &ParserState,\n    input: &mut Parser<'i, 't>,\n    _options: &ParserOptions<'_, 'i>,\n    _is_nested: bool,\n  ) -> Result<Self::AtRule, ParseError<'i, Self::Error>> {\n    match prelude {\n      Prelude::Block(name) => Ok(AtRule::Block(BlockRule {\n        name,\n        declarations: DeclarationBlock::parse(input, &ParserOptions::default())?,\n      })),\n      _ => unreachable!(),\n    }\n  }\n}\n\nimpl<'i> ToCss for AtRule<'i> {\n  fn to_css<W: std::fmt::Write>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError> {\n    match self {\n      AtRule::Block(rule) => {\n        dest.write_str(\"@block \")?;\n        rule.name.to_css(dest)?;\n        rule.declarations.to_css_block(dest)\n      }\n      AtRule::Inline(rule) => {\n        dest.write_str(\"@inline \")?;\n        rule.name.to_css(dest)?;\n        dest.write_char(';')\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "tests/test_serde.rs",
    "content": "#[cfg(feature = \"serde\")]\nuse lightningcss::stylesheet::{ParserOptions, StyleSheet};\n\n#[cfg(feature = \"serde\")]\n#[test]\nfn test_serde() {\n  let code = r#\"\n    .foo {\n      color: red;\n    }\n  \"#;\n  let (json, stylesheet) = {\n    let stylesheet = StyleSheet::parse(code, ParserOptions::default()).unwrap();\n    let json = serde_json::to_string(&stylesheet).unwrap();\n    (json, stylesheet)\n  };\n\n  let deserialized: StyleSheet = serde_json::from_str(&json).unwrap();\n  assert_eq!(&deserialized.rules, &stylesheet.rules);\n}\n"
  },
  {
    "path": "tests/testdata/a.css",
    "content": "@import \"b.css\";\n\n.a {\n  width: 32px;\n}\n"
  },
  {
    "path": "tests/testdata/apply.css",
    "content": "@import \"./mixin.css\";\n\n.foo {\n  @apply color;\n}\n"
  },
  {
    "path": "tests/testdata/b.css",
    "content": ".b {\n  height: calc(100vh - 64px);\n}\n"
  },
  {
    "path": "tests/testdata/baz.css",
    "content": ".baz { color: blue; }"
  },
  {
    "path": "tests/testdata/foo.css",
    "content": "@import 'root:hello/world.css';\n\n.foo { color: red; }\n"
  },
  {
    "path": "tests/testdata/has_external.css",
    "content": "@import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap');\n@import './does_not_exist.css';\n@import './b.css';\n"
  },
  {
    "path": "tests/testdata/hello/world.css",
    "content": "@import 'root:baz.css';\n\n.bar { color: green; }\n"
  },
  {
    "path": "tests/testdata/mixin.css",
    "content": "@mixin color {\n  color: red;\n\n  &.bar {\n    color: yellow;\n  }\n}\n"
  },
  {
    "path": "wasm/.gitignore",
    "content": "*.d.ts\n*.wasm\n*.cjs\npackage.json\nREADME.md\nbrowserslistToTargets.js\nflags.js\ncomposeVisitors.js\n"
  },
  {
    "path": "wasm/async.mjs",
    "content": "let cur_await_promise_sync;\nexport function await_promise_sync(promise_addr, result_addr, error_addr) {\n  cur_await_promise_sync(promise_addr, result_addr, error_addr);\n}\n\nconst State = {\n  None: 0,\n  Unwinding: 1,\n  Rewinding: 2\n};\n\n// This uses Binaryen's Asyncify transform to suspend native code execution while a promise is resolving.\n// That allows synchronous Rust code to call async JavaScript functions without multi-threading.\n// When Rust wants to await a promise, it calls await_promise_sync, which saves the stack state and unwinds.\n// That causes the bundle function to return early. If a promise has been queued, we can then await it\n// and \"rewind\" the function back to where it was before by calling it again. This time the result of\n// the promise can be returned, and the function can continue where it left off.\n// See the docs in https://github.com/WebAssembly/binaryen/blob/main/src/passes/Asyncify.cpp\n// The code here is also partially based on https://github.com/GoogleChromeLabs/asyncify\nexport function createBundleAsync(env) {\n  let {instance, exports} = env;\n  let {asyncify_get_state, asyncify_start_unwind, asyncify_stop_unwind, asyncify_start_rewind, asyncify_stop_rewind} = instance.exports;\n\n  // allocate __asyncify_data\n  // Stack data goes right after the initial descriptor.\n  let DATA_ADDR = instance.exports.napi_wasm_malloc(8 + 4096);\n  let DATA_START = DATA_ADDR + 8;\n  let DATA_END = DATA_ADDR + 8 + 4096;\n  new Int32Array(env.memory.buffer, DATA_ADDR).set([DATA_START, DATA_END]);\n\n  function assertNoneState() {\n    if (asyncify_get_state() !== State.None) {\n      throw new Error(`Invalid async state ${asyncify_get_state()}, expected 0.`);\n    }\n  }\n\n  let promise, result, error;\n  cur_await_promise_sync = (promise_addr, result_addr, error_addr) => {\n    let state = asyncify_get_state();\n    if (state === State.Rewinding) {\n      asyncify_stop_rewind();\n      if (result != null) {\n        env.createValue(result, result_addr);\n      }\n      if (error != null) {\n        env.createValue(error, error_addr);\n      }\n      promise = result = error = null;\n      return;\n    }\n    assertNoneState();\n    promise = env.get(promise_addr);\n    asyncify_start_unwind(DATA_ADDR);\n  };\n\n  return async function bundleAsync(options) {\n    assertNoneState();\n    let res = exports.bundle(options);\n    while (asyncify_get_state() === State.Unwinding) {\n      asyncify_stop_unwind();\n      try {\n        result = await promise;\n      } catch (err) {\n        error = err;\n      }\n      assertNoneState();\n      asyncify_start_rewind(DATA_ADDR);\n      res = exports.bundle(options);\n    }\n\n    assertNoneState();\n    return res;\n  };\n}\n"
  },
  {
    "path": "wasm/import.meta.url-polyfill.js",
    "content": "/**\n * @see https://github.com/evanw/esbuild/issues/1633\n */\nexport const import_meta_url =\n  typeof document === 'undefined' ? new (require('url'.replace('', '')).URL)('file:' + __filename).href :\n    (document.currentScript && document.currentScript.src || new URL('main.js', document.baseURI).href)\n"
  },
  {
    "path": "wasm/index.mjs",
    "content": "import { Environment, napi } from 'napi-wasm';\nimport { await_promise_sync, createBundleAsync } from './async.mjs';\n\nlet wasm, initPromise, bundleAsyncInternal;\n\nexport default async function init(input) {\n  if (wasm) return;\n  if (initPromise) {\n    await initPromise;\n    return;\n  }\n\n  input = input ?? new URL('lightningcss_node.wasm', import.meta.url);\n  if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) {\n    input = fetchOrReadFromFs(input);\n  }\n\n  let env;\n  initPromise = input\n    .then(input => load(input, {\n      env: {\n        ...napi,\n        await_promise_sync,\n        __getrandom_v03_custom: (ptr, len) => {\n          let buf = env.memory.subarray(ptr, ptr + len);\n          crypto.getRandomValues(buf);\n        },\n      }\n    }))\n    .then(({instance}) => {\n      instance.exports.register_module();\n      env = new Environment(instance);\n      bundleAsyncInternal = createBundleAsync(env);\n      wasm = env.exports;\n    });\n\n  await initPromise;\n}\n\nexport function transform(options) {\n  return wrap(wasm.transform, options);\n}\n\nexport function transformStyleAttribute(options) {\n  return wrap(wasm.transformStyleAttribute, options);\n}\n\nexport function bundle(options) {\n  return wrap(wasm.bundle, options);\n}\n\nexport function bundleAsync(options) {\n  return wrap(bundleAsyncInternal, options);\n}\n\nfunction wrap(call, options) {\n  if (typeof options.visitor === 'function') {\n    let deps = [];\n    options.visitor = options.visitor({\n      addDependency(dep) {\n        deps.push(dep);\n      }\n    });\n\n    let result = call(options);\n    if (result instanceof Promise) {\n      result = result.then(res => {\n        if (deps.length) {\n          res.dependencies ??= [];\n          res.dependencies.push(...deps);\n        }\n        return res;\n      });\n    } else if (deps.length) {\n      result.dependencies ??= [];\n      result.dependencies.push(...deps);\n    }\n    return result;\n  } else {\n    return call(options);\n  }\n}\n\nexport { browserslistToTargets } from './browserslistToTargets.js';\nexport { Features } from './flags.js';\nexport { composeVisitors } from './composeVisitors.js';\n\nasync function load(module, imports) {\n  if (typeof Response === 'function' && module instanceof Response) {\n    if (typeof WebAssembly.instantiateStreaming === 'function') {\n      try {\n        return await WebAssembly.instantiateStreaming(module, imports);\n      } catch (e) {\n        if (module.headers.get('Content-Type') != 'application/wasm') {\n          console.warn(\"`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\\n\", e);\n        } else {\n          throw e;\n        }\n      }\n    }\n\n    const bytes = await module.arrayBuffer();\n    return await WebAssembly.instantiate(bytes, imports);\n  } else {\n    const instance = await WebAssembly.instantiate(module, imports);\n    if (instance instanceof WebAssembly.Instance) {\n      return { instance, module };\n    } else {\n      return instance;\n    }\n  }\n}\n\nasync function fetchOrReadFromFs(inputPath) {\n  try {\n    const fs = await import('fs');\n    return fs.readFileSync(inputPath);\n  } catch {\n    return fetch(inputPath);\n  }\n};\n"
  },
  {
    "path": "wasm/wasm-node.mjs",
    "content": "import { Environment, napi } from 'napi-wasm';\nimport { await_promise_sync, createBundleAsync } from './async.mjs';\nimport fs from 'fs';\nimport {webcrypto as crypto} from 'node:crypto';\n\nlet wasmBytes = fs.readFileSync(new URL('lightningcss_node.wasm', import.meta.url));\nlet wasmModule = new WebAssembly.Module(wasmBytes);\nlet instance = new WebAssembly.Instance(wasmModule, {\n  env: {\n    ...napi,\n    await_promise_sync,\n    __getrandom_v03_custom: (ptr, len) => {\n      let buf = env.memory.subarray(ptr, ptr + len);\n      crypto.getRandomValues(buf);\n    },\n  },\n});\ninstance.exports.register_module();\nlet env = new Environment(instance);\nlet wasm = env.exports;\nlet bundleAsyncInternal = createBundleAsync(env);\n\nexport default async function init() {\n  // do nothing. for backward compatibility.\n}\n\nexport function transform(options) {\n  return wrap(wasm.transform, options);\n}\n\nexport function transformStyleAttribute(options) {\n  return wrap(wasm.transformStyleAttribute, options);\n}\n\nexport function bundle(options) {\n  return wrap(wasm.bundle, {\n    ...options,\n    resolver: {\n      read: (filePath) => fs.readFileSync(filePath, 'utf8')\n    }\n  });\n}\n\nexport async function bundleAsync(options) {\n  if (!options.resolver?.read) {\n    options.resolver = {\n      ...options.resolver,\n      read: (filePath) => fs.readFileSync(filePath, 'utf8')\n    };\n  }\n\n  return wrap(bundleAsyncInternal, options);\n}\n\nfunction wrap(call, options) {\n  if (typeof options.visitor === 'function') {\n    let deps = [];\n    options.visitor = options.visitor({\n      addDependency(dep) {\n        deps.push(dep);\n      }\n    });\n\n    let result = call(options);\n    if (result instanceof Promise) {\n      result = result.then(res => {\n        if (deps.length) {\n          res.dependencies ??= [];\n          res.dependencies.push(...deps);\n        }\n        return res;\n      });\n    } else if (deps.length) {\n      result.dependencies ??= [];\n      result.dependencies.push(...deps);\n    }\n    return result;\n  } else {\n    return call(options);\n  }\n}\n\nexport { browserslistToTargets } from './browserslistToTargets.js'\nexport { Features } from './flags.js'\nexport { composeVisitors } from './composeVisitors.js';\n"
  },
  {
    "path": "website/.posthtmlrc",
    "content": "{\n  \"plugins\": {\n    \"posthtml-include\": {},\n    \"posthtml-markdownit\": {\n      \"markdownit\": {\n        \"html\": true\n      },\n      \"plugins\": [\n        {\n          \"plugin\": \"markdown-it-anchor\"\n        },\n        {\n          \"plugin\": \"markdown-it-table-of-contents\",\n          \"options\": {\n            \"containerHeaderHtml\": \"<h3>On this page</h3>\",\n            \"includeLevel\": [\n              2,\n              3\n            ]\n          }\n        },\n        {\n          \"plugin\": \"markdown-it-prism\"\n        }\n      ]\n    }\n  }\n}"
  },
  {
    "path": "website/bundling.html",
    "content": "<include src=\"website/include/layout.html\" locals='{\"title\": \"Bundling\", \"url\": \"bundling.html\", \"page\": \"website/pages/bundling.md\"}' />\n"
  },
  {
    "path": "website/css-modules.html",
    "content": "<include src=\"website/include/layout.html\" locals='{\"title\": \"CSS Modules\", \"url\": \"css-modules.html\", \"page\": \"website/pages/css-modules.md\"}' />\n"
  },
  {
    "path": "website/docs.css",
    "content": "@import \"synthwave.css\";\n\nhtml {\n  color-scheme: dark;\n  background: #111;\n  font-family: system-ui;\n  --gold: lch(80% 82.34 80.104);\n  --gold-text: lch(85% 82.34 80.104);\n  --gold-shadow: lch(80% 82.34 80.104 / .7);\n}\n\n@font-face {\n  font-family:\"din-1451-lt-pro-engschrift\";\n  src:url(\"https://use.typekit.net/af/7fa6e1/00000000000000007735bbcd/30/l?primer=388f68b35a7cbf1ee3543172445c23e26935269fadd3b392a13ac7b2903677eb&fvd=n4&v=3\") format(\"woff2\"),url(\"https://use.typekit.net/af/7fa6e1/00000000000000007735bbcd/30/d?primer=388f68b35a7cbf1ee3543172445c23e26935269fadd3b392a13ac7b2903677eb&fvd=n4&v=3\") format(\"woff\"),url(\"https://use.typekit.net/af/7fa6e1/00000000000000007735bbcd/30/a?primer=388f68b35a7cbf1ee3543172445c23e26935269fadd3b392a13ac7b2903677eb&fvd=n4&v=3\") format(\"opentype\");\n  font-display:auto;font-style:normal;font-weight:400;font-stretch:normal;\n}\n\n@font-face {\n  font-family:\"urbane-rounded\";\n  src:url(\"https://use.typekit.net/af/916187/00000000000000007735bfa0/30/l?primer=81a69539b194230396845be9681d114557adfb35f4cccc679c164afb4aa47365&fvd=n6&v=3\") format(\"woff2\"),url(\"https://use.typekit.net/af/916187/00000000000000007735bfa0/30/d?primer=81a69539b194230396845be9681d114557adfb35f4cccc679c164afb4aa47365&fvd=n6&v=3\") format(\"woff\"),url(\"https://use.typekit.net/af/916187/00000000000000007735bfa0/30/a?primer=81a69539b194230396845be9681d114557adfb35f4cccc679c164afb4aa47365&fvd=n6&v=3\") format(\"opentype\");\n  font-display:auto;font-style:normal;font-weight:600;font-stretch:normal;\n}\n\nheader {\n  max-width: 800px;\n  width: 100%;\n  margin: 0 auto;\n  padding: 50px 0;\n  font-size: 16px;\n  background: radial-gradient(closest-side, lch(80% 82.34 80.104 / .25), transparent);\n  display: grid;\n  column-gap: 30px;\n  grid-area: header;\n  grid-template-areas: \"logo header\"\n                        \"logo subheader\"\n                        \". links\";\n}\n\nheader svg {\n  filter: drop-shadow(0 0 5px var(--gold-shadow)) drop-shadow(0 0 15px var(--gold-shadow));\n  grid-area: logo;\n  place-self: center end;\n  width: 50px;\n}\n\nheader svg .outer {\n  stroke-width: 30px;\n  stroke: var(--gold);\n}\n\nheader svg .inner {\n  fill: lch(100% 82.34 80.104);\n}\n\nheader .title {\n  font-family: urbane-rounded, ui-rounded;\n  font-size: 60px;\n  font-weight: 600;\n  -webkit-text-stroke: 2px var(--gold-text);\n  color: transparent;\n  filter: drop-shadow(0 0 3px var(--gold-shadow)) drop-shadow(0 0 10px var(--gold));\n  margin: 0;\n  letter-spacing: -0.02em;\n  text-decoration: none;\n}\n\nheader .title::selection {\n  -webkit-text-stroke-color: #fffddd;\n  background-color: var(--gold-text);\n}\n\nh1, h2, h3 {\n  font-family: urbane-rounded, ui-rounded;\n  font-weight: 600;\n  color: lch(65% 85 35);\n  margin: 2em 0 .5em 0;\n  letter-spacing: -0.02em;\n}\n\nh1 {\n  margin-top: 0;\n}\n\nheader p {\n  grid-area: links;\n  margin: 0;\n}\n\nheader p a {\n  font-family: urbane-rounded, ui-rounded;\n  font-weight: 600;\n  font-size: 1em;\n  color: lch(90% 50.34 80.104);\n  filter: drop-shadow(0 0 8px lch(90% 50.34 80.104 / .7));\n  text-decoration-color: lch(90% 50.34 80.104 / 0);\n  text-decoration-style: wavy;\n  text-decoration-thickness: 2px;\n  text-underline-offset: 2px;\n  text-decoration-skip-ink: none;\n  transition: text-decoration-color 150ms;\n}\n\nheader a:hover {\n  text-decoration-color: lch(90% 50.34 80.104);\n}\n\n@media (width < 500px) {\n  header {\n    grid-template-areas: \"logo\"\n                          \"header\"\n                          \"subheader\"\n                          \"links\";\n    place-items: center;\n    text-align: center;\n    gap: 8px;\n  }\n  header .title {\n    font-size: 38px;\n    -webkit-text-stroke-width: 1.5px;\n    padding: 0;\n  }\n\n  header h2 {\n    font-size: 14px;\n  }\n\n  header p a {\n    font-size: 13px;\n  }\n\n  header svg {\n    place-self: center;\n  }\n}\n\nbody {\n  --body-padding: 20px;\n  padding: 0 var(--body-padding);\n  margin: 0 auto;\n  width: fit-content;\n  display: grid;\n  grid-template-columns: 180px 1fr;\n  gap: 40px;\n  grid-template-areas: \"header header\"\n                       \"nav    main\"\n                       \"footer footer\";\n}\n\nmain {\n  max-width: 800px;\n  padding-right: 240px;\n  grid-area: main;\n  position: relative;\n}\n\np, li {\n  line-height: 1.5em;\n}\n\np:empty {\n  display: none;\n}\n\na {\n  color: lch(85% 58 205);\n}\n\nnav {\n  grid-area: nav;\n  text-align: end;\n  padding-right: 20px;\n  border-right: 1px solid lch(90% 50.34 80.104 / .1);\n  height: fit-content;\n  position: sticky;\n  top: 40px;\n}\n\nnav h3,\n.table-of-contents h3 {\n  margin-top: 0;\n}\n\nmain > aside {\n  position: sticky;\n  top: 40px;\n}\n\n.table-of-contents {\n  position: absolute;\n  left: 100%;\n  margin-left: 40px;\n  border-left: 1px solid lch(90% 50.34 80.104 / .1);\n  padding-left: 20px;\n  overflow: auto;\n  max-height: calc(100vh - 80px);\n}\n\n.table-of-contents ul,\nnav ul {\n  list-style: none;\n  padding-left: 2ch;\n}\n\n.table-of-contents > ul {\n  margin: 0;\n  padding: 0;\n  width: 180px;\n}\n\nnav > ul {\n  margin: 0;\n  padding: 0;\n}\n\n.table-of-contents li,\nnav li {\n  margin: 6px 0;\n  line-height: 1em;\n}\n\n.table-of-contents a,\nnav a {\n  color: lch(90% 50.34 80.104);\n  text-decoration: none;\n  font-family: urbane-rounded;\n  font-size: 14px;\n}\n\n.table-of-contents a:hover,\n.table-of-contents a[aria-current],\nnav a:hover,\nnav a[aria-current] {\n  color: var(--gold-text);\n}\n\na[aria-current] {\n  text-decoration: underline;\n}\n\n.features {\n  column-count: 2;\n}\n\n@media (width < 1040px) {\n  .table-of-contents {\n    display: none;\n  }\n\n  main {\n    padding-right: 0;\n  }\n\n  .features {\n    column-count: 1;\n  }\n}\n\n@media (width < 600px) {\n  body {\n    display: block;\n    width: auto;\n  }\n\n  nav {\n    text-align: start;\n    border-right: none;\n    border-bottom: 1px solid lch(90% 50.34 80.104 / .1);\n    padding-bottom: 20px;\n    position: static;\n  }\n}\n\n.warning {\n  border: 4px solid lch(70% 82.34 80.104);\n  background: lch(80% 82.34 80.104 / .15);\n  padding: 20px;\n  border-radius: 8px;\n  margin: 20px 0;\n}\n\n.warning > :first-child {\n  margin-top: 0;\n}\n\n.warning > :last-child {\n  margin-bottom: 0;\n}\n\n.warning pre {\n  background: rgb(0 0 0 / .65);\n}\n\n.warning :is(h1, h2, h3) {\n  color: white;\n}\n\nfooter {\n  font-size: 12px;\n  color: #666;\n  text-align: center;\n  padding-bottom: 20px;\n  grid-area: footer;\n}\n"
  },
  {
    "path": "website/docs.html",
    "content": "<include src=\"website/include/layout.html\" locals='{\"title\": \"Getting Started\", \"url\": \"docs.html\", \"page\": \"website/pages/docs.md\"}' />\n"
  },
  {
    "path": "website/docs.js",
    "content": "// Mark the current section in the table of contents with aria-current when scrolled into view.\nlet tocLinks = document.querySelectorAll('.table-of-contents a');\nlet headers = new Map();\nfor (let link of tocLinks) {\n  let headerId = link.hash.slice(1);\n  let header = document.getElementById(headerId);\n  headers.set(header, link);\n}\n\nlet intersectingHeaders = new Set();\nlet observer = new IntersectionObserver(entries => {\n  for (let entry of entries) {\n    if (entry.isIntersecting) {\n      intersectingHeaders.add(entry.target);\n    } else {\n      intersectingHeaders.delete(entry.target);\n    }\n  }\n\n  if (intersectingHeaders.size > 0) {\n    let current = document.querySelector('.table-of-contents a[aria-current]');\n    if (current) {\n      current.removeAttribute('aria-current');\n    }\n    let first;\n    for (let [header, link] of headers) {\n      if (intersectingHeaders.has(header)) {\n        first = link;\n        break;\n      }\n    }\n    first.setAttribute('aria-current', 'location');\n  }\n});\n\nfor (let header of headers.keys()) {\n  observer.observe(header);\n}\n"
  },
  {
    "path": "website/include/layout.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, viewport-fit=cover\">\n    <title>{{ title }} – Lightning CSS</title>\n    <link rel=\"icon\" href=\"favicon.svg\">\n    <link rel=\"mask-icon\" href=\"favicon.svg\" color=\"#f9bb03\">\n    <meta name=\"description\" content=\"An extremely fast CSS parser, transformer, bundler, and minifier.\">\n    <meta name=\"twitter:card\" content=\"summary_large_image\">\n    <meta name=\"twitter:image\" content=\"og.jpeg\">\n    <meta name=\"twitter:site\" content=\"@lightningcss\">\n    <meta name=\"twitter:creator\" content=\"@lightningcss\">\n    <meta property=\"og:type\" content=\"website\">\n    <meta property=\"og:locale\" content=\"en_US\">\n    <meta property=\"og:url\" content=\"https://lightningcss.dev/{{ url }}\">\n    <meta property=\"og:title\" content=\"{{ title }} – Lightning CSS\">\n    <meta property=\"og:description\" content=\"An extremely fast CSS parser, transformer, bundler, and minifier.\">\n    <meta property=\"og:image\" content=\"og.jpeg\">\n    <link rel=\"stylesheet\" href=\"docs.css\">\n  </head>\n  <body>\n    <header>\n      <svg viewBox=\"495 168 360 654\">\n        <path class=\"outer\" d=\"M594.41,805c-.71,0-1.43-.15-2.11-.47-2.2-1.03-3.34-3.48-2.72-5.83l67.98-253.71h-140.45c-1.86,0-3.57-1.04-4.44-2.69-.86-1.65-.73-3.65,.34-5.18l26.85-38.35q25.56-36.51,104.91-149.83l106.31-151.82c1.39-1.99,4.01-2.69,6.21-1.66,2.2,1.03,3.34,3.48,2.72,5.83l-67.98,253.71h140.45c1.86,0,3.57,1.04,4.43,2.69,.86,1.65,.73,3.65-.34,5.18l-238.07,340c-.96,1.37-2.51,2.13-4.1,2.13Zm-67.69-270h137.37c1.55,0,3.02,.72,3.97,1.96,.95,1.23,1.27,2.84,.86,4.34l-62.33,232.61,216.29-308.9h-137.36c-1.55,0-3.02-.72-3.97-1.96-.95-1.23-1.27-2.84-.86-4.34l62.33-232.61-90.04,128.59q-79.35,113.32-104.91,149.83l-21.34,30.48Z\"/>\n        <path class=\"inner\" d=\"M594.41,805c-.71,0-1.43-.15-2.11-.47-2.2-1.03-3.34-3.48-2.72-5.83l67.98-253.71h-140.45c-1.86,0-3.57-1.04-4.44-2.69-.86-1.65-.73-3.65,.34-5.18l26.85-38.35q25.56-36.51,104.91-149.83l106.31-151.82c1.39-1.99,4.01-2.69,6.21-1.66,2.2,1.03,3.34,3.48,2.72,5.83l-67.98,253.71h140.45c1.86,0,3.57,1.04,4.43,2.69,.86,1.65,.73,3.65-.34,5.18l-238.07,340c-.96,1.37-2.51,2.13-4.1,2.13Zm-67.69-270h137.37c1.55,0,3.02,.72,3.97,1.96,.95,1.23,1.27,2.84,.86,4.34l-62.33,232.61,216.29-308.9h-137.36c-1.55,0-3.02-.72-3.97-1.96-.95-1.23-1.27-2.84-.86-4.34l62.33-232.61-90.04,128.59q-79.35,113.32-104.91,149.83l-21.34,30.48Z\"/>\n      </svg>\n      <a href=\"/\" class=\"title\">Lightning CSS</a>\n      <p><a href=\"./playground/index.html\">Playground</a> • <a href=\"docs.html\">Docs</a> • <a href=\"https://docs.rs/lightningcss\" target=\"_blank\">Rust docs</a> • <a href=\"https://npmjs.com/lightningcss\" target=\"_blank\">npm</a>  • <a href=\"https://github.com/parcel-bundler/lightningcss\" target=\"_blank\">GitHub</a></p>\n    </header>\n    <nav>\n      <h3>Docs</h3>\n      <ul>\n        <li><a href=\"docs.html\">Getting started</a></li>\n        <li><a href=\"transpilation.html\">Transpilation</a></li>\n        <li><a href=\"css-modules.html\">CSS Modules</a></li>\n        <li><a href=\"bundling.html\">Bundling</a></li>\n        <li><a href=\"minification.html\">Minification</a></li>\n        <li><a href=\"transforms.html\">Custom transforms</a></li>\n        <script>document.querySelector(`nav a[href=\"${location.pathname}\"]`).setAttribute('aria-current', 'page')</script>\n      </ul>\n    </nav>\n    <main>\n      <markdown src=\"{{ page }}\"></markdown>\n    </main>\n    <footer>\n      Copyright © 2024 Devon Govett and Parcel Contributors.\n    </footer>\n    <script async src=\"docs.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "website/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, viewport-fit=cover\">\n    <title>Lightning CSS</title>\n    <link rel=\"icon\" href=\"favicon.svg\">\n    <link rel=\"mask-icon\" href=\"favicon.svg\" color=\"#f9bb03\">\n    <meta name=\"description\" content=\"An extremely fast CSS parser, transformer, bundler, and minifier.\">\n    <meta name=\"twitter:card\" content=\"summary_large_image\">\n    <meta name=\"twitter:image\" content=\"og.jpeg\">\n    <meta name=\"twitter:site\" content=\"@lightningcss\">\n    <meta name=\"twitter:creator\" content=\"@lightningcss\">\n    <meta property=\"og:type\" content=\"website\">\n    <meta property=\"og:locale\" content=\"en_US\">\n    <meta property=\"og:url\" content=\"https://lightningcss.dev\">\n    <meta property=\"og:title\" content=\"Lightning CSS\">\n    <meta property=\"og:description\" content=\"An extremely fast CSS parser, transformer, bundler, and minifier.\">\n    <meta property=\"og:image\" content=\"og.jpeg\">\n    <link rel=\"preload\" as=\"font\" type=\"font/woff2\" crossorigin href=\"https://use.typekit.net/af/916187/00000000000000007735bfa0/30/l?primer=81a69539b194230396845be9681d114557adfb35f4cccc679c164afb4aa47365&fvd=n6&v=3\">\n    <link rel=\"preload\" as=\"image\" href=\"lightspeed.svg\">\n  </head>\n  <body>\n    <header>\n      <svg viewBox=\"495 168 360 654\">\n        <path class=\"outer\" d=\"M594.41,805c-.71,0-1.43-.15-2.11-.47-2.2-1.03-3.34-3.48-2.72-5.83l67.98-253.71h-140.45c-1.86,0-3.57-1.04-4.44-2.69-.86-1.65-.73-3.65,.34-5.18l26.85-38.35q25.56-36.51,104.91-149.83l106.31-151.82c1.39-1.99,4.01-2.69,6.21-1.66,2.2,1.03,3.34,3.48,2.72,5.83l-67.98,253.71h140.45c1.86,0,3.57,1.04,4.43,2.69,.86,1.65,.73,3.65-.34,5.18l-238.07,340c-.96,1.37-2.51,2.13-4.1,2.13Zm-67.69-270h137.37c1.55,0,3.02,.72,3.97,1.96,.95,1.23,1.27,2.84,.86,4.34l-62.33,232.61,216.29-308.9h-137.36c-1.55,0-3.02-.72-3.97-1.96-.95-1.23-1.27-2.84-.86-4.34l62.33-232.61-90.04,128.59q-79.35,113.32-104.91,149.83l-21.34,30.48Z\"/>\n        <path class=\"inner\" d=\"M594.41,805c-.71,0-1.43-.15-2.11-.47-2.2-1.03-3.34-3.48-2.72-5.83l67.98-253.71h-140.45c-1.86,0-3.57-1.04-4.44-2.69-.86-1.65-.73-3.65,.34-5.18l26.85-38.35q25.56-36.51,104.91-149.83l106.31-151.82c1.39-1.99,4.01-2.69,6.21-1.66,2.2,1.03,3.34,3.48,2.72,5.83l-67.98,253.71h140.45c1.86,0,3.57,1.04,4.43,2.69,.86,1.65,.73,3.65-.34,5.18l-238.07,340c-.96,1.37-2.51,2.13-4.1,2.13Zm-67.69-270h137.37c1.55,0,3.02,.72,3.97,1.96,.95,1.23,1.27,2.84,.86,4.34l-62.33,232.61,216.29-308.9h-137.36c-1.55,0-3.02-.72-3.97-1.96-.95-1.23-1.27-2.84-.86-4.34l62.33-232.61-90.04,128.59q-79.35,113.32-104.91,149.83l-21.34,30.48Z\"/>\n      </svg>\n      <h1>Lightning CSS</h1>\n      <h2>An extremely fast CSS parser, transformer, bundler, and minifier.</h2>\n      <p><a href=\"./playground/index.html\">Playground</a> • <a href=\"docs.html\">Docs</a> • <a href=\"https://docs.rs/lightningcss\" target=\"_blank\">Rust docs</a> • <a href=\"https://npmjs.com/lightningcss\" target=\"_blank\">npm</a>  • <a href=\"https://github.com/parcel-bundler/lightningcss\" target=\"_blank\">GitHub</a></p>\n    </header>\n    <main>\n      <section class=\"warp\">\n        <h3>Light speed</h3>\n        <p><strong>Lightning CSS is over 100x faster than comparable JavaScript-based tools.</strong> It can minify over 2.7 million lines of code per second on a single thread.</p>\n        <p style=\"font-size: 0.85em\">Lightning CSS is written in Rust, a native systems programming language. It was built with performance in mind from the start, designed to make efficient use of memory, and limit AST passes.</p>\n        <p><a href=\"docs.html\">Get started →</a></p>\n        <figure>\n          <div class=\"chart\" role=\"img\" aria-label=\"Build time chart. CSSNano – 544ms, ESBuild – 17.2ms, Lightning CSS – 4.16ms\" style=\"max-width: 800px; margin-top: 40px; position: relative; padding-bottom: 110px; white-space: nowrap\">\n            <div style=\"text-align: center; font-weight: bold; font-size: 15px; margin-left: 90px; margin-bottom: 12px\">Build time</div>\n            <div style=\"position: absolute; left: 90px; right: 15px; height: 110px\">\n              <div style=\"position: absolute; top: 0; left: 0; bottom: 20px; width: 1px; background: white\"></div>\n              <div style=\"position: absolute; left: 0; bottom: 0; transform: translateX(-50%); font-size: 12px\">0ms</div>\n              <div class=\"line\" style=\"position: absolute; top: 0; left: 25%; bottom: 20px; width: 1px; background: white\"></div>\n              <div class=\"line\" style=\"position: absolute; left: 25%; bottom: 0; transform: translateX(-50%); font-size: 12px\">150ms</div>\n              <div style=\"position: absolute; top: 0; left: 50%; bottom: 20px; width: 1px; background: white\"></div>\n              <div style=\"position: absolute; left: 50%; bottom: 0; transform: translateX(-50%); font-size: 12px\">300ms</div>\n              <div class=\"line\" style=\"position: absolute; top: 0; left: 75%; bottom: 20px; width: 1px; background: white\"></div>\n              <div class=\"line\" style=\"position: absolute; left: 75%; bottom: 0; transform: translateX(-50%); font-size: 12px\">450ms</div>\n              <div style=\"position: absolute; top: 0; left: 100%; bottom: 20px; width: 1px; background: white\"></div>\n              <div style=\"position: absolute; left: 100%; bottom: 0; transform: translateX(-50%); font-size: 12px\">600ms</div>\n              \n              <div class=\"bars\" style=\"position: absolute; left: 1px; top: 0; right: 70px; --cssnano: 544.81; --esbuild: 17.2; --lightningcss: 4.16\">\n                <div style=\"position: absolute; top: 5px; left: 0; width: 100%; height: 20px; background: var(--gold)\"></div>\n                <div class=\"label\" style=\"position: absolute; left: calc(100% + 5px); top: 5px; line-height: 20px; font-size: 12px\">544.81ms</div>\n                <div style=\"position: absolute; top: 35px; left: 0; width: calc(1% * var(--esbuild) / var(--cssnano) * 100); height: 20px; background: var(--gold)\"></div>\n                <div style=\"position: absolute; left: calc(1% * var(--esbuild) / var(--cssnano) * 100 + 5px); top: 35px; line-height: 20px; font-size: 12px\">17.2ms</div>\n                <div style=\"position: absolute; top: 65px; left: 0; width: calc(1% * var(--lightningcss) / var(--cssnano) * 100); height: 20px; background: var(--gold)\"></div>\n                <div style=\"position: absolute; left: calc(1% * var(--lightningcss) / var(--cssnano) * 100 + 5px); top: 65px; line-height: 20px; font-size: 12px\">4.16ms</div>\n              </div>\n              <style>\n                @media (width < 700px) {\n                  .warp .bars {\n                    right: 5px !important;\n                  }\n\n                  .warp .bars .label {\n                    left: auto !important;\n                    right: 5px;\n                    color: black;\n                  }\n                }\n              </style>\n\n              <div style=\"position: absolute; right: calc(100% + 10px); top: 5px; line-height: 20px; font-size: 13px\">CSSNano</div>\n              <div style=\"position: absolute; right: calc(100% + 10px); top: 35px; line-height: 20px; font-size: 13px\">ESBuild</div>\n              <div style=\"position: absolute; left: -10px; transform: translateX(-100%); top: 65px; line-height: 20px; font-size: 13px\">Lightning CSS</div> \n            </div>\n          </div>\n          <figcaption>Time to minify Bootstrap 4 (~10,000 lines). See the <a href=\"https://github.com/parcel-bundler/lightningcss#benchmarks\" target=\"_blank\">readme</a> for more benchmarks.</figcaption>\n        </figure>\n      </section>\n      <section class=\"future\">\n        <div class=\"inner\">\n          <h3 class=\"title\">Live in the future</h3>\n          <div class=\"description\">\n            <p><strong>Lightning CSS lets you use modern CSS features and future syntax today.</strong> Features such as CSS nesting, custom media queries, high gamut color spaces, logical properties, and new selector features are automatically converted to more compatible syntax based on your browser targets.</p>\n            <p>Lightning CSS also automatically adds vendor prefixes for your browser targets, so you can keep your source code clean and repetition free.</p>\n            <p><a href=\"transpilation.html\">Learn more →</a></p>\n          </div>\n          <div class=\"example\">\n            <div class=\"targets\"><h4>Target Browsers</h4><code>last 2 versions</code></div>\n            <div class=\"box input\"><h4 class=\"title\">Input</h4><pre>\n<code><span class=\"class\">.foo</span> {\n  <span class=\"property\">color</span>: <span class=\"keyword\">oklab</span>(<span class=\"number\">59.686% 0.1009 0.1192</span>);\n}</code></pre></div>\n            <div class=\"box\"><h4 class=\"title\">Output</h4><pre>\n<code><span class=\"class\">.foo</span> {\n  <span class=\"property\">color</span>: <span class=\"number\">#c65d07</span>;\n  <span class=\"property\">color</span>: <span class=\"keyword\">color</span>(<span class=\"keyword\">display-p3</span> <span class=\"number\">.724144 .386777 .148795</span>);\n  <span class=\"property\">color</span>: <span class=\"keyword\">lab</span>(<span class=\"number\">52.2319% 40.1449 59.9171</span>);\n}</code></pre>\n            </div>\n          </div>\n        </div>\n      </section>\n      <section class=\"crush\">\n        <h3>Crush it!</h3>\n        <p style=\"font-weight: bold;\">Lightning CSS is not only fast when it comes to build time. It produces smaller output, so your website loads faster too.</p>\n        <p style=\"font-size: 0.85em\">The Lightning CSS minifier combines longhand properties into shorthands, removes unnecessary vendor prefixes, merges compatible adjacent rules, removes unnecessary default values, reduces <code>calc()</code> expressions, shortens colors, minifies gradients, and much more.</p>\n        <p><a href=\"minification.html\">Details →</a></p>\n        <figure>\n          <div class=\"chart\" role=\"img\" aria-label=\"Output size chart. CSSNano – 155.89 KB, ESBuild – 156.57 KB, Lightning CSS – 139.74 KB\" style=\"max-width: 800px; margin-top: 40px; position: relative; padding-bottom: 110px; white-space: nowrap\">\n            <div style=\"text-align: center; font-weight: bold; font-size: 15px; margin-left: 90px; margin-bottom: 12px\">Output size</div>\n            <div style=\"position: absolute; left: 90px; right: 15px; height: 110px\">\n              <div style=\"position: absolute; top: 0; left: 0; bottom: 20px; width: 1px; background: black\"></div>\n              <div style=\"position: absolute; left: 0; bottom: 0; transform: translateX(-50%); font-size: 12px\">0 KB</div>\n              <div class=\"line\" style=\"position: absolute; top: 0; left: 25%; bottom: 20px; width: 1px; background: black\"></div>\n              <div class=\"line\" style=\"position: absolute; left: 25%; bottom: 0; transform: translateX(-50%); font-size: 12px\">40 KB</div>\n              <div style=\"position: absolute; top: 0; left: 50%; bottom: 20px; width: 1px; background: black\"></div>\n              <div style=\"position: absolute; left: 50%; bottom: 0; transform: translateX(-50%); font-size: 12px\">80 KB</div>\n              <div class=\"line\" style=\"position: absolute; top: 0; left: 75%; bottom: 20px; width: 1px; background: black\"></div>\n              <div class=\"line\" style=\"position: absolute; left: 75%; bottom: 0; transform: translateX(-50%); font-size: 12px\">120 KB</div>\n              <div style=\"position: absolute; top: 0; left: 100%; bottom: 20px; width: 1px; background: black\"></div>\n              <div style=\"position: absolute; left: 100%; bottom: 0; transform: translateX(-50%); font-size: 12px\">160 KB</div>\n              \n              <div style=\"position: absolute; left: 1px; top: 0; right: 5px; --cssnano: 155.89; --esbuild: 156.57; --lightningcss: 139.74\">\n                <div style=\"position: absolute; top: 5px; left: 0; width: calc(1% * var(--cssnano) / var(--esbuild) * 100); height: 20px; background: rgb(186, 25, 33)\"></div>\n                <div style=\"position: absolute; right: calc(100% + 5px - 1% * var(--cssnano) / var(--esbuild) * 100); top: 5px; line-height: 20px; font-size: 12px; color: white\">155.89 KB</div>\n                <div style=\"position: absolute; top: 35px; left: 0; width: 100%; height: 20px; background: rgb(186, 25, 33)\"></div>\n                <div style=\"position: absolute; right: 5px; top: 35px; line-height: 20px; font-size: 12px; color: white\">156.57 KB</div>\n                <div style=\"position: absolute; top: 65px; left: 0; width: calc(1% * var(--lightningcss) / var(--esbuild) * 100); height: 20px; background: rgb(186, 25, 33)\"></div>\n                <div style=\"position: absolute; right: calc(100% + 5px - 1% * var(--lightningcss) / var(--esbuild) * 100); top: 65px; line-height: 20px; font-size: 12px; color: white\">139.74 KB</div>\n              </div>\n\n              <div style=\"position: absolute; right: calc(100% + 10px); top: 5px; line-height: 20px; font-size: 13px\">CSSNano</div>\n              <div style=\"position: absolute; right: calc(100% + 10px); top: 35px; line-height: 20px; font-size: 13px\">ESBuild</div>\n              <div style=\"position: absolute; left: -10px; transform: translateX(-100%); top: 65px; line-height: 20px; font-size: 13px\">Lightning CSS</div> \n            </div>\n          </div>\n          <figcaption>Output size after minifying Bootstrap 4 (~10,000 lines). See the <a href=\"https://github.com/parcel-bundler/lightningcss#benchmarks\" target=\"_blank\">readme</a> for more benchmarks.</figcaption>\n        </figure>\n      </section>\n      <section class=\"modules\">\n        <div class=\"compartment main\">\n          <h3>CSS modules</h3>\n          <p><strong>Lightning CSS supports CSS modules, which locally scope classes, ids, <code>@keyframes</code>, CSS variables, and more.</strong> This ensures that there are no unintended name clashes between different CSS files.</p>\n          <p style=\"font-size: 0.8em\">Lightning CSS generates a mapping of the original names to scoped names, which can be used from your JavaScript. This also enables unused classes and variables to be tree-shaken.</p>\n          <p><a href=\"css-modules.html\">Documentation →</a></p>\n        </div>\n        <pre class=\"compartment input\"><code>.heading {\n  composes: typography from './typography.css';\n  color: gray;\n}</code></pre>\n        <pre class=\"compartment output\"><code>.EgL3uq_heading {\n  color: gray;\n}</code></pre>\n        <pre class=\"compartment json\"><code>{\n  \"heading\": {\n    \"name\": \"EgL3uq_heading\",\n    \"composes\": [{\n      \"type\": \"dependency\",\n      \"name\": \"typography\",\n      \"specifier\": \"./typography.css\"\n    }]\n  }\n}</code></pre>\n      </section>\n      <section class=\"parser\">\n        <div>\n          <h3>Browser grade</h3>\n          <p><strong>Lightning CSS is written in Rust, using the <a href=\"https://github.com/servo/rust-cssparser\" target=\"_blank\">cssparser</a> and <a href=\"https://github.com/servo/stylo/tree/main/selectors\" target=\"_blank\">selectors</a> crates created by Mozilla and used by Firefox.</strong> These provide a solid CSS-parsing foundation on top of which Lightning CSS implements support for all specific CSS rules and properties.</p>\n          <p style=\"font-size: 0.85em\">Lightning CSS fully parses every CSS rule, property, and value just as a browser would. This reduces duplicate work for transformers, leading to improved performance and minification.</p>\n          <p><a href=\"transforms.html\">Custom transforms →</a></p>\n        </div>\n        <pre><code><span class=\"ident\">Background</span>([<span class=\"ident\">Background</span> {\n  image: <span class=\"ident\">Url</span>(<span class=\"ident\">Url</span> { url: <span class=\"string\">\"img.png\"</span> }),\n  color: <span class=\"ident\">CssColor</span>(<span class=\"ident\">RGBA</span>(<span class=\"ident\">RGBA</span> { red: <span class=\"number\">0</span>, green: <span class=\"number\">0</span>, blue: <span class=\"number\">0</span>, alpha: <span class=\"number\">0</span> })),\n  position: <span class=\"ident\">Position</span> {\n    x: <span class=\"ident\">Length</span>(<span class=\"ident\">Dimension</span>(<span class=\"ident\">Px</span>(<span class=\"number\">20.0</span>))),\n    y: <span class=\"ident\">Length</span>(<span class=\"ident\">Dimension</span>(<span class=\"ident\">Px</span>(<span class=\"number\">10.0</span>))),\n  },\n  repeat: <span class=\"ident\">BackgroundRepeat</span> {\n    x: <span class=\"ident\">Repeat</span>,\n    y: <span class=\"ident\">Repeat</span>,\n  },\n  size: <span class=\"ident\">Explicit</span> {\n    width: <span class=\"ident\">LengthPercentage</span>(<span class=\"ident\">Dimension</span>(<span class=\"ident\">Px</span>(<span class=\"number\">50.0</span>))),\n    height: <span class=\"ident\">LengthPercentage</span>(<span class=\"ident\">Dimension</span>(<span class=\"ident\">Px</span>(<span class=\"number\">100.0</span>))),\n  },\n  attachment: <span class=\"ident\">Scroll</span>,\n  origin: <span class=\"ident\">PaddingBox</span>,\n  clip: <span class=\"ident\">BorderBox</span>,\n}])</code></pre>\n      </section>\n    </main>\n    <footer>\n      Copyright © 2024 Devon Govett and Parcel Contributors.\n    </footer>\n    <style>\n      html {\n        color-scheme: dark;\n        background: #111;\n      }\n\n      body {\n        font-family: system-ui;\n        --gold: lch(80% 82.34 80.104);\n        --gold-text: lch(85% 82.34 80.104);\n        --gold-shadow: lch(80% 82.34 80.104 / .7);\n      }\n\n      @font-face {\n        font-family:\"din-1451-lt-pro-engschrift\";\n        src:url(\"https://use.typekit.net/af/7fa6e1/00000000000000007735bbcd/30/l?primer=388f68b35a7cbf1ee3543172445c23e26935269fadd3b392a13ac7b2903677eb&fvd=n4&v=3\") format(\"woff2\"),url(\"https://use.typekit.net/af/7fa6e1/00000000000000007735bbcd/30/d?primer=388f68b35a7cbf1ee3543172445c23e26935269fadd3b392a13ac7b2903677eb&fvd=n4&v=3\") format(\"woff\"),url(\"https://use.typekit.net/af/7fa6e1/00000000000000007735bbcd/30/a?primer=388f68b35a7cbf1ee3543172445c23e26935269fadd3b392a13ac7b2903677eb&fvd=n4&v=3\") format(\"opentype\");\n        font-display:auto;font-style:normal;font-weight:400;font-stretch:normal;\n      }\n\n      @font-face {\n        font-family:\"urbane-rounded\";\n        src:url(\"https://use.typekit.net/af/916187/00000000000000007735bfa0/30/l?primer=81a69539b194230396845be9681d114557adfb35f4cccc679c164afb4aa47365&fvd=n6&v=3\") format(\"woff2\"),url(\"https://use.typekit.net/af/916187/00000000000000007735bfa0/30/d?primer=81a69539b194230396845be9681d114557adfb35f4cccc679c164afb4aa47365&fvd=n6&v=3\") format(\"woff\"),url(\"https://use.typekit.net/af/916187/00000000000000007735bfa0/30/a?primer=81a69539b194230396845be9681d114557adfb35f4cccc679c164afb4aa47365&fvd=n6&v=3\") format(\"opentype\");\n        font-display:auto;font-style:normal;font-weight:600;font-stretch:normal;\n      }\n\n      header {\n        max-width: 1200px;\n        margin: 0 auto;\n        padding: 100px 0;\n        font-size: 16px;\n        background: radial-gradient(closest-side, lch(80% 82.34 80.104 / .3), transparent);\n        display: grid;\n        column-gap: 50px;\n        grid-template-areas: \"logo header\"\n                             \"logo subheader\"\n                             \". links\";\n      }\n\n      header svg {\n        filter: drop-shadow(0 0 5px var(--gold-shadow)) drop-shadow(0 0 20px var(--gold-shadow));\n        grid-area: logo;\n        place-self: center end;\n        width: 100px;\n      }\n\n      header svg .outer {\n        stroke-width: 30px;\n        stroke: var(--gold);\n      }\n\n      header svg .inner {\n        fill: lch(100% 82.34 80.104);\n      }\n\n      header h1 {\n        font-family: urbane-rounded;\n        font-size: 100px;\n        -webkit-text-stroke: 3px var(--gold-text);\n        color: transparent;\n        filter: drop-shadow(0 0 3px var(--gold-shadow)) drop-shadow(0 0 15px var(--gold));\n        padding: 20px 0;\n        margin: 0;\n        letter-spacing: -0.02em;\n      }\n\n      header h1::selection { \n        -webkit-text-stroke-color: #fffddd;\n        background-color: var(--gold-text);\n      }\n\n      header h2 {\n        font-family: urbane-rounded;\n        color: lch(65% 85 35);\n        text-shadow: 0 0 20px lch(65% 85 35);\n        margin: 0;\n        letter-spacing: -0.02em;\n      }\n\n      header p {\n        grid-area: links;\n      }\n\n      header a {\n        font-family: urbane-rounded;\n        font-size: 1.05em;\n        color: lch(90% 50.34 80.104);\n        filter: drop-shadow(0 0 8px lch(90% 50.34 80.104 / .7));\n        text-decoration-color: lch(90% 50.34 80.104 / 0);\n        text-decoration-style: wavy;\n        text-decoration-thickness: 2px;\n        text-underline-offset: 2px;\n        text-decoration-skip-ink: none;\n        transition: text-decoration-color 150ms;\n      }\n\n      header a:hover {\n        text-decoration-color: lch(90% 50.34 80.104);\n      }\n\n      @media (width < 950px) {\n        header {\n          column-gap: 30px;\n        }\n\n        header h1 {\n          font-size: 80px;\n        }\n\n        header h2 {\n          font-size: 20px;\n        }\n\n        header svg {\n          width: 80px;\n        }\n      }\n\n      @media (width < 800px) {\n        header {\n          column-gap: 15px;\n          padding: 30px 0;\n        }\n\n        header h1 {\n          font-size: 60px;\n        }\n\n        header h2 {\n          font-size: 16px;\n        }\n\n        header a {\n          font-size: 14px;\n        }\n\n        header svg {\n          width: 60px;\n        }\n      }\n\n      @media (width < 500px) {\n        header {\n          grid-template-areas: \"logo\"\n                               \"header\"\n                               \"subheader\"\n                               \"links\";\n          place-items: center;\n          text-align: center;\n          gap: 8px;\n        }\n        header h1 {\n          font-size: 38px;\n          -webkit-text-stroke-width: 1.5px;\n          padding: 0;\n        }\n\n        header h2 {\n          font-size: 14px;\n        }\n\n        header a {\n          font-size: 13px;\n        }\n\n        header svg {\n          place-self: center;\n        }\n      }\n\n      main {\n        max-width: 1400px;\n        margin: 0 auto;\n      }\n\n      main section {\n        --padding: 60px;\n        padding: var(--padding);\n        margin: 60px;\n        font-size: 22px;\n        --radius: 50px;\n        border-radius: var(--radius);\n      }\n\n      main section h3 {\n        margin-top: 0;\n        font-size: 1.8em;\n      }\n\n      main section p {\n        line-height: 1.4em;\n      }\n\n      main p a {\n        font-weight: bold;\n        font-size: 0.9em;\n        color: inherit;\n        text-decoration: none;\n      }\n\n      main p a:hover {\n        text-decoration: underline;\n      }\n\n      figure {\n        max-width: 800px;\n        margin: 0;\n      }\n\n      figcaption {\n        font-size: 10px;\n        text-align: center;\n        margin-top: 20px;\n      }\n\n      figcaption a {\n        color: inherit;\n      }\n\n      @media (width < 500px) {\n        main section {\n          margin: 20px 10px;\n          --padding: 20px;\n          --radius: 20px;\n          font-size: 16px;\n        }\n      }\n\n      .warp {\n        background: black;\n        color: white;\n        background-image: radial-gradient(rgb(10 3 34 / .2), rgb(10 3 34 / .7)), url(lightspeed.svg);\n        background-size: cover;\n        background-position: center center;\n        box-shadow: inset 0 0 0 1px rgb(255 255 255 / .15);\n      }\n\n      .warp h3 {\n        color: var(--gold-text);\n        text-shadow: 0 0 15px var(--gold-shadow);\n      }\n\n      .warp p {\n        max-width: 900px;\n      }\n\n      .warp figcaption {\n        color: #aaa;\n      }\n\n      .warp p a {\n        color: var(--gold-text);\n        text-shadow: 0 0 7px var(--gold-shadow);\n      }\n\n      .crush {\n        background-image: url(crush.svg), url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4' viewBox='0 0 4 4'%3E%3Cpath fill='rgb(0 0 0 / .2)' d='M1 3h1v1H1V3zm2-2h1v1H3V1z'%3E%3C/path%3E%3C/svg%3E\"), linear-gradient(to bottom right, lch(75% 82.34 80.104), lch(68% 82.34 80.104));\n        background-size: 600px auto, auto, auto;\n        background-repeat: no-repeat, repeat;\n        background-position: bottom right;\n        color: black;\n        padding-right: 550px;\n      }\n\n      .crush h3 {\n        font-size: 3em;\n        margin-bottom: 0;\n        color: lch(42.758% 73.588 34.159);\n        -webkit-text-stroke: 1px black;\n        text-shadow: 3px 3px 0 black;\n        text-transform: uppercase;\n        width: fit-content;\n        word-spacing: .1em;\n      }\n\n      .crush figcaption {\n        color: #222;\n      }\n\n      .crush p a {\n        color: lch(30% 73.588 34.159);\n      }\n\n      @media (width < 1200px) {\n        .crush {\n          padding-right: var(--padding);\n          padding-bottom: 500px;\n          background-position: bottom right;\n          background-size: auto 500px, auto, auto;\n        }\n      }\n\n      @media (width < 500px) {\n        .crush {\n          padding-bottom: 300px;\n          background-size: auto 300px, auto, auto;\n        }\n      }\n\n      .future {\n        background: #215178;\n        color: white;\n        background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100' viewBox='0 0 100 100'%3E%3Cg fill-rule='evenodd'%3E%3Cg fill='rgba(255,255,255,0.05)' fill-opacity='1'%3E%3Cpath opacity='.5' d='M96 95h4v1h-4v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9zm-1 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9z'/%3E%3Cpath d='M6 5V0H5v5H0v1h5v94h1V6h94V5H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E\"), radial-gradient(#1F4D97, #030B16);\n        box-shadow: inset 0 0 0 1px rgb(255 255 255 / .1);\n        position: relative;\n        --color: lch(85% 58 205);\n      }\n\n      .future .title {\n        font-family: din-1451-lt-pro-engschrift, sans-serif;\n        text-transform: uppercase;\n        position: absolute;\n        --height: 1.2em;\n        top: calc(var(--height) * -.5);\n        left: calc(var(--height) * 1.5);\n        margin-left: -5px;\n        background: var(--color);\n        filter: drop-shadow(0 0 8px var( --color));\n        width: fit-content;\n        color: #030B16;\n        margin-top: 0;\n      }\n\n      .future h3.title {\n        font-size: 1.6em;\n        line-height: var(--height);\n      }\n\n      .future h4.title {\n        font-size: 16px;\n        line-height: var(--height);\n      }\n\n      .future .title:before {\n        content: '';\n        display: inline-block;\n        position: absolute;\n        right: 100%;\n        width: 0;\n        height: 0;\n        border-top: var(--height) solid var( --color);\n        border-left: var(--height) solid transparent;\n      }\n\n      .future .title:after {\n        content: '';\n        display: inline-block;\n        position: absolute;\n        left: 100%;\n        width: 0;\n        height: 0;\n        border-bottom: var(--height) solid var( --color);\n        border-right: var(--height) solid transparent;\n      }\n\n      .future .inner {\n        border: 2px solid var(--color);\n        box-shadow: 0 0 15px var( --color), inset 0 0 15px var( --color);\n        border-radius: 20px;\n        padding: 20px;\n        display: flex;\n        flex-direction: row;\n        position: relative;\n      }\n\n      .future .description {\n        margin: 40px;\n      }\n\n      .future .description p:first-child {\n        margin-top: 0;\n      }\n\n      .future .description p:last-child {\n        margin-bottom: 0;\n      }\n\n      .future .example {\n        display: flex;\n        flex-direction: column;\n        gap: 30px;\n        flex-shrink: 0;\n      }\n\n      .future .box {\n        --color: lch(64% 103 0);\n        border: 2px solid var(--color);\n        background: lch(64% 103 0 / .15);\n        border-radius: 10px;\n        box-shadow: 0 0 20px var(--color), inset 0 0 10px var(--color);\n        padding: 15px;\n        text-shadow: 0 0 8px var(--color);\n        position: relative;\n        width: fit-content;\n        margin-left: auto;\n        max-width: 100%;\n        box-sizing: border-box;\n      }\n\n      .future .box code {\n        font-size: 14px;\n        display: block;\n        overflow: visible auto;\n      }\n\n      .future .box .title {\n        color: white\n      }\n\n      .future .box .property {\n        color: white;\n      }\n\n      .future .box .keyword {\n        color: var(--color);\n      }\n\n      .future .box .number {\n        color: lch(71% 103 52);\n      }\n\n      .future .box .class {\n        color: lch(87% 107 89);\n      }\n\n      .future .targets h4 {\n        font-family: din-1451-lt-pro-engschrift, sans-serif;\n        text-transform: uppercase;\n        display: inline;\n        vertical-align: middle;\n        margin: 0;\n        margin-right: 10px;\n        color: lch(85% 58 205 / .7);\n      }\n\n      .future .targets {\n        border: 2px solid lch(85% 58 205 / .5);\n        box-shadow: 0 0 10px lch(85% 58 205 / .5);\n        border-radius: 6px;\n        padding: 8px;\n        width: fit-content;\n        margin-left: auto;\n        font-size: 16px;\n      }\n\n      .future .targets code {\n        font-size: 15px;\n      }\n\n      .future a {\n        color: lch(85% 58 205);\n        text-shadow: 0 0 10px lch(85% 58 205 / .6);\n      }\n\n      @media (width < 1200px) {\n        .future .inner {\n          flex-direction: column;\n        }\n\n        .future .box, .future .targets {\n          margin-left: 0\n        }\n      }\n\n      @media (width < 500px) {\n        .future .description {\n          margin: 20px 4px;\n        }\n\n        .future .box {\n          padding: 10px;\n        }\n        \n        .future .targets h4 {\n          display: block;\n        }\n      }\n\n      .parser {\n        background-image: linear-gradient(rgb(0 0 0 / .1), rgb(0 0 0 / .1)), image-set(\"metal.jpeg?as=webp&width=1200\" 1x, \"metal.jpeg?as=webp&width=2400\" 2x);\n        background-size: cover;\n        color: black;\n        text-shadow: inset 0 2px 5px black;\n        position: relative;\n        --inset: 35px;\n        padding: calc(var(--padding) + var(--inset) * .6);\n        display: flex;\n        column-gap: 40px;\n        row-gap: 20px;\n        box-shadow: inset 0 0 0 1px rgb(255 255 255 / .4);\n      }\n\n      .parser > * {\n        z-index: 2;\n      }\n\n      .parser:before {\n        content: '';\n        position: absolute;\n        inset: calc(var(--inset) / 2);\n        border-radius: calc(var(--radius) * .8);\n        box-shadow: inset 2px 3px 10px rgb(0 0 0 / .7), 1px 1px 0 rgb(255 255 255 / .4);\n        background: rgb(0 0 0 / .05);\n        pointer-events: none;\n        z-index: 0;\n      }\n\n      .parser:after {\n        content: '';\n        position: absolute;\n        inset: var(--inset);\n        border-radius: calc(var(--radius) * .4);\n        box-shadow: inset 1px 1px 0 rgb(255 255 255 / .4), 2px 3px 10px rgb(0 0 0 / .7);\n        background: rgb(255 255 255 / .25);\n        pointer-events: none;\n        z-index: 1;\n      }\n\n      .parser h3 {\n        font-family: Impact, Haettenschweiler, 'Arial Narrow Bold', sans-serif;\n        letter-spacing: 2px;\n        text-transform: uppercase;\n        text-shadow: 3px 3px 2px rgba(255,255,255,0.35);\n        background-color: rgba(0 0 0 / .65);\n        color: transparent;\n        -webkit-background-clip: text;\n        background-clip: text;\n      }\n\n      .parser p {\n        text-shadow: 0 1px 0 white;\n      }\n\n      .parser p:last-child {\n        margin-bottom: 0;\n      }\n\n      .parser pre {\n        padding: 20px;\n        width: fit-content;\n        margin: 0;\n        margin-right: -40px;\n        font-size: .6em;\n        display: flex;\n        align-items: end;\n        text-shadow: 0 1px 0 rgb(255 255 255 / .6);\n        font-weight: bold;\n        overflow: auto;\n        max-width: 100%;\n        flex-shrink: 0;\n        margin-top: auto;\n      }\n\n      .parser pre .ident {\n        color: lch(30% 80 300);\n      }\n\n      .parser pre .string {\n        color: lch(40% 108 144);\n      }\n\n      .parser pre .number {\n        color: lch(35% 117 348);\n      }\n\n      .parser a {\n        color: black;\n      }\n\n      .parser p:last-of-type a {\n        font-size: 0.8em;\n      }\n\n      @media (width < 1200px) {\n        .parser {\n          flex-direction: column;\n        }\n\n        .parser pre {\n          margin: 0;\n          padding: 0;\n        }\n      }\n\n      @media (width < 500px) {\n        .parser {\n          --inset: 25px;\n        }\n\n        .parser p, .parser pre {\n          text-shadow: 0 1px 0 rgb(255 255 255 / .6);\n        }\n      }\n\n      .modules {\n        background: black;\n        box-shadow: 0 0 0 1px rgb(255 255 255 / .2), 2px 2px 5px rgb(255 255 255 / .2);\n        padding: 0;\n        display: grid;\n        grid-template-areas: \"main input  input\"\n                             \"main output json\";\n      }\n\n      .modules .compartment {\n        padding: max(20px, calc(var(--padding) / 2));\n        border: 20px solid black;\n        border-radius: 40px;\n        --shadow-size: 20px;\n        --inner-shadow-size: 2px;\n        box-shadow: inset 0 0 0 1px rgb(255 255 255 / .15), inset -1px -1px var(--inner-shadow-size) rgb(255 255 255 / .3), inset 4px 8px var(--shadow-size) rgb(0 0 0 / .9);\n        margin: 0;\n        overflow: auto;\n        background: #ba1921;\n        background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='32' viewBox='0 0 16 32'%3E%3Cg fill='%23fff' fill-opacity='0.05'%3E%3Cpath fill-rule='evenodd' d='M0 24h4v2H0v-2zm0 4h6v2H0v-2zm0-8h2v2H0v-2zM0 0h4v2H0V0zm0 4h2v2H0V4zm16 20h-6v2h6v-2zm0 4H8v2h8v-2zm0-8h-4v2h4v-2zm0-20h-6v2h6V0zm0 4h-4v2h4V4zm-2 12h2v2h-2v-2zm0-8h2v2h-2V8zM2 8h10v2H2V8zm0 8h10v2H2v-2zm-2-4h14v2H0v-2zm4-8h6v2H4V4zm0 16h6v2H4v-2zM6 0h2v2H6V0zm0 24h2v2H6v-2z'/%3E%3C/g%3E%3C/svg%3E\");\n      }\n\n      .modules .compartment.main {\n        grid-area: main;\n        border-top-left-radius: 50px;\n        border-bottom-left-radius: 50px;\n        padding: 60px 60px 50px 60px;\n        margin-right: -20px;\n        --shadow-size: 40px;\n        --inner-shadow-size: 4px;\n      }\n\n      .modules .compartment.main p:last-child {\n        margin-bottom: 0;\n      }\n\n      .modules .compartment:not(.main) {\n        display: flex;\n        align-items: center;\n        justify-content: center;\n      }\n\n      .modules h3 {\n        text-transform: uppercase;\n        font-size: 1.45em;\n        border-top: 4px solid white;\n        border-bottom: 4px solid white;\n        line-height: 1.5em;\n        width: fit-content;\n      }\n\n      .modules p {\n        font-family: Georgia, 'Times New Roman', Times, serif;\n        font-size: 0.87em;\n      }\n      \n      .modules pre {\n        font-size: 14px;\n      }\n\n      .modules .compartment.input {\n        grid-area: input;\n        border-top-right-radius: 50px;\n      }\n\n      .modules .compartment.output {\n        grid-area: output;\n        margin-top: -20px;\n      }\n\n      .modules .compartment.json {\n        grid-area: json;\n        margin-top: -20px;\n        margin-left: -20px;\n        border-bottom-right-radius: 50px;\n      }\n\n      @media (width < 1200px) {\n        .modules {\n          grid-template-areas: \"main main\"\n                               \"input input\"\n                               \"output json\";\n        }\n\n        .modules .compartment.main {\n          border-top-right-radius: 50px;\n          margin-right: 0;\n        }\n\n        .modules .compartment.input {\n          margin-top: -20px;\n        }\n\n        .modules .compartment.output {\n          border-bottom-left-radius: 50px;\n        }\n\n        .modules .compartment:not(.main) {\n          justify-content: start;\n        }\n      }\n\n      @media (width < 500px) {\n        .modules {\n          grid-template-areas: \"main\" \"input\" \"output\" \"json\";\n        }\n\n        .modules .compartment {\n          border-radius: calc(var(--radius) * 1.5) !important;\n          --shadow-size: 15px;\n        }\n\n        .modules .compartment.main {\n          padding: 30px;\n          --shadow-size: 25px;\n        }\n\n        .modules .compartment.json {\n          margin: 0;\n          margin-top: -20px;\n        }\n\n        .modules pre {\n          font-size: 12px;\n        }\n      }\n\n      footer {\n        font-size: 12px;\n        color: #666;\n        text-align: center;\n        padding-bottom: 20px;\n      }\n\n      @media (width < 600px) {\n        .chart .line {\n          display: none;\n        }\n      }\n    </style>\n  </body>\n</html>\n"
  },
  {
    "path": "website/minification.html",
    "content": "<include src=\"website/include/layout.html\" locals='{\"title\": \"Minification\", \"url\": \"minification.html\", \"page\": \"website/pages/minification.md\"}' />\n"
  },
  {
    "path": "website/pages/bundling.md",
    "content": "<aside>\n\n[[toc]]\n\n</aside>\n\n# Bundling\n\nLightning CSS supports bundling dependencies referenced by CSS `@import` rules into a single output file. When calling the Lightning CSS API, use the `bundle` or `bundleAsync` function instead of `transform`. When using the CLI, enable the `--bundle` flag.\n\nThis API requires filesystem access, so it does not accept `code` directly via the API. Instead, the `filename` option is used to read the entry file directly.\n\n```js\nimport { bundle } from 'lightningcss';\n\nlet { code, map } = bundle({\n  filename: 'style.css',\n  minify: true\n});\n```\n\n## Dependencies\n\nCSS files can contain dependencies referenced by `@import` syntax, as well as references to classes in other files via [CSS modules](css-modules.html).\n\n### @import\n\nThe [`@import`](https://developer.mozilla.org/en-US/docs/Web/CSS/@import) at-rule can be used to inline another CSS file into the same CSS bundle as the containing file. This means that at runtime a separate network request will not be needed to load the dependency. Referenced files should be relative to the containing CSS file.\n\n```css\n@import 'other.css';\n```\n\n`@import` rules must appear before all other rules in a stylesheet except `@charset` and `@layer` statement rules. Later import rules will cause an error to be emitted.\n\n### CSS modules\n\nDependencies are also bundled when referencing another file via [CSS modules composition](css-modules.html#dependencies) or [external variables](css-modules.html#local-css-variables). See the linked CSS modules documentation for more details.\n\n## Conditional imports\n\nThe `@import` rule can be conditional by appending a media query or `supports()` query. Lightning CSS will preserve this behavior by wrapping the inlined rules in `@media` and `@supports` rules as needed.\n\n```css\n/* a.css */\n@import \"b.css\" print;\n@import \"c.css\" supports(display: grid);\n\n.a { color: red }\n```\n\n```css\n/* b.css */\n.b { color: green }\n```\n\n```css\n/* c.css */\n.c { display: grid }\n```\n\ncompiles to:\n\n```css\n@media print {\n  .b { color: green }\n}\n\n@supports (display: grid) {\n  .c { display: grid }\n}\n\n.a { color: red }\n```\n\n<div class=\"warning\">\n\n**Note**: There are currently two cases where combining conditional rules is unsupported:\n\n1. Importing the same CSS file with only a media query, and again with only a supports query. This would require duplicating all rules in the file.\n2. Importing a file with a negated media type (e.g. `not print`) within another file with a negated media type.\n\n</div>\n\n## Cascade layers\n\nImported CSS rules can also be placed into a CSS cascade layer, allowing you to control the order they apply. Nested imports will be placed into nested layers.\n\n```css\n/* a.css */\n@import \"b.css\" layer(foo);\n.a { color: red }\n```\n\n```css\n/* b.css */\n@import \"c.css\" layer(bar);\n.b { color: green }\n```\n\n```css\n/* c.css */\n.c { color: green }\n```\n\ncompiles to:\n\n```css\n@layer foo.bar {\n  .c { color: green }\n}\n\n@layer foo {\n  .b { color: green }\n}\n\n.a { color: red }\n```\n\n<div class=\"warning\">\n\n**Note**: There are two unsupported layer combinations that will currently emit a compiler error:\n\n1. Importing the same CSS file with different layer names. This would require duplicating all imported rules multiple times.\n2. Nested anonymous layers.\n\n</div>\n\n## Bundling order\n\nWhen `@import` rules are processed in browsers, if the same file appears more than once, the _last_ instance applies. This is the opposite from behavior in other languages like JavaScript. Lightning CSS follows this behavior when bundling so that the output behaves the same as if it were not bundled.\n\n```css\n/* index.css */\n@import \"a.css\";\n@import \"b.css\";\n@import \"a.css\";\n```\n\n```css\n/* a.css */\nbody { background: green }\n```\n\n```css\n/* b.css */\nbody { background: red }\n```\n\ncompiles to:\n\n```css\nbody { background: green }\n```\n\n## Custom resolvers\n\nThe `bundleAsync` API is an asynchronous version of `bundle`, which also accepts a custom `resolver` object. This allows you to provide custom JavaScript functions for resolving `@import` specifiers to file paths, and reading files from the file system (or another source). The `read` and `resolve` functions are both optional, and may either return a string synchronously, or a Promise for asynchronous resolution.\n\n`resolve` may also return a `{external: string}` object to mark an `@import` as external. This will preserve the `@import` in the output instead of bundling it. The string provided to the `external` property represents the target URL to import, which may be the original specifier or a different value.\n\nNote that using a custom resolver can slow down bundling significantly, especially when reading files asynchronously. Use `readFileSync` rather than `readFile` if possible for better performance, or omit either of the methods if you don't need to override the default behavior.\n\n```js\nimport { bundleAsync } from 'lightningcss';\n\nlet { code, map } = await bundleAsync({\n  filename: 'style.css',\n  minify: true,\n  resolver: {\n    read(filePath) {\n      return fs.readFileSync(filePath, 'utf8');\n    },\n    resolve(specifier, from) {\n      if (/^https?:/.test(specifier)) {\n        return {external: specifier};\n      }\n      return path.resolve(path.dirname(from), specifier);\n    }\n  }\n});\n```\n\n<div class=\"warning\">\n\n**Note:** External imports must be placed before all bundled imports in the source code. CSS does not support interleaving `@import` rules with other rules, so this is required to preserve the behavior of the source code.\n\n```css\n@import \"bundled.css\";\n@import \"https://example.com/external.css\"; /* ❌ */\n```\n\n```css\n@import \"https://example.com/external.css\"; /* ✅ */\n@import \"bundled.css\";\n```\n\n</div>\n"
  },
  {
    "path": "website/pages/css-modules.md",
    "content": "<aside>\n\n[[toc]]\n\n</aside>\n\n# CSS modules\n\nBy default, CSS identifiers are global. If two files define the same class names, ids, custom properties, `@keyframes`, etc., they will potentially clash and overwrite each other. To solve this, Lightning CSS supports [CSS modules](https://github.com/css-modules/css-modules).\n\nCSS modules treat the classes defined in each file as unique. Each class name or identifier is renamed to include a unique hash, and a mapping is exported to JavaScript to allow referencing them.\n\nTo enable CSS modules, provide the `cssModules` option when calling the Lightning CSS API. When using the CLI, enable the `--css-modules` flag.\n\n```js\nimport {transform} from 'lightningcss';\n\nlet {code, map, exports} = transform({\n  // ...\n  cssModules: true,\n  code: Buffer.from(`\n    .logo {\n      background: skyblue;\n    }\n  `),\n});\n```\n\nThis returns an `exports` object in addition to the compiled code and source map. Each property in the `exports` object maps from the original name in the source CSS to the compiled (i.e. hashed) name. You can use this mapping in your JavaScript or template files to reference the compiled classes and identifiers.\n\nThe exports object for the above example might look like this:\n\n```js\n{\n  logo: {\n    name: '8h19c6_logo',\n    isReferenced: false,\n    composes: []\n  }\n}\n```\n\n## Class composition\n\nStyle rules in CSS modules can reference other classes with the `composes` property. This causes the referenced class to be applied whenever the composed class is used, effectively providing a form of style mixins.\n\n```css\n.bg-indigo {\n  background: indigo;\n}\n\n.indigo-white {\n  composes: bg-indigo;\n  color: white;\n}\n```\n\nIn the above example, whenever the `indigo-white` class is applied, the `bg-indigo` class will be applied as well. This is indicated in the `exports` object returned by Lightning CSS as follows:\n\n```js\n{\n  'bg-indigo': {\n    name: '8h19c6_bg-indigo',\n    isReferenced: true,\n    composes: []\n  },\n  'indigo-white': {\n    name: '8h19c6_indigo-white',\n    isReferenced: false,\n    composes: [{\n      type: 'local',\n      name: '8h19c6_bg-indigo'\n    }]\n  }\n}\n```\n\nMultiple classes can be composed at once by separating them with spaces.\n\n```css\n.logo {\n  composes: bg-indigo padding-large;\n}\n```\n\n### Dependencies\n\nYou can also reference class names defined in a different CSS file using the `from` keyword:\n\n```css\n.logo {\n  composes: bg-indigo from './colors.module.css';\n}\n```\n\nThis outputs an exports object with the dependency information. It is the caller's responsibility to resolve this dependency and apply the target class name when using the `transform` API. When using the `bundle` API, this is handled automatically.\n\n```js\n{\n  logo: {\n    name: '8h19c6_logo',\n    isReferenced: false,\n    composes: [{\n      type: 'dependency',\n      name: 'bg-indigo',\n      specifier: './colors.module.css'\n    }]\n  }\n}\n```\n\n### Global composition\n\nGlobal (i.e. non-hashed) classes can also be composed using the `global` keyword:\n\n```css\n.search {\n  composes: search-widget from global;\n}\n```\n\n## Global exceptions\n\nWithin a CSS module, all class and id selectors are local by default. You can also opt out of this behavior for a single selector using the `:global` pseudo class.\n\n```css\n.foo :global(.bar) {\n  color: red;\n}\n\n.foo .bar {\n  color: green;\n}\n```\n\ncompiles to:\n\n```css\n.EgL3uq_foo .bar {\n  color: red;\n}\n\n.EgL3uq_foo .EgL3uq_bar {\n  color: #ff0;\n}\n```\n\n## Local CSS variables\n\nBy default, class names, id selectors, and the names of `@keyframes`, `@counter-style`, and CSS grid lines and areas are scoped to the module they are defined in. Scoping for CSS variables and other [`<dashed-ident>`](https://www.w3.org/TR/css-values-4/#dashed-idents) names can also be enabled using the `dashedIdents` option when calling the Lightning CSS API. When using the CLI, enable the `--css-modules-dashed-idents` flag.\n\n```js\nlet {code, map, exports} = transform({\n  // ...\n  cssModules: {\n    dashedIdents: true,\n  },\n});\n```\n\nWhen enabled, CSS variables will be renamed so they don't conflict with variable names defined in other files. Referencing a variable uses the standard `var()` syntax, which Lightning CSS will update to match the locally scoped variable name.\n\n```css\n:root {\n  --accent-color: hotpink;\n}\n\n.button {\n  background: var(--accent-color);\n}\n```\n\nbecomes:\n\n```css\n:root {\n  --EgL3uq_accent-color: hotpink;\n}\n\n.EgL3uq_button {\n  background: var(--EgL3uq_accent-color);\n}\n```\n\nYou can also reference variables defined in other files using the `from` keyword:\n\n```css\n.button {\n  background: var(--accent-color from './vars.module.css');\n}\n```\n\nGlobal variables may be referenced using the `from global` syntax.\n\n```css\n.button {\n  color: var(--color from global);\n}\n```\n\nThe same syntax also applies to other CSS values that use the [`<dashed-ident>`](https://www.w3.org/TR/css-values-4/#dashed-idents) syntax. For example, the [@font-palette-values](https://drafts.csswg.org/css-fonts-4/#font-palette-values) rule and [font-palette](https://drafts.csswg.org/css-fonts-4/#propdef-font-palette) property use the `<dashed-ident>` syntax to define and refer to custom font color palettes, and will be scoped and referenced the same way as CSS variables.\n\n## Custom naming patterns\n\nBy default, Lightning CSS prepends the hash of the filename to each class name and identifier in a CSS file. You can configure this naming pattern using the `pattern` when calling the Lightning CSS API. When using the CLI, provide the `--css-modules-pattern` option.\n\nA pattern is a string with placeholders that will be filled in by Lightning CSS. This allows you to add custom prefixes or adjust the naming convention for scoped classes.\n\n```js\nlet {code, map, exports} = transform({\n  // ...\n  cssModules: {\n    pattern: 'my-company-[name]-[hash]-[local]',\n  },\n});\n```\n\nThe following placeholders are currently supported:\n\n- `[name]` - The base name of the file, without the extension.\n- `[hash]` - A hash of the full file path.\n- `[content-hash]` - A hash of the file contents.\n- `[local]` - The original class name or identifier.\n\n<div class=\"warning\">\n\n### CSS Grid\n\n**Note:** CSS grid line names can be ambiguous due to automatic postfixing done by the browser, which generates line names ending with `-start` and `-end` for each grid template area. When using CSS grid, your `\"pattern\"` configuration must end with the `[local]` placeholder so that these references work correctly.\n\n```js\nlet { code, map, exports } = transform({\n  // ...\n  cssModules: {\n    // ❌ [local] must be at the end so that\n    // auto-generated grid line names work\n    pattern: '[local]-[hash]'\n    // ✅ do this instead\n    pattern: '[hash]-[local]'\n  }\n});\n```\n\n```css\n.grid {\n  grid-template-areas: 'nav main';\n}\n\n.nav {\n  grid-column-start: nav-start;\n}\n```\n\n</div>\n\n\n### Pure mode\n\nJust like the `pure` option of the `css-loader` for webpack, Lightning CSS also has a `pure` option that enforces usage of one or more id or class selectors for each rule. \n\n\n```js\nlet {code, map, exports} = transform({\n  // ...\n  cssModules: {\n    pure: true,\n  },\n});\n```\n\nIf you enable this option, Lightning CSS will throw an error for CSS rules that don't have at least one id or class selector, like `div`.\nThis is useful because selectors like `div` are not scoped and affects all elements on the page.\n\n\n\n## Turning off feature scoping\n\nScoping of grid, animations, and custom identifiers can be turned off. By default all of these are scoped.\n\n```js\nlet {code, map, exports} = transform({\n  // ...\n  cssModules: {\n    animation: true,\n    grid: true,\n    customIdents: true,\n  },\n});\n```\n\n## Unsupported features\n\nLightning CSS does not currently implement all CSS modules features available in other implementations. Some of these may be added in the future.\n\n- Non-function syntax for the `:local` and `:global` pseudo classes.\n- The `@value` rule – superseded by standard CSS variables.\n- The `:import` and `:export` ICSS rules.\n"
  },
  {
    "path": "website/pages/docs.md",
    "content": "<aside>\n\n[[toc]]\n\n</aside>\n\n# Getting Started\n\nLightning CSS can be used as a library from JavaScript or Rust, or from a standalone CLI. It can also be wrapped as a plugin in other build tools, and it is built into [Parcel](https://parceljs.org) out of the box.\n\n## From Node\n\nFirst, install Lightning CSS using a package manager such as npm or Yarn.\n\n```shell\nnpm install --save-dev lightningcss\n```\n\nOnce installed, import the module and call one of the Lightning CSS APIs. The `transform` function compiles a CSS stylesheet from a [Node Buffer](https://nodejs.org/api/buffer.html). This example minifies the input CSS, and outputs the compiled code and a source map.\n\n```js\nimport { transform } from 'lightningcss';\n\nlet { code, map } = transform({\n  filename: 'style.css',\n  code: Buffer.from('.foo { color: red }'),\n  minify: true,\n  sourceMap: true\n});\n```\n\nSee [Transpilation](transpilation.html) for details about syntax lowering and vendor prefixing CSS for your browser targets, and the draft syntax support in Lightning CSS. You can also use the `bundle` API to process `@import` rules and inline them – see [Bundling](bundling.html) for details.\n\nThe [TypeScript definitions](https://github.com/parcel-bundler/lightningcss/blob/master/node/index.d.ts) also include documentation for all API options.\n\n## From Rust\n\nLightning CSS can also be used as a Rust library to parse, transform, and minify CSS. See the Rust API docs on [docs.rs](https://docs.rs/lightningcss).\n\n## With Parcel\n\n[Parcel](https://parceljs.org) includes Lightning CSS as the default CSS transformer. You should also add a `browserslist` property to your `package.json`, which defines the target browsers that your CSS will be compiled for.\n\nWhile Lightning CSS handles the most commonly used PostCSS plugins like `autoprefixer`, `postcss-preset-env`, and CSS modules, you may still need PostCSS for more custom plugins like TailwindCSS. If that's the case, your PostCSS config will be picked up automatically. You can remove the plugins listed above from your PostCSS config, and they'll be handled by Lightning CSS.\n\nYou can also configure Lightning CSS in the `package.json` in the root of your project. Currently, three options are supported: [drafts](transpilation.html#draft-syntax), which can be used to enable CSS nesting and custom media queries, [pseudoClasses](transpilation.html#pseudo-class-replacement), which allows replacing some pseudo classes like `:focus-visible` with normal classes that can be applied via JavaScript (e.g. polyfills), and [cssModules](css-modules.html), which enables CSS modules globally rather than only for files ending in `.module.css`, or accepts an options object.\n\n```json\n{\n  \"@parcel/transformer-css\": {\n    \"cssModules\": true,\n    \"drafts\": {\n      \"nesting\": true,\n      \"customMedia\": true\n    },\n    \"pseudoClasses\": {\n      \"focusVisible\": \"focus-ring\"\n    }\n  }\n}\n```\n\nSee the [Parcel docs](https://parceljs.org/languages/css) for more details.\n\n## From Deno or in browser\n\nThe `lightningcss-wasm` package can be used in Deno or directly in browsers. This uses a WebAssembly build of Lightning CSS. Use `TextEncoder` and `TextDecoder` convert code from a string to a typed array and back.\n\n```js\nimport init, { transform } from 'https://esm.run/lightningcss-wasm';\n\nawait init();\n\nlet {code, map} = transform({\n  filename: 'style.css',\n  code: new TextEncoder().encode('.foo { color: red }'),\n  minify: true,\n});\n\nconsole.log(new TextDecoder().decode(code));\n```\n\nNote that the `bundle` and visitor APIs are not currently available in the WASM build.\n\n## With webpack\n\n[css-minimizer-webpack-plugin](https://webpack.js.org/plugins/css-minimizer-webpack-plugin/) has built in support for Lightning CSS. To use it, first install Lightning CSS in your project with a package manager like npm or Yarn:\n\n```shell\nnpm install --save-dev lightningcss css-minimizer-webpack-plugin browserslist\n```\n\nNext, configure `css-minifier-webpack-plugin` to use Lightning CSS as the minifier. You can provide options using the `minimizerOptions` object. See [Transpilation](transpilation.html) for details.\n\n```js\n// webpack.config.js\nconst CssMinimizerPlugin = require('css-minimizer-webpack-plugin');\nconst lightningcss = require('lightningcss');\nconst browserslist = require('browserslist');\n\nmodule.exports = {\n  optimization: {\n    minimize: true,\n    minimizer: [\n      new CssMinimizerPlugin({\n        minify: CssMinimizerPlugin.lightningCssMinify,\n        minimizerOptions: {\n          targets: lightningcss.browserslistToTargets(browserslist('>= 0.25%'))\n        },\n      }),\n    ],\n  },\n};\n```\n\n## With Vite\n\nVite supports Lightning CSS out of the box. First, install Lightning CSS into your project:\n\n```shell\nnpm install --save-dev lightningcss\n```\n\nThen, set `'lightningcss'` as CSS [transformer](https://vitejs.dev/config/shared-options.html#css-transformer) and [minifier](https://vitejs.dev/config/build-options.html#build-cssminify) in your Vite config. You can also configure Lightning CSS options such as targets and css modules via the [css.lightningcss](https://vitejs.dev/config/shared-options.html#css-lightningcss) option in your Vite config.\n\n```js\n// vite.config.ts\nimport browserslist from 'browserslist';\nimport {browserslistToTargets} from 'lightningcss';\n\nexport default {\n  css: {\n    transformer: 'lightningcss',\n    lightningcss: {\n      targets: browserslistToTargets(browserslist('>= 0.25%'))\n    }\n  },\n  build: {\n    cssMinify: 'lightningcss'\n  }\n};\n```\n\n## From the CLI\n\nLightning CSS includes a standalone CLI that can be used to compile, minify, and bundle CSS files. It can be used when you only need to compile CSS, and don't need more advanced functionality from a larger build tool such as code splitting and support for other languages.\n\nTo use the CLI, install the `lightningcss-cli` package with an npm compatible package manager:\n\n```shell\nnpm install --save-dev lightningcss-cli\n```\n\nThen, you can run the `lightningcss` command via `npx`, `yarn`, or by setting up a script in your package.json.\n\n```json\n{\n  \"scripts\": {\n    \"build\": \"lightningcss --minify --bundle --targets \\\">= 0.25%\\\" input.css -o output.css\"\n  }\n}\n```\n\nTo see all of the available options, use the `--help` argument:\n\n```shell\nnpx lightningcss-cli --help\n```\n\n## Error recovery\n\nBy default, Lightning CSS is strict, and will error when parsing an invalid rule or declaration. However, sometimes you may encounter a third party library that you can't easily modify, which unintentionally contains invalid syntax, or IE-specific hacks. In these cases, you can enable the `errorRecovery` option (or `--error-recovery` CLI flag). This will skip over invalid rules and declarations, omitting them in the output, and producing a warning instead of an error. You should also open an issue or PR to fix the issue in the library if possible.\n\n## Source maps\n\nLightning CSS supports generating source maps when compiling, minifying, and bundling your source code to make debugging easier. Use the `sourceMap` option to enable it when using the API, or the `--sourcemap` CLI flag.\n\nIf the input CSS came from another compiler such as Sass or Less, you can also pass an input source map to Lightning CSS using the `inputSourceMap` API option. This will map compiled locations back to their location in the original source code.\n\nFinally, the `projectRoot` option can be used to make file paths in source maps relative to a root directory. This makes build stable between machines.\n"
  },
  {
    "path": "website/pages/minification.md",
    "content": "<aside>\n\n[[toc]]\n\n</aside>\n\n# Minification\n\nLightning CSS can optimize your CSS to make it smaller, which can help improve the loading performance of your website. When using the Lightning CSS API, enable the `minify` option, or when using the CLI, use the `--minify` flag.\n\n```js\nimport { transform } from 'lightningcss';\n\nlet { code, map } = transform({\n  // ...\n  minify: true\n});\n```\n\n## Optimizations\n\nThe Lightning CSS minifier includes many optimizations to generate the smallest possible output for all rules, properties, and values in your stylesheet. Lightning CSS does not perform any optimizations that change the behavior of your CSS unless it can prove that it is safe to do so. For example, only adjacent style rules are merged to avoid changing the order and potentially breaking the styles.\n\n### Shorthands\n\nLightning CSS will combine longhand properties into shorthands when all of the constituent longhand properties are defined. For example:\n\n```css\n.foo {\n  padding-top: 1px;\n  padding-left: 2px;\n  padding-bottom: 3px;\n  padding-right: 4px;\n}\n```\n\nminifies to:\n\n```css\n.foo{padding:1px 4px 3px 2px}\n```\n\nThis is supported across most shorthand properties defined in the CSS spec.\n\n### Merge adjacent rules\n\nLightning CSS will merge adjacent style rules with the same selectors or declarations.\n\n```css\n.a {\n  color: red;\n}\n\n.b {\n  color: red;\n}\n\n.c {\n  color: green;\n}\n\n.c {\n  padding: 10px;\n}\n```\n\nbecomes:\n\n```css\n.a,.b{color:red}.c{color:green;padding:10px}\n```\n\nIn addition to style rules, Lightning CSS will also merge adjacent `@media`, `@supports`, and `@container` rules with identical queries, and adjacent `@layer` rules with the same layer name.\n\nLightning CSS will not merge rules that are not adjacent, e.g. if another rule is between rules with the same declarations or selectors. This is because changing the order of the rules could cause the behavior of the compiled CSS to differ from the input CSS.\n\n### Remove prefixes\n\nLightning CSS will remove vendor prefixed properties that are not needed according to your configured browser targets. This is more likely to affect precompiled libraries that include unused prefixes rather than your own code.\n\nFor example, when compiling for modern browsers, prefixed versions of the `transition` property will be removed, since the unprefixed version is supported by all browsers.\n\n```css\n.button {\n  -webkit-transition: background 200ms;\n  -moz-transition: background 200ms;\n  transition: background 200ms;\n}\n```\n\nbecomes:\n\n```css\n.button{transition:background .2s}\n```\n\nSee [Transpilation](transpilation.html) for more on how to configure browser targets.\n\n### Reduce calc\n\nLightning CSS will reduce `calc()` and other math expressions to constant values where possible. When different units are used, the terms are reduced as much as possible.\n\n```css\n.foo {\n  width: calc(100px * 2);\n  height: calc(((75.37% - 63.5px) - 900px) + (2 * 100px));\n}\n```\n\nminifies to:\n\n```css\n.foo{width:200px;height:calc(75.37% - 763.5px)}\n```\n\nNote that `calc()` expressions with variables are currently left unmodified by Lightning CSS.\n\n### Minify colors\n\nLightning CSS will minify colors to the smallest format possible without changing the color gamut. For example, named colors as well as `rgb()` and `hsl()` colors are converted to hex notation, using hex alpha notation when supported by your browser targets.\n\n```css\n.foo {\n  color: rgba(255, 255, 0, 0.8)\n}\n```\n\nminifies to:\n\n```css\n.foo{color:#ff0c}\n```\n\nNote that only colors in the RGB gamut (including HSL and HWB) are converted to hex. Colors in other color spaces such as LAB or P3 are preserved.\n\nIn addition to static colors, Lightning CSS also supports many color functions such as `color-mix()` and relative colors. When all components are known, Lightning CSS precomputes the result of these functions and outputs a static color. This both reduces the size and makes the syntax compatible with more browser targets.\n\n```css\n.foo {\n  color: rgb(from rebeccapurple r calc(g * 2) b);\n  background: color-mix(in hsl, hsl(120deg 10% 20%), hsl(30deg 30% 40%));\n}\n```\n\nminifies to:\n\n```css\n.foo{color:#669;background:#545c3d}\n```\n\nNote that these conversions cannot be performed when any of the components include CSS variables.\n\n### Normalizing values\n\nLightning CSS parses all properties and values according to the CSS specification, filling in defaults where appropriate. When minifying, it omits default values where possible since the browser will fill those in when parsing.\n\n```css\n.foo {\n  background: 0% 0% / auto repeat scroll padding-box border-box red;\n}\n```\n\nminifies to:\n\n```css\n.foo{background:red}\n```\n\nIn addition to removing defaults, Lightning CSS also omits quotes, whitespace, and optional delimiters where possible. It also converts values to shorter equivalents where possible.\n\n```css\n.foo {\n  font-weight: bold;\n  background-position: center center;\n  background-image: url(\"logo.png\");\n}\n```\n\nminifies to:\n\n```css\n.foo{background-image:url(logo.png);background-position:50%;font-weight:700}\n```\n\n### CSS grid templates\n\nLightning CSS will minify the `grid-template-areas` property to remove unnecessary whitespace and placeholders in template strings.\n\n```css\n.foo {\n  grid-template-areas: \"head head\"\n                       \"nav  main\"\n                       \"foot ....\";\n}\n```\n\nminifies to:\n\n```css\n.foo{grid-template-areas:\"head head\"\"nav main\"\"foot.\"}\n```\n\n### Reduce transforms\n\nLightning CSS will reduce CSS transform functions to shorter equivalents where possible.\n\n```css\n.foo {\n  transform: translate(0, 50px);\n}\n```\n\nminifies to:\n\n```css\n.foo{transform:translateY(50px)}\n```\n\nIn addition, the `matrix()` and `matrix3d()` functions are converted to their equivalent transforms when shorter:\n\n```css\n.foo {\n  transform: matrix3d(1, 0, 0, 0, 0, 0.707106, 0.707106, 0, 0, -0.707106, 0.707106, 0, 100, 100, 10, 1);\n}\n```\n\nminifies to:\n\n```css\n.foo{transform:translate3d(100px,100px,10px)rotateX(45deg)}\n```\n\nWhen a matrix would be shorter, individual transform functions are converted to a single matrix instead:\n\n```css\n.foo {\n  transform: translate(100px, 200px) rotate(45deg) skew(10deg) scale(2);\n}\n```\n\nminifies to:\n\n```css\n.foo{transform:matrix(1.41421,1.41421,-1.16485,1.66358,100,200)}\n```\n\n## Unused symbols\n\nIf you know that certain class names, ids, `@keyframes` rules, CSS variables, or other CSS identifiers are unused (for example as part of a larger full project analysis), you can use the `unusedSymbols` option to remove them.\n\n```js\nlet { code, map } = transform({\n  // ...\n  minify: true,\n  unusedSymbols: ['foo', 'fade-in', '--color']\n});\n```\n\nWith this configuration, the following CSS:\n\n```css\n:root {\n  --color: red;\n}\n\n.foo {\n  color: var(--color);\n}\n\n@keyframes fade-in {\n  from { opacity: 0 }\n  to { opacity: 1 }\n}\n\n.bar {\n  color: green;\n}\n```\n\nminifies to:\n\n```css\n.bar{color:green}\n```\n"
  },
  {
    "path": "website/pages/transforms.md",
    "content": "<aside>\n\n[[toc]]\n\n</aside>\n\n# Custom transforms\n\nThe Lightning CSS visitor API can be used to implement custom transform plugins in JavaScript. It is designed to enable custom non-standard extensions to CSS, making your code easier to author while shipping standard CSS to the browser. You can implement extensions such as custom shorthand properties or additional at-rules (e.g. mixins), build time transforms (e.g. convert units, inline constants, etc.), CSS rule analysis, and much more.\n\nCustom transforms have a build time cost: it can be around 2x slower to compile with a JS visitor than without. This means visitors should generally be used to implement custom, non-standard CSS extensions. Common standard transforms such as compiling modern standard CSS features (and draft specs) for older browsers should be done in Rust as part of Lightning CSS itself. Please open an issue if there's a feature we don't handle yet.\n\n## Visitors\n\nCustom transforms are implemented by passing a `visitor` object to the Lightning CSS Node API. A visitor includes one or more functions which are called for specific value types such as `Rule`, `Property`, or `Length`. In general, you should try to be as specific as possible about the types of values you want to handle. This way, Lightning CSS needs to call into JS as infrequently as possible, with the smallest objects possible, which improves performance. See the [TypeScript definitions](https://github.com/parcel-bundler/lightningcss/blob/eb49015cf887ae720b80a2856ccbdf61bf940ef1/node/index.d.ts#L184-L214) for a full list of available visitor functions.\n\nVisitors can return a new value to update it. Each visitor accepts a different type of value, and usually expects the same type in return. This example multiplies all lengths by 2:\n\n```js\nimport { transform } from 'lightningcss';\n\nlet res = transform({\n  filename: 'test.css',\n  minify: true,\n  code: Buffer.from(`\n    .foo {\n      width: 12px;\n    }\n  `),\n  visitor: {\n    Length(length) {\n      return {\n        unit: length.unit,\n        value: length.value * 2\n      }\n    }\n  }\n});\n\nassert.equal(res.code.toString(), '.foo{width:24px}');\n```\n\nSome visitor functions accept an array as a return value, enabling you to replace one value with multiple, or remove a value by returning an empty array. You can also provide an object instead of a function to further reduce the number of times a visitor is called. For example, when providing a `Property` visitor, you can use an object with keys for specific property names. This improves performance by only calling your visitor function when needed.\n\nThis example adds `-webkit-overflow-scrolling: touch` before any `overflow` properties.\n\n```js\nlet res = transform({\n  filename: 'test.css',\n  minify: true,\n  code: Buffer.from(`\n    .foo {\n      overflow: auto;\n    }\n  `),\n  visitor: {\n    Property: {\n      overflow(property) {\n        return [{\n          property: 'custom',\n          value: {\n            name: '-webkit-overflow-scrolling',\n            value: [{\n              type: 'token',\n              value: {\n                type: 'ident',\n                value: 'touch'\n              }\n            }]\n          }\n        }, property];\n      },\n    }\n  }\n});\n\nassert.equal(res.code.toString(), '.foo{-webkit-overflow-scrolling:touch;overflow:auto}');\n```\n\n## Value types\n\nThe Lightning CSS AST is very detailed – each CSS property has a specific value type with all parts fully normalized. For example, a shorthand property such as `background` includes values for all of its sub-properties such as `background-color`, `background-image`, `background-position`, etc. This makes it both easier and faster for custom transforms to correctly handle all value types without reimplementing parsing. See the [TypeScript definitions](https://github.com/parcel-bundler/lightningcss/blob/master/node/ast.d.ts) for full documentation of all values.\n\nKnown property values can be either _parsed_ or _unparsed_. Parsed values are fully expanded following the CSS specification. Unparsed values could not be parsed according to the grammar, and are stored as raw CSS tokens. This may occur because the value is invalid, or because it included unknown values such as CSS variables. Each property visitor function will need to handle both types of values.\n\n```js\ntransform({\n  code: Buffer.from(`\n    .foo { width: 12px }\n    .bar { width: var(--w) }\n  `),\n  visitor: {\n    Property: {\n      width(v) {\n        if (v.property === 'unparsed') {\n          // Handle unparsed value, e.g. `var(--w)`\n        } else {\n          // Handle parsed value, e.g. `12px`\n        }\n      }\n    }\n  }\n});\n```\n\nUnknown properties, including custom properties, have the property type \"custom\". These values are also stored as raw CSS tokens. To visit custom properties, use the `custom` visitor function, or an object to filter by name. For example, to handle a custom `size` property and expand it to `width` and `height`, the following transform might be used.\n\n```js\nlet res = transform({\n  minify: true,\n  code: Buffer.from(`\n    .foo {\n      size: 12px;\n    }\n  `),\n  visitor: {\n    Property: {\n      custom: {\n        size(property) {\n          // Handle the size property when the value is a length.\n          if (property.value[0].type === 'length') {\n            let value = {\n              type: 'length-percentage',\n              value: { type: 'dimension', value: property.value[0].value }\n            };\n\n            return [\n              { property: 'width', value },\n              { property: 'height', value }\n            ];\n          }\n        }\n      }\n    }\n  }\n});\n\nassert.equal(res.code.toString(), '.foo{width:12px;height:12px}');\n```\n\n## Raw values\n\nThe Lightning CSS AST is very detailed, which is really useful when you need to transform it. However, it can be tedious to construct a full AST from scratch when returning entirely new values from a visitor. That's when raw values come in handy. You can return a `raw` property containing a string of CSS syntax from visitors that return declarations (i.e. properties) and tokens, and Lightning CSS will parse it for you and put it into the AST.\n\nThis example implements a custom `color` function, which returns a raw CSS color value as a string, rather than constructing the whole AST.\n\n```js\nlet res = transform({\n  minify: true,\n  code: Buffer.from(`\n    .foo {\n      color: color('red');\n    }\n  `),\n  visitor: {\n    Function: {\n      color() {\n        return { raw: 'rgb(255, 0, 0)' };\n      }\n    }\n  }\n});\n\nassert.equal(res.code.toString(), '.foo{color:red}');\n```\n\n## Entry and exit visitors\n\nBy default, visitors are called when traversing downward through the tree (a pre-order traversal). This means each node is visited before its children. Sometimes it is useful to process a node after its children instead (a post-order traversal). This can be done by using an `Exit` visitor function, such as `FunctionExit`.\n\nFor example, if you had a function visitor to double a length argument, and a visitor to replace an environment variable with a value, you could use an exit visitor to process the function after its arguments.\n\n```js\nlet res = transform({\n  filename: 'test.css',\n  minify: true,\n  code: Buffer.from(`\n    .foo {\n      padding: double(env(--branding-padding));\n    }\n  `),\n  visitor: {\n    FunctionExit: {\n      // This will run after the EnvironmentVariable visitor, below.\n      double(f) {\n        if (f.arguments[0].type === 'length') {\n          return {\n            type: 'length',\n            value: {\n              unit: f.arguments[0].value.unit,\n              value: f.arguments[0].value.value * 2\n            }\n          };\n        }\n      }\n    },\n    EnvironmentVariable: {\n      // This will run before the FunctionExit visitor, above.\n      '--branding-padding': () => ({\n        type: 'length',\n        value: {\n          unit: 'px',\n          value: 20\n        }\n      })\n    }\n  }\n});\n\nassert.equal(res.code.toString(), '.foo{padding:40px}');\n```\n\n## Composing visitors\n\nMultiple visitors can be combined into one using the `composeVisitors` function. This lets you reuse visitors between projects by publishing them as plugins. The AST is visited in a single pass, running the functions from each visitor object as if they were written together.\n\n```js\nimport { transform, composeVisitors } from 'lightningcss';\n\nlet environmentVisitor = {\n  EnvironmentVariable: {\n    '--branding-padding': () => ({\n      type: 'length',\n      value: {\n        unit: 'px',\n        value: 20\n      }\n    })\n  }\n};\n\nlet doubleFunctionVisitor = {\n  FunctionExit: {\n    double(f) {\n      if (f.arguments[0].type === 'length') {\n        return {\n          type: 'length',\n          value: {\n            unit: f.arguments[0].value.unit,\n            value: f.arguments[0].value.value * 2\n          }\n        };\n      }\n    }\n  }\n};\n\nlet res = transform({\n  filename: 'test.css',\n  minify: true,\n  code: Buffer.from(`\n    .foo {\n      padding: double(env(--branding-padding));\n    }\n  `),\n  visitor: composeVisitors([environmentVisitor, doubleFunctionVisitor])\n});\n\nassert.equal(res.code.toString(), '.foo{padding:40px}');\n```\n\nEach visitor object has the opportunity to visit every value once. If a visitor returns a new value, that value is visited by the other visitor objects but not again by the original visitor that created it. If other visitors subsequently modify the value, the previous visitors will not revisit the value. This is to avoid infinite loops.\n\n## Unknown at-rules\n\nBy default, unknown at-rules are stored in the AST as raw tokens. This allows you to interpret them however you like by writing a custom visitor. The following example allows declaring static variables using named at-rules, and inlines them when an `at-keyword` token is seen:\n\n```js\nlet declared = new Map();\nlet res = transform({\n  filename: 'test.css',\n  minify: true,\n  code: Buffer.from(`\n    @blue #056ef0;\n\n    .menu_link {\n      background: @blue;\n    }\n  `),\n  visitor: {\n    Rule: {\n      unknown(rule) {\n        declared.set(rule.name, rule.prelude);\n        return [];\n      }\n    },\n    Token: {\n      'at-keyword'(token) {\n        return declared.get(token.value);\n      }\n    }\n  }\n});\n\nassert.equal(res.code.toString(), '.menu_link{background:#056ef0}');\n```\n\n## Custom at-rules\n\nRaw tokens as stored in unknown at-rules are fine for simple cases, but in more complex cases, you may wish to interpret a custom at-rule body as a standard CSS declaration list or rule list. However, by default, Lightning CSS does not know how unknown rules should be parsed. You can define their syntax using the `customAtRules` option.\n\nThe syntax of the at-rule prelude can be defined with a [CSS syntax string](https://drafts.css-houdini.org/css-properties-values-api/#syntax-strings), which Lightning CSS will interpret and use to validate the input CSS. This uses the same syntax as the [@property](https://developer.mozilla.org/en-US/docs/Web/CSS/@property) rule. The body syntax is defined using one of the following options:\n\n* `\"declaration-list\"` – A list of CSS declarations (property value pairs), as in a style rule or other at-rules like `@font-face`.\n* `\"rule-list\"` – A list of nested CSS rules, including style rules and at rules. Directly nested declarations with CSS nesting are not allowed. This matches how rules like `@keyframes` are parsed.\n* `\"style-block\"` – A list of CSS declarations and/or nested rules. This matches the behavior of rules like `@media` and `@supports` which support directly nested declarations when inside a style rule. Note that the [nesting](transpilation.html#nesting) and [targets](transpilation.html#browser-targets) options must be defined for nesting to be compiled.\n\nThis example defines two custom at-rules. `@mixin` defines a reusable style block, supporting both directly nested declarations and nested rules. A visitor function registers the mixin in a map and removes the custom rule. `@apply` looks up the requested mixin in the map and returns the nested rules, which are inlined into the parent.\n\n```js\nlet mixins = new Map();\nlet res = transform({\n  filename: 'test.css',\n  minify: true,\n  targets: { chrome: 100 << 16 },\n  code: Buffer.from(`\n    @mixin color {\n      color: red;\n\n      &.bar {\n        color: yellow;\n      }\n    }\n\n    .foo {\n      @apply color;\n    }\n  `),\n  customAtRules: {\n    mixin: {\n      prelude: '<custom-ident>',\n      body: 'style-block'\n    },\n    apply: {\n      prelude: '<custom-ident>'\n    }\n  },\n  visitor: {\n    Rule: {\n      custom: {\n        mixin(rule) {\n          mixins.set(rule.prelude.value, rule.body.value);\n          return [];\n        },\n        apply(rule) {\n          return mixins.get(rule.prelude.value);\n        }\n      }\n    }\n  }\n});\n\nassert.equal(res.code.toString(), '.foo{color:red}.foo.bar{color:#ff0}');\n```\n\n## Dependencies\n\nVisitors can emit dependencies so the caller (e.g. bundler) knows to re-run the transformation or invalidate a cache when those files change. These are returned as part of the result's `dependencies` property (along with other dependencies when the `analyzeDependencies` option is enabled).\n\nBy passing a function to the `visitor` option instead of an object, you get access to the `addDependency` function. This accepts a dependency object with `type: 'file'` or `type: 'glob'`. File dependencies invalidate the transformation whenever the `filePath` changes (created, updated, or deleted). Glob dependencies invalidate whenever any file matched by the glob changes. `composeVisitors` also supports function visitors.\n\nBy default, Lightning CSS does not do anything with these dependencies except return them to the caller. It's the caller's responsibility to implement file watching and cache invalidation accordingly.\n\n```js\nlet res = transform({\n  filename: 'test.css',\n  code: Buffer.from(`\n    @dep \"foo.js\";\n    @glob \"**/*.json\";\n\n    .foo {\n      width: 32px;\n    }\n  `),\n  visitor: ({addDependency}) => ({\n    Rule: {\n      unknown: {\n        dep(rule) {\n          let file = rule.prelude[0].value.value;\n          addDependency({\n            type: 'file',\n            filePath: file\n          });\n          return [];\n        },\n        glob(rule) {\n          let glob = rule.prelude[0].value.value;\n          addDependency({\n            type: 'glob',\n            glob\n          });\n          return [];\n        }\n      }\n    }\n  })\n});\n\nassert.equal(res.dependencies, [\n  {\n    type: 'file',\n    filePath: 'foo.js'\n  },\n  {\n    type: 'glob',\n    filePath: '**/*.json'\n  }\n]);\n```\n\n## Examples\n\nFor examples of visitors that perform a variety of real world tasks, see the Lightning CSS [visitor tests](https://github.com/parcel-bundler/lightningcss/blob/master/node/test/visitor.test.mjs).\n\n## Publishing a plugin\n\nVisitor plugins can be published to npm in order to share them with others. Plugin packages simply consist of an exported visitor object, which users can compose with other plugins via the `composeVisitors` function as described above.\n\n```js\n// lightningcss-plugin-double-function\nexport default {\n  FunctionExit: {\n    double(f) {\n      // ...\n    }\n  }\n};\n```\n\nPlugins can also export a function in order to accept options.\n\n```js\n// lightningcss-plugin-env\nexport default (values) => ({\n  EnvironmentVariable(env) {\n    return values[env.name];\n  }\n});\n```\n\nPlugin package names should start with `lightningcss-plugin-` and be descriptive about what they do, e.g. `lightningcss-plugin-double-function`. In addition, they should include the `lightningcss-plugin` keyword in their package.json so people can find them on npm.\n\n```json\n{\n  \"name\": \"lightningcss-plugin-double-function\",\n  \"keywords\": [\"lightningcss-plugin\"],\n  \"main\": \"plugin.mjs\"\n}\n```\n\n## Using plugins\n\nTo use a published visitor plugin, install the package from npm, import it, and use the `composeVisitors` function as described above.\n\n```js\nimport { transform, composeVisitors } from 'lightningcss';\nimport environmentVisitor from 'lightningcss-plugin-environment';\nimport doubleFunctionVisitor from 'lightningcss-plugin-double-function';\n\nlet res = transform({\n  filename: 'test.css',\n  minify: true,\n  code: Buffer.from(`\n    .foo {\n      padding: double(env(--branding-padding));\n    }\n  `),\n  visitor: composeVisitors([\n    environmentVisitor({\n      '--branding-padding': {\n        type: 'length',\n        value: {\n          unit: 'px',\n          value: 20\n        }\n      }\n    }),\n    doubleFunctionVisitor\n  ])\n});\n\nassert.equal(res.code.toString(), '.foo{padding:40px}');\n```\n"
  },
  {
    "path": "website/pages/transpilation.md",
    "content": "<aside>\n\n[[toc]]\n\n</aside>\n\n# Transpilation\n\nLightning CSS includes support for transpiling modern CSS syntax to support older browsers, including vendor prefixing and syntax lowering.\n\n## Browser targets\n\nBy default Lightning CSS does not perform any transpilation of CSS syntax for older browsers. This means that if you write your code using modern syntax or without vendor prefixes, that’s what Lightning CSS will output. You can declare your app’s supported browsers using the `targets` option. When this is declared, Lightning CSS will transpile your code accordingly to ensure compatibility with your supported browsers.\n\nTargets are defined using an object that specifies the minimum version of each browser you want to support. The easiest way to build a targets object is to use [browserslist](https://browserslist.dev). This lets you use a query that automatically updates over time as new browser versions are released, market share changes, etc. The following example will return a targets object listing browsers with >= 0.25% market share.\n\n```js\nimport browserslist from 'browserslist';\nimport { transform, browserslistToTargets } from 'lightningcss';\n\n// Call this once per build.\nlet targets = browserslistToTargets(browserslist('>= 0.25%'));\n\n// Use `targets` for each file you transform.\nlet { code, map } = transform({\n  // ...\n  targets\n});\n```\n\nFor the best performance, you should call browserslist once for your whole build process, and reuse the same `targets` object when calling `transform` for each file.\n\nUnder the hood, `targets` are represented using an object that maps browser names to minimum versions. Version numbers are represented using a single 24-bit number, with one semver component (major, minor, patch) per byte. For example, this targets object would represent Safari 13.2.0.\n\n```js\nlet targets = {\n  safari: (13 << 16) | (2 << 8)\n};\n```\n\n### CLI\n\nWhen using the CLI, targets can be provided by passing a [browserslist](https://browserslist.dev) query to the `--targets` option. Alternatively, if the `--browserslist` option is provided, then `lightningcss` finds browserslist configuration, selects queries by environment and loads the resulting queries as targets.\n\nConfiguration discovery and targets resolution is modeled after the original `browserslist` Node package. The configuration is resolved in the following order:\n\n- If a `BROWSERSLIST` environment variable is present, then load targets from its value.\n- If a `BROWSERSLIST_CONFIG` environment variable is present, then load the browserslist configuration from the file at the provided path.\n- If none of the above apply, then find, parse and use targets from the first `browserslist`, `.browserslistrc`, or `package.json` configuration file in any parent directory.\n\nBrowserslist configuration files may contain sections denoted by square brackets. Use these to specify different targets for different environments. Targets which are not placed in a section are added to `defaults` and used if no section matches the environment.\n\n```ini\n# Defaults, applied when no other section matches the provided environment.\nfirefox ESR\n\n# Targets applied only to the staging environment.\n[staging]\nsamsung >= 4\n```\n\nWhen using parsed configuration from `browserslist`, `.browserslistrc`, or `package.json` configuration files, the environment is determined by:\n\n- the `BROWSERSLIST_ENV` environment variable if present\n- the `NODE_ENV` environment variable if present\n- otherwise `\"production\"` is used.\n\nIf no targets are found for the resulting environment, then the `defaults` configuration section is used.\n\n### Feature flags\n\nIn most cases, setting the `targets` option and letting Lightning CSS automatically compile your CSS works great. However, in other cases you might need a little more control over exactly what features are compiled and which are not. That's where the `include` and `exclude` options come in.\n\nThe `include` and `exclude` options allow you to explicitly turn on or off certain features. These override the defaults based on the provided browser targets. For example, you might want to only compile colors, and handle auto prefixing or other features with another tool. Or you may want to handle everything except vendor prefixing with Lightning CSS. These options make that possible.\n\nThe `include` and `exclude` options are configured using the `Features` enum, which can be imported from `lightningcss`. You can bitwise OR multiple flags together to turn them on or off.\n\n```js\nimport { transform, Features } from 'lightningcss';\n\nlet { code, map } = transform({\n  // ...\n  targets,\n  // Always compile colors and CSS nesting, regardless of browser targets.\n  include: Features.Colors | Features.Nesting,\n  // Never add any vendor prefixes, regardless of targets.\n  exclude: Features.VendorPrefixes\n});\n```\n\nHere is a full list of available flags, described in the sections below:\n\n<div class=\"features\">\n\n* `Nesting`\n* `NotSelectorList`\n* `DirSelector`\n* `LangSelectorList`\n* `IsSelector`\n* `TextDecorationThicknessPercent`\n* `MediaIntervalSyntax`\n* `MediaRangeSyntax`\n* `CustomMediaQueries`\n* `ClampFunction`\n* `ColorFunction`\n* `OklabColors`\n* `LabColors`\n* `P3Colors`\n* `HexAlphaColors`\n* `SpaceSeparatedColorNotation`\n* `LightDark`\n* `FontFamilySystemUi`\n* `DoublePositionGradients`\n* `VendorPrefixes`\n* `LogicalProperties`\n* `Selectors` – shorthand for `Nesting | NotSelectorList | DirSelector | LangSelectorList | IsSelector`\n* `MediaQueries` – shorthand for `MediaIntervalSyntax | MediaRangeSyntax | CustomMediaQueries`\n* `Colors` – shorthand for `ColorFunction | OklabColors | LabColors | P3Colors | HexAlphaColors | SpaceSeparatedColorNotation | LightDark`\n\n</div>\n\n## Vendor prefixing\n\nBased on your configured browser targets, Lightning CSS automatically adds vendor prefixed fallbacks for many CSS features. For example, when using the [`image-set()`](https://developer.mozilla.org/en-US/docs/Web/CSS/image/image-set()) function, Lightning CSS will output a fallback `-webkit-image-set()` value as well, since Chrome does not yet support the unprefixed value.\n\n```css\n.logo {\n  background: image-set(url(logo.png) 2x, url(logo.png) 1x);\n}\n```\n\ncompiles to:\n\n```css\n.logo {\n  background: -webkit-image-set(url(logo.png) 2x, url(logo.png) 1x);\n  background: image-set(\"logo.png\" 2x, \"logo.png\");\n}\n```\n\nIn addition, if your CSS source code (or more likely a library) includes unnecessary vendor prefixes, Lightning CSS will automatically remove them to reduce bundle sizes. For example, when compiling for modern browsers, prefixed versions of the `transition` property will be removed, since the unprefixed version is supported by all browsers.\n\n```css\n.button {\n  -webkit-transition: background 200ms;\n  -moz-transition: background 200ms;\n  transition: background 200ms;\n}\n```\n\nbecomes:\n\n```css\n.button {\n  transition: background .2s;\n}\n```\n\n## Syntax lowering\n\nLightning CSS automatically compiles many modern CSS syntax features to more compatible output that is supported in your target browsers.\n\n### Nesting\n\nThe [CSS Nesting](https://drafts.csswg.org/css-nesting/) spec enables style rules to be nested, with the selectors of the child rules extending the parent selector in some way. This is very commonly supported by CSS pre-processors like Sass, but with this spec, it will eventually be supported natively in browsers. Lightning CSS compiles this syntax to un-nested style rules that are supported in all browsers today.\n\n```css\n.foo {\n  color: blue;\n\n  .bar {\n    color: red;\n  }\n}\n```\n\nis equivalent to:\n\n```css\n.foo {\n  color: blue;\n}\n\n.foo .bar {\n  color: red;\n}\n```\n\n[Conditional rules](https://drafts.csswg.org/css-nesting/#conditionals) such as `@media` may also be nested within a style rule, without repeating the selector. For example:\n\n```css\n.foo {\n  display: grid;\n\n  @media (orientation: landscape) {\n    grid-auto-flow: column;\n  }\n}\n```\n\nis equivalent to:\n\n```css\n.foo {\n  display: grid;\n}\n\n@media (orientation: landscape) {\n  .foo {\n    grid-auto-flow: column;\n  }\n}\n```\n\n### Color mix\n\nThe [`color-mix()`](https://drafts.csswg.org/css-color-5/#color-mix) function allows you to mix two colors by the specified amount in a certain color space. Lightning CSS will evaluate this function statically when all components are known (i.e. not variables).\n\n```css\n.foo {\n  color: color-mix(in hsl, hsl(120deg 10% 20%) 25%, hsl(30deg 30% 40%));\n}\n```\n\ncompiles to:\n\n```css\n.foo {\n  color: #706a43;\n}\n```\n\n### Relative colors\n\nRelative colors allow you to modify the components of a color using math functions. In addition, you can convert colors between color spaces. Lightning CSS performs these calculations statically when all components are known (i.e. not variables).\n\nThis example lightens `slateblue` by 10% in the LCH color space.\n\n```css\n.foo {\n  color: lch(from slateblue calc(l * 1.1) c h);\n}\n```\n\ncompiles to:\n\n```css\n.foo {\n  color: lch(49.0282% 65.7776 296.794);\n}\n```\n\n### LAB colors\n\nLightning CSS will convert [`lab()`](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/lab()), [`lch()`](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/lch()), [`oklab()`](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/oklab), and [`oklch()`](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/oklch) colors to fallback values for unsupported browsers when needed. These functions allow you to define colors in higher gamut color spaces, making it possible to use colors that cannot be represented by RGB.\n\n```css\n.foo {\n  color: lab(40% 56.6 39);\n}\n```\n\ncompiles to:\n\n```css\n.foo {\n  color: #b32323;\n  color: color(display-p3 .643308 .192455 .167712);\n  color: lab(40% 56.6 39);\n}\n```\n\nAs shown above, a `display-p3` fallback is included in addition to RGB when a target browser supports the P3 color space. This preserves high color gamut colors when possible.\n\n### Color function\n\nLightning CSS converts the [`color()`](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/color()) function to RGB when needed for compatibility with older browsers. This allows you to use predefined color spaces such as `display-p3`, `xyz`, and `a98-rgb`.\n\n```css\n.foo {\n  color: color(a98-rgb 0.44091 0.49971 0.37408);\n}\n```\n\ncompiles to:\n\n```css\n.foo {\n  color: #6a805d;\n  color: color(a98-rgb .44091 .49971 .37408);\n}\n```\n\n### HWB colors\n\nLightning CSS converts [`hwb()`](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/hwb) colors to RGB.\n\n```css\n.foo {\n  color: hwb(194 0% 0%);\n}\n```\n\ncompiles to:\n\n```css\n.foo {\n  color: #00c4ff;\n}\n```\n\n### Color notation\n\nSpace separated color notation is converted to hex when needed. Hex colors with alpha are also converted to `rgba()` when unsupported by all targets.\n\n```css\n.foo {\n  color: #7bffff80;\n  background: rgb(123 255 255);\n}\n```\n\ncompiles to:\n\n```css\n.foo {\n  color: rgba(123, 255, 255, .5);\n  background: #7bffff;\n}\n```\n\n### light-dark() color function\n\nThe [`light-dark()`](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/light-dark) function allows you to specify a light mode and dark mode color in a single declaration, without needing to write a separate media query rule. In addition, it uses the [`color-scheme`](https://developer.mozilla.org/en-US/docs/Web/CSS/color-scheme) property to control which theme to use, which allows you to set it programmatically. The `color-scheme` property also inherits so themes can be nested and the nearest ancestor color scheme applies.\n\nLightning CSS converts the `light-dark()` function to use CSS variable fallback when your browser targets don't support it natively. For this to work, you must set the `color-scheme` property on an ancestor element. The following example shows how you can support both operating system and programmatic overrides for the color scheme.\n\n```css\nhtml {\n  color-scheme: light dark;\n}\n\nhtml[data-theme=light] {\n  color-scheme: light;\n}\n\nhtml[data-theme=dark] {\n  color-scheme: dark;\n}\n\nbutton {\n  background: light-dark(#aaa, #444);\n}\n```\n\ncompiles to:\n\n```css\nhtml {\n  --lightningcss-light: initial;\n  --lightningcss-dark: ;\n  color-scheme: light dark;\n}\n\n@media (prefers-color-scheme: dark) {\n  html {\n    --lightningcss-light: ;\n    --lightningcss-dark: initial;\n  }\n}\n\nhtml[data-theme=\"light\"] {\n  --lightningcss-light: initial;\n  --lightningcss-dark: ;\n  color-scheme: light;\n}\n\nhtml[data-theme=\"dark\"] {\n  --lightningcss-light: ;\n  --lightningcss-dark: initial;\n  color-scheme: dark;\n}\n\nbutton {\n  background: var(--lightningcss-light, #aaa) var(--lightningcss-dark, #444);\n}\n```\n\n### Logical properties\n\nCSS [logical properties](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Logical_Properties) allow you to define values in terms of writing direction, so that UIs mirror in right-to-left languages. Lightning CSS will compile these to use the `:dir()` selector when unsupported. If the `:dir()` selector is unsupported, it is compiled as described [below](#%3Adir()-selector).\n\n```css\n.foo {\n  border-start-start-radius: 20px\n}\n```\n\ncompiles to:\n\n```css\n.foo:dir(ltr) {\n  border-top-left-radius: 20px;\n}\n\n.foo:dir(rtl) {\n  border-top-right-radius: 20px;\n}\n```\n\n\n### :dir() selector\n\nThe [`:dir()`](https://developer.mozilla.org/en-US/docs/Web/CSS/:dir) selector matches elements based on the writing direction. Lightning CSS compiles this to use the `:lang()` selector when unsupported, which approximates this behavior as closely as possible.\n\n```css\na:dir(rtl) {\n  color:red\n}\n```\n\ncompiles to:\n\n```css\na:lang(ae, ar, arc, bcc, bqi, ckb, dv, fa, glk, he, ku, mzn, nqo, pnb, ps, sd, ug, ur, yi) {\n  color: red;\n}\n```\n\nIf multiple arguments to `:lang()` are unsupported, it is compiled as described [below](#%3Alang()-selector).\n\n### :lang() selector\n\nThe [`:lang()`](https://developer.mozilla.org/en-US/docs/Web/CSS/:lang) selector matches elements based on their language. Some browsers do not support multiple arguments to this function, so Lightning CSS compiles them to use `:is()` when needed.\n\n```css\na:lang(en, fr) {\n  color:red\n}\n```\n\ncompiles to:\n\n```css\na:is(:lang(en), :lang(fr)) {\n  color: red;\n}\n```\n\nWhen the `:is()` selector is unsupported, it is compiled as described [below](#%3Ais()-selector).\n\n### :is() selector\n\nThe [`:is()`](https://developer.mozilla.org/en-US/docs/Web/CSS/:is) matches when one of its arguments matches. Lightning CSS falls back to the `:-webkit-any` and `:-moz-any` prefixed selectors.\n\n```css\np:is(:first-child, .lead) {\n  margin-top: 0;\n}\n```\n\ncompiles to:\n\n```css\np:-webkit-any(:first-child, .lead) {\n  margin-top: 0;\n}\n\np:-moz-any(:first-child, .lead) {\n  margin-top: 0;\n}\n\np:is(:first-child, .lead) {\n  margin-top: 0;\n}\n```\n\n<div class=\"warning\">\n\n**Note**: The prefixed versions of these selectors do not support complex selectors (e.g. selectors with combinators). Lightning CSS will only output prefixes if the arguments are simple selectors. Complex selectors in `:is()` are not currently compiled.\n\n</div>\n\n### :not() selector\n\nThe [`:not()`](https://developer.mozilla.org/en-US/docs/Web/CSS/:not) selector can accept multiple arguments, and matches if none of the arguments match. Some older browsers only support a single argument, so Lightning CSS compiles this when needed. The `:is` selector is used to ensure the specificity remains the same, with fallback to `-webkit-any` and `-moz-any` as needed (described above).\n\n```css\np:not(:first-child, .lead) {\n  margin-top: 1em;\n}\n```\n\ncompiles to:\n\n```css\np:not(:is(:first-child, .lead)) {\n  margin-top: 1em;\n}\n```\n\n### Math functions\n\nLightning CSS simplifies [math functions](https://w3c.github.io/csswg-drafts/css-values/#math) including `clamp()`, `round()`, `rem()`, `mod()`, `abs()`, and `sign()`, [trigonometric functions](https://w3c.github.io/csswg-drafts/css-values/#trig-funcs) including `sin()`, `cos()`, `tan()`, `asin()`, `acos()`, `atan()`, and `atan2()`, and [exponential functions](https://w3c.github.io/csswg-drafts/css-values/#exponent-funcs) including `pow()`, `log()`, `sqrt()`, `exp()`, and `hypot()` when all arguments are known (i.e. not variables). In addition, the numeric constants `e`, `pi`, `infinity`, `-infinity`, and `NaN` are supported in all calculations.\n\n```css\n.foo {\n  width: round(calc(100px * sin(pi / 4)), 5px);\n}\n```\n\ncompiles to:\n\n```css\n.foo {\n  width: 70px;\n}\n```\n\n### Media query ranges\n\n[Media query range syntax](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries/Using_media_queries#syntax_improvements_in_level_4) allows defining media queries using comparison operators to create ranges and intervals. Lightning CSS compiles this to the corresponding `min` and `max` media features when needed.\n\n```css\n@media (480px <= width <= 768px) {\n  .foo { color: red }\n}\n```\n\ncompiles to:\n\n```css\n@media (min-width: 480px) and (max-width: 768px) {\n  .foo { color: red }\n}\n```\n\n### Shorthands\n\nLightning CSS compiles the following shorthands to corresponding longhands when the shorthand is not supported in all target browsers:\n\n* Alignment shorthands: [place-items](https://developer.mozilla.org/en-US/docs/Web/CSS/place-items), [place-content](https://developer.mozilla.org/en-US/docs/Web/CSS/place-content), [place-self](https://developer.mozilla.org/en-US/docs/Web/CSS/place-self)\n* [Overflow shorthand](https://developer.mozilla.org/en-US/docs/Web/CSS/overflow) with multiple values (e.g. `overflow: hidden auto`)\n* [text-decoration](https://developer.mozilla.org/en-US/docs/Web/CSS/text-decoration) with thickness, style, color, etc.\n* Two value [display](https://developer.mozilla.org/en-US/docs/Web/CSS/display) syntax (e.g. `display: inline flex`)\n\n### Double position gradients\n\nCSS gradients support using two positions in a color stop to repeat the color at two subsequent positions. When unsupported, Lightning CSS compiles it.\n\n```css\n.foo {\n  background: linear-gradient(green, red 30% 40%, pink);\n}\n```\n\ncompiles to:\n\n```css\n.foo {\n  background: linear-gradient(green, red 30%, red 40%, pink);\n}\n```\n\n### system-ui font\n\nThe `system-ui` font allows you to use the operating system default font. When unsupported, Lightning CSS compiles it to a font stack that works across major platforms.\n\n```css\n.foo {\n  font-family: system-ui;\n}\n```\n\ncompiles to:\n\n```css\n.foo {\n  font-family: system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Noto Sans, Ubuntu, Cantarell, Helvetica Neue;\n}\n```\n\n## Draft syntax\n\nLightning CSS can also be configured to compile several draft specs that are not yet available natively in any browser. Because these are drafts and the syntax can still change, they must be enabled manually in your project.\n\n### Custom media queries\n\nSupport for [custom media queries](https://drafts.csswg.org/mediaqueries-5/#custom-mq) is included in the Media Queries Level 5 draft spec. This allows you to define media queries that are reused in multiple places within a CSS file. Lightning CSS will perform this substitution ahead of time when this feature is enabled.\n\nFor example:\n\n```css\n@custom-media --modern (color), (hover);\n\n@media (--modern) and (width > 1024px) {\n  .a { color: green; }\n}\n```\n\nis equivalent to:\n\n```css\n@media ((color) or (hover)) and (width > 1024px) {\n  .a { color: green; }\n}\n```\n\nBecause custom media queries are a draft, they are not enabled by default. To use them, enable the `customMedia` option under `drafts` when calling the Lightning CSS API. When using the CLI, enable the `--custom-media` flag.\n\n```js\nlet { code, map } = transform({\n  // ...\n  drafts: {\n    customMedia: true\n  }\n});\n```\n\n## Pseudo class replacement\n\nLightning CSS supports replacing CSS pseudo classes such as `:focus-visible` with normal CSS classes that can be applied using JavaScript. This makes it possible to polyfill these pseudo classes for older browsers.\n\n```js\nlet { code, map } = transform({\n  // ...\n  pseudoClasses: {\n    focusVisible: 'focus-visible'\n  }\n});\n```\n\nThe above configuration will result in the `:focus-visible` pseudo class in all selectors being replaced with the `.focus-visible` class. This enables you to use a JavaScript [polyfill](https://github.com/WICG/focus-visible), which will apply the `.focus-visible` class as appropriate.\n\nThe following pseudo classes may be configured as shown above:\n\n* `hover` – corresponds to the `:hover` pseudo class\n* `active` – corresponds to the `:active` pseudo class\n* `focus` – corresponds to the `:focus` pseudo class\n* `focusVisible` – corresponds to the `:focus-visible` pseudo class\n* `focusWithin` – corresponds to the `:focus-within` pseudo class\n\n## Non-standard syntax\n\nFor compatibility with other tools, Lightning CSS supports parsing some non-standard CSS syntax. This must be enabled by turning on a flag under the `nonStandard` option.\n\n```js\nlet { code, map } = transform({\n  // ...\n  nonStandard: {\n    deepSelectorCombinator: true\n  }\n});\n\n```\n\nCurrently the following features are supported:\n\n* `deepSelectorCombinator` – enables parsing the Vue/Angular `>>>` and `/deep/` selector combinators.\n"
  },
  {
    "path": "website/playground/index.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>⚡️ Lightning CSS Playground</title>\n    <style>\n      @font-face {\n        font-family:\"urbane-rounded\";\n        src:url(\"https://use.typekit.net/af/916187/00000000000000007735bfa0/30/l?primer=81a69539b194230396845be9681d114557adfb35f4cccc679c164afb4aa47365&fvd=n6&v=3\") format(\"woff2\"),url(\"https://use.typekit.net/af/916187/00000000000000007735bfa0/30/d?primer=81a69539b194230396845be9681d114557adfb35f4cccc679c164afb4aa47365&fvd=n6&v=3\") format(\"woff\"),url(\"https://use.typekit.net/af/916187/00000000000000007735bfa0/30/a?primer=81a69539b194230396845be9681d114557adfb35f4cccc679c164afb4aa47365&fvd=n6&v=3\") format(\"opentype\");\n        font-display:auto;font-style:normal;font-weight:600;font-stretch:normal;\n      }\n\n      html, body {\n        margin: 0;\n        height: 100%;\n        box-sizing: border-box;\n        font-family: -apple-system, system-ui;\n        color-scheme: dark;\n        background: #111;\n      }\n\n      body {\n        display: flex;\n        flex-direction: column;\n        gap: 5px;\n        padding: 10px;\n      }\n\n      header {\n        display: flex;\n        align-items: center;\n        margin-bottom: 5px;\n        --gold: lch(80% 82.34 80.104);\n      }\n\n      header a {\n        color: inherit;\n      }\n\n      header .github {\n        fill: currentColor;\n      }\n\n      header .logo {\n        grid-area: logo;\n        place-self: center end;\n        height: 60px;\n      }\n\n      header .logo .outer {\n        stroke-width: 30px;\n        stroke: var(--gold);\n      }\n\n      header h1 {\n        font-family: urbane-rounded, ui-rounded, system-ui;\n        font-size: 35px;\n        letter-spacing: -0.02em;\n        color: var(--gold);\n        padding: 20px 0;\n        margin: 0 20px;\n        flex: 1;\n      }\n\n      .targets {\n        display: table;\n        margin-top: 10px;\n      }\n\n      .targets label {\n        display: table-row;\n      }\n\n      .targets label span,\n      .targets label input {\n        display: table-cell;\n      }\n\n      main {\n        display: grid;\n        grid-template-areas: \"sidebar source compiled         compiled\"\n                             \"sidebar visitor compiledModules compiledDependencies\";\n        grid-template-columns: auto 2fr 1fr 1fr;\n        grid-template-rows: 1fr 1fr;\n        flex: 1;\n        gap: 10px;\n        overflow: hidden;\n      }\n\n      #sidebar {\n        grid-area: sidebar;\n        overflow: auto;\n      }\n\n      #visitor {\n        grid-area: visitor;\n        overflow: hidden;\n      }\n\n      #source {\n        grid-area: source;\n        overflow: hidden;\n      }\n\n      #source[data-expanded=true] {\n        grid-row: source-start / visitor-end;\n      }\n\n      #compiled {\n        grid-area: compiled;\n        overflow: hidden;\n      }\n\n      #compiled[data-expanded=true] {\n        grid-row: compiled-start / compiledDependencies-end;\n        grid-column: compiled-start / compiledDependencies-end;\n      }\n\n      #compiledModules {\n        grid-area: compiledModules;\n        overflow: hidden;\n      }\n\n      #compiledDependencies {\n        grid-area: compiledDependencies;\n        overflow: hidden;\n      }\n\n      #compiledModules[data-expanded=true],\n      #compiledDependencies[data-expanded=true] {\n        grid-row: compiledModules-start;\n        grid-column: compiledModules-start / compiledDependencies-end;\n      }\n\n      .cm-editor {\n        height: 100%;\n      }\n\n      label {\n        display: block;\n      }\n\n      h3 {\n        margin-bottom: 4px;\n      }\n\n      div > h3:first-child {\n        margin-top: 0;\n      }\n\n      summary {\n        font-weight: bold;\n      }\n    </style>\n  </head>\n  <body>\n    <header>\n      <svg class=\"logo\" aria-hidden=\"true\" viewBox=\"495 168 360 654\">\n        <path class=\"outer\" d=\"M594.41,805c-.71,0-1.43-.15-2.11-.47-2.2-1.03-3.34-3.48-2.72-5.83l67.98-253.71h-140.45c-1.86,0-3.57-1.04-4.44-2.69-.86-1.65-.73-3.65,.34-5.18l26.85-38.35q25.56-36.51,104.91-149.83l106.31-151.82c1.39-1.99,4.01-2.69,6.21-1.66,2.2,1.03,3.34,3.48,2.72,5.83l-67.98,253.71h140.45c1.86,0,3.57,1.04,4.43,2.69,.86,1.65,.73,3.65-.34,5.18l-238.07,340c-.96,1.37-2.51,2.13-4.1,2.13Zm-67.69-270h137.37c1.55,0,3.02,.72,3.97,1.96,.95,1.23,1.27,2.84,.86,4.34l-62.33,232.61,216.29-308.9h-137.36c-1.55,0-3.02-.72-3.97-1.96-.95-1.23-1.27-2.84-.86-4.34l62.33-232.61-90.04,128.59q-79.35,113.32-104.91,149.83l-21.34,30.48Z\"/>\n      </svg>\n      <h1>Lightning CSS Playground</h1>\n      <a href=\"https://github.com/parcel-bundler/lightningcss\" target=\"_blank\" aria-label=\"GitHub\">\n        <svg class=\"github\" aria-hidden=\"true\" width=\"30\" height=\"30\" viewBox=\"0 0 20 20\"> <title>GitHub</title> <path d=\"M10 0a10 10 0 0 0-3.16 19.49c.5.1.68-.22.68-.48l-.01-1.7c-2.78.6-3.37-1.34-3.37-1.34-.46-1.16-1.11-1.47-1.11-1.47-.9-.62.07-.6.07-.6 1 .07 1.53 1.03 1.53 1.03.9 1.52 2.34 1.08 2.91.83.1-.65.35-1.09.63-1.34-2.22-.25-4.55-1.11-4.55-4.94 0-1.1.39-1.99 1.03-2.69a3.6 3.6 0 0 1 .1-2.64s.84-.27 2.75 1.02a9.58 9.58 0 0 1 5 0c1.91-1.3 2.75-1.02 2.75-1.02.55 1.37.2 2.4.1 2.64.64.7 1.03 1.6 1.03 2.69 0 3.84-2.34 4.68-4.57 4.93.36.31.68.92.68 1.85l-.01 2.75c0 .26.18.58.69.48A10 10 0 0 0 10 0\"></path> </svg>\n      </a>\n    </header>\n    <main>\n      <form id=\"sidebar\">\n        <h3>Options</h3>\n        <label><input id=\"minify\" type=\"checkbox\" checked> Minify</label>\n        <label><input id=\"cssModules\" type=\"checkbox\"> CSS modules</label>\n        <label><input id=\"analyzeDependencies\" type=\"checkbox\"> Analyze dependencies</label>\n        <label><input id=\"visitorEnabled\" type=\"checkbox\"> Visitor</label>\n        <h3>Draft syntax</h3>\n        <label><input id=\"customMedia\" type=\"checkbox\" checked> Custom media queries</label>\n        <h3>Targets</h3>\n        <div class=\"targets\">\n          <label><span>Chrome: </span><input id=\"chrome\" type=\"number\" value=\"95\"></label>\n          <label><span>Firefox: </span><input id=\"firefox\" type=\"number\"></label>\n          <label><span>Safari: </span><input id=\"safari\" type=\"number\"></label>\n          <label><span>Opera: </span><input id=\"opera\" type=\"number\"></label>\n          <label><span>Edge: </span><input id=\"edge\" type=\"number\"></label>\n          <label><span>IE: </span><input id=\"ie\" type=\"number\"></label>\n          <label><span>iOS: </span><input id=\"ios_saf\" type=\"number\"></label>\n          <label><span>Android: </span><input id=\"android\" type=\"number\"></label>\n          <label><span>Samsung: </span><input id=\"samsung\" type=\"number\"></label>\n        </div>\n        <h3>Features</h3>\n        <details>\n          <summary>Include</summary>\n          <div id=\"include\" />\n        </details>\n        <details>\n          <summary>Exclude</summary>\n          <div id=\"exclude\" />\n        </details>\n        <label>\n          <h3>Unused symbols</h3>\n          <textarea id=\"unusedSymbols\" rows=\"4\" placeholder=\"Separate items with newlines\"></textarea>\n        </label>\n        <label>\n          <h3>Version</h3>\n          <select id=\"version\"><option value=\"local\">local</option></select>\n        </label>\n      </form>\n      <div id=\"source\"></div>\n      <div id=\"visitor\" data-expanded=\"true\"></div>\n      <div id=\"compiled\" data-expanded=\"true\"></div>\n      <div id=\"compiledModules\" hidden></div>\n      <div id=\"compiledDependencies\" hidden></div>\n    </main>\n    <script type=\"module\" src=\"playground.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "website/playground/playground.js",
    "content": "import * as localWasm from '../../wasm';\nimport { EditorView, basicSetup } from 'codemirror';\nimport { javascript } from '@codemirror/lang-javascript';\nimport { css } from '@codemirror/lang-css';\nimport { oneDark } from '@codemirror/theme-one-dark';\nimport { syntaxTree } from '@codemirror/language';\nimport { linter, lintGutter } from '@codemirror/lint'\nimport { Compartment } from '@codemirror/state'\n\nconst linterCompartment = new Compartment;\nconst visitorLinterCompartment = new Compartment;\n\nlet wasm;\nlet editor, visitorEditor, outputEditor, modulesEditor, depsEditor;\nlet enc = new TextEncoder();\nlet dec = new TextDecoder();\nlet inputs = document.querySelectorAll('input[type=number]');\n\nasync function loadVersions() {\n  const { versions } = await fetch('https://data.jsdelivr.com/v1/package/npm/lightningcss-wasm').then(r => r.json());\n  versions\n    .map(v => {\n      const option = document.createElement('option');\n      option.value = v;\n      option.textContent = v;\n      return option;\n    })\n    .forEach(o => {\n      version.appendChild(o);\n    })\n}\n\nasync function loadWasm() {\n  if (version.value === 'local') {\n    wasm = localWasm;\n  } else {\n    wasm = await new Function('version', 'return import(`https://esm.sh/lightningcss-wasm@${version}?bundle`)')(version.value);\n  }\n  await wasm.default();\n}\n\nfunction loadPlaygroundState() {\n  const hash = window.location.hash.slice(1);\n  try {\n    return JSON.parse(decodeURIComponent(hash));\n  } catch {\n    return {\n      minify: minify.checked,\n      visitorEnabled: visitorEnabled.checked,\n      targets: getTargets(),\n      include: 0,\n      exclude: 0,\n      source: `@custom-media --modern (color), (hover);\n\n.foo {\n  background: yellow;\n\n  -webkit-border-radius: 2px;\n  -moz-border-radius: 2px;\n  border-radius: 2px;\n\n  -webkit-transition: background 200ms;\n  -moz-transition: background 200ms;\n  transition: background 200ms;\n\n  &.bar {\n    color: green;\n  }\n}\n\n@media (--modern) and (width > 1024px) {\n  .a {\n    color: green;\n  }\n}`,\n      version: version.value,\n      visitor: `{\n  Color(color) {\n    if (color.type === 'rgb') {\n      color.g = 0;\n      return color;\n    }\n  }\n}`\n    };\n  }\n}\n\nfunction reflectPlaygroundState(playgroundState) {\n  if (typeof playgroundState.minify !== 'undefined') {\n    minify.checked = playgroundState.minify;\n  }\n\n  if (typeof playgroundState.cssModules !== 'undefined') {\n    cssModules.checked = playgroundState.cssModules;\n    compiledModules.hidden = !playgroundState.cssModules;\n  }\n\n  if (typeof playgroundState.analyzeDependencies !== 'undefined') {\n    analyzeDependencies.checked = playgroundState.analyzeDependencies;\n    compiledDependencies.hidden = !playgroundState.analyzeDependencies;\n  }\n\n  if (typeof playgroundState.customMedia !== 'undefined') {\n    customMedia.checked = playgroundState.customMedia;\n  }\n\n  if (typeof playgroundState.visitorEnabled !== 'undefined') {\n    visitorEnabled.checked = playgroundState.visitorEnabled;\n  }\n\n  if (playgroundState.targets) {\n    const { targets } = playgroundState;\n    for (let input of inputs) {\n      let value = targets[input.id];\n      input.value = value == null ? '' : value >> 16;\n    }\n  }\n\n  updateFeatures(sidebar.elements.include, playgroundState.include);\n  updateFeatures(sidebar.elements.exclude, playgroundState.exclude);\n  if (playgroundState.include) {\n    include.parentElement.open = true;\n  }\n  if (playgroundState.exclude) {\n    exclude.parentElement.open = true;\n  }\n\n  if (playgroundState.unusedSymbols) {\n    unusedSymbols.value = playgroundState.unusedSymbols.join('\\n');\n  }\n\n  if (playgroundState.version) {\n    version.value = playgroundState.version;\n  }\n}\n\nfunction savePlaygroundState() {\n  let data = new FormData(sidebar);\n  const playgroundState = {\n    minify: minify.checked,\n    customMedia: customMedia.checked,\n    cssModules: cssModules.checked,\n    analyzeDependencies: analyzeDependencies.checked,\n    targets: getTargets(),\n    include: getFeatures(data.getAll('include')),\n    exclude: getFeatures(data.getAll('exclude')),\n    source: editor.state.doc.toString(),\n    visitorEnabled: visitorEnabled.checked,\n    visitor: visitorEditor.state.doc.toString(),\n    unusedSymbols: unusedSymbols.value.split('\\n').map(v => v.trim()).filter(Boolean),\n    version: version.value,\n  };\n\n  const hash = encodeURIComponent(JSON.stringify(playgroundState));\n\n  if (\n    typeof URL === 'function' &&\n    typeof history === 'object' &&\n    typeof history.replaceState === 'function'\n  ) {\n    const url = new URL(location.href);\n    url.hash = hash;\n    history.replaceState(null, null, url);\n  } else {\n    location.hash = hash;\n  }\n}\n\nfunction getTargets() {\n  let targets = {};\n  for (let input of inputs) {\n    if (input.value !== '') {\n      targets[input.id] = input.valueAsNumber << 16;\n    }\n  }\n\n  return targets;\n}\n\nfunction getFeatures(vals) {\n  let features = 0;\n  for (let name of vals) {\n    features |= wasm.Features[name];\n  }\n  return features;\n}\n\nfunction updateFeatures(elements, include) {\n  for (let checkbox of elements) {\n    let feature = wasm.Features[checkbox.value];\n    checkbox.checked = (include & feature) === feature;\n    checkbox.indeterminate = !checkbox.checked && (include & feature);\n  }\n}\n\nfunction update() {\n  const { transform } = wasm;\n\n  const targets = getTargets();\n  let data = new FormData(sidebar);\n  let include = getFeatures(data.getAll('include'));\n  let exclude = getFeatures(data.getAll('exclude'));\n  try {\n    let res = transform({\n      filename: 'test.css',\n      code: enc.encode(editor.state.doc.toString()),\n      minify: minify.checked,\n      targets: Object.keys(targets).length === 0 ? null : targets,\n      include,\n      exclude,\n      drafts: {\n        customMedia: customMedia.checked\n      },\n      cssModules: cssModules.checked,\n      analyzeDependencies: analyzeDependencies.checked,\n      unusedSymbols: unusedSymbols.value.split('\\n').map(v => v.trim()).filter(Boolean),\n      visitor: visitorEnabled.checked ? (0, eval)('(' + visitorEditor.state.doc.toString() + ')') : undefined,\n    });\n\n    let update = outputEditor.state.update({ changes: { from: 0, to: outputEditor.state.doc.length, insert: dec.decode(res.code) } });\n    outputEditor.update([update]);\n\n    if (res.exports) {\n      let update = modulesEditor.state.update({ changes: { from: 0, to: modulesEditor.state.doc.length, insert: '// CSS module exports\\n' + JSON.stringify(res.exports, false, 2) } });\n      modulesEditor.update([update]);\n    }\n\n    if (res.dependencies) {\n      let update = depsEditor.state.update({ changes: { from: 0, to: depsEditor.state.doc.length, insert: '// Dependencies\\n' + JSON.stringify(res.dependencies, false, 2) } });\n      depsEditor.update([update]);\n    }\n\n    compiledModules.hidden = !cssModules.checked;\n    compiledDependencies.hidden = !analyzeDependencies.checked;\n    visitor.hidden = !visitorEnabled.checked;\n    source.dataset.expanded = visitor.hidden;\n    compiled.dataset.expanded = compiledModules.hidden && compiledDependencies.hidden;\n    compiledModules.dataset.expanded = compiledDependencies.hidden;\n    compiledDependencies.dataset.expanded = compiledModules.hidden;\n\n    editor.dispatch({\n      effects: linterCompartment.reconfigure(createCssLinter(null, res.warnings))\n    });\n\n    visitorEditor.dispatch({\n      effects: visitorLinterCompartment.reconfigure(createVisitorLinter(null))\n    });\n  } catch (e) {\n    let update = outputEditor.state.update({ changes: { from: 0, to: outputEditor.state.doc.length, insert: `/* ERROR: ${e.message} */` } });\n    outputEditor.update([update]);\n\n    editor.dispatch({\n      effects: linterCompartment.reconfigure(createCssLinter(e))\n    });\n\n    visitorEditor.dispatch({\n      effects: visitorLinterCompartment.reconfigure(createVisitorLinter(e))\n    });\n  }\n\n  savePlaygroundState();\n}\n\nfunction createCssLinter(lastError, warnings) {\n  return linter(view => {\n    let diagnostics = [];\n    if (lastError && lastError.loc) {\n      let l = view.state.doc.line(lastError.loc.line);\n      let loc = l.from + lastError.loc.column - 1;\n      let node = syntaxTree(view.state).resolveInner(loc, 1);\n      diagnostics.push(\n        {\n          from: node.from,\n          to: node.to,\n          message: lastError.message,\n          severity: 'error'\n        }\n      );\n    }\n    if (warnings) {\n      for (let warning of warnings) {\n        let l = view.state.doc.line(warning.loc.line);\n        let loc = l.from + warning.loc.column - 1;\n        let node = syntaxTree(view.state).resolveInner(loc, 1);\n        diagnostics.push({\n          from: node.from,\n          to: node.to,\n          message: warning.message,\n          severity: 'warning'\n        });\n      }\n    }\n    return diagnostics;\n  }, { delay: 0 });\n}\n\nfunction createVisitorLinter(lastError) {\n  return linter(view => {\n    if (lastError && !lastError.loc) {\n      // Firefox has lineNumber and columnNumber, Safari has line and column.\n      let line = lastError.lineNumber ?? lastError.line;\n      let column = lastError.columnNumber ?? lastError.column;\n      if (lastError.column != null) {\n        column--;\n      }\n\n      if (line == null) {\n        // Chrome.\n        let match = lastError.stack.match(/(?:(?:eval.*<anonymous>:)|(?:eval:))(?<line>\\d+):(?<column>\\d+)/);\n        if (match) {\n          line = Number(match.groups.line);\n          column = Number(match.groups.column);\n\n          // Chrome's column numbers are off by the amount of leading whitespace in the line.\n          let l = view.state.doc.line(line);\n          let m = l.text.match(/^\\s*/);\n          if (m) {\n            column += m[0].length;\n          }\n        }\n      }\n\n      if (line != null) {\n        let l = view.state.doc.line(line);\n        let loc = l.from + column;\n        let node = syntaxTree(view.state).resolveInner(loc, -1);\n        return [\n          {\n            from: node.from,\n            to: node.to,\n            message: lastError.message,\n            severity: 'error'\n          }\n        ];\n      }\n    }\n    return [];\n  }, { delay: 0 });\n}\n\nfunction renderFeatures(parent, name) {\n  for (let feature in wasm.Features) {\n    let label = document.createElement('label');\n    let checkbox = document.createElement('input');\n    checkbox.type = 'checkbox';\n    checkbox.name = name;\n    checkbox.value = feature;\n    checkbox.oninput = () => {\n      let data = new FormData(sidebar);\n      let flags = getFeatures(data.getAll(name));\n      let f = wasm.Features[feature];\n      if (checkbox.checked) {\n        flags |= f;\n      } else {\n        flags &= ~f;\n      }\n      updateFeatures(sidebar.elements[name], flags);\n    };\n    label.appendChild(checkbox);\n    label.appendChild(document.createTextNode(' ' + feature))\n    parent.appendChild(label);\n  }\n}\n\nasync function main() {\n  await loadWasm();\n  renderFeatures(include, 'include');\n  renderFeatures(exclude, 'exclude');\n\n  let state = loadPlaygroundState();\n  reflectPlaygroundState(state);\n\n  editor = new EditorView({\n    extensions: [lintGutter(), basicSetup, css(), oneDark, linterCompartment.of(createCssLinter())],\n    parent: source,\n    doc: state.source,\n    dispatch(tr) {\n      editor.update([tr]);\n      if (tr.docChanged) {\n        update();\n      }\n    }\n  });\n\n  visitorEditor = new EditorView({\n    extensions: [lintGutter(), basicSetup, javascript(), oneDark, visitorLinterCompartment.of(createVisitorLinter())],\n    parent: visitor,\n    doc: state.visitor,\n    dispatch(tr) {\n      visitorEditor.update([tr]);\n      if (tr.docChanged) {\n        update();\n      }\n    }\n  });\n\n  outputEditor = new EditorView({\n    extensions: [basicSetup, css(), oneDark, EditorView.editable.of(false), EditorView.lineWrapping],\n    parent: compiled,\n  });\n\n  modulesEditor = new EditorView({\n    extensions: [basicSetup, javascript(), oneDark, EditorView.editable.of(false), EditorView.lineWrapping],\n    parent: compiledModules,\n  });\n\n  depsEditor = new EditorView({\n    extensions: [basicSetup, javascript(), oneDark, EditorView.editable.of(false), EditorView.lineWrapping],\n    parent: compiledDependencies,\n  });\n\n  update();\n  sidebar.oninput = update;\n\n  await loadVersions();\n  version.onchange = async () => {\n    await loadWasm();\n    update();\n  };\n}\n\nmain();\n"
  },
  {
    "path": "website/synthwave.css",
    "content": "/*\n * Based on Synthwave '84 Theme originally by Robb Owen [@Robb0wen] for Visual Studio Code\n * Originally ported for PrismJS by Marc Backes [@themarcba]\n */\n\npre[class*=\"language-\"] {\n  color: lab(64% 103 0);\n  text-shadow: 0 0 10px lab(64% 103 0 / .5);\n  background: rgb(255 255 255 / .05);\n  display: block;\n  padding: 20px;\n  border-radius: 8px;\n  font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;\n  font-size: 13px;\n  text-align: left;\n  white-space: pre-wrap;\n  word-spacing: normal;\n  word-break: normal;\n  word-wrap: break-word;\n  line-height: 1.5;\n\n  -moz-tab-size: 4;\n  -o-tab-size: 4;\n  tab-size: 4;\n\n  -webkit-hyphens: none;\n  -moz-hyphens: none;\n  -ms-hyphens: none;\n  hyphens: none;\n}\n\n/* Code blocks */\npre[class*=\"language-\"] {\n  padding: 1em;\n  margin: .5em 0;\n  overflow: auto;\n}\n\n/* Inline code */\n:not(pre) > code[class*=\"language-\"] {\n  padding: .1em;\n  border-radius: .3em;\n  white-space: normal;\n}\n\n.token.comment,\n.token.block-comment,\n.token.prolog,\n.token.doctype,\n.token.cdata {\n  color: #8e8e8e;\n  text-shadow: none;\n}\n\n.token.punctuation {\n  color: #ccc;\n}\n\n.token.tag,\n.token.attr-name,\n.token.namespace,\n.token.number,\n.token.unit,\n.token.hexcode,\n.token.deleted,\n.token.function {\n  color: lch(65% 85 35);\n  text-shadow: 0 0 10px lch(65% 85 35 / .5);\n}\n\n.token.property,\n.token.selector {\n  color: lch(85% 58 205);\n  text-shadow: 0 0 10px lch(85% 58 205 / .5);\n}\n\n.token.function-name {\n  color: #6196cc;\n}\n\n.token.boolean,\n.token.selector .token.id {\n  color: #fdfdfd;\n  text-shadow: 0 0 2px #001716, 0 0 3px #03edf975, 0 0 5px #03edf975, 0 0 8px #03edf975;\n}\n\n.token.class-name {\n  color: #fff5f6;\n  text-shadow: 0 0 2px #000, 0 0 10px #fc1f2c75, 0 0 5px #fc1f2c75, 0 0 25px #fc1f2c75;\n}\n\n.token.constant,\n.token.symbol {\n  color: lab(64% 103 0);\n  text-shadow: 0 0 2px #100c0f, 0 0 5px #dc078e33, 0 0 10px #fff3;\n}\n\n.token.important,\n.token.atrule,\n.token.keyword,\n.token.selector .token.class,\n.token.builtin {\n  color: #f4eee4;\n  text-shadow: 0 0 2px #393a33, 0 0 8px #f39f0575, 0 0 2px #f39f0575;\n}\n\n.token.string,\n.token.string-property,\n.token.char,\n.token.attr-value,\n.token.regex,\n.token.variable,\n.token.url {\n  color: lch(85% 82.34 80.104);\n  text-shadow: 0 0 10px lch(85% 82.34 80.104 / .5);\n}\n\n.token.operator,\n.token.entity {\n  color: #67cdcc;\n}\n\n.token.important,\n.token.bold {\n  font-weight: bold;\n}\n\n.token.italic {\n  font-style: italic;\n}\n\n.token.entity {\n  cursor: help;\n}\n\n.token.inserted {\n  color: green;\n}\n"
  },
  {
    "path": "website/transforms.html",
    "content": "<include src=\"website/include/layout.html\" locals='{\"title\": \"Custom Transforms\", \"url\": \"transforms.html\", \"page\": \"website/pages/transforms.md\"}' />\n"
  },
  {
    "path": "website/transpilation.html",
    "content": "<include src=\"website/include/layout.html\" locals='{\"title\": \"Transpilation\", \"url\": \"transpilation.html\", \"page\": \"website/pages/transpilation.md\"}' />\n"
  }
]