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..d2547c0d06 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,18 @@ +[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"] 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..fdb8d690a0 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 = "0.6.0" 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 = { version = "0.31", path = "../selectors" } +servo_arc = { version = "0.4", path = "../servo_arc" } 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 8b3d39405e..c90cb73155 100644 --- a/selectors/Cargo.toml +++ b/selectors/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "selectors" -version = "0.26.0" +version = "0.31.0" authors = ["The Servo Project Developers"] documentation = "https://docs.rs/selectors/" description = "CSS Selectors matching for Rust" @@ -21,7 +21,7 @@ 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"] } fxhash = "0.2" log = "0.4" @@ -29,7 +29,7 @@ phf = "0.11" precomputed-hash = "0.1" servo_arc = { version = "0.4", path = "../servo_arc" } smallvec = "1.0" -to_shmem = { version = "0.1", path = "../to_shmem", features = ["servo_arc"], optional = true } +to_shmem = { version = "0.2", path = "../to_shmem", features = ["servo_arc"], optional = true } to_shmem_derive = { version = "0.1", path = "../to_shmem_derive", optional = true } new_debug_unreachable = "1" diff --git a/selectors/matching.rs b/selectors/matching.rs index e6694dad7a..152de53482 100644 --- a/selectors/matching.rs +++ b/selectors/matching.rs @@ -249,7 +249,7 @@ impl From for KleeneValue { /// partial selectors (indexed from the right). We use this API design, rather /// than having the callers pass a SelectorIter, because creating a SelectorIter /// requires dereferencing the selector to get the length, which adds an -/// unncessary cache miss for cases when we can fast-reject with AncestorHashes +/// unnecessary cache miss for cases when we can fast-reject with AncestorHashes /// (which the caller can store inline with the selector pointer). #[inline(always)] pub fn matches_selector( diff --git a/servo_arc/Cargo.toml b/servo_arc/Cargo.toml index 8b0976b75d..9f77b64b43 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.1" 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 ce36e85d0d..69f1df7246 100644 --- a/style/Cargo.toml +++ b/style/Cargo.toml @@ -1,11 +1,12 @@ [package] -name = "style" -version = "0.0.1" +name = "stylo" +version = "0.6.0" 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", - "markup5ever", + "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 = { version = "0.6", path = "../stylo_dom", package = "stylo_dom" } new_debug_unreachable = "1.0" encoding_rs = {version = "0.8", optional = true} euclid = "0.22" fxhash = "0.2" -icu_segmenter = { version = "2.0", default-features = false, features = ["auto", "compiled_data"] } +icu_segmenter = { version = "1.5", 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" } -markup5ever = { version = "0.12", optional = true } +malloc_size_of = { version = "0.6", path = "../malloc_size_of", package = "stylo_malloc_size_of" } +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,25 +84,23 @@ parking_lot = "0.12" precomputed-hash = "0.1.1" rayon = "1" rayon-core = "1" -selectors = { path = "../selectors" } +selectors = { version = "0.31.0", path = "../selectors" } 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 = { version = "0.4.0", path = "../servo_arc" } +stylo_atoms = { version = "0.6", path = "../stylo_atoms", optional = true} smallbitvec = "2.3.0" smallvec = "1.0" static_assertions = "1.1" -static_prefs = { path = "../../../modules/libpref/init/static_prefs" } +static_prefs = { version = "0.6", path = "../stylo_static_prefs", package = "stylo_static_prefs" } 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 = { version = "0.6", path = "../stylo_config", package = "stylo_config", optional = true } +style_derive = { version = "0.6", path = "../style_derive", package = "stylo_derive" } +style_traits = { version = "0.6", path = "../style_traits", package = "stylo_traits" } +to_shmem = { version = "0.2", path = "../to_shmem" } +to_shmem_derive = { version = "0.1", path = "../to_shmem_derive" } +thin-vec = "0.2.1" uluru = "3.0" -unicode-bidi = { version = "0.3", default-features = false } 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/animation.rs b/style/animation.rs index c90e2327c5..5b5bcd984a 100644 --- a/style/animation.rs +++ b/style/animation.rs @@ -25,7 +25,7 @@ use crate::style_resolver::StyleResolverForElement; use crate::stylesheets::keyframes_rule::{KeyframesAnimation, KeyframesStep, KeyframesStepValue}; use crate::stylesheets::layer_rule::LayerOrder; use crate::values::animated::{Animate, Procedure}; -use crate::values::computed::{Time, TimingFunction}; +use crate::values::computed::TimingFunction; use crate::values::generics::easing::BeforeFlag; use crate::values::specified::TransitionBehavior; use crate::Atom; @@ -57,31 +57,6 @@ impl PropertyAnimation { self.from.id() } - fn from_property_declaration( - property_declaration: &PropertyDeclarationId, - timing_function: TimingFunction, - duration: Time, - old_style: &ComputedValues, - new_style: &ComputedValues, - ) -> Option { - // FIXME(emilio): Handle the case where old_style and new_style's writing mode differ. - let property_declaration = property_declaration.to_physical(new_style.writing_mode); - let from = AnimationValue::from_computed_values(property_declaration, old_style)?; - let to = AnimationValue::from_computed_values(property_declaration, new_style)?; - let duration = duration.seconds() as f64; - - if from == to || duration == 0.0 { - return None; - } - - Some(PropertyAnimation { - from, - to, - timing_function, - duration, - }) - } - /// The output of the timing function given the progress ration of this animation. fn timing_function_output(&self, progress: f64) -> f64 { let epsilon = 1. / (200. * self.duration); @@ -135,6 +110,11 @@ impl AnimationState { } } +enum IgnoreTransitions { + Canceled, + CanceledAndFinished, +} + /// This structure represents a keyframes animation current iteration state. /// /// If the iteration count is infinite, there's no other state, otherwise we @@ -762,6 +742,31 @@ pub struct Transition { } impl Transition { + fn new( + start_time: f64, + delay: f64, + duration: f64, + from: AnimationValue, + to: AnimationValue, + timing_function: &TimingFunction, + ) -> Self { + let property_animation = PropertyAnimation { + from: from.clone(), + to, + timing_function: timing_function.clone(), + duration, + }; + Self { + start_time, + delay, + property_animation, + state: AnimationState::Pending, + is_new: true, + reversing_adjusted_start_value: from, + reversing_shortening_factor: 1.0, + } + } + fn update_for_possibly_reversed_transition( &mut self, replaced_transition: &Transition, @@ -894,7 +899,7 @@ impl ElementAnimationSet { } } - if let Some(map) = self.get_value_map_for_active_transitions(now) { + if let Some(map) = self.get_value_map_for_transitions(now, IgnoreTransitions::Canceled) { for value in map.values() { value.set_in_style_for_servo(mutable_style); } @@ -1010,9 +1015,22 @@ impl ElementAnimationSet { self, ); - // Cancel any non-finished transitions that have properties which no longer transition. + // Cancel any non-finished transitions that have properties which no + // longer transition. + // + // Step 3 in https://drafts.csswg.org/css-transitions/#starting: + // > If the element has a running transition or completed transition for + // > the property, and there is not a matching transition-property value, + // > then implementations must cancel the running transition or remove the + // > completed transition from the set of completed transitions. + // + // TODO: This is happening here as opposed to in + // `start_transition_if_applicable` as an optimization, but maybe this + // code should be reworked to be more like the specification. for transition in self.transitions.iter_mut() { - if transition.state == AnimationState::Finished { + if transition.state == AnimationState::Finished + || transition.state == AnimationState::Canceled + { continue; } if transitioning_properties.contains(transition.property_animation.property_id()) { @@ -1032,86 +1050,192 @@ impl ElementAnimationSet { new_style: &Arc, ) { let style = new_style.get_ui(); - let allow_discrete = style.transition_behavior_mod(index) == TransitionBehavior::AllowDiscrete; + let allow_discrete = + style.transition_behavior_mod(index) == TransitionBehavior::AllowDiscrete; - if !property_declaration_id.is_animatable() - || (!allow_discrete && property_declaration_id.is_discrete_animatable()) - { + // FIXME(emilio): Handle the case where old_style and new_style's writing mode differ. + let Some(from) = AnimationValue::from_computed_values(*property_declaration_id, old_style) + else { return; - } + }; + let Some(to) = AnimationValue::from_computed_values(*property_declaration_id, new_style) + else { + return; + }; let timing_function = style.transition_timing_function_mod(index); - let duration = style.transition_duration_mod(index); + let duration = style.transition_duration_mod(index).seconds() as f64; let delay = style.transition_delay_mod(index).seconds() as f64; let now = context.current_time_for_animations; + let transitionable = property_declaration_id.is_animatable() + && (allow_discrete || !property_declaration_id.is_discrete_animatable()) + && (allow_discrete || from.interpolable_with(&to)); - // Only start a new transition if the style actually changes between - // the old style and the new style. - let property_animation = match PropertyAnimation::from_property_declaration( - property_declaration_id, - timing_function, - duration, - old_style, - new_style, - ) { - Some(property_animation) => property_animation, - None => return, - }; + let mut existing_transition = self.transitions.iter_mut().find(|transition| { + transition.property_animation.property_id() == *property_declaration_id + }); - // A property may have an animation type different than 'discrete', but still - // not be able to interpolate some values. In that case we would fall back to - // discrete interpolation, so we need to abort if `transition-behavior` doesn't - // allow discrete transitions. - if !allow_discrete && !property_animation.from.interpolable_with(&property_animation.to) { + // Step 1: + // > If all of the following are true: + // > - the element does not have a running transition for the property, + // > - the before-change style is different from the after-change style + // > for that property, and the values for the property are + // > transitionable, + // > - the element does not have a completed transition for the property + // > or the end value of the completed transition is different from the + // > after-change style for the property, + // > - there is a matching transition-property value, and + // > - the combined duration is greater than 0s, + // + // This function is only run if there is a matching transition-property + // value, so that check is skipped here. + let has_running_transition = existing_transition.as_ref().is_some_and(|transition| { + transition.state != AnimationState::Finished + && transition.state != AnimationState::Canceled + }); + let no_completed_transition_or_end_values_differ = existing_transition.as_ref().is_none_or(|transition| { + transition.state != AnimationState::Finished || transition.property_animation.to != to + }); + if !has_running_transition && + from != to && transitionable && + no_completed_transition_or_end_values_differ && + (duration + delay > 0.0) { + // > then implementations must remove the completed transition (if + // > present) from the set of completed transitions and start a + // > transition whose: + // > + // > - start time is the time of the style change event plus the matching transition delay, + // > - end time is the start time plus the matching transition duration, + // > - start value is the value of the transitioning property in the before-change style, + // > - end value is the value of the transitioning property in the after-change style, + // > - reversing-adjusted start value is the same as the start value, and + // > - reversing shortening factor is 1. + self.transitions.push(Transition::new( + now + delay, /* start_time */ + delay, + duration, + from, + to, + &timing_function, + )); + self.dirty = true; return; } - // Per [1], don't trigger a new transition if the end state for that - // transition is the same as that of a transition that's running or - // completed. We don't take into account any canceled animations. - // [1]: https://drafts.csswg.org/css-transitions/#starting - if self - .transitions - .iter() - .filter(|transition| transition.state != AnimationState::Canceled) - .any(|transition| transition.property_animation.to == property_animation.to) - { + // > Step 2: Otherwise, if the element has a completed transition for the + // > property and the end value of the completed transition is different + // > from the after-change style for the property, then implementations + // > must remove the completed transition from the set of completed + // > transitions. + // + // All completed transitions will be cleared from the `AnimationSet` in + // `process_animations_for_style in `matching.rs`. + + // > Step 3: If the element has a running transition or completed + // > transition for the property, and there is not a matching + // > transition-property value, then implementations must cancel the + // > running transition or remove the completed transition from the set + // > of completed transitions. + // + // - All completed transitions will be cleared cleared from the `AnimationSet` in + // `process_animations_for_style in `matching.rs`. + // - Transitions for properties that don't have a matching transition-property + // value will be canceled in `Self::update_transitions_for_new_style`. In addition, + // this method is only called for properties that do ahave a matching + // transition-property value. + + let Some(existing_transition) = existing_transition.as_mut() else { return; - } - - // We are going to start a new transition, but we might have to update - // it if we are replacing a reversed transition. - let reversing_adjusted_start_value = property_animation.from.clone(); - let mut new_transition = Transition { - start_time: now + delay, - delay, - property_animation, - state: AnimationState::Pending, - is_new: true, - reversing_adjusted_start_value, - reversing_shortening_factor: 1.0, }; - if let Some(old_transition) = self - .transitions - .iter_mut() - .filter(|transition| transition.state == AnimationState::Running) - .find(|transition| { - transition.property_animation.property_id() == *property_declaration_id - }) - { - // We always cancel any running transitions for the same property. - old_transition.state = AnimationState::Canceled; - new_transition.update_for_possibly_reversed_transition(old_transition, delay, now); - } + // > Step 4: If the element has a running transition for the property, + // > there is a matching transition-property value, and the end value of + // > the running transition is not equal to the value of the property in + // > the after-change style, then: + if has_running_transition && existing_transition.property_animation.to != to { + // > Step 4.1: If the current value of the property in the running transition is + // > equal to the value of the property in the after-change style, or + // > if these two values are not transitionable, then implementations + // > must cancel the running transition. + let current_value = existing_transition.calculate_value(now); + let transitionable_from_current_value = + transitionable && (allow_discrete || current_value.interpolable_with(&to)); + if current_value == to || !transitionable_from_current_value { + existing_transition.state = AnimationState::Canceled; + self.dirty = true; + return; + } + + // > Step 4.2: Otherwise, if the combined duration is less than or + // > equal to 0s, or if the current value of the property in the + // > running transition is not transitionable with the value of the + // > property in the after-change style, then implementations must + // > cancel the running transition. + if duration + delay <= 0.0 { + existing_transition.state = AnimationState::Canceled; + self.dirty = true; + return; + } + + // > Step 4.3: Otherwise, if the reversing-adjusted start value of the + // > running transition is the same as the value of the property in + // > the after-change style (see the section on reversing of + // > transitions for why these case exists), implementations must + // > cancel the running transition and start a new transition whose: + if existing_transition.reversing_adjusted_start_value == to { + existing_transition.state = AnimationState::Canceled; + + let mut transition = Transition::new( + now + delay, /* start_time */ + delay, + duration, + from, + to, + &timing_function, + ); + + // This function takes care of applying all of the modifications to the transition + // after "whose:" above. + transition.update_for_possibly_reversed_transition( + &existing_transition, + delay, + now, + ); + + self.transitions.push(transition); + self.dirty = true; + return; + } - self.transitions.push(new_transition); - self.dirty = true; + // > Step 4.4: Otherwise, implementations must cancel the running + // > transition and start a new transition whose: + // > - start time is the time of the style change event plus the matching transition delay, + // > - end time is the start time plus the matching transition duration, + // > - start value is the current value of the property in the running transition, + // > - end value is the value of the property in the after-change style, + // > - reversing-adjusted start value is the same as the start value, and + // > - reversing shortening factor is 1. + existing_transition.state = AnimationState::Canceled; + self.transitions.push(Transition::new( + now + delay, /* start_time */ + delay, + duration, + current_value, + to, + &timing_function, + )); + self.dirty = true; + } } /// Generate a `AnimationValueMap` for this `ElementAnimationSet`'s - /// active transitions at the given time value. - pub fn get_value_map_for_active_transitions(&self, now: f64) -> Option { + /// transitions, ignoring those specified by the `ignore_transitions` + /// argument. + fn get_value_map_for_transitions( + &self, + now: f64, + ignore_transitions: IgnoreTransitions, + ) -> Option { if !self.has_active_transition() { return None; } @@ -1119,9 +1243,21 @@ impl ElementAnimationSet { let mut map = AnimationValueMap::with_capacity_and_hasher(self.transitions.len(), Default::default()); for transition in &self.transitions { - if transition.state == AnimationState::Canceled { - continue; + match ignore_transitions { + IgnoreTransitions::Canceled => { + if transition.state == AnimationState::Canceled { + continue; + } + }, + IgnoreTransitions::CanceledAndFinished => { + if transition.state == AnimationState::Canceled + || transition.state == AnimationState::Finished + { + continue; + } + }, } + let value = transition.calculate_value(now); map.insert(value.id().to_owned(), value); } @@ -1235,7 +1371,9 @@ impl DocumentAnimationSet { self.sets .read() .get(key) - .and_then(|set| set.get_value_map_for_active_transitions(time)) + .and_then(|set| { + set.get_value_map_for_transitions(time, IgnoreTransitions::CanceledAndFinished) + }) .map(|map| { let block = PropertyDeclarationBlock::from_animation_value_map(&map); Arc::new(shared_lock.wrap(block)) @@ -1260,10 +1398,12 @@ impl DocumentAnimationSet { let block = PropertyDeclarationBlock::from_animation_value_map(&map); Arc::new(shared_lock.wrap(block)) }); - let transitions = set.get_value_map_for_active_transitions(time).map(|map| { - let block = PropertyDeclarationBlock::from_animation_value_map(&map); - Arc::new(shared_lock.wrap(block)) - }); + let transitions = set + .get_value_map_for_transitions(time, IgnoreTransitions::CanceledAndFinished) + .map(|map| { + let block = PropertyDeclarationBlock::from_animation_value_map(&map); + Arc::new(shared_lock.wrap(block)) + }); AnimationDeclarations { animations, transitions, @@ -1286,8 +1426,25 @@ pub fn start_transitions_if_applicable( new_style: &Arc, animation_state: &mut ElementAnimationSet, ) -> PropertyDeclarationIdSet { + // See + // "If a property is specified multiple times in the value of transition-property + // (either on its own, via a shorthand that contains it, or via the all value), + // then the transition that starts uses the duration, delay, and timing function + // at the index corresponding to the last item in the value of transition-property + // that calls for animating that property." + // See Example 3 of + // + // Reversing the transition order here means that transitions defined later in the list + // have preference, in accordance with the specification. + // + // TODO: It would be better to be able to do this without having to allocate an array. + // We should restructure the code or make `transition_properties()` return a reversible + // iterator in order to avoid the allocation. + let mut transition_properties = new_style.transition_properties().collect::>(); + transition_properties.reverse(); + let mut properties_that_transition = PropertyDeclarationIdSet::default(); - for transition in new_style.transition_properties() { + for transition in transition_properties { let physical_property = transition .property .as_borrowed() diff --git a/style/attr.rs b/style/attr.rs index 2121fe8619..74e01c326c 100644 --- a/style/attr.rs +++ b/style/attr.rs @@ -6,6 +6,7 @@ //! //! [attr]: https://dom.spec.whatwg.org/#interface-attr +use crate::shadow_parts::ShadowParts; use crate::color::{AbsoluteColor, parsing::parse_color_keyword}; use crate::properties::PropertyDeclarationBlock; use crate::shared_lock::Locked; @@ -72,6 +73,9 @@ pub enum AttrValue { String, #[ignore_malloc_size_of = "Arc"] Arc>, ), + + /// The value of an `exportparts` attribute. + ShadowParts(String, ShadowParts), } /// Shared implementation to parse an integer according to @@ -266,6 +270,11 @@ impl AttrValue { AttrValue::Dimension(string, parsed) } + pub fn from_shadow_parts(string: String) -> AttrValue { + let shadow_parts = ShadowParts::parse(&string); + AttrValue::ShadowParts(string, shadow_parts) + } + /// Assumes the `AttrValue` is a `TokenList` and returns its tokens /// /// ## Panics @@ -358,6 +367,22 @@ impl AttrValue { } } + /// Return the AttrValue as it's shadow-part representation. + /// + /// This corresponds to attribute values returned as `AttrValue::ShadowParts(_)` + /// by `VirtualMethods::parse_plain_attribute()`. + /// + /// ## Panics + /// + /// Panics if the `AttrValue` is not a shadow-part. + pub fn as_shadow_parts(&self) -> &ShadowParts { + if let AttrValue::ShadowParts(_, value) = &self { + value + } else { + panic!("Not a shadowpart attribute"); + } + } + pub fn eval_selector(&self, selector: &AttrSelectorOperation<&AtomString>) -> bool { // FIXME(SimonSapin) this can be more efficient by matching on `(self, selector)` variants // and doing Atom comparisons instead of string comparisons where possible, @@ -380,6 +405,7 @@ impl ::std::ops::Deref for AttrValue { AttrValue::Int(ref value, _) | AttrValue::ResolvedUrl(ref value, _) | AttrValue::Declaration(ref value, _) | + AttrValue::ShadowParts(ref value, _) | AttrValue::Dimension(ref value, _) => &value, AttrValue::Atom(ref value) => &value, } 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/context.rs b/style/context.rs index e151967ca4..b87ef73b65 100644 --- a/style/context.rs +++ b/style/context.rs @@ -9,7 +9,7 @@ use crate::animation::DocumentAnimationSet; use crate::bloom::StyleBloom; use crate::computed_value_flags::ComputedValueFlags; use crate::data::{EagerPseudoStyles, ElementData}; -use crate::dom::{SendElement, TElement}; +use crate::dom::{SendElement, TElement, TRestyleDamage}; #[cfg(feature = "gecko")] use crate::gecko_bindings::structs; use crate::parallel::{STACK_SAFETY_MARGIN_KB, STYLE_THREAD_STACK_SIZE_KB}; @@ -263,7 +263,7 @@ pub struct ElementCascadeInputs { impl ElementCascadeInputs { /// Construct inputs from previous cascade results, if any. #[inline] - pub fn new_from_element_data(data: &ElementData) -> Self { + pub fn new_from_element_data(data: &ElementData) -> Self { debug_assert!(data.has_styles()); ElementCascadeInputs { primary: CascadeInputs::new_from_style(data.styles.primary()), @@ -450,7 +450,7 @@ impl SequentialTask { /// Executes this task. pub fn execute(self) { use self::SequentialTask::*; - debug_assert_eq!(thread_state::get(), ThreadState::LAYOUT); + debug_assert!(thread_state::get().contains(ThreadState::LAYOUT)); match self { Unused(_) => unreachable!(), #[cfg(feature = "gecko")] @@ -511,7 +511,7 @@ where E: TElement, { fn drop(&mut self) { - debug_assert_eq!(thread_state::get(), ThreadState::LAYOUT); + debug_assert!(thread_state::get().contains(ThreadState::LAYOUT)); for task in self.0.drain(..) { task.execute() } diff --git a/style/data.rs b/style/data.rs index c4a3c8252e..bd5f62d98f 100644 --- a/style/data.rs +++ b/style/data.rs @@ -6,11 +6,11 @@ use crate::computed_value_flags::ComputedValueFlags; use crate::context::{SharedStyleContext, StackLimitChecker}; -use crate::dom::TElement; +use crate::dom::{TElement, TRestyleDamage}; use crate::invalidation::element::invalidator::InvalidationResult; use crate::invalidation::element::restyle_hints::RestyleHint; use crate::properties::ComputedValues; -use crate::selector_parser::{PseudoElement, RestyleDamage, EAGER_PSEUDO_COUNT}; +use crate::selector_parser::{PseudoElement, EAGER_PSEUDO_COUNT}; use crate::style_resolver::{PrimaryStyle, ResolvedElementStyles, ResolvedStyle}; #[cfg(feature = "gecko")] use malloc_size_of::MallocSizeOfOps; @@ -249,7 +249,7 @@ impl fmt::Debug for ElementStyles { /// inside of layout data, which itself hangs directly off the Element. In /// both cases, it is wrapped inside an AtomicRefCell to ensure thread safety. #[derive(Debug, Default)] -pub struct ElementData { +pub struct ElementData { /// The styles for the element and its pseudo-elements. pub styles: ElementStyles, @@ -266,7 +266,14 @@ pub struct ElementData { } // There's one of these per rendered elements so it better be small. -size_of_test!(ElementData, 24); +// mod element_data_test { +// use crate::dom::TRestyleDamage; +// use super::ElementData; +// #[derive(Debug, Copy, Clone, Default)] +// struct SizeOfRestyleDamage(u32); +// impl TRestyleDamage for SizeOfRestyleDamage {} +// size_of_test!(ElementData, 24); +// } /// The kind of restyle that a single element should do. #[derive(Debug)] @@ -282,11 +289,11 @@ pub enum RestyleKind { CascadeOnly, } -impl ElementData { +impl ElementData { /// Invalidates style for this element, its descendants, and later siblings, /// based on the snapshot of the element that we took when attributes or /// state changed. - pub fn invalidate_style_if_needed<'a, E: TElement>( + pub fn invalidate_style_if_needed<'a, E: TElement>( &mut self, element: E, shared_context: &SharedStyleContext, @@ -389,8 +396,8 @@ impl ElementData { return None; } - let needs_to_match_self = hint.intersects(RestyleHint::RESTYLE_SELF) || - (hint.intersects(RestyleHint::RESTYLE_SELF_IF_PSEUDO) && style.is_pseudo_style()); + let needs_to_match_self = hint.intersects(RestyleHint::RESTYLE_SELF) + || (hint.intersects(RestyleHint::RESTYLE_SELF_IF_PSEUDO) && style.is_pseudo_style()); if needs_to_match_self { return Some(RestyleKind::MatchAndCascade); } @@ -405,9 +412,9 @@ impl ElementData { )); } - let needs_to_recascade_self = hint.intersects(RestyleHint::RECASCADE_SELF) || - (hint.intersects(RestyleHint::RECASCADE_SELF_IF_INHERIT_RESET_STYLE) && - style + let needs_to_recascade_self = hint.intersects(RestyleHint::RECASCADE_SELF) + || (hint.intersects(RestyleHint::RECASCADE_SELF_IF_INHERIT_RESET_STYLE) + && style .flags .contains(ComputedValueFlags::INHERITS_RESET_STYLE)); if needs_to_recascade_self { @@ -447,9 +454,9 @@ impl ElementData { )); } - let needs_to_recascade_self = hint.intersects(RestyleHint::RECASCADE_SELF) || - (hint.intersects(RestyleHint::RECASCADE_SELF_IF_INHERIT_RESET_STYLE) && - style + let needs_to_recascade_self = hint.intersects(RestyleHint::RECASCADE_SELF) + || (hint.intersects(RestyleHint::RECASCADE_SELF_IF_INHERIT_RESET_STYLE) + && style .flags .contains(ComputedValueFlags::INHERITS_RESET_STYLE)); if needs_to_recascade_self { @@ -471,7 +478,7 @@ impl ElementData { /// Drops restyle flags and damage from the element. #[inline] pub fn clear_restyle_flags_and_damage(&mut self) { - self.damage = RestyleDamage::empty(); + self.damage.clear(); self.flags.remove(ElementDataFlags::WAS_RESTYLED); } @@ -521,8 +528,8 @@ impl ElementData { /// we need for style sharing, the latter does not. pub fn safe_for_cousin_sharing(&self) -> bool { if self.flags.intersects( - ElementDataFlags::TRAVERSED_WITHOUT_STYLING | - ElementDataFlags::PRIMARY_STYLE_REUSED_VIA_RULE_NODE, + ElementDataFlags::TRAVERSED_WITHOUT_STYLING + | ElementDataFlags::PRIMARY_STYLE_REUSED_VIA_RULE_NODE, ) { return false; } diff --git a/style/dom.rs b/style/dom.rs index 1196ba9deb..50e802bb2d 100644 --- a/style/dom.rs +++ b/style/dom.rs @@ -30,7 +30,7 @@ use servo_arc::{Arc, ArcBorrow}; use std::fmt; use std::fmt::Debug; use std::hash::Hash; -use std::ops::Deref; +use std::ops::{BitOrAssign, Deref}; pub use style_traits::dom::OpaqueNode; @@ -383,6 +383,41 @@ pub trait TShadowRoot: Sized + Copy + Clone + Debug + PartialEq { } } +/// Represents restyle damage (whether to repaint, relayout, etc based on changed styles) +/// +/// Stylo is mostly agnostic to this type which is used by the embedder. This trait represents the +/// minimal interface that Stylo does need to the type. +pub trait TRestyleDamage: Copy + Clone + Default + Debug + BitOrAssign { + /// Clear/reset all damage flags + fn clear(&mut self); + /// Whether the damage is empty ("no styles changed") + fn is_empty(&self) -> bool; + /// Mark the element as needing it's (eager) pseudo-elements rebuilt + fn set_rebuild_pseudos(&mut self); +} + +/// Represents the result of comparing an element's old and new style. +#[derive(Debug)] +pub struct StyleDifference { + /// The resulting damage. + pub damage: RestyleDamage, + + /// Whether any styles changed. + pub change: StyleChange, +} + +/// Represents whether or not the style of an element has changed. +#[derive(Clone, Copy, Debug)] +pub enum StyleChange { + /// The style hasn't changed. + Unchanged, + /// The style has changed. + Changed { + /// Whether only reset structs changed. + reset_only: bool, + }, +} + /// The element trait, the main abstraction the style crate acts over. pub trait TElement: Eq + PartialEq + Debug + Hash + Sized + Copy + Clone + SelectorsElement @@ -390,6 +425,9 @@ pub trait TElement: /// The concrete node type. type ConcreteNode: TNode; + /// The type representing restyle damage flags + type RestyleDamage: TRestyleDamage; + /// A concrete children iterator type in order to iterate over the `Node`s. /// /// TODO(emilio): We should eventually replace this with the `impl Trait` @@ -583,7 +621,7 @@ pub trait TElement: /// Returns whether the element's styles are up-to-date after traversal /// (i.e. in post traversal). - fn has_current_styles(&self, data: &ElementData) -> bool { + fn has_current_styles(&self, data: &ElementData) -> bool { if self.has_snapshot() && !self.handled_snapshot() { return false; } @@ -654,8 +692,6 @@ pub trait TElement: /// /// Note that we still need to compute the pseudo-elements before-hand, /// given otherwise we don't know if we need to create an element or not. - /// - /// Servo doesn't have to deal with this. fn implemented_pseudo_element(&self) -> Option { None } @@ -672,7 +708,7 @@ pub trait TElement: /// /// Unsafe because it can race to allocate and leak if not used with /// exclusive access to the element. - unsafe fn ensure_data(&self) -> AtomicRefMut; + unsafe fn ensure_data(&self) -> AtomicRefMut>; /// Clears the element data reference, if any. /// @@ -683,10 +719,10 @@ pub trait TElement: fn has_data(&self) -> bool; /// Immutably borrows the ElementData. - fn borrow_data(&self) -> Option>; + fn borrow_data(&self) -> Option>>; /// Mutably borrows the ElementData. - fn mutate_data(&self) -> Option>; + fn mutate_data(&self) -> Option>>; /// Whether we should skip any root- or item-based display property /// blockification on this element. (This function exists so that Gecko @@ -910,6 +946,22 @@ pub trait TElement: ) -> Option { None } + + /// Given the old and new style of this element, and whether it's a + /// pseudo-element, compute the restyle damage used to determine which + /// kind of layout or painting operations we'll need. + fn compute_style_difference( + &self, + _old: &ComputedValues, + _new: &ComputedValues, + pseudo: Option<&PseudoElement>, + ) -> StyleDifference { + debug_assert!(pseudo.map_or(true, |p| p.is_eager())); + StyleDifference { + damage: Default::default(), + change: StyleChange::Unchanged, + } + } } /// TNode and TElement aren't Send because we want to be careful and explicit diff --git a/style/invalidation/element/relative_selector.rs b/style/invalidation/element/relative_selector.rs index 77e0e25b13..d7755da14c 100644 --- a/style/invalidation/element/relative_selector.rs +++ b/style/invalidation/element/relative_selector.rs @@ -1020,7 +1020,7 @@ pub struct RelativeSelectorOuterInvalidationProcessor<'a, 'b, E: TElement> { /// The current shadow host, if any. pub host: Option, /// Data for the element being invalidated. - pub data: &'a mut ElementData, + pub data: &'a mut ElementData, /// Dependency to be processed. pub dependency: &'b Dependency, /// Matching context to use for invalidation. diff --git a/style/invalidation/element/state_and_attributes.rs b/style/invalidation/element/state_and_attributes.rs index d10d94763c..d019145010 100644 --- a/style/invalidation/element/state_and_attributes.rs +++ b/style/invalidation/element/state_and_attributes.rs @@ -7,7 +7,7 @@ use crate::context::SharedStyleContext; use crate::data::ElementData; -use crate::dom::{TElement, TNode}; +use crate::dom::{TElement, TNode, TRestyleDamage}; use crate::invalidation::element::element_wrapper::{ElementSnapshot, ElementWrapper}; use crate::invalidation::element::invalidation_map::*; use crate::invalidation::element::invalidator::{ @@ -56,7 +56,7 @@ where pub struct StateAndAttrInvalidationProcessor<'a, 'b: 'a, E: TElement> { shared_context: &'a SharedStyleContext<'b>, element: E, - data: &'a mut ElementData, + data: &'a mut ElementData, matching_context: MatchingContext<'a, E::Impl>, traversal_map: SiblingTraversalMap, } @@ -66,7 +66,7 @@ impl<'a, 'b: 'a, E: TElement + 'b> StateAndAttrInvalidationProcessor<'a, 'b, E> pub fn new( shared_context: &'a SharedStyleContext<'b>, element: E, - data: &'a mut ElementData, + data: &'a mut ElementData, selector_caches: &'a mut SelectorCaches, ) -> Self { let matching_context = MatchingContext::new_for_visited( @@ -125,7 +125,7 @@ where /// Whether we should process the descendants of a given element for style /// invalidation. -pub fn should_process_descendants(data: &ElementData) -> bool { +pub fn should_process_descendants(data: &ElementData) -> bool { !data.styles.is_display_none() && !data.hint.contains(RestyleHint::RESTYLE_DESCENDANTS) } diff --git a/style/lib.rs b/style/lib.rs index 0dff7117a9..9eb2b2a1d0 100644 --- a/style/lib.rs +++ b/style/lib.rs @@ -48,8 +48,7 @@ extern crate malloc_size_of; #[macro_use] extern crate malloc_size_of_derive; #[cfg(feature = "servo")] -#[macro_use] -extern crate markup5ever; +extern crate web_atoms; #[allow(unused_extern_crates)] #[macro_use] extern crate matches; @@ -159,13 +158,15 @@ pub use stylo_atoms::Atom; #[cfg(feature = "servo")] #[allow(missing_docs)] -pub type LocalName = crate::values::GenericAtomIdent; +pub type LocalName = crate::values::GenericAtomIdent; #[cfg(feature = "servo")] #[allow(missing_docs)] -pub type Namespace = crate::values::GenericAtomIdent; +pub type Namespace = crate::values::GenericAtomIdent; #[cfg(feature = "servo")] #[allow(missing_docs)] -pub type Prefix = crate::values::GenericAtomIdent; +pub type Prefix = crate::values::GenericAtomIdent; +#[cfg(feature = "servo")] +mod shadow_parts; pub use style_traits::arc_slice::ArcSlice; pub use style_traits::owned_slice::OwnedSlice; diff --git a/style/logical_geometry.rs b/style/logical_geometry.rs index 7fb8c22ca1..9ebf8e473a 100644 --- a/style/logical_geometry.rs +++ b/style/logical_geometry.rs @@ -10,7 +10,6 @@ use euclid::num::Zero; use std::cmp::{max, min}; use std::fmt::{self, Debug, Error, Formatter}; use std::ops::{Add, Sub}; -use unicode_bidi as bidi; pub enum BlockFlowDirection { TopToBottom, @@ -357,18 +356,6 @@ impl WritingMode { } } - #[inline] - /// The default bidirectional embedding level for this writing mode. - /// - /// Returns bidi level 0 if the mode is LTR, or 1 otherwise. - pub fn to_bidi_level(&self) -> bidi::Level { - if self.is_bidi_ltr() { - bidi::Level::ltr() - } else { - bidi::Level::rtl() - } - } - #[inline] /// Is the text layout vertical? pub fn is_text_vertical(&self) -> bool { diff --git a/style/macros.rs b/style/macros.rs index eac54b9080..fdf3c30290 100644 --- a/style/macros.rs +++ b/style/macros.rs @@ -41,17 +41,17 @@ macro_rules! try_match_ident_ignore_ascii_case { #[cfg(feature = "servo")] macro_rules! local_name { ($s:tt) => { - $crate::values::GenericAtomIdent(markup5ever::local_name!($s)) + $crate::values::GenericAtomIdent(web_atoms::local_name!($s)) }; } #[cfg(feature = "servo")] macro_rules! ns { () => { - $crate::values::GenericAtomIdent(markup5ever::ns!()) + $crate::values::GenericAtomIdent(web_atoms::ns!()) }; ($s:tt) => { - $crate::values::GenericAtomIdent(markup5ever::ns!($s)) + $crate::values::GenericAtomIdent(web_atoms::ns!($s)) }; } diff --git a/style/matching.rs b/style/matching.rs index 0a9fe9fafb..ceeda7adc4 100644 --- a/style/matching.rs +++ b/style/matching.rs @@ -13,7 +13,7 @@ use crate::context::CascadeInputs; use crate::context::{ElementCascadeInputs, QuirksMode}; use crate::context::{SharedStyleContext, StyleContext}; use crate::data::{ElementData, ElementStyles}; -use crate::dom::TElement; +use crate::dom::{TElement, TRestyleDamage, StyleChange}; #[cfg(feature = "servo")] use crate::dom::TNode; use crate::invalidation::element::restyle_hints::RestyleHint; @@ -21,7 +21,7 @@ use crate::properties::longhands::display::computed_value::T as Display; use crate::properties::ComputedValues; use crate::properties::PropertyDeclarationBlock; use crate::rule_tree::{CascadeLevel, StrongRuleNode}; -use crate::selector_parser::{PseudoElement, RestyleDamage}; +use crate::selector_parser::{PseudoElement}; use crate::shared_lock::Locked; use crate::style_resolver::StyleResolverForElement; use crate::style_resolver::{PseudoElementResolution, ResolvedElementStyles}; @@ -30,28 +30,6 @@ use crate::stylist::RuleInclusion; use crate::traversal_flags::TraversalFlags; use servo_arc::{Arc, ArcBorrow}; -/// Represents the result of comparing an element's old and new style. -#[derive(Debug)] -pub struct StyleDifference { - /// The resulting damage. - pub damage: RestyleDamage, - - /// Whether any styles changed. - pub change: StyleChange, -} - -/// Represents whether or not the style of an element has changed. -#[derive(Clone, Copy, Debug)] -pub enum StyleChange { - /// The style hasn't changed. - Unchanged, - /// The style has changed. - Changed { - /// Whether only reset structs changed. - reset_only: bool, - }, -} - /// Whether or not newly computed values for an element need to be cascaded to /// children (or children might need to be re-matched, e.g., for container /// queries). @@ -605,7 +583,7 @@ trait PrivateMatchMethods: TElement { fn process_animations_for_pseudo( &self, context: &mut StyleContext, - old_styles: &mut ElementStyles, + old_styles: &ElementStyles, new_resolved_styles: &mut ResolvedElementStyles, pseudo_element: PseudoElement, ) { @@ -613,7 +591,7 @@ trait PrivateMatchMethods: TElement { use crate::dom::TDocument; let key = AnimationSetKey::new_for_pseudo(self.as_node().opaque(), pseudo_element.clone()); - let mut style = match new_resolved_styles.pseudos.get(&pseudo_element) { + let style = match new_resolved_styles.pseudos.get(&pseudo_element) { Some(style) => Arc::clone(style), None => { context @@ -624,11 +602,11 @@ trait PrivateMatchMethods: TElement { }, }; - let mut old_style = old_styles.pseudos.get(&pseudo_element).cloned(); + let old_style = old_styles.pseudos.get(&pseudo_element).cloned(); self.process_animations_for_style( context, - &mut old_style, - &mut style, + &old_style, + &style, Some(pseudo_element.clone()), ); @@ -687,8 +665,8 @@ trait PrivateMatchMethods: TElement { fn process_animations_for_style( &self, context: &mut StyleContext, - old_values: &mut Option>, - new_values: &mut Arc, + old_values: &Option>, + new_values: &Arc, pseudo_element: Option, ) -> bool { use crate::animation::{AnimationSetKey, AnimationState}; @@ -749,14 +727,16 @@ trait PrivateMatchMethods: TElement { after_change_style.as_ref().unwrap_or(new_values), ); - // We clear away any finished transitions, but retain animations, because they - // might still be used for proper calculation of `animation-fill-mode`. This - // should change the computed values in the style, so we don't need to mark - // this set as dirty. + // This should change the computed values in the style, so we don't need + // to mark this set as dirty. animation_set .transitions .retain(|transition| transition.state != AnimationState::Finished); + animation_set + .animations + .retain(|animation| animation.state != AnimationState::Finished); + // If the ElementAnimationSet is empty, and don't store it in order to // save memory and to avoid extra processing later. let changed_animations = animation_set.dirty; @@ -776,7 +756,7 @@ trait PrivateMatchMethods: TElement { fn accumulate_damage_for( &self, shared_context: &SharedStyleContext, - damage: &mut RestyleDamage, + damage: &mut Self::RestyleDamage, old_values: &ComputedValues, new_values: &ComputedValues, pseudo: Option<&PseudoElement>, @@ -935,7 +915,7 @@ pub trait MatchMethods: TElement { fn finish_restyle( &self, context: &mut StyleContext, - data: &mut ElementData, + data: &mut ElementData, mut new_styles: ResolvedElementStyles, important_rules_changed: bool, ) -> ChildRestyleRequirement { @@ -1118,7 +1098,7 @@ pub trait MatchMethods: TElement { let old_pseudo_should_exist = old.as_ref().map_or(false, |s| pseudo.should_exist(s)); if new_pseudo_should_exist != old_pseudo_should_exist { - data.damage |= RestyleDamage::reconstruct(); + data.damage.set_rebuild_pseudos(); return restyle_requirement; } }, @@ -1153,19 +1133,6 @@ pub trait MatchMethods: TElement { ); result } - - /// Given the old and new style of this element, and whether it's a - /// pseudo-element, compute the restyle damage used to determine which - /// kind of layout or painting operations we'll need. - fn compute_style_difference( - &self, - old_values: &ComputedValues, - new_values: &ComputedValues, - pseudo: Option<&PseudoElement>, - ) -> StyleDifference { - debug_assert!(pseudo.map_or(true, |p| p.is_eager())); - RestyleDamage::compute_style_difference(old_values, new_values) - } } impl MatchMethods for E {} diff --git a/style/properties/build.py b/style/properties/build.py index 7e9224a1ae..a2f79c9b3c 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 @@ -113,7 +115,7 @@ def write(directory, filename, content): if not os.path.exists(directory): os.makedirs(directory) full_path = os.path.join(directory, filename) - open(full_path, "w", encoding="utf-8").write(content) + open(full_path, "w", encoding="utf-8", newline="").write(content) python_addr = RE_PYTHON_ADDR.search(content) if python_addr: diff --git a/style/properties/data.py b/style/properties/data.py index 3e769c3ff2..0e87b61c30 100644 --- a/style/properties/data.py +++ b/style/properties/data.py @@ -375,7 +375,7 @@ def __init__( ignored_when_colors_disabled=False, simple_vector_bindings=False, vector=False, - servo_restyle_damage="repaint", + servo_restyle_damage="rebuild_box", affects=None, ): Property.__init__( diff --git a/style/properties/longhands/background.mako.rs b/style/properties/longhands/background.mako.rs index 229b9e4e18..c5261110a0 100644 --- a/style/properties/longhands/background.mako.rs +++ b/style/properties/longhands/background.mako.rs @@ -14,6 +14,7 @@ ${helpers.predefined_type( ignored_when_colors_disabled=True, allow_quirks="Yes", flags="CAN_ANIMATE_ON_COMPOSITOR", + servo_restyle_damage="repaint", affects="paint", )} @@ -27,6 +28,7 @@ ${helpers.predefined_type( vector="True", animation_type="discrete", ignored_when_colors_disabled="True", + servo_restyle_damage="repaint", affects="paint", )} @@ -40,6 +42,7 @@ ${helpers.predefined_type( spec="https://drafts.csswg.org/css-backgrounds-4/#propdef-background-position-" + axis, vector=True, vector_animation_type="repeatable_list", + servo_restyle_damage="repaint", affects="paint", )} % endfor @@ -53,6 +56,7 @@ ${helpers.predefined_type( animation_type="discrete", vector=True, spec="https://drafts.csswg.org/css-backgrounds/#the-background-repeat", + servo_restyle_damage="repaint", affects="paint", )} @@ -64,6 +68,7 @@ ${helpers.single_keyword( gecko_enum_prefix="StyleImageLayerAttachment", spec="https://drafts.csswg.org/css-backgrounds/#the-background-attachment", animation_type="discrete", + servo_restyle_damage="repaint", affects="paint", )} @@ -77,6 +82,7 @@ ${helpers.single_keyword( gecko_inexhaustive=True, spec="https://drafts.csswg.org/css-backgrounds/#the-background-clip", animation_type="discrete", + servo_restyle_damage="repaint", affects="paint", )} @@ -89,6 +95,7 @@ ${helpers.single_keyword( gecko_inexhaustive=True, spec="https://drafts.csswg.org/css-backgrounds/#the-background-origin", animation_type="discrete", + servo_restyle_damage="repaint", affects="paint", )} @@ -102,6 +109,7 @@ ${helpers.predefined_type( vector=True, vector_animation_type="repeatable_list", extra_prefixes="webkit", + servo_restyle_damage="repaint", affects="paint", )} diff --git a/style/properties/longhands/border.mako.rs b/style/properties/longhands/border.mako.rs index 5fd0e6573b..a956caf3d3 100644 --- a/style/properties/longhands/border.mako.rs +++ b/style/properties/longhands/border.mako.rs @@ -52,7 +52,7 @@ logical=is_logical, logical_group="border-width", allow_quirks="No" if is_logical else "Yes", - servo_restyle_damage="reflow rebuild_and_reflow_inline", + servo_restyle_damage="rebuild_box", affects="layout", )} % endfor @@ -112,8 +112,8 @@ ${helpers.predefined_type( spec="https://drafts.csswg.org/css-backgrounds/#the-background-image", vector=False, animation_type="discrete", - boxed=engine == "servo", ignored_when_colors_disabled=True, + servo_restyle_damage="repaint", affects="paint", )} @@ -125,6 +125,7 @@ ${helpers.predefined_type( initial_specified_value="generics::rect::Rect::all(specified::NonNegativeLengthOrNumber::zero())", spec="https://drafts.csswg.org/css-backgrounds/#border-image-outset", boxed=True, + servo_restyle_damage="repaint", affects="paint", )} @@ -136,6 +137,7 @@ ${helpers.predefined_type( initial_specified_value="specified::BorderImageRepeat::stretch()", animation_type="discrete", spec="https://drafts.csswg.org/css-backgrounds/#the-border-image-repeat", + servo_restyle_damage="repaint", affects="paint", )} @@ -147,6 +149,7 @@ ${helpers.predefined_type( initial_specified_value="specified::BorderImageWidth::all(specified::BorderImageSideWidth::one())", spec="https://drafts.csswg.org/css-backgrounds/#border-image-width", boxed=True, + servo_restyle_damage="repaint", affects="paint", )} @@ -158,5 +161,6 @@ ${helpers.predefined_type( initial_specified_value="specified::BorderImageSlice::hundred_percent()", spec="https://drafts.csswg.org/css-backgrounds/#border-image-slice", boxed=True, + servo_restyle_damage="repaint", affects="paint", )} diff --git a/style/properties/longhands/box.mako.rs b/style/properties/longhands/box.mako.rs index 979e5bf950..82b6cb2173 100644 --- a/style/properties/longhands/box.mako.rs +++ b/style/properties/longhands/box.mako.rs @@ -13,7 +13,7 @@ ${helpers.predefined_type( initial_specified_value="specified::Display::inline()", animation_type="discrete", spec="https://drafts.csswg.org/css-display/#propdef-display", - servo_restyle_damage="rebuild_and_reflow", + servo_restyle_damage="rebuild_box", affects="layout", )} @@ -49,7 +49,7 @@ ${helpers.predefined_type( initial_specified_value="specified::PositionProperty::Static", animation_type="discrete", spec="https://drafts.csswg.org/css-position/#position-property", - servo_restyle_damage="rebuild_and_reflow", + servo_restyle_damage="rebuild_box", affects="layout", )} @@ -86,7 +86,7 @@ ${helpers.predefined_type( engines="gecko servo", animation_type="discrete", spec="https://drafts.csswg.org/css-box/#propdef-float", - servo_restyle_damage="rebuild_and_reflow", + servo_restyle_damage="rebuild_box", affects="layout", )} @@ -97,7 +97,7 @@ ${helpers.predefined_type( engines="gecko servo", animation_type="discrete", spec="https://drafts.csswg.org/css2/#propdef-clear", - servo_restyle_damage="rebuild_and_reflow", + servo_restyle_damage="rebuild_box", affects="layout", )} @@ -107,7 +107,7 @@ ${helpers.predefined_type( "computed::VerticalAlign::baseline()", engines="gecko servo", spec="https://www.w3.org/TR/CSS2/visudet.html#propdef-vertical-align", - servo_restyle_damage = "reflow", + servo_restyle_damage = "rebuild_box", affects="layout", )} @@ -118,7 +118,7 @@ ${helpers.predefined_type( engines="gecko servo", animation_type="discrete", spec="https://drafts.csswg.org/css-inline-3/#baseline-source", - servo_restyle_damage = "reflow", + servo_restyle_damage = "rebuild_box", affects="layout", )} @@ -161,7 +161,7 @@ ${helpers.single_keyword( logical=logical, animation_type="discrete", spec="https://drafts.csswg.org/css-overflow-3/#propdef-{}".format(full_name), - servo_restyle_damage = "reflow", + servo_restyle_damage = "rebuild_box", affects="layout", )} % endfor @@ -188,7 +188,7 @@ ${helpers.predefined_type( extra_prefixes=transform_extra_prefixes, flags="CAN_ANIMATE_ON_COMPOSITOR", spec="https://drafts.csswg.org/css-transforms/#propdef-transform", - servo_restyle_damage="reflow_out_of_flow", + servo_restyle_damage="recalculate_overflow", affects="overflow", )} @@ -200,7 +200,7 @@ ${helpers.predefined_type( boxed=True, flags="CAN_ANIMATE_ON_COMPOSITOR", spec="https://drafts.csswg.org/css-transforms-2/#individual-transforms", - servo_restyle_damage = "reflow_out_of_flow", + servo_restyle_damage = "recalculate_overflow", affects="overflow", )} @@ -212,7 +212,7 @@ ${helpers.predefined_type( boxed=True, flags="CAN_ANIMATE_ON_COMPOSITOR", spec="https://drafts.csswg.org/css-transforms-2/#individual-transforms", - servo_restyle_damage = "reflow_out_of_flow", + servo_restyle_damage = "recalculate_overflow", affects="overflow", )} @@ -224,7 +224,7 @@ ${helpers.predefined_type( boxed=True, flags="CAN_ANIMATE_ON_COMPOSITOR", spec="https://drafts.csswg.org/css-transforms-2/#individual-transforms", - servo_restyle_damage="reflow_out_of_flow", + servo_restyle_damage="recalculate_overflow", affects="overflow", )} @@ -237,7 +237,7 @@ ${helpers.predefined_type( servo_pref="layout.unimplemented", flags="CAN_ANIMATE_ON_COMPOSITOR", spec="https://drafts.fxtf.org/motion-1/#offset-path-property", - servo_restyle_damage="reflow_out_of_flow", + servo_restyle_damage="rebuild_box", affects="overflow", )} @@ -249,7 +249,7 @@ ${helpers.predefined_type( engines="gecko", flags="CAN_ANIMATE_ON_COMPOSITOR", spec="https://drafts.fxtf.org/motion-1/#offset-distance-property", - servo_restyle_damage="reflow_out_of_flow", + servo_restyle_damage="rebuild_box", affects="overflow", )} @@ -261,7 +261,7 @@ ${helpers.predefined_type( engines="gecko", flags="CAN_ANIMATE_ON_COMPOSITOR", spec="https://drafts.fxtf.org/motion-1/#offset-rotate-property", - servo_restyle_damage="reflow_out_of_flow", + servo_restyle_damage="rebuild_box", affects="overflow", )} @@ -273,7 +273,7 @@ ${helpers.predefined_type( engines="gecko", flags="CAN_ANIMATE_ON_COMPOSITOR", spec="https://drafts.fxtf.org/motion-1/#offset-anchor-property", - servo_restyle_damage="reflow_out_of_flow", + servo_restyle_damage="rebuild_box", boxed=True, affects="overflow", )} @@ -286,7 +286,7 @@ ${helpers.predefined_type( engines="gecko", flags="CAN_ANIMATE_ON_COMPOSITOR", spec="https://drafts.fxtf.org/motion-1/#offset-position-property", - servo_restyle_damage="reflow_out_of_flow", + servo_restyle_damage="rebuild_box", boxed=True, affects="overflow", )} @@ -357,6 +357,7 @@ ${helpers.single_keyword( gecko_enum_prefix="StyleIsolation", animation_type="discrete", affects="paint", + servo_restyle_damage="repaint", )} ${helpers.predefined_type( @@ -410,7 +411,7 @@ ${helpers.predefined_type( gecko_ffi_name="mChildPerspective", spec="https://drafts.csswg.org/css-transforms/#perspective", extra_prefixes=transform_extra_prefixes, - servo_restyle_damage = "reflow_out_of_flow", + servo_restyle_damage = "recalculate_overflow", affects="overflow", )} @@ -422,7 +423,7 @@ ${helpers.predefined_type( boxed=True, extra_prefixes=transform_extra_prefixes, spec="https://drafts.csswg.org/css-transforms-2/#perspective-origin-property", - servo_restyle_damage="reflow_out_of_flow", + servo_restyle_damage="recalculate_overflow", affects="overflow", )} @@ -434,6 +435,7 @@ ${helpers.single_keyword( spec="https://drafts.csswg.org/css-transforms/#backface-visibility-property", extra_prefixes=transform_extra_prefixes, animation_type="discrete", + servo_restyle_damage="repaint", affects="paint", )} @@ -455,7 +457,7 @@ ${helpers.predefined_type( spec="https://drafts.csswg.org/css-transforms-2/#transform-style-property", extra_prefixes=transform_extra_prefixes, animation_type="discrete", - servo_restyle_damage = "reflow_out_of_flow", + servo_restyle_damage = "recalculate_overflow", affects="overflow", )} @@ -468,7 +470,7 @@ ${helpers.predefined_type( gecko_ffi_name="mTransformOrigin", boxed=True, spec="https://drafts.csswg.org/css-transforms/#transform-origin-property", - servo_restyle_damage="reflow_out_of_flow", + servo_restyle_damage="recalculate_overflow", affects="overflow", )} diff --git a/style/properties/longhands/column.mako.rs b/style/properties/longhands/column.mako.rs index d1452fcc0f..50a544f715 100644 --- a/style/properties/longhands/column.mako.rs +++ b/style/properties/longhands/column.mako.rs @@ -12,7 +12,7 @@ ${helpers.predefined_type( initial_specified_value="specified::length::NonNegativeLengthOrAuto::auto()", servo_pref="layout.columns.enabled", spec="https://drafts.csswg.org/css-multicol/#propdef-column-width", - servo_restyle_damage="rebuild_and_reflow", + servo_restyle_damage="rebuild_box", affects="layout", )} @@ -24,7 +24,7 @@ ${helpers.predefined_type( initial_specified_value="specified::ColumnCount::Auto", servo_pref="layout.columns.enabled", spec="https://drafts.csswg.org/css-multicol/#propdef-column-count", - servo_restyle_damage="rebuild_and_reflow", + servo_restyle_damage="rebuild_box", affects="layout", )} diff --git a/style/properties/longhands/counters.mako.rs b/style/properties/longhands/counters.mako.rs index 85e479939e..d52f48533d 100644 --- a/style/properties/longhands/counters.mako.rs +++ b/style/properties/longhands/counters.mako.rs @@ -12,7 +12,7 @@ ${helpers.predefined_type( initial_specified_value="specified::Content::normal()", animation_type="discrete", spec="https://drafts.csswg.org/css-content/#propdef-content", - servo_restyle_damage="rebuild_and_reflow", + servo_restyle_damage="rebuild_box", affects="layout", )} @@ -24,7 +24,7 @@ ${helpers.predefined_type( initial_value="Default::default()", animation_type="discrete", spec="https://drafts.csswg.org/css-lists/#propdef-counter-increment", - servo_restyle_damage="rebuild_and_reflow", + servo_restyle_damage="rebuild_box", affects="layout", )} @@ -36,7 +36,7 @@ ${helpers.predefined_type( initial_value="Default::default()", animation_type="discrete", spec="https://drafts.csswg.org/css-lists-3/#propdef-counter-reset", - servo_restyle_damage="rebuild_and_reflow", + servo_restyle_damage="rebuild_box", affects="layout", )} @@ -47,6 +47,6 @@ ${helpers.predefined_type( initial_value="Default::default()", animation_type="discrete", spec="https://drafts.csswg.org/css-lists-3/#propdef-counter-set", - servo_restyle_damage="rebuild_and_reflow", + servo_restyle_damage="rebuild_box", affects="layout", )} diff --git a/style/properties/longhands/effects.mako.rs b/style/properties/longhands/effects.mako.rs index 90d89e9cbc..16622eedb1 100644 --- a/style/properties/longhands/effects.mako.rs +++ b/style/properties/longhands/effects.mako.rs @@ -11,7 +11,7 @@ ${helpers.predefined_type( engines="gecko servo", flags="CAN_ANIMATE_ON_COMPOSITOR", spec="https://drafts.csswg.org/css-color/#transparency", - servo_restyle_damage = "reflow_out_of_flow", + servo_restyle_damage="repaint", affects="paint", )} @@ -53,6 +53,7 @@ ${helpers.predefined_type( extra_prefixes="webkit", spec="https://drafts.fxtf.org/filters/#propdef-filter", affects="overflow", + servo_restyle_damage="repaint", )} ${helpers.predefined_type( @@ -81,4 +82,5 @@ ${helpers.single_keyword( animation_type="discrete", spec="https://drafts.fxtf.org/compositing/#propdef-mix-blend-mode", affects="paint", + servo_restyle_damage="repaint", )} diff --git a/style/properties/longhands/font.mako.rs b/style/properties/longhands/font.mako.rs index 74426ac61c..ef4c9bff3f 100644 --- a/style/properties/longhands/font.mako.rs +++ b/style/properties/longhands/font.mako.rs @@ -13,7 +13,7 @@ ${helpers.predefined_type( animation_type="discrete", spec="https://drafts.csswg.org/css-fonts/#propdef-font-family", gecko_ffi_name="mFont.family", - servo_restyle_damage="rebuild_and_reflow", + servo_restyle_damage="rebuild_box", affects="layout", )} @@ -25,7 +25,7 @@ ${helpers.predefined_type( initial_specified_value="specified::FontStyle::normal()", spec="https://drafts.csswg.org/css-fonts/#propdef-font-style", gecko_ffi_name="mFont.style", - servo_restyle_damage="rebuild_and_reflow", + servo_restyle_damage="rebuild_box", affects="layout", )} @@ -45,7 +45,7 @@ ${helpers.single_keyword( spec="https://drafts.csswg.org/css-fonts/#propdef-font-variant-caps", custom_consts=font_variant_caps_custom_consts, animation_type="discrete", - servo_restyle_damage="rebuild_and_reflow", + servo_restyle_damage="rebuild_box", affects="layout", )} @@ -57,7 +57,7 @@ ${helpers.predefined_type( initial_specified_value="specified::FontWeight::normal()", gecko_ffi_name="mFont.weight", spec="https://drafts.csswg.org/css-fonts/#propdef-font-weight", - servo_restyle_damage="rebuild_and_reflow", + servo_restyle_damage="rebuild_box", affects="layout", )} @@ -69,7 +69,7 @@ ${helpers.predefined_type( initial_specified_value="specified::FontSize::medium()", allow_quirks="Yes", spec="https://drafts.csswg.org/css-fonts/#propdef-font-size", - servo_restyle_damage="rebuild_and_reflow", + servo_restyle_damage="rebuild_box", affects="layout", )} @@ -140,7 +140,7 @@ ${helpers.predefined_type( initial_specified_value="specified::FontStretch::normal()", gecko_ffi_name="mFont.stretch", spec="https://drafts.csswg.org/css-fonts/#propdef-font-stretch", - servo_restyle_damage="rebuild_and_reflow", + servo_restyle_damage="rebuild_box", affects="layout", )} @@ -359,7 +359,7 @@ ${helpers.predefined_type( "computed::LineHeight::normal()", engines="gecko servo", spec="https://drafts.csswg.org/css2/visudet.html#propdef-line-height", - servo_restyle_damage="reflow", + servo_restyle_damage="rebuild_box", affects="layout", )} diff --git a/style/properties/longhands/inherited_box.mako.rs b/style/properties/longhands/inherited_box.mako.rs index b1961ade29..37a88c458f 100644 --- a/style/properties/longhands/inherited_box.mako.rs +++ b/style/properties/longhands/inherited_box.mako.rs @@ -25,7 +25,7 @@ ${helpers.predefined_type( spec="https://drafts.csswg.org/css-writing-modes/#propdef-writing-mode", servo_pref="layout.writing-mode.enabled", animation_type="none", - servo_restyle_damage="rebuild_and_reflow", + servo_restyle_damage="rebuild_box", affects="layout", )} @@ -36,7 +36,7 @@ ${helpers.single_keyword( animation_type="none", spec="https://drafts.csswg.org/css-writing-modes/#propdef-direction", gecko_enum_prefix="StyleDirection", - servo_restyle_damage="rebuild_and_reflow", + servo_restyle_damage="rebuild_box", affects="layout", )} diff --git a/style/properties/longhands/inherited_table.mako.rs b/style/properties/longhands/inherited_table.mako.rs index a242f288f5..3d9595bceb 100644 --- a/style/properties/longhands/inherited_table.mako.rs +++ b/style/properties/longhands/inherited_table.mako.rs @@ -11,7 +11,7 @@ ${helpers.single_keyword( gecko_enum_prefix="StyleBorderCollapse", animation_type="discrete", spec="https://drafts.csswg.org/css-tables/#propdef-border-collapse", - servo_restyle_damage = "reflow", + servo_restyle_damage = "rebuild_box", affects="layout", )} @@ -22,7 +22,7 @@ ${helpers.single_keyword( gecko_enum_prefix="StyleEmptyCells", animation_type="discrete", spec="https://drafts.csswg.org/css-tables/#propdef-empty-cells", - servo_restyle_damage="rebuild_and_reflow", + servo_restyle_damage="rebuild_box", affects="paint", )} @@ -33,7 +33,7 @@ ${helpers.predefined_type( engines="gecko servo", animation_type="discrete", spec="https://drafts.csswg.org/css-tables/#propdef-caption-side", - servo_restyle_damage="rebuild_and_reflow", + servo_restyle_damage="rebuild_box", affects="layout", )} @@ -44,6 +44,6 @@ ${helpers.predefined_type( engines="gecko servo", boxed=True, spec="https://drafts.csswg.org/css-tables/#propdef-border-spacing", - servo_restyle_damage="reflow", + servo_restyle_damage="rebuild_box", affects="layout", )} diff --git a/style/properties/longhands/inherited_text.mako.rs b/style/properties/longhands/inherited_text.mako.rs index 8a024dee5c..dde7d79b10 100644 --- a/style/properties/longhands/inherited_text.mako.rs +++ b/style/properties/longhands/inherited_text.mako.rs @@ -11,6 +11,7 @@ ${helpers.predefined_type( engines="gecko servo", ignored_when_colors_disabled="True", spec="https://drafts.csswg.org/css-color/#color", + servo_restyle_damage="repaint", affects="paint", )} @@ -23,7 +24,7 @@ ${helpers.predefined_type( engines="gecko servo", animation_type="discrete", spec="https://drafts.csswg.org/css-text/#propdef-text-transform", - servo_restyle_damage="rebuild_and_reflow", + servo_restyle_damage="rebuild_box", affects="layout", )} @@ -57,7 +58,7 @@ ${helpers.predefined_type( "computed::TextIndent::zero()", engines="gecko servo", spec="https://drafts.csswg.org/css-text/#propdef-text-indent", - servo_restyle_damage = "reflow", + servo_restyle_damage = "rebuild_box", affects="layout", )} @@ -71,7 +72,7 @@ ${helpers.predefined_type( animation_type="discrete", spec="https://drafts.csswg.org/css-text/#propdef-overflow-wrap", aliases="word-wrap", - servo_restyle_damage="rebuild_and_reflow", + servo_restyle_damage="rebuild_box", affects="layout", )} @@ -82,7 +83,7 @@ ${helpers.predefined_type( engines="gecko servo", animation_type="discrete", spec="https://drafts.csswg.org/css-text/#propdef-word-break", - servo_restyle_damage="rebuild_and_reflow", + servo_restyle_damage="rebuild_box", affects="layout", )} @@ -93,7 +94,7 @@ ${helpers.predefined_type( engines="gecko servo", animation_type="discrete", spec="https://drafts.csswg.org/css-text/#propdef-text-justify", - servo_restyle_damage="rebuild_and_reflow", + servo_restyle_damage="rebuild_box", affects="layout", )} @@ -115,7 +116,7 @@ ${helpers.predefined_type( engines="gecko servo", animation_type="discrete", spec="https://drafts.csswg.org/css-text/#propdef-text-align", - servo_restyle_damage = "reflow", + servo_restyle_damage = "rebuild_box", affects="layout", )} @@ -125,7 +126,7 @@ ${helpers.predefined_type( "computed::LetterSpacing::normal()", engines="gecko servo", spec="https://drafts.csswg.org/css-text/#propdef-letter-spacing", - servo_restyle_damage="rebuild_and_reflow", + servo_restyle_damage="rebuild_box", affects="layout", )} @@ -135,7 +136,7 @@ ${helpers.predefined_type( "computed::WordSpacing::zero()", engines="gecko servo", spec="https://drafts.csswg.org/css-text/#propdef-word-spacing", - servo_restyle_damage="rebuild_and_reflow", + servo_restyle_damage="rebuild_box", affects="layout", )} @@ -149,7 +150,7 @@ ${helpers.single_keyword( gecko_enum_prefix="StyleWhiteSpaceCollapse", animation_type="discrete", spec="https://drafts.csswg.org/css-text-4/#propdef-white-space-collapse", - servo_restyle_damage="rebuild_and_reflow", + servo_restyle_damage="rebuild_box", affects="layout", )} @@ -296,7 +297,7 @@ ${helpers.single_keyword( gecko_enum_prefix="StyleTextRendering", animation_type="discrete", spec="https://svgwg.org/svg2-draft/painting.html#TextRenderingProperty", - servo_restyle_damage="rebuild_and_reflow", + servo_restyle_damage="rebuild_box", affects="layout", )} @@ -383,7 +384,7 @@ ${helpers.single_keyword( gecko_enum_prefix="StyleTextWrapMode", animation_type="discrete", spec="https://drafts.csswg.org/css-text-4/#propdef-text-wrap-mode", - servo_restyle_damage="rebuild_and_reflow", + servo_restyle_damage="rebuild_box", affects="layout", )} diff --git a/style/properties/longhands/inherited_ui.mako.rs b/style/properties/longhands/inherited_ui.mako.rs index 9adb260960..a0a2fc8e38 100644 --- a/style/properties/longhands/inherited_ui.mako.rs +++ b/style/properties/longhands/inherited_ui.mako.rs @@ -12,6 +12,7 @@ ${helpers.predefined_type( initial_specified_value="specified::Cursor::auto()", animation_type="discrete", spec="https://drafts.csswg.org/css-ui/#cursor", + servo_restyle_damage="repaint", affects="paint", )} @@ -22,6 +23,7 @@ ${helpers.predefined_type( engines="gecko servo", animation_type="discrete", spec="https://svgwg.org/svg2-draft/interact.html#PointerEventsProperty", + servo_restyle_damage="repaint", affects="paint", )} diff --git a/style/properties/longhands/list.mako.rs b/style/properties/longhands/list.mako.rs index 603b3b09d5..69c3bf1c7a 100644 --- a/style/properties/longhands/list.mako.rs +++ b/style/properties/longhands/list.mako.rs @@ -11,7 +11,7 @@ ${helpers.single_keyword( gecko_enum_prefix="StyleListStylePosition", animation_type="discrete", spec="https://drafts.csswg.org/css-lists/#propdef-list-style-position", - servo_restyle_damage="rebuild_and_reflow", + servo_restyle_damage="rebuild_box", affects="layout", )} @@ -33,7 +33,7 @@ ${helpers.single_keyword( engines="servo", animation_type="discrete", spec="https://drafts.csswg.org/css-lists/#propdef-list-style-type", - servo_restyle_damage="rebuild_and_reflow", + servo_restyle_damage="rebuild_box", affects="layout", )} % endif @@ -46,7 +46,7 @@ ${helpers.single_keyword( initial_specified_value="specified::ListStyleType::disc()", animation_type="discrete", spec="https://drafts.csswg.org/css-lists/#propdef-list-style-type", - servo_restyle_damage="rebuild_and_reflow", + servo_restyle_damage="rebuild_box", affects="layout", )} % endif @@ -59,8 +59,7 @@ ${helpers.predefined_type( initial_specified_value="specified::Image::None", animation_type="discrete", spec="https://drafts.csswg.org/css-lists/#propdef-list-style-image", - boxed=engine == "servo", - servo_restyle_damage="rebuild_and_reflow", + servo_restyle_damage="rebuild_box", affects="layout", )} @@ -71,6 +70,6 @@ ${helpers.predefined_type( engines="gecko servo", animation_type="discrete", spec="https://drafts.csswg.org/css-content/#propdef-quotes", - servo_restyle_damage="rebuild_and_reflow", + servo_restyle_damage="rebuild_box", affects="layout", )} diff --git a/style/properties/longhands/margin.mako.rs b/style/properties/longhands/margin.mako.rs index f063658ce8..853d8a710b 100644 --- a/style/properties/longhands/margin.mako.rs +++ b/style/properties/longhands/margin.mako.rs @@ -23,7 +23,7 @@ gecko_ffi_name="mMargin.{}".format(index), spec=spec, rule_types_allowed=(DEFAULT_RULES if side[1] else DEFAULT_RULES_AND_PAGE) | POSITION_TRY_RULE, - servo_restyle_damage="reflow", + servo_restyle_damage="rebuild_box", affects="layout", )} % endfor diff --git a/style/properties/longhands/outline.mako.rs b/style/properties/longhands/outline.mako.rs index 6416481f88..f1f3e103b6 100644 --- a/style/properties/longhands/outline.mako.rs +++ b/style/properties/longhands/outline.mako.rs @@ -12,6 +12,7 @@ ${helpers.predefined_type( initial_specified_value="specified::Color::currentcolor()", ignored_when_colors_disabled=True, spec="https://drafts.csswg.org/css-ui/#propdef-outline-color", + servo_restyle_damage="repaint", affects="paint", )} @@ -23,6 +24,7 @@ ${helpers.predefined_type( initial_specified_value="specified::OutlineStyle::none()", animation_type="discrete", spec="https://drafts.csswg.org/css-ui/#propdef-outline-style", + servo_restyle_damage="repaint", affects="overflow", )} @@ -33,6 +35,7 @@ ${helpers.predefined_type( engines="gecko servo", initial_specified_value="specified::BorderSideWidth::medium()", spec="https://drafts.csswg.org/css-ui/#propdef-outline-width", + servo_restyle_damage="repaint", affects="overflow", )} @@ -42,5 +45,6 @@ ${helpers.predefined_type( "crate::values::computed::Length::new(0.)", engines="gecko servo", spec="https://drafts.csswg.org/css-ui/#propdef-outline-offset", + servo_restyle_damage="repaint", affects="overflow", )} diff --git a/style/properties/longhands/padding.mako.rs b/style/properties/longhands/padding.mako.rs index 2b65264b0f..98d486aa5e 100644 --- a/style/properties/longhands/padding.mako.rs +++ b/style/properties/longhands/padding.mako.rs @@ -22,7 +22,7 @@ spec=spec, gecko_ffi_name="mPadding.{}".format(index), allow_quirks="No" if side[1] else "Yes", - servo_restyle_damage="reflow rebuild_and_reflow_inline", + servo_restyle_damage="rebuild_box", affects="layout", )} % endfor diff --git a/style/properties/longhands/position.mako.rs b/style/properties/longhands/position.mako.rs index 1c1672d3a8..8785dd2316 100644 --- a/style/properties/longhands/position.mako.rs +++ b/style/properties/longhands/position.mako.rs @@ -16,7 +16,7 @@ allow_quirks="Yes", rule_types_allowed=DEFAULT_RULES_AND_POSITION_TRY, gecko_ffi_name="mOffset.{}".format(index), - servo_restyle_damage="reflow_out_of_flow", + servo_restyle_damage="rebuild_box", logical_group="inset", affects="layout", )} @@ -43,6 +43,7 @@ ${helpers.predefined_type( engines="gecko servo", spec="https://www.w3.org/TR/CSS2/visuren.html#z-index", affects="paint", + servo_restyle_damage="rebuild_stacking_context", )} // CSS Flexible Box Layout Module Level 1 @@ -56,7 +57,7 @@ ${helpers.single_keyword( spec="https://drafts.csswg.org/css-flexbox/#flex-direction-property", extra_prefixes="webkit", animation_type="discrete", - servo_restyle_damage = "reflow", + servo_restyle_damage = "rebuild_box", gecko_enum_prefix = "StyleFlexDirection", affects="layout", )} @@ -68,7 +69,7 @@ ${helpers.single_keyword( spec="https://drafts.csswg.org/css-flexbox/#flex-wrap-property", extra_prefixes="webkit", animation_type="discrete", - servo_restyle_damage = "reflow", + servo_restyle_damage = "rebuild_box", gecko_enum_prefix = "StyleFlexWrap", affects="layout", )} @@ -81,7 +82,7 @@ ${helpers.predefined_type( spec="https://drafts.csswg.org/css-align/#propdef-justify-content", extra_prefixes="webkit", animation_type="discrete", - servo_restyle_damage="reflow", + servo_restyle_damage="rebuild_box", affects="layout", )} @@ -93,7 +94,7 @@ ${helpers.predefined_type( spec="https://drafts.csswg.org/css-align/#propdef-align-content", extra_prefixes="webkit", animation_type="discrete", - servo_restyle_damage="reflow", + servo_restyle_damage="rebuild_box", affects="layout", )} @@ -105,7 +106,7 @@ ${helpers.predefined_type( spec="https://drafts.csswg.org/css-align/#propdef-align-items", extra_prefixes="webkit", animation_type="discrete", - servo_restyle_damage="reflow", + servo_restyle_damage="rebuild_box", affects="layout", )} @@ -127,7 +128,7 @@ ${helpers.predefined_type( engines="gecko servo", spec="https://drafts.csswg.org/css-flexbox/#flex-grow-property", extra_prefixes="webkit", - servo_restyle_damage="reflow", + servo_restyle_damage="rebuild_box", affects="layout", )} @@ -138,7 +139,7 @@ ${helpers.predefined_type( engines="gecko servo", spec="https://drafts.csswg.org/css-flexbox/#flex-shrink-property", extra_prefixes="webkit", - servo_restyle_damage = "reflow", + servo_restyle_damage = "rebuild_box", affects="layout", )} @@ -173,7 +174,7 @@ ${helpers.predefined_type( engines="gecko servo", extra_prefixes="webkit", spec="https://drafts.csswg.org/css-flexbox/#order-property", - servo_restyle_damage="reflow", + servo_restyle_damage="rebuild_box", affects="layout", )} @@ -184,7 +185,7 @@ ${helpers.predefined_type( engines="gecko servo", spec="https://drafts.csswg.org/css-flexbox/#flex-basis-property", extra_prefixes="webkit", - servo_restyle_damage="reflow", + servo_restyle_damage="rebuild_box", boxed=True, affects="layout", )} @@ -206,7 +207,7 @@ ${helpers.predefined_type( allow_quirks="No" if logical else "Yes", spec=spec % size, rule_types_allowed=DEFAULT_RULES_AND_POSITION_TRY, - servo_restyle_damage="reflow", + servo_restyle_damage="rebuild_box", affects="layout", )} // min-width, min-height, min-block-size, min-inline-size @@ -220,7 +221,7 @@ ${helpers.predefined_type( allow_quirks="No" if logical else "Yes", spec=spec % size, rule_types_allowed=DEFAULT_RULES_AND_POSITION_TRY, - servo_restyle_damage="reflow", + servo_restyle_damage="rebuild_box", affects="layout", )} ${helpers.predefined_type( @@ -233,7 +234,7 @@ ${helpers.predefined_type( allow_quirks="No" if logical else "Yes", spec=spec % size, rule_types_allowed=DEFAULT_RULES_AND_POSITION_TRY, - servo_restyle_damage="reflow", + servo_restyle_damage="rebuild_box", affects="layout", )} % endfor @@ -308,7 +309,7 @@ ${helpers.single_keyword( gecko_enum_prefix="StyleBoxSizing", custom_consts={ "content-box": "Content", "border-box": "Border" }, animation_type="discrete", - servo_restyle_damage = "reflow", + servo_restyle_damage = "rebuild_box", affects="layout", )} @@ -409,7 +410,7 @@ ${helpers.predefined_type( engines="gecko servo", aliases="grid-column-gap", spec="https://drafts.csswg.org/css-align-3/#propdef-column-gap", - servo_restyle_damage="reflow", + servo_restyle_damage="rebuild_box", affects="layout", )} @@ -421,7 +422,7 @@ ${helpers.predefined_type( engines="gecko servo", aliases="grid-row-gap", spec="https://drafts.csswg.org/css-align-3/#propdef-row-gap", - servo_restyle_damage="reflow", + servo_restyle_damage="rebuild_box", affects="layout", )} @@ -431,7 +432,7 @@ ${helpers.predefined_type( "computed::AspectRatio::auto()", engines="gecko servo", spec="https://drafts.csswg.org/css-sizing-4/#aspect-ratio", - servo_restyle_damage="reflow", + servo_restyle_damage="rebuild_box", affects="layout", )} diff --git a/style/properties/longhands/svg.mako.rs b/style/properties/longhands/svg.mako.rs index 7759118985..1e077d2f20 100644 --- a/style/properties/longhands/svg.mako.rs +++ b/style/properties/longhands/svg.mako.rs @@ -83,6 +83,7 @@ ${helpers.predefined_type( extra_prefixes="webkit", spec="https://drafts.fxtf.org/css-masking-1/#propdef-clip-path", affects="paint", + servo_restyle_damage="repaint", )} ${helpers.single_keyword( diff --git a/style/properties/longhands/table.mako.rs b/style/properties/longhands/table.mako.rs index 8b02d6f825..43dd65c33f 100644 --- a/style/properties/longhands/table.mako.rs +++ b/style/properties/longhands/table.mako.rs @@ -12,7 +12,7 @@ ${helpers.single_keyword( animation_type="discrete", gecko_enum_prefix="StyleTableLayout", spec="https://drafts.csswg.org/css-tables/#propdef-table-layout", - servo_restyle_damage="reflow", + servo_restyle_damage="rebuild_box", affects="layout", )} diff --git a/style/properties/longhands/text.mako.rs b/style/properties/longhands/text.mako.rs index a0054b77c4..eb9cb21d6a 100644 --- a/style/properties/longhands/text.mako.rs +++ b/style/properties/longhands/text.mako.rs @@ -13,7 +13,7 @@ ${helpers.predefined_type( animation_type="discrete", boxed=True, spec="https://drafts.csswg.org/css-ui/#propdef-text-overflow", - servo_restyle_damage="rebuild_and_reflow", + servo_restyle_damage="rebuild_box", affects="paint", )} @@ -24,7 +24,7 @@ ${helpers.single_keyword( gecko_enum_prefix="StyleUnicodeBidi", animation_type="none", spec="https://drafts.csswg.org/css-writing-modes/#propdef-unicode-bidi", - servo_restyle_damage="rebuild_and_reflow", + servo_restyle_damage="rebuild_box", affects="layout", )} @@ -36,7 +36,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_and_reflow", + servo_restyle_damage="rebuild_box", affects="overflow", )} diff --git a/style/properties/properties.mako.rs b/style/properties/properties.mako.rs index 044a50a09e..d7c6927f1b 100644 --- a/style/properties/properties.mako.rs +++ b/style/properties/properties.mako.rs @@ -2225,40 +2225,6 @@ impl ComputedValuesInner { box_.transform_style } } - - /// Whether given this transform value, the compositor would require a - /// layer. - pub fn transform_requires_layer(&self) -> bool { - use crate::values::generics::transform::TransformOperation; - // Check if the transform matrix is 2D or 3D - for transform in &*self.get_box().transform.0 { - match *transform { - TransformOperation::Perspective(..) => { - return true; - } - TransformOperation::Matrix3D(m) => { - // See http://dev.w3.org/csswg/css-transforms/#2d-matrix - if m.m31 != 0.0 || m.m32 != 0.0 || - m.m13 != 0.0 || m.m23 != 0.0 || - m.m43 != 0.0 || m.m14 != 0.0 || - m.m24 != 0.0 || m.m34 != 0.0 || - m.m33 != 1.0 || m.m44 != 1.0 { - return true; - } - } - TransformOperation::Translate3D(_, _, z) | - TransformOperation::TranslateZ(z) => { - if z.px() != 0. { - return true; - } - } - _ => {} - } - } - - // Neither perspective nor transform present - false - } } /// A reference to a style struct of the parent, or our own style struct. @@ -2997,29 +2963,17 @@ const_assert!(std::mem::size_of::( % endfor % if engine == "servo": -% for effect_name in ["repaint", "reflow_out_of_flow", "reflow", "rebuild_and_reflow_inline", "rebuild_and_reflow"]: - macro_rules! restyle_damage_${effect_name} { - ($old: ident, $new: ident, $damage: ident, [ $($effect:expr),* ]) => ({ - restyle_damage_${effect_name}!($old, $new, $damage, [$($effect),*], false) - }); - ($old: ident, $new: ident, $damage: ident, [ $($effect:expr),* ], $extra:expr) => ({ - if - % for style_struct in data.active_style_structs(): - % 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} || - % endif - % endfor - % endfor - - $extra || false { - $damage.insert($($effect)|*); - true - } else { - false - } - }); - } +% for effect_name in ["repaint", "recalculate_overflow", "rebuild_stacking_context", "rebuild_box"]: +/// Returns true if any of the properties marked as effecting "${effect_name}" damage have changed +pub fn restyle_damage_${effect_name} (old: &ComputedValues, new: &ComputedValues) -> bool { + % for style_struct in data.active_style_structs(): + % 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} || + % endif + % endfor + % endfor + false +} % endfor % endif 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/rule_collector.rs b/style/rule_collector.rs index e860a0eac4..fca903e38c 100644 --- a/style/rule_collector.rs +++ b/style/rule_collector.rs @@ -97,12 +97,6 @@ where let rule_hash_target = element.rule_hash_target(); let matches_user_and_content_rules = rule_hash_target.matches_user_and_content_rules(); - // Gecko definitely has pseudo-elements with style attributes, like - // ::-moz-color-swatch. - debug_assert!( - cfg!(feature = "gecko") || style_attribute.is_none() || pseudo_elements.is_empty(), - "Style attributes do not apply to pseudo-elements" - ); debug_assert!(pseudo_elements.iter().all(|p| !p.is_precomputed())); Self { diff --git a/style/selector_parser.rs b/style/selector_parser.rs index 9911a4f863..aef696fc93 100644 --- a/style/selector_parser.rs +++ b/style/selector_parser.rs @@ -31,12 +31,6 @@ pub use crate::servo::selector_parser::ServoElementSnapshot as Snapshot; #[cfg(feature = "gecko")] pub use crate::gecko::snapshot::GeckoElementSnapshot as Snapshot; -#[cfg(feature = "servo")] -pub use crate::servo::restyle_damage::ServoRestyleDamage as RestyleDamage; - -#[cfg(feature = "gecko")] -pub use crate::gecko::restyle_damage::GeckoRestyleDamage as RestyleDamage; - /// Servo's selector parser. #[cfg_attr(feature = "servo", derive(MallocSizeOf))] pub struct SelectorParser<'a> { diff --git a/style/servo/media_queries.rs b/style/servo/media_queries.rs index 5707f6b1ba..b57199b093 100644 --- a/style/servo/media_queries.rs +++ b/style/servo/media_queries.rs @@ -252,6 +252,11 @@ impl Device { (AU_PER_PX as f32 / self.device_pixel_ratio.0) as i32 } + /// Returns the device pixel ratio, ignoring the full zoom factor. + pub fn device_pixel_ratio_ignoring_full_zoom(&self) -> Scale { + self.device_pixel_ratio + } + /// Returns the device pixel ratio. pub fn device_pixel_ratio(&self) -> Scale { self.device_pixel_ratio diff --git a/style/servo/restyle_damage.rs b/style/servo/restyle_damage.rs index 6355959805..524405a583 100644 --- a/style/servo/restyle_damage.rs +++ b/style/servo/restyle_damage.rs @@ -2,269 +2,61 @@ * 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/. */ -//! The restyle damage is a hint that tells layout which kind of operations may -//! be needed in presence of incremental style changes. - -use crate::computed_values::display::T as Display; -use crate::matching::{StyleChange, StyleDifference}; -use crate::properties::ComputedValues; -use std::fmt; - -bitflags! { - /// Individual layout actions that may be necessary after restyling. - #[derive(Clone, Copy, Debug, Eq, PartialEq)] - pub struct ServoRestyleDamage: u8 { - /// Repaint the node itself. - /// - /// Currently unused; need to decide how this propagates. - const REPAINT = 0x01; - - /// The stacking-context-relative position of this node or its - /// descendants has changed. - /// - /// Propagates both up and down the flow tree. - const REPOSITION = 0x02; - - /// Recompute the overflow regions (bounding box of object and all descendants). - /// - /// Propagates down the flow tree because the computation is bottom-up. - const STORE_OVERFLOW = 0x04; - - /// Recompute intrinsic inline_sizes (minimum and preferred). - /// - /// Propagates down the flow tree because the computation is. - /// bottom-up. - const BUBBLE_ISIZES = 0x08; - - /// Recompute actual inline-sizes and block-sizes, only taking - /// out-of-flow children into account. - /// - /// Propagates up the flow tree because the computation is top-down. - const REFLOW_OUT_OF_FLOW = 0x10; - - /// Recompute actual inline_sizes and block_sizes. - /// - /// Propagates up the flow tree because the computation is top-down. - const REFLOW = 0x20; - - /// Re-resolve generated content. - /// - /// Propagates up the flow tree because the computation is inorder. - const RESOLVE_GENERATED_CONTENT = 0x40; - - /// The entire flow needs to be reconstructed. - const RECONSTRUCT_FLOW = 0x80; - } +//! Helpers for computing restyle damage + +use crate::computed_values::isolation::T as Isolation; +use crate::computed_values::mix_blend_mode::T as MixBlendMode; +use crate::computed_values::transform_style::T as TransformStyle; +use crate::properties::{ + restyle_damage_rebuild_box, restyle_damage_rebuild_stacking_context, style_structs, + ComputedValues, +}; +use crate::values::computed::basic_shape::ClipPath; +use crate::values::computed::Perspective; +use crate::values::generics::transform::{GenericRotate, GenericScale, GenericTranslate}; + +/// Returns true if "relayout" damage should be applied +pub fn restyle_damage_layout(old: &ComputedValues, new: &ComputedValues) -> bool { + let old_box = old.get_box(); + let new_box = new.get_box(); + restyle_damage_rebuild_box(old, new) + || old_box.original_display != new_box.original_display + || old_box.has_transform_or_perspective() != new_box.has_transform_or_perspective() + || old.get_effects().filter.0.is_empty() != new.get_effects().filter.0.is_empty() } -malloc_size_of_is_0!(ServoRestyleDamage); - -impl ServoRestyleDamage { - /// Compute the `StyleDifference` (including the appropriate restyle damage) - /// for a given style change between `old` and `new`. - pub fn compute_style_difference(old: &ComputedValues, new: &ComputedValues) -> StyleDifference { - let damage = compute_damage(old, new); - let change = if damage.is_empty() { - StyleChange::Unchanged - } else { - // FIXME(emilio): Differentiate between reset and inherited - // properties here, and set `reset_only` appropriately so the - // optimization to skip the cascade in those cases applies. - StyleChange::Changed { reset_only: false } - }; - StyleDifference { damage, change } - } - - /// Returns a bitmask that represents a flow that needs to be rebuilt and - /// reflowed. - /// - /// FIXME(bholley): Do we ever actually need this? Shouldn't - /// RECONSTRUCT_FLOW imply everything else? - pub fn rebuild_and_reflow() -> ServoRestyleDamage { - ServoRestyleDamage::REPAINT | - ServoRestyleDamage::REPOSITION | - ServoRestyleDamage::STORE_OVERFLOW | - ServoRestyleDamage::BUBBLE_ISIZES | - ServoRestyleDamage::REFLOW_OUT_OF_FLOW | - ServoRestyleDamage::REFLOW | - ServoRestyleDamage::RECONSTRUCT_FLOW - } - - /// Returns a bitmask indicating that the frame needs to be reconstructed. - pub fn reconstruct() -> ServoRestyleDamage { - ServoRestyleDamage::RECONSTRUCT_FLOW - } - - /// Supposing a flow has the given `position` property and this damage, - /// returns the damage that we should add to the *parent* of this flow. - pub fn damage_for_parent(self, child_is_absolutely_positioned: bool) -> ServoRestyleDamage { - if child_is_absolutely_positioned { - self & (ServoRestyleDamage::REPAINT | - ServoRestyleDamage::REPOSITION | - ServoRestyleDamage::STORE_OVERFLOW | - ServoRestyleDamage::REFLOW_OUT_OF_FLOW | - ServoRestyleDamage::RESOLVE_GENERATED_CONTENT) - } else { - self & (ServoRestyleDamage::REPAINT | - ServoRestyleDamage::REPOSITION | - ServoRestyleDamage::STORE_OVERFLOW | - ServoRestyleDamage::REFLOW | - ServoRestyleDamage::REFLOW_OUT_OF_FLOW | - ServoRestyleDamage::RESOLVE_GENERATED_CONTENT) - } - } - - /// Supposing the *parent* of a flow with the given `position` property has - /// this damage, returns the damage that we should add to this flow. - pub fn damage_for_child( - self, - parent_is_absolutely_positioned: bool, - child_is_absolutely_positioned: bool, - ) -> ServoRestyleDamage { - match ( - parent_is_absolutely_positioned, - child_is_absolutely_positioned, - ) { - (false, true) => { - // Absolute children are out-of-flow and therefore insulated from changes. - // - // FIXME(pcwalton): Au contraire, if the containing block dimensions change! - self & (ServoRestyleDamage::REPAINT | ServoRestyleDamage::REPOSITION) - }, - (true, false) => { - // Changing the position of an absolutely-positioned block requires us to reflow - // its kids. - if self.contains(ServoRestyleDamage::REFLOW_OUT_OF_FLOW) { - self | ServoRestyleDamage::REFLOW - } else { - self - } - }, - _ => { - // TODO(pcwalton): Take floatedness into account. - self & (ServoRestyleDamage::REPAINT | - ServoRestyleDamage::REPOSITION | - ServoRestyleDamage::REFLOW) - }, - } - } -} - -impl Default for ServoRestyleDamage { - fn default() -> Self { - Self::empty() - } +/// Returns true if "rebuild_stacking_context" damage should be applied +pub fn augmented_restyle_damage_rebuild_stacking_context( + old: &ComputedValues, + new: &ComputedValues, +) -> bool { + restyle_damage_rebuild_stacking_context(old, new) + || old.guarantees_stacking_context() != new.guarantees_stacking_context() } -impl fmt::Display for ServoRestyleDamage { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - let mut first_elem = true; - - let to_iter = [ - (ServoRestyleDamage::REPAINT, "Repaint"), - (ServoRestyleDamage::REPOSITION, "Reposition"), - (ServoRestyleDamage::STORE_OVERFLOW, "StoreOverflow"), - (ServoRestyleDamage::BUBBLE_ISIZES, "BubbleISizes"), - (ServoRestyleDamage::REFLOW_OUT_OF_FLOW, "ReflowOutOfFlow"), - (ServoRestyleDamage::REFLOW, "Reflow"), - ( - ServoRestyleDamage::RESOLVE_GENERATED_CONTENT, - "ResolveGeneratedContent", - ), - (ServoRestyleDamage::RECONSTRUCT_FLOW, "ReconstructFlow"), - ]; - - for &(damage, damage_str) in &to_iter { - if self.contains(damage) { - if !first_elem { - write!(f, " | ")?; - } - write!(f, "{}", damage_str)?; - first_elem = false; - } - } - - if first_elem { - write!(f, "NoDamage")?; - } - - Ok(()) +impl ComputedValues { + /// Some properties establish a stacking context when they are set to a non-initial value. + /// In that case, the damage is only set to `ServoRestyleDamage::REPAINT` because we don't + /// need to rebuild stacking contexts when the style changes between different non-initial + /// values. This function checks whether any of these properties is set to a value that + /// guarantees a stacking context, so that we only do the work when this changes. + /// Note that it's still possible to establish a stacking context when this returns false. + pub fn guarantees_stacking_context(&self) -> bool { + self.get_effects().opacity != 1.0 + || self.get_effects().mix_blend_mode != MixBlendMode::Normal + || self.get_svg().clip_path != ClipPath::None + || self.get_box().isolation == Isolation::Isolate } } -fn compute_damage(old: &ComputedValues, new: &ComputedValues) -> ServoRestyleDamage { - let mut damage = ServoRestyleDamage::empty(); - - // This should check every CSS property, as enumerated in the fields of - // https://doc.servo.org/style/properties/struct.ComputedValues.html - - // This uses short-circuiting boolean OR for its side effects and ignores the result. - let _ = restyle_damage_rebuild_and_reflow!( - old, - new, - damage, - [ - ServoRestyleDamage::REPAINT, - ServoRestyleDamage::REPOSITION, - ServoRestyleDamage::STORE_OVERFLOW, - ServoRestyleDamage::BUBBLE_ISIZES, - ServoRestyleDamage::REFLOW_OUT_OF_FLOW, - ServoRestyleDamage::REFLOW, - ServoRestyleDamage::RECONSTRUCT_FLOW - ], - old.get_box().original_display != new.get_box().original_display - ) || (new.get_box().display == Display::Inline && - restyle_damage_rebuild_and_reflow_inline!( - old, - new, - damage, - [ - ServoRestyleDamage::REPAINT, - ServoRestyleDamage::REPOSITION, - ServoRestyleDamage::STORE_OVERFLOW, - ServoRestyleDamage::BUBBLE_ISIZES, - ServoRestyleDamage::REFLOW_OUT_OF_FLOW, - ServoRestyleDamage::REFLOW, - ServoRestyleDamage::RECONSTRUCT_FLOW - ] - )) || - restyle_damage_reflow!( - old, - new, - damage, - [ - ServoRestyleDamage::REPAINT, - ServoRestyleDamage::REPOSITION, - ServoRestyleDamage::STORE_OVERFLOW, - ServoRestyleDamage::BUBBLE_ISIZES, - ServoRestyleDamage::REFLOW_OUT_OF_FLOW, - ServoRestyleDamage::REFLOW - ] - ) || - restyle_damage_reflow_out_of_flow!( - old, - new, - damage, - [ - ServoRestyleDamage::REPAINT, - ServoRestyleDamage::REPOSITION, - ServoRestyleDamage::STORE_OVERFLOW, - ServoRestyleDamage::REFLOW_OUT_OF_FLOW - ] - ) || - restyle_damage_repaint!(old, new, damage, [ServoRestyleDamage::REPAINT]); - - // Paint worklets may depend on custom properties, - // so if they have changed we should repaint. - if !old.custom_properties_equal(new) { - damage.insert(ServoRestyleDamage::REPAINT); - } - - // If the layer requirements of this flow have changed due to the value - // of the transform, then reflow is required to rebuild the layers. - if old.transform_requires_layer() != new.transform_requires_layer() { - damage.insert(ServoRestyleDamage::rebuild_and_reflow()); +impl style_structs::Box { + /// Whether there is a non-default transform or perspective style set + pub fn has_transform_or_perspective(&self) -> bool { + !self.transform.0.is_empty() + || self.scale != GenericScale::None + || self.rotate != GenericRotate::None + || self.translate != GenericTranslate::None + || self.perspective != Perspective::None + || self.transform_style == TransformStyle::Preserve3d } - - damage } diff --git a/style/servo/selector_parser.rs b/style/servo/selector_parser.rs index ad1f78c67a..7ed5f6a1eb 100644 --- a/style/servo/selector_parser.rs +++ b/style/servo/selector_parser.rs @@ -50,11 +50,23 @@ pub enum PseudoElement { // them. Also, make sure the UA sheet has the !important rules some of the // APPLIES_TO_PLACEHOLDER properties expect! - Backdrop, - // Non-eager pseudos. + Backdrop, DetailsSummary, DetailsContent, + Marker, + + // Implemented pseudos. These pseudo elements are representing the + // elements within an UA shadow DOM, and matching the elements with + // their appropriate styles. + ColorSwatch, + Placeholder, + + // Private, Servo-specific implemented pseudos. Only matchable in UA sheet. + ServoTextControlInnerContainer, + ServoTextControlInnerEditor, + + // Other Servo-specific pseudos. ServoAnonymousBox, ServoAnonymousTable, ServoAnonymousTableCell, @@ -79,6 +91,11 @@ impl ToCss for PseudoElement { Backdrop => "::backdrop", DetailsSummary => "::-servo-details-summary", DetailsContent => "::-servo-details-content", + Marker => "::marker", + ColorSwatch => "::color-swatch", + Placeholder => "::placeholder", + ServoTextControlInnerContainer => "::-servo-text-control-inner-container", + ServoTextControlInnerEditor => "::-servo-text-control-inner-editor", ServoAnonymousBox => "::-servo-anonymous-box", ServoAnonymousTable => "::-servo-anonymous-table", ServoAnonymousTableCell => "::-servo-anonymous-table-cell", @@ -139,7 +156,7 @@ impl PseudoElement { /// Whether this pseudo-element is the ::marker pseudo. #[inline] pub fn is_marker(&self) -> bool { - false + *self == PseudoElement::Marker } /// Whether this pseudo-element is the ::selection pseudo. @@ -172,10 +189,11 @@ impl PseudoElement { false } - /// Whether this pseudo-element is the ::-moz-color-swatch pseudo. + /// Whether this pseudo-element is representing the color swatch + /// inside an `` element. #[inline] pub fn is_color_swatch(&self) -> bool { - false + *self == PseudoElement::ColorSwatch } /// Whether this pseudo-element is eagerly-cascaded. @@ -210,16 +228,24 @@ impl PseudoElement { /// Returns which kind of cascade type has this pseudo. /// - /// For more info on cascade types, see docs/components/style.md + /// See the documentation for `PseudoElementCascadeType` for how we choose + /// which cascade type to use. /// - /// Note: Keep this in sync with EAGER_PSEUDO_COUNT. + /// Note: Keep eager pseudos in sync with `EAGER_PSEUDO_COUNT` and + /// `EMPTY_PSEUDO_ARRAY` in `style/data.rs` #[inline] pub fn cascade_type(&self) -> PseudoElementCascadeType { match *self { PseudoElement::After | PseudoElement::Before | PseudoElement::Selection => { PseudoElementCascadeType::Eager }, - PseudoElement::Backdrop | PseudoElement::DetailsSummary => PseudoElementCascadeType::Lazy, + PseudoElement::Backdrop | + PseudoElement::ColorSwatch | + PseudoElement::DetailsSummary | + PseudoElement::Marker | + PseudoElement::Placeholder | + PseudoElement::ServoTextControlInnerContainer | + PseudoElement::ServoTextControlInnerEditor => PseudoElementCascadeType::Lazy, PseudoElement::DetailsContent | PseudoElement::ServoAnonymousBox | PseudoElement::ServoAnonymousTable | @@ -474,8 +500,8 @@ impl ::selectors::SelectorImpl for SelectorImpl { type LocalName = LocalName; type NamespacePrefix = Prefix; type NamespaceUrl = Namespace; - type BorrowedLocalName = markup5ever::LocalName; - type BorrowedNamespaceUrl = markup5ever::Namespace; + type BorrowedLocalName = web_atoms::LocalName; + type BorrowedNamespaceUrl = web_atoms::Namespace; } impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> { @@ -499,7 +525,12 @@ impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> { #[inline] fn parse_parent_selector(&self) -> bool { - false + true + } + + #[inline] + fn parse_part(&self) -> bool { + true } #[inline] @@ -584,8 +615,9 @@ impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> { let pseudo_element = match_ignore_ascii_case! { &name, "before" => Before, "after" => After, - "selection" => Selection, "backdrop" => Backdrop, + "selection" => Selection, + "marker" => Marker, "-servo-details-summary" => { if !self.in_user_agent_stylesheet() { return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))) @@ -598,6 +630,25 @@ impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> { } DetailsContent }, + "color-swatch" => ColorSwatch, + "placeholder" => { + if !self.in_user_agent_stylesheet() { + return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))) + } + Placeholder + }, + "-servo-text-control-inner-container" => { + if !self.in_user_agent_stylesheet() { + return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))) + } + ServoTextControlInnerContainer + }, + "-servo-text-control-inner-editor" => { + if !self.in_user_agent_stylesheet() { + return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))) + } + ServoTextControlInnerEditor + }, "-servo-anonymous-box" => { if !self.in_user_agent_stylesheet() { return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))) @@ -786,8 +837,13 @@ impl ElementSnapshot for ServoElementSnapshot { .map(|v| v.as_atom()) } - fn is_part(&self, _name: &AtomIdent) -> bool { - false + fn is_part(&self, part_name: &AtomIdent) -> bool { + self.get_attr(&ns!(), &local_name!("part")) + .is_some_and(|v| { + v.as_tokens() + .iter() + .any(|atom| CaseSensitivity::CaseSensitive.eq_atom(atom, part_name)) + }) } fn imported_part(&self, _: &AtomIdent) -> Option { diff --git a/style/servo/url.rs b/style/servo/url.rs index e475d15b79..63c4b42cd2 100644 --- a/style/servo/url.rs +++ b/style/servo/url.rs @@ -10,6 +10,7 @@ use crate::values::computed::{Context, ToComputedValue}; use cssparser::Parser; use servo_arc::Arc; use std::fmt::{self, Write}; +use std::ops::Deref; use style_traits::{CssWriter, ParseError, ToCss}; use to_shmem::{SharedMemoryBuilder, ToShmem}; use url::Url; @@ -22,11 +23,16 @@ use url::Url; /// /// However, this approach is still not necessarily optimal: See /// -/// -/// TODO(emilio): This should be shrunk by making CssUrl a wrapper type of an -/// arc, and keep the serialization in that Arc. See gecko/url.rs for example. #[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize, SpecifiedValueInfo)] -pub struct CssUrl { +#[css(function = "url")] +#[repr(C)] +pub struct CssUrl(#[ignore_malloc_size_of = "Arc"] pub Arc); + +/// Data shared between CssUrls. +/// +#[derive(Debug, Deserialize, MallocSizeOf, Serialize, SpecifiedValueInfo)] +#[repr(C)] +pub struct CssUrlData { /// The original URI. This might be optional since we may insert computed /// values of images into the cascade directly, and we don't bother to /// convert their serialization. @@ -47,6 +53,13 @@ impl ToShmem for CssUrl { } } +impl Deref for CssUrl { + type Target = CssUrlData; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + impl CssUrl { /// Try to parse a URL from a string value that is a valid CSS token for a /// URL. @@ -55,10 +68,10 @@ impl CssUrl { pub fn parse_from_string(url: String, context: &ParserContext, _: CorsMode) -> Self { let serialization = Arc::new(url); let resolved = context.url_data.0.join(&serialization).ok().map(Arc::new); - CssUrl { + CssUrl(Arc::new(CssUrlData { original: Some(serialization), resolved: resolved, - } + })) } /// Returns true if the URL is definitely invalid. For Servo URLs, we can @@ -96,18 +109,18 @@ impl CssUrl { /// Creates an already specified url value from an already resolved URL /// for insertion in the cascade. pub fn for_cascade(url: Arc<::url::Url>) -> Self { - CssUrl { + CssUrl(Arc::new(CssUrlData { original: None, resolved: Some(url), - } + })) } /// Gets a new url from a string for unit tests. pub fn new_for_testing(url: &str) -> Self { - CssUrl { + CssUrl(Arc::new(CssUrlData { original: Some(Arc::new(url.into())), resolved: ::url::Url::parse(url).ok().map(Arc::new), - } + })) } /// Parses a URL request and records that the corresponding request needs to @@ -153,7 +166,7 @@ impl ToCss for CssUrl { where W: Write, { - let string = match self.original { + let string = match self.0.original { Some(ref original) => &**original, None => match self.resolved { Some(ref url) => url.as_str(), @@ -191,16 +204,17 @@ impl ToComputedValue for SpecifiedUrl { } fn from_computed_value(computed: &ComputedUrl) -> Self { - match *computed { - ComputedUrl::Valid(ref url) => SpecifiedUrl { + let data = match *computed { + ComputedUrl::Valid(ref url) => CssUrlData { original: None, resolved: Some(url.clone()), }, - ComputedUrl::Invalid(ref url) => SpecifiedUrl { + ComputedUrl::Invalid(ref url) => CssUrlData { original: Some(url.clone()), resolved: None, }, - } + }; + CssUrl(Arc::new(data)) } } diff --git a/style/shadow_parts.rs b/style/shadow_parts.rs new file mode 100644 index 0000000000..1daa97f442 --- /dev/null +++ b/style/shadow_parts.rs @@ -0,0 +1,166 @@ +/* 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 crate::Atom; +use crate::values::AtomIdent; + +type Mapping<'a> = (&'a str, &'a str); + +#[derive(Clone, Debug, MallocSizeOf)] +pub struct ShadowParts { + // FIXME: Consider a smarter data structure for this. + // Gecko uses a hashtable in both directions: + // https://searchfox.org/mozilla-central/rev/5d4178378f84c7130ccb8ac1723d33e380d7f7d7/layout/style/ShadowParts.h + mappings: Vec<(Atom, Atom)> +} + +/// +/// +/// Returns `None` in the failure case. +pub fn parse_part_mapping(input: &str) -> Option { + // Step 1. Let input be the string being parsed. + // Step 2. Let position be a pointer into input, initially pointing at the start of the string. + // NOTE: We don't need an explicit position, we just drop stuff from the input + + // Step 3. Collect a sequence of code points that are space characters + let input = input.trim_start_matches(|c| c == ' '); + + // Step 4. Collect a sequence of code points that are not space characters or U+003A COLON characters, + // and let first token be the result. + let space_or_colon_position = input.char_indices().find(|(_, c)| matches!(c, ' ' | ':')).map(|(index, _)| index).unwrap_or(input.len()); + let (first_token, input) = input.split_at(space_or_colon_position); + + // Step 5. If first token is empty then return error. + if first_token.is_empty() { + return None; + } + + // Step 6. Collect a sequence of code points that are space characters. + let input = input.trim_start_matches(|c| c == ' '); + + // Step 7. If the end of the input has been reached, return the tuple (first token, first token) + if input.is_empty() { + return Some((first_token, first_token)); + } + + // Step 8. If character at position is not a U+003A COLON character, return error. + // Step 9. Consume the U+003A COLON character. + let Some(input) = input.strip_prefix(':') else { + return None; + }; + + // Step 10. Collect a sequence of code points that are space characters. + let input = input.trim_start_matches(|c| c == ' '); + + // Step 11. Collect a sequence of code points that are not space characters or U+003A COLON characters. + // and let second token be the result. + let space_or_colon_position = input.char_indices().find(|(_, c)| matches!(c, ' ' | ':')).map(|(index, _)| index).unwrap_or(input.len()); + let (second_token, input) = input.split_at(space_or_colon_position); + + // Step 12. If second token is empty then return error. + if second_token.is_empty() { + return None; + } + + // Step 13. Collect a sequence of code points that are space characters. + let input = input.trim_start_matches(|c| c == ' '); + + // Step 14. If position is not past the end of input then return error. + if !input.is_empty() { + return None; + } + + // Step 14. Return the tuple (first token, second token). + Some((first_token, second_token)) +} + +/// +fn parse_mapping_list(input: &str) -> impl Iterator { + // Step 1. Let input be the string being parsed. + // Step 2. Split the string input on commas. Let unparsed mappings be the resulting list of strings. + let unparsed_mappings = input.split(','); + + // Step 3. Let mappings be an initially empty list of tuples of tokens. + // This list will be the result of this algorithm. + // NOTE: We return an iterator here - it is up to the caller to turn it into a list + + // Step 4. For each string unparsed mapping in unparsed mappings, run the following substeps: + unparsed_mappings.filter_map(|unparsed_mapping| { + // Step 4.1 If unparsed mapping is empty or contains only space characters, + // continue to the next iteration of the loop. + if unparsed_mapping.chars().all(|c| c == ' ') { + return None; + } + + // Step 4.2 Let mapping be the result of parsing unparsed mapping using the rules for parsing part mappings. + // Step 4.3 If mapping is an error then continue to the next iteration of the loop. + // This allows clients to skip over new syntax that is not understood. + // Step 4.4 Append mapping to mappings. + parse_part_mapping(unparsed_mapping) + }) +} + +impl ShadowParts { + pub fn parse(input: &str) -> Self { + Self { + mappings: parse_mapping_list(input).map(|(first,second)| (first.into(), second.into())).collect(), + } + } + + /// Call the provided callback for each exported part with the given name. + pub fn for_each_exported_part(&self, name: &Atom, mut callback: F) + where F: FnMut(&AtomIdent) { + for (from, to) in &self.mappings { + if from == name { + callback(AtomIdent::cast(to)); + } + } + } + + pub fn imported_part(&self, name: &Atom) -> Option<&Atom> { + self.mappings.iter().find(|(_, to)| { + to == name + }).map(|(from, _)| from) + } +} + + +#[cfg(test)] +mod tests { + use crate::shadow_parts::parse_mapping_list; + + use super::*; + + #[test] + fn parse_valid_mapping() { + assert_eq!(parse_part_mapping("foo"), Some(("foo", "foo")), "Single token"); + assert_eq!(parse_part_mapping(" foo"), Some(("foo", "foo")), "Single token with leading whitespace"); + assert_eq!(parse_part_mapping("foo "), Some(("foo", "foo")), "Single token with trailing whitespace"); + assert_eq!(parse_part_mapping("foo:bar"), Some(("foo", "bar")), "Two tokens"); + assert_eq!(parse_part_mapping("foo:bar "), Some(("foo", "bar")), "Two tokens with trailing whitespace"); + assert_eq!(parse_part_mapping("🦀:🚀"), Some(("🦀", "🚀")), "Two tokens consisting of non-ascii characters"); + } + + #[test] + fn reject_invalid_mapping() { + assert!(parse_part_mapping("").is_none(), "Empty input"); + assert!(parse_part_mapping(" ").is_none(), "Only whitespace"); + assert!(parse_part_mapping("foo bar").is_none(), "Missing colon"); + assert!(parse_part_mapping(":bar").is_none(), "Empty first token"); + assert!(parse_part_mapping("foo:").is_none(), "Empty second token"); + assert!(parse_part_mapping("foo:bar baz").is_none(), "Trailing input"); + } + + #[test] + fn parse_valid_mapping_list() { + let mut mappings = parse_mapping_list("foo: bar, totally-invalid-mapping,,"); + + // "foo: bar" is a valid mapping + assert_eq!(mappings.next(), Some(("foo", "bar")), "First mapping should be in the list"); + // "totally-invalid-mapping" is not a valid mapping and should be ignored + // "" is not valid (and consists of nothing but whitespace), so it should be ignored + assert!(mappings.next().is_none(), "No more mappings should exist"); + } + +} diff --git a/style/stylesheets/stylesheet.rs b/style/stylesheets/stylesheet.rs index 48671c8546..10a50b3e77 100644 --- a/style/stylesheets/stylesheet.rs +++ b/style/stylesheets/stylesheet.rs @@ -125,13 +125,12 @@ impl StylesheetContents { /// An empty namespace map should be fine, as it is only used for parsing, /// not serialization of existing selectors. Since UA sheets are read only, /// we should never need the namespace map. - pub fn from_shared_data( + pub fn from_data( rules: Arc>, origin: Origin, url_data: UrlExtraData, quirks_mode: QuirksMode, ) -> Arc { - debug_assert!(rules.is_static()); Arc::new(Self { rules, origin, @@ -145,6 +144,17 @@ impl StylesheetContents { }) } + /// Same as above, but ensuring that the rules are static. + pub fn from_shared_data( + rules: Arc>, + origin: Origin, + url_data: UrlExtraData, + quirks_mode: QuirksMode, + ) -> Arc { + debug_assert!(rules.is_static()); + Self::from_data(rules, origin, url_data, quirks_mode) + } + /// Returns a reference to the list of rules. #[inline] pub fn rules<'a, 'b: 'a>(&'a self, guard: &'b SharedRwLockReadGuard) -> &'a [CssRule] { diff --git a/style/thread_state.rs b/style/thread_state.rs index e07a567fe7..e578287add 100644 --- a/style/thread_state.rs +++ b/style/thread_state.rs @@ -2,16 +2,16 @@ * 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/. */ -//! Supports dynamic assertions in about what sort of thread is running and +//! Supports dynamic assertions about what sort of thread is running and //! what state it's in. #![deny(missing_docs)] -use std::cell::RefCell; +use std::cell::Cell; bitflags! { /// A thread state flag, used for multiple assertions. - #[derive(Clone, Copy, Debug, Eq, PartialEq)] + #[derive(Clone, Copy, Default, Debug, Eq, PartialEq)] pub struct ThreadState: u32 { /// Whether we're in a script thread. const SCRIPT = 0x01; @@ -27,38 +27,34 @@ bitflags! { } } -macro_rules! thread_types ( ( $( $fun:ident = $flag:path ; )* ) => ( - impl ThreadState { - /// Whether the current thread is a worker thread. - pub fn is_worker(self) -> bool { - self.contains(ThreadState::IN_WORKER) - } +impl ThreadState { + /// Whether the current thread is a worker thread. + pub fn is_worker(self) -> bool { + self.contains(ThreadState::IN_WORKER) + } - $( - #[allow(missing_docs)] - pub fn $fun(self) -> bool { - self.contains($flag) - } - )* + /// Whether the current thread is a script thread. + pub fn is_script(self) -> bool { + self.contains(ThreadState::SCRIPT) } -)); -thread_types! { - is_script = ThreadState::SCRIPT; - is_layout = ThreadState::LAYOUT; + /// Whether the current thread is a layout thread. + pub fn is_layout(self) -> bool { + self.contains(ThreadState::LAYOUT) + } } -thread_local!(static STATE: RefCell> = RefCell::new(None)); +thread_local!(static STATE: Cell> = const { Cell::new(None) }); /// Initializes the current thread state. -pub fn initialize(x: ThreadState) { - STATE.with(|ref k| { - if let Some(ref s) = *k.borrow() { - if x != *s { - panic!("Thread state already initialized as {:?}", s); +pub fn initialize(initialize_to: ThreadState) { + STATE.with(|state| { + if let Some(current_state) = state.get() { + if initialize_to != current_state { + panic!("Thread state already initialized as {:?}", current_state); } } - *k.borrow_mut() = Some(x); + state.set(Some(initialize_to)); }); } @@ -69,30 +65,25 @@ pub fn initialize_layout_worker_thread() { /// Gets the current thread state. pub fn get() -> ThreadState { - let state = STATE.with(|ref k| { - match *k.borrow() { - None => ThreadState::empty(), // Unknown thread. - Some(s) => s, - } - }); - - state + STATE.with(|state| { + state.get().unwrap_or_default() + }) } -/// Enters into a given temporary state. Panics if re-entring. -pub fn enter(x: ThreadState) { - let state = get(); - debug_assert!(!state.intersects(x)); - STATE.with(|ref k| { - *k.borrow_mut() = Some(state | x); +/// Enters into a given temporary state. Panics if re-entering. +pub fn enter(additional_flags: ThreadState) { + STATE.with(|state| { + let current_state = state.get().unwrap_or_default(); + debug_assert!(!current_state.intersects(additional_flags)); + state.set(Some(current_state | additional_flags)); }) } /// Exits a given temporary state. -pub fn exit(x: ThreadState) { - let state = get(); - debug_assert!(state.contains(x)); - STATE.with(|ref k| { - *k.borrow_mut() = Some(state & !x); +pub fn exit(flags_to_remove: ThreadState) { + STATE.with(|state| { + let current_state = state.get().unwrap_or_default(); + debug_assert!(current_state.contains(flags_to_remove)); + state.set(Some(current_state & !flags_to_remove)); }) } diff --git a/style/traversal.rs b/style/traversal.rs index eb53a342d1..ab956f1c75 100644 --- a/style/traversal.rs +++ b/style/traversal.rs @@ -6,7 +6,7 @@ use crate::context::{ElementCascadeInputs, SharedStyleContext, StyleContext}; use crate::data::{ElementData, ElementStyles, RestyleKind}; -use crate::dom::{NodeInfo, OpaqueNode, TElement, TNode}; +use crate::dom::{NodeInfo, OpaqueNode, TElement, TNode, TRestyleDamage}; use crate::invalidation::element::restyle_hints::RestyleHint; use crate::matching::{ChildRestyleRequirement, MatchMethods}; use crate::selector_parser::PseudoElement; @@ -15,6 +15,7 @@ use crate::style_resolver::{PseudoElementResolution, StyleResolverForElement}; use crate::stylist::RuleInclusion; use crate::traversal_flags::TraversalFlags; use selectors::matching::SelectorCaches; +#[cfg(feature = "gecko")] use selectors::parser::PseudoElement as PseudoElementTrait; use smallvec::SmallVec; use std::collections::HashMap; @@ -202,7 +203,7 @@ pub trait DomTraversal: Sync { /// Returns true if traversal should visit a text node. The style system /// never processes text nodes, but Servo overrides this to visit them for /// flow construction when necessary. - fn text_node_needs_traversal(node: E::ConcreteNode, _parent_data: &ElementData) -> bool { + fn text_node_needs_traversal(node: E::ConcreteNode, _parent_data: &ElementData) -> bool { debug_assert!(node.is_text_node()); false } @@ -211,7 +212,7 @@ pub trait DomTraversal: Sync { fn element_needs_traversal( el: E, traversal_flags: TraversalFlags, - data: Option<&ElementData>, + data: Option<&ElementData>, ) -> bool { debug!( "element_needs_traversal({:?}, {:?}, {:?})", @@ -391,7 +392,7 @@ pub fn recalc_style_at( traversal_data: &PerLevelTraversalData, context: &mut StyleContext, element: E, - data: &mut ElementData, + data: &mut ElementData, note_child: F, ) where E: TElement, @@ -538,7 +539,7 @@ pub fn recalc_style_at( clear_state_after_traversing(element, data, flags); } -fn clear_state_after_traversing(element: E, data: &mut ElementData, flags: TraversalFlags) +fn clear_state_after_traversing(element: E, data: &mut ElementData, flags: TraversalFlags) where E: TElement, { @@ -555,7 +556,7 @@ fn compute_style( traversal_data: &PerLevelTraversalData, context: &mut StyleContext, element: E, - data: &mut ElementData, + data: &mut ElementData, kind: RestyleKind, ) -> ChildRestyleRequirement where @@ -693,7 +694,7 @@ where } #[cfg(feature = "servo")] -fn notify_paint_worklet(context: &StyleContext, data: &ElementData) +fn notify_paint_worklet(context: &StyleContext, data: &ElementData) where E: TElement, { @@ -731,7 +732,7 @@ where } #[cfg(not(feature = "servo"))] -fn notify_paint_worklet(_context: &StyleContext, _data: &ElementData) +fn notify_paint_worklet(_context: &StyleContext, _data: &ElementData) where E: TElement, { @@ -741,7 +742,7 @@ where fn note_children( context: &mut StyleContext, element: E, - data: &ElementData, + data: &ElementData, propagated_hint: RestyleHint, is_initial_style: bool, mut note_child: F, diff --git a/style/values/computed/image.rs b/style/values/computed/image.rs index 675f2b196b..de308015ab 100644 --- a/style/values/computed/image.rs +++ b/style/values/computed/image.rs @@ -32,7 +32,7 @@ pub type Image = generic::GenericImage diff --git a/style/values/computed/length_percentage.rs b/style/values/computed/length_percentage.rs index c121cb7a9c..2de6f8ea41 100644 --- a/style/values/computed/length_percentage.rs +++ b/style/values/computed/length_percentage.rs @@ -31,6 +31,7 @@ use crate::logical_geometry::{PhysicalAxis, PhysicalSide}; use crate::values::animated::{Animate, Context as AnimatedContext, Procedure, ToAnimatedValue, ToAnimatedZero}; use crate::values::distance::{ComputeSquaredDistance, SquaredDistance}; use crate::values::generics::calc::{CalcUnits, PositivePercentageBasis}; +#[cfg(feature = "gecko")] use crate::values::generics::length::AnchorResolutionResult; use crate::values::generics::position::{AnchorSideKeyword, GenericAnchorSide}; use crate::values::generics::{calc, NonNegative}; diff --git a/style/values/generics/image.rs b/style/values/generics/image.rs index 9de4e03ed1..7b825ecd5d 100644 --- a/style/values/generics/image.rs +++ b/style/values/generics/image.rs @@ -48,7 +48,7 @@ pub enum GenericImage { /// A paint worklet image. /// #[cfg(feature = "servo")] - PaintWorklet(PaintWorklet), + PaintWorklet(Box), /// A `` image. Storing this directly inside of /// GenericImage increases the size by 8 bytes so we box it here diff --git a/style/values/resolved/counters.rs b/style/values/resolved/counters.rs index 373c694ffd..f37355aa98 100644 --- a/style/values/resolved/counters.rs +++ b/style/values/resolved/counters.rs @@ -5,6 +5,7 @@ //! Resolved values for counter properties use super::{Context, ToResolvedValue}; +#[cfg(feature = "gecko")] use selectors::parser::PseudoElement; use crate::values::computed; diff --git a/style/values/specified/image.rs b/style/values/specified/image.rs index b1fb806ab0..b28237dc6e 100644 --- a/style/values/specified/image.rs +++ b/style/values/specified/image.rs @@ -43,10 +43,7 @@ fn gradient_color_interpolation_method_enabled() -> bool { pub type Image = generic::Image; // Images should remain small, see https://github.com/servo/servo/pull/18430 -#[cfg(feature = "gecko")] size_of_test!(Image, 16); -#[cfg(feature = "servo")] -size_of_test!(Image, 40); /// Specified values for a CSS gradient. /// @@ -246,7 +243,7 @@ impl Image { let function = input.expect_function()?.clone(); input.parse_nested_block(|input| Ok(match_ignore_ascii_case! { &function, #[cfg(feature = "servo")] - "paint" => Self::PaintWorklet(PaintWorklet::parse_args(context, input)?), + "paint" => Self::PaintWorklet(Box::new(::parse_args(context, input)?)), "cross-fade" if cross_fade_enabled() => Self::CrossFade(Box::new(CrossFade::parse_args(context, input, cors_mode, flags)?)), "light-dark" if image_light_dark_enabled(context) => Self::LightDark(Box::new(GenericLightDark::parse_args_with(input, |input| { Self::parse_with_cors_mode(context, input, cors_mode, flags) diff --git a/style/values/specified/text.rs b/style/values/specified/text.rs index 0813de6eb5..a8a3563591 100644 --- a/style/values/specified/text.rs +++ b/style/values/specified/text.rs @@ -398,10 +398,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; @@ -428,6 +433,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..e5c6b63698 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 = "0.6.0" 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..fe6458d50a 100644 --- a/style_traits/Cargo.toml +++ b/style_traits/Cargo.toml @@ -1,11 +1,12 @@ [package] -name = "style_traits" -version = "0.0.1" +name = "stylo_traits" +version = "0.6.0" 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" @@ -13,21 +14,20 @@ path = "lib.rs" [features] 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 = { version = "0.6", path = "../malloc_size_of", package = "stylo_malloc_size_of" } +malloc_size_of_derive = "0.1" +selectors = { version = "0.31", path = "../selectors" } serde = "1.0" -servo_arc = { path = "../servo_arc" } -stylo_atoms = { path = "../atoms", optional = true } +servo_arc = { version = "0.4", path = "../servo_arc" } +stylo_atoms = { version = "0.6", path = "../stylo_atoms", optional = true } thin-vec = "0.2" -to_shmem = { path = "../to_shmem" } -to_shmem_derive = { path = "../to_shmem_derive" } +to_shmem = { version = "0.2", path = "../to_shmem" } +to_shmem_derive = { version = "0.1", path = "../to_shmem_derive" } url = { version = "2.5", optional = true } diff --git a/stylo_atoms/Cargo.toml b/stylo_atoms/Cargo.toml new file mode 100644 index 0000000000..d80ff756e2 --- /dev/null +++ b/stylo_atoms/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "stylo_atoms" +version = "0.6.0" +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..1692c21634 --- /dev/null +++ b/stylo_config/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "stylo_config" +version = "0.6.0" +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..6283320d12 --- /dev/null +++ b/stylo_dom/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "stylo_dom" +version = "0.6.0" +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 = { version = "0.6", path = "../malloc_size_of", package = "stylo_malloc_size_of" } 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..54fa0950b1 --- /dev/null +++ b/stylo_static_prefs/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "stylo_static_prefs" +version = "0.6.0" +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..61a6bc2cdb --- /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.basic-shape-rect.enabled") => { + true + }; + ("layout.css.basic-shape-xywh.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 + }; + ($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..58147a5e1d 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,7 +23,7 @@ string_cache = ["dep:string_cache"] thin-vec = ["dep:thin-vec"] [dependencies] -cssparser = { version = "0.34", optional = true } +cssparser = { version = "0.35", optional = true } servo_arc = { version = "0.4.0", path = "../servo_arc", optional = true } smallbitvec = { version = "2.3.0", optional = true } smallvec = { version = "1.13", optional = true }