diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000000..a4aabbe5d0 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,77 @@ +name: CI + +on: + push: + branches: ["main"] + pull_request: + workflow_dispatch: + merge_group: + types: [checks_requested] + + +jobs: + linux-debug: + name: Linux (Debug) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + - name: Run Tests + run: cargo build --features servo + env: + RUST_BACKTRACE: 1 + + linux-release: + name: Linux (Release) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + - name: Run Tests + run: cargo build --release --features servo + env: + RUST_BACKTRACE: 1 + + macos-debug: + name: macOS (Debug) + runs-on: macos-latest + steps: + - uses: actions/checkout@v6 + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + - name: Run Tests + run: cargo build --features servo + env: + RUST_BACKTRACE: 1 + + windows-debug: + name: Windows (Debug) + runs-on: windows-latest + steps: + - uses: actions/checkout@v6 + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + - name: Run Tests + run: cargo build --features servo + env: + RUST_BACKTRACE: 1 + + build-result: + name: Result + runs-on: ubuntu-latest + if: ${{ always() }} + needs: + - linux-debug + - linux-release + - macos-debug + - windows-debug + steps: + - name: Success + if: ${{ !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') }} + run: exit 0 + - name: Failure + if: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }} + run: exit 1 + diff --git a/.github/workflows/mirror-to-release-branch.yml b/.github/workflows/mirror-to-release-branch.yml new file mode 100644 index 0000000000..44fd4c3124 --- /dev/null +++ b/.github/workflows/mirror-to-release-branch.yml @@ -0,0 +1,26 @@ +name: 🪞 Mirror `main` +on: + push: + branches: + - main + +jobs: + mirror: + name: Mirror + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + - name: Get branch name + id: branch-name + run: | + first_commit=$(git log --pretty=\%H --grep='Servo initial downstream commit') + upstream_base="$first_commit~" + echo BRANCH_NAME=$(git log -n1 --pretty='%as' $upstream_base) >> $GITHUB_OUTPUT + - uses: google/mirror-branch-action@v1.0 + name: Mirror to ${{ steps.branch-name.outputs.BRANCH_NAME }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + source: main + dest: ${{ steps.branch-name.outputs.BRANCH_NAME }} diff --git a/.github/workflows/sync-upstream.yml b/.github/workflows/sync-upstream.yml new file mode 100644 index 0000000000..7df440da84 --- /dev/null +++ b/.github/workflows/sync-upstream.yml @@ -0,0 +1,23 @@ +name: Sync upstream with mozilla-central + +on: + schedule: + - cron: '0 13 * * *' + workflow_dispatch: + +jobs: + sync: + name: Sync + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 1 + - uses: actions/cache@v3 + with: + path: _cache/upstream + key: upstream + - run: | + ./sync.sh _filtered + git fetch -f --progress ./_filtered main:upstream + git push -fu --progress origin upstream diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..fc3c2f9b3c --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/_cache/ +/_filtered/ +/target/ +/style/properties/__pycache__/ +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000000..617ba02dc4 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,36 @@ +[workspace] +resolver = "2" +members = [ + "stylo_atoms", + "stylo_dom", + "malloc_size_of", + "rustfmt.toml", + "selectors", + "servo_arc", + "style", + "style_derive", + "stylo_static_prefs", + "style_traits", + "to_shmem", + "to_shmem_derive", +] +default-members = ["style"] + +[workspace.package] +version = "0.19.0" + +[workspace.dependencies] +# in-repo dependencies (separately versioned) +servo_arc = { version = "0.4.3", path = "./servo_arc" } +selectors = { version = "0.39.0", path = "./selectors" } +to_shmem = { version = "0.5.0", path = "./to_shmem", features = ["servo"] } +to_shmem_derive = { version = "0.1.0", path = "./to_shmem_derive" } + +# in-repo dependencies (main version) +malloc_size_of = { version = "0.19.0", path = "./malloc_size_of", package = "stylo_malloc_size_of", features = ["servo"] } +static_prefs = { version = "0.19.0", path = "./stylo_static_prefs", package = "stylo_static_prefs" } +stylo_atoms = { version = "0.19.0", path = "./stylo_atoms" } +dom = { version = "0.19.0", path = "./stylo_dom", package = "stylo_dom" } +style_traits = { version = "0.19.0", path = "./style_traits", features = ["servo"], package = "stylo_traits"} +style_derive = { version = "0.19.0", path = "./style_derive", package = "stylo_derive"} +stylo = { version = "0.19.0", path = "./style" } diff --git a/README.md b/README.md new file mode 100644 index 0000000000..bfc3eb5b28 --- /dev/null +++ b/README.md @@ -0,0 +1,105 @@ +Stylo +===== + +**High-Performance CSS Style Engine** + +[![Build Status](https://github.com/servo/stylo/actions/workflows/main.yml/badge.svg)](https://github.com/servo/stylo/actions) +[![Crates.io](https://img.shields.io/crates/v/stylo.svg)](https://crates.io/crates/stylo) +[![Docs](https://docs.rs/stylo/badge.svg)](https://docs.rs/stylo) +![Crates.io License](https://img.shields.io/crates/l/stylo) + +Stylo is a high-performance, browser-grade CSS style engine written in Rust that powers [Servo](https://servo.org) and [Firefox](https://firefox.com). This repo contains Servo’s downstream version of Stylo. The upstream version lives in mozilla-central with the rest of the Gecko/Firefox codebase. + +Coordination of Stylo development happens: + +- Here in Github Issues +- In the [#stylo](https://servo.zulipchat.com/#narrow/channel/417109-stylo) channel of the [Servo Zulip](https://servo.zulipchat.com/) +- In the [#layout](https://chat.mozilla.org/#/room/#layout:mozilla.org) room of the Mozilla Matrix instance (matrix.mozilla.org) + +## High-Level Documentation + +- This [Mozilla Hacks article](https://hacks.mozilla.org/2017/08/inside-a-super-fast-css-engine-quantum-css-aka-stylo) contains a high-level overview of the Stylo architecture. +- There is a [chapter](https://book.servo.org/architecture/style.html) in the Servo Book (although it is a little out of date). + +## Branches + +The branches are as follows: + +- [**upstream**](https://github.com/servo/style/tree/upstream) has upstream [mozilla-central](https://searchfox.org/mozilla-central/source/servo) filtered to the paths we care about ([style.paths](style.paths)), but is otherwise unmodified. +- [**main**](https://github.com/servo/style/tree/ci) adds our downstream patches, plus the scripts and workflows for syncing with mozilla-central on top of **upstream**. + +> [!WARNING] +> This repo syncs from upstream by creating a new branch and then rebasing our changes on top of it. This means that `git pull` will not work across syncs (you will need to use `git fetch`, `git reset` and `git rebase`). + +More information on the syncing process is available in [SYNCING.md](SYNCING.md) + +## Crates + +A guide to the crates contained within this repo + +### Stylo Crates + +These crates are largely implementation details of Stylo, although you may need to use some of them directly if you use Stylo. + +| Directory | Crate | Notes | +| --- | --- | --- | +| style | [![Crates.io](https://img.shields.io/crates/v/stylo.svg)](https://crates.io/crates/stylo) | The main Stylo crate containing the entire CSS engine | +| style_traits | [![Crates.io](https://img.shields.io/crates/v/stylo_traits.svg)](https://crates.io/crates/stylo_traits) | Types and traits which allow other code to interoperate with Stylo without depending on the main crate directly. | +| stylo_dom | [![Crates.io](https://img.shields.io/crates/v/stylo_dom.svg)](https://crates.io/crates/stylo_dom) | Similar to stylo_traits (but much smaller) | +| stylo_atoms | [![Crates.io](https://img.shields.io/crates/v/stylo_atoms.svg)](https://crates.io/crates/stylo_atoms) | [Atoms](https://docs.rs/string_cache/latest/string_cache/struct.Atom.html) for CSS and HTML event related strings | +| stylo_static_prefs | [![Crates.io](https://img.shields.io/crates/v/stylo_static_prefs.svg)](https://crates.io/crates/stylo_static_prefs) | Configuration for Stylo. Can be used to set runtime preferences (enabling/disabling properties, etc) | +| style_derive | [![Crates.io](https://img.shields.io/crates/v/stylo_derive.svg)](https://crates.io/crates/stylo_derive) | Internal derive macro for stylo crate | + +### Standalone Crates + +These crates form part of Stylo but are also be useful standalone. + +| Directory | Crate | Notes | +| --- | --- | --- | +| selectors | [![Crates.io](https://img.shields.io/crates/v/selectors.svg)](https://crates.io/crates/selectors) | CSS Selector matching | +| servo_arc | [![Crates.io](https://img.shields.io/crates/v/servo_arc.svg)](https://crates.io/crates/servo_arc) | A variant on `std::Arc` | + +You may also be interested in the `cssparser` crate which lives in the [servo/rust-cssparser](https://github.com/servo/rust-cssparser) repo. + +### Support Crates + +Low-level crates which could technically be used standalone but are unlikely to be generally useful in practice. + +| Directory | Crate | Notes | +| --- | --- | --- | +| malloc_size_of | [![Crates.io](https://img.shields.io/crates/v/stylo_malloc_size_of.svg)](https://crates.io/crates/stylo_malloc_size_of) | Heap size measurement for Stylo values | +| to_shmem | [![Crates.io](https://img.shields.io/crates/v/to_shmem.svg)](https://crates.io/crates/to_shmem) | Internal utility crate for sharing memory across processes. | +| to_shmem_derive | [![Crates.io](https://img.shields.io/crates/v/to_shmem_derive.svg)](https://crates.io/crates/to_shmem_derive) | Internal derive macro for to_shmem crate | + +## Building Servo Against a Local Copy of Stylo + +Assuming your local `servo` and `stylo` directories are siblings, you can build `servo` against `stylo` by adding the following to `servo/Cargo.toml`: + +```toml +[patch."https://github.com/servo/stylo"] +selectors = { path = "../stylo/selectors" } +servo_arc = { path = "../stylo/servo_arc" } +stylo = { path = "../stylo/style" } +stylo_atoms = { path = "../stylo/stylo_atoms" } +stylo_dom = { path = "../stylo/stylo_dom" } +stylo_malloc_size_of = { path = "../stylo/malloc_size_of" } +stylo_static_prefs = { path = "../stylo/stylo_static_prefs" } +stylo_traits = { path = "../stylo/style_traits" } +``` + +## Releases + +Releases are made every time this repository rebases its changes on top of the latest version of upstream Stylo. There are a lot of crates here. In order to publish them, they must be done in order. One order that works is: + +- selectors +- stylo_static_prefs +- stylo_atoms +- stylo_malloc_size_of +- stylo_dom +- stylo_derive +- stylo_traits +- stylo + +## License + +Stylo is licensed under MPL 2.0 diff --git a/SYNCING.md b/SYNCING.md new file mode 100644 index 0000000000..72a0a53d1d --- /dev/null +++ b/SYNCING.md @@ -0,0 +1,63 @@ +# Syncing + +This file documents the process of syncing this repository with the upstream copy of Stylo in mozilla-central. + +## Syncing `upstream` with mozilla-central + +Start by generating a filtered copy of mozilla-central. This will cache the raw mozilla-central in `_cache/upstream`, storing the result in `_filtered`: + +```sh +$ ./sync.sh _filtered +``` + +If `_filtered` already exists, you will need to delete it and try again: + +```sh +$ rm -Rf _filtered +``` + +Now overwrite our `upstream` with those commits and push: + +```sh +$ git fetch -f --progress ./_filtered main:upstream +$ git push -fu --progress origin upstream +``` + +## Rebasing `main` onto `upstream` + +Start by fetching `upstream` into your local repo: + +```sh +$ git fetch -f origin upstream:upstream +``` + +In general, the filtering process is deterministic, yielding the same commit hashes each time, so we can rebase normally: + +```sh +$ git rebase upstream +``` + +But if the filtering config changes or Mozilla moves to GitHub, the commit hashes on `upstream` may change. In this case, we need to tell git where the old upstream ends and our own commits start (notice the `~`): + +```sh +$ git log --pretty=\%H --grep='Servo initial downstream commit' +e62d7f0090941496e392e1dc91df103a38e3f488 + +$ git rebase --onto upstream e62d7f0090941496e392e1dc91df103a38e3f488~ +Successfully rebased and updated refs/heads/main. +``` + +`start-rebase.sh` takes care of this automatically, but you should still use `git rebase` for subsequent steps like `--continue` and `--abort`: + +```sh +$ ./start-rebase.sh upstream +$ ./start-rebase.sh upstream -i # interactive +$ git rebase --continue # not ./start-rebase.sh --continue +$ git rebase --abort # not ./start-rebase.sh --abort +``` + +Or if we aren’t ready to rebase onto the tip of upstream: + +```sh +$ ./start-rebase.sh upstream~10 -i +``` diff --git a/commit-from-merge.sh b/commit-from-merge.sh new file mode 100755 index 0000000000..94aa606f02 --- /dev/null +++ b/commit-from-merge.sh @@ -0,0 +1,15 @@ +#!/bin/sh +# Usage: commit-from-merge.sh [extra git-commit(1) arguments ...] +# Given a merge commit made by bors, runs git-commit(1) with your local changes +# while borrowing the author name/email from the right-hand parent of the merge, +# and the author date from the committer date of the merge. +set -eu + +lookup_repo=$1; shift +merge_commit=$1; shift +author_name_email=$(git -C "$lookup_repo" log -n1 --pretty='%aN <%aE>' "$merge_commit"\^2) +committer_date=$(git -C "$lookup_repo" log -n1 --pretty='%cd' "$merge_commit") + +set -- git commit --author="$author_name_email" --date="$committer_date" "$@" +echo "$@" +"$@" diff --git a/commit-from-squashed.sh b/commit-from-squashed.sh new file mode 100755 index 0000000000..004e0f7840 --- /dev/null +++ b/commit-from-squashed.sh @@ -0,0 +1,14 @@ +#!/bin/sh +# Usage: commit-from-squashed.sh [extra git-commit(1) arguments ...] +# Given a squashed commit made by the GitHub merge queue, runs git-commit(1) with your local changes +# while borrowing our author name/email from that commit, our author date from its committer date, +# and our commit message from that commit. +set -eu + +squashed_commit=$1; shift +committer_date=$(git log -n1 --pretty='%cd' "$squashed_commit") + +# -c is equivalent to --author=$(...'%aN <%aE>') -m $(...'%B'), but allows editing +set -- git commit -c "$squashed_commit" --date="$committer_date" "$@" +echo "$@" +"$@" diff --git a/malloc_size_of/Cargo.toml b/malloc_size_of/Cargo.toml index 7e2366d327..6bf6102158 100644 --- a/malloc_size_of/Cargo.toml +++ b/malloc_size_of/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "malloc_size_of" -version = "0.0.1" +name = "stylo_malloc_size_of" +version.workspace = true authors = ["The Servo Project Developers"] license = "MIT OR Apache-2.0" repository = "https://github.com/servo/stylo" @@ -18,10 +18,10 @@ servo = ["string_cache"] app_units = "0.7" cssparser = "0.37" euclid = "0.22" -selectors = { path = "../selectors" } -servo_arc = { path = "../servo_arc" } +selectors = { workspace = true } +servo_arc = { workspace = true } smallbitvec = "2.3.0" -smallvec = "1.0" -string_cache = { version = "0.8", optional = true } -thin-vec = { version = "0.2.1" } +smallvec = "1.13" +string_cache = { version = "0.9", optional = true } +thin-vec = { version = "0.2.13" } void = "1.0.2" diff --git a/malloc_size_of/lib.rs b/malloc_size_of/lib.rs index 412ef88710..8a80bf3ea0 100644 --- a/malloc_size_of/lib.rs +++ b/malloc_size_of/lib.rs @@ -277,6 +277,14 @@ impl MallocSizeOf for std::cell::RefCell { } } +impl MallocSizeOf for std::sync::OnceLock { + fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + self.get() + .map(|value| value.size_of(ops)) + .unwrap_or_default() + } +} + impl<'a, B: ?Sized + ToOwned> MallocSizeOf for std::borrow::Cow<'a, B> where B::Owned: MallocSizeOf, diff --git a/selectors/Cargo.toml b/selectors/Cargo.toml index c919bcdc4e..3611efe0c1 100644 --- a/selectors/Cargo.toml +++ b/selectors/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "selectors" -version = "0.26.0" +version = "0.39.0" authors = ["The Servo Project Developers"] documentation = "https://docs.rs/selectors/" description = "CSS Selectors matching for Rust" @@ -27,10 +27,10 @@ rustc-hash = "2.1.1" log = "0.4" phf = "0.13" precomputed-hash = "0.1" -servo_arc = { version = "0.4", path = "../servo_arc" } +servo_arc = { workspace = true } smallvec = "1.0" -to_shmem = { version = "0.1", path = "../to_shmem", features = ["servo_arc"], optional = true } -to_shmem_derive = { version = "0.1", path = "../to_shmem_derive", optional = true } +to_shmem = { workspace = true, optional = true } +to_shmem_derive = { workspace = true, optional = true } new_debug_unreachable = "1" [build-dependencies] diff --git a/selectors/README.md b/selectors/README.md index 3ce269fa3c..e6f5689e6f 100644 --- a/selectors/README.md +++ b/selectors/README.md @@ -6,7 +6,7 @@ rust-selectors * [crates.io](https://crates.io/crates/selectors) CSS Selectors library for Rust. -Includes parsing and serilization of selectors, +Includes parsing and serialization of selectors, as well as matching against a generic tree of elements. Pseudo-elements and most pseudo-classes are generic as well. diff --git a/servo_arc/Cargo.toml b/servo_arc/Cargo.toml index 8b0976b75d..f5ae244a50 100644 --- a/servo_arc/Cargo.toml +++ b/servo_arc/Cargo.toml @@ -1,17 +1,19 @@ [package] name = "servo_arc" -version = "0.4.0" +version = "0.4.3" authors = ["The Servo Project Developers"] license = "MIT OR Apache-2.0" repository = "https://github.com/servo/stylo" description = "A fork of std::sync::Arc with some extra functionality and without weak references" edition = "2021" +readme = "../README.md" [lib] name = "servo_arc" path = "lib.rs" [features] +default = ["track_alloc_size"] gecko_refcount_logging = [] servo = ["serde", "track_alloc_size"] track_alloc_size = [] diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000000..2c96e200aa --- /dev/null +++ b/shell.nix @@ -0,0 +1,6 @@ +with import (builtins.fetchTarball { + url = "https://github.com/NixOS/nixpkgs/archive/46ae0210ce163b3cba6c7da08840c1d63de9c701.tar.gz"; +}) {}; +stdenv.mkDerivation rec { + name = "style-sync-shell"; +} diff --git a/start-rebase.sh b/start-rebase.sh new file mode 100755 index 0000000000..fe417f7f08 --- /dev/null +++ b/start-rebase.sh @@ -0,0 +1,10 @@ +#!/bin/sh +# Usage: start-rebase.sh [extra git-rebase(1) arguments ...] +# Equivalent to git rebase --onto . +set -eu + +new_base=$1; shift +first_commit=$(git log --pretty=\%H --grep='Servo initial downstream commit') +old_base=$first_commit~ + +git rebase --onto "$new_base" "$old_base" "$@" diff --git a/style.paths b/style.paths new file mode 100644 index 0000000000..d1d2d02638 --- /dev/null +++ b/style.paths @@ -0,0 +1,8 @@ +# Filters and renames use git-filter-repo(1) --paths-from-file: +# https://htmlpreview.github.io/?https://github.com/newren/git-filter-repo/blob/docs/html/git-filter-repo.html#_filtering_based_on_many_paths + +servo/components/ +servo/rustfmt.toml + +regex:servo/components/(.+)==>\1 +servo/rustfmt.toml==>rustfmt.toml diff --git a/style/Cargo.toml b/style/Cargo.toml index 1d9dc4a1e2..6176a9a355 100644 --- a/style/Cargo.toml +++ b/style/Cargo.toml @@ -1,11 +1,12 @@ [package] -name = "style" -version = "0.0.1" +name = "stylo" +version.workspace = true authors = ["The Servo Project Developers"] license = "MPL-2.0" repository = "https://github.com/servo/stylo" edition = "2021" description = "The Stylo CSS engine" +readme = "../README.md" build = "build.rs" @@ -18,6 +19,7 @@ path = "lib.rs" doctest = false [features] +default = ["servo"] gecko = [ "bindgen", "malloc_size_of/gecko", @@ -31,15 +33,14 @@ gecko = [ "to_shmem/gecko", ] servo = [ - "arrayvec/use_union", "cssparser/serde", "encoding_rs", "malloc_size_of/servo", "web_atoms", + "mime", "serde", "servo_arc/servo", "stylo_atoms", - "servo_config", "string_cache", "style_traits/servo", "url", @@ -48,6 +49,7 @@ servo = [ ] gecko_debug = [] gecko_refcount_logging = [] +nsstring = [] [dependencies] app_units = "0.7.8" @@ -57,20 +59,20 @@ bitflags = "2" byteorder = "1.0" cssparser = "0.37" derive_more = { version = "2", features = ["add", "add_assign", "deref", "deref_mut", "from"] } -dom = { path = "../../../dom/base/rust" } +dom = { workspace = true } new_debug_unreachable = "1.0" encoding_rs = {version = "0.8", optional = true} euclid = "0.22" rustc-hash = "2.1.1" -icu_segmenter = { version = "2.0", default-features = false, features = ["auto", "compiled_data"] } +icu_segmenter = { version = ">= 1.5, <= 2.*", default-features = false, features = ["auto", "compiled_data"] } indexmap = {version = "2", features = ["std"]} itertools = "0.14" itoa = "1.0" log = "0.4" -malloc_size_of = { path = "../malloc_size_of" } -malloc_size_of_derive = { path = "../../../xpcom/rust/malloc_size_of_derive" } -web_atoms = { version = "0.1", optional = true } -nsstring = {path = "../../../xpcom/rust/nsstring/", optional = true} +malloc_size_of = { workspace = true } +malloc_size_of_derive = "0.1" +web_atoms = { version = "0.2.0", optional = true } +mime = { version = "0.3.13", optional = true } num_cpus = {version = "1.1.0"} num-integer = "0.1" num-traits = "0.2" @@ -79,26 +81,24 @@ parking_lot = "0.12" precomputed-hash = "0.1.1" rayon = "1" rayon-core = "1" -selectors = { path = "../selectors" } +selectors = { workspace = true } serde = {version = "1.0", optional = true, features = ["derive"]} -servo_arc = { path = "../servo_arc" } -stylo_atoms = {path = "../atoms", optional = true} -servo_config = {path = "../config", optional = true} +servo_arc = { workspace = true} +stylo_atoms = { workspace = true, optional = true} smallbitvec = "2.3.0" smallvec = "1.0" static_assertions = "1.1" -static_prefs = { path = "../../../modules/libpref/init/static_prefs" } -string_cache = { version = "0.8", optional = true } -strum = "0.27" -strum_macros = "0.27" -style_derive = {path = "../style_derive"} -style_traits = {path = "../style_traits"} -to_shmem = {path = "../to_shmem"} -to_shmem_derive = {path = "../to_shmem_derive"} -thin-vec = { version = "0.2.1", features = ["gecko-ffi"] } +static_prefs = { workspace = true} +string_cache = { version = "0.9", optional = true } +strum = "0.28" +strum_macros = "0.28" +style_derive = { workspace = true } +style_traits = { workspace = true } +to_shmem = { workspace = true} +to_shmem_derive = { workspace = true } +thin-vec = "0.2.1" uluru = "3.0" void = "1.0.2" -gecko-profiler = { path = "../../../tools/profiler/rust-api" } url = { version = "2.5", optional = true, features = ["serde"] } [build-dependencies] diff --git a/style/build.rs b/style/build.rs index fb1f5e36ea..26176df9ad 100644 --- a/style/build.rs +++ b/style/build.rs @@ -19,7 +19,7 @@ mod build_gecko { pub static PYTHON: LazyLock = LazyLock::new(|| { env::var("PYTHON3").ok().unwrap_or_else(|| { let candidates = if cfg!(windows) { - ["python3.exe"] + ["python.exe"] } else { ["python3"] }; @@ -56,6 +56,12 @@ fn generate_properties(engine: &str) { .join("build.py"); let status = Command::new(&*PYTHON) + // `cargo publish` isn't happy with the `__pycache__` files that are created + // when we run the property generator. + // + // TODO(mrobinson): Is this happening because of how we run this script? It + // would be better to ensure are just placed in the output directory. + .env("PYTHONDONTWRITEBYTECODE", "1") .arg(&script) .arg(engine) .arg("style-crate") diff --git a/style/device/servo.rs b/style/device/servo.rs index 2d14029f91..38ce1a054c 100644 --- a/style/device/servo.rs +++ b/style/device/servo.rs @@ -13,6 +13,7 @@ use crate::media_queries::MediaType; use crate::properties::style_structs::Font; use crate::properties::ComputedValues; use crate::queries::values::PrefersColorScheme; +use crate::servo::media_features::PointerCapabilities; use crate::values::computed::font::GenericFontFamily; use crate::values::computed::{CSSPixelLength, Length, LineHeight, NonNegativeLength}; use crate::values::specified::color::{ColorSchemeFlags, ForcedColors, SystemColor}; @@ -52,33 +53,44 @@ pub trait FontMetricsProvider: Debug + Sync { #[derive(Debug, MallocSizeOf)] pub(super) struct ExtraDeviceData { + /// Data which Stylo uses to evaluate media queries + media_data: MediaData, + /// An implementation of a trait which implements support for querying font metrics. + #[ignore_malloc_size_of = "Owned by embedder"] + font_metrics_provider: Box, +} + +#[derive(Debug, MallocSizeOf, Clone, PartialEq)] +/// Data which Stylo uses to evaluate media queries +pub struct MediaData { /// The current media type used by de device. - media_type: MediaType, + pub media_type: MediaType, /// The current viewport size, in CSS pixels. - viewport_size: Size2D, + pub viewport_size: Size2D, + /// The current screen size, in device pixels. + pub device_size: Size2D, /// The current device pixel ratio, from CSS pixels to device pixels. - device_pixel_ratio: Scale, + pub device_pixel_ratio: Scale, /// The current quirks mode. #[ignore_malloc_size_of = "Pure stack type"] - quirks_mode: QuirksMode, + pub quirks_mode: QuirksMode, /// Whether the user prefers light mode or dark mode #[ignore_malloc_size_of = "Pure stack type"] - prefers_color_scheme: PrefersColorScheme, - /// An implementation of a trait which implements support for querying font metrics. - #[ignore_malloc_size_of = "Owned by embedder"] - font_metrics_provider: Box, + pub prefers_color_scheme: PrefersColorScheme, + /// The capabilities of the primary pointer input + #[ignore_malloc_size_of = "Pure stack type"] + pub primary_pointer_capabilities: PointerCapabilities, + /// The union of the capabilities of all pointer inputs + #[ignore_malloc_size_of = "Pure stack type"] + pub all_pointer_capabilities: PointerCapabilities, } impl Device { /// Trivially construct a new `Device`. pub fn new( - media_type: MediaType, - quirks_mode: QuirksMode, - viewport_size: Size2D, - device_pixel_ratio: Scale, - font_metrics_provider: Box, default_values: Arc, - prefers_color_scheme: PrefersColorScheme, + font_metrics_provider: Box, + media_data: MediaData, ) -> Device { let root_style = RwLock::new(Arc::clone(&default_values)); Device { @@ -99,11 +111,7 @@ impl Device { default_values, body_text_color: AtomicU32::new(AbsoluteColor::BLACK.to_nscolor()), extra: ExtraDeviceData { - media_type, - viewport_size, - device_pixel_ratio, - quirks_mode, - prefers_color_scheme, + media_data, font_metrics_provider, }, } @@ -129,7 +137,7 @@ impl Device { /// Get the quirks mode of the current device. pub fn quirks_mode(&self) -> QuirksMode { - self.extra.quirks_mode + self.extra.media_data.quirks_mode } /// Gets the base size given a generic font family. @@ -145,9 +153,33 @@ impl Device { true } + /// Get the [`MediaData`] associated with this [`Device`]. + #[inline] + pub fn media_data(&self) -> &MediaData { + &self.extra.media_data + } + + /// Mutate the [`MediaData`] on this [`Device`]. + /// + /// Note that this does not update any associated `Stylist`. For this you must call + /// `Stylist::media_features_change_changed_style` and + /// `Stylist::force_stylesheet_origins_dirty`. + pub fn media_data_mut(&mut self) -> &mut MediaData { + &mut self.extra.media_data + } + + /// Set the [`MediaData`] on this [`Device`]. + /// + /// Note that this does not update any associated `Stylist`. For this you must call + /// `Stylist::media_features_change_changed_style` and + /// `Stylist::force_stylesheet_origins_dirty`. + pub fn set_media_data(&mut self, media_data: MediaData) { + self.extra.media_data = media_data; + } + /// Get the viewport size on this [`Device`]. pub fn viewport_size(&self) -> Size2D { - self.extra.viewport_size + self.extra.media_data.viewport_size } /// Set the viewport size on this [`Device`]. @@ -156,7 +188,7 @@ impl Device { /// `Stylist::media_features_change_changed_style` and /// `Stylist::force_stylesheet_origins_dirty`. pub fn set_viewport_size(&mut self, viewport_size: Size2D) { - self.extra.viewport_size = viewport_size; + self.extra.media_data.viewport_size = viewport_size; } /// Returns the viewport size of the current device in app units, needed, @@ -164,8 +196,8 @@ impl Device { #[inline] pub fn au_viewport_size(&self) -> UntypedSize2D { Size2D::new( - Au::from_f32_px(self.extra.viewport_size.width), - Au::from_f32_px(self.extra.viewport_size.height), + Au::from_f32_px(self.extra.media_data.viewport_size.width), + Au::from_f32_px(self.extra.media_data.viewport_size.height), ) } @@ -182,17 +214,17 @@ impl Device { /// Returns the number of app units per device pixel we're using currently. pub fn app_units_per_device_pixel(&self) -> i32 { - (AU_PER_PX as f32 / self.extra.device_pixel_ratio.0) as i32 + (AU_PER_PX as f32 / self.extra.media_data.device_pixel_ratio.0) as i32 } /// Returns the device pixel ratio, ignoring the full zoom factor. pub fn device_pixel_ratio_ignoring_full_zoom(&self) -> Scale { - self.extra.device_pixel_ratio + self.extra.media_data.device_pixel_ratio } /// Returns the device pixel ratio. pub fn device_pixel_ratio(&self) -> Scale { - self.extra.device_pixel_ratio + self.extra.media_data.device_pixel_ratio } /// Set a new device pixel ratio on this [`Device`]. @@ -204,7 +236,22 @@ impl Device { &mut self, device_pixel_ratio: Scale, ) { - self.extra.device_pixel_ratio = device_pixel_ratio; + self.extra.media_data.device_pixel_ratio = device_pixel_ratio; + } + + /// Set the device size on this [`Device`] (e.g. the available screen dimensions). + /// + /// Note that this does not update any associated `Stylist`. For this you must call + /// `Stylist::media_features_change_changed_style` and + /// `Stylist::force_stylesheet_origins_dirty`. + pub fn set_device_size(&mut self, device_size: Size2D) { + self.extra.media_data.device_size = device_size; + } + + /// Returns the screen size of the current device in app units. + #[inline] + pub fn device_size(&self) -> Size2D { + self.extra.media_data.device_size } /// Gets the size of the scrollbar in CSS pixels. @@ -230,9 +277,18 @@ impl Device { .query_font_metrics(vertical, font, base_size, flags) } + /// Set the media type on this [`Device`]. + /// + /// Note that this does not update any associated `Stylist`. For this you must call + /// `Stylist::media_features_change_changed_style` and + /// `Stylist::force_stylesheet_origins_dirty`. + pub fn set_media_type(&mut self, media_type: MediaType) { + self.extra.media_data.media_type = media_type; + } + /// Return the media type of the current device. pub fn media_type(&self) -> MediaType { - self.extra.media_type.clone() + self.extra.media_data.media_type.clone() } /// Returns whether document colors are enabled. @@ -256,12 +312,40 @@ impl Device { /// `Stylist::media_features_change_changed_style` and /// `Stylist::force_stylesheet_origins_dirty`. pub fn set_color_scheme(&mut self, new_color_scheme: PrefersColorScheme) { - self.extra.prefers_color_scheme = new_color_scheme; + self.extra.media_data.prefers_color_scheme = new_color_scheme; } /// Returns the color scheme of this [`Device`]. pub fn color_scheme(&self) -> PrefersColorScheme { - self.extra.prefers_color_scheme + self.extra.media_data.prefers_color_scheme + } + + /// Set the [`PointerCapbabilities`] value for the primary pointer on this [`Device`] + /// + /// Note that this does not update any associated `Stylist`. For this you must call + /// `Stylist::media_features_change_changed_style` and + /// `Stylist::force_stylesheet_origins_dirty`. + pub fn set_primary_pointer_capabilities(&mut self, capabilities: PointerCapabilities) { + self.extra.media_data.primary_pointer_capabilities = capabilities; + } + + /// Returns the pointer capabilities of this [`Device`]. + pub fn primary_pointer_capabilities(&self) -> PointerCapabilities { + self.extra.media_data.primary_pointer_capabilities + } + + /// Set the [`PointerCapbabilities`] value for all pointers on this [`Device`] + /// + /// Note that this does not update any associated `Stylist`. For this you must call + /// `Stylist::media_features_change_changed_style` and + /// `Stylist::force_stylesheet_origins_dirty`. + pub fn set_all_pointer_capabilities(&mut self, capabilities: PointerCapabilities) { + self.extra.media_data.all_pointer_capabilities = capabilities; + } + + /// Returns the pointer capabilities of this [`Device`]. + pub fn all_pointer_capabilities(&self) -> PointerCapabilities { + self.extra.media_data.all_pointer_capabilities } pub(crate) fn is_dark_color_scheme(&self, _: ColorSchemeFlags) -> bool { diff --git a/style/font_face.rs b/style/font_face.rs index 192c38dd4d..0f0ba9237e 100644 --- a/style/font_face.rs +++ b/style/font_face.rs @@ -10,6 +10,8 @@ use crate::derives::*; use crate::error_reporting::ContextualParseError; use crate::parser::{Parse, ParserContext}; use crate::shared_lock::{SharedRwLockReadGuard, ToCssWithGuard}; +use crate::values::computed::font::FontStyleFixedPoint; +use crate::values::computed::FontWeight; use crate::values::generics::font::FontStyle as GenericFontStyle; use crate::values::specified::{url::SpecifiedUrl, Angle}; use cssparser::{Parser, RuleBodyParser, SourceLocation}; @@ -333,13 +335,17 @@ macro_rules! impl_range { pub struct FontWeightRange(pub AbsoluteFontWeight, pub AbsoluteFontWeight); impl_range!(FontWeightRange, AbsoluteFontWeight); -/// The computed representation of the above so Gecko can read them easily. +/// The computed representation of the above so Gecko and Servo can read them easily. /// /// This one is needed because cbindgen doesn't know how to generate /// specified::Number. #[repr(C)] #[allow(missing_docs)] -pub struct ComputedFontWeightRange(f32, f32); +#[cfg_attr( + feature = "servo", + derive(Clone, Debug, Deserialize, Hash, MallocSizeOf, PartialEq, Serialize) +)] +pub struct ComputedFontWeightRange(pub FontWeight, pub FontWeight); #[inline] fn sort_range(a: T, b: T) -> (T, T) { @@ -353,7 +359,7 @@ fn sort_range(a: T, b: T) -> (T, T) { impl FontWeightRange { /// Returns a computed font-weight range, or None if either bound is an unresolvable calc. pub fn compute(&self) -> Option { - let (min, max) = sort_range(self.0.compute()?.value(), self.1.compute()?.value()); + let (min, max) = sort_range(self.0.compute()?, self.1.compute()?); Some(ComputedFontWeightRange(min, max)) } } @@ -365,11 +371,15 @@ impl FontWeightRange { pub struct FontStretchRange(pub SpecifiedFontStretch, pub SpecifiedFontStretch); impl_range!(FontStretchRange, SpecifiedFontStretch); -/// The computed representation of the above, so that Gecko can read them +/// The computed representation of the above, so that Gecko and Servo can read them /// easily. #[repr(C)] #[allow(missing_docs)] -pub struct ComputedFontStretchRange(FontStretch, FontStretch); +#[cfg_attr( + feature = "servo", + derive(Clone, Debug, Deserialize, Hash, MallocSizeOf, PartialEq, Serialize) +)] +pub struct ComputedFontStretchRange(pub FontStretch, pub FontStretch); impl FontStretchRange { /// Returns a computed font-stretch range, or None if any value contains a calc @@ -400,13 +410,14 @@ pub enum FontStyle { Oblique(Angle, Angle), } -/// The computed representation of the above, with angles in degrees, so that -/// Gecko can read them easily. +/// The computed representation of the above, with angles in degrees stored as +/// signed 8.8 fixed-point values, so that Gecko and Servo can read them easily. #[repr(u8)] #[allow(missing_docs)] +#[cfg_attr(feature = "servo", derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize))] pub enum ComputedFontStyleDescriptor { Italic, - Oblique(f32, f32), + Oblique(FontStyleFixedPoint, FontStyleFixedPoint), } impl Parse for FontStyle { @@ -474,7 +485,10 @@ impl FontStyle { let first = SpecifiedFontStyle::compute_angle_degrees(first)?; let second = SpecifiedFontStyle::compute_angle_degrees(second)?; let (min, max) = sort_range(first, second); - Some(ComputedFontStyleDescriptor::Oblique(min, max)) + Some(ComputedFontStyleDescriptor::Oblique( + FontStyleFixedPoint::from_float(min), + FontStyleFixedPoint::from_float(max), + )) }, } } diff --git a/style/properties/build.py b/style/properties/build.py index c09727cc20..5ad2bc0666 100644 --- a/style/properties/build.py +++ b/style/properties/build.py @@ -8,6 +8,9 @@ import sys BASE = os.path.dirname(__file__.replace("\\", "/")) +sys.path.insert(0, os.path.join(BASE, "vendored_python", "mako-1.3.10-py3-none-any.whl")) +sys.path.insert(0, os.path.join(BASE, "vendored_python", "toml-0.10.2-py2.py3-none-any.whl")) +sys.path.insert(0, os.path.join(BASE, "vendored_python")) # For importing markupsafe sys.path.insert(0, BASE) # For importing `data.py` from mako import exceptions diff --git a/style/properties/longhands.toml b/style/properties/longhands.toml index 1393e3491e..93e2eb6242 100644 --- a/style/properties/longhands.toml +++ b/style/properties/longhands.toml @@ -863,7 +863,6 @@ affects = "layout" type = "FontFeatureSettings" initial = "computed::FontFeatureSettings::normal()" struct = "font" -engine = "gecko" extra_prefixes = ["moz:layout.css.prefixes.font-features", "webkit"] animation_type = "discrete" spec = "https://drafts.csswg.org/css-fonts/#propdef-font-feature-settings" @@ -981,7 +980,6 @@ affects = "layout" type = "FontVariantEastAsian" initial = "computed::FontVariantEastAsian::empty()" struct = "font" -engine = "gecko" animation_type = "discrete" gecko_ffi_name = "mFont.variantEastAsian" spec = "https://drafts.csswg.org/css-fonts/#propdef-font-variant-east-asian" @@ -991,7 +989,6 @@ affects = "layout" type = "FontVariantLigatures" initial = "computed::FontVariantLigatures::empty()" struct = "font" -engine = "gecko" animation_type = "discrete" gecko_ffi_name = "mFont.variantLigatures" spec = "https://drafts.csswg.org/css-fonts/#propdef-font-variant-ligatures" @@ -1001,7 +998,6 @@ affects = "layout" type = "FontVariantNumeric" initial = "computed::FontVariantNumeric::empty()" struct = "font" -engine = "gecko" animation_type = "discrete" gecko_ffi_name = "mFont.variantNumeric" spec = "https://drafts.csswg.org/css-fonts/#propdef-font-variant-numeric" @@ -2072,7 +2068,7 @@ affects = "layout" type = "TouchAction" initial = "computed::TouchAction::auto()" struct = "box" -engine = "gecko" +servo_pref = "layout.unimplemented" animation_type = "discrete" spec = "https://compat.spec.whatwg.org/#touch-action" affects = "paint" @@ -2367,7 +2363,7 @@ affects = "paint" type = "position::HorizontalPosition" initial = "computed::LengthPercentage::zero_percent()" struct = "svg" -engine = "gecko" +servo_pref = "layout.unimplemented" extra_prefixes = ["webkit"] spec = "https://drafts.fxtf.org/css-masking-1/#propdef-mask-position" vector = { animation_type = "repeatable_list" } @@ -2377,7 +2373,7 @@ affects = "paint" type = "position::VerticalPosition" initial = "computed::LengthPercentage::zero_percent()" struct = "svg" -engine = "gecko" +servo_pref = "layout.unimplemented" extra_prefixes = ["webkit"] spec = "https://drafts.fxtf.org/css-masking-1/#propdef-mask-position" vector = { animation_type = "repeatable_list" } @@ -2387,7 +2383,7 @@ affects = "paint" type = "BackgroundRepeat" initial = "computed::BackgroundRepeat::repeat()" struct = "svg" -engine = "gecko" +servo_pref = "layout.unimplemented" extra_prefixes = ["webkit"] animation_type = "discrete" spec = "https://drafts.fxtf.org/css-masking-1/#propdef-mask-repeat" @@ -2398,7 +2394,7 @@ affects = "paint" type = "background::BackgroundSize" initial = "computed::BackgroundSize::auto()" struct = "svg" -engine = "gecko" +servo_pref = "layout.unimplemented" extra_prefixes = ["webkit"] spec = "https://drafts.fxtf.org/css-masking-1/#propdef-mask-size" vector = { animation_type = "repeatable_list" } @@ -3151,6 +3147,7 @@ initial = "computed::CornerShape::round()" initial_specified_value = "specified::CornerShape::round()" struct = "border" gecko_pref = "layout.css.corner-shape.enabled" +servo_pref = "layout.unimplemented" spec = "https://drafts.csswg.org/css-borders-4/#propdef-corner-top-left-shape" gecko_ffi_name = "mCornerShape.top_left" logical_group = "corner-shape" @@ -3162,6 +3159,7 @@ initial = "computed::CornerShape::round()" initial_specified_value = "specified::CornerShape::round()" struct = "border" gecko_pref = "layout.css.corner-shape.enabled" +servo_pref = "layout.unimplemented" spec = "https://drafts.csswg.org/css-borders-4/#propdef-corner-top-right-shape" gecko_ffi_name = "mCornerShape.top_right" logical_group = "corner-shape" @@ -3173,6 +3171,7 @@ initial = "computed::CornerShape::round()" initial_specified_value = "specified::CornerShape::round()" struct = "border" gecko_pref = "layout.css.corner-shape.enabled" +servo_pref = "layout.unimplemented" spec = "https://drafts.csswg.org/css-borders-4/#propdef-corner-bottom-right-shape" gecko_ffi_name = "mCornerShape.bottom_right" logical_group = "corner-shape" @@ -3184,6 +3183,7 @@ initial = "computed::CornerShape::round()" initial_specified_value = "specified::CornerShape::round()" struct = "border" gecko_pref = "layout.css.corner-shape.enabled" +servo_pref = "layout.unimplemented" spec = "https://drafts.csswg.org/css-borders-4/#propdef-corner-bottom-left-shape" gecko_ffi_name = "mCornerShape.bottom_left" logical_group = "corner-shape" @@ -3195,6 +3195,7 @@ initial = "computed::CornerShape::round()" initial_specified_value = "specified::CornerShape::round()" struct = "border" gecko_pref = "layout.css.corner-shape.enabled" +servo_pref = "layout.unimplemented" spec = "https://drafts.csswg.org/css-borders-4/#propdef-corner-start-start-shape" gecko_ffi_name = "mCornerShape.start_start" logical_group = "corner-shape" @@ -3207,6 +3208,7 @@ initial = "computed::CornerShape::round()" initial_specified_value = "specified::CornerShape::round()" struct = "border" gecko_pref = "layout.css.corner-shape.enabled" +servo_pref = "layout.unimplemented" spec = "https://drafts.csswg.org/css-borders-4/#propdef-corner-start-end-shape" gecko_ffi_name = "mCornerShape.start_end" logical_group = "corner-shape" @@ -3219,6 +3221,7 @@ initial = "computed::CornerShape::round()" initial_specified_value = "specified::CornerShape::round()" struct = "border" gecko_pref = "layout.css.corner-shape.enabled" +servo_pref = "layout.unimplemented" spec = "https://drafts.csswg.org/css-borders-4/#propdef-corner-end-start-shape" gecko_ffi_name = "mCornerShape.end_start" logical_group = "corner-shape" @@ -3231,6 +3234,7 @@ initial = "computed::CornerShape::round()" initial_specified_value = "specified::CornerShape::round()" struct = "border" gecko_pref = "layout.css.corner-shape.enabled" +servo_pref = "layout.unimplemented" spec = "https://drafts.csswg.org/css-borders-4/#propdef-corner-end-end-shape" gecko_ffi_name = "mCornerShape.end_end" logical_group = "corner-shape" @@ -3759,7 +3763,6 @@ keyword = { values = ["normal", "text", "emoji", "unicode"] } [font-variant-position] struct = "font" -engine = "gecko" spec = "https://drafts.csswg.org/css-fonts/#propdef-font-variant-position" animation_type = "discrete" affects = "layout" @@ -4020,7 +4023,7 @@ keyword = { values = ["fill", "contain", "cover", "none", "scale-down"] } [mask-type] struct = "svg" -engine = "gecko" +servo_pref = "layout.unimplemented" spec = "https://drafts.fxtf.org/css-masking-1/#propdef-mask-type" animation_type = "discrete" affects = "paint" @@ -4028,7 +4031,7 @@ keyword = { values = ["luminance", "alpha"] } [mask-mode] struct = "svg" -engine = "gecko" +servo_pref = "layout.unimplemented" spec = "https://drafts.fxtf.org/css-masking-1/#propdef-mask-mode" animation_type = "discrete" affects = "paint" @@ -4037,7 +4040,7 @@ keyword = { values = ["match-source", "alpha", "luminance"] } [mask-clip] struct = "svg" -engine = "gecko" +servo_pref = "layout.unimplemented" spec = "https://drafts.fxtf.org/css-masking-1/#propdef-mask-clip" animation_type = "discrete" affects = "paint" @@ -4047,7 +4050,7 @@ keyword = { values = ["border-box", "content-box", "padding-box"], extra_gecko_v [mask-origin] struct = "svg" -engine = "gecko" +servo_pref = "layout.unimplemented" spec = "https://drafts.fxtf.org/css-masking-1/#propdef-mask-origin" animation_type = "discrete" affects = "paint" @@ -4057,7 +4060,7 @@ keyword = { values = ["border-box", "content-box", "padding-box"], extra_gecko_v [mask-composite] struct = "svg" -engine = "gecko" +servo_pref = "layout.unimplemented" spec = "https://drafts.fxtf.org/css-masking-1/#propdef-mask-composite" animation_type = "discrete" affects = "paint" diff --git a/style/properties/properties.mako.rs b/style/properties/properties.mako.rs index b3ea47e9ce..c675e47c8a 100644 --- a/style/properties/properties.mako.rs +++ b/style/properties/properties.mako.rs @@ -14,7 +14,6 @@ use std::{ops, ptr, fmt, mem}; #[cfg(feature = "servo")] use euclid::SideOffsets2D; #[cfg(feature = "gecko")] use crate::gecko_bindings::structs::{self, NonCustomCSSPropertyId}; #[cfg(feature = "servo")] use crate::logical_geometry::LogicalMargin; -#[cfg(feature = "servo")] use crate::computed_values; #[cfg(feature = "servo")] use crate::dom::AttributeReferences; use crate::logical_geometry::WritingMode; use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; @@ -32,8 +31,8 @@ use crate::typed_om::{ToTyped, TypedValueList}; use crate::use_counters::UseCounters; use crate::rule_tree::StrongRuleNode; use crate::values::{ - computed, - resolved, + computed::{self, ToComputedValue}, + resolved::{self, ToResolvedValue}, specified::{font::SystemFont, length::LineHeightBase, color::ColorSchemeFlags}, }; use std::cell::Cell; @@ -384,30 +383,15 @@ impl NonCustomPropertyId { % if engine == "gecko": unsafe { structs::nsCSSProps_gPropertyEnabled[self.0 as usize] } % else: - static PREF_NAME: [Option<&str>; ${ - len(data.longhands) + len(data.shorthands) + len(data.all_aliases()) - }] = [ - % for property in data.longhands + data.shorthands + data.all_aliases(): - <% - pref = getattr(property, "servo_pref") - %> - % if pref: - { - const_assert!(!static_prefs::default_value!("${pref}")); - Some("${pref}") - }, - % else: - None, - % endif - % endfor - ]; - let pref = match PREF_NAME[self.0 as usize] { - None => return true, - Some(pref) => pref, - }; - - // The assertions above guarantee that the pref defaults to false. - static_prefs::Preference::get(pref, false) + match self.0 { + % for (index, property) in enumerate(data.longhands + data.shorthands + data.all_aliases()): + <% preference = getattr(property, "servo_pref") %> + % if preference: + ${index} => static_prefs::pref!("${preference}"), + % endif % + % endfor + _ => true, + } % endif }; @@ -1710,7 +1694,6 @@ impl ComputedValues { context: Option<&mut resolved::Context>, dest: &mut CssStringWriter, ) -> fmt::Result { - use crate::values::resolved::ToResolvedValue; let mut dest = CssWriter::new(dest); let property_id = property_id.to_physical(self.writing_mode); match property_id { @@ -1767,8 +1750,6 @@ impl ComputedValues { property_id: LonghandId, context: Option<&mut resolved::Context>, ) -> PropertyDeclaration { - use crate::values::resolved::ToResolvedValue; - use crate::values::computed::ToComputedValue; let physical_property_id = property_id.to_physical(self.writing_mode); match physical_property_id { % for specified_type, props in groupby(data.longhands, key=lambda x: x.specified_type()): @@ -1947,11 +1928,19 @@ impl ComputedValues { // whether the name corresponds to an inherited custom property // and then choose the inherited/non_inherited map accordingly. let p = &self.custom_properties; - let value = p - .inherited - .get(name) - .or_else(|| p.non_inherited.get(name)); - value.map_or(String::new(), |value| value.to_css_string()) + let Some(value) = p.inherited.get(name).or_else(|| p.non_inherited.get(name)) + else { + return String::new(); + }; + let mut context = resolved::Context { + style: self, + for_property: PropertyId::Custom(name.clone()), + current_longhand: None, + }; + value + .clone() + .to_resolved_value(&mut context) + .to_css_string() } } } @@ -2131,32 +2120,6 @@ impl ComputedValuesInner { &position_style.left, )) } - - /// Return true if the effects force the transform style to be Flat - pub fn overrides_transform_style(&self) -> bool { - use crate::computed_values::mix_blend_mode::T as MixBlendMode; - - let effects = self.get_effects(); - // TODO(gw): Add clip-path, isolation, mask-image, mask-border-source when supported. - effects.opacity < 1.0 || - !effects.filter.0.is_empty() || - !effects.clip.is_auto() || - effects.mix_blend_mode != MixBlendMode::Normal - } - - /// - pub fn get_used_transform_style(&self) -> computed_values::transform_style::T { - use crate::computed_values::transform_style::T as TransformStyle; - - let box_ = self.get_box(); - - if self.overrides_transform_style() { - TransformStyle::Flat - } else { - // Return the computed value if not overridden by the above exceptions - box_.transform_style - } - } } /// A reference to a style struct of the parent, or our own style struct. diff --git a/style/properties/shorthands.rs b/style/properties/shorthands.rs index 8fed140360..340233a009 100644 --- a/style/properties/shorthands.rs +++ b/style/properties/shorthands.rs @@ -2555,13 +2555,13 @@ pub mod font { use super::*; #[cfg(feature = "gecko")] use crate::properties::longhands::{ - font_family, font_feature_settings, font_language_override, font_size, font_size_adjust, - font_variant_alternates, font_variant_east_asian, font_variant_emoji, - font_variant_ligatures, font_variant_numeric, font_variant_position, + font_family, font_language_override, font_size, font_size_adjust, + font_variant_alternates, font_variant_emoji }; use crate::properties::longhands::{ - font_kerning, font_optical_sizing, font_stretch, font_style, font_variant_caps, - font_variation_settings, font_weight, + font_feature_settings, font_kerning, font_optical_sizing, font_stretch, font_style, font_variant_caps, + font_variant_east_asian, font_variant_ligatures, font_variant_numeric, font_variation_settings, + font_variant_position, font_weight, }; #[cfg(feature = "gecko")] use crate::values::specified::font::SystemFont; @@ -2668,17 +2668,12 @@ pub mod font { font_size_adjust: font_size_adjust::get_initial_specified_value(), #[cfg(feature = "gecko")] font_variant_alternates: font_variant_alternates::get_initial_specified_value(), - #[cfg(feature = "gecko")] font_variant_east_asian: font_variant_east_asian::get_initial_specified_value(), #[cfg(feature = "gecko")] font_variant_emoji: font_variant_emoji::get_initial_specified_value(), - #[cfg(feature = "gecko")] font_variant_ligatures: font_variant_ligatures::get_initial_specified_value(), - #[cfg(feature = "gecko")] font_variant_numeric: font_variant_numeric::get_initial_specified_value(), - #[cfg(feature = "gecko")] font_variant_position: font_variant_position::get_initial_specified_value(), - #[cfg(feature = "gecko")] font_feature_settings: font_feature_settings::get_initial_specified_value(), }) } @@ -2737,26 +2732,21 @@ pub mod font { { return Ok(()); } - #[cfg(feature = "gecko")] if self.font_variant_east_asian != &font_variant_east_asian::get_initial_specified_value() { return Ok(()); } - #[cfg(feature = "gecko")] if self.font_variant_ligatures != &font_variant_ligatures::get_initial_specified_value() { return Ok(()); } - #[cfg(feature = "gecko")] if self.font_variant_numeric != &font_variant_numeric::get_initial_specified_value() { return Ok(()); } - #[cfg(feature = "gecko")] if self.font_variant_position != &font_variant_position::get_initial_specified_value() { return Ok(()); } - #[cfg(feature = "gecko")] if self.font_feature_settings != &font_feature_settings::get_initial_specified_value() { return Ok(()); } @@ -2885,29 +2875,26 @@ pub mod font_variant { pub use crate::properties::generated::shorthands::font_variant::*; use super::*; - use crate::properties::longhands::font_variant_caps; #[cfg(feature = "gecko")] use crate::properties::longhands::{ - font_variant_alternates, font_variant_east_asian, font_variant_emoji, - font_variant_ligatures, font_variant_numeric, font_variant_position, + font_variant_alternates, font_variant_emoji, + }; + use crate::properties::longhands::{ + font_variant_caps, font_variant_east_asian, font_variant_ligatures, font_variant_numeric, + font_variant_position, }; - #[allow(unused_imports)] use crate::values::specified::FontVariantLigatures; pub fn parse_value<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { - #[cfg(feature = "gecko")] let mut ligatures = None; let mut caps = None; #[cfg(feature = "gecko")] let mut alternates = None; - #[cfg(feature = "gecko")] let mut numeric = None; - #[cfg(feature = "gecko")] let mut east_asian = None; - #[cfg(feature = "gecko")] let mut position = None; #[cfg(feature = "gecko")] let mut emoji = None; @@ -2920,10 +2907,7 @@ pub mod font_variant { .try_parse(|input| input.expect_ident_matching("none")) .is_ok() { - #[cfg(feature = "gecko")] - { - ligatures = Some(FontVariantLigatures::NONE); - } + ligatures = Some(FontVariantLigatures::NONE); } else { let mut parsed = 0; loop { @@ -2937,16 +2921,12 @@ pub mod font_variant { { return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); } - #[cfg(feature = "gecko")] try_parse_one!(context, input, ligatures, font_variant_ligatures::parse); try_parse_one!(context, input, caps, font_variant_caps::parse); #[cfg(feature = "gecko")] try_parse_one!(context, input, alternates, font_variant_alternates::parse); - #[cfg(feature = "gecko")] try_parse_one!(context, input, numeric, font_variant_numeric::parse); - #[cfg(feature = "gecko")] try_parse_one!(context, input, east_asian, font_variant_east_asian::parse); - #[cfg(feature = "gecko")] try_parse_one!(context, input, position, font_variant_position::parse); #[cfg(feature = "gecko")] try_parse_one!(context, input, emoji, font_variant_emoji::parse); @@ -2972,6 +2952,10 @@ pub mod font_variant { #[cfg(feature = "servo")] return Ok(expanded! { font_variant_caps: unwrap_or_initial!(font_variant_caps, caps), + font_variant_east_asian: unwrap_or_initial!(font_variant_east_asian, east_asian), + font_variant_ligatures: unwrap_or_initial!(font_variant_ligatures, ligatures), + font_variant_numeric: unwrap_or_initial!(font_variant_numeric, numeric), + font_variant_position: unwrap_or_initial!(font_variant_position, position), }); } @@ -2981,15 +2965,12 @@ pub mod font_variant { where W: fmt::Write, { - #[cfg(feature = "gecko")] let has_none_ligatures = self.font_variant_ligatures == &FontVariantLigatures::NONE; - #[cfg(feature = "servo")] - let has_none_ligatures = false; #[cfg(feature = "gecko")] const TOTAL_SUBPROPS: usize = 7; #[cfg(feature = "servo")] - const TOTAL_SUBPROPS: usize = 1; + const TOTAL_SUBPROPS: usize = 5; let mut nb_normals = 0; macro_rules! count_normal { ($e: expr, $p: ident) => { @@ -3001,16 +2982,12 @@ pub mod font_variant { count_normal!(self.$v, $v); }; } - #[cfg(feature = "gecko")] count_normal!(font_variant_ligatures); count_normal!(font_variant_caps); #[cfg(feature = "gecko")] count_normal!(font_variant_alternates); - #[cfg(feature = "gecko")] count_normal!(font_variant_numeric); - #[cfg(feature = "gecko")] count_normal!(font_variant_east_asian); - #[cfg(feature = "gecko")] count_normal!(font_variant_position); #[cfg(feature = "gecko")] if let Some(value) = self.font_variant_emoji { @@ -3043,16 +3020,12 @@ pub mod font_variant { }; } - #[cfg(feature = "gecko")] write!(font_variant_ligatures); write!(font_variant_caps); #[cfg(feature = "gecko")] write!(font_variant_alternates); - #[cfg(feature = "gecko")] write!(font_variant_numeric); - #[cfg(feature = "gecko")] write!(font_variant_east_asian); - #[cfg(feature = "gecko")] write!(font_variant_position); #[cfg(feature = "gecko")] if let Some(v) = self.font_variant_emoji { @@ -3630,7 +3603,6 @@ pub mod animation { } } -#[cfg(feature = "gecko")] pub mod mask { pub use crate::properties::generated::shorthands::mask::*; @@ -3657,12 +3629,15 @@ pub mod mask { mask_origin::single_value::SpecifiedValue::BorderBox => { mask_clip::single_value::SpecifiedValue::BorderBox }, + #[cfg(feature = "gecko")] mask_origin::single_value::SpecifiedValue::FillBox => { mask_clip::single_value::SpecifiedValue::FillBox }, + #[cfg(feature = "gecko")] mask_origin::single_value::SpecifiedValue::StrokeBox => { mask_clip::single_value::SpecifiedValue::StrokeBox }, + #[cfg(feature = "gecko")] mask_origin::single_value::SpecifiedValue::ViewBox => { mask_clip::single_value::SpecifiedValue::ViewBox }, @@ -3889,8 +3864,18 @@ pub mod mask { writer.item(repeat)?; } - if has_origin || (has_clip && *clip != Clip::NoClip) { - writer.item(origin)?; + #[cfg(feature = "gecko")] + { + if has_origin || (has_clip && *clip != Clip::NoClip) { + writer.item(origin)?; + } + } + + #[cfg(feature = "servo")] + { + if has_origin || has_clip { + writer.item(origin)?; + } } if has_clip && *clip != From::from(*origin) { @@ -3911,7 +3896,6 @@ pub mod mask { } } -#[cfg(feature = "gecko")] pub mod mask_position { pub use crate::properties::generated::shorthands::mask_position::*; diff --git a/style/properties/shorthands.toml b/style/properties/shorthands.toml index 0621df14e8..529b60a3ad 100644 --- a/style/properties/shorthands.toml +++ b/style/properties/shorthands.toml @@ -392,16 +392,16 @@ sub_properties = ["background-position-x", "background-position-y"] spec = "https://drafts.csswg.org/css-backgrounds-4/#the-background-position" [mask] -engine = "gecko" sub_properties = ["mask-mode", "mask-repeat", "mask-clip", "mask-origin", "mask-composite", "mask-position-x", "mask-position-y", "mask-size", "mask-image"] spec = "https://drafts.fxtf.org/css-masking/#propdef-mask" extra_prefixes = ["webkit"] +servo_pref = "layout.unimplemented" [mask-position] -engine = "gecko" sub_properties = ["mask-position-x", "mask-position-y"] spec = "https://drafts.csswg.org/css-masks-4/#the-mask-position" extra_prefixes = ["webkit"] +servo_pref = "layout.unimplemented" [border-radius] sub_properties = ["border-top-left-radius", "border-top-right-radius", "border-bottom-right-radius", "border-bottom-left-radius"] @@ -412,53 +412,62 @@ extra_prefixes = ["webkit"] sub_properties = ["corner-top-left-shape", "corner-top-right-shape", "corner-bottom-right-shape", "corner-bottom-left-shape"] spec = "https://drafts.csswg.org/css-borders-4/#propdef-corner-shape" gecko_pref = "layout.css.corner-shape.enabled" +servo_pref = "layout.unimplemented" [corner-top-shape] sub_properties = ["corner-top-left-shape", "corner-top-right-shape"] spec = "https://drafts.csswg.org/css-borders-4/#propdef-corner-top-shape" gecko_pref = "layout.css.corner-shape.enabled" +servo_pref = "layout.unimplemented" kind = "two_properties" [corner-right-shape] sub_properties = ["corner-top-right-shape", "corner-bottom-right-shape"] spec = "https://drafts.csswg.org/css-borders-4/#propdef-corner-right-shape" gecko_pref = "layout.css.corner-shape.enabled" +servo_pref = "layout.unimplemented" kind = "two_properties" [corner-bottom-shape] sub_properties = ["corner-bottom-left-shape", "corner-bottom-right-shape"] spec = "https://drafts.csswg.org/css-borders-4/#propdef-corner-bottom-shape" gecko_pref = "layout.css.corner-shape.enabled" +servo_pref = "layout.unimplemented" kind = "two_properties" [corner-left-shape] sub_properties = ["corner-top-left-shape", "corner-bottom-left-shape"] spec = "https://drafts.csswg.org/css-borders-4/#propdef-corner-left-shape" gecko_pref = "layout.css.corner-shape.enabled" +servo_pref = "layout.unimplemented" kind = "two_properties" [corner-block-start-shape] sub_properties = ["corner-start-start-shape", "corner-start-end-shape"] spec = "https://drafts.csswg.org/css-borders-4/#propdef-corner-block-start-shape" gecko_pref = "layout.css.corner-shape.enabled" +servo_pref = "layout.unimplemented" kind = "two_properties" [corner-block-end-shape] sub_properties = ["corner-end-start-shape", "corner-end-end-shape"] spec = "https://drafts.csswg.org/css-borders-4/#propdef-corner-block-end-shape" gecko_pref = "layout.css.corner-shape.enabled" +servo_pref = "layout.unimplemented" kind = "two_properties" [corner-inline-start-shape] sub_properties = ["corner-start-start-shape", "corner-end-start-shape"] spec = "https://drafts.csswg.org/css-borders-4/#propdef-corner-inline-start-shape" gecko_pref = "layout.css.corner-shape.enabled" +servo_pref = "layout.unimplemented" kind = "two_properties" [corner-inline-end-shape] sub_properties = ["corner-start-end-shape", "corner-end-end-shape"] spec = "https://drafts.csswg.org/css-borders-4/#propdef-corner-inline-end-shape" gecko_pref = "layout.css.corner-shape.enabled" +servo_pref = "layout.unimplemented" kind = "two_properties" [outline] @@ -485,6 +494,10 @@ spec = "https://drafts.csswg.org/css-text-decor/#propdef-text-decoration" sub_properties = [ "font-style", "font-variant-caps", + "font-variant-east-asian", + "font-variant-ligatures", + "font-variant-numeric", + "font-variant-position", "font-weight", "font-stretch", "font-size", @@ -492,25 +505,21 @@ sub_properties = [ "font-family", "font-optical-sizing", "font-variation-settings", - "font-kerning" + "font-kerning", + "font-feature-settings", ] extra_gecko_sub_properties = [ "font-size-adjust", "font-variant-alternates", - "font-variant-east-asian", "font-variant-emoji", - "font-variant-ligatures", - "font-variant-numeric", - "font-variant-position", - "font-language-override", - "font-feature-settings" + "font-language-override" ] spec = "https://drafts.csswg.org/css-fonts-3/#propdef-font" derive_value_info = false [font-variant] -sub_properties = ["font-variant-caps"] -extra_gecko_sub_properties = ["font-variant-alternates", "font-variant-east-asian", "font-variant-emoji", "font-variant-ligatures", "font-variant-numeric", "font-variant-position"] +sub_properties = ["font-variant-caps", "font-variant-east-asian", "font-variant-ligatures", "font-variant-numeric", "font-variant-position"] +extra_gecko_sub_properties = ["font-variant-alternates", "font-variant-emoji"] spec = "https://drafts.csswg.org/css-fonts-3/#propdef-font-variant" [font-synthesis] diff --git a/style/properties/vendored_python/mako-1.3.10-py3-none-any.whl b/style/properties/vendored_python/mako-1.3.10-py3-none-any.whl new file mode 100644 index 0000000000..2f85cd7b57 Binary files /dev/null and b/style/properties/vendored_python/mako-1.3.10-py3-none-any.whl differ diff --git a/style/properties/vendored_python/markupsafe/LICENSE.txt b/style/properties/vendored_python/markupsafe/LICENSE.txt new file mode 100644 index 0000000000..e270514adb --- /dev/null +++ b/style/properties/vendored_python/markupsafe/LICENSE.txt @@ -0,0 +1,28 @@ +Copyright 2010 Pallets + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/style/properties/vendored_python/markupsafe/__init__.py b/style/properties/vendored_python/markupsafe/__init__.py new file mode 100644 index 0000000000..8bf187d55e --- /dev/null +++ b/style/properties/vendored_python/markupsafe/__init__.py @@ -0,0 +1,384 @@ +# Vendored from https://github.com/pallets/markupsafe/blob/1251593f6b0e3b45f2cc8aba662622bc22d6a5e2/src/markupsafe/__init__.py +# with patched section from https://github.com/pallets/markupsafe/blob/1251593f6b0e3b45f2cc8aba662622bc22d6a5e2/src/markupsafe/_native.py +from __future__ import annotations + +import collections.abc as cabc +import string +import typing as t + +# BEGIN PATCHED SECTION +def _escape_inner(s: str, /) -> str: + return ( + s.replace("&", "&") + .replace(">", ">") + .replace("<", "<") + .replace("'", "'") + .replace('"', """) + ) +# BEGIN PATCHED SECTION + + +class _HasHTML(t.Protocol): + def __html__(self, /) -> str: ... + + +class _TPEscape(t.Protocol): + def __call__(self, s: t.Any, /) -> Markup: ... + + +def escape(s: t.Any, /) -> Markup: + """Replace the characters ``&``, ``<``, ``>``, ``'``, and ``"`` in + the string with HTML-safe sequences. Use this if you need to display + text that might contain such characters in HTML. + + If the object has an ``__html__`` method, it is called and the + return value is assumed to already be safe for HTML. + + :param s: An object to be converted to a string and escaped. + :return: A :class:`Markup` string with the escaped text. + """ + # If the object is already a plain string, skip __html__ check and string + # conversion. This is the most common use case. + # Use type(s) instead of s.__class__ because a proxy object may be reporting + # the __class__ of the proxied value. + if type(s) is str: + return Markup(_escape_inner(s)) + + if hasattr(s, "__html__"): + return Markup(s.__html__()) + + return Markup(_escape_inner(str(s))) + + +def escape_silent(s: t.Any | None, /) -> Markup: + """Like :func:`escape` but treats ``None`` as the empty string. + Useful with optional values, as otherwise you get the string + ``'None'`` when the value is ``None``. + + >>> escape(None) + Markup('None') + >>> escape_silent(None) + Markup('') + """ + if s is None: + return Markup() + + return escape(s) + + +def soft_str(s: t.Any, /) -> str: + """Convert an object to a string if it isn't already. This preserves + a :class:`Markup` string rather than converting it back to a basic + string, so it will still be marked as safe and won't be escaped + again. + + >>> value = escape("") + >>> value + Markup('<User 1>') + >>> escape(str(value)) + Markup('&lt;User 1&gt;') + >>> escape(soft_str(value)) + Markup('<User 1>') + """ + if not isinstance(s, str): + return str(s) + + return s + + +class Markup(str): + """A string that is ready to be safely inserted into an HTML or XML + document, either because it was escaped or because it was marked + safe. + + Passing an object to the constructor converts it to text and wraps + it to mark it safe without escaping. To escape the text, use the + :meth:`escape` class method instead. + + >>> Markup("Hello, World!") + Markup('Hello, World!') + >>> Markup(42) + Markup('42') + >>> Markup.escape("Hello, World!") + Markup('Hello <em>World</em>!') + + This implements the ``__html__()`` interface that some frameworks + use. Passing an object that implements ``__html__()`` will wrap the + output of that method, marking it safe. + + >>> class Foo: + ... def __html__(self): + ... return 'foo' + ... + >>> Markup(Foo()) + Markup('foo') + + This is a subclass of :class:`str`. It has the same methods, but + escapes their arguments and returns a ``Markup`` instance. + + >>> Markup("%s") % ("foo & bar",) + Markup('foo & bar') + >>> Markup("Hello ") + "" + Markup('Hello <foo>') + """ + + __slots__ = () + + def __new__( + cls, object: t.Any = "", encoding: str | None = None, errors: str = "strict" + ) -> te.Self: + if hasattr(object, "__html__"): + object = object.__html__() + + if encoding is None: + return super().__new__(cls, object) + + return super().__new__(cls, object, encoding, errors) + + def __html__(self, /) -> te.Self: + return self + + def __add__(self, value: str | _HasHTML, /) -> te.Self: + if isinstance(value, str) or hasattr(value, "__html__"): + return self.__class__(super().__add__(self.escape(value))) + + return NotImplemented + + def __radd__(self, value: str | _HasHTML, /) -> te.Self: + if isinstance(value, str) or hasattr(value, "__html__"): + return self.escape(value).__add__(self) + + return NotImplemented + + def __mul__(self, value: t.SupportsIndex, /) -> te.Self: + return self.__class__(super().__mul__(value)) + + def __rmul__(self, value: t.SupportsIndex, /) -> te.Self: + return self.__class__(super().__mul__(value)) + + def __mod__(self, value: t.Any, /) -> te.Self: + if isinstance(value, tuple): + # a tuple of arguments, each wrapped + value = tuple(_MarkupEscapeHelper(x, self.escape) for x in value) + elif hasattr(type(value), "__getitem__") and not isinstance(value, str): + # a mapping of arguments, wrapped + value = _MarkupEscapeHelper(value, self.escape) + else: + # a single argument, wrapped with the helper and a tuple + value = (_MarkupEscapeHelper(value, self.escape),) + + return self.__class__(super().__mod__(value)) + + def __repr__(self, /) -> str: + return f"{self.__class__.__name__}({super().__repr__()})" + + def join(self, iterable: cabc.Iterable[str | _HasHTML], /) -> te.Self: + return self.__class__(super().join(map(self.escape, iterable))) + + def split( # type: ignore[override] + self, /, sep: str | None = None, maxsplit: t.SupportsIndex = -1 + ) -> list[te.Self]: + return [self.__class__(v) for v in super().split(sep, maxsplit)] + + def rsplit( # type: ignore[override] + self, /, sep: str | None = None, maxsplit: t.SupportsIndex = -1 + ) -> list[te.Self]: + return [self.__class__(v) for v in super().rsplit(sep, maxsplit)] + + def splitlines( # type: ignore[override] + self, /, keepends: bool = False + ) -> list[te.Self]: + return [self.__class__(v) for v in super().splitlines(keepends)] + + def unescape(self, /) -> str: + """Convert escaped markup back into a text string. This replaces + HTML entities with the characters they represent. + + >>> Markup("Main » About").unescape() + 'Main » About' + """ + from html import unescape + + return unescape(str(self)) + + def striptags(self, /) -> str: + """:meth:`unescape` the markup, remove tags, and normalize + whitespace to single spaces. + + >>> Markup("Main »\tAbout").striptags() + 'Main » About' + """ + value = str(self) + + # Look for comments then tags separately. Otherwise, a comment that + # contains a tag would end early, leaving some of the comment behind. + + # keep finding comment start marks + while (start := value.find("", start)) == -1: + break + + value = f"{value[:start]}{value[end + 3 :]}" + + # remove tags using the same method + while (start := value.find("<")) != -1: + if (end := value.find(">", start)) == -1: + break + + value = f"{value[:start]}{value[end + 1 :]}" + + # collapse spaces + value = " ".join(value.split()) + return self.__class__(value).unescape() + + @classmethod + def escape(cls, s: t.Any, /) -> te.Self: + """Escape a string. Calls :func:`escape` and ensures that for + subclasses the correct type is returned. + """ + rv = escape(s) + + if rv.__class__ is not cls: + return cls(rv) + + return rv # type: ignore[return-value] + + def __getitem__(self, key: t.SupportsIndex | slice, /) -> te.Self: + return self.__class__(super().__getitem__(key)) + + def capitalize(self, /) -> te.Self: + return self.__class__(super().capitalize()) + + def title(self, /) -> te.Self: + return self.__class__(super().title()) + + def lower(self, /) -> te.Self: + return self.__class__(super().lower()) + + def upper(self, /) -> te.Self: + return self.__class__(super().upper()) + + def replace(self, old: str, new: str, count: t.SupportsIndex = -1, /) -> te.Self: + return self.__class__(super().replace(old, self.escape(new), count)) + + def ljust(self, width: t.SupportsIndex, fillchar: str = " ", /) -> te.Self: + return self.__class__(super().ljust(width, self.escape(fillchar))) + + def rjust(self, width: t.SupportsIndex, fillchar: str = " ", /) -> te.Self: + return self.__class__(super().rjust(width, self.escape(fillchar))) + + def lstrip(self, chars: str | None = None, /) -> te.Self: + return self.__class__(super().lstrip(chars)) + + def rstrip(self, chars: str | None = None, /) -> te.Self: + return self.__class__(super().rstrip(chars)) + + def center(self, width: t.SupportsIndex, fillchar: str = " ", /) -> te.Self: + return self.__class__(super().center(width, self.escape(fillchar))) + + def strip(self, chars: str | None = None, /) -> te.Self: + return self.__class__(super().strip(chars)) + + def translate( + self, + table: cabc.Mapping[int, str | int | None], # type: ignore[override] + /, + ) -> str: + return self.__class__(super().translate(table)) + + def expandtabs(self, /, tabsize: t.SupportsIndex = 8) -> te.Self: + return self.__class__(super().expandtabs(tabsize)) + + def swapcase(self, /) -> te.Self: + return self.__class__(super().swapcase()) + + def zfill(self, width: t.SupportsIndex, /) -> te.Self: + return self.__class__(super().zfill(width)) + + def casefold(self, /) -> te.Self: + return self.__class__(super().casefold()) + + def removeprefix(self, prefix: str, /) -> te.Self: + return self.__class__(super().removeprefix(prefix)) + + def removesuffix(self, suffix: str) -> te.Self: + return self.__class__(super().removesuffix(suffix)) + + def partition(self, sep: str, /) -> tuple[te.Self, te.Self, te.Self]: + left, sep, right = super().partition(sep) + cls = self.__class__ + return cls(left), cls(sep), cls(right) + + def rpartition(self, sep: str, /) -> tuple[te.Self, te.Self, te.Self]: + left, sep, right = super().rpartition(sep) + cls = self.__class__ + return cls(left), cls(sep), cls(right) + + def format(self, *args: t.Any, **kwargs: t.Any) -> te.Self: + formatter = EscapeFormatter(self.escape) + return self.__class__(formatter.vformat(self, args, kwargs)) + + def format_map( + self, + mapping: cabc.Mapping[str, t.Any], # type: ignore[override] + /, + ) -> te.Self: + formatter = EscapeFormatter(self.escape) + return self.__class__(formatter.vformat(self, (), mapping)) + + def __html_format__(self, format_spec: str, /) -> te.Self: + if format_spec: + raise ValueError("Unsupported format specification for Markup.") + + return self + + +class EscapeFormatter(string.Formatter): + __slots__ = ("escape",) + + def __init__(self, escape: _TPEscape) -> None: + self.escape: _TPEscape = escape + super().__init__() + + def format_field(self, value: t.Any, format_spec: str) -> str: + if hasattr(value, "__html_format__"): + rv = value.__html_format__(format_spec) + elif hasattr(value, "__html__"): + if format_spec: + raise ValueError( + f"Format specifier {format_spec} given, but {type(value)} does not" + " define __html_format__. A class that defines __html__ must define" + " __html_format__ to work with format specifiers." + ) + rv = value.__html__() + else: + # We need to make sure the format spec is str here as + # otherwise the wrong callback methods are invoked. + rv = super().format_field(value, str(format_spec)) + return str(self.escape(rv)) + + +class _MarkupEscapeHelper: + """Helper for :meth:`Markup.__mod__`.""" + + __slots__ = ("obj", "escape") + + def __init__(self, obj: t.Any, escape: _TPEscape) -> None: + self.obj: t.Any = obj + self.escape: _TPEscape = escape + + def __getitem__(self, key: t.Any, /) -> te.Self: + return self.__class__(self.obj[key], self.escape) + + def __str__(self, /) -> str: + return str(self.escape(self.obj)) + + def __repr__(self, /) -> str: + return str(self.escape(repr(self.obj))) + + def __int__(self, /) -> int: + return int(self.obj) + + def __float__(self, /) -> float: + return float(self.obj) diff --git a/style/properties/vendored_python/toml-0.10.2-py2.py3-none-any.whl b/style/properties/vendored_python/toml-0.10.2-py2.py3-none-any.whl new file mode 100644 index 0000000000..2cb8dcbd80 Binary files /dev/null and b/style/properties/vendored_python/toml-0.10.2-py2.py3-none-any.whl differ diff --git a/style/servo/animation.rs b/style/servo/animation.rs index fbf0ca0ff9..b19d2b55b6 100644 --- a/style/servo/animation.rs +++ b/style/servo/animation.rs @@ -30,6 +30,7 @@ use crate::values::computed::TimingFunction; use crate::values::generics::easing::BeforeFlag; use crate::values::specified::TransitionBehavior; use crate::Atom; +use debug_unreachable::debug_unreachable; use parking_lot::RwLock; use rustc_hash::FxHashMap; use servo_arc::Arc; @@ -134,11 +135,11 @@ pub enum KeyframesIterationState { struct IntermediateComputedKeyframe { declarations: PropertyDeclarationBlock, timing_function: Option, - start_percentage: f32, + start_percentage: f64, } impl IntermediateComputedKeyframe { - fn new(start_percentage: f32) -> Self { + fn new(start_percentage: f64) -> Self { IntermediateComputedKeyframe { declarations: PropertyDeclarationBlock::new(), timing_function: None, @@ -160,7 +161,7 @@ impl IntermediateComputedKeyframe { let mut intermediate_steps: Vec = Vec::with_capacity(animation.steps.len()); let mut current_step = IntermediateComputedKeyframe::new(0.); for step in animation.steps.iter() { - let start_percentage = step.start_percentage.0; + let start_percentage = step.start_offset.percentage.0 as f64; if start_percentage != current_step.start_percentage { let new_step = IntermediateComputedKeyframe::new(start_percentage); intermediate_steps.push(std::mem::replace(&mut current_step, new_step)); @@ -287,7 +288,7 @@ struct ComputedKeyframe { /// The starting percentage (a number between 0 and 1) which represents /// at what point in an animation iteration this step is. - start_percentage: f32, + start_percentage: f64, /// The animation values to transition to and from when processing this /// keyframe animation step. @@ -323,7 +324,7 @@ struct KeyframeDataForProperty<'a> { /// The starting percentage (a number between 0 and 1) which represents /// at what point in an animation iteration this step is. - start_percentage: f32, + start_percentage: f64, value: &'a AnimationValue, } @@ -593,38 +594,57 @@ impl Animation { return false; } - if self.on_last_iteration() { - return false; - } - - self.iterate(); - true + self.iterate_by(1.) == 1. } - fn iterate(&mut self) { - debug_assert!(!self.on_last_iteration()); + /// Attempts to advance this animation by `n` iterations, but stops when reaching + /// the last iteration, and doesn't perform fractional iterations. + /// Returns the actual number of iterations that happened. + fn iterate_by(&mut self, n: f64) -> f64 { + let n = n.trunc().min(self.remaining_iterations().ceil() - 1.0); + if n < 1. { + return 0.; + } - if let KeyframesIterationState::Finite(ref mut current, max) = self.iteration_state { - *current = (*current + 1.).min(max); + match self.iteration_state { + KeyframesIterationState::Finite(ref mut current, max) => { + *current = (*current + n).min(max); + }, + KeyframesIterationState::Infinite(ref mut current) => { + *current += n; + }, } if let AnimationState::Paused(ref mut progress) = self.state { - debug_assert!(*progress > 1.); - *progress -= 1.; + debug_assert!(*progress >= n); + *progress -= n; } // Update the next iteration direction if applicable. - self.started_at += self.duration; + self.started_at += self.duration * n; match self.direction { - AnimationDirection::Alternate | AnimationDirection::AlternateReverse => { + AnimationDirection::Alternate | AnimationDirection::AlternateReverse + if n % 2. == 1.0 => + { self.current_direction = match self.current_direction { AnimationDirection::Normal => AnimationDirection::Reverse, AnimationDirection::Reverse => AnimationDirection::Normal, - _ => unreachable!(), + _ => unreachable!( + "Current animation direction can only be `normal` or `reverse`." + ), }; }, _ => {}, } + + n + } + + fn remaining_iterations(&self) -> f64 { + match self.iteration_state { + KeyframesIterationState::Finite(current, max) => max - current, + KeyframesIterationState::Infinite(_) => f64::INFINITY, + } } /// A number (> 0 and <= 1) which represents the fraction of a full iteration @@ -632,10 +652,7 @@ impl Animation { /// if the current iteration is the fractional remainder of a non-integral /// iteration count. pub fn current_iteration_end_progress(&self) -> f64 { - match self.iteration_state { - KeyframesIterationState::Finite(current, max) => (max - current).min(1.), - KeyframesIterationState::Infinite(_) => 1., - } + self.remaining_iterations().min(1.) } /// The duration of the current iteration of this animation which may be less @@ -652,10 +669,7 @@ impl Animation { /// Assuming this animation is running, whether or not it is on the last iteration. fn on_last_iteration(&self) -> bool { - match self.iteration_state { - KeyframesIterationState::Finite(current, max) => current >= (max - 1.), - KeyframesIterationState::Infinite(_) => false, - } + self.remaining_iterations() <= 1. } /// Whether or not this animation has finished at the provided time. This does @@ -694,57 +708,96 @@ impl Animation { // NB: We shall not touch the started_at field, since we don't want to // restart the animation. let old_started_at = self.started_at; + let old_delay = self.delay; let old_duration = self.duration; let old_direction = self.current_direction; let old_state = self.state.clone(); let old_iteration_state = self.iteration_state.clone(); *self = other.clone(); - - self.started_at = old_started_at; self.current_direction = old_direction; - // Don't update the iteration count, just the iteration limit. - // TODO: see how changing the limit affects rendering in other browsers. - // We might need to keep the iteration count even when it's infinite. - match (&mut self.iteration_state, old_iteration_state) { - ( - &mut KeyframesIterationState::Finite(ref mut iters, _), - KeyframesIterationState::Finite(old_iters, _), - ) => *iters = old_iters, - _ => {}, - } + if self.delay != old_delay { + // `started_at` incorporates the delay, so changing the delay necessarily changes `started_at`. + // Note: `started_at` may actually be in the future. + self.started_at = old_started_at + (self.delay - old_delay); + + match old_state { + Paused(old_progress) => { + let mut progress = old_progress + (old_delay - self.delay) / self.duration; + progress -= self.iterate_by(progress); + self.state = Paused(progress); + }, + Finished => { + if self.has_ended(now) { + self.state = Finished; + } else if self.started_at <= now { + self.state = Running; + } else { + self.state = Pending; + } + }, + _ => { + // Running or Pending — re-advance iterations from a fresh + // iteration state. + let starting_progress = (now - self.started_at) / self.duration; + match self.iteration_state { + KeyframesIterationState::Finite(ref mut current, _) => *current = 0.0, + _ => {}, + } + self.iterate_by(starting_progress); + }, + } - // Don't pause or restart animations that should remain finished. - // We call mem::replace because `has_ended(...)` looks at `Animation::state`. - let new_state = std::mem::replace(&mut self.state, Running); - if old_state == Finished && self.has_ended(now) { - self.state = Finished; + // Don't check old_state when delay changed. + if self.state == Pending && self.started_at <= now { + self.state = Running; + } } else { - self.state = new_state; - } + self.started_at = old_started_at; + + // Don't update the iteration count, just the iteration limit. + // TODO: see how changing the limit affects rendering in other browsers. + // We might need to keep the iteration count even when it's infinite. + match (&mut self.iteration_state, old_iteration_state) { + ( + &mut KeyframesIterationState::Finite(ref mut iters, _), + KeyframesIterationState::Finite(old_iters, _), + ) => *iters = old_iters, + _ => {}, + } - // If we're unpausing the animation, fake the start time so we seem to - // restore it. - // - // If the animation keeps paused, keep the old value. - // - // If we're pausing the animation, compute the progress value. - match (&mut self.state, &old_state) { - (&mut Pending, &Paused(progress)) => { - self.started_at = now - (self.duration * progress); - }, - (&mut Paused(ref mut new), &Paused(old)) => *new = old, - (&mut Paused(ref mut progress), &Running) => { - *progress = (now - old_started_at) / old_duration - }, - _ => {}, - } + // Don't pause or restart animations that should remain finished. + // We call mem::replace because `has_ended(...)` looks at `Animation::state`. + let new_state = std::mem::replace(&mut self.state, Running); + if old_state == Finished && self.has_ended(now) { + self.state = Finished; + } else { + self.state = new_state; + } - // Try to detect when we should skip straight to the running phase to - // avoid sending multiple animationstart events. - if self.state == Pending && self.started_at <= now && old_state != Pending { - self.state = Running; + // If we're unpausing the animation, fake the start time so we seem to + // restore it. + // + // If the animation keeps paused, keep the old value. + // + // If we're pausing the animation, compute the progress value. + match (&mut self.state, &old_state) { + (&mut Pending, &Paused(progress)) => { + self.started_at = now - (self.duration * progress); + }, + (&mut Paused(ref mut new), &Paused(old)) => *new = old, + (&mut Paused(ref mut progress), &Running) => { + *progress = (now - old_started_at) / old_duration + }, + _ => {}, + } + + // Try to detect when we should skip straight to the running phase to + // avoid sending multiple animationstart events. + if self.state == Pending && self.started_at <= now && old_state != Pending { + self.state = Running; + } } } @@ -756,7 +809,9 @@ impl Animation { return; } - let total_progress = match self.state { + // Raw progress ratio of the animation: can be negative (before start) or + // >1.0 (after end or during multiple iterations). + let progress = match self.state { AnimationState::Running | AnimationState::Pending | AnimationState::Finished => { (now - self.started_at) / self.duration }, @@ -764,7 +819,7 @@ impl Animation { AnimationState::Canceled => return, }; - if total_progress < 0. + if progress < 0. && self.fill_mode != AnimationFillMode::Backwards && self.fill_mode != AnimationFillMode::Both { @@ -776,9 +831,43 @@ impl Animation { { return; } - let total_progress = total_progress - .min(self.current_iteration_end_progress()) - .max(0.0); + + // If we only need to take into account one keyframe, then exit early + // in order to avoid doing more work. + let mut add_declarations_to_map = |keyframe: &ComputedKeyframe| { + for value_or_reference in keyframe.values.iter() { + let AnimationValueOrReference::AnimationValue(value) = value_or_reference else { + unreachable!("First or last keyframes define all properties"); + }; + map.insert(value.id().to_owned(), value.clone()); + } + }; + + // Handle negative progress (before animation start) with backwards/both fill mode + if progress < 0.0 { + if let Some(keyframe) = match self.current_direction { + AnimationDirection::Normal => self.computed_steps.first(), + AnimationDirection::Reverse => self.computed_steps.last(), + _ => unreachable!("Current animation direction can only be `normal` or `reverse`."), + } { + add_declarations_to_map(keyframe); + } + return; + } + + // Progress clamped to the current iteration [0.0, 1.0]. + let total_progress = progress.min(self.current_iteration_end_progress()).max(0.0); + + // At 1.0 there is nothing left to interpolate. Return end keyframe. + if total_progress == 1.0 { + let keyframe = match self.current_direction { + AnimationDirection::Normal => self.computed_steps.last().unwrap(), + AnimationDirection::Reverse => self.computed_steps.first().unwrap(), + _ => unreachable!("Current animation direction can only be `normal` or `reverse`."), + }; + add_declarations_to_map(keyframe); + return; + } // Get the indices of the previous (from) keyframe and the next (to) keyframe. let next_keyframe_index; @@ -789,7 +878,7 @@ impl Animation { next_keyframe_index = self .computed_steps .iter() - .position(|step| total_progress as f32 <= step.start_percentage); + .position(|step| total_progress < step.start_percentage); prev_keyframe_index = next_keyframe_index .and_then(|pos| if pos != 0 { Some(pos - 1) } else { None }) .unwrap_or(0); @@ -799,7 +888,7 @@ impl Animation { .computed_steps .iter() .rev() - .position(|step| total_progress as f32 <= 1. - step.start_percentage) + .position(|step| total_progress <= 1. - step.start_percentage) .map(|pos| num_steps - pos - 1); prev_keyframe_index = next_keyframe_index .and_then(|pos| { @@ -819,41 +908,27 @@ impl Animation { prev_keyframe_index, next_keyframe_index ); + let prev_keyframe = &self.computed_steps[prev_keyframe_index]; let Some(next_keyframe_index) = next_keyframe_index else { - return; - }; - - // If we only need to take into account one keyframe, then exit early - // in order to avoid doing more work. - let mut add_declarations_to_map = |keyframe_index: usize| { - for value_or_reference in &self.computed_steps[keyframe_index].values { - let AnimationValueOrReference::AnimationValue(value) = value_or_reference else { - unreachable!("First or last keyframes define all properties"); - }; - - map.insert(value.id().to_owned(), value.clone()); + unsafe { + debug_unreachable!( + "next_keyframe_index should always be Some: \ + total_progress is in [0, 1) at this point. \ + Normal direction: keyframe with start_percentage 1.0 always satisfies. \ + Reverse direction: keyframe with start_percentage 0.0 always satisfies." + ); } }; - let reversed = self.current_direction != AnimationDirection::Normal; - if total_progress <= 0.0 { - if reversed { - add_declarations_to_map(self.computed_steps.len() - 1); - } else { - add_declarations_to_map(0); - } - return; - } - if total_progress >= 1.0 { - if reversed { - add_declarations_to_map(0); - } else { - add_declarations_to_map(self.computed_steps.len() - 1); - } + // Prevent division by zero from percentage_between_keyframes. + // This can happen for reverse direction at total_progress == 0.0. + if prev_keyframe_index == next_keyframe_index { + add_declarations_to_map(&prev_keyframe); return; } // Interpolate a new value for each animating property + let reversed = self.current_direction != AnimationDirection::Normal; for property_index in 0..self.number_of_animating_properties { let Some(previous_keyframe) = self.next_relevant_keyframe_for_property_in_direction( property_index, @@ -878,11 +953,11 @@ impl Animation { }; let percentage_between_keyframes = - (next_keyframe.start_percentage - previous_keyframe.start_percentage).abs() as f64; + (next_keyframe.start_percentage - previous_keyframe.start_percentage).abs(); let duration_between_keyframes = percentage_between_keyframes * self.duration; let direction_aware_prev_keyframe_start_percentage = match self.current_direction { - AnimationDirection::Normal => previous_keyframe.start_percentage as f64, - AnimationDirection::Reverse => 1. - previous_keyframe.start_percentage as f64, + AnimationDirection::Normal => previous_keyframe.start_percentage, + AnimationDirection::Reverse => 1. - previous_keyframe.start_percentage, _ => unreachable!(), }; let progress_between_keyframes = (total_progress @@ -892,7 +967,7 @@ impl Animation { from: previous_keyframe.value.clone(), to: next_keyframe.value.clone(), timing_function: previous_keyframe.timing_function.clone(), - duration: duration_between_keyframes as f64, + duration: duration_between_keyframes, }; let value = animation.calculate_value(progress_between_keyframes); @@ -1051,9 +1126,15 @@ impl Transition { /// Update the given animation at a given point of progress. pub fn calculate_value(&self, time: f64) -> AnimationValue { - let progress = (time - self.start_time) / (self.property_animation.duration); - self.property_animation - .calculate_value(progress.clamp(0.0, 1.0)) + let progress = if time < self.start_time { + 0.0 + } else if self.property_animation.duration == 0.0 { + 1.0 + } else { + ((time - self.start_time) / self.property_animation.duration).clamp(0.0, 1.0) + }; + + self.property_animation.calculate_value(progress) } } @@ -1711,7 +1792,7 @@ pub fn maybe_start_animations( // NB: This delay may be negative, meaning that the animation may be created // in a state where we have advanced one or more iterations or even that the // animation begins in a finished state. - let delay = style.animation_delay_mod(i).seconds(); + let delay = style.animation_delay_mod(i).seconds() as f64; let iteration_count = style.animation_iteration_count_mod(i); let iteration_state = if iteration_count.0.is_infinite() { @@ -1732,8 +1813,8 @@ pub fn maybe_start_animations( }; let now = context.current_time_for_animations; - let started_at = now + delay as f64; - let mut starting_progress = (now - started_at) / duration; + let started_at = now + delay; + let starting_progress = (now - started_at) / duration; let state = match style.animation_play_state_mod(i) { AnimationPlayState::Paused => AnimationState::Paused(starting_progress), AnimationPlayState::Running => AnimationState::Pending, @@ -1770,7 +1851,7 @@ pub fn maybe_start_animations( started_at, duration, fill_mode: style.animation_fill_mode_mod(i), - delay: delay as f64, + delay, iteration_state, state, direction: animation_direction, @@ -1781,10 +1862,7 @@ pub fn maybe_start_animations( // If we started with a negative delay, make sure we iterate the animation if // the delay moves us past the first iteration. - while starting_progress > 1. && !new_animation.on_last_iteration() { - new_animation.iterate(); - starting_progress -= 1.; - } + new_animation.iterate_by(starting_progress); animation_state.dirty = true; diff --git a/style/servo/attr.rs b/style/servo/attr.rs index 2a6793b5c5..ffae7b8ade 100644 --- a/style/servo/attr.rs +++ b/style/servo/attr.rs @@ -6,24 +6,29 @@ //! //! [attr]: https://dom.spec.whatwg.org/#interface-attr +use std::str::FromStr; +use std::sync::OnceLock; + +use app_units::Au; +use euclid::num::Zero; +use num_traits::ToPrimitive; +use selectors::attr::AttrSelectorOperation; +use servo_arc::Arc; + use super::shadow_parts::ShadowParts; -use crate::color::{parsing::parse_color_keyword, AbsoluteColor}; +use crate::color::parsing::parse_color_keyword; +use crate::color::AbsoluteColor; use crate::derives::*; use crate::properties::PropertyDeclarationBlock; -use crate::shared_lock::Locked; -use crate::str::str_join; -use crate::str::{read_exponent, read_fraction, HTML_SPACE_CHARACTERS}; -use crate::str::{read_numbers, split_commas, split_html_space_chars}; +use crate::shared_lock::{Locked, SharedRwLock}; +use crate::str::{ + read_exponent, read_fraction, read_numbers, split_commas, split_html_space_chars, str_join, + HTML_SPACE_CHARACTERS, +}; use crate::values::specified::color::Color; use crate::values::specified::LengthPercentage; use crate::values::AtomString; use crate::{Atom, LocalName, Namespace, Prefix}; -use app_units::Au; -use euclid::num::Zero; -use num_traits::ToPrimitive; -use selectors::attr::AttrSelectorOperation; -use servo_arc::Arc; -use std::str::FromStr; // Duplicated from script::dom::values. const UNSIGNED_LONG_MAX: u32 = 2147483647; @@ -39,16 +44,36 @@ pub enum LengthOrPercentageOrAuto { #[derive(Clone, Debug)] #[cfg_attr(feature = "servo", derive(MallocSizeOf))] pub enum AttrValue { + // + // Variants that are stored in their serialized form. + // String(String), - TokenList(String, Vec), - UInt(String, u32), - Int(String, i32), - Double(String, f64), Atom(Atom), + + // + // Variants that support lazy serialization. + // + TokenList(OnceLock, Vec), + UInt(OnceLock, u32), + Int(OnceLock, i32), + Double(OnceLock, f64), + /// Note that this variant is only used transitively as a fast path to set + /// the property declaration block relevant to the style of an element when + /// set from the inline declaration of that element (that is, + /// `element.style`). + Declaration { + #[ignore_malloc_size_of = "Arc"] + block: Arc>, + lock: SharedRwLock, + serialization: OnceLock, + }, + + // + // Variants without a serialization implementation which must be eagerly serialized. + // LengthPercentage(String, Option), Color(String, Option), Dimension(String, LengthOrPercentageOrAuto), - /// Stores a URL, computed from the input string and a document's base URL. /// /// The URL is resolved at setting-time, so this kind of attribute value is @@ -57,28 +82,40 @@ pub enum AttrValue { String, #[ignore_malloc_size_of = "Arc"] Option>, ), - - /// Note that this variant is only used transitively as a fast path to set - /// the property declaration block relevant to the style of an element when - /// set from the inline declaration of that element (that is, - /// `element.style`). - /// - /// This can, as of this writing, only correspond to the value of the - /// `style` element, and is set from its relevant CSSInlineStyleDeclaration, - /// and then converted to a string in Element::attribute_mutated. - /// - /// Note that we don't necessarily need to do that (we could just clone the - /// declaration block), but that avoids keeping a refcounted - /// declarationblock for longer than needed. - Declaration( - String, - #[ignore_malloc_size_of = "Arc"] Arc>, - ), - /// The value of an `exportparts` attribute. ShadowParts(String, ShadowParts), } +impl From for AttrValue { + fn from(value: String) -> Self { + Self::String(value) + } +} + +impl From for AttrValue { + fn from(value: u32) -> Self { + Self::UInt(OnceLock::new(), value) + } +} + +impl From for AttrValue { + fn from(value: i32) -> Self { + Self::Int(OnceLock::new(), value) + } +} + +impl From for AttrValue { + fn from(value: f64) -> Self { + Self::Double(OnceLock::new(), value) + } +} + +impl From> for AttrValue { + fn from(value: Vec) -> Self { + Self::TokenList(OnceLock::new(), value) + } +} + /// Shared implementation to parse an integer according to /// or /// @@ -176,7 +213,7 @@ impl AttrValue { } acc }); - AttrValue::TokenList(tokens, atoms) + AttrValue::TokenList(tokens.into(), atoms) } pub fn from_comma_separated_tokenlist(tokens: String) -> AttrValue { @@ -188,13 +225,7 @@ impl AttrValue { } acc }); - AttrValue::TokenList(tokens, atoms) - } - - pub fn from_atomic_tokens(atoms: Vec) -> AttrValue { - // TODO(ajeffrey): effecient conversion of Vec to String - let tokens = String::from(str_join(&atoms, "\x20")); - AttrValue::TokenList(tokens, atoms) + AttrValue::TokenList(tokens.into(), atoms) } // https://html.spec.whatwg.org/multipage/#reflecting-content-attributes-in-idl-attributes:idl-unsigned-long @@ -205,12 +236,12 @@ impl AttrValue { } else { result }; - AttrValue::UInt(string, result) + AttrValue::UInt(string.into(), result) } pub fn from_i32(string: String, default: i32) -> AttrValue { let result = parse_integer(string.chars()).unwrap_or(default); - AttrValue::Int(string, result) + AttrValue::Int(string.into(), result) } // https://html.spec.whatwg.org/multipage/#reflecting-content-attributes-in-idl-attributes:idl-double @@ -218,9 +249,9 @@ impl AttrValue { let result = parse_double(&string).unwrap_or(default); if result.is_normal() { - AttrValue::Double(string, result) + AttrValue::Double(string.into(), result) } else { - AttrValue::Double(string, default) + AttrValue::Double(string.into(), default) } } @@ -229,9 +260,9 @@ impl AttrValue { let result = parse_integer(string.chars()).unwrap_or(default); if result < 0 { - AttrValue::Int(string, default) + AttrValue::Int(string.into(), default) } else { - AttrValue::Int(string, result) + AttrValue::Int(string.into(), result) } } @@ -243,12 +274,11 @@ impl AttrValue { } else { result }; - AttrValue::UInt(string, result) + AttrValue::UInt(string.into(), result) } pub fn from_atomic(string: String) -> AttrValue { - let value = Atom::from(string); - AttrValue::Atom(value) + AttrValue::Atom(string.into()) } pub fn from_resolved_url(base: &Arc<::url::Url>, url: String) -> AttrValue { @@ -276,6 +306,17 @@ impl AttrValue { AttrValue::ShadowParts(string, shadow_parts) } + pub fn from_declaration( + block: Arc>, + lock: SharedRwLock, + ) -> AttrValue { + AttrValue::Declaration { + block, + lock, + serialization: OnceLock::new(), + } + } + /// Assumes the `AttrValue` is a `TokenList` and returns its tokens /// /// ## Panics @@ -423,19 +464,39 @@ impl ::std::ops::Deref for AttrValue { type Target = str; fn deref(&self) -> &str { - match *self { - AttrValue::String(ref value) - | AttrValue::TokenList(ref value, _) - | AttrValue::UInt(ref value, _) - | AttrValue::Double(ref value, _) - | AttrValue::LengthPercentage(ref value, _) - | AttrValue::Color(ref value, _) - | AttrValue::Int(ref value, _) - | AttrValue::ResolvedUrl(ref value, _) - | AttrValue::Declaration(ref value, _) - | AttrValue::ShadowParts(ref value, _) - | AttrValue::Dimension(ref value, _) => &value, - AttrValue::Atom(ref value) => &value, + match self { + AttrValue::String(value) => &value, + AttrValue::Atom(atom) => &atom, + AttrValue::TokenList(serialization, tokens) => { + serialization.get_or_init(|| { + // TODO(ajeffrey): Efficient conversion of Vec to String + str_join(tokens, "\x20") + }) + }, + AttrValue::UInt(serialization, value) => { + serialization.get_or_init(|| value.to_string()) + }, + AttrValue::Double(serialization, value) => { + serialization.get_or_init(|| value.to_string()) + }, + AttrValue::Int(serialization, value) => serialization.get_or_init(|| value.to_string()), + AttrValue::Declaration { + block, + lock, + serialization, + } => serialization.get_or_init(|| { + let mut serialization = String::new(); + block + .read_with(&lock.read()) + .to_css(&mut serialization) + .expect("Should always be able to produce a valid serialization"); + serialization + }), + AttrValue::LengthPercentage(serialization, _) + | AttrValue::Color(serialization, _) + | AttrValue::Dimension(serialization, _) + | AttrValue::ResolvedUrl(serialization, _) + | AttrValue::ShadowParts(serialization, _) => &serialization, } } } diff --git a/style/servo/media_features.rs b/style/servo/media_features.rs index a6a56039df..14a3e520c9 100644 --- a/style/servo/media_features.rs +++ b/style/servo/media_features.rs @@ -6,8 +6,8 @@ use crate::derives::*; use crate::queries::feature::{AllowsRanges, Evaluator, FeatureFlags, QueryFeatureDescription}; -use crate::queries::values::PrefersColorScheme; -use crate::values::computed::{CSSPixelLength, Context, Resolution}; +use crate::queries::values::{Orientation, PrefersColorScheme}; +use crate::values::computed::{CSSPixelLength, Context, Ratio, Resolution}; use std::fmt::Debug; /// https://drafts.csswg.org/mediaqueries-4/#width @@ -15,6 +15,30 @@ fn eval_width(context: &Context) -> CSSPixelLength { CSSPixelLength::new(context.device().au_viewport_size().width.to_f32_px()) } +/// https://drafts.csswg.org/mediaqueries-4/#height +fn eval_height(context: &Context) -> CSSPixelLength { + CSSPixelLength::new(context.device().au_viewport_size().height.to_f32_px()) +} + +/// https://drafts.csswg.org/mediaqueries-4/#device-width +fn eval_device_width(context: &Context) -> CSSPixelLength { + let device = context.device(); + let scaled = device.device_size() / device.device_pixel_ratio(); + CSSPixelLength::new(scaled.width) +} + +/// https://drafts.csswg.org/mediaqueries-4/#device-height +fn eval_device_height(context: &Context) -> CSSPixelLength { + let device = context.device(); + let scaled = device.device_size() / device.device_pixel_ratio(); + CSSPixelLength::new(scaled.height) +} + +/// https://drafts.csswg.org/mediaqueries-4/#orientation +fn eval_orientation(context: &Context, value: Option) -> bool { + Orientation::eval(context.device().au_viewport_size(), value) +} + #[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] #[repr(u8)] enum Scan { @@ -46,12 +70,155 @@ fn eval_prefers_color_scheme(context: &Context, query_value: Option Self { + PointerCapabilities::COARSE + } + #[cfg(not(any(target_os = "ios", target_os = "android", target_env = "ohos")))] + fn default() -> Self { + PointerCapabilities::FINE | PointerCapabilities::HOVER + } +} + +#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] +#[repr(u8)] +enum Pointer { + None, + Coarse, + Fine, +} + +fn eval_pointer_capabilities( + query_value: Option, + pointer_capabilities: PointerCapabilities, +) -> bool { + match query_value { + None => !pointer_capabilities.is_empty(), + Some(Pointer::None) => pointer_capabilities.is_empty(), + Some(Pointer::Coarse) => pointer_capabilities.intersects(PointerCapabilities::COARSE), + Some(Pointer::Fine) => pointer_capabilities.intersects(PointerCapabilities::FINE), + } +} + +/// https://drafts.csswg.org/mediaqueries-4/#pointer +fn eval_pointer(context: &Context, query_value: Option) -> bool { + eval_pointer_capabilities(query_value, context.device().primary_pointer_capabilities()) +} + +/// https://drafts.csswg.org/mediaqueries-4/#descdef-media-any-pointer +fn eval_any_pointer(context: &Context, query_value: Option) -> bool { + eval_pointer_capabilities(query_value, context.device().all_pointer_capabilities()) +} + +#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] +#[repr(u8)] +enum Hover { + None, + Hover, +} + +fn eval_hover_capabilities( + query_value: Option, + pointer_capabilities: PointerCapabilities, +) -> bool { + let can_hover = pointer_capabilities.intersects(PointerCapabilities::HOVER); + match query_value { + Some(Hover::None) => !can_hover, + Some(Hover::Hover) => can_hover, + None => return can_hover, + } +} + +/// https://drafts.csswg.org/mediaqueries-4/#hover +fn eval_hover(context: &Context, query_value: Option) -> bool { + eval_hover_capabilities(query_value, context.device().primary_pointer_capabilities()) +} + +/// https://drafts.csswg.org/mediaqueries-4/#descdef-media-any-hover +fn eval_any_hover(context: &Context, query_value: Option) -> bool { + eval_hover_capabilities(query_value, context.device().all_pointer_capabilities()) +} + +/// +fn eval_aspect_ratio(context: &Context) -> Ratio { + let size = context.device().au_viewport_size(); + Ratio::new(size.width.0 as f32, size.height.0 as f32) +} + /// A list with all the media features that Servo supports. -pub static MEDIA_FEATURES: [QueryFeatureDescription; 6] = [ +pub static MEDIA_FEATURES: [QueryFeatureDescription; 15] = [ feature!( atom!("width"), AllowsRanges::Yes, Evaluator::Length(eval_width), + FeatureFlags::VIEWPORT_DEPENDENT, + ), + feature!( + atom!("height"), + AllowsRanges::Yes, + Evaluator::Length(eval_height), + FeatureFlags::VIEWPORT_DEPENDENT, + ), + feature!( + atom!("orientation"), + AllowsRanges::No, + keyword_evaluator!(eval_orientation, Orientation), + FeatureFlags::VIEWPORT_DEPENDENT, + ), + feature!( + atom!("pointer"), + AllowsRanges::No, + keyword_evaluator!(eval_pointer, Pointer), + FeatureFlags::empty(), + ), + feature!( + atom!("any-pointer"), + AllowsRanges::No, + keyword_evaluator!(eval_any_pointer, Pointer), + FeatureFlags::empty(), + ), + feature!( + atom!("hover"), + AllowsRanges::No, + keyword_evaluator!(eval_hover, Hover), + FeatureFlags::empty(), + ), + feature!( + atom!("any-hover"), + AllowsRanges::No, + keyword_evaluator!(eval_any_hover, Hover), + FeatureFlags::empty(), + ), + feature!( + atom!("aspect-ratio"), + AllowsRanges::Yes, + Evaluator::NumberRatio(eval_aspect_ratio), + FeatureFlags::VIEWPORT_DEPENDENT, + ), + feature!( + atom!("device-width"), + AllowsRanges::Yes, + Evaluator::Length(eval_device_width), + FeatureFlags::empty(), + ), + feature!( + atom!("device-height"), + AllowsRanges::Yes, + Evaluator::Length(eval_device_height), FeatureFlags::empty(), ), feature!( diff --git a/style/shared_lock.rs b/style/shared_lock.rs index 397509a025..fe2e602dee 100644 --- a/style/shared_lock.rs +++ b/style/shared_lock.rs @@ -5,46 +5,33 @@ //! Different objects protected by the same lock use crate::stylesheets::Origin; -#[cfg(feature = "gecko")] use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut}; -#[cfg(feature = "servo")] -use parking_lot::RwLock; use servo_arc::Arc; use std::cell::UnsafeCell; use std::fmt; -#[cfg(feature = "servo")] -use std::mem; -#[cfg(feature = "gecko")] use std::ptr; use style_traits::{CssString, CssStringWriter}; use to_shmem::{SharedMemoryBuilder, ToShmem}; /// A shared read/write lock that can protect multiple objects. /// -/// In Gecko builds, we don't need the blocking behavior, just the safety. As -/// such we implement this with an AtomicRefCell instead in Gecko builds, -/// which is ~2x as fast, and panics (rather than deadlocking) when things go -/// wrong (which is much easier to debug on CI). -/// -/// Servo needs the blocking behavior for its unsynchronized animation setup, -/// but that may not be web-compatible and may need to be changed (at which -/// point Servo could use AtomicRefCell too). +/// We don't need the blocking behavior, just the safety. As such we implement +/// this with an AtomicRefCell, which is ~2x as fast as an RwLock, and panics +/// (rather than deadlocking) when things go wrong (which is much easier to +/// debug on CI). /// /// Gecko also needs the ability to have "read only" SharedRwLocks, which are /// used for objects stored in (read only) shared memory. Attempting to acquire /// write access to objects protected by a read only SharedRwLock will panic. #[derive(Clone)] -#[cfg_attr(feature = "servo", derive(crate::derives::MallocSizeOf))] pub struct SharedRwLock { - #[cfg(feature = "servo")] - #[cfg_attr(feature = "servo", ignore_malloc_size_of = "Arc")] - arc: Arc>, - - #[cfg(feature = "gecko")] cell: Option>>, } -#[cfg(feature = "gecko")] +#[cfg(feature = "servo")] +malloc_size_of::malloc_size_of_is_0!(SharedRwLock); + +#[cfg_attr(feature = "servo", derive(crate::derives::MallocSizeOf))] struct SomethingZeroSizedButTyped; impl fmt::Debug for SharedRwLock { @@ -54,32 +41,14 @@ impl fmt::Debug for SharedRwLock { } impl SharedRwLock { - /// Create a new shared lock (servo). - #[cfg(feature = "servo")] - pub fn new() -> Self { - SharedRwLock { - arc: Arc::new(RwLock::new(())), - } - } - - /// Create a new shared lock (gecko). - #[cfg(feature = "gecko")] + /// Create a new shared lock. pub fn new() -> Self { SharedRwLock { cell: Some(Arc::new(AtomicRefCell::new(SomethingZeroSizedButTyped))), } } - /// Create a new global shared lock (servo). - #[cfg(feature = "servo")] - pub fn new_leaked() -> Self { - SharedRwLock { - arc: Arc::new_leaked(RwLock::new(())), - } - } - - /// Create a new global shared lock (gecko). - #[cfg(feature = "gecko")] + /// Create a new global shared lock. pub fn new_leaked() -> Self { SharedRwLock { cell: Some(Arc::new_leaked(AtomicRefCell::new( @@ -88,13 +57,11 @@ impl SharedRwLock { } } - /// Create a new read-only shared lock (gecko). - #[cfg(feature = "gecko")] + /// Create a new read-only shared lock. pub fn read_only() -> Self { SharedRwLock { cell: None } } - #[cfg(feature = "gecko")] #[inline] fn ptr(&self) -> *const SomethingZeroSizedButTyped { self.cell @@ -111,51 +78,22 @@ impl SharedRwLock { } } - /// Obtain the lock for reading (servo). - #[cfg(feature = "servo")] - pub fn read(&self) -> SharedRwLockReadGuard<'_> { - mem::forget(self.arc.read()); - SharedRwLockReadGuard(self) - } - - /// Obtain the lock for reading (gecko). - #[cfg(feature = "gecko")] + /// Obtain the lock for reading. pub fn read(&self) -> SharedRwLockReadGuard<'_> { SharedRwLockReadGuard(self.cell.as_ref().map(|cell| cell.borrow())) } - /// Obtain the lock for writing (servo). - #[cfg(feature = "servo")] - pub fn write(&self) -> SharedRwLockWriteGuard<'_> { - mem::forget(self.arc.write()); - SharedRwLockWriteGuard(self) - } - - /// Obtain the lock for writing (gecko). - #[cfg(feature = "gecko")] + /// Obtain the lock for writing. pub fn write(&self) -> SharedRwLockWriteGuard<'_> { SharedRwLockWriteGuard(self.cell.as_ref().unwrap().borrow_mut()) } } -/// Proof that a shared lock was obtained for reading (servo). -#[cfg(feature = "servo")] -pub struct SharedRwLockReadGuard<'a>(&'a SharedRwLock); -/// Proof that a shared lock was obtained for reading (gecko). -#[cfg(feature = "gecko")] +/// Proof that a shared lock was obtained for reading. pub struct SharedRwLockReadGuard<'a>(Option>); -#[cfg(feature = "servo")] -impl<'a> Drop for SharedRwLockReadGuard<'a> { - fn drop(&mut self) { - // Unsafe: self.lock is private to this module, only ever set after `read()`, - // and never copied or cloned (see `compile_time_assert` below). - unsafe { self.0.arc.force_unlock_read() } - } -} impl<'a> SharedRwLockReadGuard<'a> { #[inline] - #[cfg(feature = "gecko")] fn ptr(&self) -> *const SomethingZeroSizedButTyped { self.0 .as_ref() @@ -164,20 +102,8 @@ impl<'a> SharedRwLockReadGuard<'a> { } } -/// Proof that a shared lock was obtained for writing (servo). -#[cfg(feature = "servo")] -pub struct SharedRwLockWriteGuard<'a>(&'a SharedRwLock); -/// Proof that a shared lock was obtained for writing (gecko). -#[cfg(feature = "gecko")] +/// Proof that a shared lock was obtained for writing. pub struct SharedRwLockWriteGuard<'a>(AtomicRefMut<'a, SomethingZeroSizedButTyped>); -#[cfg(feature = "servo")] -impl<'a> Drop for SharedRwLockWriteGuard<'a> { - fn drop(&mut self) { - // Unsafe: self.lock is private to this module, only ever set after `write()`, - // and never copied or cloned (see `compile_time_assert` below). - unsafe { self.0.arc.force_unlock_write() } - } -} /// Data protect by a shared lock. pub struct Locked { @@ -198,33 +124,23 @@ impl fmt::Debug for Locked { } impl Locked { - #[cfg(feature = "gecko")] #[inline] fn is_read_only_lock(&self) -> bool { self.shared_lock.cell.is_none() } - #[cfg(feature = "servo")] - fn same_lock_as(&self, lock: &SharedRwLock) -> bool { - Arc::ptr_eq(&self.shared_lock.arc, &lock.arc) - } - - #[cfg(feature = "gecko")] fn same_lock_as(&self, ptr: *const SomethingZeroSizedButTyped) -> bool { ptr::eq(self.shared_lock.ptr(), ptr) } /// Access the data for reading. pub fn read_with<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> &'a T { - #[cfg(feature = "gecko")] assert!( self.is_read_only_lock() || self.same_lock_as(guard.ptr()), "Locked::read_with called with a guard from an unrelated SharedRwLock: {:?} vs. {:?}", self.shared_lock.ptr(), guard.ptr(), ); - #[cfg(not(feature = "gecko"))] - assert!(self.same_lock_as(&guard.0)); let ptr = self.data.get(); @@ -245,13 +161,10 @@ impl Locked { /// Access the data for writing. pub fn write_with<'a>(&'a self, guard: &'a mut SharedRwLockWriteGuard) -> &'a mut T { - #[cfg(feature = "gecko")] assert!( !self.is_read_only_lock() && self.same_lock_as(&*guard.0), "Locked::write_with called with a guard from a read only or unrelated SharedRwLock" ); - #[cfg(not(feature = "gecko"))] - assert!(self.same_lock_as(&guard.0)); let ptr = self.data.get(); @@ -267,7 +180,6 @@ impl Locked { } } -#[cfg(feature = "gecko")] impl ToShmem for Locked { fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> to_shmem::Result { use std::mem::ManuallyDrop; @@ -282,13 +194,6 @@ impl ToShmem for Locked { } } -#[cfg(feature = "servo")] -impl ToShmem for Locked { - fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> to_shmem::Result { - panic!("ToShmem not supported in Servo currently") - } -} - #[allow(dead_code)] mod compile_time_assert { use super::{SharedRwLockReadGuard, SharedRwLockWriteGuard}; diff --git a/style/stylist.rs b/style/stylist.rs index 10f035367a..30bd8d1f0f 100644 --- a/style/stylist.rs +++ b/style/stylist.rs @@ -13,7 +13,9 @@ use crate::custom_properties::ComputedCustomProperties; use crate::custom_properties::{parse_name, SpecifiedValue}; use crate::derives::*; use crate::device::Device; -use crate::dom::{TElement, TShadowRoot}; +use crate::dom::TElement; +#[cfg(feature = "gecko")] +use crate::dom::TShadowRoot; #[cfg(feature = "gecko")] use crate::gecko_bindings::structs::{ServoStyleSetSizes, StyleRuleInclusion}; use crate::invalidation::element::invalidation_map::{ diff --git a/style/values/generics/calc.rs b/style/values/generics/calc.rs index cebc2b4a32..78809de2a2 100644 --- a/style/values/generics/calc.rs +++ b/style/values/generics/calc.rs @@ -574,9 +574,7 @@ impl CalcNode { pub fn unit(&self) -> Result { Ok(match self { CalcNode::Leaf(l) => l.unit(), - CalcNode::Negate(child) | CalcNode::Invert(child) | CalcNode::Abs(child) => { - child.unit()? - }, + CalcNode::Negate(child) | CalcNode::Abs(child) => child.unit()?, CalcNode::Sum(children) => { let mut unit = children.first().unwrap().unit()?; for child in children.iter().skip(1) { @@ -692,7 +690,7 @@ impl CalcNode { } CalcUnits::empty() }, - CalcNode::Sqrt(ref c) | CalcNode::Exp(ref c) => { + CalcNode::Invert(ref c) | CalcNode::Sqrt(ref c) | CalcNode::Exp(ref c) => { let child_unit = c.unit()?; if !child_unit.is_empty() { return Err(()); diff --git a/style/values/generics/font.rs b/style/values/generics/font.rs index 3ad75de64e..832b0c7375 100644 --- a/style/values/generics/font.rs +++ b/style/values/generics/font.rs @@ -36,6 +36,7 @@ pub trait TaggedFontValue { ToResolvedValue, ToShmem, )] +#[cfg_attr(feature = "servo", derive(Deserialize, Hash, Serialize))] pub struct FeatureTagValue { /// A four-character tag, packed into a u32 (one byte per character). pub tag: FontTag, @@ -115,7 +116,7 @@ impl TaggedFontValue for VariationValue { ToShmem, ToTyped, )] -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] +#[cfg_attr(feature = "servo", derive(Deserialize, Hash, Serialize))] #[css(comma)] #[typed(todo_derive_fields)] pub struct FontSettings(#[css(if_empty = "normal", iterable)] pub Box<[T]>); @@ -159,7 +160,6 @@ impl Parse for FontSettings { #[derive( Clone, Copy, - Debug, Eq, MallocSizeOf, PartialEq, @@ -169,9 +169,23 @@ impl Parse for FontSettings { ToResolvedValue, ToShmem, )] -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] +#[cfg_attr(feature = "servo", derive(Deserialize, Hash, Serialize))] pub struct FontTag(pub u32); +impl fmt::Debug for FontTag { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let tag_bytes = self.0.to_be_bytes(); + + let mut tuple = f.debug_tuple("FontTag"); + if let Ok(utf8_tag) = str::from_utf8(&tag_bytes) { + tuple.field(&utf8_tag); + } else { + tuple.field(&tag_bytes); + }; + tuple.finish() + } +} + impl ToCss for FontTag { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where diff --git a/style/values/specified/box.rs b/style/values/specified/box.rs index a43d8c2a22..d95aa98ef0 100644 --- a/style/values/specified/box.rs +++ b/style/values/specified/box.rs @@ -557,7 +557,7 @@ impl ToTyped for Display { } let keyword = self.to_css_cssstring(); - debug_assert!(!keyword.as_ref().contains(&b' ')); + debug_assert!(!AsRef::<[u8]>::as_ref(&keyword).contains(&b' ')); dest.push(TypedValue::Keyword(KeywordValue(keyword))); return Ok(()); @@ -746,18 +746,23 @@ pub enum AlignmentBaseline { /// Use the text-under baseline. TextBottom, /// Use the alphabetic baseline. + #[cfg(feature = "gecko")] Alphabetic, /// Use the ideographic-under baseline. + #[cfg(feature = "gecko")] Ideographic, /// In general, use the x-middle baselines; except under text-orientation: upright /// (where the alphabetic and x-height baselines are essentially meaningless) use /// the central baseline instead. Middle, /// Use the central baseline. + #[cfg(feature = "gecko")] Central, /// Use the math baseline. + #[cfg(feature = "gecko")] Mathematical, /// Use the hanging baseline. + #[cfg(feature = "gecko")] Hanging, /// Use the text-over baseline. TextTop, diff --git a/style/values/specified/font.rs b/style/values/specified/font.rs index 986fdd25bc..a0d22ebe2f 100644 --- a/style/values/specified/font.rs +++ b/style/values/specified/font.rs @@ -1318,6 +1318,7 @@ impl Parse for FontVariantAlternates { ToShmem, ToTyped, )] +#[cfg_attr(feature = "servo", derive(Deserialize, Hash, Serialize))] #[css(bitflags( single = "normal", mixed = "jis78,jis83,jis90,jis04,simplified,traditional,full-width,proportional-width,ruby", @@ -1387,6 +1388,7 @@ impl FontVariantEastAsian { ToShmem, ToTyped, )] +#[cfg_attr(feature = "servo", derive(Deserialize, Hash, Serialize))] #[css(bitflags( single = "normal,none", mixed = "common-ligatures,no-common-ligatures,discretionary-ligatures,no-discretionary-ligatures,historical-ligatures,no-historical-ligatures,contextual,no-contextual", @@ -1458,6 +1460,7 @@ impl FontVariantLigatures { mixed = "lining-nums,oldstyle-nums,proportional-nums,tabular-nums,diagonal-fractions,stacked-fractions,ordinal,slashed-zero", validate_mixed = "Self::validate_mixed_flags", ))] +#[cfg_attr(feature = "servo", derive(Serialize, Deserialize, Hash))] #[repr(C)] pub struct FontVariantNumeric(u8); bitflags! { diff --git a/style_derive/Cargo.toml b/style_derive/Cargo.toml index 8d8a85f62d..b1c84ad073 100644 --- a/style_derive/Cargo.toml +++ b/style_derive/Cargo.toml @@ -1,11 +1,12 @@ [package] -name = "style_derive" -version = "0.0.1" +name = "stylo_derive" +version.workspace = true authors = ["The Servo Project Developers"] license = "MPL-2.0" repository = "https://github.com/servo/stylo" edition = "2021" description = "Derive crate for Stylo CSS engine" +readme = "../README.md" [lib] path = "lib.rs" diff --git a/style_traits/Cargo.toml b/style_traits/Cargo.toml index 73ddfa72d3..18d065433e 100644 --- a/style_traits/Cargo.toml +++ b/style_traits/Cargo.toml @@ -1,33 +1,34 @@ [package] -name = "style_traits" -version = "0.0.1" +name = "stylo_traits" +version.workspace = true authors = ["The Servo Project Developers"] license = "MPL-2.0" repository = "https://github.com/servo/stylo" edition = "2021" description = "Types used by the Stylo CSS engine" +readme = "../README.md" [lib] name = "style_traits" path = "lib.rs" [features] +default = ["servo"] servo = ["stylo_atoms", "cssparser/serde", "url", "euclid/serde"] -gecko = ["nsstring"] +gecko = [] [dependencies] app_units = "0.7" bitflags = "2" cssparser = "0.37" euclid = "0.22" -malloc_size_of = { path = "../malloc_size_of" } -malloc_size_of_derive = { path = "../../../xpcom/rust/malloc_size_of_derive" } -nsstring = {path = "../../../xpcom/rust/nsstring/", optional = true} -selectors = { path = "../selectors" } +malloc_size_of = { workspace = true} +malloc_size_of_derive = "0.1" +selectors = { workspace = true} serde = "1.0" -servo_arc = { path = "../servo_arc" } -stylo_atoms = { path = "../atoms", optional = true } +servo_arc = { workspace = true} +stylo_atoms = { workspace = true, optional = true } thin-vec = "0.2" -to_shmem = { path = "../to_shmem" } -to_shmem_derive = { path = "../to_shmem_derive" } +to_shmem = { workspace = true} +to_shmem_derive = { workspace = true} url = { version = "2.5", optional = true } diff --git a/stylo_atoms/Cargo.toml b/stylo_atoms/Cargo.toml new file mode 100644 index 0000000000..e39f28f9b1 --- /dev/null +++ b/stylo_atoms/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "stylo_atoms" +version.workspace = true +authors = ["The Servo Project Developers"] +documentation = "https://docs.rs/stylo_atoms/" +description = "Interned string type for the Servo and Stylo projects" +repository = "https://github.com/servo/stylo" +license = "MPL-2.0" +edition = "2018" +build = "build.rs" +readme = "../README.md" + +[lib] +path = "lib.rs" + +[dependencies] +string_cache = "0.9" + +[build-dependencies] +string_cache_codegen = "0.6.1" diff --git a/stylo_atoms/build.rs b/stylo_atoms/build.rs new file mode 100644 index 0000000000..b5f6775724 --- /dev/null +++ b/stylo_atoms/build.rs @@ -0,0 +1,31 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use std::env; +use std::fs::File; +use std::io::{BufRead, BufReader}; +use std::path::Path; + +fn main() { + let static_atoms = + Path::new(&env::var_os("CARGO_MANIFEST_DIR").unwrap()).join("static_atoms.txt"); + let static_atoms = BufReader::new(File::open(&static_atoms).unwrap()); + let mut atom_type = string_cache_codegen::AtomType::new("Atom", "atom!"); + + macro_rules! predefined { + ($($name: expr,)+) => { + { + $( + atom_type.atom($name); + )+ + } + } + } + include!("./predefined_counter_styles.rs"); + + atom_type + .atoms(static_atoms.lines().map(Result::unwrap)) + .write_to_file(&Path::new(&env::var_os("OUT_DIR").unwrap()).join("atom.rs")) + .unwrap(); +} diff --git a/stylo_atoms/lib.rs b/stylo_atoms/lib.rs new file mode 100644 index 0000000000..03560a40c0 --- /dev/null +++ b/stylo_atoms/lib.rs @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +include!(concat!(env!("OUT_DIR"), "/atom.rs")); diff --git a/stylo_atoms/predefined_counter_styles.rs b/stylo_atoms/predefined_counter_styles.rs new file mode 100644 index 0000000000..f376981e32 --- /dev/null +++ b/stylo_atoms/predefined_counter_styles.rs @@ -0,0 +1,66 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + + // THIS FILE IS DUPLICATED FROM style/counter_style/predefined.rs. + // TO UPDATE IT: + // - Run `python style/counter_style/updated_predefined.py` + // - Re-copy style/counter_style/predefined.rs to this location + +predefined! { + "decimal", + "decimal-leading-zero", + "arabic-indic", + "armenian", + "upper-armenian", + "lower-armenian", + "bengali", + "cambodian", + "khmer", + "cjk-decimal", + "devanagari", + "georgian", + "gujarati", + "gurmukhi", + "hebrew", + "kannada", + "lao", + "malayalam", + "mongolian", + "myanmar", + "oriya", + "persian", + "lower-roman", + "upper-roman", + "tamil", + "telugu", + "thai", + "tibetan", + "lower-alpha", + "lower-latin", + "upper-alpha", + "upper-latin", + "cjk-earthly-branch", + "cjk-heavenly-stem", + "lower-greek", + "hiragana", + "hiragana-iroha", + "katakana", + "katakana-iroha", + "disc", + "circle", + "square", + "disclosure-open", + "disclosure-closed", + "japanese-informal", + "japanese-formal", + "korean-hangul-formal", + "korean-hanja-informal", + "korean-hanja-formal", + "simp-chinese-informal", + "simp-chinese-formal", + "trad-chinese-informal", + "trad-chinese-formal", + "cjk-ideographic", + "ethiopic-numeric", +} diff --git a/stylo_atoms/static_atoms.txt b/stylo_atoms/static_atoms.txt new file mode 100644 index 0000000000..af047ae96e --- /dev/null +++ b/stylo_atoms/static_atoms.txt @@ -0,0 +1,195 @@ +-moz-content-preferred-color-scheme +-moz-device-pixel-ratio +-moz-fixed-pos-containing-block +-moz-gtk-csd-close-button-position +-moz-gtk-csd-maximize-button-position +-moz-gtk-csd-menu-radius +-moz-gtk-csd-minimize-button-position +-moz-gtk-csd-titlebar-button-spacing +-moz-gtk-csd-titlebar-radius +-moz-gtk-csd-tooltip-radius +-moz-gtk-menu-radius +-moz-mac-titlebar-height +-moz-overlay-scrollbar-fade-duration +DOMContentLoaded +abort +activate +addtrack +all +animationcancel +animationend +animationiteration +animationstart +any-hover +any-pointer +aspect-ratio +beforeinput +beforetoggle +beforeunload +block-size +button +canplay +canplaythrough +center +change +characteristicvaluechanged +checkbox +cancel +click +close +closing +color +command +complete +compositionend +compositionstart +compositionupdate +controllerchange +cursive +dark +datachannel +date +datetime-local +dir +device-height +device-pixel-ratio +device-width +durationchange +email +emptied +end +ended +error +fantasy +fetch +file +fill +fill-opacity +formdata +fullscreenchange +fullscreenerror +gattserverdisconnected +hairline +hashchange +height +hidden +hover +icecandidate +iceconnectionstatechange +icegatheringstatechange +image +inline-size +input +inputsourceschange +invalid +keydown +keypress +kind +left +light +ltr +load +loadeddata +loadedmetadata +loadend +loadstart +match-element +message +message +messageerror +monospace +month +mousedown +mousemove +mouseover +mouseup +negotiationneeded +none +normal +number +onchange +open +orientation +pagehide +pageshow +password +pause +play +playing +pointer +popstate +postershown +prefers-color-scheme +print +progress +radio +range +ratechange +readystatechange +referrer +reftest-wait +rejectionhandled +removetrack +reset +resize +resolution +resourcetimingbufferfull +right +rtl +sans-serif +safe-area-inset-top +safe-area-inset-bottom +safe-area-inset-left +safe-area-inset-right +scan +screen +scroll-position +scrollbar-inline-size +search +seeked +seeking +select +selectend +selectionchange +selectstart +serif +sessionavailable +show +signalingstatechange +slotchange +squeeze +squeezeend +squeezestart +srclang +statechange +stroke +stroke-opacity +storage +submit +suspend +system-ui +tel +text +time +timeupdate +toggle +track +transitioncancel +transitionend +transitionrun +transitionstart +uncapturederror +unhandledrejection +unload +url +visibilitychange +volumechange +waiting +webglcontextcreationerror +webkitAnimationEnd +webkitAnimationIteration +webkitAnimationStart +webkitTransitionEnd +webkitTransitionRun +week +width diff --git a/stylo_dom/Cargo.toml b/stylo_dom/Cargo.toml new file mode 100644 index 0000000000..5d25f57c50 --- /dev/null +++ b/stylo_dom/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "stylo_dom" +version.workspace = true +authors = ["The Servo Project Developers"] +documentation = "https://docs.rs/stylo_dom/" +description = "DOM state types for Stylo" +repository = "https://github.com/servo/stylo" +keywords = ["css", "style"] +license = "MPL-2.0" +edition = "2021" +readme = "../README.md" + +[lib] +path = "lib.rs" + +[dependencies] +bitflags = "2" +malloc_size_of = { workspace = true } diff --git a/stylo_dom/lib.rs b/stylo_dom/lib.rs new file mode 100644 index 0000000000..5a968cd52d --- /dev/null +++ b/stylo_dom/lib.rs @@ -0,0 +1,196 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +//! DOM types to be shared between Rust and C++. + +use bitflags::bitflags; +use malloc_size_of::malloc_size_of_is_0; + +pub const HEADING_LEVEL_OFFSET: usize = 57; + +bitflags! { + /// Event-based element states. + #[repr(C)] + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub struct ElementState: u64 { + /// The mouse is down on this element. + /// + /// FIXME(#7333): set/unset this when appropriate + const ACTIVE = 1 << 0; + /// This element has focus. + /// + const FOCUS = 1 << 1; + /// The mouse is hovering over this element. + /// + const HOVER = 1 << 2; + /// Content is enabled (and can be disabled). + /// + const ENABLED = 1 << 3; + /// Content is disabled. + /// + const DISABLED = 1 << 4; + /// Content is checked. + /// + const CHECKED = 1 << 5; + /// + const INDETERMINATE = 1 << 6; + /// + const PLACEHOLDER_SHOWN = 1 << 7; + /// + const URLTARGET = 1 << 8; + /// + const FULLSCREEN = 1 << 9; + /// + const VALID = 1 << 10; + /// + const INVALID = 1 << 11; + /// + const USER_VALID = 1 << 12; + /// + const USER_INVALID = 1 << 13; + /// All the validity bits at once. + const VALIDITY_STATES = Self::VALID.bits() | Self::INVALID.bits() | Self::USER_VALID.bits() | Self::USER_INVALID.bits(); + /// Non-standard: https://developer.mozilla.org/en-US/docs/Web/CSS/:-moz-broken + const BROKEN = 1 << 14; + /// + const REQUIRED = 1 << 15; + /// + /// We use an underscore to workaround a silly windows.h define. + const OPTIONAL_ = 1 << 16; + /// + const DEFINED = 1 << 17; + /// + const VISITED = 1 << 18; + /// + const UNVISITED = 1 << 19; + /// + const VISITED_OR_UNVISITED = Self::VISITED.bits() | Self::UNVISITED.bits(); + /// Non-standard: https://developer.mozilla.org/en-US/docs/Web/CSS/:-moz-drag-over + const DRAGOVER = 1 << 20; + /// + const INRANGE = 1 << 21; + /// + const OUTOFRANGE = 1 << 22; + /// + const READONLY = 1 << 23; + /// + const READWRITE = 1 << 24; + /// + const DEFAULT = 1 << 25; + /// Non-standard & undocumented. + const OPTIMUM = 1 << 26; + /// Non-standard & undocumented. + const SUB_OPTIMUM = 1 << 27; + /// Non-standard & undocumented. + const SUB_SUB_OPTIMUM = 1 << 28; + /// All the above bits in one place. + const METER_OPTIMUM_STATES = Self::OPTIMUM.bits() | Self::SUB_OPTIMUM.bits() | Self::SUB_SUB_OPTIMUM.bits(); + /// Non-standard & undocumented. + const INCREMENT_SCRIPT_LEVEL = 1 << 29; + /// + const FOCUSRING = 1 << 30; + /// + const FOCUS_WITHIN = 1u64 << 31; + /// :dir matching; the states are used for dynamic change detection. + /// State that elements that match :dir(ltr) are in. + const LTR = 1u64 << 32; + /// State that elements that match :dir(rtl) are in. + const RTL = 1u64 << 33; + /// State that HTML elements that have a "dir" attr are in. + const HAS_DIR_ATTR = 1u64 << 34; + /// State that HTML elements with dir="ltr" (or something + /// case-insensitively equal to "ltr") are in. + const HAS_DIR_ATTR_LTR = 1u64 << 35; + /// State that HTML elements with dir="rtl" (or something + /// case-insensitively equal to "rtl") are in. + const HAS_DIR_ATTR_RTL = 1u64 << 36; + /// State that HTML elements without a valid-valued "dir" attr or + /// any HTML elements (including ) with dir="auto" (or something + /// case-insensitively equal to "auto") are in. + const HAS_DIR_ATTR_LIKE_AUTO = 1u64 << 37; + /// Non-standard & undocumented. + const AUTOFILL = 1u64 << 38; + /// Non-standard & undocumented. + const AUTOFILL_PREVIEW = 1u64 << 39; + /// State for modal elements: + /// + const MODAL = 1u64 << 40; + /// + const INERT = 1u64 << 41; + /// State for the topmost modal element in top layer + const TOPMOST_MODAL = 1u64 << 42; + /// Initially used for the devtools highlighter, but now somehow only + /// used for the devtools accessibility inspector. + const DEVTOOLS_HIGHLIGHTED = 1u64 << 43; + /// Used for the devtools style editor. Probably should go away. + const STYLEEDITOR_TRANSITIONING = 1u64 << 44; + /// For :-moz-value-empty (to show widgets like the reveal password + /// button or the clear button). + const VALUE_EMPTY = 1u64 << 45; + /// For :-moz-revealed. + const REVEALED = 1u64 << 46; + /// https://html.spec.whatwg.org/#selector-popover-open + /// Match element's popover visibility state of showing + const POPOVER_OPEN = 1u64 << 47; + /// https://drafts.csswg.org/css-scoping-1/#the-has-slotted-pseudo + /// Match whether a slot element has assigned nodes + const HAS_SLOTTED = 1u64 << 48; + /// https://drafts.csswg.org/selectors-4/#open-state + /// Match whether an openable element is currently open + const OPEN = 1u64 << 49; + /// For :active-view-transition. + /// + const ACTIVE_VIEW_TRANSITION = 1u64 << 50; + /// For :-moz-suppress-for-print-selection. + const SUPPRESS_FOR_PRINT_SELECTION = 1u64 << 51; + /// https://html.spec.whatwg.org/multipage/semantics-other.html#selector-paused + const PAUSED = 1u64 << 52; + /// https://html.spec.whatwg.org/multipage/semantics-other.html#selector-seeking + const SEEKING = 1u64 << 53; + /// https://html.spec.whatwg.org/multipage/semantics-other.html#selector-buffering + const BUFFERING = 1u64 << 54; + /// https://html.spec.whatwg.org/multipage/semantics-other.html#selector-stalled + const STALLED = 1u64 << 55; + /// https://html.spec.whatwg.org/multipage/semantics-other.html#selector-muted + const MUTED = 1u64 << 56; + /// This element is fullscreen and was requested to have keyboard lock. + const FULLSCREEN_KEYBOARD_LOCK = 1u64 << 57; + /// https://drafts.csswg.org/selectors-5/#headings + /// These 4 bits are used to pack the elements heading level into the element state + /// Heading levels can be from 1-9 so 4 bits allows us to express the full range. + const HEADING_LEVEL_BITS = 0b1111u64 << HEADING_LEVEL_OFFSET; + /// https://w3c.github.io/picture-in-picture/#css-pseudo-class + const PICTURE_IN_PICTURE = 1u64 << 61; + + /// Some convenience unions. + const DIR_STATES = Self::LTR.bits() | Self::RTL.bits(); + + const DIR_ATTR_STATES = Self::HAS_DIR_ATTR.bits() | + Self::HAS_DIR_ATTR_LTR.bits() | + Self::HAS_DIR_ATTR_RTL.bits() | + Self::HAS_DIR_ATTR_LIKE_AUTO.bits(); + + const DISABLED_STATES = Self::DISABLED.bits() | Self::ENABLED.bits(); + + const REQUIRED_STATES = Self::REQUIRED.bits() | Self::OPTIONAL_.bits(); + } +} + +bitflags! { + /// Event-based document states. + #[repr(C)] + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub struct DocumentState: u64 { + /// Window activation status + const WINDOW_INACTIVE = 1 << 0; + /// RTL locale: specific to the XUL localedir attribute + const RTL_LOCALE = 1 << 1; + /// LTR locale: specific to the XUL localedir attribute + const LTR_LOCALE = 1 << 2; + + const ALL_LOCALEDIR_BITS = Self::LTR_LOCALE.bits() | Self::RTL_LOCALE.bits(); + } +} + +malloc_size_of_is_0!(ElementState, DocumentState); diff --git a/stylo_static_prefs/Cargo.toml b/stylo_static_prefs/Cargo.toml new file mode 100644 index 0000000000..3c6ef6804c --- /dev/null +++ b/stylo_static_prefs/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "stylo_static_prefs" +version.workspace = true +authors = ["The Servo Project Developers"] +documentation = "https://docs.rs/stylo_static_prefs/" +description = "Configuration for Stylo" +repository = "https://github.com/servo/stylo" +keywords = ["css", "style"] +license = "MPL-2.0" +edition = "2021" +readme = "../README.md" +build = "build.rs" + +[build-dependencies] +toml = { version = "1.1.2" } diff --git a/stylo_static_prefs/build.rs b/stylo_static_prefs/build.rs new file mode 100644 index 0000000000..b1b0dfcc8e --- /dev/null +++ b/stylo_static_prefs/build.rs @@ -0,0 +1,131 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use std::{env, fs, path::Path}; + +use toml::{Table, Value}; + +struct BooleanPreference { + name: String, + default: bool, +} + +struct IntegerPreference { + name: String, + default: i64, +} + +fn main() -> Result<(), std::io::Error> { + println!("cargo::rerun-if-changed=preferences.toml"); + generate_code(parse_preferences()?) +} + +fn parse_preferences() -> Result<(Vec, Vec), std::io::Error> { + let preferences_text = fs::read_to_string("preferences.toml")?; + let toml = preferences_text + .parse::() + .expect("Could not parse preferences.toml"); + + let mut boolean_preferences = Vec::new(); + let mut integer_preferences = Vec::new(); + for (name, default) in toml { + match default { + Value::Boolean(default) => { + boolean_preferences.push(BooleanPreference { name, default }); + }, + Value::Integer(default) => { + integer_preferences.push(IntegerPreference { name, default }); + }, + _ => panic!("Found unknown preference type: {default:?}"), + } + } + + Ok((boolean_preferences, integer_preferences)) +} + +fn generate_code( + preferences: (Vec, Vec), +) -> Result<(), std::io::Error> { + let mut output = Vec::new(); + + let (boolean_preferences, integer_preferences) = preferences; + let boolean_count = boolean_preferences.len(); + output.push(format!( + "pub static BOOLS: [AtomicBool; {boolean_count}] = [" + )); + for preference in &boolean_preferences { + output.push(format!(" AtomicBool::new({}),", preference.default)); + } + output.push(format!("];")); + + let integer_count = integer_preferences.len(); + output.push(format!( + "pub static INTEGERS: [AtomicI32; {integer_count}] = [" + )); + for preference in &integer_preferences { + output.push(format!(" AtomicI32::new({}),", preference.default)); + } + output.push(format!("];")); + + output.push(format!( + "/// Returns the value of a preference exposed to the style crate. If the embedder" + )); + output.push(format!( + "/// has not set a value for it, this returns the default value of the preference" + )); + output.push(format!("#[macro_export]")); + output.push(format!("macro_rules! pref {{")); + + for (index, preference) in boolean_preferences.iter().enumerate() { + output.push(format!(" ({:?}) => {{", preference.name)); + output.push(format!( + " $crate::BOOLS[{index:?}].load(std::sync::atomic::Ordering::Relaxed)", + )); + output.push(format!(" }};")); + } + + for (index, preference) in integer_preferences.iter().enumerate() { + output.push(format!(" ({:?}) => {{", preference.name)); + output.push(format!( + " $crate::INTEGERS[{index:?}].load(std::sync::atomic::Ordering::Relaxed)", + )); + output.push(format!(" }};")); + } + output.push(format!("}}")); + + output.push(format!("#[macro_export]")); + output.push(format!("macro_rules! set_pref {{")); + + for (index, preference) in boolean_preferences.iter().enumerate() { + output.push(format!( + " ({:?}, $($value:expr)+) => {{", + preference.name + )); + + output.push(format!("let value = $($value)+;")); + output.push(format!( + " $crate::BOOLS[{index:?}].store(value, std::sync::atomic::Ordering::Relaxed)", + )); + output.push(format!(" }};")); + } + + for (index, preference) in integer_preferences.iter().enumerate() { + output.push(format!( + " ({:?}, $($value:expr)+) => {{", + preference.name + )); + output.push(format!("let value = $($value)+;")); + output.push(format!( + " $crate::INTEGERS[{index:?}].store(value, std::sync::atomic::Ordering::Relaxed)", + )); + output.push(format!(" }};")); + } + output.push(format!("}}")); + + let output = output.join("\n"); + println!("{output}"); + + let out_dir = env::var_os("OUT_DIR").expect("Should always have OUT_DIR set"); + fs::write(Path::new(&out_dir).join("generated.rs"), output) +} diff --git a/stylo_static_prefs/preferences.toml b/stylo_static_prefs/preferences.toml new file mode 100644 index 0000000000..e95170d306 --- /dev/null +++ b/stylo_static_prefs/preferences.toml @@ -0,0 +1,44 @@ +"dom.select.customizable_select.enabled" = false +"dom.viewTransitions.cross-document.enabled" = false +"layout.columns.enabled" = false +"layout.container-queries.enabled" = false +"layout.css.anchor-positioning.enabled" = false +"layout.css.appearance-base.enabled" = false +"layout.css.at-scope.enabled" = false +"layout.css.attr.enabled" = false +"layout.css.basic-shape-shape.enabled" = false +"layout.css.color-mix-multi-color.enabled" = true +"layout.css.content.alt-text.enabled" = false +"layout.css.contrast-color.enabled" = true +"layout.css.custom-media.enabled" = false +"layout.css.fit-content-function.enabled" = true +"layout.css.font-palette.enabled" = false +"layout.css.font-tech.enabled" = false +"layout.css.font-variations.enabled" = true +"layout.css.gradient-color-interpolation-method.enabled" = true +"layout.css.light-dark.images.enabled" = false +"layout.css.margin-rules.enabled" = false +"layout.css.marker.restricted" = true +"layout.css.motion-path-url.enabled" = false +"layout.css.outline-offset.snapping" = 1 +"layout.css.properties-and-values.enabled" = true +"layout.css.relative-color-syntax.enabled" = true +"layout.css.revert-rule.enabled" = true +"layout.css.scroll-driven-animations.enabled" = false +"layout.css.scroll-state.enabled" = false +"layout.css.starting-style-at-rules.enabled" = false +"layout.css.stretch-size-keyword.enabled" = true +"layout.css.style-queries.enabled" = false +"layout.css.stylo-local-work-queue.in-main-thread" = 32 +"layout.css.stylo-local-work-queue.in-worker" = 0 +"layout.css.stylo-work-unit-size" = 16 +"layout.css.system-ui.enabled" = true +"layout.css.webkit-fill-available.all-size-properties.enabled" = true +"layout.css.webkit-fill-available.enabled" = true +"layout.grid.enabled" = false +# Negative means auto, 0 disables the thread-pool (main-thread styling), +# other numbers override as specified. +"layout.threads" = -1 +"layout.unimplemented" = false +"layout.variable_fonts.enabled" = false +"layout.writing-mode.enabled" = false diff --git a/stylo_static_prefs/src/lib.rs b/stylo_static_prefs/src/lib.rs new file mode 100644 index 0000000000..7b176c9749 --- /dev/null +++ b/stylo_static_prefs/src/lib.rs @@ -0,0 +1,18 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use std::sync::atomic::{AtomicBool, AtomicI32}; + +include!(concat!(env!("OUT_DIR"), "/generated.rs")); + +#[test] +fn test_basic_preferences() { + assert!(!pref!("layout.unimplemented")); + set_pref!("layout.unimplemented", true); + assert!(pref!("layout.unimplemented")); + + assert_eq!(pref!("layout.threads"), -1); + set_pref!("layout.threads", 42); + assert_eq!(pref!("layout.threads"), 42); +} diff --git a/sync.sh b/sync.sh new file mode 100755 index 0000000000..68c8689c9c --- /dev/null +++ b/sync.sh @@ -0,0 +1,43 @@ +#!/bin/sh +# Usage: sync.sh +set -eu + +root=$(pwd) +mkdir -p "$1" +cd -- "$1" +filtered=$(pwd) +mkdir -p "$root/_cache" +cd "$root/_cache" +export PATH="$PWD:$PATH" + +step() { + if [ "${TERM-}" != '' ]; then + tput setaf 12 + fi + >&2 printf '* %s\n' "$*" + if [ "${TERM-}" != '' ]; then + tput sgr0 + fi +} + +step Downloading git-filter-repo if needed +if ! git filter-repo --version 2> /dev/null; then + curl -O https://raw.githubusercontent.com/newren/git-filter-repo/v2.38.0/git-filter-repo + chmod +x git-filter-repo + + git filter-repo --version +fi + +step Cloning upstream if needed +if ! [ -e upstream ]; then + git clone --bare --single-branch --branch main --progress https://github.com/mozilla-firefox/firefox.git upstream +fi + +step Updating upstream +branch=$(git -C upstream rev-parse --abbrev-ref HEAD) +git -C upstream fetch origin $branch:$branch + +step Filtering upstream +# Cloning and filtering is much faster than git filter-repo --source --target. +git clone --bare upstream -- "$filtered" +git -C "$filtered" filter-repo --force --paths-from-file "$root/style.paths" diff --git a/to_shmem/Cargo.toml b/to_shmem/Cargo.toml index fdc04b8970..21c89aa2d8 100644 --- a/to_shmem/Cargo.toml +++ b/to_shmem/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "to_shmem" -version = "0.1.0" +version = "0.5.0" authors = ["The Servo Project Developers"] license = "MPL-2.0" repository = "https://github.com/servo/stylo" @@ -24,8 +24,8 @@ thin-vec = ["dep:thin-vec"] [dependencies] cssparser = { version = "0.37", optional = true } -servo_arc = { version = "0.4.0", path = "../servo_arc", optional = true } +servo_arc = { workspace = true, optional = true } smallbitvec = { version = "2.3.0", optional = true } smallvec = { version = "1.13", optional = true } -string_cache = { version = "0.8", optional = true } +string_cache = { version = "0.9", optional = true } thin-vec = { version = "0.2.1", optional = true }