From 60ca97f79acde48b6522e1413125eaac5b8c678b Mon Sep 17 00:00:00 2001 From: Delan Azabani Date: Wed, 7 Feb 2024 17:52:11 +0800 Subject: [PATCH 1/8] Servo initial downstream commit Any ancestors of this commit are from upstream mozilla-central, with some filtering and renaming. Our patches and sync tooling start here. The sync tooling has all been squashed into this commit, based on: https://github.com/servo/stylo/commits/64731e10dc8ef87ef52aa2fb9f988c3b2530f3a7 --- .github/workflows/main.yml | 51 +++++++++++ .../workflows/mirror-to-release-branch.yml | 26 ++++++ .github/workflows/sync-upstream.yml | 23 +++++ .gitignore | 4 + README.md | 88 +++++++++++++++++++ commit-from-merge.sh | 15 ++++ commit-from-squashed.sh | 14 +++ shell.nix | 6 ++ start-rebase.sh | 10 +++ style.paths | 8 ++ sync.sh | 43 +++++++++ 11 files changed, 288 insertions(+) create mode 100644 .github/workflows/main.yml create mode 100644 .github/workflows/mirror-to-release-branch.yml create mode 100644 .github/workflows/sync-upstream.yml create mode 100644 .gitignore create mode 100644 README.md create mode 100755 commit-from-merge.sh create mode 100755 commit-from-squashed.sh create mode 100644 shell.nix create mode 100755 start-rebase.sh create mode 100644 style.paths create mode 100755 sync.sh diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000000..aafc8d0e84 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,51 @@ +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 + + 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..992fe6d2ce --- /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 master:upstream + git push -fu --progress origin upstream diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..52d314bb6a --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/_cache/ +/_filtered/ +/target/ +/style/properties/__pycache__/ diff --git a/README.md b/README.md new file mode 100644 index 0000000000..198c0db215 --- /dev/null +++ b/README.md @@ -0,0 +1,88 @@ +Stylo +===== + +This repo contains Servo’s downstream fork of [Stylo](https://searchfox.org/mozilla-central/source/servo). + +The branches are as follows: + +- [`upstream`](https://github.com/servo/style/tree/upstream) has upstream mozilla-central filtered to the paths we care about ([style.paths](style.paths)), but is otherwise unmodified +- [`main`](https://github.com/servo/style/tree/ci) has our downstream patches, plus the scripts and workflows for syncing with mozilla-central, to be rebased onto `upstream` + +## Building Servo against your local 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.git"] +derive_common = { path = "../stylo/derive_common" } +malloc_size_of = { path = "../stylo/malloc_size_of" } +selectors = { path = "../stylo/selectors" } +servo_arc = { path = "../stylo/servo_arc" } +servo_atoms = { path = "../stylo/atoms" } +size_of_test = { path = "../stylo/size_of_test" } +static_prefs = { path = "../stylo/style_static_prefs" } +style_config = { path = "../stylo/style_config" } +style_derive = { path = "../stylo/style_derive" } +style = { path = "../stylo/style" } +style_traits = { path = "../stylo/style_traits" } +``` + +## 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 master: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/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/sync.sh b/sync.sh new file mode 100755 index 0000000000..e92182c746 --- /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 --progress https://github.com/mozilla/gecko-dev.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" From dfed17bd04a713f5dce775176c3a28c39c934970 Mon Sep 17 00:00:00 2001 From: Delan Azabani Date: Thu, 15 Feb 2024 18:30:14 +0800 Subject: [PATCH 2/8] Commit our changes on top of upstream Stylo This is a rebase of fbd6a1448bb699000ae18067ea1b455e4cce7c97 Signed-off-by: Oriol Brufau --- .gitignore | 1 + Cargo.toml | 16 + atoms/Cargo.toml | 17 + atoms/build.rs | 31 ++ atoms/lib.rs | 5 + atoms/static_atoms.txt | 179 +++++++++ dom/Cargo.toml | 14 + dom/lib.rs | 164 ++++++++ malloc_size_of/Cargo.toml | 4 +- selectors/tree.rs | 5 + style/Cargo.toml | 20 +- style/README.md | 2 +- style/animation.rs | 10 + style/build.rs | 2 +- style/context.rs | 4 +- style/encoding_support.rs | 2 - style/font_face.rs | 12 - style/gecko/media_features.rs | 11 +- style/global_style_data.rs | 2 +- .../invalidation/element/invalidation_map.rs | 2 +- .../invalidation/element/relative_selector.rs | 2 + style/lib.rs | 1 + style/matching.rs | 13 +- .../Mako-1.1.2-py2.py3-none-any.whl | Bin 0 -> 75521 bytes style/properties/build.py | 8 +- style/properties/cascade.rs | 58 +-- style/properties/declaration_block.rs | 2 +- style/properties/helpers.mako.rs | 2 +- .../helpers/animated_properties.mako.rs | 2 +- style/properties/longhands/counters.mako.rs | 2 + .../longhands/inherited_text.mako.rs | 2 + style/properties/longhands/list.mako.rs | 1 + style/properties/longhands/text.mako.rs | 1 + style/properties/mod.rs | 15 +- style/properties/properties.mako.rs | 170 ++++---- style/properties/shorthands/font.mako.rs | 6 +- style/properties_and_values/registry.rs | 1 + style/queries/values.rs | 9 + style/servo/media_queries.rs | 270 +++++++++++-- style/servo/restyle_damage.rs | 4 +- style/servo/selector_parser.rs | 374 +++++++++++++----- style/str.rs | 8 + style/stylesheets/import_rule.rs | 42 +- style/stylesheets/mod.rs | 4 +- style/stylesheets/position_try_rule.rs | 1 + style/stylesheets/stylesheet.rs | 14 +- style/stylist.rs | 13 +- style/values/animated/effects.rs | 2 +- style/values/computed/effects.rs | 2 +- style/values/computed/font.rs | 2 +- style/values/computed/image.rs | 2 +- style/values/computed/length.rs | 54 ++- style/values/computed/length_percentage.rs | 9 +- style/values/generics/image.rs | 4 +- style/values/generics/length.rs | 40 +- style/values/generics/position.rs | 7 + style/values/generics/transform.rs | 7 + style/values/mod.rs | 12 +- style/values/specified/animation.rs | 5 +- style/values/specified/box.rs | 10 +- style/values/specified/effects.rs | 2 +- style/values/specified/font.rs | 39 +- style/values/specified/image.rs | 6 +- style/values/specified/length.rs | 13 +- style/values/specified/list.rs | 2 + style/values/specified/text.rs | 20 +- style_config/Cargo.toml | 14 + style_config/lib.rs | 95 +++++ style_static_prefs/Cargo.toml | 7 + style_static_prefs/src/lib.rs | 39 ++ style_traits/Cargo.toml | 5 +- style_traits/lib.rs | 4 +- style_traits/values.rs | 1 + 73 files changed, 1528 insertions(+), 403 deletions(-) create mode 100644 Cargo.toml create mode 100644 atoms/Cargo.toml create mode 100644 atoms/build.rs create mode 100644 atoms/lib.rs create mode 100644 atoms/static_atoms.txt create mode 100644 dom/Cargo.toml create mode 100644 dom/lib.rs create mode 100644 style/properties/Mako-1.1.2-py2.py3-none-any.whl create mode 100644 style_config/Cargo.toml create mode 100644 style_config/lib.rs create mode 100644 style_static_prefs/Cargo.toml create mode 100644 style_static_prefs/src/lib.rs diff --git a/.gitignore b/.gitignore index 52d314bb6a..fc3c2f9b3c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ /_filtered/ /target/ /style/properties/__pycache__/ +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000000..72d5501272 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,16 @@ +[workspace] +resolver = "2" +members = [ + "dom", + "malloc_size_of", + "rustfmt.toml", + "selectors", + "servo_arc", + "style", + "style_derive", + "style_static_prefs", + "style_traits", + "to_shmem", + "to_shmem_derive", +] +default-members = ["style"] diff --git a/atoms/Cargo.toml b/atoms/Cargo.toml new file mode 100644 index 0000000000..aa232e86e5 --- /dev/null +++ b/atoms/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "servo_atoms" +version = "0.0.1" +authors = ["The Servo Project Developers"] +license = "MPL-2.0" +edition = "2018" +publish = false +build = "build.rs" + +[lib] +path = "lib.rs" + +[dependencies] +string_cache = "0.8" + +[build-dependencies] +string_cache_codegen = "0.5" diff --git a/atoms/build.rs b/atoms/build.rs new file mode 100644 index 0000000000..6bd2de3703 --- /dev/null +++ b/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!("../style/counter_style/predefined.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/atoms/lib.rs b/atoms/lib.rs new file mode 100644 index 0000000000..03560a40c0 --- /dev/null +++ b/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/atoms/static_atoms.txt b/atoms/static_atoms.txt new file mode 100644 index 0000000000..d658fcf1c6 --- /dev/null +++ b/atoms/static_atoms.txt @@ -0,0 +1,179 @@ +-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-menu-radius +-moz-mac-titlebar-height +-moz-overlay-scrollbar-fade-duration +DOMContentLoaded +abort +activate +addtrack +animationcancel +animationend +animationiteration +animationstart +aspect-ratio +beforeunload +block-size +button +canplay +canplaythrough +center +change +characteristicvaluechanged +checkbox +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 +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 +signalingstatechange +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/dom/Cargo.toml b/dom/Cargo.toml new file mode 100644 index 0000000000..9d678fa5df --- /dev/null +++ b/dom/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "dom" +version = "0.0.1" +authors = ["The Servo Project Developers"] +license = "MPL-2.0" +edition = "2021" +publish = false + +[lib] +path = "lib.rs" + +[dependencies] +bitflags = "2" +malloc_size_of = { path = "../malloc_size_of" } diff --git a/dom/lib.rs b/dom/lib.rs new file mode 100644 index 0000000000..9137763491 --- /dev/null +++ b/dom/lib.rs @@ -0,0 +1,164 @@ +/* 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; + +// 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; + + /// 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/malloc_size_of/Cargo.toml b/malloc_size_of/Cargo.toml index eefd6cd89a..68defa0d61 100644 --- a/malloc_size_of/Cargo.toml +++ b/malloc_size_of/Cargo.toml @@ -20,7 +20,7 @@ euclid = "0.22" selectors = { path = "../selectors" } servo_arc = { 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/tree.rs b/selectors/tree.rs index c4765ebb78..fa2df7a13e 100644 --- a/selectors/tree.rs +++ b/selectors/tree.rs @@ -31,6 +31,11 @@ impl OpaqueElement { } } + /// Creates a new OpaqueElement from a type-erased non-null pointer + pub fn from_non_null_ptr(ptr: NonNull<()>) -> Self { + Self(ptr) + } + /// Returns a const ptr to the contained reference. pub unsafe fn as_const_ptr(&self) -> *const T { self.0.as_ptr() as *const T diff --git a/style/Cargo.toml b/style/Cargo.toml index 58de5ba26a..d37a742e7e 100644 --- a/style/Cargo.toml +++ b/style/Cargo.toml @@ -31,15 +31,15 @@ gecko = [ "to_shmem/gecko", ] servo = [ - "arrayvec/use_union", "cssparser/serde", "encoding_rs", "malloc_size_of/servo", "markup5ever", + "mime", "serde", "servo_arc/servo", "servo_atoms", - "servo_config", + "style_config", "string_cache", "style_traits/servo", "url", @@ -48,6 +48,7 @@ servo = [ ] gecko_debug = [] gecko_refcount_logging = [] +nsstring = [] [dependencies] app_units = "0.7" @@ -57,7 +58,7 @@ bitflags = "2" byteorder = "1.0" cssparser = "0.34" derive_more = { version = "0.99", default-features = false, features = ["add", "add_assign", "deref", "deref_mut", "from"] } -dom = { path = "../../../dom/base/rust" } +dom = { path = "../dom" } new_debug_unreachable = "1.0" encoding_rs = {version = "0.8", optional = true} euclid = "0.22" @@ -69,10 +70,10 @@ 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_derive = "0.1" +markup5ever = { version = "0.14", 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" @@ -85,21 +86,20 @@ selectors = { path = "../selectors" } serde = {version = "1.0", optional = true, features = ["derive"]} servo_arc = { path = "../servo_arc" } servo_atoms = {path = "../atoms", optional = true} -servo_config = {path = "../config", optional = true} smallbitvec = "2.3.0" smallvec = "1.0" static_assertions = "1.1" -static_prefs = { path = "../../../modules/libpref/init/static_prefs" } +static_prefs = { path = "../style_static_prefs" } string_cache = { version = "0.8", optional = true } +style_config = { path = "../style_config", 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"] } +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/README.md b/style/README.md index 96457e1b30..bdbe36e44c 100644 --- a/style/README.md +++ b/style/README.md @@ -3,4 +3,4 @@ servo-style Style system for Servo, using [rust-cssparser](https://github.com/servo/rust-cssparser) for parsing. - * [Documentation](https://github.com/servo/servo/blob/master/docs/components/style.md). + * [Documentation](https://github.com/servo/servo/blob/main/docs/components/style.md). diff --git a/style/animation.rs b/style/animation.rs index 29c98b0c7e..f90b9df81f 100644 --- a/style/animation.rs +++ b/style/animation.rs @@ -27,6 +27,7 @@ use crate::stylesheets::layer_rule::LayerOrder; use crate::values::animated::{Animate, Procedure}; use crate::values::computed::{Time, TimingFunction}; use crate::values::generics::easing::BeforeFlag; +use crate::values::specified::TransitionBehavior; use crate::Atom; use fxhash::FxHashMap; use parking_lot::RwLock; @@ -1030,6 +1031,15 @@ impl ElementAnimationSet { new_style: &Arc, ) { let style = new_style.get_ui(); + + #[cfg(feature = "servo")] + if !property_declaration_id.is_animatable() || + (style.transition_behavior_mod(index) != TransitionBehavior::AllowDiscrete && + property_declaration_id.is_discrete_animatable()) + { + return; + } + let timing_function = style.transition_timing_function_mod(index); let duration = style.transition_duration_mod(index); let delay = style.transition_delay_mod(index).seconds() as f64; diff --git a/style/build.rs b/style/build.rs index 4b27edbe2c..eacb9b3fc9 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"] }; diff --git a/style/context.rs b/style/context.rs index 1e4fcca15f..5337617689 100644 --- a/style/context.rs +++ b/style/context.rs @@ -475,7 +475,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")] @@ -551,7 +551,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/encoding_support.rs b/style/encoding_support.rs index c144ad0b3b..b07fccfd30 100644 --- a/style/encoding_support.rs +++ b/style/encoding_support.rs @@ -75,7 +75,6 @@ impl Stylesheet { stylesheet_loader, error_reporter, quirks_mode, - 0, AllowImportRules::Yes, ) } @@ -98,7 +97,6 @@ impl Stylesheet { url_data, stylesheet_loader, error_reporter, - 0, AllowImportRules::Yes, ) } diff --git a/style/font_face.rs b/style/font_face.rs index fd52874da8..9c117604b2 100644 --- a/style/font_face.rs +++ b/style/font_face.rs @@ -485,18 +485,6 @@ pub fn parse_font_face_block( #[cfg(feature = "servo")] pub struct FontFace<'a>(&'a FontFaceRuleData); -#[cfg(feature = "servo")] -impl Iterator for EffectiveSources { - type Item = Source; - fn next(&mut self) -> Option { - self.0.pop() - } - - fn size_hint(&self) -> (usize, Option) { - (self.0.len(), Some(self.0.len())) - } -} - struct FontFaceRuleParser<'a, 'b: 'a> { context: &'a ParserContext<'b>, rule: &'a mut FontFaceRuleData, diff --git a/style/gecko/media_features.rs b/style/gecko/media_features.rs index d0f7dad876..743697d8c5 100644 --- a/style/gecko/media_features.rs +++ b/style/gecko/media_features.rs @@ -8,7 +8,7 @@ use crate::gecko_bindings::bindings; use crate::gecko_bindings::structs; use crate::media_queries::{Device, MediaType}; use crate::queries::feature::{AllowsRanges, Evaluator, FeatureFlags, QueryFeatureDescription}; -use crate::queries::values::Orientation; +use crate::queries::values::{Orientation, PrefersColorScheme}; use crate::values::computed::{CSSPixelLength, Context, Ratio, Resolution}; use crate::values::specified::color::ForcedColors; use crate::values::AtomString; @@ -191,15 +191,6 @@ enum PrefersReducedTransparency { Reduce, } -/// Values for the prefers-color-scheme media feature. -#[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, ToCss)] -#[repr(u8)] -#[allow(missing_docs)] -pub enum PrefersColorScheme { - Light, - Dark, -} - /// Values for the dynamic-range and video-dynamic-range media features. /// https://drafts.csswg.org/mediaqueries-5/#dynamic-range /// This implements PartialOrd so that lower values will correctly match diff --git a/style/global_style_data.rs b/style/global_style_data.rs index d6e49e7da6..943e53d8fe 100644 --- a/style/global_style_data.rs +++ b/style/global_style_data.rs @@ -139,7 +139,7 @@ impl StyleThreadPool { #[cfg(feature = "servo")] fn stylo_threads_pref() -> i32 { - pref!(layout.threads) + style_config::get_i32("layout.threads") } #[cfg(feature = "gecko")] diff --git a/style/invalidation/element/invalidation_map.rs b/style/invalidation/element/invalidation_map.rs index 80e85bfc7b..f5b940f666 100644 --- a/style/invalidation/element/invalidation_map.rs +++ b/style/invalidation/element/invalidation_map.rs @@ -515,7 +515,7 @@ trait Collector { fn class_map(&mut self) -> &mut IdOrClassDependencyMap; fn state_map(&mut self) -> &mut StateDependencyMap; fn attribute_map(&mut self) -> &mut LocalNameDependencyMap; - fn custom_state_map(&mut self) -> &mut LocalNameDependencyMap; + fn custom_state_map(&mut self) -> &mut CustomStateDependencyMap; fn update_states(&mut self, element_state: ElementState, document_state: DocumentState); // In normal invalidations, type-based dependencies don't need to be explicitly tracked; diff --git a/style/invalidation/element/relative_selector.rs b/style/invalidation/element/relative_selector.rs index e633df468d..e951c2598d 100644 --- a/style/invalidation/element/relative_selector.rs +++ b/style/invalidation/element/relative_selector.rs @@ -22,6 +22,8 @@ use crate::invalidation::element::state_and_attributes::{ check_dependency, dependency_may_be_relevant, invalidated_descendants, invalidated_self, invalidated_sibling, push_invalidation, should_process_descendants, }; +#[cfg(feature = "servo")] +use crate::selector_parser::SnapshotMap as ServoElementSnapshotTable; use crate::stylist::{CascadeData, Stylist}; use dom::ElementState; use fxhash::FxHashMap; diff --git a/style/lib.rs b/style/lib.rs index 578ac5baba..bb69c986e8 100644 --- a/style/lib.rs +++ b/style/lib.rs @@ -173,6 +173,7 @@ pub use style_traits::owned_str::OwnedStr; use std::hash::{BuildHasher, Hash}; +#[macro_use] pub mod properties; #[cfg(feature = "gecko")] diff --git a/style/matching.rs b/style/matching.rs index ddb806e4f8..c1007a3f9d 100644 --- a/style/matching.rs +++ b/style/matching.rs @@ -8,6 +8,8 @@ #![deny(missing_docs)] use crate::computed_value_flags::ComputedValueFlags; +#[cfg(feature = "servo")] +use crate::context::CascadeInputs; use crate::context::{ElementCascadeInputs, QuirksMode}; use crate::context::{SharedStyleContext, StyleContext}; use crate::data::{ElementData, ElementStyles}; @@ -585,7 +587,8 @@ trait PrivateMatchMethods: TElement { // If we have modified animation or transitions, we recascade style for this node. if style_changed { - let mut rule_node = new_resolved_styles.primary_style().rules().clone(); + let primary_style = new_resolved_styles.primary_style(); + let mut rule_node = primary_style.rules().clone(); let declarations = context.shared.animations.get_all_declarations( &AnimationSetKey::new_for_non_pseudo(self.as_node().opaque()), context.shared.current_time_for_animations, @@ -594,20 +597,23 @@ trait PrivateMatchMethods: TElement { Self::replace_single_rule_node( &context.shared, CascadeLevel::Transitions, + LayerOrder::root(), declarations.transitions.as_ref().map(|a| a.borrow_arc()), &mut rule_node, ); Self::replace_single_rule_node( &context.shared, CascadeLevel::Animations, + LayerOrder::root(), declarations.animations.as_ref().map(|a| a.borrow_arc()), &mut rule_node, ); - if rule_node != *new_resolved_styles.primary_style().rules() { + if rule_node != *primary_style.rules() { let inputs = CascadeInputs { rules: Some(rule_node), - visited_rules: new_resolved_styles.primary_style().visited_rules().cloned(), + visited_rules: primary_style.visited_rules().cloned(), + flags: primary_style.flags.for_cascade_inputs(), }; new_resolved_styles.primary.style = StyleResolverForElement::new( @@ -696,6 +702,7 @@ trait PrivateMatchMethods: TElement { let inputs = CascadeInputs { rules: Some(rule_node), visited_rules: style.visited_rules().cloned(), + flags: style.flags.for_cascade_inputs(), }; let new_style = StyleResolverForElement::new( diff --git a/style/properties/Mako-1.1.2-py2.py3-none-any.whl b/style/properties/Mako-1.1.2-py2.py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..9593025a4739374da2fde2966afdaffa1d0b8e05 GIT binary patch literal 75521 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

wOg(?)H>s{J*n4v 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%0A>{n;f&I{@ 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 literal 0 HcmV?d00001 diff --git a/style/properties/build.py b/style/properties/build.py index d1f76d4a25..1a92544bfd 100644 --- a/style/properties/build.py +++ b/style/properties/build.py @@ -8,6 +8,7 @@ import sys BASE = os.path.dirname(__file__.replace("\\", "/")) +sys.path.insert(0, os.path.join(BASE, "Mako-1.1.2-py2.py3-none-any.whl")) sys.path.insert(0, BASE) # For importing `data.py` from mako import exceptions @@ -66,7 +67,12 @@ def main(): os.path.join(BASE, "properties.html.mako"), properties=properties_dict ) as_json = json.dumps(properties_dict, indent=4, sort_keys=True) - doc_servo = os.path.join(BASE, "..", "..", "..", "target", "doc", "servo") + + # Four dotdots: /path/to/target(4)/debug(3)/build(2)/style-*(1)/out + # Do not ascend above the target dir, because it may not be called target + # or even have a parent (see CARGO_TARGET_DIR). + doc_servo = os.path.join(OUT_DIR, "..", "..", "..", "..", "doc", "stylo") + write(doc_servo, "css-properties.html", as_html) write(doc_servo, "css-properties.json", as_json) write(OUT_DIR, "css-properties.json", as_json) diff --git a/style/properties/cascade.rs b/style/properties/cascade.rs index e38259dd39..56f0a13522 100644 --- a/style/properties/cascade.rs +++ b/style/properties/cascade.rs @@ -11,8 +11,6 @@ use crate::custom_properties::{ CustomPropertiesBuilder, DeferFontRelativeCustomPropertyResolution, }; use crate::dom::TElement; -#[cfg(feature = "gecko")] -use crate::font_metrics::FontMetricsOrientation; use crate::logical_geometry::WritingMode; use crate::properties::{ property_counts, CSSWideKeyword, ComputedValues, DeclarationImportanceIterator, Importance, @@ -761,7 +759,9 @@ impl<'b> Cascade<'b> { return; } - let has_writing_mode = apply!(WritingMode) | apply!(Direction) | apply!(TextOrientation); + let has_writing_mode = apply!(WritingMode) | apply!(Direction); + #[cfg(feature = "gecko")] + let has_writing_mode = has_writing_mode | apply!(TextOrientation); if has_writing_mode { context.builder.writing_mode = WritingMode::new(context.builder.get_inherited_box()) } @@ -781,29 +781,41 @@ impl<'b> Cascade<'b> { // Compute font-family. let has_font_family = apply!(FontFamily); let has_lang = apply!(XLang); - if has_lang { - self.recompute_initial_font_family_if_needed(&mut context.builder); - } - if has_font_family { - self.prioritize_user_fonts_if_needed(&mut context.builder); + + #[cfg(feature = "gecko")] { + if has_lang { + self.recompute_initial_font_family_if_needed(&mut context.builder); + } + if has_font_family { + self.prioritize_user_fonts_if_needed(&mut context.builder); + } } // Compute font-size. - if apply!(XTextScale) { - self.unzoom_fonts_if_needed(&mut context.builder); - } - let has_font_size = apply!(FontSize); - let has_math_depth = apply!(MathDepth); - let has_min_font_size_ratio = apply!(MozMinFontSizeRatio); + #[cfg(feature = "gecko")] { + if apply!(XTextScale) { + self.unzoom_fonts_if_needed(&mut context.builder); + } + let has_font_size = apply!(FontSize); + let has_math_depth = apply!(MathDepth); + let has_min_font_size_ratio = apply!(MozMinFontSizeRatio); - if has_math_depth && has_font_size { - self.recompute_math_font_size_if_needed(context); - } - if has_lang || has_font_family { - self.recompute_keyword_font_size_if_needed(context); + if has_math_depth && has_font_size { + self.recompute_math_font_size_if_needed(context); + } + if has_lang || has_font_family { + self.recompute_keyword_font_size_if_needed(context); + } + if has_font_size || has_min_font_size_ratio || has_lang || has_font_family { + self.constrain_font_size_if_needed(&mut context.builder); + } } - if has_font_size || has_min_font_size_ratio || has_lang || has_font_family { - self.constrain_font_size_if_needed(&mut context.builder); + #[cfg(feature = "servo")] + { + apply!(FontSize); + if has_lang || has_font_family { + self.recompute_keyword_font_size_if_needed(context); + } } // Compute the rest of the first-available-font-affecting properties. @@ -1035,6 +1047,7 @@ impl<'b> Cascade<'b> { builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_WORD_SPACING); } + #[cfg(feature = "gecko")] if self .author_specified .contains(LonghandId::FontSynthesisWeight) @@ -1042,6 +1055,7 @@ impl<'b> Cascade<'b> { builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_FONT_SYNTHESIS_WEIGHT); } + #[cfg(feature = "gecko")] if self .author_specified .contains(LonghandId::FontSynthesisStyle) @@ -1177,7 +1191,6 @@ impl<'b> Cascade<'b> { } /// Some keyword sizes depend on the font family and language. - #[cfg(feature = "gecko")] fn recompute_keyword_font_size_if_needed(&self, context: &mut computed::Context) { use crate::values::computed::ToComputedValue; @@ -1196,6 +1209,7 @@ impl<'b> Cascade<'b> { }, }; + #[cfg(feature = "gecko")] if font.mScriptUnconstrainedSize == new_size.computed_size { return; } diff --git a/style/properties/declaration_block.rs b/style/properties/declaration_block.rs index 49b1d020f7..78a3bdbfbb 100644 --- a/style/properties/declaration_block.rs +++ b/style/properties/declaration_block.rs @@ -940,7 +940,7 @@ impl PropertyDeclarationBlock { ); if let Some(cv) = computed_values { - context.builder.custom_properties = cv.custom_properties.clone(); + context.builder.custom_properties = cv.custom_properties().clone(); }; match (declaration, computed_values) { diff --git a/style/properties/helpers.mako.rs b/style/properties/helpers.mako.rs index 764ae9288a..3523516416 100644 --- a/style/properties/helpers.mako.rs +++ b/style/properties/helpers.mako.rs @@ -598,7 +598,7 @@ <%def name="inner_body(keyword, needs_conversion=False)"> pub use self::computed_value::T as SpecifiedValue; pub mod computed_value { - #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] + #[cfg_attr(feature = "servo", derive(Deserialize, Hash, Serialize))] #[derive(Clone, Copy, Debug, Eq, FromPrimitive, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToAnimatedValue, ToComputedValue, ToCss, ToResolvedValue, ToShmem)] pub enum T { % for variant in keyword.values_for(engine): diff --git a/style/properties/helpers/animated_properties.mako.rs b/style/properties/helpers/animated_properties.mako.rs index 513a198a6a..cf5665782b 100644 --- a/style/properties/helpers/animated_properties.mako.rs +++ b/style/properties/helpers/animated_properties.mako.rs @@ -396,7 +396,7 @@ impl AnimationValue { Some(animatable) } - /// Get an AnimationValue for an declaration id from a given computed values. + /// Get an AnimationValue for an AnimatableLonghand from a given computed values. pub fn from_computed_values( property: PropertyDeclarationId, style: &ComputedValues, diff --git a/style/properties/longhands/counters.mako.rs b/style/properties/longhands/counters.mako.rs index 80f5c50847..ae871d1d31 100644 --- a/style/properties/longhands/counters.mako.rs +++ b/style/properties/longhands/counters.mako.rs @@ -20,6 +20,7 @@ ${helpers.predefined_type( "counter-increment", "CounterIncrement", engines="gecko servo", + servo_pref="layout.legacy_layout", initial_value="Default::default()", animation_type="discrete", spec="https://drafts.csswg.org/css-lists/#propdef-counter-increment", @@ -31,6 +32,7 @@ ${helpers.predefined_type( "counter-reset", "CounterReset", engines="gecko servo", + servo_pref="layout.legacy_layout", initial_value="Default::default()", animation_type="discrete", spec="https://drafts.csswg.org/css-lists-3/#propdef-counter-reset", diff --git a/style/properties/longhands/inherited_text.mako.rs b/style/properties/longhands/inherited_text.mako.rs index 537cf2f486..49fb0dab1c 100644 --- a/style/properties/longhands/inherited_text.mako.rs +++ b/style/properties/longhands/inherited_text.mako.rs @@ -147,6 +147,7 @@ ${helpers.single_keyword( gecko_aliases="-moz-pre-space=preserve-spaces", engines="gecko servo", gecko_enum_prefix="StyleWhiteSpaceCollapse", + needs_conversion="True", animation_type="discrete", spec="https://drafts.csswg.org/css-text-4/#propdef-white-space-collapse", servo_restyle_damage="rebuild_and_reflow", @@ -381,6 +382,7 @@ ${helpers.single_keyword( "wrap nowrap", engines="gecko servo", gecko_enum_prefix="StyleTextWrapMode", + needs_conversion="True", animation_type="discrete", spec="https://drafts.csswg.org/css-text-4/#propdef-text-wrap-mode", servo_restyle_damage="rebuild_and_reflow", diff --git a/style/properties/longhands/list.mako.rs b/style/properties/longhands/list.mako.rs index 603b3b09d5..83b30b189b 100644 --- a/style/properties/longhands/list.mako.rs +++ b/style/properties/longhands/list.mako.rs @@ -69,6 +69,7 @@ ${helpers.predefined_type( "Quotes", "computed::Quotes::get_initial_value()", engines="gecko servo", + servo_pref="layout.legacy_layout", animation_type="discrete", spec="https://drafts.csswg.org/css-content/#propdef-quotes", servo_restyle_damage="rebuild_and_reflow", diff --git a/style/properties/longhands/text.mako.rs b/style/properties/longhands/text.mako.rs index 55b0928ba4..9cb725048b 100644 --- a/style/properties/longhands/text.mako.rs +++ b/style/properties/longhands/text.mako.rs @@ -9,6 +9,7 @@ ${helpers.predefined_type( "TextOverflow", "computed::TextOverflow::get_initial_value()", engines="gecko servo", + servo_pref="layout.legacy_layout", animation_type="discrete", boxed=True, spec="https://drafts.csswg.org/css-ui/#propdef-text-overflow", diff --git a/style/properties/mod.rs b/style/properties/mod.rs index a6a853abb1..d2299d6760 100644 --- a/style/properties/mod.rs +++ b/style/properties/mod.rs @@ -360,7 +360,10 @@ impl PropertyId { pub fn is_animatable(&self) -> bool { match self { Self::NonCustom(id) => id.is_animatable(), - Self::Custom(..) => true, + #[cfg(feature = "gecko")] + Self::Custom(_) => true, + #[cfg(feature = "servo")] + Self::Custom(_) => false, } } @@ -1086,7 +1089,10 @@ impl<'a> PropertyDeclarationId<'a> { pub fn is_animatable(&self) -> bool { match self { Self::Longhand(id) => id.is_animatable(), - Self::Custom(_) => true, + #[cfg(feature = "gecko")] + PropertyDeclarationId::Custom(_) => true, + #[cfg(feature = "servo")] + PropertyDeclarationId::Custom(_) => false, } } @@ -1096,7 +1102,10 @@ impl<'a> PropertyDeclarationId<'a> { match self { Self::Longhand(longhand) => longhand.is_discrete_animatable(), // TODO(bug 1885995): Refine this. + #[cfg(feature = "gecko")] Self::Custom(_) => true, + #[cfg(feature = "servo")] + Self::Custom(_) => false, } } @@ -1301,7 +1310,7 @@ pub struct SourcePropertyDeclaration { // This is huge, but we allocate it on the stack and then never move it, // we only pass `&mut SourcePropertyDeclaration` references around. -size_of_test!(SourcePropertyDeclaration, 632); +size_of_test!(SourcePropertyDeclaration, 568); impl SourcePropertyDeclaration { /// Create one with a single PropertyDeclaration. diff --git a/style/properties/properties.mako.rs b/style/properties/properties.mako.rs index 22b7758c85..84b15c0284 100644 --- a/style/properties/properties.mako.rs +++ b/style/properties/properties.mako.rs @@ -27,7 +27,6 @@ use crate::media_queries::Device; use crate::parser::ParserContext; use crate::selector_parser::PseudoElement; use crate::stylist::Stylist; -#[cfg(feature = "servo")] use servo_config::prefs; use style_traits::{CssWriter, KeywordsCollectFn, ParseError, SpecifiedValueInfo, StyleParseErrorKind, ToCss}; use crate::stylesheets::{CssRuleType, CssRuleTypes, Origin}; use crate::logical_geometry::{LogicalAxis, LogicalCorner, LogicalSide}; @@ -455,7 +454,7 @@ pub mod property_counts { /// The number of non-custom properties. pub const NON_CUSTOM: usize = LONGHANDS_AND_SHORTHANDS + ALIASES; /// The number of prioritary properties that we have. - pub const PRIORITARY: usize = ${len(PRIORITARY_PROPERTIES)}; + pub const PRIORITARY: usize = ${len(PRIORITARY_PROPERTIES.intersection(set(list(map(lambda p: p.name, data.longhands)))))}; /// The max number of longhands that a shorthand other than "all" expands to. pub const MAX_SHORTHAND_EXPANDED: usize = ${max(len(s.sub_properties) for s in data.shorthands_except_all())}; @@ -529,7 +528,7 @@ impl NonCustomPropertyId { Some(pref) => pref, }; - prefs::pref_map().get(pref).as_bool().unwrap_or(false) + style_config::get_bool(pref) % endif }; @@ -1375,9 +1374,6 @@ pub mod style_structs { use fxhash::FxHasher; use super::longhands; use std::hash::{Hash, Hasher}; - use crate::logical_geometry::WritingMode; - use crate::media_queries::Device; - use crate::values::computed::NonNegativeLength; use crate::values::specified::color::ColorSchemeFlags; % for style_struct in data.active_style_structs(): @@ -1507,8 +1503,6 @@ pub mod style_structs { /// Computes a font hash in order to be able to cache fonts /// effectively in GFX and layout. pub fn compute_font_hash(&mut self) { - // Corresponds to the fields in - // `gfx::font_template::FontTemplateDescriptor`. let mut hasher: FxHasher = Default::default(); self.font_weight.hash(&mut hasher); self.font_stretch.hash(&mut hasher); @@ -1516,25 +1510,17 @@ pub mod style_structs { self.font_family.hash(&mut hasher); self.hash = hasher.finish() } - - /// (Servo does not handle MathML, so this just calls copy_font_size_from) - pub fn inherit_font_size_from(&mut self, parent: &Self, - _: Option, - _: &Device) { - self.copy_font_size_from(parent); - } - /// (Servo does not handle MathML, so this just calls set_font_size) - pub fn apply_font_size(&mut self, - v: longhands::font_size::computed_value::T, - _: &Self, - _: &Device) -> Option { - self.set_font_size(v); - None - } - /// (Servo does not handle MathML, so this does nothing) - pub fn apply_unconstrained_font_size(&mut self, _: NonNegativeLength) { + /// Create a new Font with the initial values of all members. + pub fn initial_values() -> Self { + Self { + % for longhand in style_struct.longhands: + % if not longhand.logical: + ${longhand.ident}: longhands::${longhand.ident}::get_initial_value(), + % endif + % endfor + hash: 0, + } } - % elif style_struct.name == "InheritedUI": /// Returns the ColorSchemeFlags corresponding to the value of `color-scheme`. #[inline] @@ -1708,7 +1694,7 @@ pub struct ComputedValuesInner { pub writing_mode: WritingMode, /// The effective zoom value. - pub effective_zoom: Zoom, + pub effective_zoom: computed::Zoom, /// A set of flags we use to store misc information regarding this style. pub flags: ComputedValueFlags, @@ -1950,16 +1936,67 @@ impl ComputedValues { } /// Get the initial computed values. - pub fn initial_values() -> &'static Self { &*INITIAL_SERVO_VALUES } + pub fn initial_values_with_font_override(default_font: super::style_structs::Font) -> Arc { + use crate::logical_geometry::WritingMode; + use crate::computed_value_flags::ComputedValueFlags; + use servo_arc::Arc; + use super::{ComputedValues, ComputedValuesInner, longhands, style_structs}; + + Arc::new(ComputedValues { + inner: ComputedValuesInner { + % for style_struct in data.active_style_structs(): + % if style_struct.name == "Font": + font: Arc::new(default_font), + <% continue %> + % endif % + + ${style_struct.ident}: Arc::new(style_structs::${style_struct.name} { + % for longhand in style_struct.longhands: + % if not longhand.logical: + ${longhand.ident}: longhands::${longhand.ident}::get_initial_value(), + % endif + % endfor + % if style_struct.name == "InheritedText": + text_decorations_in_effect: + crate::values::computed::text::TextDecorationsInEffect::default(), + % endif + % if style_struct.name == "Box": + original_display: longhands::display::get_initial_value(), + % endif + }), + % endfor + custom_properties: crate::custom_properties::ComputedCustomProperties::default(), + writing_mode: WritingMode::empty(), + rules: None, + visited_style: None, + effective_zoom: crate::values::computed::Zoom::ONE, + flags: ComputedValueFlags::empty(), + }, + pseudo: None, + }) + } + + /// Converts the computed values to an Arc<> from a reference. + pub fn to_arc(&self) -> Arc { + // SAFETY: We're guaranteed to be allocated as an Arc<> since the + // functions above are the only ones that create ComputedValues + // instances in Servo (and that must be the case since ComputedValues' + // member is private). + unsafe { Arc::from_raw_addrefed(self) } + } /// Serializes the computed value of this property as a string. pub fn computed_value_to_string(&self, property: PropertyDeclarationId) -> String { match property { PropertyDeclarationId::Longhand(id) => { + let context = resolved::Context { + style: self, + }; let mut s = String::new(); - self.get_longhand_property_value( + self.computed_or_resolved_value( id, - &mut CssWriter::new(&mut s) + Some(&context), + &mut s ).unwrap(); s } @@ -1970,9 +2007,8 @@ impl ComputedValues { let p = &self.custom_properties; let value = p .inherited - .as_ref() - .and_then(|map| map.get(name)) - .or_else(|| p.non_inherited.as_ref().and_then(|map| map.get(name))); + .get(name) + .or_else(|| p.non_inherited.get(name)); value.map_or(String::new(), |value| value.to_css_string()) } } @@ -2034,7 +2070,7 @@ impl ComputedValuesInner { use crate::values::generics::counters::Content; match self.get_counters().content { Content::Normal | Content::None => true, - Content::Items(ref items) => items.is_empty(), + Content::Items(ref items) => items.items.is_empty(), } } @@ -2128,7 +2164,7 @@ impl ComputedValuesInner { /// Gets the logical computed margin from this style. #[inline] - pub fn logical_margin(&self) -> LogicalMargin<<&computed::LengthPercentageOrAuto> { + pub fn logical_margin(&self) -> LogicalMargin<<&computed::Margin> { let margin_style = self.get_margin(); LogicalMargin::from_physical(self.writing_mode, SideOffsets2D::new( &margin_style.margin_top, @@ -2140,7 +2176,7 @@ impl ComputedValuesInner { /// Gets the logical position from this style. #[inline] - pub fn logical_position(&self) -> LogicalMargin<<&computed::LengthPercentageOrAuto> { + pub fn logical_position(&self) -> LogicalMargin<<&computed::Inset> { // FIXME(SimonSapin): should be the writing mode of the containing block, maybe? let position_style = self.get_position(); LogicalMargin::from_physical(self.writing_mode, SideOffsets2D::new( @@ -2803,52 +2839,6 @@ impl<'a> StyleBuilder<'a> { % endfor } -#[cfg(feature = "servo")] -pub use self::lazy_static_module::INITIAL_SERVO_VALUES; - -// Use a module to work around #[cfg] on lazy_static! not being applied to every generated item. -#[cfg(feature = "servo")] -#[allow(missing_docs)] -mod lazy_static_module { - use crate::logical_geometry::WritingMode; - use crate::computed_value_flags::ComputedValueFlags; - use servo_arc::Arc; - use super::{ComputedValues, ComputedValuesInner, longhands, style_structs}; - - lazy_static! { - /// The initial values for all style structs as defined by the specification. - pub static ref INITIAL_SERVO_VALUES: ComputedValues = ComputedValues { - inner: ComputedValuesInner { - % for style_struct in data.active_style_structs(): - ${style_struct.ident}: Arc::new(style_structs::${style_struct.name} { - % for longhand in style_struct.longhands: - % if not longhand.logical: - ${longhand.ident}: longhands::${longhand.ident}::get_initial_value(), - % endif - % endfor - % if style_struct.name == "InheritedText": - text_decorations_in_effect: - crate::values::computed::text::TextDecorationsInEffect::default(), - % endif - % if style_struct.name == "Font": - hash: 0, - % endif - % if style_struct.name == "Box": - original_display: longhands::display::get_initial_value(), - % endif - }), - % endfor - custom_properties, - writing_mode: WritingMode::empty(), - rules: None, - visited_style: None, - flags: ComputedValueFlags::empty(), - }, - pseudo: None, - }; - } -} - /// A per-longhand function that performs the CSS cascade for that longhand. pub type CascadePropertyFn = unsafe extern "Rust" fn( @@ -2928,10 +2918,10 @@ macro_rules! css_properties_accessors { % for prop in [property] + property.aliases: % if '-' in prop.name: [${prop.ident.capitalize()}, Set${prop.ident.capitalize()}, - PropertyId::${kind}(${kind}Id::${property.camel_case})], + PropertyId::NonCustom(${kind}Id::${property.camel_case}.into())], % endif [${prop.camel_case}, Set${prop.camel_case}, - PropertyId::${kind}(${kind}Id::${property.camel_case})], + PropertyId::NonCustom(${kind}Id::${property.camel_case}.into())], % endfor % endif % endfor @@ -2945,6 +2935,9 @@ macro_rules! css_properties_accessors { /// ``` /// { snake_case_ident } /// ``` +/// +/// … where the boolean indicates whether the property value type +/// is wrapped in a `Box<_>` in the corresponding `PropertyDeclaration` variant. #[macro_export] macro_rules! longhand_properties_idents { ($macro_name: ident) => { @@ -2957,7 +2950,7 @@ macro_rules! longhand_properties_idents { } // Large pages generate tens of thousands of ComputedValues. -size_of_test!(ComputedValues, 240); +size_of_test!(ComputedValues, 208); // FFI relies on this. size_of_test!(Option>, 8); @@ -2987,6 +2980,9 @@ const_assert!(std::mem::size_of::( % 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: @@ -2997,13 +2993,13 @@ const_assert!(std::mem::size_of::( % endfor % endfor - false { + $extra || false { $damage.insert($($effect)|*); true } else { false } - }) + }); } % endfor % endif diff --git a/style/properties/shorthands/font.mako.rs b/style/properties/shorthands/font.mako.rs index 198e862871..d708a737ea 100644 --- a/style/properties/shorthands/font.mako.rs +++ b/style/properties/shorthands/font.mako.rs @@ -34,7 +34,9 @@ > use crate::computed_values::font_variant_caps::T::SmallCaps; use crate::parser::Parse; - use crate::properties::longhands::{font_family, font_style, font_size, font_weight, font_stretch}; + use crate::properties::longhands::{font_family, font_style, font_weight, font_stretch}; + #[cfg(feature = "gecko")] + use crate::properties::longhands::font_size; use crate::properties::longhands::font_variant_caps; use crate::values::specified::font::LineHeight; use crate::values::specified::{FontSize, FontWeight}; @@ -315,7 +317,9 @@ % for p in subprops_for_value_info: ${p}::collect_completion_keywords(f); % endfor + % if engine == "gecko": ::collect_completion_keywords(f); + % endif } } diff --git a/style/properties_and_values/registry.rs b/style/properties_and_values/registry.rs index e3cd552c9c..5734aa1b2c 100644 --- a/style/properties_and_values/registry.rs +++ b/style/properties_and_values/registry.rs @@ -69,6 +69,7 @@ impl PropertyRegistration { /// The script registry of custom properties. /// #[derive(Default)] +#[cfg_attr(feature = "servo", derive(MallocSizeOf))] pub struct ScriptRegistry { properties: PrecomputedHashMap, } diff --git a/style/queries/values.rs b/style/queries/values.rs index f4934408c4..077f8f7a08 100644 --- a/style/queries/values.rs +++ b/style/queries/values.rs @@ -34,3 +34,12 @@ impl Orientation { } } } + +/// Values for the prefers-color-scheme media feature. +#[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, ToCss, MallocSizeOf)] +#[repr(u8)] +#[allow(missing_docs)] +pub enum PrefersColorScheme { + Light, + Dark, +} diff --git a/style/servo/media_queries.rs b/style/servo/media_queries.rs index 4f87c2c49e..799f1ef5fa 100644 --- a/style/servo/media_queries.rs +++ b/style/servo/media_queries.rs @@ -7,21 +7,44 @@ use crate::color::AbsoluteColor; use crate::context::QuirksMode; use crate::custom_properties::CssEnvironment; -use crate::media_queries::media_feature::{AllowsRanges, ParsingRequirements}; -use crate::media_queries::media_feature::{Evaluator, MediaFeatureDescription}; -use crate::media_queries::media_feature_expression::RangeOrOperator; +use crate::font_metrics::FontMetrics; +use crate::queries::feature::{AllowsRanges, Evaluator, FeatureFlags, QueryFeatureDescription}; +use crate::queries::values::PrefersColorScheme; +use crate::logical_geometry::WritingMode; use crate::media_queries::MediaType; +use crate::properties::style_structs::Font; use crate::properties::ComputedValues; -use crate::values::computed::CSSPixelLength; +use crate::values::computed::{CSSPixelLength, Context, Length, LineHeight, NonNegativeLength, Resolution}; +use crate::values::computed::font::GenericFontFamily; use crate::values::specified::color::{ColorSchemeFlags, ForcedColors}; -use crate::values::specified::font::FONT_MEDIUM_PX; +use crate::values::specified::font::{FONT_MEDIUM_LINE_HEIGHT_PX, FONT_MEDIUM_PX}; +use crate::values::specified::ViewportVariant; use crate::values::KeyframesName; -use app_units::Au; +use app_units::{Au, AU_PER_PX}; use euclid::default::Size2D as UntypedSize2D; use euclid::{Scale, SideOffsets2D, Size2D}; +use mime::Mime; +use servo_arc::Arc; +use std::fmt::Debug; use std::sync::atomic::{AtomicBool, AtomicU32, Ordering}; use style_traits::{CSSPixel, DevicePixel}; +/// A trait used to query font metrics in clients of Stylo. This is used by Device to +/// query font metrics in a way that is specific to the client using Stylo. +pub trait FontMetricsProvider: Debug + Sync { + /// Query the font metrics for the given font and the given base font size. + fn query_font_metrics( + &self, + vertical: bool, + font: &Font, + base_size: CSSPixelLength, + in_media_query: bool, + retrieve_math_scales: bool, + ) -> FontMetrics; + /// Gets the base size given a generic font family. + fn base_size_for_generic(&self, generic: GenericFontFamily) -> Length; +} + /// A device is a structure that represents the current media a given document /// is displayed in. /// @@ -48,16 +71,34 @@ pub struct Device { /// a relaxed atomic here. #[ignore_malloc_size_of = "Pure stack type"] root_font_size: AtomicU32, + /// Line height of the root element, used for rlh units in other elements. + #[ignore_malloc_size_of = "Pure stack type"] + root_line_height: AtomicU32, /// Whether any styles computed in the document relied on the root font-size /// by using rem units. #[ignore_malloc_size_of = "Pure stack type"] used_root_font_size: AtomicBool, + /// Whether any styles computed in the document relied on the root line-height + /// by using rlh units. + #[ignore_malloc_size_of = "Pure stack type"] + used_root_line_height: AtomicBool, + /// Whether any styles computed in the document relied on font metrics. + used_font_metrics: AtomicBool, /// Whether any styles computed in the document relied on the viewport size. #[ignore_malloc_size_of = "Pure stack type"] used_viewport_units: AtomicBool, + /// Whether the user prefers light mode or dark mode + #[ignore_malloc_size_of = "Pure stack type"] + prefers_color_scheme: PrefersColorScheme, /// The CssEnvironment object responsible of getting CSS environment /// variables. environment: CssEnvironment, + /// An implementation of a trait which implements support for querying font metrics. + #[ignore_malloc_size_of = "Owned by embedder"] + font_metrics_provider: Box, + /// The default computed values for this Device. + #[ignore_malloc_size_of = "Arc is shared"] + default_computed_values: Arc, } impl Device { @@ -67,17 +108,25 @@ impl Device { quirks_mode: QuirksMode, viewport_size: Size2D, device_pixel_ratio: Scale, + font_metrics_provider: Box, + default_computed_values: Arc, + prefers_color_scheme: PrefersColorScheme, ) -> Device { Device { media_type, viewport_size, device_pixel_ratio, quirks_mode, - // FIXME(bz): Seems dubious? root_font_size: AtomicU32::new(FONT_MEDIUM_PX.to_bits()), + root_line_height: AtomicU32::new(FONT_MEDIUM_LINE_HEIGHT_PX.to_bits()), used_root_font_size: AtomicBool::new(false), + used_root_line_height: AtomicBool::new(false), + used_font_metrics: AtomicBool::new(false), used_viewport_units: AtomicBool::new(false), + prefers_color_scheme, environment: CssEnvironment, + font_metrics_provider, + default_computed_values, } } @@ -89,10 +138,7 @@ impl Device { /// Return the default computed values for this device. pub fn default_computed_values(&self) -> &ComputedValues { - // FIXME(bz): This isn't really right, but it's no more wrong - // than what we used to do. See - // https://github.com/servo/servo/issues/14773 for fixing it properly. - ComputedValues::initial_values() + &self.default_computed_values } /// Get the font size of the root element (for rem) @@ -101,10 +147,39 @@ impl Device { CSSPixelLength::new(f32::from_bits(self.root_font_size.load(Ordering::Relaxed))) } - /// Set the font size of the root element (for rem) - pub fn set_root_font_size(&self, size: CSSPixelLength) { - self.root_font_size - .store(size.px().to_bits(), Ordering::Relaxed) + /// Set the font size of the root element (for rem), in zoom-independent CSS pixels. + pub fn set_root_font_size(&self, size: f32) { + self.root_font_size.store(size.to_bits(), Ordering::Relaxed) + } + + /// Get the line height of the root element (for rlh) + pub fn root_line_height(&self) -> CSSPixelLength { + self.used_root_line_height.store(true, Ordering::Relaxed); + CSSPixelLength::new(f32::from_bits( + self.root_line_height.load(Ordering::Relaxed), + )) + } + + /// Set the line height of the root element (for rlh), in zoom-independent CSS pixels. + pub fn set_root_line_height(&self, size: f32) { + self.root_line_height.store(size.to_bits(), Ordering::Relaxed); + } + + /// Returns the computed line-height for the font in a given computed values instance. + /// + /// If you pass down an element, then the used line-height is returned. + pub fn calc_line_height( + &self, + font: &crate::properties::style_structs::Font, + _writing_mode: WritingMode, + _element: Option<()>, + ) -> NonNegativeLength { + (match font.line_height { + // TODO: compute `normal` from the font metrics + LineHeight::Normal => CSSPixelLength::new(0.), + LineHeight::Number(number) => font.font_size.computed_size() * number.0, + LineHeight::Length(length) => length.0, + }).into() } /// Get the quirks mode of the current device. @@ -119,6 +194,11 @@ impl Device { // Servo doesn't implement this quirk (yet) } + /// Gets the base size given a generic font family. + pub fn base_size_for_generic(&self, generic: GenericFontFamily) -> Length { + self.font_metrics_provider.base_size_for_generic(generic) + } + /// Whether a given animation name may be referenced from style. pub fn animation_name_may_be_referenced(&self, _: &KeyframesName) -> bool { // Assume it is, since we don't have any good way to prove it's not. @@ -130,6 +210,16 @@ impl Device { self.used_root_font_size.load(Ordering::Relaxed) } + /// Returns whether we ever looked up the root line-height of the device. + pub fn used_root_line_height(&self) -> bool { + self.used_root_line_height.load(Ordering::Relaxed) + } + + /// Returns whether font metrics have been queried. + pub fn used_font_metrics(&self) -> bool { + self.used_font_metrics.load(Ordering::Relaxed) + } + /// Returns the viewport size of the current device in app units, needed, /// among other things, to resolve viewport units. #[inline] @@ -141,8 +231,13 @@ impl Device { } /// Like the above, but records that we've used viewport units. - pub fn au_viewport_size_for_viewport_unit_resolution(&self) -> UntypedSize2D { + pub fn au_viewport_size_for_viewport_unit_resolution( + &self, + _: ViewportVariant, + ) -> UntypedSize2D { self.used_viewport_units.store(true, Ordering::Relaxed); + // Servo doesn't have dynamic UA interfaces that affect the viewport, + // so we can just ignore the ViewportVariant. self.au_viewport_size() } @@ -151,11 +246,41 @@ impl Device { self.used_viewport_units.load(Ordering::Relaxed) } + /// Returns the number of app units per device pixel we're using currently. + pub fn app_units_per_device_pixel(&self) -> i32 { + (AU_PER_PX as f32 / self.device_pixel_ratio.0) as i32 + } + /// Returns the device pixel ratio. pub fn device_pixel_ratio(&self) -> Scale { self.device_pixel_ratio } + /// Gets the size of the scrollbar in CSS pixels. + pub fn scrollbar_inline_size(&self) -> CSSPixelLength { + // TODO: implement this. + CSSPixelLength::new(0.0) + } + + /// Queries font metrics using the [`FontMetricsProvider`] interface. + pub fn query_font_metrics( + &self, + vertical: bool, + font: &Font, + base_size: CSSPixelLength, + in_media_query: bool, + retrieve_math_scales: bool, + ) -> FontMetrics { + self.used_font_metrics.store(true, Ordering::Relaxed); + self.font_metrics_provider.query_font_metrics( + vertical, + font, + base_size, + in_media_query, + retrieve_math_scales, + ) + } + /// Return the media type of the current device. pub fn media_type(&self) -> MediaType { self.media_type.clone() @@ -176,6 +301,11 @@ impl Device { AbsoluteColor::BLACK } + /// Returns the color scheme of this [`Device`]. + pub fn color_scheme(&self) -> PrefersColorScheme { + self.prefers_color_scheme + } + pub(crate) fn is_dark_color_scheme(&self, _: ColorSchemeFlags) -> bool { false } @@ -184,19 +314,34 @@ impl Device { pub fn safe_area_insets(&self) -> SideOffsets2D { SideOffsets2D::zero() } + + /// Returns true if the given MIME type is supported + pub fn is_supported_mime_type(&self, mime_type: &str) -> bool { + match mime_type.parse::() { + Ok(m) => { + // Keep this in sync with 'image_classifer' from + // components/net/mime_classifier.rs + m == mime::IMAGE_BMP + || m == mime::IMAGE_GIF + || m == mime::IMAGE_PNG + || m == mime::IMAGE_JPEG + || m == "image/x-icon" + || m == "image/webp" + } + _ => false, + } + } + + /// Return whether the document is a chrome document. + #[inline] + pub fn chrome_rules_enabled_for_document(&self) -> bool { + false + } } /// https://drafts.csswg.org/mediaqueries-4/#width -fn eval_width( - device: &Device, - value: Option, - range_or_operator: Option, -) -> bool { - RangeOrOperator::evaluate( - range_or_operator, - value.map(Au::from), - device.au_viewport_size().width, - ) +fn eval_width(context: &Context) -> CSSPixelLength { + CSSPixelLength::new(context.device().au_viewport_size().width.to_f32_px()) } #[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] @@ -207,26 +352,65 @@ enum Scan { } /// https://drafts.csswg.org/mediaqueries-4/#scan -fn eval_scan(_: &Device, _: Option) -> bool { +fn eval_scan(_: &Context, _: Option) -> bool { // Since we doesn't support the 'tv' media type, the 'scan' feature never // matches. false } -lazy_static! { - /// A list with all the media features that Servo supports. - pub static ref MEDIA_FEATURES: [MediaFeatureDescription; 2] = [ - feature!( - atom!("width"), - AllowsRanges::Yes, - Evaluator::Length(eval_width), - ParsingRequirements::empty(), - ), - feature!( - atom!("scan"), - AllowsRanges::No, - keyword_evaluator!(eval_scan, Scan), - ParsingRequirements::empty(), - ), - ]; +/// https://drafts.csswg.org/mediaqueries-4/#resolution +fn eval_resolution(context: &Context) -> Resolution { + Resolution::from_dppx(context.device().device_pixel_ratio.0) +} + +/// https://compat.spec.whatwg.org/#css-media-queries-webkit-device-pixel-ratio +fn eval_device_pixel_ratio(context: &Context) -> f32 { + eval_resolution(context).dppx() } + +fn eval_prefers_color_scheme(context: &Context, query_value: Option) -> bool { + match query_value { + Some(v) => context.device().prefers_color_scheme == v, + None => true, + } +} + +/// A list with all the media features that Servo supports. +pub static MEDIA_FEATURES: [QueryFeatureDescription; 6] = [ + feature!( + atom!("width"), + AllowsRanges::Yes, + Evaluator::Length(eval_width), + FeatureFlags::empty(), + ), + feature!( + atom!("scan"), + AllowsRanges::No, + keyword_evaluator!(eval_scan, Scan), + FeatureFlags::empty(), + ), + feature!( + atom!("resolution"), + AllowsRanges::Yes, + Evaluator::Resolution(eval_resolution), + FeatureFlags::empty(), + ), + feature!( + atom!("device-pixel-ratio"), + AllowsRanges::Yes, + Evaluator::Float(eval_device_pixel_ratio), + FeatureFlags::WEBKIT_PREFIX, + ), + feature!( + atom!("-moz-device-pixel-ratio"), + AllowsRanges::Yes, + Evaluator::Float(eval_device_pixel_ratio), + FeatureFlags::empty(), + ), + feature!( + atom!("prefers-color-scheme"), + AllowsRanges::No, + keyword_evaluator!(eval_prefers_color_scheme, PrefersColorScheme), + FeatureFlags::empty(), + ), +]; diff --git a/style/servo/restyle_damage.rs b/style/servo/restyle_damage.rs index fe17fa6198..6355959805 100644 --- a/style/servo/restyle_damage.rs +++ b/style/servo/restyle_damage.rs @@ -12,6 +12,7 @@ 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. /// @@ -210,7 +211,8 @@ fn compute_damage(old: &ComputedValues, new: &ComputedValues) -> ServoRestyleDam 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, diff --git a/style/servo/selector_parser.rs b/style/servo/selector_parser.rs index b20f1754a0..556c3992a3 100644 --- a/style/servo/selector_parser.rs +++ b/style/servo/selector_parser.rs @@ -7,6 +7,7 @@ //! Servo's selector parser. use crate::attr::{AttrIdentifier, AttrValue}; +use crate::computed_value_flags::ComputedValueFlags; use crate::dom::{OpaqueNode, TElement, TNode}; use crate::invalidation::element::document_state::InvalidationMatchingData; use crate::invalidation::element::element_wrapper::ElementSnapshot; @@ -49,27 +50,29 @@ 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. DetailsSummary, DetailsContent, - ServoText, - ServoInputText, - ServoTableWrapper, - ServoAnonymousTableWrapper, + ServoAnonymousBox, ServoAnonymousTable, - ServoAnonymousTableRow, ServoAnonymousTableCell, - ServoAnonymousBlock, - ServoInlineBlockWrapper, - ServoInlineAbsolute, + ServoAnonymousTableRow, + ServoLegacyText, + ServoLegacyInputText, + ServoLegacyTableWrapper, + ServoLegacyAnonymousTableWrapper, + ServoLegacyAnonymousTable, + ServoLegacyAnonymousBlock, + ServoLegacyInlineBlockWrapper, + ServoLegacyInlineAbsolute, + ServoTableGrid, + ServoTableWrapper, } /// The count of all pseudo-elements. -pub const PSEUDO_COUNT: usize = PseudoElement::ServoInlineAbsolute as usize + 1; - -impl ::selectors::parser::PseudoElement for PseudoElement { - type Impl = SelectorImpl; -} +pub const PSEUDO_COUNT: usize = PseudoElement::ServoTableWrapper as usize + 1; impl ToCss for PseudoElement { fn to_css(&self, dest: &mut W) -> fmt::Result @@ -81,22 +84,31 @@ impl ToCss for PseudoElement { After => "::after", Before => "::before", Selection => "::selection", + Backdrop => "::backdrop", DetailsSummary => "::-servo-details-summary", DetailsContent => "::-servo-details-content", - ServoText => "::-servo-text", - ServoInputText => "::-servo-input-text", - ServoTableWrapper => "::-servo-table-wrapper", - ServoAnonymousTableWrapper => "::-servo-anonymous-table-wrapper", + ServoAnonymousBox => "::-servo-anonymous-box", ServoAnonymousTable => "::-servo-anonymous-table", - ServoAnonymousTableRow => "::-servo-anonymous-table-row", ServoAnonymousTableCell => "::-servo-anonymous-table-cell", - ServoAnonymousBlock => "::-servo-anonymous-block", - ServoInlineBlockWrapper => "::-servo-inline-block-wrapper", - ServoInlineAbsolute => "::-servo-inline-absolute", + ServoAnonymousTableRow => "::-servo-anonymous-table-row", + ServoLegacyText => "::-servo-legacy-text", + ServoLegacyInputText => "::-servo-legacy-input-text", + ServoLegacyTableWrapper => "::-servo-legacy-table-wrapper", + ServoLegacyAnonymousTableWrapper => "::-servo-legacy-anonymous-table-wrapper", + ServoLegacyAnonymousTable => "::-servo-legacy-anonymous-table", + ServoLegacyAnonymousBlock => "::-servo-legacy-anonymous-block", + ServoLegacyInlineBlockWrapper => "::-servo-legacy-inline-block-wrapper", + ServoLegacyInlineAbsolute => "::-servo-legacy-inline-absolute", + ServoTableGrid => "::-servo-table-grid", + ServoTableWrapper => "::-servo-table-wrapper", }) } } +impl ::selectors::parser::PseudoElement for PseudoElement { + type Impl = SelectorImpl; +} + /// The number of eager pseudo-elements. Keep this in sync with cascade_type. pub const EAGER_PSEUDO_COUNT: usize = 3; @@ -223,18 +235,22 @@ impl PseudoElement { PseudoElement::After | PseudoElement::Before | PseudoElement::Selection => { PseudoElementCascadeType::Eager }, - PseudoElement::DetailsSummary => PseudoElementCascadeType::Lazy, + PseudoElement::Backdrop | PseudoElement::DetailsSummary => PseudoElementCascadeType::Lazy, PseudoElement::DetailsContent | - PseudoElement::ServoText | - PseudoElement::ServoInputText | - PseudoElement::ServoTableWrapper | - PseudoElement::ServoAnonymousTableWrapper | + PseudoElement::ServoAnonymousBox | PseudoElement::ServoAnonymousTable | - PseudoElement::ServoAnonymousTableRow | PseudoElement::ServoAnonymousTableCell | - PseudoElement::ServoAnonymousBlock | - PseudoElement::ServoInlineBlockWrapper | - PseudoElement::ServoInlineAbsolute => PseudoElementCascadeType::Precomputed, + PseudoElement::ServoAnonymousTableRow | + PseudoElement::ServoLegacyText | + PseudoElement::ServoLegacyInputText | + PseudoElement::ServoLegacyTableWrapper | + PseudoElement::ServoLegacyAnonymousTableWrapper | + PseudoElement::ServoLegacyAnonymousTable | + PseudoElement::ServoLegacyAnonymousBlock | + PseudoElement::ServoLegacyInlineBlockWrapper | + PseudoElement::ServoLegacyInlineAbsolute | + PseudoElement::ServoTableGrid | + PseudoElement::ServoTableWrapper => PseudoElementCascadeType::Precomputed, } } @@ -273,6 +289,10 @@ impl PseudoElement { /// The type used for storing `:lang` arguments. pub type Lang = Box; +/// The type used to store the state argument to the `:state` pseudo-class. +#[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToCss, ToShmem)] +pub struct CustomState(pub AtomIdent); + /// A non tree-structural pseudo-class. /// See https://drafts.csswg.org/selectors-4/#structural-pseudos #[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToShmem)] @@ -280,21 +300,37 @@ pub type Lang = Box; pub enum NonTSPseudoClass { Active, AnyLink, + Autofill, Checked, + /// The :state` pseudo-class. + CustomState(CustomState), + Default, Defined, Disabled, Enabled, Focus, + FocusWithin, + FocusVisible, Fullscreen, Hover, + InRange, Indeterminate, + Invalid, Lang(Lang), Link, + Modal, + Optional, + OutOfRange, PlaceholderShown, - ReadWrite, + PopoverOpen, ReadOnly, + ReadWrite, + Required, ServoNonZeroBorder, Target, + UserInvalid, + UserValid, + Valid, Visited, } @@ -335,24 +371,38 @@ impl ToCss for NonTSPseudoClass { } dest.write_str(match *self { - Active => ":active", - AnyLink => ":any-link", - Checked => ":checked", - Defined => ":defined", - Disabled => ":disabled", - Enabled => ":enabled", - Focus => ":focus", - Fullscreen => ":fullscreen", - Hover => ":hover", - Indeterminate => ":indeterminate", - Link => ":link", - PlaceholderShown => ":placeholder-shown", - ReadWrite => ":read-write", - ReadOnly => ":read-only", - ServoNonZeroBorder => ":-servo-nonzero-border", - Target => ":target", - Visited => ":visited", - Lang(_) => unreachable!(), + Self::Active => ":active", + Self::AnyLink => ":any-link", + Self::Autofill => ":autofill", + Self::Checked => ":checked", + Self::Default => ":default", + Self::Defined => ":defined", + Self::Disabled => ":disabled", + Self::Enabled => ":enabled", + Self::Focus => ":focus", + Self::FocusVisible => ":focus-visible", + Self::FocusWithin => ":focus-within", + Self::Fullscreen => ":fullscreen", + Self::Hover => ":hover", + Self::InRange => ":in-range", + Self::Indeterminate => ":indeterminate", + Self::Invalid => ":invalid", + Self::Link => ":link", + Self::Modal => ":modal", + Self::Optional => ":optional", + Self::OutOfRange => ":out-of-range", + Self::PlaceholderShown => ":placeholder-shown", + Self::PopoverOpen => ":popover-open", + Self::ReadOnly => ":read-only", + Self::ReadWrite => ":read-write", + Self::Required => ":required", + Self::ServoNonZeroBorder => ":-servo-nonzero-border", + Self::Target => ":target", + Self::UserInvalid => ":user-invalid", + Self::UserValid => ":user-valid", + Self::Valid => ":valid", + Self::Visited => ":visited", + Self::Lang(_) | Self::CustomState(_) => unreachable!(), }) } } @@ -361,22 +411,38 @@ impl NonTSPseudoClass { /// Gets a given state flag for this pseudo-class. This is used to do /// selector matching, and it's set from the DOM. pub fn state_flag(&self) -> ElementState { - use self::NonTSPseudoClass::*; match *self { - Active => ElementState::IN_ACTIVE_STATE, - Focus => ElementState::IN_FOCUS_STATE, - Fullscreen => ElementState::IN_FULLSCREEN_STATE, - Hover => ElementState::IN_HOVER_STATE, - Defined => ElementState::IN_DEFINED_STATE, - Enabled => ElementState::IN_ENABLED_STATE, - Disabled => ElementState::IN_DISABLED_STATE, - Checked => ElementState::IN_CHECKED_STATE, - Indeterminate => ElementState::IN_INDETERMINATE_STATE, - ReadOnly | ReadWrite => ElementState::IN_READWRITE_STATE, - PlaceholderShown => ElementState::IN_PLACEHOLDER_SHOWN_STATE, - Target => ElementState::IN_TARGET_STATE, - - AnyLink | Lang(_) | Link | Visited | ServoNonZeroBorder => ElementState::empty(), + Self::Active => ElementState::ACTIVE, + Self::AnyLink => ElementState::VISITED_OR_UNVISITED, + Self::Autofill => ElementState::AUTOFILL, + Self::Checked => ElementState::CHECKED, + Self::Default => ElementState::DEFAULT, + Self::Defined => ElementState::DEFINED, + Self::Disabled => ElementState::DISABLED, + Self::Enabled => ElementState::ENABLED, + Self::Focus => ElementState::FOCUS, + Self::FocusVisible => ElementState::FOCUSRING, + Self::FocusWithin => ElementState::FOCUS_WITHIN, + Self::Fullscreen => ElementState::FULLSCREEN, + Self::Hover => ElementState::HOVER, + Self::InRange => ElementState::INRANGE, + Self::Indeterminate => ElementState::INDETERMINATE, + Self::Invalid => ElementState::INVALID, + Self::Link => ElementState::UNVISITED, + Self::Modal => ElementState::MODAL, + Self::Optional => ElementState::OPTIONAL_, + Self::OutOfRange => ElementState::OUTOFRANGE, + Self::PlaceholderShown => ElementState::PLACEHOLDER_SHOWN, + Self::PopoverOpen => ElementState::POPOVER_OPEN, + Self::ReadOnly => ElementState::READONLY, + Self::ReadWrite => ElementState::READWRITE, + Self::Required => ElementState::REQUIRED, + Self::Target => ElementState::URLTARGET, + Self::UserInvalid => ElementState::USER_INVALID, + Self::UserValid => ElementState::USER_VALID, + Self::Valid => ElementState::VALID, + Self::Visited => ElementState::VISITED, + Self::CustomState(_) | Self::Lang(_) | Self::ServoNonZeroBorder => ElementState::empty(), } } @@ -397,57 +463,106 @@ impl NonTSPseudoClass { #[cfg_attr(feature = "servo", derive(MallocSizeOf))] pub struct SelectorImpl; +/// A set of extra data to carry along with the matching context, either for +/// selector-matching or invalidation. +#[derive(Debug, Default)] +pub struct ExtraMatchingData<'a> { + /// The invalidation data to invalidate doc-state pseudo-classes correctly. + pub invalidation_data: InvalidationMatchingData, + + /// The invalidation bits from matching container queries. These are here + /// just for convenience mostly. + pub cascade_input_flags: ComputedValueFlags, + + /// The style of the originating element in order to evaluate @container + /// size queries affecting pseudo-elements. + pub originating_element_style: Option<&'a ComputedValues>, +} + impl ::selectors::SelectorImpl for SelectorImpl { type PseudoElement = PseudoElement; type NonTSPseudoClass = NonTSPseudoClass; - type ExtraMatchingData = InvalidationMatchingData; - type AttrValue = String; - type Identifier = Atom; - type ClassName = Atom; - type PartName = Atom; + type ExtraMatchingData<'a> = ExtraMatchingData<'a>; + type AttrValue = AtomString; + type Identifier = AtomIdent; type LocalName = LocalName; type NamespacePrefix = Prefix; type NamespaceUrl = Namespace; - type BorrowedLocalName = LocalName; - type BorrowedNamespaceUrl = Namespace; + type BorrowedLocalName = markup5ever::LocalName; + type BorrowedNamespaceUrl = markup5ever::Namespace; } impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> { type Impl = SelectorImpl; type Error = StyleParseErrorKind<'i>; + #[inline] + fn parse_nth_child_of(&self) -> bool { + false + } + + #[inline] + fn parse_is_and_where(&self) -> bool { + true + } + + #[inline] + fn parse_has(&self) -> bool { + false + } + + #[inline] + fn parse_parent_selector(&self) -> bool { + false + } + + #[inline] + fn allow_forgiving_selectors(&self) -> bool { + !self.for_supports_rule + } + fn parse_non_ts_pseudo_class( &self, location: SourceLocation, name: CowRcStr<'i>, ) -> Result> { - use self::NonTSPseudoClass::*; let pseudo_class = match_ignore_ascii_case! { &name, - "active" => Active, - "any-link" => AnyLink, - "checked" => Checked, - "defined" => Defined, - "disabled" => Disabled, - "enabled" => Enabled, - "focus" => Focus, - "fullscreen" => Fullscreen, - "hover" => Hover, - "indeterminate" => Indeterminate, - "-moz-inert" => MozInert, - "link" => Link, - "placeholder-shown" => PlaceholderShown, - "read-write" => ReadWrite, - "read-only" => ReadOnly, - "target" => Target, - "visited" => Visited, + "active" => NonTSPseudoClass::Active, + "any-link" => NonTSPseudoClass::AnyLink, + "autofill" => NonTSPseudoClass::Autofill, + "checked" => NonTSPseudoClass::Checked, + "default" => NonTSPseudoClass::Default, + "defined" => NonTSPseudoClass::Defined, + "disabled" => NonTSPseudoClass::Disabled, + "enabled" => NonTSPseudoClass::Enabled, + "focus" => NonTSPseudoClass::Focus, + "focus-visible" => NonTSPseudoClass::FocusVisible, + "focus-within" => NonTSPseudoClass::FocusWithin, + "fullscreen" => NonTSPseudoClass::Fullscreen, + "hover" => NonTSPseudoClass::Hover, + "indeterminate" => NonTSPseudoClass::Indeterminate, + "invalid" => NonTSPseudoClass::Invalid, + "link" => NonTSPseudoClass::Link, + "optional" => NonTSPseudoClass::Optional, + "out-of-range" => NonTSPseudoClass::OutOfRange, + "placeholder-shown" => NonTSPseudoClass::PlaceholderShown, + "popover-open" => NonTSPseudoClass::PopoverOpen, + "read-only" => NonTSPseudoClass::ReadOnly, + "read-write" => NonTSPseudoClass::ReadWrite, + "required" => NonTSPseudoClass::Required, + "target" => NonTSPseudoClass::Target, + "user-invalid" => NonTSPseudoClass::UserInvalid, + "user-valid" => NonTSPseudoClass::UserValid, + "valid" => NonTSPseudoClass::Valid, + "visited" => NonTSPseudoClass::Visited, "-servo-nonzero-border" => { if !self.in_user_agent_stylesheet() { return Err(location.new_custom_error( SelectorParseErrorKind::UnexpectedIdent("-servo-nonzero-border".into()) )) } - ServoNonZeroBorder + NonTSPseudoClass::ServoNonZeroBorder }, _ => return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))), }; @@ -459,10 +574,11 @@ impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> { &self, name: CowRcStr<'i>, parser: &mut CssParser<'i, 't>, + after_part: bool, ) -> Result> { use self::NonTSPseudoClass::*; let pseudo_class = match_ignore_ascii_case! { &name, - "lang" => { + "lang" if !after_part => { Lang(parser.expect_ident_or_string()?.as_ref().into()) }, _ => return Err(parser.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))), @@ -481,6 +597,7 @@ impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> { "before" => Before, "after" => After, "selection" => Selection, + "backdrop" => Backdrop, "-servo-details-summary" => { if !self.in_user_agent_stylesheet() { return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))) @@ -493,29 +610,41 @@ impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> { } DetailsContent }, - "-servo-text" => { + "-servo-anonymous-box" => { if !self.in_user_agent_stylesheet() { return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))) } - ServoText + ServoAnonymousBox }, - "-servo-input-text" => { + "-servo-legacy-text" => { if !self.in_user_agent_stylesheet() { return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))) } - ServoInputText + ServoLegacyText }, - "-servo-table-wrapper" => { + "-servo-legacy-input-text" => { if !self.in_user_agent_stylesheet() { return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))) } - ServoTableWrapper + ServoLegacyInputText + }, + "-servo-legacy-table-wrapper" => { + if !self.in_user_agent_stylesheet() { + return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))) + } + ServoLegacyTableWrapper }, - "-servo-anonymous-table-wrapper" => { + "-servo-legacy-anonymous-table-wrapper" => { if !self.in_user_agent_stylesheet() { return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))) } - ServoAnonymousTableWrapper + ServoLegacyAnonymousTableWrapper + }, + "-servo-legacy-anonymous-table" => { + if !self.in_user_agent_stylesheet() { + return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))) + } + ServoLegacyAnonymousTable }, "-servo-anonymous-table" => { if !self.in_user_agent_stylesheet() { @@ -535,23 +664,35 @@ impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> { } ServoAnonymousTableCell }, - "-servo-anonymous-block" => { + "-servo-legacy-anonymous-block" => { + if !self.in_user_agent_stylesheet() { + return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))) + } + ServoLegacyAnonymousBlock + }, + "-servo-legacy-inline-block-wrapper" => { + if !self.in_user_agent_stylesheet() { + return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))) + } + ServoLegacyInlineBlockWrapper + }, + "-servo-legacy-inline-absolute" => { if !self.in_user_agent_stylesheet() { return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))) } - ServoAnonymousBlock + ServoLegacyInlineAbsolute }, - "-servo-inline-block-wrapper" => { + "-servo-table-grid" => { if !self.in_user_agent_stylesheet() { return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))) } - ServoInlineBlockWrapper + ServoTableGrid }, - "-servo-inline-absolute" => { + "-servo-table-wrapper" => { if !self.in_user_agent_stylesheet() { return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))) } - ServoInlineAbsolute + ServoTableWrapper }, _ => return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))) @@ -567,6 +708,10 @@ impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> { fn namespace_for_prefix(&self, prefix: &Prefix) -> Option { self.namespaces.prefixes.get(prefix).cloned() } + + fn parse_host(&self) -> bool { + true + } } impl SelectorImpl { @@ -730,6 +875,25 @@ impl ElementSnapshot for ServoElementSnapshot { .or_else(|| self.get_attr(&ns!(), &local_name!("lang"))) .map(|v| SelectorAttrValue::from(v as &str)) } + + /// Returns true if the snapshot has stored state for custom states + #[inline] + fn has_custom_states(&self) -> bool { + false + } + + /// Returns true if the snapshot has a given CustomState + #[inline] + fn has_custom_state(&self, _state: &AtomIdent) -> bool { + false + } + + #[inline] + fn each_custom_state(&self, mut _callback: F) + where + F: FnMut(&AtomIdent), + { + } } impl ServoElementSnapshot { diff --git a/style/str.rs b/style/str.rs index fe598f546e..ad706746fe 100644 --- a/style/str.rs +++ b/style/str.rs @@ -178,3 +178,11 @@ pub type CssStringWriter = ::nsstring::nsACString; /// needs to allocate a temporary string. #[cfg(feature = "gecko")] pub type CssString = ::nsstring::nsCString; + +/// String. The comments for the Gecko types explain the need for this abstraction. +#[cfg(feature = "servo")] +pub type CssStringWriter = String; + +/// String. The comments for the Gecko types explain the need for this abstraction. +#[cfg(feature = "servo")] +pub type CssString = String; diff --git a/style/stylesheets/import_rule.rs b/style/stylesheets/import_rule.rs index d0e5528c18..477942ca0c 100644 --- a/style/stylesheets/import_rule.rs +++ b/style/stylesheets/import_rule.rs @@ -108,18 +108,45 @@ impl DeepCloneWithLock for ImportSheet { /// A sheet that is held from an import rule. #[cfg(feature = "servo")] #[derive(Debug)] -pub struct ImportSheet(pub ::servo_arc::Arc); +pub enum ImportSheet { + /// A bonafide stylesheet. + Sheet(::servo_arc::Arc), + + /// An @import created with a false , so will never be fetched. + Refused, +} #[cfg(feature = "servo")] impl ImportSheet { + /// Creates a new ImportSheet from a stylesheet. + pub fn new(sheet: ::servo_arc::Arc) -> Self { + ImportSheet::Sheet(sheet) + } + + /// Creates a refused ImportSheet for a load that will not happen. + pub fn new_refused() -> Self { + ImportSheet::Refused + } + + /// Returns a reference to the stylesheet in this ImportSheet, if it exists. + pub fn as_sheet(&self) -> Option<&::servo_arc::Arc> { + match *self { + ImportSheet::Sheet(ref s) => Some(s), + ImportSheet::Refused => None, + } + } + /// Returns the media list for this import rule. pub fn media<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> Option<&'a MediaList> { - self.0.media(guard) + self.as_sheet().and_then(|s| s.media(guard)) } /// Returns the rules for this import rule. pub fn rules<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> &'a [CssRule] { - self.0.rules() + match self.as_sheet() { + Some(s) => s.rules(guard), + None => &[], + } } } @@ -130,8 +157,13 @@ impl DeepCloneWithLock for ImportSheet { _lock: &SharedRwLock, _guard: &SharedRwLockReadGuard, ) -> Self { - use servo_arc::Arc; - ImportSheet(Arc::new((&*self.0).clone())) + match *self { + ImportSheet::Sheet(ref s) => { + use servo_arc::Arc; + ImportSheet::Sheet(Arc::new((&**s).clone())) + }, + ImportSheet::Refused => ImportSheet::Refused, + } } } diff --git a/style/stylesheets/mod.rs b/style/stylesheets/mod.rs index 8af13ef15c..1871f1037d 100644 --- a/style/stylesheets/mod.rs +++ b/style/stylesheets/mod.rs @@ -115,8 +115,8 @@ pub struct UrlExtraData(usize); /// Extra data that the backend may need to resolve url values. #[cfg(feature = "servo")] -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct UrlExtraData(pub Arc<::url::Url>); +#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq)] +pub struct UrlExtraData(#[ignore_malloc_size_of = "Arc"] pub Arc<::url::Url>); #[cfg(feature = "servo")] impl UrlExtraData { diff --git a/style/stylesheets/position_try_rule.rs b/style/stylesheets/position_try_rule.rs index 3efab5ee5a..584d4122c4 100644 --- a/style/stylesheets/position_try_rule.rs +++ b/style/stylesheets/position_try_rule.rs @@ -15,6 +15,7 @@ use crate::shared_lock::{ use crate::str::CssStringWriter; use crate::values::DashedIdent; use cssparser::SourceLocation; +#[cfg(feature = "gecko")] use malloc_size_of::{MallocSizeOf, MallocSizeOfOps, MallocUnconditionalShallowSizeOf}; use servo_arc::Arc; use style_traits::{CssWriter, ToCss}; diff --git a/style/stylesheets/stylesheet.rs b/style/stylesheets/stylesheet.rs index 64d78fae46..e34e8d8e93 100644 --- a/style/stylesheets/stylesheet.rs +++ b/style/stylesheets/stylesheet.rs @@ -122,13 +122,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, @@ -141,6 +140,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/stylist.rs b/style/stylist.rs index 9801b53965..d4950c5e22 100644 --- a/style/stylist.rs +++ b/style/stylist.rs @@ -47,9 +47,10 @@ use crate::stylesheets::scope_rule::{ #[cfg(feature = "gecko")] use crate::stylesheets::{ CounterStyleRule, FontFaceRule, FontFeatureValuesRule, FontPaletteValuesRule, + PagePseudoClassFlags, }; use crate::stylesheets::{ - CssRule, EffectiveRulesIterator, Origin, OriginSet, PagePseudoClassFlags, PageRule, PerOrigin, + CssRule, EffectiveRulesIterator, Origin, OriginSet, PageRule, PerOrigin, PerOriginIter, StylesheetContents, StylesheetInDocument, }; use crate::values::{computed, AtomIdent}; @@ -57,9 +58,9 @@ use crate::AllocErr; use crate::{Atom, LocalName, Namespace, ShrinkIfNeeded, WeakAtom}; use dom::{DocumentState, ElementState}; use fxhash::FxHashMap; -use malloc_size_of::MallocSizeOf; +use malloc_size_of::{MallocSizeOf, MallocShallowSizeOf, MallocSizeOfOps}; #[cfg(feature = "gecko")] -use malloc_size_of::{MallocShallowSizeOf, MallocSizeOfOps, MallocUnconditionalShallowSizeOf}; +use malloc_size_of::MallocUnconditionalShallowSizeOf; use selectors::attr::{CaseSensitivity, NamespaceConstraint}; use selectors::bloom::BloomFilter; use selectors::matching::{ @@ -542,6 +543,7 @@ pub struct Stylist { stylesheets: StylistStylesheetSet, /// A cache of CascadeDatas for AuthorStylesheetSets (i.e., shadow DOM). + #[cfg_attr(feature = "servo", ignore_malloc_size_of = "XXX: how to handle this?")] author_data_cache: CascadeDataCache, /// If true, the quirks-mode stylesheet is applied. @@ -564,6 +566,7 @@ pub struct Stylist { script_custom_properties: CustomPropertyScriptRegistry, /// Initial values for registered custom properties. + #[ignore_malloc_size_of = "Arc"] initial_values_for_custom_properties: ComputedCustomProperties, /// Flags set from computing registered custom property initial values. @@ -3188,7 +3191,9 @@ impl CascadeData { } } #[cfg(feature = "gecko")] - self.extra_data.sort_by_layer(&self.layers); + { + self.extra_data.sort_by_layer(&self.layers); + } self.animations .sort_with(&self.layers, compare_keyframes_in_same_layer); self.custom_property_registrations.sort(&self.layers) diff --git a/style/values/animated/effects.rs b/style/values/animated/effects.rs index 67557e54b7..e2b37cecbe 100644 --- a/style/values/animated/effects.rs +++ b/style/values/animated/effects.rs @@ -24,4 +24,4 @@ pub type AnimatedFilter = /// An animated value for a single `filter`. #[cfg(not(feature = "gecko"))] -pub type AnimatedFilter = GenericFilter; +pub type AnimatedFilter = GenericFilter; diff --git a/style/values/computed/effects.rs b/style/values/computed/effects.rs index b0a92024ca..ce3498d141 100644 --- a/style/values/computed/effects.rs +++ b/style/values/computed/effects.rs @@ -36,7 +36,7 @@ pub type Filter = GenericFilter< NonNegativeNumber, ZeroToOneNumber, NonNegativeLength, - Impossible, + SimpleShadow, Impossible, >; diff --git a/style/values/computed/font.rs b/style/values/computed/font.rs index 3219fb5815..0e5c128607 100644 --- a/style/values/computed/font.rs +++ b/style/values/computed/font.rs @@ -1205,7 +1205,7 @@ pub type FontStretchFixedPoint = FixedPoint; #[derive( Clone, ComputeSquaredDistance, Copy, Debug, MallocSizeOf, PartialEq, PartialOrd, ToResolvedValue, )] -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] +#[cfg_attr(feature = "servo", derive(Deserialize, Hash, Serialize))] #[repr(C)] pub struct FontStretch(pub FontStretchFixedPoint); diff --git a/style/values/computed/image.rs b/style/values/computed/image.rs index 8a91d95313..4a317b4912 100644 --- a/style/values/computed/image.rs +++ b/style/values/computed/image.rs @@ -29,7 +29,7 @@ pub use specified::ImageRendering; pub type Image = generic::GenericImage; // Images should remain small, see https://github.com/servo/servo/pull/18430 -size_of_test!(Image, 16); +size_of_test!(Image, 40); /// Computed values for a CSS gradient. /// diff --git a/style/values/computed/length.rs b/style/values/computed/length.rs index bed8d37b74..b7e78183e7 100644 --- a/style/values/computed/length.rs +++ b/style/values/computed/length.rs @@ -19,7 +19,7 @@ use crate::values::{specified, CSSFloat}; use crate::Zero; use app_units::Au; use std::fmt::{self, Write}; -use std::ops::{Add, AddAssign, Div, Mul, MulAssign, Neg, Sub}; +use std::ops::{Add, AddAssign, Div, Mul, MulAssign, Neg, Sub, SubAssign}; use style_traits::{CSSPixel, CssWriter, ToCss}; pub use super::image::Image; @@ -182,8 +182,23 @@ impl MaxSize { #[inline] pub fn to_used_value(&self, percentage_basis: Au) -> Option { match *self { - GenericMaxSize::None => None, - GenericMaxSize::LengthPercentage(ref lp) => Some(lp.to_used_value(percentage_basis)), + Self::None | Self::MinContent | Self::MaxContent | Self::FitContent | Self::Stretch => { + None + }, + Self::LengthPercentage(ref lp) => Some(lp.to_used_value(percentage_basis)), + Self::AnchorSizeFunction(_) => unreachable!("anchor-size() should be disabled"), + } + } + + /// Convert the computed value into used value if there is enough information. + #[inline] + pub fn maybe_to_used_value(&self, percentage_basis: Option) -> Option { + match *self { + Self::None | Self::MinContent | Self::MaxContent | Self::FitContent | Self::Stretch => { + None + }, + Self::LengthPercentage(ref lp) => lp.maybe_to_used_value(percentage_basis), + Self::AnchorSizeFunction(_) => unreachable!("anchor-size() should be disabled"), } } } @@ -194,8 +209,24 @@ impl Size { #[cfg(feature = "servo")] pub fn to_used_value(&self, percentage_basis: Au) -> Option { match *self { - GenericSize::Auto => None, - GenericSize::LengthPercentage(ref lp) => Some(lp.to_used_value(percentage_basis)), + Self::Auto | Self::MinContent | Self::MaxContent | Self::FitContent | Self::Stretch => { + None + }, + Self::LengthPercentage(ref lp) => Some(lp.to_used_value(percentage_basis)), + Self::AnchorSizeFunction(_) => unreachable!("anchor-size() should be disabled"), + } + } + + /// Convert the computed value into used value if there is enough information. + #[inline] + #[cfg(feature = "servo")] + pub fn maybe_to_used_value(&self, percentage_basis: Option) -> Option { + match *self { + Self::Auto | Self::MinContent | Self::MaxContent | Self::FitContent | Self::Stretch => { + None + }, + Self::LengthPercentage(ref lp) => lp.maybe_to_used_value(percentage_basis), + Self::AnchorSizeFunction(_) => unreachable!("anchor-size() should be disabled"), } } @@ -205,15 +236,13 @@ impl Size { match *self { Self::Auto => false, Self::LengthPercentage(ref lp) => lp.is_definitely_zero(), - #[cfg(feature = "gecko")] Self::MinContent | Self::MaxContent | Self::FitContent | - Self::MozAvailable | - Self::WebkitFillAvailable | Self::Stretch | - Self::FitContentFunction(_) | Self::AnchorSizeFunction(_) => false, + #[cfg(feature = "gecko")] + Self::MozAvailable | Self::WebkitFillAvailable | Self::FitContentFunction(_) => false, } } } @@ -457,6 +486,13 @@ impl Sub for CSSPixelLength { } } +impl SubAssign for CSSPixelLength { + #[inline] + fn sub_assign(&mut self, other: Self) { + self.0 -= other.0; + } +} + impl From for Au { #[inline] fn from(len: CSSPixelLength) -> Self { diff --git a/style/values/computed/length_percentage.rs b/style/values/computed/length_percentage.rs index ade53ce5e2..fed5c2a190 100644 --- a/style/values/computed/length_percentage.rs +++ b/style/values/computed/length_percentage.rs @@ -202,9 +202,12 @@ impl ToResolvedValue for LengthPercentage { /// An unpacked `` that borrows the `calc()` variant. #[derive(Clone, Debug, PartialEq, ToCss)] -enum Unpacked<'a> { +pub enum Unpacked<'a> { + /// A `calc()` value Calc(&'a CalcLengthPercentage), + /// A length value Length(Length), + /// A percentage value Percentage(Percentage), } @@ -397,8 +400,10 @@ impl LengthPercentage { } } + /// Unpack the tagged pointer representation of a length-percentage into an enum + /// representation with separate tag and value. #[inline] - fn unpack<'a>(&'a self) -> Unpacked<'a> { + pub fn unpack<'a>(&'a self) -> Unpacked<'a> { unsafe { match self.tag() { Tag::Calc => Unpacked::Calc(&*self.calc_ptr()), diff --git a/style/values/generics/image.rs b/style/values/generics/image.rs index ca1c716052..7865b4fc55 100644 --- a/style/values/generics/image.rs +++ b/style/values/generics/image.rs @@ -13,7 +13,6 @@ use crate::values::generics::Optional; use crate::values::serialize_atom_identifier; use crate::Atom; use crate::Zero; -use servo_arc::Arc; use std::fmt::{self, Write}; use style_traits::{CssWriter, ToCss}; @@ -368,10 +367,9 @@ pub struct PaintWorklet { pub name: Atom, /// The arguments for the worklet. /// TODO: store a parsed representation of the arguments. - #[cfg_attr(feature = "servo", ignore_malloc_size_of = "Arc")] #[compute(no_field_bound)] #[resolve(no_field_bound)] - pub arguments: Vec>, + pub arguments: Vec, } impl ::style_traits::SpecifiedValueInfo for PaintWorklet {} diff --git a/style/values/generics/length.rs b/style/values/generics/length.rs index 6512d7dd7f..73e804c62c 100644 --- a/style/values/generics/length.rs +++ b/style/values/generics/length.rs @@ -7,7 +7,6 @@ use crate::parser::{Parse, ParserContext}; use crate::values::generics::Optional; use crate::values::DashedIdent; -#[cfg(feature = "gecko")] use crate::Zero; use cssparser::Parser; use std::fmt::Write; @@ -75,13 +74,13 @@ impl LengthPercentageOrAuto { } } -impl LengthPercentageOrAuto +impl LengthPercentageOrAuto where - LengthPercentage: Clone, + T: Clone, { /// Resolves `auto` values by calling `f`. #[inline] - pub fn auto_is(&self, f: impl FnOnce() -> LengthPercentage) -> LengthPercentage { + pub fn auto_is(&self, f: impl FnOnce() -> T) -> T { match self { LengthPercentageOrAuto::LengthPercentage(length) => length.clone(), LengthPercentageOrAuto::Auto => f(), @@ -90,7 +89,7 @@ where /// Returns the non-`auto` value, if any. #[inline] - pub fn non_auto(&self) -> Option { + pub fn non_auto(&self) -> Option { match self { LengthPercentageOrAuto::LengthPercentage(length) => Some(length.clone()), LengthPercentageOrAuto::Auto => None, @@ -98,7 +97,7 @@ where } /// Maps the length of this value. - pub fn map(&self, f: impl FnOnce(LengthPercentage) -> T) -> LengthPercentageOrAuto { + pub fn map(&self, f: impl FnOnce(T) -> U) -> LengthPercentageOrAuto { match self { LengthPercentageOrAuto::LengthPercentage(l) => { LengthPercentageOrAuto::LengthPercentage(f(l.clone())) @@ -155,13 +154,10 @@ impl Parse for LengthPercentageOrAuto pub enum GenericSize { LengthPercentage(LengthPercent), Auto, - #[cfg(feature = "gecko")] #[animation(error)] MaxContent, - #[cfg(feature = "gecko")] #[animation(error)] MinContent, - #[cfg(feature = "gecko")] #[animation(error)] FitContent, #[cfg(feature = "gecko")] @@ -172,6 +168,7 @@ pub enum GenericSize { WebkitFillAvailable, #[animation(error)] Stretch, + #[cfg(feature = "gecko")] #[animation(error)] #[css(function = "fit-content")] FitContentFunction(LengthPercent), @@ -234,13 +231,10 @@ impl Size { pub enum GenericMaxSize { LengthPercentage(LengthPercent), None, - #[cfg(feature = "gecko")] #[animation(error)] MaxContent, - #[cfg(feature = "gecko")] #[animation(error)] MinContent, - #[cfg(feature = "gecko")] #[animation(error)] FitContent, #[cfg(feature = "gecko")] @@ -251,6 +245,7 @@ pub enum GenericMaxSize { WebkitFillAvailable, #[animation(error)] Stretch, + #[cfg(feature = "gecko")] #[animation(error)] #[css(function = "fit-content")] FitContentFunction(LengthPercent), @@ -560,6 +555,27 @@ pub enum GenericMargin { ), } +#[cfg(feature = "servo")] +impl GenericMargin { + /// Return true if it is 'auto'. + #[inline] + pub fn is_auto(&self) -> bool { + matches!(self, Self::Auto) + } +} + +#[cfg(feature = "servo")] +impl GenericMargin { + /// Returns true if the computed value is absolute 0 or 0%. + #[inline] + pub fn is_definitely_zero(&self) -> bool { + match self { + Self::LengthPercentage(lp) => lp.is_definitely_zero(), + _ => false, + } + } +} + impl SpecifiedValueInfo for GenericMargin where LP: SpecifiedValueInfo, diff --git a/style/values/generics/position.rs b/style/values/generics/position.rs index c895fc09b3..ab75d9ad9a 100644 --- a/style/values/generics/position.rs +++ b/style/values/generics/position.rs @@ -314,6 +314,13 @@ impl GenericInset { pub fn auto() -> Self { Self::Auto } + + /// Return true if it is 'auto'. + #[inline] + #[cfg(feature = "servo")] + pub fn is_auto(&self) -> bool { + matches!(self, Self::Auto) + } } pub use self::GenericInset as Inset; diff --git a/style/values/generics/transform.rs b/style/values/generics/transform.rs index 0d26dc2c9e..70dba82a05 100644 --- a/style/values/generics/transform.rs +++ b/style/values/generics/transform.rs @@ -604,6 +604,13 @@ impl Transform { } /// Same as Transform::to_transform_3d_matrix but a f64 version. + pub fn to_transform_3d_matrix_f64( + &self, + reference_box: Option<&Rect> + ) -> Result<(Transform3D, bool), ()> { + Self::components_to_transform_3d_matrix_f64(&self.0, reference_box) + } + fn components_to_transform_3d_matrix_f64( ops: &[T], reference_box: Option<&Rect>, diff --git a/style/values/mod.rs b/style/values/mod.rs index ef0ed1fb0c..ad416b09d8 100644 --- a/style/values/mod.rs +++ b/style/values/mod.rs @@ -297,6 +297,16 @@ impl cssparser::ToCss for GenericAtomIdent style_traits::ToCss for GenericAtomIdent { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: Write, + { + serialize_atom_identifier(&self.0, dest) + } +} + #[cfg(feature = "servo")] impl PrecomputedHash for GenericAtomIdent { #[inline] @@ -718,7 +728,7 @@ impl ToCss for KeyframesName { return dest.write_str("none"); } - let serialize = |string: &_| { + let mut serialize = |string: &_| { if CustomIdent::is_valid(string, &["none"]) { serialize_identifier(string, dest) } else { diff --git a/style/values/specified/animation.rs b/style/values/specified/animation.rs index fd12e43c14..a711661609 100644 --- a/style/values/specified/animation.rs +++ b/style/values/specified/animation.rs @@ -311,7 +311,7 @@ impl AnimationPlayState { #[cfg(feature = "gecko")] return name.with_str(|n| Self::from_ident(n).is_ok()); #[cfg(feature = "servo")] - return Self::from_ident(atom).is_ok(); + return Self::from_ident(name).is_ok(); } false } @@ -346,7 +346,10 @@ impl AnimationFillMode { #[inline] pub fn match_keywords(name: &AnimationName) -> bool { if let Some(atom) = name.as_atom() { + #[cfg(feature = "gecko")] return !name.is_none() && atom.with_str(|n| Self::from_ident(n).is_ok()); + #[cfg(feature = "servo")] + return !name.is_none() && Self::from_ident(atom).is_ok(); } false } diff --git a/style/values/specified/box.rs b/style/values/specified/box.rs index 93f26b6325..f227ad61d4 100644 --- a/style/values/specified/box.rs +++ b/style/values/specified/box.rs @@ -5,7 +5,6 @@ //! Specified types for box properties. use crate::parser::{Parse, ParserContext}; -#[cfg(feature = "gecko")] use crate::properties::{LonghandId, PropertyDeclarationId, PropertyId}; use crate::values::generics::box_::{ GenericContainIntrinsicSize, GenericLineClamp, GenericPerspective, GenericVerticalAlign, @@ -16,7 +15,7 @@ use crate::values::specified::{AllowQuirks, Integer, NonNegativeNumberOrPercenta use crate::values::CustomIdent; use cssparser::Parser; use num_traits::FromPrimitive; -use std::fmt::{self, Write}; +use std::fmt::{self, Debug, Write}; use style_traits::{CssWriter, KeywordsCollectFn, ParseError}; use style_traits::{SpecifiedValueInfo, StyleParseErrorKind, ToCss}; @@ -31,10 +30,7 @@ fn grid_enabled() -> bool { #[cfg(feature = "servo")] fn flexbox_enabled() -> bool { - servo_config::prefs::pref_map() - .get("layout.flexbox.enabled") - .as_bool() - .unwrap_or(false) + style_config::get_bool("layout.flexbox.enabled") } #[cfg(feature = "servo")] fn grid_enabled() -> bool { @@ -1025,7 +1021,7 @@ bitflags! { } #[cfg(feature="servo")] -fn change_bits_for_longhand(longhand: LonghandId) -> WillChangeBits { WillChangeBits::empty() } +fn change_bits_for_longhand(_longhand: LonghandId) -> WillChangeBits { WillChangeBits::empty() } #[cfg(feature = "gecko")] fn change_bits_for_longhand(longhand: LonghandId) -> WillChangeBits { diff --git a/style/values/specified/effects.rs b/style/values/specified/effects.rs index 64e7ed290c..8ed38b739c 100644 --- a/style/values/specified/effects.rs +++ b/style/values/specified/effects.rs @@ -53,7 +53,7 @@ pub type SpecifiedFilter = GenericFilter< NonNegativeFactor, ZeroToOneFactor, NonNegativeLength, - Impossible, + SimpleShadow, Impossible, >; diff --git a/style/values/specified/font.rs b/style/values/specified/font.rs index ee01506248..0457ee8f4d 100644 --- a/style/values/specified/font.rs +++ b/style/values/specified/font.rs @@ -771,48 +771,41 @@ pub const FONT_MEDIUM_PX: f32 = 16.0; pub const FONT_MEDIUM_LINE_HEIGHT_PX: f32 = FONT_MEDIUM_PX * 1.2; impl FontSizeKeyword { - #[inline] - #[cfg(feature = "servo")] - fn to_length(&self, _: &Context) -> NonNegativeLength { - let medium = Length::new(FONT_MEDIUM_PX); - // https://drafts.csswg.org/css-fonts-3/#font-size-prop - NonNegative(match *self { - FontSizeKeyword::XXSmall => medium * 3.0 / 5.0, - FontSizeKeyword::XSmall => medium * 3.0 / 4.0, - FontSizeKeyword::Small => medium * 8.0 / 9.0, - FontSizeKeyword::Medium => medium, - FontSizeKeyword::Large => medium * 6.0 / 5.0, - FontSizeKeyword::XLarge => medium * 3.0 / 2.0, - FontSizeKeyword::XXLarge => medium * 2.0, - FontSizeKeyword::XXXLarge => medium * 3.0, - FontSizeKeyword::Math | FontSizeKeyword::None => unreachable!(), - }) - } - - #[cfg(feature = "gecko")] #[inline] fn to_length(&self, cx: &Context) -> NonNegativeLength { let font = cx.style().get_font(); + + #[cfg(feature = "servo")] + let family = &font.font_family.families; + + #[cfg(feature = "gecko")] let family = &font.mFont.family.families; + let generic = family .single_generic() .unwrap_or(computed::GenericFontFamily::None); + + #[cfg(feature = "gecko")] let base_size = unsafe { Atom::with(font.mLanguage.mRawPtr, |language| { cx.device().base_size_for_generic(language, generic) }) }; + + #[cfg(feature = "servo")] + let base_size = cx.device().base_size_for_generic(generic); + self.to_length_without_context(cx.quirks_mode, base_size) } /// Resolve a keyword length without any context, with explicit arguments. - #[cfg(feature = "gecko")] #[inline] pub fn to_length_without_context( &self, quirks_mode: QuirksMode, base_size: Length, ) -> NonNegativeLength { + #[cfg(feature = "gecko")] debug_assert_ne!(*self, FontSizeKeyword::Math); // The tables in this function are originally from // nsRuleNode::CalcFontPointSize in Gecko: @@ -932,6 +925,7 @@ impl FontSize { calc.resolve(base_size.resolve(context).computed_size()) }, FontSize::Keyword(i) => { + #[cfg(feature="gecko")] if i.kw == FontSizeKeyword::Math { // Scaling is done in recompute_math_font_size_if_needed(). info = compose_keyword(1.); @@ -946,6 +940,11 @@ impl FontSize { info = i; i.to_computed_value(context).clamp_to_non_negative() } + #[cfg(feature="servo")] { + // As a specified keyword, this is keyword derived + info = i; + i.to_computed_value(context).clamp_to_non_negative() + } }, FontSize::Smaller => { info = compose_keyword(1. / LARGER_FONT_SIZE_RATIO); diff --git a/style/values/specified/image.rs b/style/values/specified/image.rs index 5d494a44c7..8ce05399f6 100644 --- a/style/values/specified/image.rs +++ b/style/values/specified/image.rs @@ -43,7 +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 -size_of_test!(Image, 16); +size_of_test!(Image, 40); /// Specified values for a CSS gradient. /// @@ -1274,13 +1274,13 @@ impl generic::ColorStop { impl PaintWorklet { #[cfg(feature = "servo")] - fn parse_args<'i>(input: &mut Parser<'i, '_>) -> Result> { + fn parse_args<'i>(context: &ParserContext, input: &mut Parser<'i, '_>) -> Result> { use crate::custom_properties::SpecifiedValue; let name = Atom::from(&**input.expect_ident()?); let arguments = input .try_parse(|input| { input.expect_comma()?; - input.parse_comma_separated(SpecifiedValue::parse) + input.parse_comma_separated(|input| SpecifiedValue::parse(input, &context.url_data)) }) .unwrap_or_default(); Ok(Self { name, arguments }) diff --git a/style/values/specified/length.rs b/style/values/specified/length.rs index 352c14314c..95951ec5ce 100644 --- a/style/values/specified/length.rs +++ b/style/values/specified/length.rs @@ -1231,6 +1231,7 @@ impl NoCalcLength { /// absolute or (if a font metrics getter is provided) font-relative units. #[cfg(feature = "gecko")] #[inline] + #[cfg(feature = "gecko")] pub fn to_computed_pixel_length_with_font_metrics( &self, get_font_metrics: Option GeckoFontMetrics>, @@ -2008,17 +2009,13 @@ macro_rules! parse_size_non_length { ($size:ident, $input:expr, $auto_or_none:expr => $auto_or_none_ident:ident) => {{ let size = $input.try_parse(|input| { Ok(try_match_ident_ignore_ascii_case! { input, - #[cfg(feature = "gecko")] "min-content" | "-moz-min-content" => $size::MinContent, - #[cfg(feature = "gecko")] "max-content" | "-moz-max-content" => $size::MaxContent, - #[cfg(feature = "gecko")] "fit-content" | "-moz-fit-content" => $size::FitContent, #[cfg(feature = "gecko")] "-moz-available" => $size::MozAvailable, #[cfg(feature = "gecko")] "-webkit-fill-available" if is_webkit_fill_available_keyword_enabled() => $size::WebkitFillAvailable, - #[cfg(feature = "gecko")] "stretch" if is_stretch_enabled() => $size::Stretch, $auto_or_none => $size::$auto_or_none_ident, }) @@ -2033,7 +2030,6 @@ macro_rules! parse_size_non_length { fn is_webkit_fill_available_keyword_enabled() -> bool { static_prefs::pref!("layout.css.webkit-fill-available.enabled") } -#[cfg(feature = "gecko")] fn is_stretch_enabled() -> bool { static_prefs::pref!("layout.css.stretch-size-keyword.enabled") } @@ -2042,11 +2038,8 @@ fn is_stretch_enabled() -> bool { fn is_fit_content_function_enabled() -> bool { static_prefs::pref!("layout.css.fit-content-function.enabled") } -#[cfg(feature = "servo")] -fn is_fit_content_function_enabled() -> bool { - false -} +#[cfg(feature = "gecko")] macro_rules! parse_fit_content_function { ($size:ident, $input:expr, $context:expr, $allow_quirks:expr) => { if is_fit_content_function_enabled() { @@ -2070,6 +2063,7 @@ impl Size { allow_quirks: AllowQuirks, ) -> Result> { parse_size_non_length!(Size, input, "auto" => Auto); + #[cfg(feature = "gecko")] parse_fit_content_function!(Size, input, context, allow_quirks); if let Ok(length) = @@ -2109,6 +2103,7 @@ impl MaxSize { allow_quirks: AllowQuirks, ) -> Result> { parse_size_non_length!(MaxSize, input, "none" => None); + #[cfg(feature = "gecko")] parse_fit_content_function!(MaxSize, input, context, allow_quirks); if let Ok(length) = diff --git a/style/values/specified/list.rs b/style/values/specified/list.rs index d085bcf189..d10aaa96d8 100644 --- a/style/values/specified/list.rs +++ b/style/values/specified/list.rs @@ -7,6 +7,8 @@ #[cfg(feature = "gecko")] use crate::counter_style::{CounterStyle, CounterStyleParsingFlags}; use crate::parser::{Parse, ParserContext}; +#[cfg(feature = "servo")] +use crate::properties::longhands::list_style_type::SpecifiedValue as ListStyleType; use cssparser::{Parser, Token}; use style_traits::{ParseError, StyleParseErrorKind}; diff --git a/style/values/specified/text.rs b/style/values/specified/text.rs index 1e9c7a6819..b99d8fd1c2 100644 --- a/style/values/specified/text.rs +++ b/style/values/specified/text.rs @@ -365,10 +365,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; @@ -395,6 +400,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. @@ -483,7 +501,6 @@ pub enum TextAlign { /// Since selectors can't depend on the ancestor styles, we implement it with a /// magic value that computes to the right thing. Since this is an /// implementation detail, it shouldn't be exposed to web content. - #[cfg(feature = "gecko")] #[parse(condition = "ParserContext::chrome_rules_enabled")] MozCenterOrInherit, } @@ -519,7 +536,6 @@ impl ToComputedValue for TextAlign { _ => parent, } }, - #[cfg(feature = "gecko")] TextAlign::MozCenterOrInherit => { let parent = _context .builder diff --git a/style_config/Cargo.toml b/style_config/Cargo.toml new file mode 100644 index 0000000000..0076dbf6e8 --- /dev/null +++ b/style_config/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "style_config" +version = "0.0.1" +authors = ["The Servo Project Developers"] +license = "MPL-2.0" +edition = "2021" +publish = false + +[lib] +name = "style_config" +path = "lib.rs" + +[dependencies] +lazy_static = "1.4" diff --git a/style_config/lib.rs b/style_config/lib.rs new file mode 100644 index 0000000000..4881ea6df6 --- /dev/null +++ b/style_config/lib.rs @@ -0,0 +1,95 @@ +/* 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::RwLock; + +use lazy_static::lazy_static; + +lazy_static! { + static ref PREFS: Preferences = 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/style_static_prefs/Cargo.toml b/style_static_prefs/Cargo.toml new file mode 100644 index 0000000000..7a42225d8e --- /dev/null +++ b/style_static_prefs/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "static_prefs" +version = "0.1.0" +edition = "2021" +authors = ["The Servo Project Developers"] +license = "MPL-2.0" +publish = false diff --git a/style_static_prefs/src/lib.rs b/style_static_prefs/src/lib.rs new file mode 100644 index 0000000000..97c27c3ac9 --- /dev/null +++ b/style_static_prefs/src/lib.rs @@ -0,0 +1,39 @@ +/* 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.relative-color-syntax.enabled") => { + true + }; + ("layout.css.stretch-size-keyword.enabled") => { + true + }; + ("layout.css.transition-behavior.enabled") => { + true + }; + ($string:literal) => { + false + }; +} diff --git a/style_traits/Cargo.toml b/style_traits/Cargo.toml index 6e456c80e4..f558c4cef2 100644 --- a/style_traits/Cargo.toml +++ b/style_traits/Cargo.toml @@ -12,7 +12,7 @@ path = "lib.rs" [features] servo = ["servo_atoms", "cssparser/serde", "url", "euclid/serde"] -gecko = ["nsstring"] +gecko = [] [dependencies] app_units = "0.7" @@ -21,8 +21,7 @@ cssparser = "0.34" euclid = "0.22" lazy_static = "1" 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} +malloc_size_of_derive = "0.1" selectors = { path = "../selectors" } serde = "1.0" servo_arc = { path = "../servo_arc" } diff --git a/style_traits/lib.rs b/style_traits/lib.rs index 7e155538fb..66a07884d2 100644 --- a/style_traits/lib.rs +++ b/style_traits/lib.rs @@ -28,12 +28,12 @@ extern crate serde; extern crate servo_arc; #[cfg(feature = "servo")] extern crate servo_atoms; -#[cfg(feature = "servo")] -extern crate servo_url; extern crate thin_vec; extern crate to_shmem; #[macro_use] extern crate to_shmem_derive; +#[cfg(feature = "servo")] +extern crate url; use cssparser::{CowRcStr, Token}; use selectors::parser::SelectorParseErrorKind; diff --git a/style_traits/values.rs b/style_traits/values.rs index e406bb7b1c..e2b87b0229 100644 --- a/style_traits/values.rs +++ b/style_traits/values.rs @@ -100,6 +100,7 @@ pub trait ToCss { /// /// (This is a convenience wrapper for `to_css` and probably should not be overridden.) #[inline] + #[cfg(feature = "gecko")] fn to_css_nscstring(&self) -> nsCString { let mut s = nsCString::new(); self.to_css(&mut CssWriter::new(&mut s)).unwrap(); From e5643425c05e27fe4aeeec1b8e5e95d6c4550d0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20W=C3=BClker?= Date: Wed, 22 Jan 2025 23:54:28 +0100 Subject: [PATCH 3/8] Add slotchange atom (#112) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Needed for slotchange events Signed-off-by: Simon Wülker --- atoms/static_atoms.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/atoms/static_atoms.txt b/atoms/static_atoms.txt index d658fcf1c6..76e2b6e1f9 100644 --- a/atoms/static_atoms.txt +++ b/atoms/static_atoms.txt @@ -141,6 +141,7 @@ selectstart serif sessionavailable signalingstatechange +slotchange squeeze squeezeend squeezestart From 8247a356e5f1b355a0f1e2fb1e5fe0331d27079f Mon Sep 17 00:00:00 2001 From: Keith Cirkel Date: Fri, 24 Jan 2025 21:46:31 +0000 Subject: [PATCH 4/8] add cancel to static_atoms (#113) Signed-off-by: Keith Cirkel --- atoms/static_atoms.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/atoms/static_atoms.txt b/atoms/static_atoms.txt index 76e2b6e1f9..0b922e1482 100644 --- a/atoms/static_atoms.txt +++ b/atoms/static_atoms.txt @@ -28,6 +28,7 @@ center change characteristicvaluechanged checkbox +cancel click close closing From b2aff706472aa08db61ad1cae0e3dd54306f6fdf Mon Sep 17 00:00:00 2001 From: Oriol Brufau Date: Thu, 30 Jan 2025 11:55:02 -0800 Subject: [PATCH 5/8] Let animations&transitions fall back to discrete interpolation (#109) When we are trying to interpolate two values that can't be interpolated according to the animation type of the CSS property, CSS animations should always fall back to discrete interpolation, and CSS transitions should also do so when `transition-behavior` is `allow-discrete`. Signed-off-by: Oriol Brufau --- style/animation.rs | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/style/animation.rs b/style/animation.rs index f90b9df81f..41b26b98c4 100644 --- a/style/animation.rs +++ b/style/animation.rs @@ -95,11 +95,21 @@ impl PropertyAnimation { } /// Update the given animation at a given point of progress. - fn calculate_value(&self, progress: f64) -> Result { + fn calculate_value(&self, progress: f64, allow_discrete: bool) -> Result { + let progress = self.timing_function_output(progress); let procedure = Procedure::Interpolate { - progress: self.timing_function_output(progress), + progress, }; - self.from.animate(&self.to, procedure) + let animate = self.from.animate(&self.to, procedure); + if animate.is_ok() || !allow_discrete { + return animate; + } + // Fall back to discrete interpolation + Ok(if progress < 0.5 { + self.from.clone() + } else { + self.to.clone() + }) } } @@ -702,7 +712,9 @@ impl Animation { duration: duration_between_keyframes as f64, }; - if let Ok(value) = animation.calculate_value(progress_between_keyframes) { + if let Ok(value) = animation + .calculate_value(progress_between_keyframes, true /* allow_discrete */) + { map.insert(value.id().to_owned(), value); } } @@ -752,6 +764,10 @@ pub struct Transition { /// If this `Transition` has been replaced by a new one this field is /// used to help produce better reversed transitions. pub reversing_shortening_factor: f64, + + /// Whether this `Transition` allows discrete interpolation of values that + /// can't be interpolated according to the animation type of the property. + pub allow_discrete: bool, } impl Transition { @@ -833,14 +849,14 @@ impl Transition { } /// Update the given animation at a given point of progress. - pub fn calculate_value(&self, time: f64) -> Option { + pub fn calculate_value(&self, time: f64, allow_discrete: bool) -> Option { let progress = (time - self.start_time) / (self.property_animation.duration); if progress < 0.0 { return None; } self.property_animation - .calculate_value(progress.min(1.0)) + .calculate_value(progress.min(1.0), allow_discrete) .ok() } } @@ -1031,11 +1047,10 @@ impl ElementAnimationSet { new_style: &Arc, ) { let style = new_style.get_ui(); + let allow_discrete = style.transition_behavior_mod(index) == TransitionBehavior::AllowDiscrete; - #[cfg(feature = "servo")] - if !property_declaration_id.is_animatable() || - (style.transition_behavior_mod(index) != TransitionBehavior::AllowDiscrete && - property_declaration_id.is_discrete_animatable()) + if !property_declaration_id.is_animatable() + || (!allow_discrete && property_declaration_id.is_discrete_animatable()) { return; } @@ -1082,6 +1097,7 @@ impl ElementAnimationSet { is_new: true, reversing_adjusted_start_value, reversing_shortening_factor: 1.0, + allow_discrete, }; if let Some(old_transition) = self @@ -1114,7 +1130,7 @@ impl ElementAnimationSet { if transition.state == AnimationState::Canceled { continue; } - let value = match transition.calculate_value(now) { + let value = match transition.calculate_value(now, transition.allow_discrete) { Some(value) => value, None => continue, }; From 06e3f12281781e087d06f03ed506eb6c9355c3c1 Mon Sep 17 00:00:00 2001 From: Oriol Brufau Date: Fri, 31 Jan 2025 07:13:01 -0800 Subject: [PATCH 6/8] Avoid starting transitions if values can't be interpolated (#115) A property may have an animation type different than 'discrete', but still not be able to interpolate some values. In that case animations fall back to discrete interpolation, but transitions can only do so if allowed by `transition-behavior`. We were already handling that well, but we were still triggering `transitionstart` events even if no transition ended up happening. This patchs prevents the creation of the transition, and simplifies a bunch of code since we no longer need to check whether the transition is allowed while the transition runs. Signed-off-by: Oriol Brufau --- style/animation.rs | 53 ++++++++++++++++++---------------------------- 1 file changed, 21 insertions(+), 32 deletions(-) diff --git a/style/animation.rs b/style/animation.rs index 41b26b98c4..c90e2327c5 100644 --- a/style/animation.rs +++ b/style/animation.rs @@ -95,20 +95,18 @@ impl PropertyAnimation { } /// Update the given animation at a given point of progress. - fn calculate_value(&self, progress: f64, allow_discrete: bool) -> Result { + fn calculate_value(&self, progress: f64) -> AnimationValue { let progress = self.timing_function_output(progress); let procedure = Procedure::Interpolate { progress, }; - let animate = self.from.animate(&self.to, procedure); - if animate.is_ok() || !allow_discrete { - return animate; - } - // Fall back to discrete interpolation - Ok(if progress < 0.5 { - self.from.clone() - } else { - self.to.clone() + self.from.animate(&self.to, procedure).unwrap_or_else(|()| { + // Fall back to discrete interpolation + if progress < 0.5 { + self.from.clone() + } else { + self.to.clone() + } }) } } @@ -712,11 +710,8 @@ impl Animation { duration: duration_between_keyframes as f64, }; - if let Ok(value) = animation - .calculate_value(progress_between_keyframes, true /* allow_discrete */) - { - map.insert(value.id().to_owned(), value); - } + let value = animation.calculate_value(progress_between_keyframes); + map.insert(value.id().to_owned(), value); } } } @@ -764,10 +759,6 @@ pub struct Transition { /// If this `Transition` has been replaced by a new one this field is /// used to help produce better reversed transitions. pub reversing_shortening_factor: f64, - - /// Whether this `Transition` allows discrete interpolation of values that - /// can't be interpolated according to the animation type of the property. - pub allow_discrete: bool, } impl Transition { @@ -849,15 +840,9 @@ impl Transition { } /// Update the given animation at a given point of progress. - pub fn calculate_value(&self, time: f64, allow_discrete: bool) -> Option { + pub fn calculate_value(&self, time: f64) -> AnimationValue { let progress = (time - self.start_time) / (self.property_animation.duration); - if progress < 0.0 { - return None; - } - - self.property_animation - .calculate_value(progress.min(1.0), allow_discrete) - .ok() + self.property_animation.calculate_value(progress.clamp(0.0, 1.0)) } } @@ -1073,6 +1058,14 @@ impl ElementAnimationSet { None => return, }; + // 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) { + 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. @@ -1097,7 +1090,6 @@ impl ElementAnimationSet { is_new: true, reversing_adjusted_start_value, reversing_shortening_factor: 1.0, - allow_discrete, }; if let Some(old_transition) = self @@ -1130,10 +1122,7 @@ impl ElementAnimationSet { if transition.state == AnimationState::Canceled { continue; } - let value = match transition.calculate_value(now, transition.allow_discrete) { - Some(value) => value, - None => continue, - }; + let value = transition.calculate_value(now); map.insert(value.id().to_owned(), value); } From 816c35500ca077f88b5d38a3bcc79c8c568d307a Mon Sep 17 00:00:00 2001 From: Oriol Brufau Date: Fri, 31 Jan 2025 13:16:17 -0800 Subject: [PATCH 7/8] Let `resolve_color()` take its parameter by reference (#116) This way the callers don't have to clone it if they don't have ownership or want to use the value later. Signed-off-by: Oriol Brufau --- style/properties/properties.mako.rs | 4 ++-- style/values/resolved/color.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/style/properties/properties.mako.rs b/style/properties/properties.mako.rs index 84b15c0284..1c27ecbaff 100644 --- a/style/properties/properties.mako.rs +++ b/style/properties/properties.mako.rs @@ -1874,9 +1874,9 @@ impl ComputedValues { /// /// Usage example: /// let top_color = - /// style.resolve_color(style.get_border().clone_border_top_color()); + /// style.resolve_color(&style.get_border().clone_border_top_color()); #[inline] - pub fn resolve_color(&self, color: computed::Color) -> crate::color::AbsoluteColor { + pub fn resolve_color(&self, color: &computed::Color) -> crate::color::AbsoluteColor { let current_color = self.get_inherited_text().clone_color(); color.resolve_to_absolute(¤t_color) } diff --git a/style/values/resolved/color.rs b/style/values/resolved/color.rs index 79dfd8685f..b60ad6d59d 100644 --- a/style/values/resolved/color.rs +++ b/style/values/resolved/color.rs @@ -16,7 +16,7 @@ impl ToResolvedValue for computed::Color { #[inline] fn to_resolved_value(self, context: &Context) -> Self::ResolvedValue { - context.style.resolve_color(self) + context.style.resolve_color(&self) } #[inline] From 5440cf558fb49cfc45d0902391d0b87f6a118a89 Mon Sep 17 00:00:00 2001 From: Jelte Fennema-Nio Date: Mon, 3 Feb 2025 20:44:45 +0100 Subject: [PATCH 8/8] Update derive_more to v2.0.0 This updates `derive_more` to the most recent version. The main reason I'm interested in doing this is so that the latest version will become available on the Rust Playground. The `derive_more` crate is being pulled due to selectors, but it pulls in an outdated version due to selectors depending on that version. --- selectors/Cargo.toml | 2 +- style/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/selectors/Cargo.toml b/selectors/Cargo.toml index 4e495a6c6b..f13e4994c4 100644 --- a/selectors/Cargo.toml +++ b/selectors/Cargo.toml @@ -22,7 +22,7 @@ to_shmem = ["dep:to_shmem", "dep:to_shmem_derive"] [dependencies] bitflags = "2" cssparser = "0.34" -derive_more = { version = "0.99", default-features = false, features = ["add", "add_assign"] } +derive_more = { version = "2", default-features = false, features = ["add", "add_assign"] } fxhash = "0.2" log = "0.4" phf = "0.11" diff --git a/style/Cargo.toml b/style/Cargo.toml index d37a742e7e..e4e17704e1 100644 --- a/style/Cargo.toml +++ b/style/Cargo.toml @@ -57,7 +57,7 @@ atomic_refcell = "0.1" bitflags = "2" byteorder = "1.0" cssparser = "0.34" -derive_more = { version = "0.99", default-features = false, features = ["add", "add_assign", "deref", "deref_mut", "from"] } +derive_more = { version = "2", default-features = false, features = ["add", "add_assign", "deref", "deref_mut", "from"] } dom = { path = "../dom" } new_debug_unreachable = "1.0" encoding_rs = {version = "0.8", optional = true}