diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000000..fa6c67068c --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,75 @@ +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@v4 + - 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@v4 + - 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@v4 + - 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@v4 + - 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 + 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..c8593195da --- /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@v4 + 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..adf329ffa6 --- /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@v3 + 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..f217b8c5da --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,38 @@ +[workspace] +resolver = "2" +members = [ + "stylo_atoms", + "stylo_dom", + "malloc_size_of", + "rustfmt.toml", + "selectors", + "servo_arc", + "style", + "style_derive", + "stylo_config", + "stylo_static_prefs", + "style_traits", + "to_shmem", + "to_shmem_derive", +] +default-members = ["style"] + +[workspace.package] +version = "0.8.0" + +[workspace.dependencies] +# in-repo dependencies (separately versioned) +servo_arc = { version = "0.4.2", path = "./servo_arc" } +selectors = { version = "0.32.0", path = "./selectors" } +to_shmem = { version = "0.2.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.8.0", path = "./malloc_size_of", package = "stylo_malloc_size_of", features = ["servo"] } +static_prefs = { version = "0.8.0", path = "./stylo_static_prefs", package = "stylo_static_prefs" } +style_config = { version = "0.8.0", path = "./stylo_config", package = "stylo_config"} +stylo_atoms = { version = "0.8.0", path = "./stylo_atoms" } +dom = { version = "0.8.0", path = "./stylo_dom", package = "stylo_dom" } +style_traits = { version = "0.8.0", path = "./style_traits", features = ["servo"], package = "stylo_traits"} +style_derive = { version = "0.8.0", path = "./style_derive", package = "stylo_derive"} +stylo = { version = "0.8.0", path = "./style" } diff --git a/README.md b/README.md new file mode 100644 index 0000000000..527db6b2f4 --- /dev/null +++ b/README.md @@ -0,0 +1,107 @@ +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_config | [![Crates.io](https://img.shields.io/crates/v/stylo_config.svg)](https://crates.io/crates/stylo_config) | Configuration for Stylo. Can be used to set runtime preferences (enabling/disabling properties, etc) | +| stylo_static_prefs | [![Crates.io](https://img.shields.io/crates/v/stylo_static_prefs.svg)](https://crates.io/crates/stylo_static_prefs) | Static configuration for Stylo. Config be overridden by patching in a replacement crate. | +| 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_atoms = { path = "../stylo/stylo_atoms" } +stylo = { path = "../stylo/style" } +stylo_config = { path = "../stylo/stylo_config" } +stylo_dom = { path = "../stylo/stylo_dom" } +stylo_malloc_size_of = { path = "../stylo/malloc_size_of" } +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_config +- 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 a12c652662..2b96c6ce13 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" @@ -15,12 +15,12 @@ servo = ["string_cache"] [dependencies] app_units = "0.7" -cssparser = "0.34" +cssparser = "0.35" 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" +smallvec = "1.13" string_cache = { version = "0.8", optional = true } -thin-vec = { version = "0.2.1" } +thin-vec = { version = "0.2.13" } void = "1.0.2" diff --git a/selectors/Cargo.toml b/selectors/Cargo.toml index 3802bfcf2a..a74fdea9f3 100644 --- a/selectors/Cargo.toml +++ b/selectors/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "selectors" -version = "0.26.0" +version = "0.32.0" authors = ["The Servo Project Developers"] documentation = "https://docs.rs/selectors/" description = "CSS Selectors matching for Rust" @@ -21,16 +21,16 @@ to_shmem = ["dep:to_shmem", "dep:to_shmem_derive"] [dependencies] bitflags = "2" -cssparser = "0.34" +cssparser = "0.35" derive_more = { version = "2", features = ["add", "add_assign"] } rustc-hash = "2.1.1" log = "0.4" phf = "0.11" 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/servo_arc/Cargo.toml b/servo_arc/Cargo.toml index 8b0976b75d..2cb0b98363 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.2" 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 6dd01aba80..5aea59fa4b 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,15 @@ 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", + "style_config", "string_cache", "style_traits/servo", "url", @@ -48,6 +50,7 @@ servo = [ ] gecko_debug = [] gecko_refcount_logging = [] +nsstring = [] [dependencies] app_units = "0.7.8" @@ -55,24 +58,24 @@ arrayvec = "0.7" atomic_refcell = "0.1" bitflags = "2" byteorder = "1.0" -cssparser = "0.34" +cssparser = "0.35" 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" lazy_static = "1" 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 } +malloc_size_of = { workspace = true } +malloc_size_of_derive = "0.1" +web_atoms = { version = "0.1.3", optional = true } matches = "0.1" -nsstring = {path = "../../../xpcom/rust/nsstring/", optional = true} +mime = { version = "0.3.13", optional = true } num_cpus = {version = "1.1.0"} num-integer = "0.1" num-traits = "0.2" @@ -81,24 +84,23 @@ 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" } +static_prefs = { workspace = true} string_cache = { version = "0.8", optional = true } -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"] } +style_config = { workspace = true, optional = true } +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/author_styles.rs b/style/author_styles.rs index a0223dcecc..6a7ba2875b 100644 --- a/style/author_styles.rs +++ b/style/author_styles.rs @@ -6,7 +6,6 @@ //! ones used for ShadowRoot. use crate::dom::TElement; -use crate::invalidation::media_queries::ToMediaListKey; use crate::shared_lock::SharedRwLockReadGuard; use crate::stylesheet_set::AuthorStylesheetSet; use crate::stylesheets::StylesheetInDocument; @@ -56,7 +55,6 @@ where pub fn flush(&mut self, stylist: &mut Stylist, guard: &SharedRwLockReadGuard) where E: TElement, - S: ToMediaListKey, { let flusher = self .stylesheets diff --git a/style/build.rs b/style/build.rs index 4b27edbe2c..2f12477133 100644 --- a/style/build.rs +++ b/style/build.rs @@ -21,7 +21,7 @@ mod build_gecko { lazy_static! { pub static ref PYTHON: String = env::var("PYTHON3").ok().unwrap_or_else(|| { let candidates = if cfg!(windows) { - ["python3.exe"] + ["python.exe"] } else { ["python3"] }; @@ -58,6 +58,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/gecko/data.rs b/style/gecko/data.rs index b54d02fd56..440cc2df70 100644 --- a/style/gecko/data.rs +++ b/style/gecko/data.rs @@ -9,7 +9,6 @@ use crate::gecko_bindings::bindings; use crate::gecko_bindings::structs::{ self, ServoStyleSetSizes, StyleSheet as DomStyleSheet, StyleSheetInfo, }; -use crate::invalidation::media_queries::{MediaListKey, ToMediaListKey}; use crate::media_queries::{Device, MediaList}; use crate::properties::ComputedValues; use crate::selector_parser::SnapshotMap; @@ -41,22 +40,15 @@ unsafe impl Sync for GeckoStyleSheet {} impl fmt::Debug for GeckoStyleSheet { fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - let contents = self.contents(); + let contents = self.raw_contents(); formatter .debug_struct("GeckoStyleSheet") .field("origin", &contents.origin) - .field("url_data", &*contents.url_data.read()) + .field("url_data", &contents.url_data) .finish() } } -impl ToMediaListKey for crate::gecko::data::GeckoStyleSheet { - fn to_media_list_key(&self) -> MediaListKey { - use std::mem; - unsafe { MediaListKey::from_raw(mem::transmute(self.0)) } - } -} - impl GeckoStyleSheet { /// Create a `GeckoStyleSheet` from a raw `DomStyleSheet` pointer. #[inline] @@ -89,6 +81,11 @@ impl GeckoStyleSheet { fn inner(&self) -> &StyleSheetInfo { unsafe { &*(self.raw().mInner as *const StyleSheetInfo) } } + + fn raw_contents(&self) -> &StylesheetContents { + debug_assert!(!self.inner().mContents.mRawPtr.is_null()); + unsafe { &*self.inner().mContents.mRawPtr } + } } impl Drop for GeckoStyleSheet { @@ -125,9 +122,8 @@ impl StylesheetInDocument for GeckoStyleSheet { } #[inline] - fn contents(&self) -> &StylesheetContents { - debug_assert!(!self.inner().mContents.mRawPtr.is_null()); - unsafe { &*self.inner().mContents.mRawPtr } + fn contents<'a>(&'a self, _: &'a SharedRwLockReadGuard) -> &'a StylesheetContents { + self.raw_contents() } fn implicit_scope_root(&self) -> Option { diff --git a/style/invalidation/stylesheets.rs b/style/invalidation/stylesheets.rs index 260ed6335a..c5c0b6c78b 100644 --- a/style/invalidation/stylesheets.rs +++ b/style/invalidation/stylesheets.rs @@ -146,7 +146,7 @@ impl StylesheetInvalidationSet { } let quirks_mode = device.quirks_mode(); - for rule in stylesheet.effective_rules(device, guard) { + for rule in stylesheet.contents(guard).effective_rules(device, guard) { self.collect_invalidations_for_rule( rule, guard, diff --git a/style/lib.rs b/style/lib.rs index aa1ece2e64..f8184f5a62 100644 --- a/style/lib.rs +++ b/style/lib.rs @@ -17,6 +17,8 @@ //! //! [recalc_style_at]: traversal/fn.recalc_style_at.html //! +//! A list of supported style properties can be found as [docs::supported_properties] +//! //! Major dependencies are the [cssparser][cssparser] and [selectors][selectors] //! crates. //! @@ -48,7 +50,6 @@ extern crate malloc_size_of; #[macro_use] extern crate malloc_size_of_derive; #[cfg(feature = "servo")] -#[macro_use] extern crate web_atoms; #[allow(unused_extern_crates)] #[macro_use] @@ -135,6 +136,16 @@ pub mod use_counters; #[allow(non_camel_case_types)] pub mod values; +#[cfg(all(doc, feature = "servo"))] +/// Documentation +pub mod docs { + /// The CSS properties supported by the style system. + /// Generated from the `properties.mako.rs` template by `build.rs` + pub mod supported_properties { + #![doc = include_str!(concat!(env!("OUT_DIR"), "/css-properties.html"))] + } +} + #[cfg(feature = "gecko")] pub use crate::gecko_string_cache as string_cache; #[cfg(feature = "gecko")] diff --git a/style/matching.rs b/style/matching.rs index ac22bb6092..e1bf7d277f 100644 --- a/style/matching.rs +++ b/style/matching.rs @@ -691,7 +691,7 @@ trait PrivateMatchMethods: TElement { new_values: &Arc, pseudo_element: Option, ) -> bool { - use crate::animation::{self, AnimationSetKey, AnimationState}; + use crate::animation::{AnimationSetKey, AnimationState}; // We need to call this before accessing the `ElementAnimationSet` from the // map because this call will do a RwLock::read(). diff --git a/style/properties/build.py b/style/properties/build.py index 3d0aed3b21..cfb6cb92a4 100644 --- a/style/properties/build.py +++ b/style/properties/build.py @@ -8,6 +8,8 @@ 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")) # For importing markupsafe sys.path.insert(0, BASE) # For importing `data.py` from mako import exceptions @@ -75,6 +77,7 @@ def main(): write(doc_servo, "css-properties.html", as_html) write(doc_servo, "css-properties.json", as_json) write(OUT_DIR, "css-properties.json", as_json) + write(OUT_DIR, "css-properties.html", as_html) elif output == "geckolib": if len(sys.argv) < 4: abort(usage) diff --git a/style/properties/cascade.rs b/style/properties/cascade.rs index 3a2bf14ff3..0997f79b52 100644 --- a/style/properties/cascade.rs +++ b/style/properties/cascade.rs @@ -1074,7 +1074,6 @@ impl<'b> Cascade<'b> { builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_WORD_SPACING); } - #[cfg(feature = "gecko")] if self .author_specified .contains(LonghandId::FontSynthesisWeight) diff --git a/style/properties/longhands/font.mako.rs b/style/properties/longhands/font.mako.rs index 95f0e6d609..6d8c5ec805 100644 --- a/style/properties/longhands/font.mako.rs +++ b/style/properties/longhands/font.mako.rs @@ -87,7 +87,7 @@ ${helpers.predefined_type( ${helpers.predefined_type( "font-synthesis-weight", "FontSynthesis", - engines="gecko", + engines="gecko servo", initial_value="computed::FontSynthesis::Auto", initial_specified_value="specified::FontSynthesis::Auto", gecko_ffi_name="mFont.synthesisWeight", diff --git a/style/properties/longhands/inherited_text.mako.rs b/style/properties/longhands/inherited_text.mako.rs index 433b43c21f..a5cca06425 100644 --- a/style/properties/longhands/inherited_text.mako.rs +++ b/style/properties/longhands/inherited_text.mako.rs @@ -176,6 +176,7 @@ ${helpers.predefined_type( ignored_when_colors_disabled=True, simple_vector_bindings=True, spec="https://drafts.csswg.org/css-text-decor-3/#text-shadow-property", + servo_restyle_damage="repaint", affects="overflow", )} diff --git a/style/properties/longhands/text.mako.rs b/style/properties/longhands/text.mako.rs index b0a24e7df2..4dc4789842 100644 --- a/style/properties/longhands/text.mako.rs +++ b/style/properties/longhands/text.mako.rs @@ -9,7 +9,6 @@ ${helpers.predefined_type( "TextOverflow", "computed::TextOverflow::get_initial_value()", engines="gecko servo", - servo_pref="layout.unimplemented", animation_type="discrete", boxed=True, spec="https://drafts.csswg.org/css-ui/#propdef-text-overflow", @@ -36,7 +35,7 @@ ${helpers.predefined_type( initial_specified_value="specified::TextDecorationLine::none()", animation_type="discrete", spec="https://drafts.csswg.org/css-text-decor/#propdef-text-decoration-line", - servo_restyle_damage="rebuild_box", + servo_restyle_damage="recalculate_overflow", affects="overflow", )} @@ -47,6 +46,7 @@ ${helpers.single_keyword( gecko_enum_prefix="StyleTextDecorationStyle", animation_type="discrete", spec="https://drafts.csswg.org/css-text-decor/#propdef-text-decoration-style", + servo_restyle_damage="recalculate_overflow", affects="overflow", )} @@ -70,6 +70,7 @@ ${helpers.predefined_type( initial_specified_value="specified::Color::currentcolor()", ignored_when_colors_disabled=True, spec="https://drafts.csswg.org/css-text-decor/#propdef-text-decoration-color", + servo_restyle_damage="recalculate_overflow", affects="paint", )} diff --git a/style/properties/properties.mako.rs b/style/properties/properties.mako.rs index 9741cdc80c..b40e41dead 100644 --- a/style/properties/properties.mako.rs +++ b/style/properties/properties.mako.rs @@ -2041,6 +2041,7 @@ impl ComputedValues { PropertyDeclarationId::Longhand(id) => { let context = resolved::Context { style: self, + for_property: id.into(), }; let mut s = String::new(); self.computed_or_resolved_value( @@ -3003,11 +3004,22 @@ const_assert!(std::mem::size_of::( % for effect_name in ["repaint", "recalculate_overflow", "rebuild_stacking_context", "rebuild_box"]: pub(crate) fn restyle_damage_${effect_name} (old: &ComputedValues, new: &ComputedValues) -> bool { % for style_struct in data.active_style_structs(): - % for longhand in style_struct.longhands: + <% longhands_affected = [effect_name in longhand.servo_restyle_damage.split() for longhand in style_struct.longhands if not longhand.logical] %> + % if any(longhands_affected): + let old_${style_struct.name_lower} = old.get_${style_struct.name_lower}(); + let new_${style_struct.name_lower} = new.get_${style_struct.name_lower}(); + if !std::ptr::eq(old_${style_struct.name_lower}, new_${style_struct.name_lower}) { + if + % for longhand in style_struct.longhands: % if effect_name in longhand.servo_restyle_damage.split() and not longhand.logical: - old.get_${style_struct.name_lower}().${longhand.ident} != new.get_${style_struct.name_lower}().${longhand.ident} || + old_${style_struct.name_lower}.${longhand.ident} != new_${style_struct.name_lower}.${longhand.ident} || % endif - % endfor + % endfor + false { + return true; + } + } + % endif % endfor false } 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/servo/animation.rs b/style/servo/animation.rs index 8a91faec93..152f162adf 100644 --- a/style/servo/animation.rs +++ b/style/servo/animation.rs @@ -1495,7 +1495,7 @@ pub fn maybe_start_animations( continue; } - let keyframe_animation = match context.stylist.get_animation(name, element) { + let keyframe_animation = match context.stylist.lookup_keyframes(name, element) { Some(animation) => animation, None => continue, }; diff --git a/style/servo/encoding_support.rs b/style/servo/encoding_support.rs index b07fccfd30..c0429f4cd0 100644 --- a/style/servo/encoding_support.rs +++ b/style/servo/encoding_support.rs @@ -7,7 +7,7 @@ use crate::context::QuirksMode; use crate::error_reporting::ParseErrorReporter; use crate::media_queries::MediaList; -use crate::shared_lock::SharedRwLock; +use crate::shared_lock::{Locked, SharedRwLock}; use crate::stylesheets::{AllowImportRules, Origin, Stylesheet, StylesheetLoader, UrlExtraData}; use cssparser::{stylesheet_encoding, EncodingSupport}; use servo_arc::Arc; @@ -59,7 +59,7 @@ impl Stylesheet { protocol_encoding_label: Option<&str>, environment_encoding: Option<&'static encoding_rs::Encoding>, origin: Origin, - media: MediaList, + media: Arc>, shared_lock: SharedRwLock, stylesheet_loader: Option<&dyn StylesheetLoader>, error_reporter: Option<&dyn ParseErrorReporter>, @@ -70,7 +70,7 @@ impl Stylesheet { &string, url_data, origin, - Arc::new(shared_lock.wrap(media)), + media, shared_lock, stylesheet_loader, error_reporter, @@ -78,26 +78,4 @@ impl Stylesheet { AllowImportRules::Yes, ) } - - /// Updates an empty stylesheet with a set of bytes that reached over the - /// network. - pub fn update_from_bytes( - existing: &Stylesheet, - bytes: &[u8], - protocol_encoding_label: Option<&str>, - environment_encoding: Option<&'static encoding_rs::Encoding>, - url_data: UrlExtraData, - stylesheet_loader: Option<&dyn StylesheetLoader>, - error_reporter: Option<&dyn ParseErrorReporter>, - ) { - let string = decode_stylesheet_bytes(bytes, protocol_encoding_label, environment_encoding); - Self::update_from_str( - existing, - &string, - url_data, - stylesheet_loader, - error_reporter, - AllowImportRules::Yes, - ) - } } diff --git a/style/servo/restyle_damage.rs b/style/servo/restyle_damage.rs index d7e6857b1d..5933d5eaa4 100644 --- a/style/servo/restyle_damage.rs +++ b/style/servo/restyle_damage.rs @@ -59,7 +59,12 @@ impl ServoRestyleDamage { old: &ComputedValues, new: &ComputedValues, ) -> StyleDifference { - let mut damage = compute_damage(old, new); + let mut damage = if std::ptr::eq(old, new) { + ServoRestyleDamage::empty() + } else { + compute_damage(old, new) + }; + if damage.is_empty() { return StyleDifference { damage, diff --git a/style/servo/selector_parser.rs b/style/servo/selector_parser.rs index add90511d7..88b54c598a 100644 --- a/style/servo/selector_parser.rs +++ b/style/servo/selector_parser.rs @@ -410,7 +410,12 @@ impl ToCss for NonTSPseudoClass { Self::UserValid => ":user-valid", Self::Valid => ":valid", Self::Visited => ":visited", - Self::Lang(_) | Self::CustomState(_) => unreachable!(), + NonTSPseudoClass::CustomState(ref state) => { + dest.write_str(":state(")?; + state.0.to_css(dest)?; + return dest.write_char(')'); + }, + Self::Lang(_) => unreachable!(), }) } } @@ -597,10 +602,13 @@ impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> { parser: &mut CssParser<'i, 't>, after_part: bool, ) -> Result> { - use self::NonTSPseudoClass::*; let pseudo_class = match_ignore_ascii_case! { &name, "lang" if !after_part => { - Lang(parser.expect_ident_or_string()?.as_ref().into()) + NonTSPseudoClass::Lang(parser.expect_ident_or_string()?.as_ref().into()) + }, + "state" => { + let result = AtomIdent::from(parser.expect_ident()?.as_ref()); + NonTSPseudoClass::CustomState(CustomState(result)) }, _ => return Err(parser.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))), }; diff --git a/style/stylesheet_set.rs b/style/stylesheet_set.rs index e9c481a749..018c804bfa 100644 --- a/style/stylesheet_set.rs +++ b/style/stylesheet_set.rs @@ -9,10 +9,8 @@ use crate::invalidation::stylesheets::{RuleChangeKind, StylesheetInvalidationSet use crate::media_queries::Device; use crate::selector_parser::SnapshotMap; use crate::shared_lock::SharedRwLockReadGuard; -use crate::stylesheets::{ - CssRule, Origin, OriginSet, OriginSetIterator, PerOrigin, StylesheetInDocument, -}; -use std::{mem, slice}; +use crate::stylesheets::{CssRule, Origin, OriginSet, PerOrigin, StylesheetInDocument}; +use std::mem; /// Entry for a StylesheetSet. #[derive(MallocSizeOf)] @@ -39,75 +37,6 @@ where } } -/// A iterator over the stylesheets of a list of entries in the StylesheetSet. -pub struct StylesheetCollectionIterator<'a, S>(slice::Iter<'a, StylesheetSetEntry>) -where - S: StylesheetInDocument + PartialEq + 'static; - -impl<'a, S> Clone for StylesheetCollectionIterator<'a, S> -where - S: StylesheetInDocument + PartialEq + 'static, -{ - fn clone(&self) -> Self { - StylesheetCollectionIterator(self.0.clone()) - } -} - -impl<'a, S> Iterator for StylesheetCollectionIterator<'a, S> -where - S: StylesheetInDocument + PartialEq + 'static, -{ - type Item = &'a S; - - fn next(&mut self) -> Option { - self.0.next().map(|entry| &entry.sheet) - } - - fn size_hint(&self) -> (usize, Option) { - self.0.size_hint() - } -} - -/// An iterator over the flattened view of the stylesheet collections. -#[derive(Clone)] -pub struct StylesheetIterator<'a, S> -where - S: StylesheetInDocument + PartialEq + 'static, -{ - origins: OriginSetIterator, - collections: &'a PerOrigin>, - current: Option<(Origin, StylesheetCollectionIterator<'a, S>)>, -} - -impl<'a, S> Iterator for StylesheetIterator<'a, S> -where - S: StylesheetInDocument + PartialEq + 'static, -{ - type Item = (&'a S, Origin); - - fn next(&mut self) -> Option { - loop { - if self.current.is_none() { - let next_origin = self.origins.next()?; - - self.current = Some(( - next_origin, - self.collections.borrow_for_origin(&next_origin).iter(), - )); - } - - { - let (origin, ref mut iter) = *self.current.as_mut().unwrap(); - if let Some(s) = iter.next() { - return Some((s, origin)); - } - } - - self.current = None; - } - } -} - /// The validity of the data in a given cascade origin. #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Ord, PartialEq, PartialOrd)] pub enum DataValidity { @@ -166,8 +95,8 @@ where /// Returns the list of stylesheets for `origin`. /// /// Only used for UA sheets. - pub fn origin_sheets(&mut self, origin: Origin) -> StylesheetCollectionIterator<'_, S> { - self.collections.borrow_mut_for_origin(&origin).iter() + pub fn origin_sheets(&self, origin: Origin) -> impl Iterator { + self.collections.borrow_for_origin(&origin).iter() } /// Returns whether any DOM invalidations were processed as a result of the @@ -367,8 +296,13 @@ where } /// Returns an iterator over the current list of stylesheets. - fn iter(&self) -> StylesheetCollectionIterator<'_, S> { - StylesheetCollectionIterator(self.entries.iter()) + fn iter(&self) -> impl Iterator { + self.entries.iter().map(|e| &e.sheet) + } + + /// Returns a mutable iterator over the current list of stylesheets. + fn iter_mut(&mut self) -> impl Iterator { + self.entries.iter_mut().map(|e| &mut e.sheet) } fn flush(&mut self) -> SheetCollectionFlusher<'_, S> { @@ -427,7 +361,7 @@ macro_rules! sheet_set_methods { ) { debug!(concat!($set_name, "::append_stylesheet")); self.collect_invalidations_for(device, &sheet, guard); - let collection = self.collection_for(&sheet); + let collection = self.collection_for(&sheet, guard); collection.append(sheet); } @@ -442,7 +376,7 @@ macro_rules! sheet_set_methods { debug!(concat!($set_name, "::insert_stylesheet_before")); self.collect_invalidations_for(device, &sheet, guard); - let collection = self.collection_for(&sheet); + let collection = self.collection_for(&sheet, guard); collection.insert_before(sheet, &before_sheet); } @@ -456,7 +390,7 @@ macro_rules! sheet_set_methods { debug!(concat!($set_name, "::remove_stylesheet")); self.collect_invalidations_for(device, &sheet, guard); - let collection = self.collection_for(&sheet); + let collection = self.collection_for(&sheet, guard); collection.remove(&sheet) } @@ -506,7 +440,7 @@ macro_rules! sheet_set_methods { RuleChangeKind::StyleRuleDeclarations => DataValidity::FullyInvalid, }; - let collection = self.collection_for(&sheet); + let collection = self.collection_for(&sheet, guard); collection.set_data_validity_at_least(validity); } }; @@ -524,8 +458,12 @@ where } } - fn collection_for(&mut self, sheet: &S) -> &mut SheetCollection { - let origin = sheet.contents().origin; + fn collection_for( + &mut self, + sheet: &S, + guard: &SharedRwLockReadGuard, + ) -> &mut SheetCollection { + let origin = sheet.contents(guard).origin; self.collections.borrow_mut_for_origin(&origin) } @@ -597,12 +535,17 @@ where } /// Return an iterator over the flattened view of all the stylesheets. - pub fn iter(&self) -> StylesheetIterator<'_, S> { - StylesheetIterator { - origins: OriginSet::all().iter_origins(), - collections: &self.collections, - current: None, - } + pub fn iter(&self) -> impl Iterator { + self.collections + .iter_origins() + .flat_map(|(c, o)| c.iter().map(move |s| (s, o))) + } + + /// Return an iterator over the flattened view of all the stylesheets, mutably. + pub fn iter_mut(&mut self) -> impl Iterator { + self.collections + .iter_mut_origins() + .flat_map(|(c, o)| c.iter_mut().map(move |s| (s, o))) } /// Mark the stylesheets for the specified origin as dirty, because @@ -674,17 +617,22 @@ where self.collection.len() } - fn collection_for(&mut self, _sheet: &S) -> &mut SheetCollection { + fn collection_for(&mut self, _: &S, _: &SharedRwLockReadGuard) -> &mut SheetCollection { &mut self.collection } sheet_set_methods!("AuthorStylesheetSet"); /// Iterate over the list of stylesheets. - pub fn iter(&self) -> StylesheetCollectionIterator<'_, S> { + pub fn iter(&self) -> impl Iterator { self.collection.iter() } + /// Returns a mutable iterator over the current list of stylesheets. + pub fn iter_mut(&mut self) -> impl Iterator { + self.collection.iter_mut() + } + /// Mark the sheet set dirty, as appropriate. pub fn force_dirty(&mut self) { self.invalidations.invalidate_fully(); diff --git a/style/stylesheets/import_rule.rs b/style/stylesheets/import_rule.rs index a6876d7621..6ed29a48da 100644 --- a/style/stylesheets/import_rule.rs +++ b/style/stylesheets/import_rule.rs @@ -19,12 +19,16 @@ use std::fmt::{self, Write}; use style_traits::{CssStringWriter, CssWriter, ToCss}; use to_shmem::{SharedMemoryBuilder, ToShmem}; -/// A sheet that is held from an import rule. #[cfg(feature = "gecko")] +type StyleSheet = crate::gecko::data::GeckoStyleSheet; +#[cfg(feature = "servo")] +type StyleSheet = ::servo_arc::Arc; + +/// A sheet that is held from an import rule. #[derive(Debug)] pub enum ImportSheet { /// A bonafide stylesheet. - Sheet(crate::gecko::data::GeckoStyleSheet), + Sheet(StyleSheet), /// An @import created while parsing off-main-thread, whose Gecko sheet has /// yet to be created and attached. @@ -34,10 +38,9 @@ pub enum ImportSheet { Refused, } -#[cfg(feature = "gecko")] impl ImportSheet { - /// Creates a new ImportSheet from a GeckoStyleSheet. - pub fn new(sheet: crate::gecko::data::GeckoStyleSheet) -> Self { + /// Creates a new ImportSheet from a stylesheet. + pub fn new(sheet: StyleSheet) -> Self { ImportSheet::Sheet(sheet) } @@ -51,10 +54,10 @@ impl ImportSheet { ImportSheet::Refused } - /// Returns a reference to the GeckoStyleSheet in this ImportSheet, if it - /// exists. - pub fn as_sheet(&self) -> Option<&crate::gecko::data::GeckoStyleSheet> { + /// Returns a reference to the stylesheet in this ImportSheet, if it exists. + pub fn as_sheet(&self) -> Option<&StyleSheet> { match *self { + #[cfg(feature = "gecko")] ImportSheet::Sheet(ref s) => { debug_assert!(!s.hack_is_null()); if s.hack_is_null() { @@ -62,6 +65,8 @@ impl ImportSheet { } Some(s) }, + #[cfg(feature = "servo")] + ImportSheet::Sheet(ref s) => Some(s), ImportSheet::Refused | ImportSheet::Pending => None, } } @@ -74,81 +79,27 @@ impl ImportSheet { /// Returns the rule list for this import rule. pub fn rules<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> &'a [CssRule] { match self.as_sheet() { - Some(s) => s.rules(guard), + Some(s) => s.contents(guard).rules(guard), None => &[], } } } -#[cfg(feature = "gecko")] impl DeepCloneWithLock for ImportSheet { fn deep_clone_with_lock(&self, _lock: &SharedRwLock, _guard: &SharedRwLockReadGuard) -> Self { - use crate::gecko::data::GeckoStyleSheet; - use crate::gecko_bindings::bindings; match *self { + #[cfg(feature = "gecko")] ImportSheet::Sheet(ref s) => { + use crate::gecko_bindings::bindings; let clone = unsafe { bindings::Gecko_StyleSheet_Clone(s.raw() as *const _) }; - ImportSheet::Sheet(unsafe { GeckoStyleSheet::from_addrefed(clone) }) + ImportSheet::Sheet(unsafe { StyleSheet::from_addrefed(clone) }) }, - ImportSheet::Pending => ImportSheet::Pending, - ImportSheet::Refused => ImportSheet::Refused, - } - } -} - -/// A sheet that is held from an import rule. -#[cfg(feature = "servo")] -#[derive(Debug)] -pub enum ImportSheet { - /// A bonafide stylesheet. - Sheet(::servo_arc::Arc), - - /// An @import created with a false , so will never be fetched. - Refused, -} - -#[cfg(feature = "servo")] -impl ImportSheet { - /// Creates a new ImportSheet from a stylesheet. - pub fn new(sheet: ::servo_arc::Arc) -> Self { - ImportSheet::Sheet(sheet) - } - - /// Creates a refused ImportSheet for a load that will not happen. - pub fn new_refused() -> Self { - ImportSheet::Refused - } - - /// Returns a reference to the stylesheet in this ImportSheet, if it exists. - pub fn as_sheet(&self) -> Option<&::servo_arc::Arc> { - match *self { - ImportSheet::Sheet(ref s) => Some(s), - ImportSheet::Refused => None, - } - } - - /// Returns the media list for this import rule. - pub fn media<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> Option<&'a MediaList> { - self.as_sheet().and_then(|s| s.media(guard)) - } - - /// Returns the rules for this import rule. - pub fn rules<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> &'a [CssRule] { - match self.as_sheet() { - Some(s) => s.rules(guard), - None => &[], - } - } -} - -#[cfg(feature = "servo")] -impl DeepCloneWithLock for ImportSheet { - fn deep_clone_with_lock(&self, _lock: &SharedRwLock, _guard: &SharedRwLockReadGuard) -> Self { - match *self { + #[cfg(feature = "servo")] ImportSheet::Sheet(ref s) => { use servo_arc::Arc; ImportSheet::Sheet(Arc::new((&**s).clone())) }, + ImportSheet::Pending => ImportSheet::Pending, ImportSheet::Refused => ImportSheet::Refused, } } diff --git a/style/stylesheets/keyframes_rule.rs b/style/stylesheets/keyframes_rule.rs index 8c8bc1ba7b..06572d640e 100644 --- a/style/stylesheets/keyframes_rule.rs +++ b/style/stylesheets/keyframes_rule.rs @@ -204,8 +204,8 @@ impl Keyframe { parent_stylesheet_contents: &StylesheetContents, lock: &SharedRwLock, ) -> Result>, ParseError<'i>> { - let url_data = parent_stylesheet_contents.url_data.read(); - let namespaces = parent_stylesheet_contents.namespaces.read(); + let url_data = &parent_stylesheet_contents.url_data; + let namespaces = &parent_stylesheet_contents.namespaces; let mut context = ParserContext::new( parent_stylesheet_contents.origin, &url_data, diff --git a/style/stylesheets/loader.rs b/style/stylesheets/loader.rs index f987cf9597..b6c0777ef9 100644 --- a/style/stylesheets/loader.rs +++ b/style/stylesheets/loader.rs @@ -6,7 +6,6 @@ //! for `@import` rules. use crate::media_queries::MediaList; -use crate::parser::ParserContext; use crate::shared_lock::{Locked, SharedRwLock}; use crate::stylesheets::import_rule::{ImportLayer, ImportRule, ImportSupportsCondition}; use crate::values::CssUrl; @@ -22,7 +21,6 @@ pub trait StylesheetLoader { &self, url: CssUrl, location: SourceLocation, - context: &ParserContext, lock: &SharedRwLock, media: Arc>, supports: Option, diff --git a/style/stylesheets/mod.rs b/style/stylesheets/mod.rs index 078999741c..2667642ba3 100644 --- a/style/stylesheets/mod.rs +++ b/style/stylesheets/mod.rs @@ -68,7 +68,7 @@ pub use self::origin::{Origin, OriginSet, OriginSetIterator, PerOrigin, PerOrigi pub use self::page_rule::{PagePseudoClassFlags, PageRule, PageSelector, PageSelectors}; pub use self::position_try_rule::PositionTryRule; pub use self::property_rule::PropertyRule; -pub use self::rule_list::{CssRules, CssRulesHelpers}; +pub use self::rule_list::CssRules; pub use self::rule_parser::{InsertRuleContext, State, TopLevelRuleParser}; pub use self::rules_iterator::{AllRules, EffectiveRules}; pub use self::rules_iterator::{ @@ -569,8 +569,8 @@ impl CssRule { loader: Option<&dyn StylesheetLoader>, allow_import_rules: AllowImportRules, ) -> Result { - let url_data = parent_stylesheet_contents.url_data.read(); - let namespaces = parent_stylesheet_contents.namespaces.read(); + let url_data = &parent_stylesheet_contents.url_data; + let namespaces = &parent_stylesheet_contents.namespaces; let mut context = ParserContext::new( parent_stylesheet_contents.origin, &url_data, diff --git a/style/stylesheets/rule_list.rs b/style/stylesheets/rule_list.rs index 56077b2669..ce8aa25d9c 100644 --- a/style/stylesheets/rule_list.rs +++ b/style/stylesheets/rule_list.rs @@ -120,11 +120,9 @@ impl CssRules { } dest.write_str("\n}") } -} -/// A trait to implement helpers for `Arc>`. -pub trait CssRulesHelpers { - /// + /// Parses a rule for . Caller is + /// responsible for calling insert() afterwards. /// /// Written in this funky way because parsing an @import rule may cause us /// to clone a stylesheet from the same document due to caching in the CSS @@ -132,21 +130,7 @@ pub trait CssRulesHelpers { /// /// TODO(emilio): We could also pass the write guard down into the loader /// instead, but that seems overkill. - fn insert_rule( - &self, - lock: &SharedRwLock, - rule: &str, - parent_stylesheet_contents: &StylesheetContents, - index: usize, - nested: CssRuleTypes, - parse_relative_rule_type: Option, - loader: Option<&dyn StylesheetLoader>, - allow_import_rules: AllowImportRules, - ) -> Result; -} - -impl CssRulesHelpers for Locked { - fn insert_rule( + pub fn parse_rule_for_insert( &self, lock: &SharedRwLock, rule: &str, @@ -157,39 +141,26 @@ impl CssRulesHelpers for Locked { loader: Option<&dyn StylesheetLoader>, allow_import_rules: AllowImportRules, ) -> Result { - let new_rule = { - let read_guard = lock.read(); - let rules = self.read_with(&read_guard); - - // Step 1, 2 - if index > rules.0.len() { - return Err(RulesMutateError::IndexSize); - } + // Step 1, 2 + if index > self.0.len() { + return Err(RulesMutateError::IndexSize); + } - let insert_rule_context = InsertRuleContext { - rule_list: &rules.0, - index, - containing_rule_types, - parse_relative_rule_type, - }; - - // Steps 3, 4, 5, 6 - CssRule::parse( - &rule, - insert_rule_context, - parent_stylesheet_contents, - lock, - loader, - allow_import_rules, - )? + let insert_rule_context = InsertRuleContext { + rule_list: &self.0, + index, + containing_rule_types, + parse_relative_rule_type, }; - { - let mut write_guard = lock.write(); - let rules = self.write_with(&mut write_guard); - rules.0.insert(index, new_rule.clone()); - } - - Ok(new_rule) + // Steps 3, 4, 5, 6 + CssRule::parse( + &rule, + insert_rule_context, + parent_stylesheet_contents, + lock, + loader, + allow_import_rules, + ) } } diff --git a/style/stylesheets/rule_parser.rs b/style/stylesheets/rule_parser.rs index 772eb9fbcf..6f2abc6c17 100644 --- a/style/stylesheets/rule_parser.rs +++ b/style/stylesheets/rule_parser.rs @@ -424,7 +424,6 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a, 'i> { let import_rule = loader.request_stylesheet( url, start.source_location(), - &self.context, &self.shared_lock, media, supports, diff --git a/style/stylesheets/stylesheet.rs b/style/stylesheets/stylesheet.rs index f8f1ec18b0..7c975abf5f 100644 --- a/style/stylesheets/stylesheet.rs +++ b/style/stylesheets/stylesheet.rs @@ -18,7 +18,6 @@ use crate::{Namespace, Prefix}; use cssparser::{Parser, ParserInput, StyleSheetParser}; #[cfg(feature = "gecko")] use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalShallowSizeOf}; -use parking_lot::RwLock; use rustc_hash::FxHashMap; use servo_arc::Arc; use std::sync::atomic::{AtomicBool, Ordering}; @@ -56,15 +55,15 @@ pub struct StylesheetContents { /// The origin of this stylesheet. pub origin: Origin, /// The url data this stylesheet should use. - pub url_data: RwLock, + pub url_data: UrlExtraData, /// The namespaces that apply to this stylesheet. - pub namespaces: RwLock, + pub namespaces: Namespaces, /// The quirks mode of this stylesheet. pub quirks_mode: QuirksMode, /// This stylesheet's source map URL. - pub source_map_url: RwLock>, + pub source_map_url: Option, /// This stylesheet's source URL. - pub source_url: RwLock>, + pub source_url: Option, /// The use counters of the original stylesheet. pub use_counters: UseCounters, @@ -104,11 +103,11 @@ impl StylesheetContents { Arc::new(Self { rules: CssRules::new(rules, &shared_lock), origin, - url_data: RwLock::new(url_data), - namespaces: RwLock::new(namespaces), + url_data, + namespaces, quirks_mode, - source_map_url: RwLock::new(source_map_url), - source_url: RwLock::new(source_url), + source_map_url, + source_url, use_counters, _forbid_construction: (), }) @@ -135,11 +134,11 @@ impl StylesheetContents { Arc::new(Self { rules, origin, - url_data: RwLock::new(url_data), - namespaces: RwLock::new(Namespaces::default()), + url_data, + namespaces: Namespaces::default(), quirks_mode, - source_map_url: RwLock::new(None), - source_url: RwLock::new(None), + source_map_url: None, + source_url: None, use_counters: UseCounters::default(), _forbid_construction: (), }) @@ -161,6 +160,30 @@ impl StylesheetContents { self.rules.unconditional_shallow_size_of(ops) + self.rules.read_with(guard).size_of(guard, ops) } + + /// Return an iterator using the condition `C`. + #[inline] + pub fn iter_rules<'a, 'b, C>( + &'a self, + device: &'a Device, + guard: &'a SharedRwLockReadGuard<'b>, + ) -> RulesIterator<'a, 'b, C> + where + C: NestedRuleIterationCondition, + { + RulesIterator::new(device, self.quirks_mode, guard, self.rules(guard).iter()) + } + + /// Return an iterator over the effective rules within the style-sheet, as + /// according to the supplied `Device`. + #[inline] + pub fn effective_rules<'a, 'b>( + &'a self, + device: &'a Device, + guard: &'a SharedRwLockReadGuard<'b>, + ) -> EffectiveRulesIterator<'a, 'b> { + self.iter_rules::(device, guard) + } } impl DeepCloneWithLock for StylesheetContents { @@ -175,10 +198,10 @@ impl DeepCloneWithLock for StylesheetContents { rules: Arc::new(lock.wrap(rules)), quirks_mode: self.quirks_mode, origin: self.origin, - url_data: RwLock::new((*self.url_data.read()).clone()), - namespaces: RwLock::new((*self.namespaces.read()).clone()), - source_map_url: RwLock::new((*self.source_map_url.read()).clone()), - source_url: RwLock::new((*self.source_url.read()).clone()), + url_data: self.url_data.clone(), + namespaces: self.namespaces.clone(), + source_map_url: self.source_map_url.clone(), + source_url: self.source_url.clone(), use_counters: self.use_counters.clone(), _forbid_construction: (), } @@ -189,7 +212,7 @@ impl DeepCloneWithLock for StylesheetContents { #[derive(Debug)] pub struct Stylesheet { /// The contents of this stylesheet. - pub contents: Arc, + pub contents: Locked>, /// The lock used for objects inside this stylesheet pub shared_lock: SharedRwLock, /// List of media associated with the Stylesheet. @@ -206,52 +229,17 @@ pub trait StylesheetInDocument: ::std::fmt::Debug { /// Get the media associated with this stylesheet. fn media<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> Option<&'a MediaList>; - /// Returns a reference to the list of rules in this stylesheet. - fn rules<'a, 'b: 'a>(&'a self, guard: &'b SharedRwLockReadGuard) -> &'a [CssRule] { - self.contents().rules(guard) - } - /// Returns a reference to the contents of the stylesheet. - fn contents(&self) -> &StylesheetContents; - - /// Return an iterator using the condition `C`. - #[inline] - fn iter_rules<'a, 'b, C>( - &'a self, - device: &'a Device, - guard: &'a SharedRwLockReadGuard<'b>, - ) -> RulesIterator<'a, 'b, C> - where - C: NestedRuleIterationCondition, - { - let contents = self.contents(); - RulesIterator::new( - device, - contents.quirks_mode, - guard, - contents.rules(guard).iter(), - ) - } + fn contents<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> &'a StylesheetContents; /// Returns whether the style-sheet applies for the current device. fn is_effective_for_device(&self, device: &Device, guard: &SharedRwLockReadGuard) -> bool { match self.media(guard) { - Some(medialist) => medialist.evaluate(device, self.contents().quirks_mode), + Some(medialist) => medialist.evaluate(device, self.contents(guard).quirks_mode), None => true, } } - /// Return an iterator over the effective rules within the style-sheet, as - /// according to the supplied `Device`. - #[inline] - fn effective_rules<'a, 'b>( - &'a self, - device: &'a Device, - guard: &'a SharedRwLockReadGuard<'b>, - ) -> EffectiveRulesIterator<'a, 'b> { - self.iter_rules::(device, guard) - } - /// Return the implicit scope root for this stylesheet, if one exists. fn implicit_scope_root(&self) -> Option; } @@ -266,8 +254,8 @@ impl StylesheetInDocument for Stylesheet { } #[inline] - fn contents(&self) -> &StylesheetContents { - &self.contents + fn contents<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> &'a StylesheetContents { + self.contents.read_with(guard) } fn implicit_scope_root(&self) -> Option { @@ -299,8 +287,8 @@ impl StylesheetInDocument for DocumentStyleSheet { } #[inline] - fn contents(&self) -> &StylesheetContents { - self.0.contents() + fn contents<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> &'a StylesheetContents { + self.0.contents(guard) } fn implicit_scope_root(&self) -> Option { @@ -397,40 +385,6 @@ impl SanitizationData { } impl Stylesheet { - /// Updates an empty stylesheet from a given string of text. - pub fn update_from_str( - existing: &Stylesheet, - css: &str, - url_data: UrlExtraData, - stylesheet_loader: Option<&dyn StylesheetLoader>, - error_reporter: Option<&dyn ParseErrorReporter>, - allow_import_rules: AllowImportRules, - ) { - let use_counters = UseCounters::default(); - let (namespaces, rules, source_map_url, source_url) = Self::parse_rules( - css, - &url_data, - existing.contents.origin, - &existing.shared_lock, - stylesheet_loader, - error_reporter, - existing.contents.quirks_mode, - Some(&use_counters), - allow_import_rules, - /* sanitization_data = */ None, - ); - - *existing.contents.url_data.write() = url_data; - *existing.contents.namespaces.write() = namespaces; - - // Acquire the lock *after* parsing, to minimize the exclusive section. - let mut guard = existing.shared_lock.write(); - *existing.contents.rules.write_with(&mut guard) = CssRules(rules); - *existing.contents.source_map_url.write() = source_map_url; - *existing.contents.source_url.write() = source_url; - existing.contents.use_counters.merge(&use_counters); - } - fn parse_rules( css: &str, url_data: &UrlExtraData, @@ -508,11 +462,7 @@ impl Stylesheet { ) } - /// Creates an empty stylesheet and parses it with a given base url, origin - /// and media. - /// - /// Effectively creates a new stylesheet and forwards the hard work to - /// `Stylesheet::update_from_str`. + /// Creates an empty stylesheet and parses it with a given base url, origin and media. pub fn from_str( css: &str, url_data: UrlExtraData, @@ -538,7 +488,7 @@ impl Stylesheet { ); Stylesheet { - contents, + contents: shared_lock.wrap(contents), shared_lock, media, disabled: AtomicBool::new(false), @@ -573,7 +523,11 @@ impl Clone for Stylesheet { // Make a deep clone of the media, using the new lock. let media = self.media.read_with(&guard).clone(); let media = Arc::new(lock.wrap(media)); - let contents = Arc::new(self.contents.deep_clone_with_lock(&lock, &guard)); + let contents = lock.wrap(Arc::new( + self.contents + .read_with(&guard) + .deep_clone_with_lock(&lock, &guard), + )); Stylesheet { contents, diff --git a/style/stylist.rs b/style/stylist.rs index fc0122e9e8..a8b87e5128 100644 --- a/style/stylist.rs +++ b/style/stylist.rs @@ -1564,6 +1564,7 @@ impl Stylist { /// Returns the registered `@position-try-rule` animation for the specified name. #[inline] + #[cfg(feature = "gecko")] pub fn lookup_position_try<'a, E>( &'a self, name: &Atom, @@ -3385,15 +3386,15 @@ impl CascadeData { } debug!(" + {:?}", stylesheet); - let contents = stylesheet.contents(); + let contents = stylesheet.contents(guard); results.push(contents.to_media_list_key()); // Safety: StyleSheetContents are reference-counted with Arc. contents_list.push(StylesheetContentsPtr(unsafe { - Arc::from_raw_addrefed(contents) + Arc::from_raw_addrefed(&*contents) })); - for rule in stylesheet.effective_rules(device, guard) { + for rule in stylesheet.contents(guard).effective_rules(device, guard) { match *rule { CssRule::Import(ref lock) => { let import_rule = lock.read_with(guard); @@ -4072,10 +4073,9 @@ impl CascadeData { return Ok(()); } - let contents = stylesheet.contents(); - + let contents = stylesheet.contents(guard); if rebuild_kind.should_rebuild_invalidation() { - self.effective_media_query_results.saw_effective(contents); + self.effective_media_query_results.saw_effective(&*contents); } let mut state = ContainingRuleState::default(); @@ -4110,9 +4110,8 @@ impl CascadeData { let effective_now = stylesheet.is_effective_for_device(device, guard); - let effective_then = self - .effective_media_query_results - .was_effective(stylesheet.contents()); + let contents = stylesheet.contents(guard); + let effective_then = self.effective_media_query_results.was_effective(contents); if effective_now != effective_then { debug!( @@ -4128,8 +4127,7 @@ impl CascadeData { return true; } - let mut iter = stylesheet.iter_rules::(device, guard); - + let mut iter = contents.iter_rules::(device, guard); while let Some(rule) = iter.next() { match *rule { CssRule::Style(..) diff --git a/style/values/computed/font.rs b/style/values/computed/font.rs index 5686c6441b..1e418270f4 100644 --- a/style/values/computed/font.rs +++ b/style/values/computed/font.rs @@ -420,6 +420,7 @@ impl FontFamily { generic_font_family!(MONOSPACE, Monospace); generic_font_family!(CURSIVE, Cursive); generic_font_family!(FANTASY, Fantasy); + #[cfg(feature = "gecko")] generic_font_family!(MATH, Math); #[cfg(feature = "gecko")] generic_font_family!(MOZ_EMOJI, MozEmoji); @@ -435,6 +436,7 @@ impl FontFamily { GenericFontFamily::Monospace => &*MONOSPACE, GenericFontFamily::Cursive => &*CURSIVE, GenericFontFamily::Fantasy => &*FANTASY, + #[cfg(feature = "gecko")] GenericFontFamily::Math => &*MATH, #[cfg(feature = "gecko")] GenericFontFamily::MozEmoji => &*MOZ_EMOJI, @@ -577,6 +579,7 @@ fn system_ui_enabled(_: &ParserContext) -> bool { static_prefs::pref!("layout.css.system-ui.enabled") } +#[cfg(feature = "gecko")] fn math_enabled(context: &ParserContext) -> bool { context.chrome_rules_enabled() || static_prefs::pref!("mathml.font_family_math.enabled") } @@ -619,6 +622,7 @@ pub enum GenericFontFamily { Monospace, Cursive, Fantasy, + #[cfg(feature = "gecko")] #[parse(condition = "math_enabled")] Math, #[parse(condition = "system_ui_enabled")] @@ -635,9 +639,9 @@ impl GenericFontFamily { /// the user. See bug 789788 and bug 1730098. pub(crate) fn valid_for_user_font_prioritization(self) -> bool { match self { - Self::None | Self::Cursive | Self::Fantasy | Self::Math | Self::SystemUi => false, + Self::None | Self::Cursive | Self::Fantasy | Self::SystemUi => false, #[cfg(feature = "gecko")] - Self::MozEmoji => false, + Self::Math | Self::MozEmoji => false, Self::Serif | Self::SansSerif | Self::Monospace => true, } } diff --git a/style/values/computed/length.rs b/style/values/computed/length.rs index 1c02fb5c6a..a7077506a8 100644 --- a/style/values/computed/length.rs +++ b/style/values/computed/length.rs @@ -103,16 +103,6 @@ macro_rules! computed_length_percentage_or_auto { Self::LengthPercentage(ref lp) => Some(lp.to_used_value(percentage_basis)), } } - - /// Returns true if the computed value is absolute 0 or 0%. - #[inline] - pub fn is_definitely_zero(&self) -> bool { - use crate::values::generics::length::LengthPercentageOrAuto::*; - match *self { - LengthPercentage(ref l) => l.is_definitely_zero(), - Auto => false, - } - } }; } diff --git a/style/values/computed/length_percentage.rs b/style/values/computed/length_percentage.rs index 1a4b6621da..0d762389c8 100644 --- a/style/values/computed/length_percentage.rs +++ b/style/values/computed/length_percentage.rs @@ -449,16 +449,6 @@ impl LengthPercentage { } } - /// Returns true if the computed value is absolute 0 or 0%. - #[inline] - pub fn is_definitely_zero(&self) -> bool { - match self.unpack() { - Unpacked::Length(l) => l.px() == 0.0, - Unpacked::Percentage(p) => p.0 == 0.0, - Unpacked::Calc(..) => false, - } - } - /// Resolves the percentage. #[inline] pub fn resolve(&self, basis: Length) -> Length { @@ -657,16 +647,21 @@ impl Zero for LengthPercentage { LengthPercentage::new_length(Length::zero()) } + /// Returns true if the computed value is absolute 0 or 0%. #[inline] fn is_zero(&self) -> bool { - self.is_definitely_zero() + match self.unpack() { + Unpacked::Length(l) => l.px() == 0.0, + Unpacked::Percentage(p) => p.0 == 0.0, + Unpacked::Calc(..) => false, + } } } impl ZeroNoPercent for LengthPercentage { #[inline] fn is_zero_no_percent(&self) -> bool { - self.is_definitely_zero() && !self.has_percentage() + self.to_length().is_some_and(|l| l.px() == 0.0) } } @@ -1322,12 +1317,6 @@ impl ToAnimatedValue for NonNegativeLengthPercentage { } impl NonNegativeLengthPercentage { - /// Returns true if the computed value is absolute 0 or 0%. - #[inline] - pub fn is_definitely_zero(&self) -> bool { - self.0.is_definitely_zero() - } - /// Returns the used value. #[inline] pub fn to_used_value(&self, containing_length: Au) -> Au { diff --git a/style/values/generics/length.rs b/style/values/generics/length.rs index 9956ff7af8..7f20f5390f 100644 --- a/style/values/generics/length.rs +++ b/style/values/generics/length.rs @@ -166,7 +166,6 @@ pub enum GenericSize { #[cfg(feature = "gecko")] #[animation(error)] MozAvailable, - #[cfg(feature = "gecko")] #[animation(error)] WebkitFillAvailable, #[animation(error)] @@ -184,14 +183,12 @@ where { fn collect_completion_keywords(f: style_traits::KeywordsCollectFn) { LengthPercent::collect_completion_keywords(f); - f(&["auto", "stretch", "fit-content"]); + f(&["auto", "stretch", "fit-content", "max-content", "min-content"]); if cfg!(feature = "gecko") { - f(&[ - "max-content", - "min-content", - "-moz-available", - "-webkit-fill-available", - ]); + f(&["-moz-available"]); + } + if static_prefs::pref!("layout.css.webkit-fill-available.enabled") { + f(&["-webkit-fill-available"]); } if static_prefs::pref!("layout.css.anchor-positioning.enabled") { f(&["anchor-size"]); @@ -245,7 +242,6 @@ pub enum GenericMaxSize { #[cfg(feature = "gecko")] #[animation(error)] MozAvailable, - #[cfg(feature = "gecko")] #[animation(error)] WebkitFillAvailable, #[animation(error)] @@ -263,14 +259,12 @@ where { fn collect_completion_keywords(f: style_traits::KeywordsCollectFn) { LP::collect_completion_keywords(f); - f(&["none", "stretch", "fit-content"]); + f(&["none", "stretch", "fit-content", "max-content", "min-content"]); if cfg!(feature = "gecko") { - f(&[ - "max-content", - "min-content", - "-moz-available", - "-webkit-fill-available", - ]); + f(&["-moz-available"]); + } + if static_prefs::pref!("layout.css.webkit-fill-available.enabled") { + f(&["-webkit-fill-available"]); } if static_prefs::pref!("layout.css.anchor-positioning.enabled") { f(&["anchor-size"]); @@ -595,18 +589,6 @@ impl GenericMargin { } } -#[cfg(feature = "servo")] -impl GenericMargin { - /// Returns true if the computed value is absolute 0 or 0%. - #[inline] - pub fn is_definitely_zero(&self) -> bool { - match self { - Self::LengthPercentage(lp) => lp.is_definitely_zero(), - _ => false, - } - } -} - impl SpecifiedValueInfo for GenericMargin where LP: SpecifiedValueInfo, diff --git a/style/values/specified/font.rs b/style/values/specified/font.rs index dd4b2da23e..a95eefe49c 100644 --- a/style/values/specified/font.rs +++ b/style/values/specified/font.rs @@ -1526,6 +1526,7 @@ impl Parse for FontLanguageOverride { Copy, Debug, Eq, + Hash, MallocSizeOf, Parse, PartialEq, @@ -1536,6 +1537,7 @@ impl Parse for FontLanguageOverride { ToShmem, ToTyped, )] +#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] pub enum FontSynthesis { /// This attribute may be synthesized if not supported by a face. Auto, diff --git a/style/values/specified/length.rs b/style/values/specified/length.rs index 4e0b864692..91fd30c204 100644 --- a/style/values/specified/length.rs +++ b/style/values/specified/length.rs @@ -2057,7 +2057,6 @@ macro_rules! parse_size_non_length { "fit-content" | "-moz-fit-content" => $size::FitContent, #[cfg(feature = "gecko")] "-moz-available" => $size::MozAvailable, - #[cfg(feature = "gecko")] "-webkit-fill-available" if $allow_webkit_fill_available => $size::WebkitFillAvailable, "stretch" if is_stretch_enabled() => $size::Stretch, $auto_or_none => $size::$auto_or_none_ident, @@ -2069,12 +2068,10 @@ macro_rules! parse_size_non_length { }}; } -#[cfg(feature = "gecko")] fn is_webkit_fill_available_enabled_in_width_and_height() -> bool { static_prefs::pref!("layout.css.webkit-fill-available.enabled") } -#[cfg(feature = "gecko")] fn is_webkit_fill_available_enabled_in_all_size_properties() -> bool { // For convenience at the callsites, we check both prefs here, // since both must be 'true' in order for the keyword to be @@ -2083,16 +2080,6 @@ fn is_webkit_fill_available_enabled_in_all_size_properties() -> bool { && static_prefs::pref!("layout.css.webkit-fill-available.all-size-properties.enabled") } -#[cfg(feature = "servo")] -fn is_webkit_fill_available_enabled_in_width_and_height() -> bool { - false -} - -#[cfg(feature = "servo")] -fn is_webkit_fill_available_enabled_in_all_size_properties() -> bool { - false -} - fn is_stretch_enabled() -> bool { static_prefs::pref!("layout.css.stretch-size-keyword.enabled") } diff --git a/style/values/specified/text.rs b/style/values/specified/text.rs index 0499954d39..e88d93b5cc 100644 --- a/style/values/specified/text.rs +++ b/style/values/specified/text.rs @@ -423,10 +423,15 @@ bitflags! { /// Capitalize each word. const CAPITALIZE = 1 << 2; /// Automatic italicization of math variables. + #[cfg(feature = "gecko")] const MATH_AUTO = 1 << 3; /// All the case transforms, which are exclusive with each other. + #[cfg(feature = "gecko")] const CASE_TRANSFORMS = Self::UPPERCASE.0 | Self::LOWERCASE.0 | Self::CAPITALIZE.0 | Self::MATH_AUTO.0; + /// All the case transforms, which are exclusive with each other. + #[cfg(feature = "servo")] + const CASE_TRANSFORMS = Self::UPPERCASE.0 | Self::LOWERCASE.0 | Self::CAPITALIZE.0; /// full-width const FULL_WIDTH = 1 << 4; @@ -453,6 +458,19 @@ impl TextTransform { // Case bits are exclusive with each other. case.is_empty() || case.bits().is_power_of_two() } + + /// Returns the corresponding TextTransformCase. + pub fn case(&self) -> TextTransformCase { + match *self & Self::CASE_TRANSFORMS { + Self::NONE => TextTransformCase::None, + Self::UPPERCASE => TextTransformCase::Uppercase, + Self::LOWERCASE => TextTransformCase::Lowercase, + Self::CAPITALIZE => TextTransformCase::Capitalize, + #[cfg(feature = "gecko")] + Self::MATH_AUTO => TextTransformCase::MathAuto, + _ => unreachable!("Case bits are exclusive with each other"), + } + } } /// Specified and computed value of text-align-last. 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 4d827aaffa..a5ccb2b571 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.34" +cssparser = "0.35" 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..2e1fe30192 --- /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.8" + +[build-dependencies] +string_cache_codegen = "0.5" 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..8e18e69021 --- /dev/null +++ b/stylo_atoms/static_atoms.txt @@ -0,0 +1,185 @@ +-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 +animationcancel +animationend +animationiteration +animationstart +aspect-ratio +beforetoggle +beforeunload +block-size +button +canplay +canplaythrough +center +change +characteristicvaluechanged +checkbox +cancel +click +close +closing +color +complete +compositionend +compositionstart +compositionupdate +controllerchange +cursive +dark +datachannel +date +datetime-local +dir +device-pixel-ratio +durationchange +email +emptied +end +ended +error +fantasy +fetch +file +fill +fill-opacity +formdata +fullscreenchange +fullscreenerror +gattserverdisconnected +hairline +hashchange +height +hidden +icecandidate +iceconnectionstatechange +icegatheringstatechange +image +inline-size +input +inputsourceschange +invalid +keydown +keypress +kind +left +light +ltr +load +loadeddata +loadedmetadata +loadend +loadstart +message +message +messageerror +monospace +month +mousedown +mousemove +mouseover +mouseup +negotiationneeded +none +normal +number +onchange +open +orientation +pagehide +pageshow +password +pause +play +playing +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_config/Cargo.toml b/stylo_config/Cargo.toml new file mode 100644 index 0000000000..0aeaf42d71 --- /dev/null +++ b/stylo_config/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "stylo_config" +version.workspace = true +authors = ["The Servo Project Developers"] +documentation = "https://docs.rs/stylo_config/" +description = "Runtime configuration for Stylo" +repository = "https://github.com/servo/stylo" +keywords = ["css", "style"] +license = "MPL-2.0" +edition = "2021" +readme = "../README.md" + +[lib] +name = "stylo_config" +path = "lib.rs" diff --git a/stylo_config/lib.rs b/stylo_config/lib.rs new file mode 100644 index 0000000000..ba504f625d --- /dev/null +++ b/stylo_config/lib.rs @@ -0,0 +1,91 @@ +/* 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::collections::HashMap; +use std::sync::{LazyLock, RwLock}; + +static PREFS: LazyLock = LazyLock::new(Preferences::default); + +#[derive(Debug, Default)] +pub struct Preferences { + bool_prefs: RwLock>, + i32_prefs: RwLock>, +} + +impl Preferences { + pub fn get_bool(&self, key: &str) -> bool { + let prefs = self.bool_prefs.read().expect("RwLock is poisoned"); + *prefs.get(key).unwrap_or(&false) + } + + pub fn get_i32(&self, key: &str) -> i32 { + let prefs = self.i32_prefs.read().expect("RwLock is poisoned"); + *prefs.get(key).unwrap_or(&0) + } + + pub fn set_bool(&self, key: &str, value: bool) { + let mut prefs = self.bool_prefs.write().expect("RwLock is poisoned"); + + // Avoid cloning the key if it exists. + if let Some(pref) = prefs.get_mut(key) { + *pref = value; + } else { + prefs.insert(key.to_owned(), value); + } + } + + pub fn set_i32(&self, key: &str, value: i32) { + let mut prefs = self.i32_prefs.write().expect("RwLock is poisoned"); + + // Avoid cloning the key if it exists. + if let Some(pref) = prefs.get_mut(key) { + *pref = value; + } else { + prefs.insert(key.to_owned(), value); + } + } +} + +pub fn get_bool(key: &str) -> bool { + PREFS.get_bool(key) +} + +pub fn get_i32(key: &str) -> i32 { + PREFS.get_i32(key) +} + +pub fn set_bool(key: &str, value: bool) { + PREFS.set_bool(key, value) +} + +pub fn set_i32(key: &str, value: i32) { + PREFS.set_i32(key, value) +} + +#[test] +fn test() { + let prefs = Preferences::default(); + + // Prefs have default values when unset. + assert_eq!(prefs.get_bool("foo"), false); + assert_eq!(prefs.get_i32("bar"), 0); + + // Prefs can be set and retrieved. + prefs.set_bool("foo", true); + prefs.set_i32("bar", 1); + assert_eq!(prefs.get_bool("foo"), true); + assert_eq!(prefs.get_i32("bar"), 1); + prefs.set_bool("foo", false); + prefs.set_i32("bar", 2); + assert_eq!(prefs.get_bool("foo"), false); + assert_eq!(prefs.get_i32("bar"), 2); + + // Each value type currently has an independent namespace. + prefs.set_i32("foo", 3); + prefs.set_bool("bar", true); + assert_eq!(prefs.get_i32("foo"), 3); + assert_eq!(prefs.get_bool("foo"), false); + assert_eq!(prefs.get_bool("bar"), true); + assert_eq!(prefs.get_i32("bar"), 2); +} 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..98a0330cf4 --- /dev/null +++ b/stylo_dom/lib.rs @@ -0,0 +1,181 @@ +/* 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 bitflags::bitflags; +use malloc_size_of::malloc_size_of_is_0; + +pub const HEADING_LEVEL_OFFSET: usize = 52; + +// DOM types to be shared between Rust and C++. +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://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; + + /// 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..28c0a54c40 --- /dev/null +++ b/stylo_static_prefs/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "stylo_static_prefs" +version.workspace = true +authors = ["The Servo Project Developers"] +documentation = "https://docs.rs/stylo_static_prefs/" +description = "Static configuration for Stylo" +repository = "https://github.com/servo/stylo" +keywords = ["css", "style"] +license = "MPL-2.0" +edition = "2021" +readme = "../README.md" diff --git a/stylo_static_prefs/src/lib.rs b/stylo_static_prefs/src/lib.rs new file mode 100644 index 0000000000..5a497cbe32 --- /dev/null +++ b/stylo_static_prefs/src/lib.rs @@ -0,0 +1,42 @@ +/* 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/. */ + +//! A list of static preferences exposed to the style crate. These should +//! be kept sync with the preferences used by the style. +#[macro_export] +macro_rules! pref { + ("layout.css.stylo-local-work-queue.in-main-thread") => { + 32 + }; + ("layout.css.stylo-work-unit-size") => { + 16 + }; + ("layout.css.stylo-local-work-queue.in-worker") => { + 0 + }; + ("layout.css.system-ui.enabled") => { + true + }; + ("layout.css.fit-content-function.enabled") => { + true + }; + ("layout.css.relative-color-syntax.enabled") => { + true + }; + ("layout.css.stretch-size-keyword.enabled") => { + true + }; + ("layout.css.marker.restricted") => { + true + }; + ("layout.css.webkit-fill-available.enabled") => { + true + }; + ("layout.css.webkit-fill-available.all-size-properties.enabled") => { + true + }; + ($string:literal) => { + false + }; +} 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 fadd1e6e3a..bfc6e940ce 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.2.0" authors = ["The Servo Project Developers"] license = "MPL-2.0" repository = "https://github.com/servo/stylo" @@ -23,8 +23,8 @@ string_cache = ["dep:string_cache"] thin-vec = ["dep:thin-vec"] [dependencies] -cssparser = { version = "0.34", optional = true } -servo_arc = { version = "0.4.0", path = "../servo_arc", optional = true } +cssparser = { version = "0.35", 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 }