Resolve merge conflict

Signed-off-by: Uthman Yahaya Baba <uthmanyahayababa@gmail.com>
This commit is contained in:
Uthman Yahaya Baba 2025-05-23 23:11:55 +01:00
commit d3c02b1b99
2408 changed files with 74057 additions and 404769 deletions

1
.github/CODEOWNERS vendored
View file

@ -11,7 +11,6 @@
/components/compositing @mrobinson
# Reviewers for layout-related code
/components/layout_2020 @mrobinson @Loirooriol @nicoburns
/components/layout @mrobinson @Loirooriol @nicoburns
# Reviewers for Minibrowser related code

View file

@ -8,6 +8,9 @@ on:
profile:
required: true
type: string
build-args:
required: true
type: string
wpt-args:
required: true
type: string
@ -35,6 +38,7 @@ jobs:
secrets: inherit
with:
profile: ${{ inputs.profile }}
build-args: ${{ inputs.build-args }}
unit-tests: ${{ inputs.unit-tests }}
build-libservo: ${{ inputs.build-libservo }}
bencher: ${{ inputs.bencher }}
@ -46,6 +50,7 @@ jobs:
secrets: inherit
with:
profile: ${{ inputs.profile }}
build-args: ${{ inputs.build-args }}
wpt: ${{ inputs.wpt }}
unit-tests: ${{ inputs.unit-tests }}
build-libservo: ${{ inputs.build-libservo }}
@ -59,6 +64,7 @@ jobs:
secrets: inherit
with:
profile: ${{ inputs.profile }}
build-args: ${{ inputs.build-args }}
wpt: ${{ inputs.wpt }}
number-of-wpt-chunks: ${{ inputs.number-of-wpt-chunks }}
unit-tests: ${{ inputs.unit-tests }}

View file

@ -6,6 +6,10 @@ on:
required: false
default: "release"
type: string
build-args:
default: ""
required: false
type: string
wpt-args:
default: ""
required: false
@ -166,7 +170,7 @@ jobs:
- name: Build (${{ inputs.profile }})
run: |
./mach build --use-crown --locked --${{ inputs.profile }}
./mach build --use-crown --locked --${{ inputs.profile }} ${{ inputs.build-args }}
cp -r target/cargo-timings target/cargo-timings-linux
- name: Smoketest
run: xvfb-run ./mach smoketest --${{ inputs.profile }}

View file

@ -7,6 +7,10 @@ on:
required: false
default: "release"
type: string
build-args:
default: ""
required: false
type: string
wpt-args:
default: ""
required: false
@ -146,7 +150,7 @@ jobs:
brew install gnu-tar
- name: Build (${{ inputs.profile }})
run: |
./mach build --use-crown --locked --${{ inputs.profile }}
./mach build --use-crown --locked --${{ inputs.profile }} ${{ inputs.build-args }}
cp -r target/cargo-timings target/cargo-timings-macos
- name: Smoketest
uses: nick-fields/retry@v3

View file

@ -56,6 +56,7 @@ jobs:
unit-tests: ${{ matrix.unit_tests }}
build-libservo: ${{ matrix.build_libservo }}
wpt-args: ${{ matrix.wpt_args }}
build-args: ${{ matrix.build_args }}
number-of-wpt-chunks: ${{ matrix.number_of_wpt_chunks }}
bencher: ${{ matrix.bencher }}

View file

@ -36,6 +36,7 @@ env:
RUST_BACKTRACE: 1
SHELL: /bin/bash
CARGO_INCREMENTAL: 0
BENCHER_PROJECT: ${{ vars.BENCHER_PROJECT || 'servo' }}
jobs:
build:
@ -168,11 +169,17 @@ jobs:
- name: Build for aarch64 HarmonyOS
run: |
./mach build --locked --target aarch64-unknown-linux-ohos --profile=${{ inputs.profile }} --flavor=harmonyos --no-default-features --features tracing,tracing-hitrace
- name: Upload supprt/hitrace-bencher/runs.json
uses: actions/upload-artifact@v4
with:
name: runs.json
path: support/hitrace-bencher/runs.json
overwrite: true
- uses: actions/upload-artifact@v4
with:
# Upload the **unsigned** artifact - We don't have the signing materials in pull request workflows
path: target/openharmony/aarch64-unknown-linux-ohos/${{ inputs.profile }}/entry/build/harmonyos/outputs/default/servoshell-default-unsigned.hap
name: servoshell-hos-${{ inputs.profile }}.hap
path: target/openharmony/aarch64-unknown-linux-ohos/${{ inputs.profile }}/entry/build/harmonyos/outputs/default/servoshell-default-unsigned.hap
test-harmonyos-aarch64:
@ -223,10 +230,8 @@ jobs:
hdc shell snapshot_display -f /data/local/tmp/servo.jpeg
hdc file recv /data/local/tmp/servo.jpeg test_output/servo_hos_screenshot.jpeg
hdc file recv /data/local/tmp/ohtrace.txt test_output/servo.ftrace
# To limit the logsize we only save logs from servo.
# Todo: investigate giving servo a custom domain tag, so we can also log appspawn etc,
# since another common error might be the dynamic loader failing to relocate libservoshell.so
hdc shell hilog --exit --pid=${servo_pid} > test_output/servo.log
# To limit the logsize we only save logs from servo.
hdc shell hilog --exit -D 0xE0C3 > test_output/servo.log
# todo: Also benchmark some other websites....
- name: Upload artifacts
uses: actions/upload-artifact@v4
@ -240,3 +245,19 @@ jobs:
[[ $servo_pid =~ ^[0-9]+$ ]] || { echo "It looks like servo crashed!" ; exit 1; }
# If the grep fails, then the trace output for the "page loaded" prompt is missing
grep 'org\.servo\.servo-.* tracing_mark_write.*PageLoadEndedPrompt' test_output/servo.ftrace
- name: Getting runs file
uses: actions/download-artifact@v4
with:
# Name of the artifact to download.
# If unspecified, all artifacts for the run are downloaded.
name: runs.json
- name: "Run benchmark"
run: hitrace-bench -r runs.json
- name: Getting bencher
uses: bencherdev/bencher@main
- name: Getting model name
run: |
echo "MODEL_NAME=$(hdc bugreport | head -n 20 | grep MarketName | awk '{for (i=2; i<NF; i++) printf $i " "; print $NF}' -)" >> $GITHUB_ENV
- name: Uploading to bencher.dev
run: |
bencher run --adapter json --file bench.json --project '${{ env.BENCHER_PROJECT }}' --token '${{ secrets.BENCHER_API_TOKEN }}' --github-actions '${{ secrets.GITHUB_TOKEN }}' --testbed="$MODEL_NAME"

View file

@ -128,6 +128,7 @@ jobs:
unit-tests: ${{ matrix.unit_tests }}
build-libservo: ${{ matrix.build_libservo }}
wpt-args: ${{ matrix.wpt_args }}
build-args: ${{ matrix.build_args }}
number-of-wpt-chunks: ${{ matrix.number_of_wpt_chunks }}
bencher: ${{ matrix.bencher }}

View file

@ -10,6 +10,10 @@ on:
default: "release"
type: choice
options: ["release", "debug", "production"]
build-args:
default: ""
required: false
type: string
wpt-args:
default: ""
required: false
@ -79,6 +83,7 @@ jobs:
// WPT-related overrides only affect Linux currently, as tests don't run by default on other platforms.
configuration.matrix[0].wpt = Boolean(${{ inputs.wpt }});
configuration.matrix[0].wpt_args = "${{ inputs.wpt-args }}" || "";
configuration.matrix[0].build_args = "${{ inputs.build-args }}" || "";
let unit_tests = Boolean(${{ inputs.unit-tests }});
let profile = '${{ inputs.profile }}';
@ -107,6 +112,7 @@ jobs:
profile: ${{ matrix.profile }}
unit-tests: ${{ matrix.unit_tests }}
build-libservo: ${{ matrix.build_libservo }}
build-args: ${{ matrix.build_args }}
wpt-args: ${{ matrix.wpt_args }}
number-of-wpt-chunks: ${{ matrix.number_of_wpt_chunks }}
bencher: ${{ matrix.bencher }}

View file

@ -7,6 +7,10 @@ on:
required: false
default: "release"
type: string
build-args:
default: ""
required: false
type: string
unit-tests:
required: false
default: false
@ -161,7 +165,7 @@ jobs:
- name: Build (${{ inputs.profile }})
run: |
.\mach build --use-crown --locked --${{ inputs.profile }}
.\mach build --use-crown --locked --${{ inputs.profile }} ${{ inputs.build-args }}
cp C:\a\servo\servo\target\cargo-timings C:\a\servo\servo\target\cargo-timings-windows -Recurse
- name: Copy resources
if: ${{ runner.environment != 'self-hosted' }}

3
.gitignore vendored
View file

@ -57,6 +57,9 @@ webrender-captures/
Session.vim
Sessionx.vim
# Zed
/.zed
/unminified-js
/unminified-css

821
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -16,7 +16,7 @@ publish = false
rust-version = "1.85.0"
[workspace.dependencies]
accountable-refcell = "0.2.0"
accountable-refcell = "0.2.2"
aes = "0.8.4"
aes-gcm = "0.10.3"
aes-kw = { version = "0.2.1", features = ["alloc"] }
@ -84,7 +84,7 @@ image = "0.24"
imsz = "0.2"
indexmap = { version = "2.9.0", features = ["std"] }
ipc-channel = "0.19"
itertools = "0.13"
itertools = "0.14"
js = { package = "mozjs", git = "https://github.com/servo/mozjs" }
keyboard-types = "0.7"
libc = "0.2"
@ -115,30 +115,32 @@ rayon = "1"
regex = "1.11"
rustls = { version = "0.23", default-features = false, features = ["logging", "std", "tls12"] }
rustls-pemfile = "2.0"
rustls-pki-types = "1.11"
rustls-pki-types = "1.12"
script_layout_interface = { path = "components/shared/script_layout" }
script_traits = { path = "components/shared/script" }
selectors = { git = "https://github.com/servo/stylo", branch = "2025-03-15" }
selectors = { git = "https://github.com/servo/stylo", branch = "2025-05-01" }
serde = "1.0.219"
serde_bytes = "0.11"
serde_json = "1.0"
servo-media = { git = "https://github.com/servo/media" }
servo-media-dummy = { git = "https://github.com/servo/media" }
servo-media-gstreamer = { git = "https://github.com/servo/media" }
servo_arc = { git = "https://github.com/servo/stylo", branch = "2025-03-15" }
servo-tracing = { path = "components/servo_tracing" }
servo_arc = { git = "https://github.com/servo/stylo", branch = "2025-05-01" }
smallbitvec = "2.6.0"
smallvec = "1.15"
snapshot = { path = "./components/shared/snapshot" }
static_assertions = "1.1"
string_cache = "0.8"
string_cache_codegen = "0.5"
strum = "0.26"
strum_macros = "0.26"
stylo = { git = "https://github.com/servo/stylo", branch = "2025-03-15" }
stylo_atoms = { git = "https://github.com/servo/stylo", branch = "2025-03-15" }
stylo_config = { git = "https://github.com/servo/stylo", branch = "2025-03-15" }
stylo_dom = { git = "https://github.com/servo/stylo", branch = "2025-03-15" }
stylo_malloc_size_of = { git = "https://github.com/servo/stylo", branch = "2025-03-15" }
stylo_traits = { git = "https://github.com/servo/stylo", branch = "2025-03-15" }
stylo = { git = "https://github.com/servo/stylo", branch = "2025-05-01" }
stylo_atoms = { git = "https://github.com/servo/stylo", branch = "2025-05-01" }
stylo_config = { git = "https://github.com/servo/stylo", branch = "2025-05-01" }
stylo_dom = { git = "https://github.com/servo/stylo", branch = "2025-05-01" }
stylo_malloc_size_of = { git = "https://github.com/servo/stylo", branch = "2025-05-01" }
stylo_traits = { git = "https://github.com/servo/stylo", branch = "2025-05-01" }
surfman = { git = "https://github.com/servo/surfman", rev = "f7688b4585f9e0b5d4bf8ee8e4a91e82349610b1", features = ["chains"] }
syn = { version = "2", default-features = false, features = ["clone-impls", "derive", "parsing"] }
synstructure = "0.13"
@ -160,19 +162,20 @@ unicode-properties = { version = "0.1.3", features = ["emoji"] }
unicode-script = "0.5"
unicode-segmentation = "1.12.0"
url = "2.5"
urlpattern = "0.3"
uuid = { version = "1.12.1", features = ["v4"] }
webdriver = "0.51.0"
webdriver = "0.53.0"
webgpu_traits = { path = "components/shared/webgpu" }
webpki-roots = "0.26"
webrender = { git = "https://github.com/servo/webrender", branch = "0.66", features = ["capture"] }
webrender_api = { git = "https://github.com/servo/webrender", branch = "0.66" }
webrender = { git = "https://github.com/servo/webrender", branch = "0.67", features = ["capture"] }
webrender_api = { git = "https://github.com/servo/webrender", branch = "0.67" }
webxr-api = { path = "components/shared/webxr" }
wgpu-core = "25"
wgpu-types = "25"
winapi = "0.3"
windows-sys = "0.59"
wio = "0.2"
wr_malloc_size_of = { git = "https://github.com/servo/webrender", branch = "0.66" }
wr_malloc_size_of = { git = "https://github.com/servo/webrender", branch = "0.67" }
xi-unicode = "0.3.0"
xml5ever = "0.22"
@ -241,3 +244,7 @@ codegen-units = 1
#
# [patch."https://github.com/servo/<repository>"]
# <crate> = { path = "/path/to/local/checkout" }
#
# [patch."https://github.com/servo/rust-content-security-policy"]
# content-security-policy = { path = "../rust-content-security-policy/" }
# content-security-policy = { git = "https://github.com/timvdlippe/rust-content-security-policy/", branch = "fix-report-checks", features = ["serde"] }

View file

@ -1,4 +1,4 @@
# Security Policy
Given that Servo does not yet have customers or products, we are comfortable accepting the security related vulnerabilities as a [new GitHub issue](https://github.com/servo/servo/security/advisories/new) for now.
Given that Servo does not yet have customers or products, we are comfortable accepting the security related issues as [GitHub security reports](https://github.com/servo/servo/security/advisories/new) for now.

View file

@ -11,37 +11,24 @@ rust-version.workspace = true
name = "canvas"
path = "lib.rs"
[features]
webgl_backtrace = ["canvas_traits/webgl_backtrace"]
webxr = ["dep:webxr", "dep:webxr-api"]
[dependencies]
app_units = { workspace = true }
bitflags = { workspace = true }
byteorder = { workspace = true }
canvas_traits = { workspace = true }
compositing_traits = { workspace = true }
crossbeam-channel = { workspace = true }
cssparser = { workspace = true }
euclid = { workspace = true }
fnv = { workspace = true }
font-kit = "0.14"
fonts = { path = "../fonts" }
glow = { workspace = true }
half = "2"
ipc-channel = { workspace = true }
log = { workspace = true }
lyon_geom = "1.0.4"
net_traits = { workspace = true }
num-traits = { workspace = true }
pixels = { path = "../pixels" }
range = { path = "../range" }
raqote = "0.8.5"
servo_arc = { workspace = true }
snapshot = { workspace = true }
stylo = { workspace = true }
surfman = { workspace = true }
unicode-script = { workspace = true }
webrender = { workspace = true }
webrender_api = { workspace = true }
webxr = { path = "../webxr", features = ["ipc"], optional = true }
webxr-api = { workspace = true, features = ["ipc"], optional = true }

View file

@ -0,0 +1,270 @@
/* 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 canvas_traits::canvas::{
CompositionOrBlending, FillOrStrokeStyle, LineCapStyle, LineJoinStyle,
};
use euclid::Angle;
use euclid::default::{Point2D, Rect, Size2D, Transform2D, Vector2D};
use lyon_geom::Arc;
use style::color::AbsoluteColor;
use crate::canvas_data::{CanvasPaintState, Filter, TextRun};
pub(crate) trait Backend: Clone + Sized {
type Pattern<'a>: PatternHelpers + Clone;
type StrokeOptions: StrokeOptionsHelpers + Clone;
type Color: Clone;
type DrawOptions: DrawOptionsHelpers + Clone;
type CompositionOp;
type DrawTarget: GenericDrawTarget<Self>;
type PathBuilder: GenericPathBuilder<Self>;
type SourceSurface;
type Bytes<'a>: AsRef<[u8]>;
type Path: PathHelpers<Self> + Clone;
type GradientStop;
type GradientStops;
fn get_composition_op(&self, opts: &Self::DrawOptions) -> Self::CompositionOp;
fn need_to_draw_shadow(&self, color: &Self::Color) -> bool;
fn set_shadow_color(&mut self, color: AbsoluteColor, state: &mut CanvasPaintState<'_, Self>);
fn set_fill_style(
&mut self,
style: FillOrStrokeStyle,
state: &mut CanvasPaintState<'_, Self>,
drawtarget: &Self::DrawTarget,
);
fn set_stroke_style(
&mut self,
style: FillOrStrokeStyle,
state: &mut CanvasPaintState<'_, Self>,
drawtarget: &Self::DrawTarget,
);
fn set_global_composition(
&mut self,
op: CompositionOrBlending,
state: &mut CanvasPaintState<'_, Self>,
);
fn create_drawtarget(&self, size: Size2D<u64>) -> Self::DrawTarget;
fn new_paint_state<'a>(&self) -> CanvasPaintState<'a, Self>;
}
// This defines required methods for a DrawTarget (currently only implemented for raqote). The
// prototypes are derived from the now-removed Azure backend's methods.
pub(crate) trait GenericDrawTarget<B: Backend> {
fn clear_rect(&mut self, rect: &Rect<f32>);
fn copy_surface(
&mut self,
surface: B::SourceSurface,
source: Rect<i32>,
destination: Point2D<i32>,
);
fn create_path_builder(&self) -> B::PathBuilder;
fn create_similar_draw_target(&self, size: &Size2D<i32>) -> Self;
fn create_source_surface_from_data(&self, data: &[u8]) -> Option<B::SourceSurface>;
fn draw_surface(
&mut self,
surface: B::SourceSurface,
dest: Rect<f64>,
source: Rect<f64>,
filter: Filter,
draw_options: &B::DrawOptions,
);
fn draw_surface_with_shadow(
&self,
surface: B::SourceSurface,
dest: &Point2D<f32>,
color: &B::Color,
offset: &Vector2D<f32>,
sigma: f32,
operator: B::CompositionOp,
);
fn fill(&mut self, path: &B::Path, pattern: B::Pattern<'_>, draw_options: &B::DrawOptions);
fn fill_text(
&mut self,
text_runs: Vec<TextRun>,
start: Point2D<f32>,
pattern: &B::Pattern<'_>,
draw_options: &B::DrawOptions,
);
fn fill_rect(
&mut self,
rect: &Rect<f32>,
pattern: B::Pattern<'_>,
draw_options: Option<&B::DrawOptions>,
);
fn get_size(&self) -> Size2D<i32>;
fn get_transform(&self) -> Transform2D<f32>;
fn pop_clip(&mut self);
fn push_clip(&mut self, path: &B::Path);
fn set_transform(&mut self, matrix: &Transform2D<f32>);
fn stroke(
&mut self,
path: &B::Path,
pattern: B::Pattern<'_>,
stroke_options: &B::StrokeOptions,
draw_options: &B::DrawOptions,
);
fn stroke_line(
&mut self,
start: Point2D<f32>,
end: Point2D<f32>,
pattern: B::Pattern<'_>,
stroke_options: &B::StrokeOptions,
draw_options: &B::DrawOptions,
);
fn stroke_rect(
&mut self,
rect: &Rect<f32>,
pattern: B::Pattern<'_>,
stroke_options: &B::StrokeOptions,
draw_options: &B::DrawOptions,
);
fn surface(&self) -> B::SourceSurface;
fn bytes(&'_ self) -> B::Bytes<'_>;
}
/// A generic PathBuilder that abstracts the interface for azure's and raqote's PathBuilder.
pub(crate) trait GenericPathBuilder<B: Backend> {
fn arc(
&mut self,
origin: Point2D<f32>,
radius: f32,
start_angle: f32,
end_angle: f32,
anticlockwise: bool,
) {
Self::ellipse(
self,
origin,
radius,
radius,
0.,
start_angle,
end_angle,
anticlockwise,
);
}
fn bezier_curve_to(
&mut self,
control_point1: &Point2D<f32>,
control_point2: &Point2D<f32>,
control_point3: &Point2D<f32>,
);
fn close(&mut self);
#[allow(clippy::too_many_arguments)]
fn ellipse(
&mut self,
origin: Point2D<f32>,
radius_x: f32,
radius_y: f32,
rotation_angle: f32,
start_angle: f32,
end_angle: f32,
anticlockwise: bool,
) {
let mut start = Angle::radians(start_angle);
let mut end = Angle::radians(end_angle);
// Wrap angles mod 2 * PI if necessary
if !anticlockwise && start > end + Angle::two_pi() ||
anticlockwise && end > start + Angle::two_pi()
{
start = start.positive();
end = end.positive();
}
// Calculate the total arc we're going to sweep.
let sweep = match anticlockwise {
true => {
if end - start == Angle::two_pi() {
-Angle::two_pi()
} else if end > start {
-(Angle::two_pi() - (end - start))
} else {
-(start - end)
}
},
false => {
if start - end == Angle::two_pi() {
Angle::two_pi()
} else if start > end {
Angle::two_pi() - (start - end)
} else {
end - start
}
},
};
let arc: Arc<f32> = Arc {
center: origin,
radii: Vector2D::new(radius_x, radius_y),
start_angle: start,
sweep_angle: sweep,
x_rotation: Angle::radians(rotation_angle),
};
self.line_to(arc.from());
arc.for_each_quadratic_bezier(&mut |q| {
self.quadratic_curve_to(&q.ctrl, &q.to);
});
}
fn get_current_point(&mut self) -> Option<Point2D<f32>>;
fn line_to(&mut self, point: Point2D<f32>);
fn move_to(&mut self, point: Point2D<f32>);
fn quadratic_curve_to(&mut self, control_point: &Point2D<f32>, end_point: &Point2D<f32>);
fn svg_arc(
&mut self,
radius_x: f32,
radius_y: f32,
rotation_angle: f32,
large_arc: bool,
sweep: bool,
end_point: Point2D<f32>,
) {
let Some(start) = self.get_current_point() else {
return;
};
let arc = lyon_geom::SvgArc {
from: start,
to: end_point,
radii: lyon_geom::vector(radius_x, radius_y),
x_rotation: lyon_geom::Angle::degrees(rotation_angle),
flags: lyon_geom::ArcFlags { large_arc, sweep },
};
arc.for_each_quadratic_bezier(&mut |q| {
self.quadratic_curve_to(&q.ctrl, &q.to);
});
}
fn finish(&mut self) -> B::Path;
}
pub(crate) trait PatternHelpers {
fn is_zero_size_gradient(&self) -> bool;
fn draw_rect(&self, rect: &Rect<f32>) -> Rect<f32>;
}
pub(crate) trait StrokeOptionsHelpers {
fn set_line_width(&mut self, _val: f32);
fn set_miter_limit(&mut self, _val: f32);
fn set_line_join(&mut self, val: LineJoinStyle);
fn set_line_cap(&mut self, val: LineCapStyle);
fn set_line_dash(&mut self, items: Vec<f32>);
fn set_line_dash_offset(&mut self, offset: f32);
}
pub(crate) trait DrawOptionsHelpers {
fn set_alpha(&mut self, val: f32);
}
pub(crate) trait PathHelpers<B: Backend> {
fn transformed_copy_to_builder(&self, transform: &Transform2D<f32>) -> B::PathBuilder;
fn contains_point(&self, x: f64, y: f64, path_transform: &Transform2D<f32>) -> bool;
fn copy_to_builder(&self) -> B::PathBuilder;
}

File diff suppressed because it is too large Load diff

View file

@ -11,18 +11,21 @@ use canvas_traits::ConstellationCanvasMsg;
use canvas_traits::canvas::*;
use compositing_traits::CrossProcessCompositorApi;
use crossbeam_channel::{Sender, select, unbounded};
use euclid::default::Size2D;
use euclid::default::{Point2D, Rect, Size2D, Transform2D};
use fonts::{FontContext, SystemFontServiceProxy};
use ipc_channel::ipc::{self, IpcSender};
use ipc_channel::router::ROUTER;
use log::warn;
use net_traits::ResourceThreads;
use style::color::AbsoluteColor;
use style::properties::style_structs::Font as FontStyleStruct;
use webrender_api::ImageKey;
use crate::canvas_data::*;
use crate::raqote_backend::RaqoteBackend;
pub struct CanvasPaintThread<'a> {
canvases: HashMap<CanvasId, CanvasData<'a>>,
canvases: HashMap<CanvasId, Canvas<'a>>,
next_canvas_id: CanvasId,
compositor_api: CrossProcessCompositorApi,
font_context: Arc<FontContext>,
@ -76,7 +79,11 @@ impl<'a> CanvasPaintThread<'a> {
},
Ok(CanvasMsg::FromScript(message, canvas_id)) => match message {
FromScriptMsg::SendPixels(chan) => {
canvas_paint_thread.canvas(canvas_id).send_pixels(chan);
chan.send(canvas_paint_thread
.canvas(canvas_id)
.read_pixels(None, None)
.as_ipc()
).unwrap();
},
},
Err(e) => {
@ -109,10 +116,14 @@ impl<'a> CanvasPaintThread<'a> {
let canvas_id = self.next_canvas_id;
self.next_canvas_id.0 += 1;
let canvas_data =
CanvasData::new(size, self.compositor_api.clone(), self.font_context.clone());
let canvas_data = CanvasData::new(
size,
self.compositor_api.clone(),
self.font_context.clone(),
RaqoteBackend,
);
let image_key = canvas_data.image_key();
self.canvases.insert(canvas_id, canvas_data);
self.canvases.insert(canvas_id, Canvas::Raqote(canvas_data));
(canvas_id, image_key)
}
@ -159,24 +170,21 @@ impl<'a> CanvasPaintThread<'a> {
Canvas2dMsg::IsPointInPath(path, x, y, fill_rule, chan) => self
.canvas(canvas_id)
.is_point_in_path_(&path[..], x, y, fill_rule, chan),
Canvas2dMsg::DrawImage(
ref image_data,
image_size,
dest_rect,
source_rect,
smoothing_enabled,
) => self.canvas(canvas_id).draw_image(
image_data,
image_size,
dest_rect,
source_rect,
smoothing_enabled,
true,
),
Canvas2dMsg::DrawImage(snapshot, dest_rect, source_rect, smoothing_enabled) => {
let snapshot = snapshot.to_owned();
self.canvas(canvas_id).draw_image(
snapshot.data(),
snapshot.size(),
dest_rect,
source_rect,
smoothing_enabled,
!snapshot.alpha_mode().is_premultiplied(),
)
},
Canvas2dMsg::DrawEmptyImage(image_size, dest_rect, source_rect) => {
self.canvas(canvas_id).draw_image(
&vec![0; image_size.area() as usize * 4],
image_size,
image_size.to_u64(),
dest_rect,
source_rect,
false,
@ -192,10 +200,10 @@ impl<'a> CanvasPaintThread<'a> {
) => {
let image_data = self
.canvas(canvas_id)
.read_pixels(source_rect.to_u64(), image_size.to_u64());
.read_pixels(Some(source_rect.to_u64()), Some(image_size.to_u64()));
self.canvas(other_canvas_id).draw_image(
&image_data,
source_rect.size,
image_data.data(),
source_rect.size.to_u64(),
dest_rect,
source_rect,
smoothing,
@ -244,8 +252,10 @@ impl<'a> CanvasPaintThread<'a> {
self.canvas(canvas_id).set_global_composition(op)
},
Canvas2dMsg::GetImageData(dest_rect, canvas_size, sender) => {
let pixels = self.canvas(canvas_id).read_pixels(dest_rect, canvas_size);
sender.send(&pixels).unwrap();
let snapshot = self
.canvas(canvas_id)
.read_pixels(Some(dest_rect), Some(canvas_size));
sender.send(snapshot.as_ipc()).unwrap();
},
Canvas2dMsg::PutImageData(rect, receiver) => {
self.canvas(canvas_id)
@ -273,7 +283,347 @@ impl<'a> CanvasPaintThread<'a> {
}
}
fn canvas(&mut self, canvas_id: CanvasId) -> &mut CanvasData<'a> {
fn canvas(&mut self, canvas_id: CanvasId) -> &mut Canvas<'a> {
self.canvases.get_mut(&canvas_id).expect("Bogus canvas id")
}
}
enum Canvas<'a> {
Raqote(CanvasData<'a, RaqoteBackend>),
}
impl Canvas<'_> {
fn set_fill_style(&mut self, style: FillOrStrokeStyle) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.set_fill_style(style),
}
}
fn fill(&mut self) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.fill(),
}
}
fn fill_text(&mut self, text: String, x: f64, y: f64, max_width: Option<f64>, is_rtl: bool) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.fill_text(text, x, y, max_width, is_rtl),
}
}
fn fill_rect(&mut self, rect: &Rect<f32>) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.fill_rect(rect),
}
}
fn set_stroke_style(&mut self, style: FillOrStrokeStyle) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.set_stroke_style(style),
}
}
fn stroke_rect(&mut self, rect: &Rect<f32>) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.stroke_rect(rect),
}
}
fn begin_path(&mut self) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.begin_path(),
}
}
fn close_path(&mut self) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.close_path(),
}
}
fn fill_path(&mut self, path: &[PathSegment]) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.fill_path(path),
}
}
fn stroke(&mut self) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.stroke(),
}
}
fn stroke_path(&mut self, path: &[PathSegment]) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.stroke_path(path),
}
}
fn clip(&mut self) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.clip(),
}
}
fn is_point_in_path(&mut self, x: f64, y: f64, fill_rule: FillRule, chan: IpcSender<bool>) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.is_point_in_path(x, y, fill_rule, chan),
}
}
fn is_point_in_path_(
&mut self,
path: &[PathSegment],
x: f64,
y: f64,
fill_rule: FillRule,
chan: IpcSender<bool>,
) {
match self {
Canvas::Raqote(canvas_data) => {
canvas_data.is_point_in_path_(path, x, y, fill_rule, chan)
},
}
}
fn clear_rect(&mut self, rect: &Rect<f32>) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.clear_rect(rect),
}
}
fn draw_image(
&mut self,
data: &[u8],
size: Size2D<u64>,
dest_rect: Rect<f64>,
source_rect: Rect<f64>,
smoothing_enabled: bool,
is_premultiplied: bool,
) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.draw_image(
data,
size,
dest_rect,
source_rect,
smoothing_enabled,
is_premultiplied,
),
}
}
fn read_pixels(
&mut self,
read_rect: Option<Rect<u64>>,
canvas_size: Option<Size2D<u64>>,
) -> snapshot::Snapshot {
match self {
Canvas::Raqote(canvas_data) => canvas_data.read_pixels(read_rect, canvas_size),
}
}
fn move_to(&mut self, point: &Point2D<f32>) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.move_to(point),
}
}
fn line_to(&mut self, point: &Point2D<f32>) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.line_to(point),
}
}
fn rect(&mut self, rect: &Rect<f32>) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.rect(rect),
}
}
fn quadratic_curve_to(&mut self, cp: &Point2D<f32>, pt: &Point2D<f32>) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.quadratic_curve_to(cp, pt),
}
}
fn bezier_curve_to(&mut self, cp1: &Point2D<f32>, cp2: &Point2D<f32>, pt: &Point2D<f32>) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.bezier_curve_to(cp1, cp2, pt),
}
}
fn arc(&mut self, center: &Point2D<f32>, radius: f32, start: f32, end: f32, ccw: bool) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.arc(center, radius, start, end, ccw),
}
}
fn arc_to(&mut self, cp1: &Point2D<f32>, cp2: &Point2D<f32>, radius: f32) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.arc_to(cp1, cp2, radius),
}
}
#[allow(clippy::too_many_arguments)]
fn ellipse(
&mut self,
center: &Point2D<f32>,
radius_x: f32,
radius_y: f32,
rotation: f32,
start: f32,
end: f32,
ccw: bool,
) {
match self {
Canvas::Raqote(canvas_data) => {
canvas_data.ellipse(center, radius_x, radius_y, rotation, start, end, ccw)
},
}
}
fn restore_context_state(&mut self) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.restore_context_state(),
}
}
fn save_context_state(&mut self) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.save_context_state(),
}
}
fn set_line_width(&mut self, width: f32) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.set_line_width(width),
}
}
fn set_line_cap(&mut self, cap: LineCapStyle) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.set_line_cap(cap),
}
}
fn set_line_join(&mut self, join: LineJoinStyle) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.set_line_join(join),
}
}
fn set_miter_limit(&mut self, limit: f32) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.set_miter_limit(limit),
}
}
fn set_line_dash(&mut self, items: Vec<f32>) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.set_line_dash(items),
}
}
fn set_line_dash_offset(&mut self, offset: f32) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.set_line_dash_offset(offset),
}
}
fn set_transform(&mut self, matrix: &Transform2D<f32>) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.set_transform(matrix),
}
}
fn set_global_alpha(&mut self, alpha: f32) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.set_global_alpha(alpha),
}
}
fn set_global_composition(&mut self, op: CompositionOrBlending) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.set_global_composition(op),
}
}
fn set_shadow_offset_x(&mut self, value: f64) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.set_shadow_offset_x(value),
}
}
fn set_shadow_offset_y(&mut self, value: f64) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.set_shadow_offset_y(value),
}
}
fn set_shadow_blur(&mut self, value: f64) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.set_shadow_blur(value),
}
}
fn set_shadow_color(&mut self, color: AbsoluteColor) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.set_shadow_color(color),
}
}
fn set_font(&mut self, font_style: FontStyleStruct) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.set_font(font_style),
}
}
fn set_text_align(&mut self, text_align: TextAlign) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.set_text_align(text_align),
}
}
fn set_text_baseline(&mut self, text_baseline: TextBaseline) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.set_text_baseline(text_baseline),
}
}
fn measure_text(&mut self, text: String) -> TextMetrics {
match self {
Canvas::Raqote(canvas_data) => canvas_data.measure_text(text),
}
}
fn clip_path(&mut self, path: &[PathSegment]) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.clip_path(path),
}
}
fn get_transform(&self) -> Transform2D<f32> {
match self {
Canvas::Raqote(canvas_data) => canvas_data.get_transform(),
}
}
fn put_image_data(&mut self, unwrap: Vec<u8>, rect: Rect<u64>) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.put_image_data(unwrap, rect),
}
}
fn update_image_rendering(&mut self) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.update_image_rendering(),
}
}
fn recreate(&mut self, size: Option<Size2D<u64>>) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.recreate(size),
}
}
}

View file

@ -4,14 +4,8 @@
#![deny(unsafe_code)]
mod backend;
mod raqote_backend;
pub use webgl_mode::WebGLComm;
pub mod canvas_data;
pub mod canvas_paint_thread;
mod webgl_limits;
mod webgl_mode;
pub mod webgl_thread;
#[cfg(feature = "webxr")]
mod webxr;

View file

@ -7,20 +7,19 @@ use std::collections::HashMap;
use canvas_traits::canvas::*;
use cssparser::color::clamp_unit_f32;
use euclid::Angle;
use euclid::default::{Point2D, Rect, Size2D, Transform2D, Vector2D};
use font_kit::font::Font;
use fonts::{ByteIndex, FontIdentifier, FontTemplateRefMethods};
use log::warn;
use lyon_geom::Arc;
use range::Range;
use raqote::PathOp;
use style::color::AbsoluteColor;
use crate::canvas_data::{
self, Backend, CanvasPaintState, Color, CompositionOp, DrawOptions, Filter, GenericDrawTarget,
GenericPathBuilder, GradientStop, GradientStops, Path, SourceSurface, StrokeOptions, TextRun,
use crate::backend::{
Backend, DrawOptionsHelpers, GenericDrawTarget, GenericPathBuilder, PathHelpers,
PatternHelpers, StrokeOptionsHelpers,
};
use crate::canvas_data::{CanvasPaintState, Filter, TextRun};
thread_local! {
/// The shared font cache used by all canvases that render on a thread. It would be nicer
@ -30,80 +29,85 @@ thread_local! {
static SHARED_FONT_CACHE: RefCell<HashMap<FontIdentifier, Font>> = RefCell::default();
}
#[derive(Default)]
pub struct RaqoteBackend;
#[derive(Clone, Default)]
pub(crate) struct RaqoteBackend;
impl Backend for RaqoteBackend {
fn get_composition_op(&self, opts: &DrawOptions) -> CompositionOp {
CompositionOp::Raqote(opts.as_raqote().blend_mode)
type Pattern<'a> = Pattern<'a>;
type StrokeOptions = raqote::StrokeStyle;
type Color = raqote::SolidSource;
type DrawOptions = raqote::DrawOptions;
type CompositionOp = raqote::BlendMode;
type DrawTarget = raqote::DrawTarget;
type PathBuilder = PathBuilder;
type SourceSurface = Vec<u8>; // TODO: See if we can avoid the alloc (probably?)
type Bytes<'a> = &'a [u8];
type Path = raqote::Path;
type GradientStop = raqote::GradientStop;
type GradientStops = Vec<raqote::GradientStop>;
fn get_composition_op(&self, opts: &Self::DrawOptions) -> Self::CompositionOp {
opts.blend_mode
}
fn need_to_draw_shadow(&self, color: &Color) -> bool {
color.as_raqote().a != 0
fn need_to_draw_shadow(&self, color: &Self::Color) -> bool {
color.a != 0
}
fn set_shadow_color(&mut self, color: AbsoluteColor, state: &mut CanvasPaintState<'_>) {
state.shadow_color = Color::Raqote(color.to_raqote_style());
fn set_shadow_color(&mut self, color: AbsoluteColor, state: &mut CanvasPaintState<'_, Self>) {
state.shadow_color = color.to_raqote_style();
}
fn set_fill_style(
&mut self,
style: FillOrStrokeStyle,
state: &mut CanvasPaintState<'_>,
_drawtarget: &dyn GenericDrawTarget,
state: &mut CanvasPaintState<'_, Self>,
_drawtarget: &Self::DrawTarget,
) {
if let Some(pattern) = style.to_raqote_pattern() {
state.fill_style = canvas_data::Pattern::Raqote(pattern);
state.fill_style = pattern;
}
}
fn set_stroke_style(
&mut self,
style: FillOrStrokeStyle,
state: &mut CanvasPaintState<'_>,
_drawtarget: &dyn GenericDrawTarget,
state: &mut CanvasPaintState<'_, Self>,
_drawtarget: &Self::DrawTarget,
) {
if let Some(pattern) = style.to_raqote_pattern() {
state.stroke_style = canvas_data::Pattern::Raqote(pattern);
state.stroke_style = pattern;
}
}
fn set_global_composition(
&mut self,
op: CompositionOrBlending,
state: &mut CanvasPaintState<'_>,
state: &mut CanvasPaintState<'_, Self>,
) {
state.draw_options.as_raqote_mut().blend_mode = op.to_raqote_style();
state.draw_options.blend_mode = op.to_raqote_style();
}
fn create_drawtarget(&self, size: Size2D<u64>) -> Box<dyn GenericDrawTarget> {
Box::new(raqote::DrawTarget::new(
size.width as i32,
size.height as i32,
))
fn create_drawtarget(&self, size: Size2D<u64>) -> Self::DrawTarget {
raqote::DrawTarget::new(size.width as i32, size.height as i32)
}
fn recreate_paint_state<'a>(&self, _state: &CanvasPaintState<'a>) -> CanvasPaintState<'a> {
CanvasPaintState::default()
}
}
impl Default for CanvasPaintState<'_> {
fn default() -> Self {
fn new_paint_state<'a>(&self) -> CanvasPaintState<'a, Self> {
let pattern = Pattern::Color(255, 0, 0, 0);
CanvasPaintState {
draw_options: DrawOptions::Raqote(raqote::DrawOptions::new()),
fill_style: canvas_data::Pattern::Raqote(pattern.clone()),
stroke_style: canvas_data::Pattern::Raqote(pattern),
stroke_opts: StrokeOptions::Raqote(Default::default()),
draw_options: raqote::DrawOptions::new(),
fill_style: pattern.clone(),
stroke_style: pattern,
stroke_opts: Default::default(),
transform: Transform2D::identity(),
shadow_offset_x: 0.0,
shadow_offset_y: 0.0,
shadow_blur: 0.0,
shadow_color: Color::Raqote(raqote::SolidSource::from_unpremultiplied_argb(0, 0, 0, 0)),
shadow_color: raqote::SolidSource::from_unpremultiplied_argb(0, 0, 0, 0),
font_style: None,
text_align: TextAlign::default(),
text_baseline: TextBaseline::default(),
_backend: std::marker::PhantomData,
}
}
}
@ -230,136 +234,122 @@ impl Repetition {
}
}
impl canvas_data::Pattern<'_> {
pub fn source(&self) -> raqote::Source {
pub fn source<'a>(pattern: &Pattern<'a>) -> raqote::Source<'a> {
match pattern {
Pattern::Color(a, r, g, b) => raqote::Source::Solid(
raqote::SolidSource::from_unpremultiplied_argb(*a, *r, *g, *b),
),
Pattern::LinearGradient(pattern) => raqote::Source::new_linear_gradient(
pattern.gradient.clone(),
pattern.start,
pattern.end,
raqote::Spread::Pad,
),
Pattern::RadialGradient(pattern) => raqote::Source::new_two_circle_radial_gradient(
pattern.gradient.clone(),
pattern.center1,
pattern.radius1,
pattern.center2,
pattern.radius2,
raqote::Spread::Pad,
),
Pattern::Surface(pattern) => raqote::Source::Image(
pattern.image,
pattern.extend,
pattern.filter,
pattern.transform,
),
}
}
impl PatternHelpers for Pattern<'_> {
fn is_zero_size_gradient(&self) -> bool {
match self {
canvas_data::Pattern::Raqote(pattern) => match pattern {
Pattern::Color(a, r, g, b) => raqote::Source::Solid(
raqote::SolidSource::from_unpremultiplied_argb(*a, *r, *g, *b),
),
Pattern::LinearGradient(pattern) => raqote::Source::new_linear_gradient(
pattern.gradient.clone(),
pattern.start,
pattern.end,
raqote::Spread::Pad,
),
Pattern::RadialGradient(pattern) => raqote::Source::new_two_circle_radial_gradient(
pattern.gradient.clone(),
pattern.center1,
pattern.radius1,
pattern.center2,
pattern.radius2,
raqote::Spread::Pad,
),
Pattern::Surface(pattern) => raqote::Source::Image(
pattern.image,
pattern.extend,
pattern.filter,
pattern.transform,
),
Pattern::RadialGradient(pattern) => {
let centers_equal = pattern.center1 == pattern.center2;
let radii_equal = pattern.radius1 == pattern.radius2;
(centers_equal && radii_equal) || pattern.gradient.stops.is_empty()
},
}
}
pub fn is_zero_size_gradient(&self) -> bool {
match self {
canvas_data::Pattern::Raqote(pattern) => match pattern {
Pattern::RadialGradient(pattern) => {
let centers_equal = pattern.center1 == pattern.center2;
let radii_equal = pattern.radius1 == pattern.radius2;
(centers_equal && radii_equal) || pattern.gradient.stops.is_empty()
},
Pattern::LinearGradient(pattern) => {
(pattern.start == pattern.end) || pattern.gradient.stops.is_empty()
},
Pattern::Color(..) | Pattern::Surface(..) => false,
Pattern::LinearGradient(pattern) => {
(pattern.start == pattern.end) || pattern.gradient.stops.is_empty()
},
Pattern::Color(..) | Pattern::Surface(..) => false,
}
}
fn draw_rect(&self, rect: &Rect<f32>) -> Rect<f32> {
match self {
Pattern::Surface(pattern) => {
let pattern_rect = Rect::new(Point2D::origin(), pattern.size());
let mut draw_rect = rect.intersection(&pattern_rect).unwrap_or(Rect::zero());
match pattern.repetition() {
Repetition::NoRepeat => {
draw_rect.size.width = draw_rect.size.width.min(pattern_rect.size.width);
draw_rect.size.height = draw_rect.size.height.min(pattern_rect.size.height);
},
Repetition::RepeatX => {
draw_rect.size.width = rect.size.width;
draw_rect.size.height = draw_rect.size.height.min(pattern_rect.size.height);
},
Repetition::RepeatY => {
draw_rect.size.height = rect.size.height;
draw_rect.size.width = draw_rect.size.width.min(pattern_rect.size.width);
},
Repetition::Repeat => {
draw_rect = *rect;
},
}
draw_rect
},
Pattern::Color(..) | Pattern::LinearGradient(..) | Pattern::RadialGradient(..) => *rect,
}
}
}
impl StrokeOptions {
pub fn set_line_width(&mut self, _val: f32) {
match self {
StrokeOptions::Raqote(options) => options.width = _val,
}
impl StrokeOptionsHelpers for raqote::StrokeStyle {
fn set_line_width(&mut self, _val: f32) {
self.width = _val;
}
pub fn set_miter_limit(&mut self, _val: f32) {
match self {
StrokeOptions::Raqote(options) => options.miter_limit = _val,
}
fn set_miter_limit(&mut self, _val: f32) {
self.miter_limit = _val;
}
pub fn set_line_join(&mut self, val: LineJoinStyle) {
match self {
StrokeOptions::Raqote(options) => options.join = val.to_raqote_style(),
}
fn set_line_join(&mut self, val: LineJoinStyle) {
self.join = val.to_raqote_style();
}
pub fn set_line_cap(&mut self, val: LineCapStyle) {
match self {
StrokeOptions::Raqote(options) => options.cap = val.to_raqote_style(),
}
fn set_line_cap(&mut self, val: LineCapStyle) {
self.cap = val.to_raqote_style();
}
pub fn set_line_dash(&mut self, items: Vec<f32>) {
match self {
StrokeOptions::Raqote(options) => options.dash_array = items,
}
fn set_line_dash(&mut self, items: Vec<f32>) {
self.dash_array = items;
}
pub fn set_line_dash_offset(&mut self, offset: f32) {
match self {
StrokeOptions::Raqote(options) => options.dash_offset = offset,
}
}
pub fn as_raqote(&self) -> &raqote::StrokeStyle {
match self {
StrokeOptions::Raqote(options) => options,
}
fn set_line_dash_offset(&mut self, offset: f32) {
self.dash_offset = offset;
}
}
impl DrawOptions {
pub fn set_alpha(&mut self, val: f32) {
match self {
DrawOptions::Raqote(draw_options) => draw_options.alpha = val,
}
}
pub fn as_raqote(&self) -> &raqote::DrawOptions {
match self {
DrawOptions::Raqote(options) => options,
}
}
fn as_raqote_mut(&mut self) -> &mut raqote::DrawOptions {
match self {
DrawOptions::Raqote(options) => options,
}
impl DrawOptionsHelpers for raqote::DrawOptions {
fn set_alpha(&mut self, val: f32) {
self.alpha = val;
}
}
impl Path {
pub fn transformed_copy_to_builder(
&self,
transform: &Transform2D<f32>,
) -> Box<dyn GenericPathBuilder> {
Box::new(PathBuilder(Some(raqote::PathBuilder::from(
self.as_raqote().clone().transform(transform),
))))
impl PathHelpers<RaqoteBackend> for raqote::Path {
fn transformed_copy_to_builder(&self, transform: &Transform2D<f32>) -> PathBuilder {
PathBuilder(Some(raqote::PathBuilder::from(
self.clone().transform(transform),
)))
}
pub fn contains_point(&self, x: f64, y: f64, path_transform: &Transform2D<f32>) -> bool {
self.as_raqote()
.clone()
fn contains_point(&self, x: f64, y: f64, path_transform: &Transform2D<f32>) -> bool {
self.clone()
.transform(path_transform)
.contains_point(0.1, x as f32, y as f32)
}
pub fn copy_to_builder(&self) -> Box<dyn GenericPathBuilder> {
Box::new(PathBuilder(Some(raqote::PathBuilder::from(
self.as_raqote().clone(),
))))
}
pub fn as_raqote(&self) -> &raqote::Path {
match self {
Path::Raqote(p) => p,
}
fn copy_to_builder(&self) -> PathBuilder {
PathBuilder(Some(raqote::PathBuilder::from(self.clone())))
}
}
@ -373,7 +363,7 @@ fn create_gradient_stops(gradient_stops: Vec<CanvasGradientStop>) -> Vec<raqote:
stops
}
impl GenericDrawTarget for raqote::DrawTarget {
impl GenericDrawTarget<RaqoteBackend> for raqote::DrawTarget {
fn clear_rect(&mut self, rect: &Rect<f32>) {
let mut pb = raqote::PathBuilder::new();
pb.rect(
@ -385,59 +375,47 @@ impl GenericDrawTarget for raqote::DrawTarget {
let mut options = raqote::DrawOptions::new();
options.blend_mode = raqote::BlendMode::Clear;
let pattern = Pattern::Color(0, 0, 0, 0);
GenericDrawTarget::fill(
self,
&Path::Raqote(pb.finish()),
canvas_data::Pattern::Raqote(pattern),
&DrawOptions::Raqote(options),
);
<Self as GenericDrawTarget<RaqoteBackend>>::fill(self, &pb.finish(), pattern, &options);
}
#[allow(unsafe_code)]
fn copy_surface(
&mut self,
surface: SourceSurface,
surface: <RaqoteBackend as Backend>::SourceSurface,
source: Rect<i32>,
destination: Point2D<i32>,
) {
let mut dt = raqote::DrawTarget::new(source.size.width, source.size.height);
let data = surface.as_raqote();
let data = surface;
let s = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u32, data.len() / 4) };
dt.get_data_mut().copy_from_slice(s);
raqote::DrawTarget::copy_surface(self, &dt, source.to_box2d(), destination);
}
// TODO(pylbrecht)
// Somehow a duplicate of `create_gradient_stops()` with different types.
// It feels cumbersome to convert GradientStop back and forth just to use
// `create_gradient_stops()`, so I'll leave this here for now.
fn create_gradient_stops(&self, gradient_stops: Vec<GradientStop>) -> GradientStops {
let mut stops = gradient_stops
.into_iter()
.map(|item| *item.as_raqote())
.collect::<Vec<raqote::GradientStop>>();
// https://www.w3.org/html/test/results/2dcontext/annotated-spec/canvas.html#testrefs.2d.gradient.interpolate.overlap
stops.sort_by(|a, b| a.position.partial_cmp(&b.position).unwrap());
GradientStops::Raqote(stops)
}
fn create_path_builder(&self) -> Box<dyn GenericPathBuilder> {
Box::new(PathBuilder::new())
fn create_path_builder(&self) -> <RaqoteBackend as Backend>::PathBuilder {
PathBuilder::new()
}
fn create_similar_draw_target(&self, size: &Size2D<i32>) -> Box<dyn GenericDrawTarget> {
Box::new(raqote::DrawTarget::new(size.width, size.height))
fn create_similar_draw_target(
&self,
size: &Size2D<i32>,
) -> <RaqoteBackend as Backend>::DrawTarget {
raqote::DrawTarget::new(size.width, size.height)
}
fn create_source_surface_from_data(&self, data: &[u8]) -> Option<SourceSurface> {
Some(SourceSurface::Raqote(data.to_vec()))
fn create_source_surface_from_data(
&self,
data: &[u8],
) -> Option<<RaqoteBackend as Backend>::SourceSurface> {
Some(data.to_vec())
}
#[allow(unsafe_code)]
fn draw_surface(
&mut self,
surface: SourceSurface,
surface: <RaqoteBackend as Backend>::SourceSurface,
dest: Rect<f64>,
source: Rect<f64>,
filter: Filter,
draw_options: &DrawOptions,
draw_options: &<RaqoteBackend as Backend>::DrawOptions,
) {
let surface_data = surface.as_raqote();
let surface_data = surface;
let image = raqote::Image {
width: source.size.width as i32,
height: source.size.height as i32,
@ -470,33 +448,29 @@ impl GenericDrawTarget for raqote::DrawTarget {
dest.size.height as f32,
);
GenericDrawTarget::fill(
self,
&Path::Raqote(pb.finish()),
canvas_data::Pattern::Raqote(pattern),
draw_options,
);
<Self as GenericDrawTarget<RaqoteBackend>>::fill(self, &pb.finish(), pattern, draw_options);
}
fn draw_surface_with_shadow(
&self,
_surface: SourceSurface,
_surface: <RaqoteBackend as Backend>::SourceSurface,
_dest: &Point2D<f32>,
_color: &Color,
_color: &<RaqoteBackend as Backend>::Color,
_offset: &Vector2D<f32>,
_sigma: f32,
_operator: CompositionOp,
_operator: <RaqoteBackend as Backend>::CompositionOp,
) {
warn!("no support for drawing shadows");
}
fn fill(&mut self, path: &Path, pattern: canvas_data::Pattern, draw_options: &DrawOptions) {
match draw_options.as_raqote().blend_mode {
fn fill(
&mut self,
path: &<RaqoteBackend as Backend>::Path,
pattern: <RaqoteBackend as Backend>::Pattern<'_>,
draw_options: &<RaqoteBackend as Backend>::DrawOptions,
) {
match draw_options.blend_mode {
raqote::BlendMode::Src => {
self.clear(raqote::SolidSource::from_unpremultiplied_argb(0, 0, 0, 0));
self.fill(
path.as_raqote(),
&pattern.source(),
draw_options.as_raqote(),
);
self.fill(path, &source(&pattern), draw_options);
},
raqote::BlendMode::Clear |
raqote::BlendMode::SrcAtop |
@ -505,26 +479,19 @@ impl GenericDrawTarget for raqote::DrawTarget {
raqote::BlendMode::Xor |
raqote::BlendMode::DstOver |
raqote::BlendMode::SrcOver => {
self.fill(
path.as_raqote(),
&pattern.source(),
draw_options.as_raqote(),
);
self.fill(path, &source(&pattern), draw_options);
},
raqote::BlendMode::SrcIn |
raqote::BlendMode::SrcOut |
raqote::BlendMode::DstIn |
raqote::BlendMode::DstAtop => {
let mut options = *draw_options.as_raqote();
let mut options = *draw_options;
self.push_layer_with_blend(1., options.blend_mode);
options.blend_mode = raqote::BlendMode::SrcOver;
self.fill(path.as_raqote(), &pattern.source(), &options);
self.fill(path, &source(&pattern), &options);
self.pop_layer();
},
_ => warn!(
"unrecognized blend mode: {:?}",
draw_options.as_raqote().blend_mode
),
_ => warn!("unrecognized blend mode: {:?}", draw_options.blend_mode),
}
}
@ -532,8 +499,8 @@ impl GenericDrawTarget for raqote::DrawTarget {
&mut self,
text_runs: Vec<TextRun>,
start: Point2D<f32>,
pattern: &canvas_data::Pattern,
draw_options: &DrawOptions,
pattern: &<RaqoteBackend as Backend>::Pattern<'_>,
draw_options: &<RaqoteBackend as Backend>::DrawOptions,
) {
let mut advance = 0.;
for run in text_runs.iter() {
@ -581,8 +548,8 @@ impl GenericDrawTarget for raqote::DrawTarget {
run.font.descriptor.pt_size.to_f32_px(),
&ids,
&positions,
&pattern.source(),
draw_options.as_raqote(),
&source(pattern),
draw_options,
);
})
}
@ -591,8 +558,8 @@ impl GenericDrawTarget for raqote::DrawTarget {
fn fill_rect(
&mut self,
rect: &Rect<f32>,
pattern: canvas_data::Pattern,
draw_options: Option<&DrawOptions>,
pattern: <RaqoteBackend as Backend>::Pattern<'_>,
draw_options: Option<&<RaqoteBackend as Backend>::DrawOptions>,
) {
let mut pb = raqote::PathBuilder::new();
pb.rect(
@ -602,16 +569,16 @@ impl GenericDrawTarget for raqote::DrawTarget {
rect.size.height,
);
let draw_options = if let Some(options) = draw_options {
*options.as_raqote()
*options
} else {
raqote::DrawOptions::new()
};
GenericDrawTarget::fill(
<Self as GenericDrawTarget<RaqoteBackend>>::fill(
self,
&Path::Raqote(pb.finish()),
&pb.finish(),
pattern,
&DrawOptions::Raqote(draw_options),
&draw_options,
);
}
fn get_size(&self) -> Size2D<i32> {
@ -623,41 +590,36 @@ impl GenericDrawTarget for raqote::DrawTarget {
fn pop_clip(&mut self) {
self.pop_clip();
}
fn push_clip(&mut self, path: &Path) {
self.push_clip(path.as_raqote());
fn push_clip(&mut self, path: &<RaqoteBackend as Backend>::Path) {
self.push_clip(path);
}
fn set_transform(&mut self, matrix: &Transform2D<f32>) {
self.set_transform(matrix);
}
fn snapshot(&self) -> SourceSurface {
SourceSurface::Raqote(self.snapshot_data_owned())
fn surface(&self) -> <RaqoteBackend as Backend>::SourceSurface {
self.bytes().to_vec()
}
fn stroke(
&mut self,
path: &Path,
pattern: canvas_data::Pattern,
stroke_options: &StrokeOptions,
draw_options: &DrawOptions,
path: &<RaqoteBackend as Backend>::Path,
pattern: Pattern<'_>,
stroke_options: &<RaqoteBackend as Backend>::StrokeOptions,
draw_options: &<RaqoteBackend as Backend>::DrawOptions,
) {
self.stroke(
path.as_raqote(),
&pattern.source(),
stroke_options.as_raqote(),
draw_options.as_raqote(),
);
self.stroke(path, &source(&pattern), stroke_options, draw_options);
}
fn stroke_line(
&mut self,
start: Point2D<f32>,
end: Point2D<f32>,
pattern: canvas_data::Pattern,
stroke_options: &StrokeOptions,
draw_options: &DrawOptions,
pattern: <RaqoteBackend as Backend>::Pattern<'_>,
stroke_options: &<RaqoteBackend as Backend>::StrokeOptions,
draw_options: &<RaqoteBackend as Backend>::DrawOptions,
) {
let mut pb = raqote::PathBuilder::new();
pb.move_to(start.x, start.y);
pb.line_to(end.x, end.y);
let mut stroke_options = stroke_options.as_raqote().clone();
let mut stroke_options = stroke_options.clone();
let cap = match stroke_options.join {
raqote::LineJoin::Round => raqote::LineCap::Round,
_ => raqote::LineCap::Butt,
@ -666,17 +628,17 @@ impl GenericDrawTarget for raqote::DrawTarget {
self.stroke(
&pb.finish(),
&pattern.source(),
&source(&pattern),
&stroke_options,
draw_options.as_raqote(),
draw_options,
);
}
fn stroke_rect(
&mut self,
rect: &Rect<f32>,
pattern: canvas_data::Pattern,
stroke_options: &StrokeOptions,
draw_options: &DrawOptions,
pattern: <RaqoteBackend as Backend>::Pattern<'_>,
stroke_options: &<RaqoteBackend as Backend>::StrokeOptions,
draw_options: &<RaqoteBackend as Backend>::DrawOptions,
) {
let mut pb = raqote::PathBuilder::new();
pb.rect(
@ -688,26 +650,15 @@ impl GenericDrawTarget for raqote::DrawTarget {
self.stroke(
&pb.finish(),
&pattern.source(),
stroke_options.as_raqote(),
draw_options.as_raqote(),
&source(&pattern),
stroke_options,
draw_options,
);
}
#[allow(unsafe_code)]
fn snapshot_data(&self, f: &dyn Fn(&[u8]) -> Vec<u8>) -> Vec<u8> {
fn bytes(&self) -> &[u8] {
let v = self.get_data();
f(
unsafe {
std::slice::from_raw_parts(v.as_ptr() as *const u8, std::mem::size_of_val(v))
},
)
}
#[allow(unsafe_code)]
fn snapshot_data_owned(&self) -> Vec<u8> {
let v = self.get_data();
unsafe {
std::slice::from_raw_parts(v.as_ptr() as *const u8, std::mem::size_of_val(v)).into()
}
unsafe { std::slice::from_raw_parts(v.as_ptr() as *const u8, std::mem::size_of_val(v)) }
}
}
@ -720,7 +671,7 @@ impl Filter {
}
}
struct PathBuilder(Option<raqote::PathBuilder>);
pub(crate) struct PathBuilder(Option<raqote::PathBuilder>);
impl PathBuilder {
fn new() -> PathBuilder {
@ -728,25 +679,7 @@ impl PathBuilder {
}
}
impl GenericPathBuilder for PathBuilder {
fn arc(
&mut self,
origin: Point2D<f32>,
radius: f32,
start_angle: f32,
end_angle: f32,
anticlockwise: bool,
) {
self.ellipse(
origin,
radius,
radius,
0.,
start_angle,
end_angle,
anticlockwise,
);
}
impl GenericPathBuilder<RaqoteBackend> for PathBuilder {
fn bezier_curve_to(
&mut self,
control_point1: &Point2D<f32>,
@ -762,98 +695,16 @@ impl GenericPathBuilder for PathBuilder {
control_point3.y,
);
}
fn close(&mut self) {
self.0.as_mut().unwrap().close();
}
fn ellipse(
&mut self,
origin: Point2D<f32>,
radius_x: f32,
radius_y: f32,
rotation_angle: f32,
start_angle: f32,
end_angle: f32,
anticlockwise: bool,
) {
let mut start = Angle::radians(start_angle);
let mut end = Angle::radians(end_angle);
// Wrap angles mod 2 * PI if necessary
if !anticlockwise && start > end + Angle::two_pi() ||
anticlockwise && end > start + Angle::two_pi()
{
start = start.positive();
end = end.positive();
}
// Calculate the total arc we're going to sweep.
let sweep = match anticlockwise {
true => {
if end - start == Angle::two_pi() {
-Angle::two_pi()
} else if end > start {
-(Angle::two_pi() - (end - start))
} else {
-(start - end)
}
},
false => {
if start - end == Angle::two_pi() {
Angle::two_pi()
} else if start > end {
Angle::two_pi() - (start - end)
} else {
end - start
}
},
};
let arc: Arc<f32> = Arc {
center: origin,
radii: Vector2D::new(radius_x, radius_y),
start_angle: start,
sweep_angle: sweep,
x_rotation: Angle::radians(rotation_angle),
};
self.line_to(arc.from());
arc.for_each_quadratic_bezier(&mut |q| {
self.quadratic_curve_to(&q.ctrl, &q.to);
});
}
fn svg_arc(
&mut self,
radius_x: f32,
radius_y: f32,
rotation_angle: f32,
large_arc: bool,
sweep: bool,
end_point: Point2D<f32>,
) {
let Some(start) = self.get_current_point() else {
return;
};
let arc = lyon_geom::SvgArc {
from: start,
to: end_point,
radii: lyon_geom::vector(radius_x, radius_y),
x_rotation: lyon_geom::Angle::degrees(rotation_angle),
flags: lyon_geom::ArcFlags { large_arc, sweep },
};
arc.for_each_quadratic_bezier(&mut |q| {
self.quadratic_curve_to(&q.ctrl, &q.to);
});
}
fn get_current_point(&mut self) -> Option<Point2D<f32>> {
let path = self.finish();
self.0 = Some(path.as_raqote().clone().into());
self.0 = Some(path.clone().into());
path.as_raqote().ops.iter().last().and_then(|op| match op {
path.ops.iter().last().and_then(|op| match op {
PathOp::MoveTo(point) | PathOp::LineTo(point) => Some(Point2D::new(point.x, point.y)),
PathOp::CubicTo(_, _, point) => Some(Point2D::new(point.x, point.y)),
PathOp::QuadTo(_, point) => Some(Point2D::new(point.x, point.y)),
@ -875,8 +726,8 @@ impl GenericPathBuilder for PathBuilder {
end_point.y,
);
}
fn finish(&mut self) -> Path {
Path::Raqote(self.0.take().unwrap().finish())
fn finish(&mut self) -> raqote::Path {
self.0.take().unwrap().finish()
}
}
@ -988,14 +839,6 @@ impl ToRaqotePattern<'_> for FillOrStrokeStyle {
}
}
impl Color {
fn as_raqote(&self) -> &raqote::SolidSource {
match self {
Color::Raqote(s) => s,
}
}
}
impl ToRaqoteStyle for AbsoluteColor {
type Target = raqote::SolidSource;
@ -1065,19 +908,3 @@ impl ToRaqoteStyle for CompositionStyle {
}
}
}
impl SourceSurface {
fn as_raqote(&self) -> &Vec<u8> {
match self {
SourceSurface::Raqote(s) => s,
}
}
}
impl GradientStop {
fn as_raqote(&self) -> &raqote::GradientStop {
match self {
GradientStop::Raqote(s) => s,
}
}
}

View file

@ -34,7 +34,6 @@ log = { workspace = true }
net = { path = "../net" }
pixels = { path = "../pixels" }
profile_traits = { workspace = true }
script_traits = { workspace = true }
servo_allocator = { path = "../allocator" }
servo_config = { path = "../config" }
servo_geometry = { path = "../geometry" }

View file

@ -7,7 +7,6 @@ use std::collections::HashMap;
use std::env;
use std::fs::create_dir_all;
use std::iter::once;
use std::mem::take;
use std::rc::Rc;
use std::sync::Arc;
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
@ -21,14 +20,14 @@ use compositing_traits::rendering_context::RenderingContext;
use compositing_traits::{
CompositionPipeline, CompositorMsg, ImageUpdate, SendableFrameTree, WebViewTrait,
};
use constellation_traits::{AnimationTickType, EmbedderToConstellationMessage, PaintMetricEvent};
use constellation_traits::{EmbedderToConstellationMessage, PaintMetricEvent};
use crossbeam_channel::{Receiver, Sender};
use dpi::PhysicalSize;
use embedder_traits::{
AnimationState, CompositorHitTestResult, Cursor, InputEvent, MouseButtonEvent, MouseMoveEvent,
ShutdownState, TouchEventType, UntrustedNodeAddress, ViewportDetails,
CompositorHitTestResult, Cursor, InputEvent, MouseButtonEvent, MouseMoveEvent, ShutdownState,
TouchEventType, UntrustedNodeAddress, ViewportDetails, WheelDelta, WheelEvent, WheelMode,
};
use euclid::{Point2D, Rect, Scale, Size2D, Transform3D};
use euclid::{Point2D, Rect, Scale, Size2D, Transform3D, Vector2D};
use fnv::FnvHashMap;
use ipc_channel::ipc::{self, IpcSharedMemory};
use libc::c_void;
@ -55,7 +54,7 @@ use webrender_api::{
use crate::InitialCompositorState;
use crate::webview_manager::WebViewManager;
use crate::webview_renderer::{UnknownWebView, WebViewRenderer};
use crate::webview_renderer::{PinchZoomResult, UnknownWebView, WebViewRenderer};
#[derive(Debug, PartialEq)]
enum UnableToComposite {
@ -197,9 +196,6 @@ pub(crate) struct PipelineDetails {
/// The pipeline associated with this PipelineDetails object.
pub pipeline: Option<CompositionPipeline>,
/// The [`PipelineId`] of this pipeline.
pub id: PipelineId,
/// The id of the parent pipeline, if any.
pub parent_pipeline_id: Option<PipelineId>,
@ -243,32 +239,12 @@ impl PipelineDetails {
pub(crate) fn animating(&self) -> bool {
!self.throttled && (self.animation_callbacks_running || self.animations_running)
}
pub(crate) fn tick_animations(&self, compositor: &IOCompositor) {
if !self.animating() {
return;
}
let mut tick_type = AnimationTickType::empty();
if self.animations_running {
tick_type.insert(AnimationTickType::CSS_ANIMATIONS_AND_TRANSITIONS);
}
if self.animation_callbacks_running {
tick_type.insert(AnimationTickType::REQUEST_ANIMATION_FRAME);
}
let msg = EmbedderToConstellationMessage::TickAnimation(self.id, tick_type);
if let Err(e) = compositor.global.borrow().constellation_sender.send(msg) {
warn!("Sending tick to constellation failed ({:?}).", e);
}
}
}
impl PipelineDetails {
pub(crate) fn new(id: PipelineId) -> PipelineDetails {
pub(crate) fn new() -> PipelineDetails {
PipelineDetails {
pipeline: None,
id,
parent_pipeline_id: None,
most_recent_display_list_epoch: None,
animations_running: false,
@ -543,22 +519,14 @@ impl IOCompositor {
pipeline_id,
animation_state,
) => {
let mut throttled = true;
if let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) {
throttled = webview_renderer
.change_running_animations_state(pipeline_id, animation_state);
}
// These operations should eventually happen per-WebView, but they are global now as rendering
// is still global to all WebViews.
if !throttled && animation_state == AnimationState::AnimationsPresent {
self.set_needs_repaint(RepaintReason::ChangedAnimationState);
}
if !throttled && animation_state == AnimationState::AnimationCallbacksPresent {
// We need to fetch the WebView again in order to avoid a double borrow.
if let Some(webview_renderer) = self.webview_renderers.get(webview_id) {
webview_renderer.tick_animations_for_pipeline(pipeline_id, self);
if webview_renderer
.change_pipeline_running_animations_state(pipeline_id, animation_state) &&
webview_renderer.animating()
{
// These operations should eventually happen per-WebView, but they are
// global now as rendering is still global to all WebViews.
self.process_animations(true);
}
}
},
@ -605,8 +573,13 @@ impl IOCompositor {
CompositorMsg::SetThrottled(webview_id, pipeline_id, throttled) => {
if let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) {
webview_renderer.set_throttled(pipeline_id, throttled);
self.process_animations(true);
if webview_renderer.set_throttled(pipeline_id, throttled) &&
webview_renderer.animating()
{
// These operations should eventually happen per-WebView, but they are
// global now as rendering is still global to all WebViews.
self.process_animations(true);
}
}
},
@ -647,29 +620,57 @@ impl IOCompositor {
}
},
CompositorMsg::WebDriverMouseButtonEvent(webview_id, action, button, x, y) => {
CompositorMsg::WebDriverMouseButtonEvent(
webview_id,
action,
button,
x,
y,
message_id,
) => {
let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) else {
warn!("Handling input event for unknown webview: {webview_id}");
return;
};
let dppx = webview_renderer.device_pixels_per_page_pixel();
let point = dppx.transform_point(Point2D::new(x, y));
webview_renderer.dispatch_input_event(InputEvent::MouseButton(MouseButtonEvent {
point,
action,
button,
}));
webview_renderer.dispatch_input_event(
InputEvent::MouseButton(MouseButtonEvent::new(action, button, point))
.with_webdriver_message_id(Some(message_id)),
);
},
CompositorMsg::WebDriverMouseMoveEvent(webview_id, x, y) => {
CompositorMsg::WebDriverMouseMoveEvent(webview_id, x, y, message_id) => {
let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) else {
warn!("Handling input event for unknown webview: {webview_id}");
return;
};
let dppx = webview_renderer.device_pixels_per_page_pixel();
let point = dppx.transform_point(Point2D::new(x, y));
webview_renderer.dispatch_input_event(
InputEvent::MouseMove(MouseMoveEvent::new(point))
.with_webdriver_message_id(Some(message_id)),
);
},
CompositorMsg::WebDriverWheelScrollEvent(webview_id, x, y, delta_x, delta_y) => {
let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) else {
warn!("Handling input event for unknown webview: {webview_id}");
return;
};
let delta = WheelDelta {
x: delta_x,
y: delta_y,
z: 0.0,
mode: WheelMode::DeltaPixel,
};
let dppx = webview_renderer.device_pixels_per_page_pixel();
let point = dppx.transform_point(Point2D::new(x, y));
let scroll_delta =
dppx.transform_vector(Vector2D::new(delta_x as f32, delta_y as f32));
webview_renderer
.dispatch_input_event(InputEvent::MouseMove(MouseMoveEvent { point }));
.dispatch_input_event(InputEvent::Wheel(WheelEvent { delta, point }));
webview_renderer.on_webdriver_wheel_action(scroll_delta, point);
},
CompositorMsg::SendInitialTransaction(pipeline) => {
@ -1283,8 +1284,23 @@ impl IOCompositor {
}
self.last_animation_tick = Instant::now();
for webview_renderer in self.webview_renderers.iter() {
webview_renderer.tick_all_animations(self);
let animating_webviews: Vec<_> = self
.webview_renderers
.iter()
.filter_map(|webview_renderer| {
if webview_renderer.animating() {
Some(webview_renderer.id)
} else {
None
}
})
.collect();
if !animating_webviews.is_empty() {
if let Err(error) = self.global.borrow().constellation_sender.send(
EmbedderToConstellationMessage::TickAnimation(animating_webviews),
) {
warn!("Sending tick to constellation failed ({error:?}).");
}
}
}
@ -1448,10 +1464,11 @@ impl IOCompositor {
format: PixelFormat::RGBA8,
frames: vec![ImageFrame {
delay: None,
bytes: ipc::IpcSharedMemory::from_bytes(&image),
byte_range: 0..image.len(),
width: image.width(),
height: image.height(),
}],
bytes: ipc::IpcSharedMemory::from_bytes(&image),
id: None,
cors_status: CorsStatus::Safe,
}))
@ -1659,11 +1676,39 @@ impl IOCompositor {
if let Err(err) = self.rendering_context.make_current() {
warn!("Failed to make the rendering context current: {:?}", err);
}
let mut webview_renderers = take(&mut self.webview_renderers);
for webview_renderer in webview_renderers.iter_mut() {
webview_renderer.process_pending_scroll_events(self);
let mut need_zoom = false;
let scroll_offset_updates: Vec<_> = self
.webview_renderers
.iter_mut()
.filter_map(|webview_renderer| {
let (zoom, scroll_result) =
webview_renderer.process_pending_scroll_and_pinch_zoom_events();
need_zoom = need_zoom || (zoom == PinchZoomResult::DidPinchZoom);
scroll_result
})
.collect();
if need_zoom || !scroll_offset_updates.is_empty() {
let mut transaction = Transaction::new();
if need_zoom {
self.send_root_pipeline_display_list_in_transaction(&mut transaction);
}
for update in scroll_offset_updates {
let offset = LayoutVector2D::new(-update.offset.x, -update.offset.y);
transaction.set_scroll_offsets(
update.external_scroll_id,
vec![SampledScrollOffset {
offset,
generation: 0,
}],
);
}
self.generate_frame(&mut transaction, RenderReasons::APZ);
self.global.borrow_mut().send_transaction(transaction);
}
self.webview_renderers = webview_renderers;
self.global.borrow().shutdown_state() != ShutdownState::FinishedShuttingDown
}

View file

@ -42,6 +42,7 @@ mod from_constellation {
Self::LoadComplete(..) => target!("LoadComplete"),
Self::WebDriverMouseButtonEvent(..) => target!("WebDriverMouseButtonEvent"),
Self::WebDriverMouseMoveEvent(..) => target!("WebDriverMouseMoveEvent"),
Self::WebDriverWheelScrollEvent(..) => target!("WebDriverWheelScrollEvent"),
Self::SendInitialTransaction(..) => target!("SendInitialTransaction"),
Self::SendScrollNode(..) => target!("SendScrollNode"),
Self::SendDisplayList { .. } => target!("SendDisplayList"),

View file

@ -20,15 +20,11 @@ use fnv::FnvHashSet;
use log::{debug, warn};
use servo_geometry::DeviceIndependentPixel;
use style_traits::{CSSPixel, PinchZoomFactor};
use webrender::Transaction;
use webrender_api::units::{
DeviceIntPoint, DeviceIntRect, DevicePixel, DevicePoint, DeviceRect, LayoutVector2D,
};
use webrender_api::{
ExternalScrollId, HitTestFlags, RenderReasons, SampledScrollOffset, ScrollLocation,
};
use webrender_api::{ExternalScrollId, HitTestFlags, ScrollLocation};
use crate::IOCompositor;
use crate::compositor::{PipelineDetails, ServoRenderer};
use crate::touch::{TouchHandler, TouchMoveAction, TouchMoveAllowed, TouchSequenceState};
@ -55,6 +51,19 @@ enum ScrollZoomEvent {
Scroll(ScrollEvent),
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) struct ScrollResult {
pub pipeline_id: PipelineId,
pub external_scroll_id: ExternalScrollId,
pub offset: LayoutVector2D,
}
#[derive(PartialEq)]
pub(crate) enum PinchZoomResult {
DidPinchZoom,
DidNotPinchZoom,
}
/// A renderer for a libservo `WebView`. This is essentially the [`ServoRenderer`]'s interface to a
/// libservo `WebView`, but the code here cannot depend on libservo in order to prevent circular
/// dependencies, which is why we store a `dyn WebViewTrait` here instead of the `WebView` itself.
@ -86,6 +95,9 @@ pub(crate) struct WebViewRenderer {
/// The HiDPI scale factor for the `WebView` associated with this renderer. This is controlled
/// by the embedding layer.
hidpi_scale_factor: Scale<f32, DeviceIndependentPixel, DevicePixel>,
/// Whether or not this [`WebViewRenderer`] isn't throttled and has a pipeline with
/// active animations or animation frame callbacks.
animating: bool,
}
impl Drop for WebViewRenderer {
@ -119,6 +131,7 @@ impl WebViewRenderer {
min_viewport_zoom: Some(PinchZoomFactor::new(1.0)),
max_viewport_zoom: None,
hidpi_scale_factor: Scale::new(hidpi_scale_factor.0),
animating: false,
}
}
@ -138,6 +151,10 @@ impl WebViewRenderer {
self.pipelines.keys()
}
pub(crate) fn animating(&self) -> bool {
self.animating
}
/// Returns the [`PipelineDetails`] for the given [`PipelineId`], creating it if needed.
pub(crate) fn ensure_pipeline_details(
&mut self,
@ -148,14 +165,10 @@ impl WebViewRenderer {
.borrow_mut()
.pipeline_to_webview_map
.insert(pipeline_id, self.id);
PipelineDetails::new(pipeline_id)
PipelineDetails::new()
})
}
pub(crate) fn set_throttled(&mut self, pipeline_id: PipelineId, throttled: bool) {
self.ensure_pipeline_details(pipeline_id).throttled = throttled;
}
pub(crate) fn remove_pipeline(&mut self, pipeline_id: PipelineId) {
self.global
.borrow_mut()
@ -245,51 +258,45 @@ impl WebViewRenderer {
})
}
/// Sets or unsets the animations-running flag for the given pipeline. Returns true if
/// the pipeline is throttled.
pub(crate) fn change_running_animations_state(
/// Sets or unsets the animations-running flag for the given pipeline. Returns
/// true if the [`WebViewRenderer`]'s overall animating state changed.
pub(crate) fn change_pipeline_running_animations_state(
&mut self,
pipeline_id: PipelineId,
animation_state: AnimationState,
) -> bool {
let throttled = {
let pipeline_details = self.ensure_pipeline_details(pipeline_id);
match animation_state {
AnimationState::AnimationsPresent => {
pipeline_details.animations_running = true;
},
AnimationState::AnimationCallbacksPresent => {
pipeline_details.animation_callbacks_running = true;
},
AnimationState::NoAnimationsPresent => {
pipeline_details.animations_running = false;
},
AnimationState::NoAnimationCallbacksPresent => {
pipeline_details.animation_callbacks_running = false;
},
}
pipeline_details.throttled
};
let pipeline_details = self.ensure_pipeline_details(pipeline_id);
match animation_state {
AnimationState::AnimationsPresent => {
pipeline_details.animations_running = true;
},
AnimationState::AnimationCallbacksPresent => {
pipeline_details.animation_callbacks_running = true;
},
AnimationState::NoAnimationsPresent => {
pipeline_details.animations_running = false;
},
AnimationState::NoAnimationCallbacksPresent => {
pipeline_details.animation_callbacks_running = false;
},
}
self.update_animation_state()
}
/// Sets or unsets the throttled flag for the given pipeline. Returns
/// true if the [`WebViewRenderer`]'s overall animating state changed.
pub(crate) fn set_throttled(&mut self, pipeline_id: PipelineId, throttled: bool) -> bool {
self.ensure_pipeline_details(pipeline_id).throttled = throttled;
// Throttling a pipeline can cause it to be taken into the "not-animating" state.
self.update_animation_state()
}
pub(crate) fn update_animation_state(&mut self) -> bool {
let animating = self.pipelines.values().any(PipelineDetails::animating);
self.webview.set_animating(animating);
throttled
}
pub(crate) fn tick_all_animations(&self, compositor: &IOCompositor) {
for pipeline_details in self.pipelines.values() {
pipeline_details.tick_animations(compositor)
}
}
pub(crate) fn tick_animations_for_pipeline(
&self,
pipeline_id: PipelineId,
compositor: &IOCompositor,
) {
if let Some(pipeline_details) = self.pipelines.get(&pipeline_id) {
pipeline_details.tick_animations(compositor);
}
let old_animating = std::mem::replace(&mut self.animating, animating);
self.webview.set_animating(self.animating);
old_animating != self.animating
}
/// On a Window refresh tick (e.g. vsync)
@ -680,22 +687,17 @@ impl WebViewRenderer {
/// <http://w3c.github.io/touch-events/#mouse-events>
fn simulate_mouse_click(&mut self, point: DevicePoint) {
let button = MouseButton::Left;
self.dispatch_input_event(InputEvent::MouseMove(MouseMoveEvent { point }));
self.dispatch_input_event(InputEvent::MouseButton(MouseButtonEvent {
self.dispatch_input_event(InputEvent::MouseMove(MouseMoveEvent::new(point)));
self.dispatch_input_event(InputEvent::MouseButton(MouseButtonEvent::new(
MouseButtonAction::Down,
button,
action: MouseButtonAction::Down,
point,
}));
self.dispatch_input_event(InputEvent::MouseButton(MouseButtonEvent {
)));
self.dispatch_input_event(InputEvent::MouseButton(MouseButtonEvent::new(
MouseButtonAction::Up,
button,
action: MouseButtonAction::Up,
point,
}));
self.dispatch_input_event(InputEvent::MouseButton(MouseButtonEvent {
button,
action: MouseButtonAction::Click,
point,
}));
)));
}
pub(crate) fn notify_scroll_event(
@ -728,11 +730,35 @@ impl WebViewRenderer {
}));
}
pub(crate) fn process_pending_scroll_events(&mut self, compositor: &mut IOCompositor) {
if self.pending_scroll_zoom_events.is_empty() {
/// Push scroll pending event when receiving wheel action from webdriver
pub(crate) fn on_webdriver_wheel_action(
&mut self,
scroll_delta: Vector2D<f32, DevicePixel>,
point: Point2D<f32, DevicePixel>,
) {
if self.global.borrow().shutdown_state() != ShutdownState::NotShuttingDown {
return;
}
let scroll_location =
ScrollLocation::Delta(LayoutVector2D::from_untyped(scroll_delta.to_untyped()));
let cursor = DeviceIntPoint::new(point.x as i32, point.y as i32);
self.on_scroll_window_event(scroll_location, cursor)
}
/// Process pending scroll events for this [`WebViewRenderer`]. Returns a tuple containing:
///
/// - A boolean that is true if a zoom occurred.
/// - An optional [`ScrollResult`] if a scroll occurred.
///
/// It is up to the caller to ensure that these events update the rendering appropriately.
pub(crate) fn process_pending_scroll_and_pinch_zoom_events(
&mut self,
) -> (PinchZoomResult, Option<ScrollResult>) {
if self.pending_scroll_zoom_events.is_empty() {
return (PinchZoomResult::DidNotPinchZoom, None);
}
// Batch up all scroll events into one, or else we'll do way too much painting.
let mut combined_scroll_event: Option<ScrollEvent> = None;
let mut combined_magnification = 1.0;
@ -781,37 +807,24 @@ impl WebViewRenderer {
}
}
let zoom_changed =
self.set_pinch_zoom_level(self.pinch_zoom_level().get() * combined_magnification);
let scroll_result = combined_scroll_event.and_then(|combined_event| {
self.scroll_node_at_device_point(
combined_event.cursor.to_f32(),
combined_event.scroll_location,
)
});
if !zoom_changed && scroll_result.is_none() {
return;
if let Some(scroll_result) = scroll_result {
self.send_scroll_positions_to_layout_for_pipeline(scroll_result.pipeline_id);
}
let mut transaction = Transaction::new();
if zoom_changed {
compositor.send_root_pipeline_display_list_in_transaction(&mut transaction);
}
let pinch_zoom_result = match self
.set_pinch_zoom_level(self.pinch_zoom_level().get() * combined_magnification)
{
true => PinchZoomResult::DidPinchZoom,
false => PinchZoomResult::DidNotPinchZoom,
};
if let Some((pipeline_id, external_id, offset)) = scroll_result {
let offset = LayoutVector2D::new(-offset.x, -offset.y);
transaction.set_scroll_offsets(
external_id,
vec![SampledScrollOffset {
offset,
generation: 0,
}],
);
self.send_scroll_positions_to_layout_for_pipeline(pipeline_id);
}
compositor.generate_frame(&mut transaction, RenderReasons::APZ);
self.global.borrow_mut().send_transaction(transaction);
(pinch_zoom_result, scroll_result)
}
/// Perform a hit test at the given [`DevicePoint`] and apply the [`ScrollLocation`]
@ -822,7 +835,7 @@ impl WebViewRenderer {
&mut self,
cursor: DevicePoint,
scroll_location: ScrollLocation,
) -> Option<(PipelineId, ExternalScrollId, LayoutVector2D)> {
) -> Option<ScrollResult> {
let scroll_location = match scroll_location {
ScrollLocation::Delta(delta) => {
let device_pixels_per_page = self.device_pixels_per_page_pixel();
@ -862,8 +875,12 @@ impl WebViewRenderer {
let scroll_result = pipeline_details
.scroll_tree
.scroll_node_or_ancestor(scroll_tree_node, scroll_location);
if let Some((external_id, offset)) = scroll_result {
return Some((*pipeline_id, external_id, offset));
if let Some((external_scroll_id, offset)) = scroll_result {
return Some(ScrollResult {
pipeline_id: *pipeline_id,
external_scroll_id,
offset,
});
}
}
}

View file

@ -104,7 +104,7 @@ pub struct DebugOptions {
/// Dumps the rule tree.
pub dump_rule_tree: bool,
/// Print the flow tree (Layout 2013) or fragment tree (Layout 2020) after each layout.
/// Print the fragment tree after each layout.
pub dump_flow_tree: bool,
/// Print the stacking context tree after each layout.

View file

@ -99,7 +99,6 @@ pub struct Preferences {
pub dom_serviceworker_timeout_seconds: i64,
pub dom_servo_helpers_enabled: bool,
pub dom_servoparser_async_html_tokenizer_enabled: bool,
pub dom_shadowdom_enabled: bool,
pub dom_svg_enabled: bool,
pub dom_testable_crash_enabled: bool,
pub dom_testbinding_enabled: bool,
@ -236,6 +235,8 @@ pub struct Preferences {
/// The user-agent to use for Servo. This can also be set via [`UserAgentPlatform`] in
/// order to set the value to the default value for the given platform.
pub user_agent: String,
pub log_filter: String,
}
impl Preferences {
@ -275,7 +276,6 @@ impl Preferences {
dom_serviceworker_timeout_seconds: 60,
dom_servo_helpers_enabled: false,
dom_servoparser_async_html_tokenizer_enabled: false,
dom_shadowdom_enabled: true,
dom_svg_enabled: false,
dom_testable_crash_enabled: false,
dom_testbinding_enabled: false,
@ -398,6 +398,7 @@ impl Preferences {
threadpools_webrender_workers_max: 4,
webgl_testing_context_creation_error: false,
user_agent: String::new(),
log_filter: String::new(),
}
}
}

View file

@ -112,11 +112,11 @@ use compositing_traits::{
CompositorMsg, CompositorProxy, SendableFrameTree, WebrenderExternalImageRegistry,
};
use constellation_traits::{
AnimationTickType, AuxiliaryWebViewCreationRequest, AuxiliaryWebViewCreationResponse,
BroadcastMsg, DocumentState, EmbedderToConstellationMessage, IFrameLoadInfo,
IFrameLoadInfoWithData, IFrameSandboxState, IFrameSizeMsg, Job, LoadData, LoadOrigin, LogEntry,
MessagePortMsg, NavigationHistoryBehavior, PaintMetricEvent, PortMessageTask, SWManagerMsg,
SWManagerSenders, ScriptToConstellationChan, ScriptToConstellationMessage, ScrollState,
AuxiliaryWebViewCreationRequest, AuxiliaryWebViewCreationResponse, BroadcastMsg, DocumentState,
EmbedderToConstellationMessage, IFrameLoadInfo, IFrameLoadInfoWithData, IFrameSandboxState,
IFrameSizeMsg, Job, LoadData, LoadOrigin, LogEntry, MessagePortMsg, NavigationHistoryBehavior,
PaintMetricEvent, PortMessageTask, PortTransferInfo, SWManagerMsg, SWManagerSenders,
ScriptToConstellationChan, ScriptToConstellationMessage, ScrollState,
ServiceWorkerManagerFactory, ServiceWorkerMsg, StructuredSerializedData, TraversalDirection,
WindowSizeType,
};
@ -128,10 +128,11 @@ use devtools_traits::{
use embedder_traits::resources::{self, Resource};
use embedder_traits::user_content_manager::UserContentManager;
use embedder_traits::{
AnimationState, CompositorHitTestResult, Cursor, EmbedderMsg, EmbedderProxy, ImeEvent,
InputEvent, MediaSessionActionType, MediaSessionEvent, MediaSessionPlaybackState, MouseButton,
MouseButtonAction, MouseButtonEvent, Theme, ViewportDetails, WebDriverCommandMsg,
WebDriverLoadStatus,
AnimationState, CompositorHitTestResult, Cursor, EmbedderMsg, EmbedderProxy,
FocusSequenceNumber, ImeEvent, InputEvent, JSValue, JavaScriptEvaluationError,
JavaScriptEvaluationId, MediaSessionActionType, MediaSessionEvent, MediaSessionPlaybackState,
MouseButton, MouseButtonAction, MouseButtonEvent, Theme, ViewportDetails, WebDriverCommandMsg,
WebDriverCommandResponse, WebDriverLoadStatus,
};
use euclid::Size2D;
use euclid::default::Size2D as UntypedSize2D;
@ -203,9 +204,6 @@ enum TransferState {
/// While a completion failed, another global requested to complete the transfer.
/// We are still buffering messages, and awaiting the return of the buffer from the global who failed.
CompletionRequested(MessagePortRouterId, VecDeque<PortMessageTask>),
/// The entangled port has been removed while the port was in-transfer,
/// the current port should be removed as well once it is managed again.
EntangledRemoved,
}
#[derive(Debug)]
@ -374,6 +372,7 @@ pub struct Constellation<STF, SWF> {
mem_profiler_chan: mem::ProfilerChan,
/// A single WebRender document the constellation operates on.
#[cfg(feature = "webgpu")]
webrender_document: DocumentId,
/// Webrender related objects required by WebGPU threads
@ -533,6 +532,8 @@ pub struct InitialConstellationState {
struct WebDriverData {
load_channel: Option<(PipelineId, IpcSender<WebDriverLoadStatus>)>,
resize_channel: Option<IpcSender<Size2D<f32, CSSPixel>>>,
// Forward responses from the script thread to the webdriver server.
input_command_response_sender: Option<IpcSender<WebDriverCommandResponse>>,
}
impl WebDriverData {
@ -540,6 +541,7 @@ impl WebDriverData {
WebDriverData {
load_channel: None,
resize_channel: None,
input_command_response_sender: None,
}
}
}
@ -718,6 +720,7 @@ where
phantom: PhantomData,
webdriver: WebDriverData::new(),
document_states: HashMap::new(),
#[cfg(feature = "webgpu")]
webrender_document: state.webrender_document,
#[cfg(feature = "webgpu")]
webrender_wgpu,
@ -1044,6 +1047,44 @@ where
}
}
/// Enumerate the specified browsing context's ancestor pipelines up to
/// the top-level pipeline.
fn ancestor_pipelines_of_browsing_context_iter(
&self,
browsing_context_id: BrowsingContextId,
) -> impl Iterator<Item = &Pipeline> + '_ {
let mut state: Option<PipelineId> = self
.browsing_contexts
.get(&browsing_context_id)
.and_then(|browsing_context| browsing_context.parent_pipeline_id);
std::iter::from_fn(move || {
if let Some(pipeline_id) = state {
let pipeline = self.pipelines.get(&pipeline_id)?;
let browsing_context = self.browsing_contexts.get(&pipeline.browsing_context_id)?;
state = browsing_context.parent_pipeline_id;
Some(pipeline)
} else {
None
}
})
}
/// Enumerate the specified browsing context's ancestor-or-self pipelines up
/// to the top-level pipeline.
fn ancestor_or_self_pipelines_of_browsing_context_iter(
&self,
browsing_context_id: BrowsingContextId,
) -> impl Iterator<Item = &Pipeline> + '_ {
let this_pipeline = self
.browsing_contexts
.get(&browsing_context_id)
.map(|browsing_context| browsing_context.pipeline_id)
.and_then(|pipeline_id| self.pipelines.get(&pipeline_id));
this_pipeline
.into_iter()
.chain(self.ancestor_pipelines_of_browsing_context_iter(browsing_context_id))
}
/// Create a new browsing context and update the internal bookkeeping.
#[allow(clippy::too_many_arguments)]
fn new_browsing_context(
@ -1398,8 +1439,8 @@ where
EmbedderToConstellationMessage::ThemeChange(theme) => {
self.handle_theme_change(theme);
},
EmbedderToConstellationMessage::TickAnimation(pipeline_id, tick_type) => {
self.handle_tick_animation(pipeline_id, tick_type)
EmbedderToConstellationMessage::TickAnimation(webview_ids) => {
self.handle_tick_animation(webview_ids)
},
EmbedderToConstellationMessage::WebDriverCommand(command) => {
self.handle_webdriver_msg(command);
@ -1440,6 +1481,52 @@ where
EmbedderToConstellationMessage::PaintMetric(pipeline_id, paint_metric_event) => {
self.handle_paint_metric(pipeline_id, paint_metric_event);
},
EmbedderToConstellationMessage::EvaluateJavaScript(
webview_id,
evaluation_id,
script,
) => {
self.handle_evaluate_javascript(webview_id, evaluation_id, script);
},
}
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace")
)]
fn handle_evaluate_javascript(
&mut self,
webview_id: WebViewId,
evaluation_id: JavaScriptEvaluationId,
script: String,
) {
let browsing_context_id = BrowsingContextId::from(webview_id);
let Some(pipeline) = self
.browsing_contexts
.get(&browsing_context_id)
.and_then(|browsing_context| self.pipelines.get(&browsing_context.pipeline_id))
else {
self.handle_finish_javascript_evaluation(
evaluation_id,
Err(JavaScriptEvaluationError::InternalError),
);
return;
};
if pipeline
.event_loop
.send(ScriptThreadMessage::EvaluateJavaScript(
pipeline.id,
evaluation_id,
script,
))
.is_err()
{
self.handle_finish_javascript_evaluation(
evaluation_id,
Err(JavaScriptEvaluationError::InternalError),
);
}
}
@ -1487,12 +1574,12 @@ where
ScriptToConstellationMessage::NewMessagePort(router_id, port_id) => {
self.handle_new_messageport(router_id, port_id);
},
ScriptToConstellationMessage::RemoveMessagePort(port_id) => {
self.handle_remove_messageport(port_id);
},
ScriptToConstellationMessage::EntanglePorts(port1, port2) => {
self.handle_entangle_messageports(port1, port2);
},
ScriptToConstellationMessage::DisentanglePorts(port1, port2) => {
self.handle_disentangle_messageports(port1, port2);
},
ScriptToConstellationMessage::NewBroadcastChannelRouter(
router_id,
response_sender,
@ -1622,8 +1709,15 @@ where
data,
);
},
ScriptToConstellationMessage::Focus => {
self.handle_focus_msg(source_pipeline_id);
ScriptToConstellationMessage::Focus(focused_child_browsing_context_id, sequence) => {
self.handle_focus_msg(
source_pipeline_id,
focused_child_browsing_context_id,
sequence,
);
},
ScriptToConstellationMessage::FocusRemoteDocument(focused_browsing_context_id) => {
self.handle_focus_remote_document_msg(focused_browsing_context_id);
},
ScriptToConstellationMessage::SetThrottledComplete(throttled) => {
self.handle_set_throttled_complete(source_pipeline_id, throttled);
@ -1773,6 +1867,21 @@ where
self.mem_profiler_chan
.send(mem::ProfilerMsg::Report(sender));
},
ScriptToConstellationMessage::FinishJavaScriptEvaluation(evaluation_id, result) => {
self.handle_finish_javascript_evaluation(evaluation_id, result)
},
ScriptToConstellationMessage::WebDriverInputComplete(msg_id) => {
if let Some(ref reply_sender) = self.webdriver.input_command_response_sender {
reply_sender
.send(WebDriverCommandResponse { id: msg_id })
.unwrap_or_else(|_| {
warn!("Failed to send WebDriverInputComplete {:?}", msg_id);
self.webdriver.input_command_response_sender = None;
});
} else {
warn!("No WebDriver input_command_response_sender");
}
},
}
}
@ -2073,17 +2182,6 @@ where
Entry::Occupied(entry) => entry,
};
match entry.get().state {
TransferState::EntangledRemoved => {
// If the entangled port has been removed while this one was in-transfer,
// remove it now.
if let Some(ipc_sender) = self.message_port_routers.get(&router_id) {
let _ = ipc_sender.send(MessagePortMsg::RemoveMessagePort(port_id));
} else {
warn!("No message-port sender for {:?}", router_id);
}
entry.remove_entry();
continue;
},
TransferState::CompletionInProgress(expected_router_id) => {
// Here, the transfer was normally completed.
@ -2107,9 +2205,9 @@ where
fn handle_message_port_transfer_failed(
&mut self,
ports: HashMap<MessagePortId, VecDeque<PortMessageTask>>,
ports: HashMap<MessagePortId, PortTransferInfo>,
) {
for (port_id, mut previous_buffer) in ports.into_iter() {
for (port_id, mut transfer_info) in ports.into_iter() {
let entry = match self.message_ports.remove(&port_id) {
None => {
warn!(
@ -2121,11 +2219,6 @@ where
Some(entry) => entry,
};
let new_info = match entry.state {
TransferState::EntangledRemoved => {
// If the entangled port has been removed while this one was in-transfer,
// just drop it.
continue;
},
TransferState::CompletionFailed(mut current_buffer) => {
// The transfer failed,
// and now the global has returned us the buffer we previously sent.
@ -2133,7 +2226,7 @@ where
// Tasks in the previous buffer are older,
// hence need to be added to the front of the current one.
while let Some(task) = previous_buffer.pop_back() {
while let Some(task) = transfer_info.port_message_queue.pop_back() {
current_buffer.push_front(task);
}
// Update the state to transfer-in-progress.
@ -2152,7 +2245,7 @@ where
// Tasks in the previous buffer are older,
// hence need to be added to the front of the current one.
while let Some(task) = previous_buffer.pop_back() {
while let Some(task) = transfer_info.port_message_queue.pop_back() {
current_buffer.push_front(task);
}
// Forward the buffered message-queue to complete the current transfer.
@ -2160,7 +2253,10 @@ where
if ipc_sender
.send(MessagePortMsg::CompletePendingTransfer(
port_id,
current_buffer,
PortTransferInfo {
port_message_queue: current_buffer,
disentangled: entry.entangled_with.is_none(),
},
))
.is_err()
{
@ -2207,18 +2303,14 @@ where
Some(entry) => entry,
};
let new_info = match entry.state {
TransferState::EntangledRemoved => {
// If the entangled port has been removed while this one was in-transfer,
// remove it now.
if let Some(ipc_sender) = self.message_port_routers.get(&router_id) {
let _ = ipc_sender.send(MessagePortMsg::RemoveMessagePort(port_id));
} else {
warn!("No message-port sender for {:?}", router_id);
}
continue;
},
TransferState::TransferInProgress(buffer) => {
response.insert(port_id, buffer);
response.insert(
port_id,
PortTransferInfo {
port_message_queue: buffer,
disentangled: entry.entangled_with.is_none(),
},
);
// If the port was in transfer, and a global is requesting completion,
// we note the start of the completion.
@ -2297,10 +2389,6 @@ where
TransferState::TransferInProgress(queue) => queue.push_back(task),
TransferState::CompletionFailed(queue) => queue.push_back(task),
TransferState::CompletionRequested(_, queue) => queue.push_back(task),
TransferState::EntangledRemoved => warn!(
"Messageport received a message, but entangled has alread been removed {:?}",
port_id
),
}
}
@ -2362,59 +2450,6 @@ where
}
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace")
)]
fn handle_remove_messageport(&mut self, port_id: MessagePortId) {
let entangled = match self.message_ports.remove(&port_id) {
Some(info) => info.entangled_with,
None => {
return warn!(
"Constellation asked to remove unknown messageport {:?}",
port_id
);
},
};
let entangled_id = match entangled {
Some(id) => id,
None => return,
};
let info = match self.message_ports.get_mut(&entangled_id) {
Some(info) => info,
None => {
return warn!(
"Constellation asked to remove unknown entangled messageport {:?}",
entangled_id
);
},
};
let router_id = match info.state {
TransferState::EntangledRemoved => {
return warn!(
"Constellation asked to remove entangled messageport by a port that was already removed {:?}",
port_id
);
},
TransferState::TransferInProgress(_) |
TransferState::CompletionInProgress(_) |
TransferState::CompletionFailed(_) |
TransferState::CompletionRequested(_, _) => {
// Note: since the port is in-transer, we don't have a router to send it a message
// to let it know that its entangled port has been removed.
// Hence we mark it so that it will be messaged and removed once the transfer completes.
info.state = TransferState::EntangledRemoved;
return;
},
TransferState::Managed(router_id) => router_id,
};
if let Some(sender) = self.message_port_routers.get(&router_id) {
let _ = sender.send(MessagePortMsg::RemoveMessagePort(entangled_id));
} else {
warn!("No message-port sender for {:?}", router_id);
}
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace")
@ -2438,6 +2473,57 @@ where
}
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace")
)]
/// <https://html.spec.whatwg.org/multipage/#disentangle>
fn handle_disentangle_messageports(
&mut self,
port1: MessagePortId,
port2: Option<MessagePortId>,
) {
// Disentangle initiatorPort and otherPort,
// so that they are no longer entangled or associated with each other.
// Note: If `port2` is some, then this is the first message
// and `port1` is the initiatorPort, `port2` is the otherPort.
// We can immediately remove the initiator.
let _ = self.message_ports.remove(&port1);
// Note: the none case is when otherPort sent this message
// in response to completing its own local disentanglement.
let Some(port2) = port2 else {
return;
};
// Start disentanglement of the other port.
if let Some(info) = self.message_ports.get_mut(&port2) {
info.entangled_with = None;
match &mut info.state {
TransferState::Managed(router_id) |
TransferState::CompletionInProgress(router_id) => {
// We try to disentangle the other port now,
// and if it has been transfered out by the time the message is received,
// it will be ignored,
// and disentanglement will be completed as part of the transfer.
if let Some(ipc_sender) = self.message_port_routers.get(router_id) {
let _ = ipc_sender.send(MessagePortMsg::CompleteDisentanglement(port2));
} else {
warn!("No message-port sender for {:?}", router_id);
}
},
_ => {
// Note: the port is in transfer, disentanglement will complete along with it.
},
}
} else {
warn!(
"Constellation asked to disentangle unknown messageport: {:?}",
port2
);
}
}
/// <https://w3c.github.io/ServiceWorker/#schedule-job-algorithm>
/// and
/// <https://w3c.github.io/ServiceWorker/#dfn-job-queue>
@ -3157,6 +3243,22 @@ where
}
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace")
)]
fn handle_finish_javascript_evaluation(
&mut self,
evaluation_id: JavaScriptEvaluationId,
result: Result<JSValue, JavaScriptEvaluationError>,
) {
self.embedder_proxy
.send(EmbedderMsg::FinishJavaScriptEvaluation(
evaluation_id,
result,
));
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace")
@ -3528,15 +3630,24 @@ where
feature = "tracing",
tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace")
)]
fn handle_tick_animation(&mut self, pipeline_id: PipelineId, tick_type: AnimationTickType) {
let pipeline = match self.pipelines.get(&pipeline_id) {
Some(pipeline) => pipeline,
None => return warn!("{}: Got script tick after closure", pipeline_id),
};
fn handle_tick_animation(&mut self, webview_ids: Vec<WebViewId>) {
let mut animating_event_loops = HashSet::new();
let message = ScriptThreadMessage::TickAllAnimations(pipeline_id, tick_type);
if let Err(e) = pipeline.event_loop.send(message) {
self.handle_send_error(pipeline_id, e);
for webview_id in webview_ids.iter() {
for browsing_context in self.fully_active_browsing_contexts_iter(*webview_id) {
let Some(pipeline) = self.pipelines.get(&browsing_context.pipeline_id) else {
continue;
};
animating_event_loops.insert(pipeline.event_loop.clone());
}
}
for event_loop in animating_event_loops {
// No error handling here. It's unclear what to do when this fails as the error isn't associated
// with a particular pipeline. In addition, the danger of not progressing animations is pretty
// low, so it's probably safe to ignore this error and handle the crashed ScriptThread on
// some other message.
let _ = event_loop.send(ScriptThreadMessage::TickAllAnimations(webview_ids.clone()));
}
}
@ -3736,8 +3847,8 @@ where
fn handle_load_complete_msg(&mut self, webview_id: WebViewId, pipeline_id: PipelineId) {
let mut webdriver_reset = false;
if let Some((expected_pipeline_id, ref reply_chan)) = self.webdriver.load_channel {
debug!("Sending load for {:?} to WebDriver", expected_pipeline_id);
if expected_pipeline_id == pipeline_id {
debug!("Sending load for {:?} to WebDriver", expected_pipeline_id);
let _ = reply_chan.send(WebDriverLoadStatus::Complete);
webdriver_reset = true;
}
@ -4062,6 +4173,7 @@ where
}
new_pipeline.set_throttled(false);
self.notify_focus_state(new_pipeline_id);
}
self.update_activity(old_pipeline_id);
@ -4267,22 +4379,96 @@ where
feature = "tracing",
tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace")
)]
fn handle_focus_msg(&mut self, pipeline_id: PipelineId) {
let (browsing_context_id, webview_id) = match self.pipelines.get(&pipeline_id) {
Some(pipeline) => (pipeline.browsing_context_id, pipeline.webview_id),
fn handle_focus_msg(
&mut self,
pipeline_id: PipelineId,
focused_child_browsing_context_id: Option<BrowsingContextId>,
sequence: FocusSequenceNumber,
) {
let (browsing_context_id, webview_id) = match self.pipelines.get_mut(&pipeline_id) {
Some(pipeline) => {
pipeline.focus_sequence = sequence;
(pipeline.browsing_context_id, pipeline.webview_id)
},
None => return warn!("{}: Focus parent after closure", pipeline_id),
};
// Ignore if the pipeline isn't fully active.
if self.get_activity(pipeline_id) != DocumentActivity::FullyActive {
debug!(
"Ignoring the focus request because pipeline {} is not \
fully active",
pipeline_id
);
return;
}
// Focus the top-level browsing context.
self.webviews.focus(webview_id);
self.embedder_proxy
.send(EmbedderMsg::WebViewFocused(webview_id));
// If a container with a non-null nested browsing context is focused,
// the nested browsing context's active document becomes the focused
// area of the top-level browsing context instead.
let focused_browsing_context_id =
focused_child_browsing_context_id.unwrap_or(browsing_context_id);
// Send focus messages to the affected pipelines, except
// `pipeline_id`, which has already its local focus state
// updated.
self.focus_browsing_context(Some(pipeline_id), focused_browsing_context_id);
}
fn handle_focus_remote_document_msg(&mut self, focused_browsing_context_id: BrowsingContextId) {
let pipeline_id = match self.browsing_contexts.get(&focused_browsing_context_id) {
Some(browsing_context) => browsing_context.pipeline_id,
None => return warn!("Browsing context {} not found", focused_browsing_context_id),
};
// Ignore if its active document isn't fully active.
if self.get_activity(pipeline_id) != DocumentActivity::FullyActive {
debug!(
"Ignoring the remote focus request because pipeline {} of \
browsing context {} is not fully active",
pipeline_id, focused_browsing_context_id,
);
return;
}
self.focus_browsing_context(None, focused_browsing_context_id);
}
/// Perform [the focusing steps][1] for the active document of
/// `focused_browsing_context_id`.
///
/// If `initiator_pipeline_id` is specified, this method avoids sending
/// a message to `initiator_pipeline_id`, assuming its local focus state has
/// already been updated. This is necessary for performing the focusing
/// steps for an object that is not the document itself but something that
/// belongs to the document.
///
/// [1]: https://html.spec.whatwg.org/multipage/#focusing-steps
#[cfg_attr(
feature = "tracing",
tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace")
)]
fn focus_browsing_context(
&mut self,
initiator_pipeline_id: Option<PipelineId>,
focused_browsing_context_id: BrowsingContextId,
) {
let webview_id = match self.browsing_contexts.get(&focused_browsing_context_id) {
Some(browsing_context) => browsing_context.top_level_id,
None => return warn!("Browsing context {} not found", focused_browsing_context_id),
};
// Update the webviews focused browsing context.
match self.webviews.get_mut(webview_id) {
Some(webview) => {
webview.focused_browsing_context_id = browsing_context_id;
},
let old_focused_browsing_context_id = match self.webviews.get_mut(webview_id) {
Some(browser) => replace(
&mut browser.focused_browsing_context_id,
focused_browsing_context_id,
),
None => {
return warn!(
"{}: Browsing context for focus msg does not exist",
@ -4291,42 +4477,133 @@ where
},
};
// Focus parent iframes recursively
self.focus_parent_pipeline(browsing_context_id);
}
// The following part is similar to [the focus update steps][1] except
// that only `Document`s in the given focus chains are considered. It's
// ultimately up to the script threads to fire focus events at the
// affected objects.
//
// [1]: https://html.spec.whatwg.org/multipage/#focus-update-steps
let mut old_focus_chain_pipelines: Vec<&Pipeline> = self
.ancestor_or_self_pipelines_of_browsing_context_iter(old_focused_browsing_context_id)
.collect();
let mut new_focus_chain_pipelines: Vec<&Pipeline> = self
.ancestor_or_self_pipelines_of_browsing_context_iter(focused_browsing_context_id)
.collect();
#[cfg_attr(
feature = "tracing",
tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace")
)]
fn focus_parent_pipeline(&mut self, browsing_context_id: BrowsingContextId) {
let parent_pipeline_id = match self.browsing_contexts.get(&browsing_context_id) {
Some(ctx) => ctx.parent_pipeline_id,
None => {
return warn!("{}: Focus parent after closure", browsing_context_id);
},
};
let parent_pipeline_id = match parent_pipeline_id {
Some(parent_id) => parent_id,
None => {
return debug!("{}: Focus has no parent", browsing_context_id);
},
};
debug!(
"old_focus_chain_pipelines = {:?}",
old_focus_chain_pipelines
.iter()
.map(|p| p.id.to_string())
.collect::<Vec<_>>()
);
debug!(
"new_focus_chain_pipelines = {:?}",
new_focus_chain_pipelines
.iter()
.map(|p| p.id.to_string())
.collect::<Vec<_>>()
);
// Send a message to the parent of the provided browsing context (if it
// exists) telling it to mark the iframe element as focused.
let msg = ScriptThreadMessage::FocusIFrame(parent_pipeline_id, browsing_context_id);
let (result, parent_browsing_context_id) = match self.pipelines.get(&parent_pipeline_id) {
Some(pipeline) => {
let result = pipeline.event_loop.send(msg);
(result, pipeline.browsing_context_id)
// At least the last entries should match. Otherwise something is wrong,
// and we don't want to proceed and crash the top-level pipeline by
// sending an impossible `Unfocus` message to it.
match (
&old_focus_chain_pipelines[..],
&new_focus_chain_pipelines[..],
) {
([.., p1], [.., p2]) if p1.id == p2.id => {},
_ => {
warn!("Aborting the focus operation - focus chain sanity check failed");
return;
},
None => return warn!("{}: Focus after closure", parent_pipeline_id),
};
if let Err(e) = result {
self.handle_send_error(parent_pipeline_id, e);
}
self.focus_parent_pipeline(parent_browsing_context_id);
// > If the last entry in `old chain` and the last entry in `new chain`
// > are the same, pop the last entry from `old chain` and the last
// > entry from `new chain` and redo this step.
let mut first_common_pipeline_in_chain = None;
while let ([.., p1], [.., p2]) = (
&old_focus_chain_pipelines[..],
&new_focus_chain_pipelines[..],
) {
if p1.id != p2.id {
break;
}
old_focus_chain_pipelines.pop();
first_common_pipeline_in_chain = new_focus_chain_pipelines.pop();
}
let mut send_errors = Vec::new();
// > For each entry `entry` in `old chain`, in order, run these
// > substeps: [...]
for &pipeline in old_focus_chain_pipelines.iter() {
if Some(pipeline.id) != initiator_pipeline_id {
let msg = ScriptThreadMessage::Unfocus(pipeline.id, pipeline.focus_sequence);
trace!("Sending {:?} to {}", msg, pipeline.id);
if let Err(e) = pipeline.event_loop.send(msg) {
send_errors.push((pipeline.id, e));
}
} else {
trace!(
"Not notifying {} - it's the initiator of this focus operation",
pipeline.id
);
}
}
// > For each entry entry in `new chain`, in reverse order, run these
// > substeps: [...]
let mut child_browsing_context_id = None;
for &pipeline in new_focus_chain_pipelines.iter().rev() {
// Don't send a message to the browsing context that initiated this
// focus operation. It already knows that it has gotten focus.
if Some(pipeline.id) != initiator_pipeline_id {
let msg = if let Some(child_browsing_context_id) = child_browsing_context_id {
// Focus the container element of `child_browsing_context_id`.
ScriptThreadMessage::FocusIFrame(
pipeline.id,
child_browsing_context_id,
pipeline.focus_sequence,
)
} else {
// Focus the document.
ScriptThreadMessage::FocusDocument(pipeline.id, pipeline.focus_sequence)
};
trace!("Sending {:?} to {}", msg, pipeline.id);
if let Err(e) = pipeline.event_loop.send(msg) {
send_errors.push((pipeline.id, e));
}
} else {
trace!(
"Not notifying {} - it's the initiator of this focus operation",
pipeline.id
);
}
child_browsing_context_id = Some(pipeline.browsing_context_id);
}
if let (Some(pipeline), Some(child_browsing_context_id)) =
(first_common_pipeline_in_chain, child_browsing_context_id)
{
if Some(pipeline.id) != initiator_pipeline_id {
// Focus the container element of `child_browsing_context_id`.
let msg = ScriptThreadMessage::FocusIFrame(
pipeline.id,
child_browsing_context_id,
pipeline.focus_sequence,
);
trace!("Sending {:?} to {}", msg, pipeline.id);
if let Err(e) = pipeline.event_loop.send(msg) {
send_errors.push((pipeline.id, e));
}
}
}
for (pipeline_id, e) in send_errors {
self.handle_send_error(pipeline_id, e);
}
}
#[cfg_attr(
@ -4495,6 +4772,7 @@ where
NavigationHistoryBehavior::Replace,
);
},
// TODO: This should use the ScriptThreadMessage::EvaluateJavaScript command
WebDriverCommandMsg::ScriptCommand(browsing_context_id, cmd) => {
let pipeline_id = match self.browsing_contexts.get(&browsing_context_id) {
Some(browsing_context) => browsing_context.pipeline_id,
@ -4573,7 +4851,11 @@ where
mouse_button,
x,
y,
msg_id,
response_sender,
) => {
self.webdriver.input_command_response_sender = Some(response_sender);
self.compositor_proxy
.send(CompositorMsg::WebDriverMouseButtonEvent(
webview_id,
@ -4581,11 +4863,22 @@ where
mouse_button,
x,
y,
msg_id,
));
},
WebDriverCommandMsg::MouseMoveAction(webview_id, x, y) => {
WebDriverCommandMsg::MouseMoveAction(webview_id, x, y, msg_id, response_sender) => {
self.webdriver.input_command_response_sender = Some(response_sender);
self.compositor_proxy
.send(CompositorMsg::WebDriverMouseMoveEvent(webview_id, x, y));
.send(CompositorMsg::WebDriverMouseMoveEvent(
webview_id, x, y, msg_id,
));
},
WebDriverCommandMsg::WheelScrollAction(webview, x, y, delta_x, delta_y) => {
self.compositor_proxy
.send(CompositorMsg::WebDriverWheelScrollEvent(
webview, x, y, delta_x, delta_y,
));
},
WebDriverCommandMsg::TakeScreenshot(webview_id, rect, response_sender) => {
self.compositor_proxy.send(CompositorMsg::CreatePng(
@ -4921,10 +5214,42 @@ where
self.trim_history(top_level_id);
}
self.notify_focus_state(change.new_pipeline_id);
self.notify_history_changed(change.webview_id);
self.update_webview_in_compositor(change.webview_id);
}
/// Update the focus state of the specified pipeline that recently became
/// active (thus doesn't have a focused container element) and may have
/// out-dated information.
fn notify_focus_state(&mut self, pipeline_id: PipelineId) {
let pipeline = match self.pipelines.get(&pipeline_id) {
Some(pipeline) => pipeline,
None => return warn!("Pipeline {} is closed", pipeline_id),
};
let is_focused = match self.webviews.get(pipeline.webview_id) {
Some(webview) => webview.focused_browsing_context_id == pipeline.browsing_context_id,
None => {
return warn!(
"Pipeline {}'s top-level browsing context {} is closed",
pipeline_id, pipeline.webview_id
);
},
};
// If the browsing context is focused, focus the document
let msg = if is_focused {
ScriptThreadMessage::FocusDocument(pipeline_id, pipeline.focus_sequence)
} else {
ScriptThreadMessage::Unfocus(pipeline_id, pipeline.focus_sequence)
};
if let Err(e) = pipeline.event_loop.send(msg) {
self.handle_send_error(pipeline_id, e);
}
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace")
@ -5374,7 +5699,29 @@ where
None => {
warn!("{parent_pipeline_id}: Child closed after parent");
},
Some(parent_pipeline) => parent_pipeline.remove_child(browsing_context_id),
Some(parent_pipeline) => {
parent_pipeline.remove_child(browsing_context_id);
// If `browsing_context_id` has focus, focus the parent
// browsing context
if let Some(webview) = self.webviews.get_mut(browsing_context.top_level_id) {
if webview.focused_browsing_context_id == browsing_context_id {
trace!(
"About-to-be-closed browsing context {} is currently focused, so \
focusing its parent {}",
browsing_context_id, parent_pipeline.browsing_context_id
);
webview.focused_browsing_context_id =
parent_pipeline.browsing_context_id;
}
} else {
warn!(
"Browsing context {} contains a reference to \
a non-existent top-level browsing context {}",
browsing_context_id, browsing_context.top_level_id
);
}
},
};
}
debug!("{}: Closed", browsing_context_id);

View file

@ -6,17 +6,36 @@
//! view of a script thread. When an `EventLoop` is dropped, an `ExitScriptThread`
//! message is sent to the script thread, asking it to shut down.
use std::hash::Hash;
use std::marker::PhantomData;
use std::rc::Rc;
use std::sync::atomic::{AtomicUsize, Ordering};
use ipc_channel::Error;
use ipc_channel::ipc::IpcSender;
use script_traits::ScriptThreadMessage;
static CURRENT_EVENT_LOOP_ID: AtomicUsize = AtomicUsize::new(0);
/// <https://html.spec.whatwg.org/multipage/#event-loop>
pub struct EventLoop {
script_chan: IpcSender<ScriptThreadMessage>,
dont_send_or_sync: PhantomData<Rc<()>>,
id: usize,
}
impl PartialEq for EventLoop {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Eq for EventLoop {}
impl Hash for EventLoop {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}
impl Drop for EventLoop {
@ -28,9 +47,11 @@ impl Drop for EventLoop {
impl EventLoop {
/// Create a new event loop from the channel to its script thread.
pub fn new(script_chan: IpcSender<ScriptThreadMessage>) -> Rc<EventLoop> {
let id = CURRENT_EVENT_LOOP_ID.fetch_add(1, Ordering::Relaxed);
Rc::new(EventLoop {
script_chan,
dont_send_or_sync: PhantomData,
id,
})
}

View file

@ -25,7 +25,7 @@ use constellation_traits::{LoadData, SWManagerMsg, ScriptToConstellationChan};
use crossbeam_channel::{Sender, unbounded};
use devtools_traits::{DevtoolsControlMsg, ScriptToDevtoolsControlMsg};
use embedder_traits::user_content_manager::UserContentManager;
use embedder_traits::{AnimationState, ViewportDetails};
use embedder_traits::{AnimationState, FocusSequenceNumber, ViewportDetails};
use fonts::{SystemFontServiceProxy, SystemFontServiceProxySender};
use ipc_channel::Error;
use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
@ -102,6 +102,8 @@ pub struct Pipeline {
/// The last compositor [`Epoch`] that was laid out in this pipeline if "exit after load" is
/// enabled.
pub layout_epoch: Epoch,
pub focus_sequence: FocusSequenceNumber,
}
/// Initial setup data needed to construct a pipeline.
@ -370,6 +372,7 @@ impl Pipeline {
completely_loaded: false,
title: String::new(),
layout_epoch: Epoch(0),
focus_sequence: FocusSequenceNumber::default(),
};
pipeline.set_throttled(throttled);

View file

@ -159,6 +159,7 @@ pub fn spawn_multiprocess(content: UnprivilegedContent) -> Result<Process, Error
let mut child_process = process::Command::new(path_to_self);
setup_common(&mut child_process, token);
#[allow(clippy::zombie_processes)]
let child = child_process
.spawn()
.expect("Failed to start unsandboxed child process!");

View file

@ -77,6 +77,7 @@ mod from_compositor {
Self::SetWebViewThrottled(_, _) => target!("SetWebViewThrottled"),
Self::SetScrollStates(..) => target!("SetScrollStates"),
Self::PaintMetric(..) => target!("PaintMetric"),
Self::EvaluateJavaScript(..) => target!("EvaluateJavaScript"),
}
}
}
@ -123,8 +124,8 @@ mod from_script {
Self::RemoveMessagePortRouter(..) => target!("RemoveMessagePortRouter"),
Self::RerouteMessagePort(..) => target!("RerouteMessagePort"),
Self::MessagePortShipped(..) => target!("MessagePortShipped"),
Self::RemoveMessagePort(..) => target!("RemoveMessagePort"),
Self::EntanglePorts(..) => target!("EntanglePorts"),
Self::DisentanglePorts(..) => target!("DisentanglePorts"),
Self::NewBroadcastChannelRouter(..) => target!("NewBroadcastChannelRouter"),
Self::RemoveBroadcastChannelRouter(..) => target!("RemoveBroadcastChannelRouter"),
Self::NewBroadcastChannelNameInRouter(..) => {
@ -138,7 +139,8 @@ mod from_script {
Self::BroadcastStorageEvent(..) => target!("BroadcastStorageEvent"),
Self::ChangeRunningAnimationsState(..) => target!("ChangeRunningAnimationsState"),
Self::CreateCanvasPaintThread(..) => target!("CreateCanvasPaintThread"),
Self::Focus => target!("Focus"),
Self::Focus(..) => target!("Focus"),
Self::FocusRemoteDocument(..) => target!("FocusRemoteDocument"),
Self::GetTopForBrowsingContext(..) => target!("GetTopForBrowsingContext"),
Self::GetBrowsingContextInfo(..) => target!("GetBrowsingContextInfo"),
Self::GetChildBrowsingContextId(..) => target!("GetChildBrowsingContextId"),
@ -175,6 +177,8 @@ mod from_script {
Self::TitleChanged(..) => target!("TitleChanged"),
Self::IFrameSizes(..) => target!("IFrameSizes"),
Self::ReportMemory(..) => target!("ReportMemory"),
Self::WebDriverInputComplete(..) => target!("WebDriverInputComplete"),
Self::FinishJavaScriptEvaluation(..) => target!("FinishJavaScriptEvaluation"),
}
}
}
@ -236,7 +240,10 @@ mod from_script {
Self::StopGamepadHapticEffect(..) => target_variant!("StopGamepadHapticEffect"),
Self::ShutdownComplete => target_variant!("ShutdownComplete"),
Self::ShowNotification(..) => target_variant!("ShowNotification"),
Self::ShowSelectElementMenu(..) => target_variant!("ShowSelectElementMenu"),
Self::ShowFormControl(..) => target_variant!("ShowFormControl"),
Self::FinishJavaScriptEvaluation(..) => {
target_variant!("FinishJavaScriptEvaluation")
},
}
}
}

View file

@ -31,6 +31,7 @@ use crate::actors::thread::ThreadActor;
use crate::actors::watcher::{SessionContext, SessionContextType, WatcherActor};
use crate::id::{DevtoolsBrowserId, DevtoolsBrowsingContextId, DevtoolsOuterWindowId, IdMap};
use crate::protocol::JsonPacketStream;
use crate::resource::ResourceAvailable;
use crate::{EmptyReplyMsg, StreamId};
#[derive(Serialize)]
@ -56,14 +57,6 @@ struct FrameUpdateMsg {
title: String,
}
#[derive(Serialize)]
struct ResourceAvailableReply<T: Serialize> {
from: String,
#[serde(rename = "type")]
type_: String,
array: Vec<(String, Vec<T>)>,
}
#[derive(Serialize)]
struct TabNavigated {
from: String,
@ -152,6 +145,12 @@ pub(crate) struct BrowsingContextActor {
pub watcher: String,
}
impl ResourceAvailable for BrowsingContextActor {
fn actor_name(&self) -> String {
self.name.clone()
}
}
impl Actor for BrowsingContextActor {
fn name(&self) -> String {
self.name.clone()
@ -358,26 +357,6 @@ impl BrowsingContextActor {
});
}
pub(crate) fn resource_available<T: Serialize>(&self, resource: T, resource_type: String) {
self.resources_available(vec![resource], resource_type);
}
pub(crate) fn resources_available<T: Serialize>(
&self,
resources: Vec<T>,
resource_type: String,
) {
let msg = ResourceAvailableReply::<T> {
from: self.name(),
type_: "resources-available-array".into(),
array: vec![(resource_type, resources)],
};
for stream in self.streams.borrow_mut().values_mut() {
let _ = stream.write_json_packet(&msg);
}
}
pub fn simulate_color_scheme(&self, theme: Theme) -> Result<(), ()> {
self.script_chan
.send(SimulateColorScheme(self.active_pipeline_id.get(), theme))

View file

@ -30,6 +30,7 @@ use crate::actors::browsing_context::BrowsingContextActor;
use crate::actors::object::ObjectActor;
use crate::actors::worker::WorkerActor;
use crate::protocol::JsonPacketStream;
use crate::resource::ResourceAvailable;
use crate::{StreamId, UniqueId};
trait EncodableConsoleMessage {
@ -251,6 +252,7 @@ impl ConsoleActor {
page_error: PageError,
id: UniqueId,
registry: &ActorRegistry,
stream: &mut TcpStream,
) {
self.cached_events
.borrow_mut()
@ -261,7 +263,11 @@ impl ConsoleActor {
if let Root::BrowsingContext(bc) = &self.root {
registry
.find::<BrowsingContextActor>(bc)
.resource_available(PageErrorWrapper { page_error }, "error-message".into())
.resource_available(
PageErrorWrapper { page_error },
"error-message".into(),
stream,
)
};
}
}
@ -271,6 +277,7 @@ impl ConsoleActor {
console_message: ConsoleMessage,
id: UniqueId,
registry: &ActorRegistry,
stream: &mut TcpStream,
) {
let log_message: ConsoleLog = console_message.into();
self.cached_events
@ -282,7 +289,7 @@ impl ConsoleActor {
if let Root::BrowsingContext(bc) = &self.root {
registry
.find::<BrowsingContextActor>(bc)
.resource_available(log_message, "console-message".into())
.resource_available(log_message, "console-message".into(), stream)
};
}
}

View file

@ -166,6 +166,8 @@ impl Actor for InspectorActor {
if self.highlighter.borrow().is_none() {
let highlighter_actor = HighlighterActor {
name: registry.new_name("highlighter"),
pipeline,
script_sender: self.script_chan.clone(),
};
let mut highlighter = self.highlighter.borrow_mut();
*highlighter = Some(highlighter_actor.name());

View file

@ -7,6 +7,9 @@
use std::net::TcpStream;
use base::id::PipelineId;
use devtools_traits::DevtoolScriptControlMsg;
use ipc_channel::ipc::IpcSender;
use serde::Serialize;
use serde_json::{self, Map, Value};
@ -21,6 +24,8 @@ pub struct HighlighterMsg {
pub struct HighlighterActor {
pub name: String,
pub script_sender: IpcSender<DevtoolScriptControlMsg>,
pub pipeline: PipelineId,
}
#[derive(Serialize)]
@ -41,14 +46,39 @@ impl Actor for HighlighterActor {
/// - `hide`: Disables highlighting for the selected node
fn handle_message(
&self,
_registry: &ActorRegistry,
registry: &ActorRegistry,
msg_type: &str,
_msg: &Map<String, Value>,
msg: &Map<String, Value>,
stream: &mut TcpStream,
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
Ok(match msg_type {
"show" => {
let Some(node_actor) = msg.get("node") else {
// TODO: send missing parameter error
return Ok(ActorMessageStatus::Ignored);
};
let Some(node_actor_name) = node_actor.as_str() else {
// TODO: send invalid parameter error
return Ok(ActorMessageStatus::Ignored);
};
if node_actor_name.starts_with("inspector") {
// TODO: For some reason, the client initially asks us to highlight
// the inspector? Investigate what this is supposed to mean.
let msg = ShowReply {
from: self.name(),
value: false,
};
let _ = stream.write_json_packet(&msg);
return Ok(ActorMessageStatus::Processed);
}
self.instruct_script_thread_to_highlight_node(
Some(node_actor_name.to_owned()),
registry,
);
let msg = ShowReply {
from: self.name(),
value: true,
@ -58,6 +88,8 @@ impl Actor for HighlighterActor {
},
"hide" => {
self.instruct_script_thread_to_highlight_node(None, registry);
let msg = EmptyReplyMsg { from: self.name() };
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
@ -67,3 +99,19 @@ impl Actor for HighlighterActor {
})
}
}
impl HighlighterActor {
fn instruct_script_thread_to_highlight_node(
&self,
node_actor: Option<String>,
registry: &ActorRegistry,
) {
let node_id = node_actor.map(|node_actor| registry.actor_to_script(node_actor));
self.script_sender
.send(DevtoolScriptControlMsg::HighlightDomNode(
self.pipeline,
node_id,
))
.unwrap();
}
}

View file

@ -78,6 +78,18 @@ pub struct NodeActorMsg {
shadow_root_mode: Option<String>,
traits: HashMap<String, ()>,
attrs: Vec<AttrMsg>,
/// The `DOCTYPE` name if this is a `DocumentType` node, `None` otherwise
#[serde(skip_serializing_if = "Option::is_none")]
name: Option<String>,
/// The `DOCTYPE` public identifier if this is a `DocumentType` node, `None` otherwise
#[serde(skip_serializing_if = "Option::is_none")]
public_id: Option<String>,
/// The `DOCTYPE` system identifier if this is a `DocumentType` node, `None` otherwise
#[serde(skip_serializing_if = "Option::is_none")]
system_id: Option<String>,
}
pub struct NodeActor {
@ -276,6 +288,9 @@ impl NodeInfoToProtocol for NodeInfo {
value: attr.value,
})
.collect(),
name: self.doctype_name,
public_id: self.doctype_public_identifier,
system_id: self.doctype_system_identifier,
}
}
}

View file

@ -0,0 +1,50 @@
/* 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::cell::{Ref, RefCell};
use std::collections::BTreeSet;
use serde::Serialize;
use servo_url::ServoUrl;
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct SourceData {
pub actor: String,
/// URL of the script, or URL of the page for inline scripts.
pub url: String,
pub is_black_boxed: bool,
}
#[derive(Serialize)]
pub(crate) struct SourcesReply {
pub from: String,
pub sources: Vec<SourceData>,
}
pub(crate) struct Source {
actor_name: String,
source_urls: RefCell<BTreeSet<SourceData>>,
}
impl Source {
pub fn new(actor_name: String) -> Self {
Self {
actor_name,
source_urls: RefCell::new(BTreeSet::default()),
}
}
pub fn add_source(&self, url: ServoUrl) {
self.source_urls.borrow_mut().insert(SourceData {
actor: self.actor_name.clone(),
url: url.to_string(),
is_black_boxed: false,
});
}
pub fn sources(&self) -> Ref<BTreeSet<SourceData>> {
self.source_urls.borrow()
}
}

View file

@ -2,14 +2,12 @@
* 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::cell::{Ref, RefCell};
use std::collections::BTreeSet;
use std::net::TcpStream;
use serde::Serialize;
use serde_json::{Map, Value};
use servo_url::ServoUrl;
use super::source::{Source, SourcesReply};
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::protocol::JsonPacketStream;
use crate::{EmptyReplyMsg, StreamId};
@ -52,45 +50,18 @@ struct ThreadInterruptedReply {
type_: String,
}
#[derive(Serialize)]
struct SourcesReply {
from: String,
sources: Vec<Source>,
}
#[derive(Eq, Ord, PartialEq, PartialOrd, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Source {
pub actor: String,
/// URL of the script, or URL of the page for inline scripts.
pub url: String,
pub is_black_boxed: bool,
}
pub struct ThreadActor {
name: String,
source_urls: RefCell<BTreeSet<Source>>,
pub name: String,
pub source_manager: Source,
}
impl ThreadActor {
pub fn new(name: String) -> ThreadActor {
ThreadActor {
name,
source_urls: RefCell::new(BTreeSet::default()),
name: name.clone(),
source_manager: Source::new(name),
}
}
pub fn add_source(&self, url: ServoUrl) {
self.source_urls.borrow_mut().insert(Source {
actor: self.name.clone(),
url: url.to_string(),
is_black_boxed: false,
});
}
pub fn sources(&self) -> Ref<BTreeSet<Source>> {
self.source_urls.borrow()
}
}
impl Actor for ThreadActor {

View file

@ -20,8 +20,10 @@ use serde_json::{Map, Value};
use self::network_parent::{NetworkParentActor, NetworkParentActorMsg};
use super::thread::ThreadActor;
use super::worker::WorkerMsg;
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::actors::browsing_context::{BrowsingContextActor, BrowsingContextActorMsg};
use crate::actors::root::RootActor;
use crate::actors::watcher::target_configuration::{
TargetConfigurationActor, TargetConfigurationActorMsg,
};
@ -29,7 +31,8 @@ use crate::actors::watcher::thread_configuration::{
ThreadConfigurationActor, ThreadConfigurationActorMsg,
};
use crate::protocol::JsonPacketStream;
use crate::{EmptyReplyMsg, StreamId};
use crate::resource::ResourceAvailable;
use crate::{EmptyReplyMsg, StreamId, WorkerActor};
pub mod network_parent;
pub mod target_configuration;
@ -54,7 +57,7 @@ impl SessionContext {
supported_targets: HashMap::from([
("frame", true),
("process", false),
("worker", false),
("worker", true),
("service_worker", false),
("shared_worker", false),
]),
@ -101,12 +104,19 @@ pub enum SessionContextType {
_All,
}
#[derive(Serialize)]
#[serde(untagged)]
enum TargetActorMsg {
BrowsingContext(BrowsingContextActorMsg),
Worker(WorkerMsg),
}
#[derive(Serialize)]
struct WatchTargetsReply {
from: String,
#[serde(rename = "type")]
type_: String,
target: BrowsingContextActorMsg,
target: TargetActorMsg,
}
#[derive(Serialize)]
@ -211,16 +221,38 @@ impl Actor for WatcherActor {
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
let target = registry.find::<BrowsingContextActor>(&self.browsing_context_actor);
let root = registry.find::<RootActor>("root");
Ok(match msg_type {
"watchTargets" => {
let msg = WatchTargetsReply {
from: self.name(),
type_: "target-available-form".into(),
target: target.encodable(),
};
let _ = stream.write_json_packet(&msg);
// As per logs we either get targetType as "frame" or "worker"
let target_type = msg
.get("targetType")
.and_then(Value::as_str)
.unwrap_or("frame"); // default to "frame"
target.frame_update(stream);
if target_type == "frame" {
let msg = WatchTargetsReply {
from: self.name(),
type_: "target-available-form".into(),
target: TargetActorMsg::BrowsingContext(target.encodable()),
};
let _ = stream.write_json_packet(&msg);
target.frame_update(stream);
} else if target_type == "worker" {
for worker_name in &root.workers {
let worker = registry.find::<WorkerActor>(worker_name);
let worker_msg = WatchTargetsReply {
from: self.name(),
type_: "target-available-form".into(),
target: TargetActorMsg::Worker(worker.encodable()),
};
let _ = stream.write_json_packet(&worker_msg);
}
} else {
warn!("Unexpected target_type: {}", target_type);
return Ok(ActorMessageStatus::Ignored);
}
// Messages that contain a `type` field are used to send event callbacks, but they
// don't count as a reply. Since every message needs to be responded, we send an
@ -259,13 +291,29 @@ impl Actor for WatcherActor {
title: Some(target.title.borrow().clone()),
url: Some(target.url.borrow().clone()),
};
target.resource_available(event, "document-event".into());
target.resource_available(event, "document-event".into(), stream);
}
},
"source" => {
let thread_actor = registry.find::<ThreadActor>(&target.thread);
let sources = thread_actor.sources();
target.resources_available(sources.iter().collect(), "source".into());
let sources = thread_actor.source_manager.sources();
target.resources_available(
sources.iter().collect(),
"source".into(),
stream,
);
for worker_name in &root.workers {
let worker = registry.find::<WorkerActor>(worker_name);
let thread = registry.find::<ThreadActor>(&worker.thread);
let worker_sources = thread.source_manager.sources();
worker.resources_available(
worker_sources.iter().collect(),
"source".into(),
stream,
);
}
},
"console-message" | "error-message" => {},
_ => warn!("resource {} not handled yet", resource),

View file

@ -17,6 +17,7 @@ use servo_url::ServoUrl;
use crate::StreamId;
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::protocol::JsonPacketStream;
use crate::resource::ResourceAvailable;
#[derive(Clone, Copy)]
#[allow(dead_code)]
@ -47,12 +48,20 @@ impl WorkerActor {
url: self.url.to_string(),
traits: WorkerTraits {
is_parent_intercept_enabled: false,
supports_top_level_target_flag: false,
},
type_: self.type_ as u32,
target_type: "worker".to_string(),
}
}
}
impl ResourceAvailable for WorkerActor {
fn actor_name(&self) -> String {
self.name.clone()
}
}
impl Actor for WorkerActor {
fn name(&self) -> String {
self.name.clone()
@ -149,6 +158,7 @@ struct ConnectReply {
#[serde(rename_all = "camelCase")]
struct WorkerTraits {
is_parent_intercept_enabled: bool,
supports_top_level_target_flag: bool,
}
#[derive(Serialize)]
@ -162,4 +172,6 @@ pub(crate) struct WorkerMsg {
traits: WorkerTraits,
#[serde(rename = "type")]
type_: u32,
#[serde(rename = "targetType")]
target_type: String,
}

View file

@ -19,7 +19,7 @@ use std::net::{Shutdown, TcpListener, TcpStream};
use std::sync::{Arc, Mutex};
use std::thread;
use actors::thread::Source;
use actors::source::SourceData;
use base::id::{BrowsingContextId, PipelineId, WebViewId};
use crossbeam_channel::{Receiver, Sender, unbounded};
use devtools_traits::{
@ -30,6 +30,7 @@ use devtools_traits::{
use embedder_traits::{AllowOrDeny, EmbedderMsg, EmbedderProxy};
use ipc_channel::ipc::{self, IpcSender};
use log::trace;
use resource::ResourceAvailable;
use serde::Serialize;
use servo_rand::RngCore;
@ -65,6 +66,7 @@ mod actors {
pub mod process;
pub mod reflow;
pub mod root;
pub mod source;
pub mod stylesheets;
pub mod tab;
pub mod thread;
@ -75,6 +77,7 @@ mod actors {
mod id;
mod network_handler;
mod protocol;
mod resource;
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
enum UniqueId {
@ -411,7 +414,7 @@ impl DevtoolsInstance {
}
fn handle_page_error(
&self,
&mut self,
pipeline_id: PipelineId,
worker_id: Option<WorkerId>,
page_error: PageError,
@ -423,11 +426,13 @@ impl DevtoolsInstance {
let actors = self.actors.lock().unwrap();
let console_actor = actors.find::<ConsoleActor>(&console_actor_name);
let id = worker_id.map_or(UniqueId::Pipeline(pipeline_id), UniqueId::Worker);
console_actor.handle_page_error(page_error, id, &actors);
for stream in self.connections.values_mut() {
console_actor.handle_page_error(page_error.clone(), id.clone(), &actors, stream);
}
}
fn handle_console_message(
&self,
&mut self,
pipeline_id: PipelineId,
worker_id: Option<WorkerId>,
console_message: ConsoleMessage,
@ -439,7 +444,9 @@ impl DevtoolsInstance {
let actors = self.actors.lock().unwrap();
let console_actor = actors.find::<ConsoleActor>(&console_actor_name);
let id = worker_id.map_or(UniqueId::Pipeline(pipeline_id), UniqueId::Worker);
console_actor.handle_console_api(console_message, id, &actors);
for stream in self.connections.values_mut() {
console_actor.handle_console_api(console_message.clone(), id.clone(), &actors, stream);
}
}
fn find_console_actor(
@ -507,33 +514,60 @@ impl DevtoolsInstance {
fn handle_script_source_info(&mut self, pipeline_id: PipelineId, source_info: SourceInfo) {
let mut actors = self.actors.lock().unwrap();
let browsing_context_id = match self.pipelines.get(&pipeline_id) {
Some(id) => id,
None => return,
};
if let Some(worker_id) = source_info.worker_id {
let Some(worker_actor_name) = self.actor_workers.get(&worker_id) else {
return;
};
let actor_name = match self.browsing_contexts.get(browsing_context_id) {
Some(name) => name,
None => return,
};
let thread_actor_name = actors.find::<WorkerActor>(worker_actor_name).thread.clone();
let thread_actor_name = actors
.find::<BrowsingContextActor>(actor_name)
.thread
.clone();
let thread_actor = actors.find_mut::<ThreadActor>(&thread_actor_name);
thread_actor
.source_manager
.add_source(source_info.url.clone());
let thread_actor = actors.find_mut::<ThreadActor>(&thread_actor_name);
thread_actor.add_source(source_info.url.clone());
let source = SourceData {
actor: thread_actor_name.clone(),
url: source_info.url.to_string(),
is_black_boxed: false,
};
let source = Source {
actor: thread_actor_name.clone(),
url: source_info.url.to_string(),
is_black_boxed: false,
};
let worker_actor = actors.find::<WorkerActor>(worker_actor_name);
// Notify browsing context about the new source
let browsing_context = actors.find::<BrowsingContextActor>(actor_name);
browsing_context.resource_available(source, "source".into());
for stream in self.connections.values_mut() {
worker_actor.resource_available(&source, "source".into(), stream);
}
} else {
let Some(browsing_context_id) = self.pipelines.get(&pipeline_id) else {
return;
};
let Some(actor_name) = self.browsing_contexts.get(browsing_context_id) else {
return;
};
let thread_actor_name = actors
.find::<BrowsingContextActor>(actor_name)
.thread
.clone();
let thread_actor = actors.find_mut::<ThreadActor>(&thread_actor_name);
thread_actor
.source_manager
.add_source(source_info.url.clone());
let source = SourceData {
actor: thread_actor_name.clone(),
url: source_info.url.to_string(),
is_black_boxed: false,
};
// Notify browsing context about the new source
let browsing_context = actors.find::<BrowsingContextActor>(actor_name);
for stream in self.connections.values_mut() {
browsing_context.resource_available(&source, "source".into(), stream);
}
}
}
}

View file

@ -0,0 +1,45 @@
/* 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::net::TcpStream;
use serde::Serialize;
use crate::protocol::JsonPacketStream;
#[derive(Serialize)]
pub(crate) struct ResourceAvailableReply<T: Serialize> {
pub from: String,
#[serde(rename = "type")]
pub type_: String,
pub array: Vec<(String, Vec<T>)>,
}
pub(crate) trait ResourceAvailable {
fn actor_name(&self) -> String;
fn resource_available<T: Serialize>(
&self,
resource: T,
resource_type: String,
stream: &mut TcpStream,
) {
self.resources_available(vec![resource], resource_type, stream);
}
fn resources_available<T: Serialize>(
&self,
resources: Vec<T>,
resource_type: String,
stream: &mut TcpStream,
) {
let msg = ResourceAvailableReply::<T> {
from: self.actor_name(),
type_: "resources-available-array".into(),
array: vec![(resource_type, resources)],
};
let _ = stream.write_json_packet(&msg);
}
}

View file

@ -38,6 +38,7 @@ memmap2 = { workspace = true }
net_traits = { workspace = true }
num-traits = { workspace = true }
parking_lot = { workspace = true }
profile_traits = { workspace = true }
range = { path = "../range" }
serde = { workspace = true }
servo_arc = { workspace = true }

View file

@ -5,8 +5,8 @@
use std::collections::HashMap;
use std::sync::Arc;
use atomic_refcell::AtomicRefCell;
use log::warn;
use malloc_size_of_derive::MallocSizeOf;
use parking_lot::RwLock;
use style::stylesheets::DocumentStyleSheet;
use style::values::computed::{FontStyle, FontWeight};
@ -15,7 +15,7 @@ use crate::font::FontDescriptor;
use crate::font_template::{FontTemplate, FontTemplateRef, FontTemplateRefMethods, IsOblique};
use crate::system_font_service::{FontIdentifier, LowercaseFontFamilyName};
#[derive(Default)]
#[derive(Default, MallocSizeOf)]
pub struct FontStore {
pub(crate) families: HashMap<LowercaseFontFamilyName, FontTemplates>,
web_fonts_loading_for_stylesheets: Vec<(DocumentStyleSheet, usize)>,
@ -134,7 +134,7 @@ impl FontStore {
///
/// This optimization is taken from:
/// <https://searchfox.org/mozilla-central/source/gfx/thebes/gfxFontEntry.cpp>.
#[derive(Clone, Debug, Default)]
#[derive(Clone, Debug, Default, MallocSizeOf)]
struct SimpleFamily {
regular: Option<FontTemplateRef>,
bold: Option<FontTemplateRef>,
@ -190,7 +190,7 @@ impl SimpleFamily {
}
}
/// A list of font templates that make up a given font family.
#[derive(Clone, Debug)]
#[derive(Clone, Debug, MallocSizeOf)]
pub struct FontTemplates {
pub(crate) templates: Vec<FontTemplateRef>,
simple_family: Option<SimpleFamily>,
@ -263,7 +263,7 @@ impl FontTemplates {
}
}
let new_template = Arc::new(AtomicRefCell::new(new_template));
let new_template = FontTemplateRef::new(new_template);
self.templates.push(new_template.clone());
self.update_simple_family(new_template);
}

View file

@ -3,7 +3,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::fmt::{Debug, Error, Formatter};
use std::ops::RangeInclusive;
use std::ops::{Deref, RangeInclusive};
use std::sync::Arc;
use atomic_refcell::AtomicRefCell;
@ -20,7 +20,21 @@ use crate::system_font_service::{
};
/// A reference to a [`FontTemplate`] with shared ownership and mutability.
pub type FontTemplateRef = Arc<AtomicRefCell<FontTemplate>>;
#[derive(Clone, Debug, MallocSizeOf)]
pub struct FontTemplateRef(#[conditional_malloc_size_of] Arc<AtomicRefCell<FontTemplate>>);
impl FontTemplateRef {
pub fn new(template: FontTemplate) -> Self {
Self(Arc::new(AtomicRefCell::new(template)))
}
}
impl Deref for FontTemplateRef {
type Target = Arc<AtomicRefCell<FontTemplate>>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
/// Describes how to select a font from a given family. This is very basic at the moment and needs
/// to be expanded or refactored when we support more of the font styling parameters.

View file

@ -287,7 +287,7 @@ impl PlatformFontMethods for PlatformFont {
.unwrap_or(average_advance);
let metrics = FontMetrics {
underline_size: Au::from_f64_au(underline_thickness),
underline_size: Au::from_f64_px(underline_thickness),
// TODO(Issue #201): underline metrics are not reliable. Have to pull out of font table
// directly.
//

View file

@ -132,7 +132,9 @@ impl PlatformFontMethods for PlatformFont {
pt_size: Option<Au>,
) -> Result<PlatformFont, &'static str> {
let font_face = FontCollection::system()
.get_font_from_descriptor(&font_identifier.font_descriptor)
.font_from_descriptor(&font_identifier.font_descriptor)
.ok()
.flatten()
.ok_or("Could not create Font from descriptor")?
.create_font_face();
Self::new(font_face, pt_size)

View file

@ -25,7 +25,9 @@ where
{
let system_fc = FontCollection::system();
for family in system_fc.families_iter() {
callback(family.name());
if let Ok(family_name) = family.family_name() {
callback(family_name);
}
}
}
@ -40,13 +42,17 @@ pub struct LocalFontIdentifier {
impl LocalFontIdentifier {
pub fn index(&self) -> u32 {
FontCollection::system()
.get_font_from_descriptor(&self.font_descriptor)
.font_from_descriptor(&self.font_descriptor)
.ok()
.flatten()
.map_or(0, |font| font.create_font_face().get_index())
}
pub(crate) fn native_font_handle(&self) -> NativeFontHandle {
let face = FontCollection::system()
.get_font_from_descriptor(&self.font_descriptor)
.font_from_descriptor(&self.font_descriptor)
.ok()
.flatten()
.expect("Could not create Font from FontDescriptor")
.create_font_face();
let path = face
@ -62,7 +68,9 @@ impl LocalFontIdentifier {
}
pub(crate) fn read_data_from_file(&self) -> Option<Vec<u8>> {
let font = FontCollection::system().get_font_from_descriptor(&self.font_descriptor)?;
let font = FontCollection::system()
.font_from_descriptor(&self.font_descriptor)
.ok()??;
let face = font.create_font_face();
let files = face.get_files();
assert!(!files.is_empty());
@ -86,10 +94,12 @@ where
F: FnMut(FontTemplate),
{
let system_fc = FontCollection::system();
if let Some(family) = system_fc.get_font_family_by_name(family_name) {
if let Ok(Some(family)) = system_fc.font_family_by_name(family_name) {
let count = family.get_font_count();
for i in 0..count {
let font = family.get_font(i);
let Ok(font) = family.font(i) else {
continue;
};
let template_descriptor = (&font).into();
let local_font_identifier = LocalFontIdentifier {
font_descriptor: Arc::new(font.to_descriptor()),

View file

@ -6,16 +6,19 @@ use std::borrow::ToOwned;
use std::cell::OnceCell;
use std::collections::HashMap;
use std::ops::{Deref, RangeInclusive};
use std::sync::Arc;
use std::{fmt, thread};
use app_units::Au;
use atomic_refcell::AtomicRefCell;
use compositing_traits::CrossProcessCompositorApi;
use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
use log::debug;
use malloc_size_of::MallocSizeOf as MallocSizeOfTrait;
use malloc_size_of_derive::MallocSizeOf;
use parking_lot::{Mutex, RwLock};
use profile_traits::mem::{
ProcessReports, ProfilerChan, Report, ReportKind, ReportsChan, perform_memory_report,
};
use profile_traits::path;
use serde::{Deserialize, Serialize};
use servo_config::pref;
use servo_url::ServoUrl;
@ -66,11 +69,12 @@ pub enum SystemFontServiceMessage {
),
GetFontKey(IpcSender<FontKey>),
GetFontInstanceKey(IpcSender<FontInstanceKey>),
CollectMemoryReport(ReportsChan),
Exit(IpcSender<()>),
Ping,
}
#[derive(Default)]
#[derive(Default, MallocSizeOf)]
struct ResolvedGenericFontFamilies {
default: OnceCell<LowercaseFontFamilyName>,
serif: OnceCell<LowercaseFontFamilyName>,
@ -84,6 +88,7 @@ struct ResolvedGenericFontFamilies {
/// The system font service. There is one of these for every Servo instance. This is a thread,
/// responsible for reading the list of system fonts, handling requests to match against
/// them, and ensuring that only one copy of system font data is loaded at a time.
#[derive(MallocSizeOf)]
pub struct SystemFontService {
port: IpcReceiver<SystemFontServiceMessage>,
local_families: FontStore,
@ -118,8 +123,12 @@ impl SystemFontServiceProxySender {
}
impl SystemFontService {
pub fn spawn(compositor_api: CrossProcessCompositorApi) -> SystemFontServiceProxySender {
pub fn spawn(
compositor_api: CrossProcessCompositorApi,
memory_profiler_sender: ProfilerChan,
) -> SystemFontServiceProxySender {
let (sender, receiver) = ipc::channel().unwrap();
let memory_reporter_sender = sender.clone();
thread::Builder::new()
.name("SystemFontService".to_owned())
@ -138,7 +147,13 @@ impl SystemFontService {
cache.fetch_new_keys();
cache.refresh_local_families();
cache.run();
memory_profiler_sender.run_with_memory_reporting(
|| cache.run(),
"system-fonts".to_owned(),
memory_reporter_sender,
SystemFontServiceMessage::CollectMemoryReport,
);
})
.expect("Thread spawning failed");
@ -172,6 +187,9 @@ impl SystemFontService {
self.fetch_new_keys();
let _ = result_sender.send(self.free_font_instance_keys.pop().unwrap());
},
SystemFontServiceMessage::CollectMemoryReport(report_sender) => {
self.collect_memory_report(report_sender);
},
SystemFontServiceMessage::Ping => (),
SystemFontServiceMessage::Exit(result) => {
let _ = result.send(());
@ -181,6 +199,17 @@ impl SystemFontService {
}
}
fn collect_memory_report(&self, report_sender: ReportsChan) {
perform_memory_report(|ops| {
let reports = vec![Report {
path: path!["system-fonts"],
kind: ReportKind::ExplicitSystemHeapSize,
size: self.size_of(ops),
}];
report_sender.send(ProcessReports::new(reports));
});
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace")
@ -528,11 +557,7 @@ impl SystemFontServiceProxy {
panic!("SystemFontService has already exited.");
};
let templates: Vec<_> = templates
.into_iter()
.map(AtomicRefCell::new)
.map(Arc::new)
.collect();
let templates: Vec<_> = templates.into_iter().map(FontTemplateRef::new).collect();
self.templates.write().insert(cache_key, templates.clone());
templates

View file

@ -5,14 +5,13 @@
use std::fs::File;
use std::io::Read;
use std::path::PathBuf;
use std::sync::Arc;
use app_units::Au;
use euclid::num::Zero;
use fonts::platform::font::PlatformFont;
use fonts::{
Font, FontData, FontDescriptor, FontIdentifier, FontTemplate, PlatformFontMethods,
ShapingFlags, ShapingOptions,
Font, FontData, FontDescriptor, FontIdentifier, FontTemplate, FontTemplateRef,
PlatformFontMethods, ShapingFlags, ShapingOptions,
};
use servo_url::ServoUrl;
use style::properties::longhands::font_variant_caps::computed_value::T as FontVariantCaps;
@ -42,13 +41,7 @@ fn make_font(path: PathBuf) -> Font {
variant: FontVariantCaps::Normal,
pt_size: Au::from_px(24),
};
Font::new(
Arc::new(atomic_refcell::AtomicRefCell::new(template)),
descriptor,
Some(data),
None,
)
.unwrap()
Font::new(FontTemplateRef::new(template), descriptor, Some(data), None).unwrap()
}
#[test]

View file

@ -137,6 +137,7 @@ mod font_context {
break;
},
SystemFontServiceMessage::Ping => {},
SystemFontServiceMessage::CollectMemoryReport(..) => {},
}
}
}

View file

@ -21,7 +21,6 @@ app_units = { workspace = true }
atomic_refcell = { workspace = true }
base = { workspace = true }
bitflags = { workspace = true }
canvas_traits = { workspace = true }
compositing_traits = { workspace = true }
constellation_traits = { workspace = true }
data-url = { workspace = true }

View file

@ -12,7 +12,7 @@ use style::selector_parser::PseudoElement;
use crate::PropagatedBoxTreeData;
use crate::context::LayoutContext;
use crate::dom::{BoxSlot, NodeExt};
use crate::dom::BoxSlot;
use crate::dom_traversal::{Contents, NodeAndStyleInfo, TraversalHandler};
use crate::flow::inline::construct::InlineFormattingContextBuilder;
use crate::flow::{BlockContainer, BlockFormattingContext};
@ -24,32 +24,32 @@ use crate::layout_box_base::LayoutBoxBase;
use crate::style_ext::DisplayGeneratingBox;
/// A builder used for both flex and grid containers.
pub(crate) struct ModernContainerBuilder<'a, 'dom, Node> {
pub(crate) struct ModernContainerBuilder<'a, 'dom> {
context: &'a LayoutContext<'a>,
info: &'a NodeAndStyleInfo<Node>,
info: &'a NodeAndStyleInfo<'dom>,
propagated_data: PropagatedBoxTreeData,
contiguous_text_runs: Vec<ModernContainerTextRun<'dom, Node>>,
contiguous_text_runs: Vec<ModernContainerTextRun<'dom>>,
/// To be run in parallel with rayon in `finish`
jobs: Vec<ModernContainerJob<'dom, Node>>,
jobs: Vec<ModernContainerJob<'dom>>,
has_text_runs: bool,
}
enum ModernContainerJob<'dom, Node> {
enum ModernContainerJob<'dom> {
ElementOrPseudoElement {
info: NodeAndStyleInfo<Node>,
info: NodeAndStyleInfo<'dom>,
display: DisplayGeneratingBox,
contents: Contents,
box_slot: BoxSlot<'dom>,
},
TextRuns(Vec<ModernContainerTextRun<'dom, Node>>),
TextRuns(Vec<ModernContainerTextRun<'dom>>),
}
struct ModernContainerTextRun<'dom, Node> {
info: NodeAndStyleInfo<Node>,
struct ModernContainerTextRun<'dom> {
info: NodeAndStyleInfo<'dom>,
text: Cow<'dom, str>,
}
impl<Node> ModernContainerTextRun<'_, Node> {
impl ModernContainerTextRun<'_> {
/// <https://drafts.csswg.org/css-text/#white-space>
fn is_only_document_white_space(&self) -> bool {
// FIXME: is this the right definition? See
@ -73,11 +73,8 @@ pub(crate) struct ModernItem<'dom> {
pub formatting_context: IndependentFormattingContext,
}
impl<'dom, Node: 'dom> TraversalHandler<'dom, Node> for ModernContainerBuilder<'_, 'dom, Node>
where
Node: NodeExt<'dom>,
{
fn handle_text(&mut self, info: &NodeAndStyleInfo<Node>, text: Cow<'dom, str>) {
impl<'dom> TraversalHandler<'dom> for ModernContainerBuilder<'_, 'dom> {
fn handle_text(&mut self, info: &NodeAndStyleInfo<'dom>, text: Cow<'dom, str>) {
self.contiguous_text_runs.push(ModernContainerTextRun {
info: info.clone(),
text,
@ -87,7 +84,7 @@ where
/// Or pseudo-element
fn handle_element(
&mut self,
info: &NodeAndStyleInfo<Node>,
info: &NodeAndStyleInfo<'dom>,
display: DisplayGeneratingBox,
contents: Contents,
box_slot: BoxSlot<'dom>,
@ -103,13 +100,10 @@ where
}
}
impl<'a, 'dom, Node: 'dom> ModernContainerBuilder<'a, 'dom, Node>
where
Node: NodeExt<'dom>,
{
impl<'a, 'dom> ModernContainerBuilder<'a, 'dom> {
pub fn new(
context: &'a LayoutContext<'a>,
info: &'a NodeAndStyleInfo<Node>,
info: &'a NodeAndStyleInfo<'dom>,
propagated_data: PropagatedBoxTreeData,
) -> Self {
ModernContainerBuilder {
@ -148,7 +142,7 @@ where
.filter_map(|job| match job {
ModernContainerJob::TextRuns(runs) => {
let mut inline_formatting_context_builder =
InlineFormattingContextBuilder::new();
InlineFormattingContextBuilder::new(self.info);
for flex_text_run in runs.into_iter() {
inline_formatting_context_builder
.push_text(flex_text_run.text, &flex_text_run.info);
@ -156,7 +150,6 @@ where
let inline_formatting_context = inline_formatting_context_builder.finish(
self.context,
self.propagated_data,
true, /* has_first_formatted_line */
false, /* is_single_line_text_box */
self.info.style.writing_mode.to_bidi_level(),
@ -165,7 +158,7 @@ where
let block_formatting_context = BlockFormattingContext::from_block_container(
BlockContainer::InlineFormattingContext(inline_formatting_context),
);
let info: &NodeAndStyleInfo<_> = &*anonymous_info;
let info: &NodeAndStyleInfo = &anonymous_info;
let formatting_context = IndependentFormattingContext {
base: LayoutBoxBase::new(info.into(), info.style.clone()),
contents: IndependentFormattingContextContents::NonReplaced(

View file

@ -46,6 +46,9 @@ pub struct LayoutContext<'a> {
Arc<RwLock<FnvHashMap<(ServoUrl, UsePlaceholder), WebRenderImageInfo>>>,
pub node_image_animation_map: Arc<RwLock<FxHashMap<OpaqueNode, ImageAnimationState>>>,
/// The DOM node that is highlighted by the devtools inspector, if any
pub highlighted_dom_node: Option<OpaqueNode>,
}
pub enum ResolvedImage<'a> {
@ -61,6 +64,20 @@ impl Drop for LayoutContext<'_> {
}
}
#[derive(Debug)]
pub enum ResolveImageError {
LoadError,
ImagePending,
ImageRequested,
OnlyMetadata,
InvalidUrl,
MissingNode,
ImageMissingFromImageSet,
FailedToResolveImageFromImageSet,
NotImplementedYet(&'static str),
None,
}
impl LayoutContext<'_> {
#[inline(always)]
pub fn shared_context(&self) -> &SharedStyleContext {
@ -72,7 +89,7 @@ impl LayoutContext<'_> {
node: OpaqueNode,
url: ServoUrl,
use_placeholder: UsePlaceholder,
) -> Option<ImageOrMetadataAvailable> {
) -> Result<ImageOrMetadataAvailable, ResolveImageError> {
// Check for available image or start tracking.
let cache_result = self.image_cache.get_cached_image_status(
url.clone(),
@ -82,7 +99,7 @@ impl LayoutContext<'_> {
);
match cache_result {
ImageCacheResult::Available(img_or_meta) => Some(img_or_meta),
ImageCacheResult::Available(img_or_meta) => Ok(img_or_meta),
// Image has been requested, is still pending. Return no image for this paint loop.
// When the image loads it will trigger a reflow and/or repaint.
ImageCacheResult::Pending(id) => {
@ -93,7 +110,7 @@ impl LayoutContext<'_> {
origin: self.origin.clone(),
};
self.pending_images.lock().push(image);
None
Result::Err(ResolveImageError::ImagePending)
},
// Not yet requested - request image or metadata from the cache
ImageCacheResult::ReadyForRequest(id) => {
@ -104,10 +121,10 @@ impl LayoutContext<'_> {
origin: self.origin.clone(),
};
self.pending_images.lock().push(image);
None
Result::Err(ResolveImageError::ImageRequested)
},
// Image failed to load, so just return nothing
ImageCacheResult::LoadError => None,
ImageCacheResult::LoadError => Result::Err(ResolveImageError::LoadError),
}
}
@ -120,14 +137,23 @@ impl LayoutContext<'_> {
if image_state.image_key() != image.id {
if image.should_animate() {
// i. Register/Replace tracking item in image_animation_manager.
store.insert(node, ImageAnimationState::new(image));
store.insert(
node,
ImageAnimationState::new(
image,
self.shared_context().current_time_for_animations,
),
);
} else {
// ii. Cancel Action if the node's image is no longer animated.
store.remove(&node);
}
}
} else if image.should_animate() {
store.insert(node, ImageAnimationState::new(image));
store.insert(
node,
ImageAnimationState::new(image, self.shared_context().current_time_for_animations),
);
}
}
@ -136,31 +162,34 @@ impl LayoutContext<'_> {
node: OpaqueNode,
url: ServoUrl,
use_placeholder: UsePlaceholder,
) -> Option<WebRenderImageInfo> {
) -> Result<WebRenderImageInfo, ResolveImageError> {
if let Some(existing_webrender_image) = self
.webrender_image_cache
.read()
.get(&(url.clone(), use_placeholder))
{
return Some(*existing_webrender_image);
return Ok(*existing_webrender_image);
}
match self.get_or_request_image_or_meta(node, url.clone(), use_placeholder) {
Some(ImageOrMetadataAvailable::ImageAvailable { image, .. }) => {
let image_or_meta =
self.get_or_request_image_or_meta(node, url.clone(), use_placeholder)?;
match image_or_meta {
ImageOrMetadataAvailable::ImageAvailable { image, .. } => {
self.handle_animated_image(node, image.clone());
let image_info = WebRenderImageInfo {
size: Size2D::new(image.width, image.height),
key: image.id,
};
if image_info.key.is_none() {
Some(image_info)
Ok(image_info)
} else {
let mut webrender_image_cache = self.webrender_image_cache.write();
webrender_image_cache.insert((url, use_placeholder), image_info);
Some(image_info)
Ok(image_info)
}
},
None | Some(ImageOrMetadataAvailable::MetadataAvailable(..)) => None,
ImageOrMetadataAvailable::MetadataAvailable(..) => {
Result::Err(ResolveImageError::OnlyMetadata)
},
}
}
@ -168,11 +197,15 @@ impl LayoutContext<'_> {
&self,
node: Option<OpaqueNode>,
image: &'a Image,
) -> Option<ResolvedImage<'a>> {
) -> Result<ResolvedImage<'a>, ResolveImageError> {
match image {
// TODO: Add support for PaintWorklet and CrossFade rendering.
Image::None | Image::CrossFade(_) | Image::PaintWorklet(_) => None,
Image::Gradient(gradient) => Some(ResolvedImage::Gradient(gradient)),
Image::None => Result::Err(ResolveImageError::None),
Image::CrossFade(_) => Result::Err(ResolveImageError::NotImplementedYet("CrossFade")),
Image::PaintWorklet(_) => {
Result::Err(ResolveImageError::NotImplementedYet("PaintWorklet"))
},
Image::Gradient(gradient) => Ok(ResolvedImage::Gradient(gradient)),
Image::Url(image_url) => {
// FIXME: images wont always have in intrinsic width or
// height when support for SVG is added, or a WebRender
@ -180,18 +213,20 @@ impl LayoutContext<'_> {
//
// FIXME: It feels like this should take into account the pseudo
// element and not just the node.
let image_url = image_url.url()?;
let image_url = image_url.url().ok_or(ResolveImageError::InvalidUrl)?;
let node = node.ok_or(ResolveImageError::MissingNode)?;
let webrender_info = self.get_webrender_image_for_url(
node?,
node,
image_url.clone().into(),
UsePlaceholder::No,
)?;
Some(ResolvedImage::Image(webrender_info))
Ok(ResolvedImage::Image(webrender_info))
},
Image::ImageSet(image_set) => {
image_set
.items
.get(image_set.selected_index)
.ok_or(ResolveImageError::ImageMissingFromImageSet)
.and_then(|image| {
self.resolve_image(node, &image.image)
.map(|info| match info {

View file

@ -66,7 +66,7 @@ impl<'a> BackgroundPainter<'a> {
if &BackgroundAttachment::Fixed ==
get_cyclic(&background.background_attachment.0, layer_index)
{
let viewport_size = builder.display_list.compositor_info.viewport_size;
let viewport_size = builder.compositor_info.viewport_size;
return units::LayoutRect::from_origin_and_size(Point2D::origin(), viewport_size);
}
@ -121,7 +121,7 @@ impl<'a> BackgroundPainter<'a> {
if &BackgroundAttachment::Fixed ==
get_cyclic(&style.get_background().background_attachment.0, layer_index)
{
common.spatial_id = builder.current_reference_frame_scroll_node_id.spatial_id;
common.spatial_id = builder.spatial_id(builder.current_reference_frame_scroll_node_id);
}
common
}

View file

@ -0,0 +1,276 @@
/* 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 app_units::Au;
use base::id::ScrollTreeNodeId;
use style::values::computed::basic_shape::{BasicShape, ClipPath};
use style::values::computed::length_percentage::NonNegativeLengthPercentage;
use style::values::computed::position::Position;
use style::values::generics::basic_shape::{GenericShapeRadius, ShapeBox, ShapeGeometryBox};
use style::values::generics::position::GenericPositionOrAuto;
use webrender_api::BorderRadius;
use webrender_api::units::{LayoutRect, LayoutSideOffsets, LayoutSize};
use super::{BuilderForBoxFragment, compute_margin_box_radius, normalize_radii};
/// An identifier for a clip used during StackingContextTree construction. This is a simple index in
/// a [`ClipStore`]s vector of clips.
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) struct ClipId(pub usize);
impl ClipId {
/// Equivalent to [`ClipChainId::INVALID`]. This means "no clip."
pub(crate) const INVALID: ClipId = ClipId(usize::MAX);
}
/// All the information needed to create a clip on a WebRender display list. These are created at
/// two times: during `StackingContextTree` creation and during WebRender display list construction.
/// Only the former are stored in a [`ClipStore`].
#[derive(Clone)]
pub(crate) struct Clip {
pub id: ClipId,
pub radii: BorderRadius,
pub rect: LayoutRect,
pub parent_scroll_node_id: ScrollTreeNodeId,
pub parent_clip_id: ClipId,
}
/// A simple vector of [`Clip`] that is built during `StackingContextTree` construction.
/// These are later turned into WebRender clips and clip chains during WebRender display
/// list construction.
#[derive(Clone, Default)]
pub(crate) struct StackingContextTreeClipStore(pub Vec<Clip>);
impl StackingContextTreeClipStore {
pub(crate) fn add(
&mut self,
radii: webrender_api::BorderRadius,
rect: LayoutRect,
parent_scroll_node_id: ScrollTreeNodeId,
parent_clip_id: ClipId,
) -> ClipId {
let id = ClipId(self.0.len());
self.0.push(Clip {
id,
radii,
rect,
parent_scroll_node_id,
parent_clip_id,
});
id
}
pub(super) fn add_for_clip_path(
&mut self,
clip_path: ClipPath,
parent_scroll_node_id: &ScrollTreeNodeId,
parent_clip_chain_id: &ClipId,
fragment_builder: BuilderForBoxFragment,
) -> Option<ClipId> {
let geometry_box = match clip_path {
ClipPath::Shape(_, ShapeGeometryBox::ShapeBox(shape_box)) => shape_box,
ClipPath::Shape(_, ShapeGeometryBox::ElementDependent) => ShapeBox::BorderBox,
ClipPath::Box(ShapeGeometryBox::ShapeBox(shape_box)) => shape_box,
ClipPath::Box(ShapeGeometryBox::ElementDependent) => ShapeBox::BorderBox,
_ => return None,
};
let layout_rect = match geometry_box {
ShapeBox::BorderBox => fragment_builder.border_rect,
ShapeBox::ContentBox => *fragment_builder.content_rect(),
ShapeBox::PaddingBox => *fragment_builder.padding_rect(),
ShapeBox::MarginBox => *fragment_builder.margin_rect(),
};
if let ClipPath::Shape(shape, _) = clip_path {
match *shape {
BasicShape::Circle(_) | BasicShape::Ellipse(_) | BasicShape::Rect(_) => self
.add_for_basic_shape(
*shape,
layout_rect,
parent_scroll_node_id,
parent_clip_chain_id,
),
BasicShape::Polygon(_) | BasicShape::PathOrShape(_) => None,
}
} else {
Some(self.add(
match geometry_box {
ShapeBox::MarginBox => compute_margin_box_radius(
fragment_builder.border_radius,
layout_rect.size(),
fragment_builder.fragment,
),
_ => fragment_builder.border_radius,
},
layout_rect,
*parent_scroll_node_id,
*parent_clip_chain_id,
))
}
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(
name = "StackingContextClipStore::add_for_basic_shape",
skip_all,
fields(servo_profiling = true),
level = "trace",
)
)]
fn add_for_basic_shape(
&mut self,
shape: BasicShape,
layout_box: LayoutRect,
parent_scroll_node_id: &ScrollTreeNodeId,
parent_clip_chain_id: &ClipId,
) -> Option<ClipId> {
match shape {
BasicShape::Rect(rect) => {
let box_height = Au::from_f32_px(layout_box.height());
let box_width = Au::from_f32_px(layout_box.width());
let insets = LayoutSideOffsets::new(
rect.rect.0.to_used_value(box_height).to_f32_px(),
rect.rect.1.to_used_value(box_width).to_f32_px(),
rect.rect.2.to_used_value(box_height).to_f32_px(),
rect.rect.3.to_used_value(box_width).to_f32_px(),
);
// `inner_rect()` will cause an assertion failure if the insets are larger than the
// rectangle dimension.
let shape_rect = if insets.left + insets.right >= layout_box.width() ||
insets.top + insets.bottom > layout_box.height()
{
LayoutRect::from_origin_and_size(layout_box.min, LayoutSize::zero())
} else {
layout_box.to_rect().inner_rect(insets).to_box2d()
};
let corner = |corner: &style::values::computed::BorderCornerRadius| {
LayoutSize::new(
corner.0.width.0.to_used_value(box_width).to_f32_px(),
corner.0.height.0.to_used_value(box_height).to_f32_px(),
)
};
let mut radii = webrender_api::BorderRadius {
top_left: corner(&rect.round.top_left),
top_right: corner(&rect.round.top_right),
bottom_left: corner(&rect.round.bottom_left),
bottom_right: corner(&rect.round.bottom_right),
};
normalize_radii(&layout_box, &mut radii);
Some(self.add(
radii,
shape_rect,
*parent_scroll_node_id,
*parent_clip_chain_id,
))
},
BasicShape::Circle(circle) => {
let center = match circle.position {
GenericPositionOrAuto::Position(position) => position,
GenericPositionOrAuto::Auto => Position::center(),
};
let anchor_x = center
.horizontal
.to_used_value(Au::from_f32_px(layout_box.width()));
let anchor_y = center
.vertical
.to_used_value(Au::from_f32_px(layout_box.height()));
let center = layout_box
.min
.add_size(&LayoutSize::new(anchor_x.to_f32_px(), anchor_y.to_f32_px()));
let horizontal = compute_shape_radius(
center.x,
&circle.radius,
layout_box.min.x,
layout_box.max.x,
);
let vertical = compute_shape_radius(
center.y,
&circle.radius,
layout_box.min.y,
layout_box.max.y,
);
// If the value is `Length` then both values should be the same at this point.
let radius = match circle.radius {
GenericShapeRadius::FarthestSide => horizontal.max(vertical),
GenericShapeRadius::ClosestSide => horizontal.min(vertical),
GenericShapeRadius::Length(_) => horizontal,
};
let radius = LayoutSize::new(radius, radius);
let mut radii = webrender_api::BorderRadius {
top_left: radius,
top_right: radius,
bottom_left: radius,
bottom_right: radius,
};
let start = center.add_size(&-radius);
let rect = LayoutRect::from_origin_and_size(start, radius * 2.);
normalize_radii(&layout_box, &mut radii);
Some(self.add(radii, rect, *parent_scroll_node_id, *parent_clip_chain_id))
},
BasicShape::Ellipse(ellipse) => {
let center = match ellipse.position {
GenericPositionOrAuto::Position(position) => position,
GenericPositionOrAuto::Auto => Position::center(),
};
let anchor_x = center
.horizontal
.to_used_value(Au::from_f32_px(layout_box.width()));
let anchor_y = center
.vertical
.to_used_value(Au::from_f32_px(layout_box.height()));
let center = layout_box
.min
.add_size(&LayoutSize::new(anchor_x.to_f32_px(), anchor_y.to_f32_px()));
let width = compute_shape_radius(
center.x,
&ellipse.semiaxis_x,
layout_box.min.x,
layout_box.max.x,
);
let height = compute_shape_radius(
center.y,
&ellipse.semiaxis_y,
layout_box.min.y,
layout_box.max.y,
);
let mut radii = webrender_api::BorderRadius {
top_left: LayoutSize::new(width, height),
top_right: LayoutSize::new(width, height),
bottom_left: LayoutSize::new(width, height),
bottom_right: LayoutSize::new(width, height),
};
let size = LayoutSize::new(width, height);
let start = center.add_size(&-size);
let rect = LayoutRect::from_origin_and_size(start, size * 2.);
normalize_radii(&rect, &mut radii);
Some(self.add(radii, rect, *parent_scroll_node_id, *parent_clip_chain_id))
},
_ => None,
}
}
}
fn compute_shape_radius(
center: f32,
radius: &GenericShapeRadius<NonNegativeLengthPercentage>,
min_edge: f32,
max_edge: f32,
) -> f32 {
let distance_from_min_edge = (min_edge - center).abs();
let distance_from_max_edge = (max_edge - center).abs();
match radius {
GenericShapeRadius::FarthestSide => distance_from_min_edge.max(distance_from_max_edge),
GenericShapeRadius::ClosestSide => distance_from_min_edge.min(distance_from_max_edge),
GenericShapeRadius::Length(length) => length
.0
.to_used_value(Au::from_f32_px(max_edge - min_edge))
.to_f32_px(),
}
}

View file

@ -1,259 +0,0 @@
/* 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 app_units::Au;
use base::id::ScrollTreeNodeId;
use style::values::computed::basic_shape::{BasicShape, ClipPath};
use style::values::computed::length_percentage::NonNegativeLengthPercentage;
use style::values::computed::position::Position;
use style::values::generics::basic_shape::{GenericShapeRadius, ShapeBox, ShapeGeometryBox};
use style::values::generics::position::GenericPositionOrAuto;
use webrender_api::ClipChainId;
use webrender_api::units::{LayoutRect, LayoutSideOffsets, LayoutSize};
use super::{BuilderForBoxFragment, DisplayList, compute_margin_box_radius, normalize_radii};
pub(super) fn build_clip_path_clip_chain_if_necessary(
clip_path: ClipPath,
display_list: &mut DisplayList,
parent_scroll_node_id: &ScrollTreeNodeId,
parent_clip_chain_id: &ClipChainId,
fragment_builder: BuilderForBoxFragment,
) -> Option<ClipChainId> {
let geometry_box = match clip_path {
ClipPath::Shape(_, ShapeGeometryBox::ShapeBox(shape_box)) => shape_box,
ClipPath::Shape(_, ShapeGeometryBox::ElementDependent) => ShapeBox::BorderBox,
ClipPath::Box(ShapeGeometryBox::ShapeBox(shape_box)) => shape_box,
ClipPath::Box(ShapeGeometryBox::ElementDependent) => ShapeBox::BorderBox,
_ => return None,
};
let layout_rect = match geometry_box {
ShapeBox::BorderBox => fragment_builder.border_rect,
ShapeBox::ContentBox => *fragment_builder.content_rect(),
ShapeBox::PaddingBox => *fragment_builder.padding_rect(),
ShapeBox::MarginBox => *fragment_builder.margin_rect(),
};
if let ClipPath::Shape(shape, _) = clip_path {
match *shape {
BasicShape::Circle(_) | BasicShape::Ellipse(_) | BasicShape::Rect(_) => {
build_simple_shape(
*shape,
layout_rect,
parent_scroll_node_id,
parent_clip_chain_id,
display_list,
)
},
BasicShape::Polygon(_) | BasicShape::PathOrShape(_) => None,
}
} else {
Some(create_rect_clip_chain(
match geometry_box {
ShapeBox::MarginBox => compute_margin_box_radius(
fragment_builder.border_radius,
layout_rect.size(),
fragment_builder.fragment,
),
_ => fragment_builder.border_radius,
},
layout_rect,
parent_scroll_node_id,
parent_clip_chain_id,
display_list,
))
}
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(
name = "display_list::build_simple_shape",
skip_all,
fields(servo_profiling = true),
level = "trace",
)
)]
fn build_simple_shape(
shape: BasicShape,
layout_box: LayoutRect,
parent_scroll_node_id: &ScrollTreeNodeId,
parent_clip_chain_id: &ClipChainId,
display_list: &mut DisplayList,
) -> Option<ClipChainId> {
match shape {
BasicShape::Rect(rect) => {
let box_height = Au::from_f32_px(layout_box.height());
let box_width = Au::from_f32_px(layout_box.width());
let insets = LayoutSideOffsets::new(
rect.rect.0.to_used_value(box_height).to_f32_px(),
rect.rect.1.to_used_value(box_width).to_f32_px(),
rect.rect.2.to_used_value(box_height).to_f32_px(),
rect.rect.3.to_used_value(box_width).to_f32_px(),
);
// `inner_rect()` will cause an assertion failure if the insets are larger than the
// rectangle dimension.
let shape_rect = if insets.left + insets.right >= layout_box.width() ||
insets.top + insets.bottom > layout_box.height()
{
LayoutRect::from_origin_and_size(layout_box.min, LayoutSize::zero())
} else {
layout_box.to_rect().inner_rect(insets).to_box2d()
};
let corner = |corner: &style::values::computed::BorderCornerRadius| {
LayoutSize::new(
corner.0.width.0.to_used_value(box_width).to_f32_px(),
corner.0.height.0.to_used_value(box_height).to_f32_px(),
)
};
let mut radii = webrender_api::BorderRadius {
top_left: corner(&rect.round.top_left),
top_right: corner(&rect.round.top_right),
bottom_left: corner(&rect.round.bottom_left),
bottom_right: corner(&rect.round.bottom_right),
};
normalize_radii(&layout_box, &mut radii);
Some(create_rect_clip_chain(
radii,
shape_rect,
parent_scroll_node_id,
parent_clip_chain_id,
display_list,
))
},
BasicShape::Circle(circle) => {
let center = match circle.position {
GenericPositionOrAuto::Position(position) => position,
GenericPositionOrAuto::Auto => Position::center(),
};
let anchor_x = center
.horizontal
.to_used_value(Au::from_f32_px(layout_box.width()));
let anchor_y = center
.vertical
.to_used_value(Au::from_f32_px(layout_box.height()));
let center = layout_box
.min
.add_size(&LayoutSize::new(anchor_x.to_f32_px(), anchor_y.to_f32_px()));
let horizontal =
compute_shape_radius(center.x, &circle.radius, layout_box.min.x, layout_box.max.x);
let vertical =
compute_shape_radius(center.y, &circle.radius, layout_box.min.y, layout_box.max.y);
// If the value is `Length` then both values should be the same at this point.
let radius = match circle.radius {
GenericShapeRadius::FarthestSide => horizontal.max(vertical),
GenericShapeRadius::ClosestSide => horizontal.min(vertical),
GenericShapeRadius::Length(_) => horizontal,
};
let radius = LayoutSize::new(radius, radius);
let mut radii = webrender_api::BorderRadius {
top_left: radius,
top_right: radius,
bottom_left: radius,
bottom_right: radius,
};
let start = center.add_size(&-radius);
let rect = LayoutRect::from_origin_and_size(start, radius * 2.);
normalize_radii(&layout_box, &mut radii);
Some(create_rect_clip_chain(
radii,
rect,
parent_scroll_node_id,
parent_clip_chain_id,
display_list,
))
},
BasicShape::Ellipse(ellipse) => {
let center = match ellipse.position {
GenericPositionOrAuto::Position(position) => position,
GenericPositionOrAuto::Auto => Position::center(),
};
let anchor_x = center
.horizontal
.to_used_value(Au::from_f32_px(layout_box.width()));
let anchor_y = center
.vertical
.to_used_value(Au::from_f32_px(layout_box.height()));
let center = layout_box
.min
.add_size(&LayoutSize::new(anchor_x.to_f32_px(), anchor_y.to_f32_px()));
let width = compute_shape_radius(
center.x,
&ellipse.semiaxis_x,
layout_box.min.x,
layout_box.max.x,
);
let height = compute_shape_radius(
center.y,
&ellipse.semiaxis_y,
layout_box.min.y,
layout_box.max.y,
);
let mut radii = webrender_api::BorderRadius {
top_left: LayoutSize::new(width, height),
top_right: LayoutSize::new(width, height),
bottom_left: LayoutSize::new(width, height),
bottom_right: LayoutSize::new(width, height),
};
let size = LayoutSize::new(width, height);
let start = center.add_size(&-size);
let rect = LayoutRect::from_origin_and_size(start, size * 2.);
normalize_radii(&rect, &mut radii);
Some(create_rect_clip_chain(
radii,
rect,
parent_scroll_node_id,
parent_clip_chain_id,
display_list,
))
},
_ => None,
}
}
fn compute_shape_radius(
center: f32,
radius: &GenericShapeRadius<NonNegativeLengthPercentage>,
min_edge: f32,
max_edge: f32,
) -> f32 {
let distance_from_min_edge = (min_edge - center).abs();
let distance_from_max_edge = (max_edge - center).abs();
match radius {
GenericShapeRadius::FarthestSide => distance_from_min_edge.max(distance_from_max_edge),
GenericShapeRadius::ClosestSide => distance_from_min_edge.min(distance_from_max_edge),
GenericShapeRadius::Length(length) => length
.0
.to_used_value(Au::from_f32_px(max_edge - min_edge))
.to_f32_px(),
}
}
fn create_rect_clip_chain(
radii: webrender_api::BorderRadius,
rect: LayoutRect,
parent_scroll_node_id: &ScrollTreeNodeId,
parent_clip_chain_id: &ClipChainId,
display_list: &mut DisplayList,
) -> ClipChainId {
let new_clip_id = if radii.is_zero() {
display_list
.wr
.define_clip_rect(parent_scroll_node_id.spatial_id, rect)
} else {
display_list.wr.define_clip_rounded_rect(
parent_scroll_node_id.spatial_id,
webrender_api::ComplexClipRegion {
rect,
radii,
mode: webrender_api::ClipMode::Clip,
},
)
};
display_list.define_clip_chain(*parent_clip_chain_id, [new_clip_id])
}

View file

@ -125,11 +125,15 @@ impl ToWebRender for ComputedTextDecorationStyle {
type Type = LineStyle;
fn to_webrender(&self) -> Self::Type {
match *self {
ComputedTextDecorationStyle::Solid => LineStyle::Solid,
ComputedTextDecorationStyle::Solid | ComputedTextDecorationStyle::Double => {
LineStyle::Solid
},
ComputedTextDecorationStyle::Dotted => LineStyle::Dotted,
ComputedTextDecorationStyle::Dashed => LineStyle::Dashed,
ComputedTextDecorationStyle::Wavy => LineStyle::Wavy,
_ => LineStyle::Solid,
ComputedTextDecorationStyle::MozNone => {
unreachable!("Should never try to draw a moz-none text decoration")
},
}
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -2,36 +2,39 @@
* 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::any::Any;
use std::marker::PhantomData;
use std::sync::Arc;
use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
use base::id::{BrowsingContextId, PipelineId};
use html5ever::{local_name, ns};
use malloc_size_of_derive::MallocSizeOf;
use pixels::Image;
use script::layout_dom::ServoLayoutNode;
use script_layout_interface::wrapper_traits::{
LayoutDataTrait, LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode,
};
use script_layout_interface::{
HTMLCanvasDataSource, LayoutElementType, LayoutNodeType as ScriptLayoutNodeType,
GenericLayoutDataTrait, LayoutElementType, LayoutNodeType as ScriptLayoutNodeType,
};
use servo_arc::Arc as ServoArc;
use style::context::SharedStyleContext;
use style::properties::ComputedValues;
use style::selector_parser::PseudoElement;
use crate::cell::ArcRefCell;
use crate::context::LayoutContext;
use crate::flexbox::FlexLevelBox;
use crate::flow::BlockLevelBox;
use crate::flow::inline::InlineItem;
use crate::flow::inline::{InlineItem, SharedInlineStyles};
use crate::fragment_tree::Fragment;
use crate::geom::PhysicalSize;
use crate::replaced::{CanvasInfo, CanvasSource};
use crate::replaced::CanvasInfo;
use crate::table::TableLevelBox;
use crate::taffy::TaffyItemBox;
/// The data that is stored in each DOM node that is used by layout.
#[derive(Default)]
#[derive(Default, MallocSizeOf)]
pub struct InnerDOMLayoutData {
pub(super) self_box: ArcRefCell<Option<LayoutBox>>,
pub(super) pseudo_before_box: ArcRefCell<Option<LayoutBox>>,
@ -54,10 +57,11 @@ impl InnerDOMLayoutData {
}
/// A box that is stored in one of the `DOMLayoutData` slots.
#[derive(MallocSizeOf)]
pub(super) enum LayoutBox {
DisplayContents,
DisplayContents(SharedInlineStyles),
BlockLevel(ArcRefCell<BlockLevelBox>),
InlineLevel(ArcRefCell<InlineItem>),
InlineLevel(Vec<ArcRefCell<InlineItem>>),
FlexLevel(ArcRefCell<FlexLevelBox>),
TableLevelBox(TableLevelBox),
TaffyItemBox(ArcRefCell<TaffyItemBox>),
@ -66,12 +70,14 @@ pub(super) enum LayoutBox {
impl LayoutBox {
fn invalidate_cached_fragment(&self) {
match self {
LayoutBox::DisplayContents => {},
LayoutBox::DisplayContents(..) => {},
LayoutBox::BlockLevel(block_level_box) => {
block_level_box.borrow().invalidate_cached_fragment()
},
LayoutBox::InlineLevel(inline_item) => {
inline_item.borrow().invalidate_cached_fragment()
LayoutBox::InlineLevel(inline_items) => {
for inline_item in inline_items.iter() {
inline_item.borrow().invalidate_cached_fragment()
}
},
LayoutBox::FlexLevel(flex_level_box) => {
flex_level_box.borrow().invalidate_cached_fragment()
@ -85,24 +91,67 @@ impl LayoutBox {
pub(crate) fn fragments(&self) -> Vec<Fragment> {
match self {
LayoutBox::DisplayContents => vec![],
LayoutBox::DisplayContents(..) => vec![],
LayoutBox::BlockLevel(block_level_box) => block_level_box.borrow().fragments(),
LayoutBox::InlineLevel(inline_item) => inline_item.borrow().fragments(),
LayoutBox::InlineLevel(inline_items) => inline_items
.iter()
.flat_map(|inline_item| inline_item.borrow().fragments())
.collect(),
LayoutBox::FlexLevel(flex_level_box) => flex_level_box.borrow().fragments(),
LayoutBox::TaffyItemBox(taffy_item_box) => taffy_item_box.borrow().fragments(),
LayoutBox::TableLevelBox(table_box) => table_box.fragments(),
}
}
fn repair_style(
&self,
context: &SharedStyleContext,
node: &ServoLayoutNode,
new_style: &ServoArc<ComputedValues>,
) {
match self {
LayoutBox::DisplayContents(inline_shared_styles) => {
*inline_shared_styles.style.borrow_mut() = new_style.clone();
*inline_shared_styles.selected.borrow_mut() = node.to_threadsafe().selected_style();
},
LayoutBox::BlockLevel(block_level_box) => {
block_level_box
.borrow_mut()
.repair_style(context, node, new_style);
},
LayoutBox::InlineLevel(inline_items) => {
for inline_item in inline_items {
inline_item
.borrow_mut()
.repair_style(context, node, new_style);
}
},
LayoutBox::FlexLevel(flex_level_box) => flex_level_box
.borrow_mut()
.repair_style(context, node, new_style),
LayoutBox::TableLevelBox(table_level_box) => {
table_level_box.repair_style(context, node, new_style)
},
LayoutBox::TaffyItemBox(taffy_item_box) => taffy_item_box
.borrow_mut()
.repair_style(context, node, new_style),
}
}
}
/// A wrapper for [`InnerDOMLayoutData`]. This is necessary to give the entire data
/// structure interior mutability, as we will need to mutate the layout data of
/// non-mutable DOM nodes.
#[derive(Default)]
#[derive(Default, MallocSizeOf)]
pub struct DOMLayoutData(AtomicRefCell<InnerDOMLayoutData>);
// The implementation of this trait allows the data to be stored in the DOM.
impl LayoutDataTrait for DOMLayoutData {}
impl GenericLayoutDataTrait for DOMLayoutData {
fn as_any(&self) -> &dyn Any {
self
}
}
pub struct BoxSlot<'dom> {
pub(crate) slot: Option<ArcRefCell<Option<LayoutBox>>>,
@ -145,34 +194,33 @@ impl Drop for BoxSlot<'_> {
}
}
pub(crate) trait NodeExt<'dom>: 'dom + LayoutNode<'dom> {
pub(crate) trait NodeExt<'dom> {
/// Returns the image if its loaded, and its size in image pixels
/// adjusted for `image_density`.
fn as_image(self) -> Option<(Option<Arc<Image>>, PhysicalSize<f64>)>;
fn as_canvas(self) -> Option<(CanvasInfo, PhysicalSize<f64>)>;
fn as_iframe(self) -> Option<(PipelineId, BrowsingContextId)>;
fn as_video(self) -> Option<(Option<webrender_api::ImageKey>, Option<PhysicalSize<f64>>)>;
fn as_typeless_object_with_data_attribute(self) -> Option<String>;
fn style(self, context: &LayoutContext) -> ServoArc<ComputedValues>;
fn as_image(&self) -> Option<(Option<Arc<Image>>, PhysicalSize<f64>)>;
fn as_canvas(&self) -> Option<(CanvasInfo, PhysicalSize<f64>)>;
fn as_iframe(&self) -> Option<(PipelineId, BrowsingContextId)>;
fn as_video(&self) -> Option<(Option<webrender_api::ImageKey>, Option<PhysicalSize<f64>>)>;
fn as_typeless_object_with_data_attribute(&self) -> Option<String>;
fn style(&self, context: &SharedStyleContext) -> ServoArc<ComputedValues>;
fn layout_data_mut(self) -> AtomicRefMut<'dom, InnerDOMLayoutData>;
fn layout_data(self) -> Option<AtomicRef<'dom, InnerDOMLayoutData>>;
fn layout_data_mut(&self) -> AtomicRefMut<'dom, InnerDOMLayoutData>;
fn layout_data(&self) -> Option<AtomicRef<'dom, InnerDOMLayoutData>>;
fn element_box_slot(&self) -> BoxSlot<'dom>;
fn pseudo_element_box_slot(&self, which: PseudoElement) -> BoxSlot<'dom>;
fn unset_pseudo_element_box(self, which: PseudoElement);
fn unset_pseudo_element_box(&self, which: PseudoElement);
/// Remove boxes for the element itself, and its `:before` and `:after` if any.
fn unset_all_boxes(self);
fn unset_all_boxes(&self);
fn fragments_for_pseudo(&self, pseudo_element: Option<PseudoElement>) -> Vec<Fragment>;
fn invalidate_cached_fragment(self);
fn invalidate_cached_fragment(&self);
fn repair_style(&self, context: &SharedStyleContext);
}
impl<'dom, LayoutNodeType> NodeExt<'dom> for LayoutNodeType
where
LayoutNodeType: 'dom + LayoutNode<'dom>,
{
fn as_image(self) -> Option<(Option<Arc<Image>>, PhysicalSize<f64>)> {
impl<'dom> NodeExt<'dom> for ServoLayoutNode<'dom> {
fn as_image(&self) -> Option<(Option<Arc<Image>>, PhysicalSize<f64>)> {
let node = self.to_threadsafe();
let (resource, metadata) = node.image_data()?;
let (width, height) = resource
@ -188,7 +236,7 @@ where
Some((resource, PhysicalSize::new(width, height)))
}
fn as_video(self) -> Option<(Option<webrender_api::ImageKey>, Option<PhysicalSize<f64>>)> {
fn as_video(&self) -> Option<(Option<webrender_api::ImageKey>, Option<PhysicalSize<f64>>)> {
let node = self.to_threadsafe();
let data = node.media_data()?;
let natural_size = if let Some(frame) = data.current_frame {
@ -203,22 +251,17 @@ where
))
}
fn as_canvas(self) -> Option<(CanvasInfo, PhysicalSize<f64>)> {
fn as_canvas(&self) -> Option<(CanvasInfo, PhysicalSize<f64>)> {
let node = self.to_threadsafe();
let canvas_data = node.canvas_data()?;
let source = match canvas_data.source {
HTMLCanvasDataSource::WebGL(texture_id) => CanvasSource::WebGL(texture_id),
HTMLCanvasDataSource::Image(image_key) => CanvasSource::Image(image_key),
HTMLCanvasDataSource::WebGPU(image_key) => CanvasSource::WebGPU(image_key),
HTMLCanvasDataSource::Empty => CanvasSource::Empty,
};
let source = canvas_data.source;
Some((
CanvasInfo { source },
PhysicalSize::new(canvas_data.width.into(), canvas_data.height.into()),
))
}
fn as_iframe(self) -> Option<(PipelineId, BrowsingContextId)> {
fn as_iframe(&self) -> Option<(PipelineId, BrowsingContextId)> {
let node = self.to_threadsafe();
match (node.iframe_pipeline_id(), node.iframe_browsing_context_id()) {
(Some(pipeline_id), Some(browsing_context_id)) => {
@ -228,8 +271,10 @@ where
}
}
fn as_typeless_object_with_data_attribute(self) -> Option<String> {
if self.type_id() != ScriptLayoutNodeType::Element(LayoutElementType::HTMLObjectElement) {
fn as_typeless_object_with_data_attribute(&self) -> Option<String> {
if LayoutNode::type_id(self) !=
ScriptLayoutNodeType::Element(LayoutElementType::HTMLObjectElement)
{
return None;
}
@ -245,25 +290,31 @@ where
.map(|string| string.to_owned())
}
fn style(self, context: &LayoutContext) -> ServoArc<ComputedValues> {
self.to_threadsafe().style(context.shared_context())
fn style(&self, context: &SharedStyleContext) -> ServoArc<ComputedValues> {
self.to_threadsafe().style(context)
}
fn layout_data_mut(self) -> AtomicRefMut<'dom, InnerDOMLayoutData> {
if LayoutNode::layout_data(&self).is_none() {
fn layout_data_mut(&self) -> AtomicRefMut<'dom, InnerDOMLayoutData> {
if LayoutNode::layout_data(self).is_none() {
self.initialize_layout_data::<DOMLayoutData>();
}
LayoutNode::layout_data(&self)
LayoutNode::layout_data(self)
.unwrap()
.as_any()
.downcast_ref::<DOMLayoutData>()
.unwrap()
.0
.borrow_mut()
}
fn layout_data(self) -> Option<AtomicRef<'dom, InnerDOMLayoutData>> {
LayoutNode::layout_data(&self)
.map(|data| data.downcast_ref::<DOMLayoutData>().unwrap().0.borrow())
fn layout_data(&self) -> Option<AtomicRef<'dom, InnerDOMLayoutData>> {
LayoutNode::layout_data(self).map(|data| {
data.as_any()
.downcast_ref::<DOMLayoutData>()
.unwrap()
.0
.borrow()
})
}
fn element_box_slot(&self) -> BoxSlot<'dom> {
@ -284,7 +335,7 @@ where
BoxSlot::new(cell.clone())
}
fn unset_pseudo_element_box(self, pseudo_element_type: PseudoElement) {
fn unset_pseudo_element_box(&self, pseudo_element_type: PseudoElement) {
let data = self.layout_data_mut();
let cell = match pseudo_element_type {
PseudoElement::Before => &data.pseudo_before_box,
@ -298,7 +349,7 @@ where
*cell.borrow_mut() = None;
}
fn unset_all_boxes(self) {
fn unset_all_boxes(&self) {
let data = self.layout_data_mut();
*data.self_box.borrow_mut() = None;
*data.pseudo_before_box.borrow_mut() = None;
@ -308,7 +359,7 @@ where
// for DOM descendants of elements with `display: none`.
}
fn invalidate_cached_fragment(self) {
fn invalidate_cached_fragment(&self) {
let data = self.layout_data_mut();
if let Some(data) = data.self_box.borrow_mut().as_mut() {
data.invalidate_cached_fragment();
@ -316,7 +367,7 @@ where
}
fn fragments_for_pseudo(&self, pseudo_element: Option<PseudoElement>) -> Vec<Fragment> {
NodeExt::layout_data(*self)
NodeExt::layout_data(self)
.and_then(|layout_data| {
layout_data
.for_pseudo(pseudo_element)
@ -325,4 +376,30 @@ where
})
.unwrap_or_default()
}
fn repair_style(&self, context: &SharedStyleContext) {
let data = self.layout_data_mut();
if let Some(layout_object) = &*data.self_box.borrow() {
let style = self.to_threadsafe().style(context);
layout_object.repair_style(context, self, &style);
}
if let Some(layout_object) = &*data.pseudo_before_box.borrow() {
if let Some(node) = self.to_threadsafe().with_pseudo(PseudoElement::Before) {
layout_object.repair_style(context, self, &node.style(context));
}
}
if let Some(layout_object) = &*data.pseudo_after_box.borrow() {
if let Some(node) = self.to_threadsafe().with_pseudo(PseudoElement::After) {
layout_object.repair_style(context, self, &node.style(context));
}
}
if let Some(layout_object) = &*data.pseudo_marker_box.borrow() {
if let Some(node) = self.to_threadsafe().with_pseudo(PseudoElement::Marker) {
layout_object.repair_style(context, self, &node.style(context));
}
}
}
}

View file

@ -8,11 +8,14 @@ use std::iter::FusedIterator;
use fonts::ByteIndex;
use html5ever::{LocalName, local_name};
use range::Range;
use script_layout_interface::wrapper_traits::{ThreadSafeLayoutElement, ThreadSafeLayoutNode};
use script::layout_dom::ServoLayoutNode;
use script_layout_interface::wrapper_traits::{
LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode,
};
use script_layout_interface::{LayoutElementType, LayoutNodeType};
use selectors::Element as SelectorsElement;
use servo_arc::Arc as ServoArc;
use style::dom::{TElement, TShadowRoot};
use style::dom::{NodeInfo, TElement, TNode, TShadowRoot};
use style::properties::ComputedValues;
use style::selector_parser::PseudoElement;
use style::values::generics::counters::{Content, ContentItem};
@ -20,6 +23,7 @@ use style::values::specified::Quotes;
use crate::context::LayoutContext;
use crate::dom::{BoxSlot, LayoutBox, NodeExt};
use crate::flow::inline::SharedInlineStyles;
use crate::fragment_tree::{BaseFragmentInfo, FragmentFlags, Tag};
use crate::quotes::quotes_for_lang;
use crate::replaced::ReplacedContents;
@ -28,15 +32,15 @@ use crate::style_ext::{Display, DisplayGeneratingBox, DisplayInside, DisplayOuts
/// A data structure used to pass and store related layout information together to
/// avoid having to repeat the same arguments in argument lists.
#[derive(Clone)]
pub(crate) struct NodeAndStyleInfo<Node> {
pub node: Node,
pub(crate) struct NodeAndStyleInfo<'dom> {
pub node: ServoLayoutNode<'dom>,
pub pseudo_element_type: Option<PseudoElement>,
pub style: ServoArc<ComputedValues>,
}
impl<'dom, Node: NodeExt<'dom>> NodeAndStyleInfo<Node> {
impl<'dom> NodeAndStyleInfo<'dom> {
fn new_with_pseudo(
node: Node,
node: ServoLayoutNode<'dom>,
pseudo_element_type: PseudoElement,
style: ServoArc<ComputedValues>,
) -> Self {
@ -47,7 +51,7 @@ impl<'dom, Node: NodeExt<'dom>> NodeAndStyleInfo<Node> {
}
}
pub(crate) fn new(node: Node, style: ServoArc<ComputedValues>) -> Self {
pub(crate) fn new(node: ServoLayoutNode<'dom>, style: ServoArc<ComputedValues>) -> Self {
Self {
node,
pseudo_element_type: None,
@ -86,11 +90,8 @@ impl<'dom, Node: NodeExt<'dom>> NodeAndStyleInfo<Node> {
}
}
impl<'dom, Node> From<&NodeAndStyleInfo<Node>> for BaseFragmentInfo
where
Node: NodeExt<'dom>,
{
fn from(info: &NodeAndStyleInfo<Node>) -> Self {
impl<'dom> From<&NodeAndStyleInfo<'dom>> for BaseFragmentInfo {
fn from(info: &NodeAndStyleInfo<'dom>) -> Self {
let node = info.node;
let pseudo = info.pseudo_element_type;
let threadsafe_node = node.to_threadsafe();
@ -174,43 +175,37 @@ pub(super) enum PseudoElementContentItem {
Replaced(ReplacedContents),
}
pub(super) trait TraversalHandler<'dom, Node>
where
Node: 'dom,
{
fn handle_text(&mut self, info: &NodeAndStyleInfo<Node>, text: Cow<'dom, str>);
pub(super) trait TraversalHandler<'dom> {
fn handle_text(&mut self, info: &NodeAndStyleInfo<'dom>, text: Cow<'dom, str>);
/// Or pseudo-element
fn handle_element(
&mut self,
info: &NodeAndStyleInfo<Node>,
info: &NodeAndStyleInfo<'dom>,
display: DisplayGeneratingBox,
contents: Contents,
box_slot: BoxSlot<'dom>,
);
/// Notify the handler that we are about to recurse into a `display: contents` element.
fn enter_display_contents(&mut self, _: SharedInlineStyles) {}
/// Notify the handler that we have finished a `display: contents` element.
fn leave_display_contents(&mut self) {}
}
fn traverse_children_of<'dom, Node>(
parent_element: Node,
fn traverse_children_of<'dom>(
parent_element: ServoLayoutNode<'dom>,
context: &LayoutContext,
handler: &mut impl TraversalHandler<'dom, Node>,
) where
Node: NodeExt<'dom>,
{
handler: &mut impl TraversalHandler<'dom>,
) {
traverse_eager_pseudo_element(PseudoElement::Before, parent_element, context, handler);
let is_text_input_element = matches!(
parent_element.type_id(),
LayoutNodeType::Element(LayoutElementType::HTMLInputElement)
);
let is_textarea_element = matches!(
parent_element.type_id(),
LayoutNodeType::Element(LayoutElementType::HTMLTextAreaElement)
);
if is_text_input_element || is_textarea_element {
let info = NodeAndStyleInfo::new(parent_element, parent_element.style(context));
if parent_element.is_text_input() {
let info = NodeAndStyleInfo::new(
parent_element,
parent_element.style(context.shared_context()),
);
let node_text_content = parent_element.to_threadsafe().node_text_content();
if node_text_content.is_empty() {
// The addition of zero-width space here forces the text input to have an inline formatting
@ -224,12 +219,10 @@ fn traverse_children_of<'dom, Node>(
} else {
handler.handle_text(&info, node_text_content);
}
}
if !is_text_input_element && !is_textarea_element {
} else {
for child in iter_child_nodes(parent_element) {
if child.is_text_node() {
let info = NodeAndStyleInfo::new(child, child.style(context));
let info = NodeAndStyleInfo::new(child, child.style(context.shared_context()));
handler.handle_text(&info, child.to_threadsafe().node_text_content());
} else if child.is_element() {
traverse_element(child, context, handler);
@ -240,19 +233,17 @@ fn traverse_children_of<'dom, Node>(
traverse_eager_pseudo_element(PseudoElement::After, parent_element, context, handler);
}
fn traverse_element<'dom, Node>(
element: Node,
fn traverse_element<'dom>(
element: ServoLayoutNode<'dom>,
context: &LayoutContext,
handler: &mut impl TraversalHandler<'dom, Node>,
) where
Node: NodeExt<'dom>,
{
handler: &mut impl TraversalHandler<'dom>,
) {
// Clear any existing pseudo-element box slot, because markers are not handled like
// `::before`` and `::after`. They are processed during box tree creation.
element.unset_pseudo_element_box(PseudoElement::Marker);
let replaced = ReplacedContents::for_element(element, context);
let style = element.style(context);
let style = element.style(context.shared_context());
match Display::from(style.get_box().display) {
Display::None => element.unset_all_boxes(),
Display::Contents => {
@ -261,8 +252,15 @@ fn traverse_element<'dom, Node>(
// <https://drafts.csswg.org/css-display-3/#valdef-display-contents>
element.unset_all_boxes()
} else {
element.element_box_slot().set(LayoutBox::DisplayContents);
traverse_children_of(element, context, handler)
let shared_inline_styles: SharedInlineStyles =
(&NodeAndStyleInfo::new(element, style)).into();
element
.element_box_slot()
.set(LayoutBox::DisplayContents(shared_inline_styles.clone()));
handler.enter_display_contents(shared_inline_styles);
traverse_children_of(element, context, handler);
handler.leave_display_contents();
}
},
Display::GeneratingBox(display) => {
@ -286,14 +284,12 @@ fn traverse_element<'dom, Node>(
}
}
fn traverse_eager_pseudo_element<'dom, Node>(
fn traverse_eager_pseudo_element<'dom>(
pseudo_element_type: PseudoElement,
node: Node,
node: ServoLayoutNode<'dom>,
context: &LayoutContext,
handler: &mut impl TraversalHandler<'dom, Node>,
) where
Node: NodeExt<'dom>,
{
handler: &mut impl TraversalHandler<'dom>,
) {
assert!(pseudo_element_type.is_eager());
// First clear any old contents from the node.
@ -317,8 +313,12 @@ fn traverse_eager_pseudo_element<'dom, Node>(
Display::Contents => {
let items = generate_pseudo_element_content(&info.style, node, context);
let box_slot = node.pseudo_element_box_slot(pseudo_element_type);
box_slot.set(LayoutBox::DisplayContents);
let shared_inline_styles: SharedInlineStyles = (&info).into();
box_slot.set(LayoutBox::DisplayContents(shared_inline_styles.clone()));
handler.enter_display_contents(shared_inline_styles);
traverse_pseudo_element_contents(&info, context, handler, items);
handler.leave_display_contents();
},
Display::GeneratingBox(display) => {
let items = generate_pseudo_element_content(&info.style, node, context);
@ -329,14 +329,12 @@ fn traverse_eager_pseudo_element<'dom, Node>(
}
}
fn traverse_pseudo_element_contents<'dom, Node>(
info: &NodeAndStyleInfo<Node>,
fn traverse_pseudo_element_contents<'dom>(
info: &NodeAndStyleInfo<'dom>,
context: &LayoutContext,
handler: &mut impl TraversalHandler<'dom, Node>,
handler: &mut impl TraversalHandler<'dom>,
items: Vec<PseudoElementContentItem>,
) where
Node: NodeExt<'dom>,
{
) {
let mut anonymous_info = None;
for item in items {
match item {
@ -396,14 +394,12 @@ impl std::convert::TryFrom<Contents> for NonReplacedContents {
}
impl NonReplacedContents {
pub(crate) fn traverse<'dom, Node>(
pub(crate) fn traverse<'dom>(
self,
context: &LayoutContext,
info: &NodeAndStyleInfo<Node>,
handler: &mut impl TraversalHandler<'dom, Node>,
) where
Node: NodeExt<'dom>,
{
info: &NodeAndStyleInfo<'dom>,
handler: &mut impl TraversalHandler<'dom>,
) {
match self {
NonReplacedContents::OfElement | NonReplacedContents::OfTextControl => {
traverse_children_of(info.node, context, handler)
@ -427,14 +423,11 @@ where
}
/// <https://www.w3.org/TR/CSS2/generate.html#propdef-content>
fn generate_pseudo_element_content<'dom, Node>(
fn generate_pseudo_element_content(
pseudo_element_style: &ComputedValues,
element: Node,
element: ServoLayoutNode<'_>,
context: &LayoutContext,
) -> Vec<PseudoElementContentItem>
where
Node: NodeExt<'dom>,
{
) -> Vec<PseudoElementContentItem> {
match &pseudo_element_style.get_counters().content {
Content::Items(items) => {
let mut vec = vec![];
@ -517,18 +510,14 @@ where
}
}
pub enum ChildNodeIterator<Node> {
pub enum ChildNodeIterator<'dom> {
/// Iterating over the children of a node
Node(Option<Node>),
Node(Option<ServoLayoutNode<'dom>>),
/// Iterating over the assigned nodes of a `HTMLSlotElement`
Slottables(<Vec<Node> as IntoIterator>::IntoIter),
Slottables(<Vec<ServoLayoutNode<'dom>> as IntoIterator>::IntoIter),
}
#[allow(clippy::unnecessary_to_owned)] // Clippy is wrong.
pub(crate) fn iter_child_nodes<'dom, Node>(parent: Node) -> ChildNodeIterator<Node>
where
Node: NodeExt<'dom>,
{
pub(crate) fn iter_child_nodes(parent: ServoLayoutNode<'_>) -> ChildNodeIterator<'_> {
if let Some(element) = parent.as_element() {
if let Some(shadow) = element.shadow_root() {
return iter_child_nodes(shadow.as_node());
@ -536,6 +525,7 @@ where
let slotted_nodes = element.slotted_nodes();
if !slotted_nodes.is_empty() {
#[allow(clippy::unnecessary_to_owned)] // Clippy is wrong.
return ChildNodeIterator::Slottables(slotted_nodes.to_owned().into_iter());
}
}
@ -544,11 +534,8 @@ where
ChildNodeIterator::Node(first)
}
impl<'dom, Node> Iterator for ChildNodeIterator<Node>
where
Node: NodeExt<'dom>,
{
type Item = Node;
impl<'dom> Iterator for ChildNodeIterator<'dom> {
type Item = ServoLayoutNode<'dom>;
fn next(&mut self) -> Option<Self::Item> {
match self {
@ -562,4 +549,4 @@ where
}
}
impl<'dom, Node> FusedIterator for ChildNodeIterator<Node> where Node: NodeExt<'dom> {}
impl FusedIterator for ChildNodeIterator<'_> {}

View file

@ -29,8 +29,10 @@ use super::{FlexContainer, FlexContainerConfig, FlexItemBox, FlexLevelBox};
use crate::cell::ArcRefCell;
use crate::context::LayoutContext;
use crate::formatting_contexts::{Baselines, IndependentFormattingContextContents};
use crate::fragment_tree::{BoxFragment, CollapsedBlockMargins, Fragment, FragmentFlags};
use crate::geom::{AuOrAuto, LogicalRect, LogicalSides, LogicalVec2, Size, Sizes};
use crate::fragment_tree::{
BoxFragment, CollapsedBlockMargins, Fragment, FragmentFlags, SpecificLayoutInfo,
};
use crate::geom::{AuOrAuto, LazySize, LogicalRect, LogicalSides, LogicalVec2, Size, Sizes};
use crate::layout_box_base::CacheableLayoutResult;
use crate::positioned::{
AbsolutelyPositionedBox, PositioningContext, PositioningContextLength, relative_adjustement,
@ -49,7 +51,6 @@ use crate::{
struct FlexContext<'a> {
config: FlexContainerConfig,
layout_context: &'a LayoutContext<'a>,
positioning_context: &'a mut PositioningContext,
containing_block: &'a ContainingBlock<'a>, // For items
container_inner_size_constraint: FlexRelativeVec2<SizeConstraint>,
}
@ -143,6 +144,9 @@ struct FlexItemLayoutResult {
// Whether or not this layout had a child that dependeded on block constraints.
has_child_which_depends_on_block_constraints: bool,
// The specific layout info that this flex item had.
specific_layout_info: Option<SpecificLayoutInfo>,
}
impl FlexItemLayoutResult {
@ -296,7 +300,8 @@ impl FlexLineItem<'_> {
.sides_to_flow_relative(item_margin)
.to_physical(container_writing_mode),
None, /* clearance */
);
)
.with_specific_layout_info(self.layout_result.specific_layout_info);
// If this flex item establishes a containing block for absolutely-positioned
// descendants, then lay out any relevant absolutely-positioned children. This
@ -412,9 +417,8 @@ struct DesiredFlexFractionAndGrowOrShrinkFactor {
#[derive(Default)]
struct FlexItemBoxInlineContentSizesInfo {
outer_flex_base_size: Au,
content_min_main_size: Au,
content_max_main_size: Option<Au>,
pbm_auto_is_zero: FlexRelativeVec2<Au>,
outer_min_main_size: Au,
outer_max_main_size: Option<Au>,
min_flex_factors: DesiredFlexFractionAndGrowOrShrinkFactor,
max_flex_factors: DesiredFlexFractionAndGrowOrShrinkFactor,
min_content_main_size_for_multiline_container: Au,
@ -578,9 +582,8 @@ impl FlexContainer {
for FlexItemBoxInlineContentSizesInfo {
outer_flex_base_size,
content_min_main_size,
content_max_main_size,
pbm_auto_is_zero,
outer_min_main_size,
outer_max_main_size,
min_flex_factors,
max_flex_factors,
min_content_main_size_for_multiline_container,
@ -590,16 +593,13 @@ impl FlexContainer {
// > 4. Add each items flex base size to the product of its flex grow factor (scaled flex shrink
// > factor, if shrinking) and the chosen flex fraction, then clamp that result by the max main size
// > floored by the min main size.
let outer_min_main_size = *content_min_main_size + pbm_auto_is_zero.main;
let outer_max_main_size = content_max_main_size.map(|v| v + pbm_auto_is_zero.main);
// > 5. The flex containers max-content size is the largest sum (among all the lines) of the
// > afore-calculated sizes of all items within a single line.
container_max_content_size += (*outer_flex_base_size +
Au::from_f32_px(
max_flex_factors.flex_grow_or_shrink_factor * chosen_max_flex_fraction,
))
.clamp_between_extremums(outer_min_main_size, outer_max_main_size);
.clamp_between_extremums(*outer_min_main_size, *outer_max_main_size);
// > The min-content main size of a single-line flex container is calculated
// > identically to the max-content main size, except that the flex items
@ -616,7 +616,7 @@ impl FlexContainer {
Au::from_f32_px(
min_flex_factors.flex_grow_or_shrink_factor * chosen_min_flex_fraction,
))
.clamp_between_extremums(outer_min_main_size, outer_max_main_size);
.clamp_between_extremums(*outer_min_main_size, *outer_max_main_size);
} else {
container_min_content_size
.max_assign(*min_content_main_size_for_multiline_container);
@ -650,6 +650,7 @@ impl FlexContainer {
positioning_context: &mut PositioningContext,
containing_block: &ContainingBlock,
depends_on_block_constraints: bool,
lazy_block_size: &LazySize,
) -> CacheableLayoutResult {
let depends_on_block_constraints =
depends_on_block_constraints || self.config.flex_direction == FlexDirection::Column;
@ -657,7 +658,6 @@ impl FlexContainer {
let mut flex_context = FlexContext {
config: self.config.clone(),
layout_context,
positioning_context,
containing_block,
// https://drafts.csswg.org/css-flexbox/#definite-sizes
container_inner_size_constraint: self.config.flex_axis.vec2_to_flex_relative(
@ -672,14 +672,13 @@ impl FlexContainer {
// https://drafts.csswg.org/css-flexbox/#algo-main-container
let container_main_size = match self.config.flex_axis {
FlexAxis::Row => containing_block.size.inline,
FlexAxis::Column => match containing_block.size.block {
SizeConstraint::Definite(size) => size,
SizeConstraint::MinMax(min, max) => self
.main_content_sizes(layout_context, &containing_block.into(), || &flex_context)
FlexAxis::Column => lazy_block_size.resolve(|| {
let mut containing_block = IndefiniteContainingBlock::from(containing_block);
containing_block.size.block = None;
self.main_content_sizes(layout_context, &containing_block, || &flex_context)
.sizes
.max_content
.clamp_between_extremums(min, max),
},
}),
};
// Actual length may be less, but we guess that usually not by a lot
@ -762,30 +761,23 @@ impl FlexContainer {
.map(|layout| layout.line_size.cross)
.sum::<Au>() +
cross_gap * (line_count as i32 - 1);
let content_block_size = match self.config.flex_axis {
FlexAxis::Row => content_cross_size,
FlexAxis::Column => container_main_size,
};
// https://drafts.csswg.org/css-flexbox/#algo-cross-container
let container_cross_size = match flex_context.container_inner_size_constraint.cross {
SizeConstraint::Definite(cross_size) => cross_size,
SizeConstraint::MinMax(min, max) => {
content_cross_size.clamp_between_extremums(min, max)
},
let container_cross_size = match self.config.flex_axis {
FlexAxis::Row => lazy_block_size.resolve(|| content_cross_size),
FlexAxis::Column => containing_block.size.inline,
};
let container_size = FlexRelativeVec2 {
main: container_main_size,
cross: container_cross_size,
};
let content_block_size = flex_context
.config
.flex_axis
.vec2_to_flow_relative(container_size)
.block;
let mut remaining_free_cross_space =
match flex_context.container_inner_size_constraint.cross {
SizeConstraint::Definite(cross_size) => cross_size - content_cross_size,
_ => Au::zero(),
};
let mut remaining_free_cross_space = container_cross_size - content_cross_size;
// Implement fallback alignment.
//
@ -1774,16 +1766,8 @@ impl FlexItem<'_> {
non_stretch_layout_result: Option<&mut FlexItemLayoutResult>,
) -> Option<FlexItemLayoutResult> {
let containing_block = flex_context.containing_block;
let mut positioning_context = PositioningContext::new_for_style(self.box_.style())
.unwrap_or_else(|| {
PositioningContext::new_for_subtree(
flex_context
.positioning_context
.collects_for_nearest_positioned_ancestor(),
)
});
let independent_formatting_context = &self.box_.independent_formatting_context;
let mut positioning_context = PositioningContext::default();
let item_writing_mode = independent_formatting_context.style().writing_mode;
let item_is_horizontal = item_writing_mode.is_horizontal();
let flex_axis = flex_context.config.flex_axis;
@ -1920,6 +1904,7 @@ impl FlexItem<'_> {
// size can differ from the hypothetical cross size, we should defer
// synthesizing until needed.
baseline_relative_to_margin_box: None,
specific_layout_info: None,
})
},
IndependentFormattingContextContents::NonReplaced(non_replaced) => {
@ -1939,6 +1924,29 @@ impl FlexItem<'_> {
}
}
let lazy_block_size = if !cross_axis_is_item_block_axis {
used_main_size.into()
} else if let Some(cross_size) = used_cross_size_override {
cross_size.into()
} else {
// This means that an auto size with stretch alignment will behave different than
// a stretch size. That's not what the spec says, but matches other browsers.
// To be discussed in https://github.com/w3c/csswg-drafts/issues/11784.
let stretch_size = containing_block
.size
.block
.to_definite()
.map(|size| Au::zero().max(size - self.pbm_auto_is_zero.cross));
LazySize::new(
&self.content_cross_sizes,
Direction::Block,
Size::FitContent,
Au::zero,
stretch_size,
self.is_table(),
)
};
let layout = non_replaced.layout(
flex_context.layout_context,
&mut positioning_context,
@ -1948,12 +1956,14 @@ impl FlexItem<'_> {
flex_axis == FlexAxis::Column ||
self.cross_size_stretches_to_line ||
self.depends_on_block_constraints,
&lazy_block_size,
);
let CacheableLayoutResult {
fragments,
content_block_size,
baselines: content_box_baselines,
depends_on_block_constraints,
specific_layout_info,
..
} = layout;
@ -1964,22 +1974,7 @@ impl FlexItem<'_> {
});
let hypothetical_cross_size = if cross_axis_is_item_block_axis {
// This means that an auto size with stretch alignment will behave different than
// a stretch size. That's not what the spec says, but matches other browsers.
// To be discussed in https://github.com/w3c/csswg-drafts/issues/11784.
let stretch_size = containing_block
.size
.block
.to_definite()
.map(|size| Au::zero().max(size - self.pbm_auto_is_zero.cross));
self.content_cross_sizes.resolve(
Direction::Block,
Size::FitContent,
Au::zero,
stretch_size,
|| content_block_size.into(),
self.is_table(),
)
lazy_block_size.resolve(|| content_block_size)
} else {
inline_size
};
@ -2022,6 +2017,7 @@ impl FlexItem<'_> {
containing_block_block_size: item_as_containing_block.size.block,
depends_on_block_constraints,
has_child_which_depends_on_block_constraints,
specific_layout_info,
})
},
}
@ -2457,6 +2453,8 @@ impl FlexItemBox {
};
let outer_flex_base_size = flex_base_size + pbm_auto_is_zero.main;
let outer_min_main_size = content_min_main_size + pbm_auto_is_zero.main;
let outer_max_main_size = content_max_main_size.map(|v| v + pbm_auto_is_zero.main);
let max_flex_factors = self.desired_flex_factors_for_preferred_width(
content_contribution_sizes.max_content,
flex_base_size,
@ -2482,20 +2480,19 @@ impl FlexItemBox {
content_contribution_sizes.min_content;
let style_position = &self.style().get_position();
if style_position.flex_grow.is_zero() {
min_content_main_size_for_multiline_container.min_assign(flex_base_size);
min_content_main_size_for_multiline_container.min_assign(outer_flex_base_size);
}
if style_position.flex_shrink.is_zero() {
min_content_main_size_for_multiline_container.max_assign(flex_base_size);
min_content_main_size_for_multiline_container.max_assign(outer_flex_base_size);
}
min_content_main_size_for_multiline_container =
min_content_main_size_for_multiline_container
.clamp_between_extremums(content_min_main_size, content_max_main_size);
.clamp_between_extremums(outer_min_main_size, outer_max_main_size);
FlexItemBoxInlineContentSizesInfo {
outer_flex_base_size,
content_min_main_size,
content_max_main_size,
pbm_auto_is_zero,
outer_min_main_size,
outer_max_main_size,
min_flex_factors,
max_flex_factors,
min_content_main_size_for_multiline_container,
@ -2616,15 +2613,7 @@ impl FlexItemBox {
cross_size_stretches_to_container_size: bool,
intrinsic_sizing_mode: IntrinsicSizingMode,
) -> Au {
let mut positioning_context = PositioningContext::new_for_style(self.style())
.unwrap_or_else(|| {
PositioningContext::new_for_subtree(
flex_context
.positioning_context
.collects_for_nearest_positioned_ancestor(),
)
});
let mut positioning_context = PositioningContext::default();
let style = self.independent_formatting_context.style();
match &self.independent_formatting_context.contents {
IndependentFormattingContextContents::Replaced(replaced) => {
@ -2696,6 +2685,7 @@ impl FlexItemBox {
flex_context.containing_block,
&self.independent_formatting_context.base,
false, /* depends_on_block_constraints */
&LazySize::intrinsic(),
)
.content_block_size
};

View file

@ -4,7 +4,9 @@
use geom::{FlexAxis, MainStartCrossStart};
use malloc_size_of_derive::MallocSizeOf;
use script::layout_dom::ServoLayoutNode;
use servo_arc::Arc as ServoArc;
use style::context::SharedStyleContext;
use style::logical_geometry::WritingMode;
use style::properties::ComputedValues;
use style::properties::longhands::align_items::computed_value::T as AlignItems;
@ -17,7 +19,7 @@ use crate::PropagatedBoxTreeData;
use crate::cell::ArcRefCell;
use crate::construct_modern::{ModernContainerBuilder, ModernItemKind};
use crate::context::LayoutContext;
use crate::dom::{LayoutBox, NodeExt};
use crate::dom::LayoutBox;
use crate::dom_traversal::{NodeAndStyleInfo, NonReplacedContents};
use crate::formatting_contexts::IndependentFormattingContext;
use crate::fragment_tree::{BaseFragmentInfo, Fragment};
@ -90,7 +92,6 @@ impl FlexContainerConfig {
pub(crate) struct FlexContainer {
children: Vec<ArcRefCell<FlexLevelBox>>,
#[conditional_malloc_size_of]
style: ServoArc<ComputedValues>,
/// The configuration of this [`FlexContainer`].
@ -98,14 +99,13 @@ pub(crate) struct FlexContainer {
}
impl FlexContainer {
pub fn construct<'dom>(
pub fn construct(
context: &LayoutContext,
info: &NodeAndStyleInfo<impl NodeExt<'dom>>,
info: &NodeAndStyleInfo<'_>,
contents: NonReplacedContents,
propagated_data: PropagatedBoxTreeData,
) -> Self {
let mut builder =
ModernContainerBuilder::new(context, info, propagated_data.union(&info.style));
let mut builder = ModernContainerBuilder::new(context, info, propagated_data);
contents.traverse(context, info, &mut builder);
let items = builder.finish();
@ -137,6 +137,11 @@ impl FlexContainer {
config: FlexContainerConfig::new(&info.style),
}
}
pub(crate) fn repair_style(&mut self, new_style: &ServoArc<ComputedValues>) {
self.config = FlexContainerConfig::new(new_style);
self.style = new_style.clone();
}
}
#[derive(Debug, MallocSizeOf)]
@ -146,6 +151,23 @@ pub(crate) enum FlexLevelBox {
}
impl FlexLevelBox {
pub(crate) fn repair_style(
&mut self,
context: &SharedStyleContext,
node: &ServoLayoutNode,
new_style: &ServoArc<ComputedValues>,
) {
match self {
FlexLevelBox::FlexItem(flex_item_box) => flex_item_box
.independent_formatting_context
.repair_style(context, node, new_style),
FlexLevelBox::OutOfFlowAbsolutelyPositionedBox(positioned_box) => positioned_box
.borrow_mut()
.context
.repair_style(context, node, new_style),
}
}
pub(crate) fn invalidate_cached_fragment(&self) {
match self {
FlexLevelBox::FlexItem(flex_item_box) => flex_item_box

View file

@ -13,9 +13,9 @@ use style::selector_parser::PseudoElement;
use style::str::char_is_whitespace;
use super::OutsideMarker;
use super::inline::InlineFormattingContext;
use super::inline::construct::InlineFormattingContextBuilder;
use super::inline::inline_box::InlineBox;
use super::inline::{InlineFormattingContext, SharedInlineStyles};
use crate::PropagatedBoxTreeData;
use crate::cell::ArcRefCell;
use crate::context::LayoutContext;
@ -33,16 +33,13 @@ use crate::style_ext::{ComputedValuesExt, DisplayGeneratingBox, DisplayInside, D
use crate::table::{AnonymousTableContent, Table};
impl BlockFormattingContext {
pub(crate) fn construct<'dom, Node>(
pub(crate) fn construct(
context: &LayoutContext,
info: &NodeAndStyleInfo<Node>,
info: &NodeAndStyleInfo<'_>,
contents: NonReplacedContents,
propagated_data: PropagatedBoxTreeData,
is_list_item: bool,
) -> Self
where
Node: NodeExt<'dom>,
{
) -> Self {
Self::from_block_container(BlockContainer::construct(
context,
info,
@ -61,8 +58,8 @@ impl BlockFormattingContext {
}
}
struct BlockLevelJob<'dom, Node> {
info: NodeAndStyleInfo<Node>,
struct BlockLevelJob<'dom> {
info: NodeAndStyleInfo<'dom>,
box_slot: BoxSlot<'dom>,
propagated_data: PropagatedBoxTreeData,
kind: BlockLevelCreator,
@ -111,12 +108,12 @@ enum IntermediateBlockContainer {
///
/// This builder starts from the first child of a given DOM node
/// and does a preorder traversal of all of its inclusive siblings.
pub(crate) struct BlockContainerBuilder<'dom, 'style, Node> {
pub(crate) struct BlockContainerBuilder<'dom, 'style> {
context: &'style LayoutContext<'style>,
/// This NodeAndStyleInfo contains the root node, the corresponding pseudo
/// content designator, and the block container style.
info: &'style NodeAndStyleInfo<Node>,
info: &'style NodeAndStyleInfo<'dom>,
/// The list of block-level boxes to be built for the final block container.
///
@ -131,7 +128,7 @@ pub(crate) struct BlockContainerBuilder<'dom, 'style, Node> {
/// doesn't have a next sibling, we either reached the end of the container
/// root or there are ongoing inline-level boxes
/// (see `handle_block_level_element`).
block_level_boxes: Vec<BlockLevelJob<'dom, Node>>,
block_level_boxes: Vec<BlockLevelJob<'dom>>,
/// Whether or not this builder has yet produced a block which would be
/// be considered the first line for the purposes of `text-indent`.
@ -140,29 +137,35 @@ pub(crate) struct BlockContainerBuilder<'dom, 'style, Node> {
/// The propagated data to use for BoxTree construction.
propagated_data: PropagatedBoxTreeData,
inline_formatting_context_builder: InlineFormattingContextBuilder,
/// The [`InlineFormattingContextBuilder`] if we have encountered any inline items,
/// otherwise None.
///
/// TODO: This can be `OnceCell` once `OnceCell::get_mut_or_init` is stabilized.
inline_formatting_context_builder: Option<InlineFormattingContextBuilder>,
/// The [`NodeAndStyleInfo`] to use for anonymous block boxes pushed to the list of
/// block-level boxes, lazily initialized (see `end_ongoing_inline_formatting_context`).
anonymous_box_info: Option<NodeAndStyleInfo<Node>>,
/// block-level boxes, lazily initialized.
anonymous_box_info: Option<NodeAndStyleInfo<'dom>>,
/// A collection of content that is being added to an anonymous table. This is
/// composed of any sequence of internal table elements or table captions that
/// are found outside of a table.
anonymous_table_content: Vec<AnonymousTableContent<'dom, Node>>,
anonymous_table_content: Vec<AnonymousTableContent<'dom>>,
/// Any [`InlineFormattingContexts`] created need to know about the ongoing `display: contents`
/// ancestors that have been processed. This `Vec` allows passing those into new
/// [`InlineFormattingContext`]s that we create.
display_contents_shared_styles: Vec<SharedInlineStyles>,
}
impl BlockContainer {
pub fn construct<'dom, Node>(
pub fn construct(
context: &LayoutContext,
info: &NodeAndStyleInfo<Node>,
info: &NodeAndStyleInfo<'_>,
contents: NonReplacedContents,
propagated_data: PropagatedBoxTreeData,
is_list_item: bool,
) -> BlockContainer
where
Node: NodeExt<'dom>,
{
) -> BlockContainer {
let mut builder = BlockContainerBuilder::new(context, info, propagated_data);
if is_list_item {
@ -186,43 +189,57 @@ impl BlockContainer {
}
}
impl<'dom, 'style, Node> BlockContainerBuilder<'dom, 'style, Node>
where
Node: NodeExt<'dom>,
{
impl<'dom, 'style> BlockContainerBuilder<'dom, 'style> {
pub(crate) fn new(
context: &'style LayoutContext,
info: &'style NodeAndStyleInfo<Node>,
info: &'style NodeAndStyleInfo<'dom>,
propagated_data: PropagatedBoxTreeData,
) -> Self {
BlockContainerBuilder {
context,
info,
block_level_boxes: Vec::new(),
propagated_data: propagated_data.union(&info.style),
propagated_data,
have_already_seen_first_line_for_text_indent: false,
anonymous_box_info: None,
anonymous_table_content: Vec::new(),
inline_formatting_context_builder: InlineFormattingContextBuilder::new(),
inline_formatting_context_builder: None,
display_contents_shared_styles: Vec::new(),
}
}
pub(crate) fn finish(mut self) -> BlockContainer {
debug_assert!(
!self
.inline_formatting_context_builder
.currently_processing_inline_box()
);
fn currently_processing_inline_box(&self) -> bool {
self.inline_formatting_context_builder
.as_ref()
.is_some_and(InlineFormattingContextBuilder::currently_processing_inline_box)
}
self.finish_anonymous_table_if_needed();
fn ensure_inline_formatting_context_builder(&mut self) -> &mut InlineFormattingContextBuilder {
self.inline_formatting_context_builder
.get_or_insert_with(|| {
let mut builder = InlineFormattingContextBuilder::new(self.info);
for shared_inline_styles in self.display_contents_shared_styles.iter() {
builder.enter_display_contents(shared_inline_styles.clone());
}
builder
})
}
if let Some(inline_formatting_context) = self.inline_formatting_context_builder.finish(
fn finish_ongoing_inline_formatting_context(&mut self) -> Option<InlineFormattingContext> {
self.inline_formatting_context_builder.take()?.finish(
self.context,
self.propagated_data,
!self.have_already_seen_first_line_for_text_indent,
self.info.is_single_line_text_input(),
self.info.style.writing_mode.to_bidi_level(),
) {
)
}
pub(crate) fn finish(mut self) -> BlockContainer {
debug_assert!(!self.currently_processing_inline_box());
self.finish_anonymous_table_if_needed();
if let Some(inline_formatting_context) = self.finish_ongoing_inline_formatting_context() {
// There are two options here. This block was composed of both one or more inline formatting contexts
// and child blocks OR this block was a single inline formatting context. In the latter case, we
// just return the inline formatting context as the block itself.
@ -260,21 +277,9 @@ where
//
// Note that text content in the inline formatting context isn't enough to force the
// creation of an inline table. It requires the parent to be an inline box.
let inline_table = self
.inline_formatting_context_builder
.currently_processing_inline_box();
let inline_table = self.currently_processing_inline_box();
// Text decorations are not propagated to atomic inline-level descendants.
// From https://drafts.csswg.org/css2/#lining-striking-props:
// > Note that text decorations are not propagated to floating and absolutely
// > positioned descendants, nor to the contents of atomic inline-level descendants
// > such as inline blocks and inline tables.
let propagated_data = match inline_table {
true => self.propagated_data.without_text_decorations(),
false => self.propagated_data,
};
let contents: Vec<AnonymousTableContent<'dom, Node>> =
let contents: Vec<AnonymousTableContent<'dom>> =
self.anonymous_table_content.drain(..).collect();
let last_text = match contents.last() {
Some(AnonymousTableContent::Text(info, text)) => Some((info.clone(), text.clone())),
@ -282,18 +287,24 @@ where
};
let (table_info, ifc) =
Table::construct_anonymous(self.context, self.info, contents, propagated_data);
Table::construct_anonymous(self.context, self.info, contents, self.propagated_data);
if inline_table {
self.inline_formatting_context_builder.push_atomic(ifc);
self.ensure_inline_formatting_context_builder()
.push_atomic(ifc);
} else {
let table_block = ArcRefCell::new(BlockLevelBox::Independent(ifc));
self.end_ongoing_inline_formatting_context();
if let Some(inline_formatting_context) = self.finish_ongoing_inline_formatting_context()
{
self.push_block_level_job_for_inline_formatting_context(inline_formatting_context);
}
self.block_level_boxes.push(BlockLevelJob {
info: table_info,
box_slot: BoxSlot::dummy(),
kind: BlockLevelCreator::AnonymousTable { table_block },
propagated_data,
propagated_data: self.propagated_data,
});
}
@ -312,13 +323,10 @@ where
}
}
impl<'dom, Node> TraversalHandler<'dom, Node> for BlockContainerBuilder<'dom, '_, Node>
where
Node: NodeExt<'dom>,
{
impl<'dom> TraversalHandler<'dom> for BlockContainerBuilder<'dom, '_> {
fn handle_element(
&mut self,
info: &NodeAndStyleInfo<Node>,
info: &NodeAndStyleInfo<'dom>,
display: DisplayGeneratingBox,
contents: Contents,
box_slot: BoxSlot<'dom>,
@ -359,7 +367,7 @@ where
}
}
fn handle_text(&mut self, info: &NodeAndStyleInfo<Node>, text: Cow<'dom, str>) {
fn handle_text(&mut self, info: &NodeAndStyleInfo<'dom>, text: Cow<'dom, str>) {
if text.is_empty() {
return;
}
@ -375,18 +383,30 @@ where
self.finish_anonymous_table_if_needed();
}
self.inline_formatting_context_builder.push_text(text, info);
self.ensure_inline_formatting_context_builder()
.push_text(text, info);
}
fn enter_display_contents(&mut self, styles: SharedInlineStyles) {
self.display_contents_shared_styles.push(styles.clone());
if let Some(builder) = self.inline_formatting_context_builder.as_mut() {
builder.enter_display_contents(styles);
}
}
fn leave_display_contents(&mut self) {
self.display_contents_shared_styles.pop();
if let Some(builder) = self.inline_formatting_context_builder.as_mut() {
builder.leave_display_contents();
}
}
}
impl<'dom, Node> BlockContainerBuilder<'dom, '_, Node>
where
Node: NodeExt<'dom>,
{
impl<'dom> BlockContainerBuilder<'dom, '_> {
fn handle_list_item_marker_inside(
&mut self,
marker_info: &NodeAndStyleInfo<Node>,
container_info: &NodeAndStyleInfo<Node>,
marker_info: &NodeAndStyleInfo<'dom>,
container_info: &NodeAndStyleInfo<'dom>,
contents: Vec<crate::dom_traversal::PseudoElementContentItem>,
) {
// TODO: We do not currently support saving box slots for ::marker pseudo-elements
@ -411,8 +431,8 @@ where
fn handle_list_item_marker_outside(
&mut self,
marker_info: &NodeAndStyleInfo<Node>,
container_info: &NodeAndStyleInfo<Node>,
marker_info: &NodeAndStyleInfo<'dom>,
container_info: &NodeAndStyleInfo<'dom>,
contents: Vec<crate::dom_traversal::PseudoElementContentItem>,
list_item_style: Arc<ComputedValues>,
) {
@ -433,13 +453,13 @@ where
contents,
list_item_style,
},
propagated_data: self.propagated_data.without_text_decorations(),
propagated_data: self.propagated_data,
});
}
fn handle_inline_level_element(
&mut self,
info: &NodeAndStyleInfo<Node>,
info: &NodeAndStyleInfo<'dom>,
display_inside: DisplayInside,
contents: Contents,
box_slot: BoxSlot<'dom>,
@ -448,25 +468,25 @@ where
(display_inside, contents.is_replaced())
else {
// If this inline element is an atomic, handle it and return.
let atomic = self.inline_formatting_context_builder.push_atomic(
let context = self.context;
let propagated_data = self.propagated_data;
let atomic = self.ensure_inline_formatting_context_builder().push_atomic(
IndependentFormattingContext::construct(
self.context,
context,
info,
display_inside,
contents,
// Text decorations are not propagated to atomic inline-level descendants.
self.propagated_data.without_text_decorations(),
propagated_data,
),
);
box_slot.set(LayoutBox::InlineLevel(atomic));
box_slot.set(LayoutBox::InlineLevel(vec![atomic]));
return;
};
// Otherwise, this is just a normal inline box. Whatever happened before, all we need to do
// before recurring is to remember this ongoing inline level box.
let inline_item = self
.inline_formatting_context_builder
.start_inline_box(InlineBox::new(info));
self.ensure_inline_formatting_context_builder()
.start_inline_box(InlineBox::new(info), None);
if is_list_item {
if let Some((marker_info, marker_contents)) =
@ -486,13 +506,22 @@ where
self.finish_anonymous_table_if_needed();
self.inline_formatting_context_builder.end_inline_box();
box_slot.set(LayoutBox::InlineLevel(inline_item));
// As we are ending this inline box, during the course of the `traverse()` above, the ongoing
// inline formatting context may have been split around block-level elements. In that case,
// more than a single inline box tree item may have been produced for this inline-level box.
// `InlineFormattingContextBuilder::end_inline_box()` is returning all of those box tree
// items.
box_slot.set(LayoutBox::InlineLevel(
self.inline_formatting_context_builder
.as_mut()
.expect("Should be building an InlineFormattingContext")
.end_inline_box(),
));
}
fn handle_block_level_element(
&mut self,
info: &NodeAndStyleInfo<Node>,
info: &NodeAndStyleInfo<'dom>,
display_inside: DisplayInside,
contents: Contents,
box_slot: BoxSlot<'dom>,
@ -505,12 +534,14 @@ where
// that we want to have after we push the block below.
if let Some(inline_formatting_context) = self
.inline_formatting_context_builder
.split_around_block_and_finish(
self.context,
self.propagated_data,
!self.have_already_seen_first_line_for_text_indent,
self.info.style.writing_mode.to_bidi_level(),
)
.as_mut()
.and_then(|builder| {
builder.split_around_block_and_finish(
self.context,
!self.have_already_seen_first_line_for_text_indent,
self.info.style.writing_mode.to_bidi_level(),
)
})
{
self.push_block_level_job_for_inline_formatting_context(inline_formatting_context);
}
@ -560,22 +591,23 @@ where
fn handle_absolutely_positioned_element(
&mut self,
info: &NodeAndStyleInfo<Node>,
info: &NodeAndStyleInfo<'dom>,
display_inside: DisplayInside,
contents: Contents,
box_slot: BoxSlot<'dom>,
) {
if !self.inline_formatting_context_builder.is_empty() {
let inline_level_box = self
.inline_formatting_context_builder
.push_absolutely_positioned_box(AbsolutelyPositionedBox::construct(
self.context,
info,
display_inside,
contents,
));
box_slot.set(LayoutBox::InlineLevel(inline_level_box));
return;
if let Some(builder) = self.inline_formatting_context_builder.as_mut() {
if !builder.is_empty() {
let inline_level_box =
builder.push_absolutely_positioned_box(AbsolutelyPositionedBox::construct(
self.context,
info,
display_inside,
contents,
));
box_slot.set(LayoutBox::InlineLevel(vec![inline_level_box]));
return;
}
}
let kind = BlockLevelCreator::OutOfFlowAbsolutelyPositionedBox {
@ -586,29 +618,29 @@ where
info: info.clone(),
box_slot,
kind,
propagated_data: self.propagated_data.without_text_decorations(),
propagated_data: self.propagated_data,
});
}
fn handle_float_element(
&mut self,
info: &NodeAndStyleInfo<Node>,
info: &NodeAndStyleInfo<'dom>,
display_inside: DisplayInside,
contents: Contents,
box_slot: BoxSlot<'dom>,
) {
if !self.inline_formatting_context_builder.is_empty() {
let inline_level_box =
self.inline_formatting_context_builder
.push_float_box(FloatBox::construct(
self.context,
info,
display_inside,
contents,
self.propagated_data,
));
box_slot.set(LayoutBox::InlineLevel(inline_level_box));
return;
if let Some(builder) = self.inline_formatting_context_builder.as_mut() {
if !builder.is_empty() {
let inline_level_box = builder.push_float_box(FloatBox::construct(
self.context,
info,
display_inside,
contents,
self.propagated_data,
));
box_slot.set(LayoutBox::InlineLevel(vec![inline_level_box]));
return;
}
}
let kind = BlockLevelCreator::OutOfFlowFloatBox {
@ -619,22 +651,10 @@ where
info: info.clone(),
box_slot,
kind,
propagated_data: self.propagated_data.without_text_decorations(),
propagated_data: self.propagated_data,
});
}
fn end_ongoing_inline_formatting_context(&mut self) {
if let Some(inline_formatting_context) = self.inline_formatting_context_builder.finish(
self.context,
self.propagated_data,
!self.have_already_seen_first_line_for_text_indent,
self.info.is_single_line_text_input(),
self.info.style.writing_mode.to_bidi_level(),
) {
self.push_block_level_job_for_inline_formatting_context(inline_formatting_context);
}
}
fn push_block_level_job_for_inline_formatting_context(
&mut self,
inline_formatting_context: InlineFormattingContext,
@ -665,10 +685,7 @@ where
}
}
impl<'dom, Node> BlockLevelJob<'dom, Node>
where
Node: NodeExt<'dom>,
{
impl BlockLevelJob<'_> {
fn finish(self, context: &LayoutContext) -> ArcRefCell<BlockLevelBox> {
let info = &self.info;
let block_level_box = match self.kind {
@ -724,7 +741,7 @@ where
context,
info,
contents,
self.propagated_data.without_text_decorations(),
self.propagated_data,
false, /* is_list_item */
);
ArcRefCell::new(BlockLevelBox::OutsideMarker(OutsideMarker {
@ -742,14 +759,7 @@ where
}
impl IntermediateBlockContainer {
fn finish<'dom, Node>(
self,
context: &LayoutContext,
info: &NodeAndStyleInfo<Node>,
) -> BlockContainer
where
Node: NodeExt<'dom>,
{
fn finish(self, context: &LayoutContext, info: &NodeAndStyleInfo<'_>) -> BlockContainer {
match self {
IntermediateBlockContainer::Deferred {
contents,

View file

@ -22,7 +22,6 @@ use style::properties::ComputedValues;
use style::values::computed::Clear as StyleClear;
use crate::context::LayoutContext;
use crate::dom::NodeExt;
use crate::dom_traversal::{Contents, NodeAndStyleInfo};
use crate::formatting_contexts::IndependentFormattingContext;
use crate::fragment_tree::{BoxFragment, CollapsedMargin};
@ -885,9 +884,9 @@ impl FloatBandLink {
impl FloatBox {
/// Creates a new float box.
pub fn construct<'dom>(
pub fn construct(
context: &LayoutContext,
info: &NodeAndStyleInfo<impl NodeExt<'dom>>,
info: &NodeAndStyleInfo<'_>,
display_inside: DisplayInside,
contents: Contents,
propagated_data: PropagatedBoxTreeData,
@ -898,8 +897,7 @@ impl FloatBox {
info,
display_inside,
contents,
// Text decorations are not propagated to any out-of-flow descendants
propagated_data.without_text_decorations(),
propagated_data,
),
}
}
@ -913,11 +911,10 @@ impl FloatBox {
positioning_context: &mut PositioningContext,
containing_block: &ContainingBlock,
) -> BoxFragment {
let style = self.contents.style().clone();
positioning_context.layout_maybe_position_relative_fragment(
layout_context,
containing_block,
&style,
&self.contents.base,
|positioning_context| {
self.contents
.layout_float_or_atomic_inline(

View file

@ -6,17 +6,18 @@ use std::borrow::Cow;
use std::char::{ToLowercase, ToUppercase};
use icu_segmenter::WordSegmenter;
use servo_arc::Arc;
use itertools::izip;
use style::computed_values::white_space_collapse::T as WhiteSpaceCollapse;
use style::values::specified::text::TextTransformCase;
use unicode_bidi::Level;
use super::text_run::TextRun;
use super::{InlineBox, InlineBoxIdentifier, InlineBoxes, InlineFormattingContext, InlineItem};
use crate::PropagatedBoxTreeData;
use super::{
InlineBox, InlineBoxIdentifier, InlineBoxes, InlineFormattingContext, InlineItem,
SharedInlineStyles,
};
use crate::cell::ArcRefCell;
use crate::context::LayoutContext;
use crate::dom::NodeExt;
use crate::dom_traversal::NodeAndStyleInfo;
use crate::flow::float::FloatBox;
use crate::formatting_contexts::IndependentFormattingContext;
@ -25,6 +26,12 @@ use crate::style_ext::ComputedValuesExt;
#[derive(Default)]
pub(crate) struct InlineFormattingContextBuilder {
/// A stack of [`SharedInlineStyles`] including one for the root, one for each inline box on the
/// inline box stack, and importantly, one for every `display: contents` element that we are
/// currently processing. Normally `display: contents` elements don't affect the structure of
/// the [`InlineFormattingContext`], but the styles they provide do style their children.
pub shared_inline_styles_stack: Vec<SharedInlineStyles>,
/// The collection of text strings that make up this [`InlineFormattingContext`] under
/// construction.
pub text_segments: Vec<String>,
@ -63,20 +70,37 @@ pub(crate) struct InlineFormattingContextBuilder {
/// The traversal is at all times as deep in the tree as this stack is,
/// which is why the code doesn't need to keep track of the actual
/// container root (see `handle_inline_level_element`).
///
//_
/// When an inline box ends, it's removed from this stack.
inline_box_stack: Vec<InlineBoxIdentifier>,
/// Normally, an inline box produces a single box tree [`InlineItem`]. When a block
/// element causes an inline box [to be split], it can produce multiple
/// [`InlineItem`]s, all inserted into different [`InlineFormattingContext`]s.
/// [`Self::block_in_inline_splits`] is responsible for tracking all of these split
/// inline box results, so that they can be inserted into the [`crate::dom::BoxSlot`]
/// for the DOM element once it has been processed for BoxTree construction.
///
/// [to be split]: https://www.w3.org/TR/CSS2/visuren.html#anonymous-block-level
block_in_inline_splits: Vec<Vec<ArcRefCell<InlineItem>>>,
/// Whether or not the inline formatting context under construction has any
/// uncollapsible text content.
pub has_uncollapsible_text_content: bool,
}
impl InlineFormattingContextBuilder {
pub(crate) fn new() -> Self {
// For the purposes of `text-transform: capitalize` the start of the IFC is a word boundary.
pub(crate) fn new(info: &NodeAndStyleInfo) -> Self {
Self::new_for_shared_styles(vec![info.into()])
}
pub(crate) fn new_for_shared_styles(
shared_inline_styles_stack: Vec<SharedInlineStyles>,
) -> Self {
Self {
// For the purposes of `text-transform: capitalize` the start of the IFC is a word boundary.
on_word_boundary: true,
shared_inline_styles_stack,
..Default::default()
}
}
@ -90,6 +114,13 @@ impl InlineFormattingContextBuilder {
self.current_text_offset += string_to_push.len();
}
fn shared_inline_styles(&self) -> SharedInlineStyles {
self.shared_inline_styles_stack
.last()
.expect("Should always have at least one SharedInlineStyles")
.clone()
}
/// Return true if this [`InlineFormattingContextBuilder`] is empty for the purposes of ignoring
/// during box tree construction. An IFC is empty if it only contains TextRuns with
/// completely collapsible whitespace. When that happens it can be ignored completely.
@ -125,7 +156,7 @@ impl InlineFormattingContextBuilder {
independent_formatting_context: IndependentFormattingContext,
) -> ArcRefCell<InlineItem> {
let inline_level_box = ArcRefCell::new(InlineItem::Atomic(
Arc::new(independent_formatting_context),
ArcRefCell::new(independent_formatting_context),
self.current_text_offset,
Level::ltr(), /* This will be assigned later if necessary. */
));
@ -156,35 +187,59 @@ impl InlineFormattingContextBuilder {
}
pub(crate) fn push_float_box(&mut self, float_box: FloatBox) -> ArcRefCell<InlineItem> {
let inline_level_box = ArcRefCell::new(InlineItem::OutOfFlowFloatBox(Arc::new(float_box)));
let inline_level_box =
ArcRefCell::new(InlineItem::OutOfFlowFloatBox(ArcRefCell::new(float_box)));
self.inline_items.push(inline_level_box.clone());
self.contains_floats = true;
inline_level_box
}
pub(crate) fn start_inline_box(&mut self, inline_box: InlineBox) -> ArcRefCell<InlineItem> {
pub(crate) fn start_inline_box(
&mut self,
inline_box: InlineBox,
block_in_inline_splits: Option<Vec<ArcRefCell<InlineItem>>>,
) {
self.push_control_character_string(inline_box.base.style.bidi_control_chars().0);
// Don't push a `SharedInlineStyles` if we are pushing this box when splitting
// an IFC for a block-in-inline split. Shared styles are pushed as part of setting
// up the second split of the IFC.
if inline_box.is_first_split {
self.shared_inline_styles_stack
.push(inline_box.shared_inline_styles.clone());
}
let (identifier, inline_box) = self.inline_boxes.start_inline_box(inline_box);
let inline_level_box = ArcRefCell::new(InlineItem::StartInlineBox(inline_box));
self.inline_items.push(inline_level_box.clone());
self.inline_box_stack.push(identifier);
inline_level_box
let mut block_in_inline_splits = block_in_inline_splits.unwrap_or_default();
block_in_inline_splits.push(inline_level_box);
self.block_in_inline_splits.push(block_in_inline_splits);
}
pub(crate) fn end_inline_box(&mut self) -> ArcRefCell<InlineBox> {
let identifier = self.end_inline_box_internal();
/// End the ongoing inline box in this [`InlineFormattingContextBuilder`], returning
/// shared references to all of the box tree items that were created for it. More than
/// a single box tree items may be produced for a single inline box when that inline
/// box is split around a block-level element.
pub(crate) fn end_inline_box(&mut self) -> Vec<ArcRefCell<InlineItem>> {
self.shared_inline_styles_stack.pop();
let (identifier, block_in_inline_splits) = self.end_inline_box_internal();
let inline_level_box = self.inline_boxes.get(&identifier);
inline_level_box.borrow_mut().is_last_fragment = true;
{
let mut inline_level_box = inline_level_box.borrow_mut();
inline_level_box.is_last_split = true;
self.push_control_character_string(inline_level_box.base.style.bidi_control_chars().1);
}
self.push_control_character_string(
inline_level_box.borrow().base.style.bidi_control_chars().1,
);
inline_level_box
block_in_inline_splits.unwrap_or_default()
}
fn end_inline_box_internal(&mut self) -> InlineBoxIdentifier {
fn end_inline_box_internal(
&mut self,
) -> (InlineBoxIdentifier, Option<Vec<ArcRefCell<InlineItem>>>) {
let identifier = self
.inline_box_stack
.pop()
@ -193,14 +248,15 @@ impl InlineFormattingContextBuilder {
.push(ArcRefCell::new(InlineItem::EndInlineBox));
self.inline_boxes.end_inline_box(identifier);
identifier
// This might be `None` if this builder has already drained its block-in-inline-splits
// into the new builder on the other side of a new block-in-inline split.
let block_in_inline_splits = self.block_in_inline_splits.pop();
(identifier, block_in_inline_splits)
}
pub(crate) fn push_text<'dom, Node: NodeExt<'dom>>(
&mut self,
text: Cow<'dom, str>,
info: &NodeAndStyleInfo<Node>,
) {
pub(crate) fn push_text<'dom>(&mut self, text: Cow<'dom, str>, info: &NodeAndStyleInfo<'dom>) {
let white_space_collapse = info.style.clone_white_space_collapse();
let collapsed = WhitespaceCollapse::new(
text.chars(),
@ -248,8 +304,6 @@ impl InlineFormattingContextBuilder {
}
let selection_range = info.get_selection_range();
let selected_style = info.get_selected_style();
if let Some(last_character) = new_text.chars().next_back() {
self.on_word_boundary = last_character.is_whitespace();
self.last_inline_box_ended_with_collapsible_white_space =
@ -271,18 +325,24 @@ impl InlineFormattingContextBuilder {
.push(ArcRefCell::new(InlineItem::TextRun(ArcRefCell::new(
TextRun::new(
info.into(),
info.style.clone(),
self.shared_inline_styles(),
new_range,
selection_range,
selected_style,
),
))));
}
pub(crate) fn enter_display_contents(&mut self, shared_inline_styles: SharedInlineStyles) {
self.shared_inline_styles_stack.push(shared_inline_styles);
}
pub(crate) fn leave_display_contents(&mut self) {
self.shared_inline_styles_stack.pop();
}
pub(crate) fn split_around_block_and_finish(
&mut self,
layout_context: &LayoutContext,
propagated_data: PropagatedBoxTreeData,
has_first_formatted_line: bool,
default_bidi_level: Level,
) -> Option<InlineFormattingContext> {
@ -294,13 +354,23 @@ impl InlineFormattingContextBuilder {
// context. It has the same inline box structure as this builder, except the boxes are
// marked as not being the first fragment. No inline content is carried over to this new
// builder.
let mut new_builder = InlineFormattingContextBuilder::new();
for identifier in self.inline_box_stack.iter() {
let mut new_builder = Self::new_for_shared_styles(self.shared_inline_styles_stack.clone());
let block_in_inline_splits = std::mem::take(&mut self.block_in_inline_splits);
for (identifier, historical_inline_boxes) in
izip!(self.inline_box_stack.iter(), block_in_inline_splits)
{
// Start a new inline box for every ongoing inline box in this
// InlineFormattingContext once we are done processing this block element,
// being sure to give the block-in-inline-split to the new
// InlineFormattingContext. These will finally be inserted into the DOM's
// BoxSlot once the inline box has been fully processed.
new_builder.start_inline_box(
self.inline_boxes
.get(identifier)
.borrow()
.split_around_block(),
Some(historical_inline_boxes),
);
}
let mut inline_builder_from_before_split = std::mem::replace(self, new_builder);
@ -314,7 +384,6 @@ impl InlineFormattingContextBuilder {
inline_builder_from_before_split.finish(
layout_context,
propagated_data,
has_first_formatted_line,
/* is_single_line_text_input = */ false,
default_bidi_level,
@ -323,9 +392,8 @@ impl InlineFormattingContextBuilder {
/// Finish the current inline formatting context, returning [`None`] if the context was empty.
pub(crate) fn finish(
&mut self,
self,
layout_context: &LayoutContext,
propagated_data: PropagatedBoxTreeData,
has_first_formatted_line: bool,
is_single_line_text_input: bool,
default_bidi_level: Level,
@ -334,13 +402,10 @@ impl InlineFormattingContextBuilder {
return None;
}
let old_builder = std::mem::replace(self, InlineFormattingContextBuilder::new());
assert!(old_builder.inline_box_stack.is_empty());
assert!(self.inline_box_stack.is_empty());
Some(InlineFormattingContext::new_with_builder(
old_builder,
self,
layout_context,
propagated_data,
has_first_formatted_line,
is_single_line_text_input,
default_bidi_level,

View file

@ -7,12 +7,18 @@ use std::vec::IntoIter;
use app_units::Au;
use fonts::FontMetrics;
use malloc_size_of_derive::MallocSizeOf;
use script::layout_dom::ServoLayoutNode;
use script_layout_interface::wrapper_traits::{LayoutNode, ThreadSafeLayoutNode};
use servo_arc::Arc as ServoArc;
use style::properties::ComputedValues;
use super::{InlineContainerState, InlineContainerStateFlags, inline_container_needs_strut};
use super::{
InlineContainerState, InlineContainerStateFlags, SharedInlineStyles,
inline_container_needs_strut,
};
use crate::ContainingBlock;
use crate::cell::ArcRefCell;
use crate::context::LayoutContext;
use crate::dom::NodeExt;
use crate::dom_traversal::NodeAndStyleInfo;
use crate::fragment_tree::BaseFragmentInfo;
use crate::layout_box_base::LayoutBoxBase;
@ -21,23 +27,31 @@ use crate::style_ext::{LayoutStyle, PaddingBorderMargin};
#[derive(Debug, MallocSizeOf)]
pub(crate) struct InlineBox {
pub base: LayoutBoxBase,
/// The [`SharedInlineStyles`] for this [`InlineBox`] that are used to share styles
/// with all [`super::TextRun`] children.
pub(super) shared_inline_styles: SharedInlineStyles,
/// The identifier of this inline box in the containing [`super::InlineFormattingContext`].
pub(super) identifier: InlineBoxIdentifier,
pub is_first_fragment: bool,
pub is_last_fragment: bool,
/// Whether or not this is the first instance of an [`InlineBox`] before a possible
/// block-in-inline split. When no split occurs, this is always true.
pub is_first_split: bool,
/// Whether or not this is the last instance of an [`InlineBox`] before a possible
/// block-in-inline split. When no split occurs, this is always true.
pub is_last_split: bool,
/// The index of the default font in the [`super::InlineFormattingContext`]'s font metrics store.
/// This is initialized during IFC shaping.
pub default_font_index: Option<usize>,
}
impl InlineBox {
pub(crate) fn new<'dom, Node: NodeExt<'dom>>(info: &NodeAndStyleInfo<Node>) -> Self {
pub(crate) fn new(info: &NodeAndStyleInfo) -> Self {
Self {
base: LayoutBoxBase::new(info.into(), info.style.clone()),
shared_inline_styles: info.into(),
// This will be assigned later, when the box is actually added to the IFC.
identifier: InlineBoxIdentifier::default(),
is_first_fragment: true,
is_last_fragment: false,
is_first_split: true,
is_last_split: false,
default_font_index: None,
}
}
@ -45,8 +59,9 @@ impl InlineBox {
pub(crate) fn split_around_block(&self) -> Self {
Self {
base: LayoutBoxBase::new(self.base.base_fragment_info, self.base.style.clone()),
is_first_fragment: false,
is_last_fragment: false,
shared_inline_styles: self.shared_inline_styles.clone(),
is_first_split: false,
is_last_split: false,
..*self
}
}
@ -55,6 +70,16 @@ impl InlineBox {
pub(crate) fn layout_style(&self) -> LayoutStyle {
LayoutStyle::Default(&self.base.style)
}
pub(crate) fn repair_style(
&mut self,
node: &ServoLayoutNode,
new_style: &ServoArc<ComputedValues>,
) {
self.base.repair_style(new_style);
*self.shared_inline_styles.style.borrow_mut() = new_style.clone();
*self.shared_inline_styles.selected.borrow_mut() = node.to_threadsafe().selected_style();
}
}
#[derive(Debug, Default, MallocSizeOf)]
@ -231,13 +256,7 @@ impl InlineBoxContainerState {
}
Self {
base: InlineContainerState::new(
style,
flags,
Some(parent_container),
parent_container.text_decoration_line,
font_metrics,
),
base: InlineContainerState::new(style, flags, Some(parent_container), font_metrics),
identifier: inline_box.identifier,
base_fragment_info: inline_box.base.base_fragment_info,
pbm,

View file

@ -7,7 +7,6 @@ use bitflags::bitflags;
use fonts::{ByteIndex, FontMetrics, GlyphStore};
use itertools::Either;
use range::Range;
use servo_arc::Arc;
use style::Zero;
use style::computed_values::position::T as Position;
use style::computed_values::white_space_collapse::T as WhiteSpaceCollapse;
@ -16,12 +15,11 @@ use style::values::generics::box_::{GenericVerticalAlign, VerticalAlignKeyword};
use style::values::generics::font::LineHeight;
use style::values::specified::align::AlignFlags;
use style::values::specified::box_::DisplayOutside;
use style::values::specified::text::TextDecorationLine;
use unicode_bidi::{BidiInfo, Level};
use webrender_api::FontInstanceKey;
use super::inline_box::{InlineBoxContainerState, InlineBoxIdentifier, InlineBoxTreePathToken};
use super::{InlineFormattingContextLayout, LineBlockSizes};
use super::{InlineFormattingContextLayout, LineBlockSizes, SharedInlineStyles};
use crate::cell::ArcRefCell;
use crate::fragment_tree::{BaseFragmentInfo, BoxFragment, Fragment, TextFragment};
use crate::geom::{LogicalRect, LogicalVec2, PhysicalRect, ToLogical};
@ -326,13 +324,12 @@ impl LineItemLayout<'_, '_> {
let inline_box = self.layout.ifc.inline_boxes.get(identifier);
let inline_box = &*(inline_box.borrow());
let style = &inline_box.base.style;
let space_above_baseline = inline_box_state.calculate_space_above_baseline();
let block_start_offset =
self.calculate_inline_box_block_start(inline_box_state, space_above_baseline);
let positioning_context_or_start_offset_in_parent =
match PositioningContext::new_for_style(style) {
match PositioningContext::new_for_layout_box_base(&inline_box.base) {
Some(positioning_context) => Either::Left(positioning_context),
None => Either::Right(self.current_positioning_context_mut().len()),
};
@ -569,15 +566,13 @@ impl LineItemLayout<'_, '_> {
self.current_state.fragments.push((
Fragment::Text(ArcRefCell::new(TextFragment {
base: text_item.base_fragment_info.into(),
parent_style: text_item.parent_style,
inline_styles: text_item.inline_styles.clone(),
rect: PhysicalRect::zero(),
font_metrics: text_item.font_metrics,
font_key: text_item.font_key,
glyphs: text_item.text,
text_decoration_line: text_item.text_decoration_line,
justification_adjustment: self.justification_adjustment,
selection_range: text_item.selection_range,
selected_style: text_item.selected_style,
})),
content_rect,
));
@ -764,21 +759,23 @@ impl LineItem {
pub(super) struct TextRunLineItem {
pub base_fragment_info: BaseFragmentInfo,
pub parent_style: Arc<ComputedValues>,
pub inline_styles: SharedInlineStyles,
pub text: Vec<std::sync::Arc<GlyphStore>>,
pub font_metrics: FontMetrics,
pub font_key: FontInstanceKey,
pub text_decoration_line: TextDecorationLine,
/// The BiDi level of this [`TextRunLineItem`] to enable reordering.
pub bidi_level: Level,
pub selection_range: Option<Range<ByteIndex>>,
pub selected_style: Arc<ComputedValues>,
}
impl TextRunLineItem {
fn trim_whitespace_at_end(&mut self, whitespace_trimmed: &mut Au) -> bool {
if matches!(
self.parent_style.get_inherited_text().white_space_collapse,
self.inline_styles
.style
.borrow()
.get_inherited_text()
.white_space_collapse,
WhiteSpaceCollapse::Preserve | WhiteSpaceCollapse::BreakSpaces
) {
return false;
@ -804,7 +801,11 @@ impl TextRunLineItem {
fn trim_whitespace_at_start(&mut self, whitespace_trimmed: &mut Au) -> bool {
if matches!(
self.parent_style.get_inherited_text().white_space_collapse,
self.inline_styles
.style
.borrow()
.get_inherited_text()
.white_space_collapse,
WhiteSpaceCollapse::Preserve | WhiteSpaceCollapse::BreakSpaces
) {
return false;

View file

@ -90,18 +90,20 @@ use line::{
use line_breaker::LineBreaker;
use malloc_size_of_derive::MallocSizeOf;
use range::Range;
use script::layout_dom::ServoLayoutNode;
use script_layout_interface::wrapper_traits::{LayoutNode, ThreadSafeLayoutNode};
use servo_arc::Arc;
use style::Zero;
use style::computed_values::text_wrap_mode::T as TextWrapMode;
use style::computed_values::vertical_align::T as VerticalAlign;
use style::computed_values::white_space_collapse::T as WhiteSpaceCollapse;
use style::context::QuirksMode;
use style::context::{QuirksMode, SharedStyleContext};
use style::properties::ComputedValues;
use style::properties::style_structs::InheritedText;
use style::values::generics::box_::VerticalAlignKeyword;
use style::values::generics::font::LineHeight;
use style::values::specified::box_::BaselineSource;
use style::values::specified::text::{TextAlignKeyword, TextDecorationLine};
use style::values::specified::text::TextAlignKeyword;
use style::values::specified::{TextAlignLast, TextJustify};
use text_run::{
TextRun, XI_LINE_BREAKING_CLASS_GL, XI_LINE_BREAKING_CLASS_WJ, XI_LINE_BREAKING_CLASS_ZWJ,
@ -118,6 +120,7 @@ use super::{
};
use crate::cell::ArcRefCell;
use crate::context::LayoutContext;
use crate::dom_traversal::NodeAndStyleInfo;
use crate::flow::CollapsibleWithParentStartMargin;
use crate::flow::float::{FloatBox, SequentialLayoutState};
use crate::formatting_contexts::{
@ -131,7 +134,7 @@ use crate::geom::{LogicalRect, LogicalVec2, ToLogical};
use crate::positioned::{AbsolutelyPositionedBox, PositioningContext};
use crate::sizing::{ComputeInlineContentSizes, ContentSizes, InlineContentSizesResult};
use crate::style_ext::{ComputedValuesExt, PaddingBorderMargin};
use crate::{ConstraintSpace, ContainingBlock, PropagatedBoxTreeData};
use crate::{ConstraintSpace, ContainingBlock, SharedStyle};
// From gfxFontConstants.h in Firefox.
static FONT_SUBSCRIPT_OFFSET_RATIO: f32 = 0.20;
@ -156,7 +159,9 @@ pub(crate) struct InlineFormattingContext {
/// context in order to avoid duplicating this information.
pub font_metrics: Vec<FontKeyAndMetrics>,
pub(super) text_decoration_line: TextDecorationLine,
/// The [`SharedInlineStyles`] for the root of this [`InlineFormattingContext`] that are used to
/// share styles with all [`TextRun`] children.
pub(super) shared_inline_styles: SharedInlineStyles,
/// Whether this IFC contains the 1st formatted line of an element:
/// <https://www.w3.org/TR/css-pseudo-4/#first-formatted-line>.
@ -173,6 +178,25 @@ pub(crate) struct InlineFormattingContext {
pub(super) has_right_to_left_content: bool,
}
/// [`TextRun`] and `TextFragment`s need a handle on their parent inline box (or inline
/// formatting context root)'s style. In order to implement incremental layout, these are
/// wrapped in [`SharedStyle`]. This allows updating the parent box tree element without
/// updating every single descendant box tree node and fragment.
#[derive(Clone, Debug, MallocSizeOf)]
pub(crate) struct SharedInlineStyles {
pub style: SharedStyle,
pub selected: SharedStyle,
}
impl From<&NodeAndStyleInfo<'_>> for SharedInlineStyles {
fn from(info: &NodeAndStyleInfo) -> Self {
Self {
style: SharedStyle::new(info.style.clone()),
selected: SharedStyle::new(info.get_selected_style()),
}
}
}
/// A collection of data used to cache [`FontMetrics`] in the [`InlineFormattingContext`]
#[derive(Debug, MallocSizeOf)]
pub(crate) struct FontKeyAndMetrics {
@ -190,15 +214,43 @@ pub(crate) enum InlineItem {
ArcRefCell<AbsolutelyPositionedBox>,
usize, /* offset_in_text */
),
OutOfFlowFloatBox(#[conditional_malloc_size_of] Arc<FloatBox>),
OutOfFlowFloatBox(ArcRefCell<FloatBox>),
Atomic(
#[conditional_malloc_size_of] Arc<IndependentFormattingContext>,
ArcRefCell<IndependentFormattingContext>,
usize, /* offset_in_text */
Level, /* bidi_level */
),
}
impl InlineItem {
pub(crate) fn repair_style(
&self,
context: &SharedStyleContext,
node: &ServoLayoutNode,
new_style: &Arc<ComputedValues>,
) {
match self {
InlineItem::StartInlineBox(inline_box) => {
inline_box.borrow_mut().repair_style(node, new_style);
},
InlineItem::EndInlineBox => {},
// TextRun holds a handle the `InlineSharedStyles` which is updated when repairing inline box
// and `display: contents` styles.
InlineItem::TextRun(..) => {},
InlineItem::OutOfFlowAbsolutelyPositionedBox(positioned_box, ..) => positioned_box
.borrow_mut()
.context
.repair_style(context, node, new_style),
InlineItem::OutOfFlowFloatBox(float_box) => float_box
.borrow_mut()
.contents
.repair_style(context, node, new_style),
InlineItem::Atomic(atomic, ..) => {
atomic.borrow_mut().repair_style(context, node, new_style)
},
}
}
pub(crate) fn invalidate_cached_fragment(&self) {
match self {
InlineItem::StartInlineBox(inline_box) => {
@ -212,11 +264,14 @@ impl InlineItem {
.base
.invalidate_cached_fragment();
},
InlineItem::OutOfFlowFloatBox(float_box) => {
float_box.contents.base.invalidate_cached_fragment()
},
InlineItem::OutOfFlowFloatBox(float_box) => float_box
.borrow()
.contents
.base
.invalidate_cached_fragment(),
InlineItem::Atomic(independent_formatting_context, ..) => {
independent_formatting_context
.borrow()
.base
.invalidate_cached_fragment();
},
@ -232,9 +287,11 @@ impl InlineItem {
InlineItem::OutOfFlowAbsolutelyPositionedBox(positioned_box, ..) => {
positioned_box.borrow().context.base.fragments()
},
InlineItem::OutOfFlowFloatBox(float_box) => float_box.contents.base.fragments(),
InlineItem::OutOfFlowFloatBox(float_box) => {
float_box.borrow().contents.base.fragments()
},
InlineItem::Atomic(independent_formatting_context, ..) => {
independent_formatting_context.base.fragments()
independent_formatting_context.borrow().base.fragments()
},
}
}
@ -569,12 +626,6 @@ pub(super) struct InlineContainerState {
/// this inline box on the current line OR any previous line.
has_content: RefCell<bool>,
/// Indicates whether this nesting level have text decorations in effect.
/// From <https://drafts.csswg.org/css-text-decor/#line-decoration>
// "When specified on or propagated to a block container that establishes
// an IFC..."
text_decoration_line: TextDecorationLine,
/// The block size contribution of this container's default font ie the size of the
/// "strut." Whether this is integrated into the [`Self::nested_strut_block_sizes`]
/// depends on the line-height quirk described in
@ -744,7 +795,7 @@ impl InlineFormattingContextLayout<'_> {
self.containing_block,
self.layout_context,
self.current_inline_container_state(),
inline_box.is_last_fragment,
inline_box.is_last_split,
inline_box
.default_font_index
.map(|index| &self.ifc.font_metrics[index].metrics),
@ -773,7 +824,7 @@ impl InlineFormattingContextLayout<'_> {
);
}
if inline_box.is_first_fragment {
if inline_box.is_first_split {
self.current_line_segment.inline_size += inline_box_state.pbm.padding.inline_start +
inline_box_state.pbm.border.inline_start +
inline_box_state.pbm.margin.inline_start.auto_is(Au::zero);
@ -958,6 +1009,7 @@ impl InlineFormattingContextLayout<'_> {
.as_physical(Some(self.containing_block));
self.fragments
.push(Fragment::Positioning(PositioningFragment::new_anonymous(
self.root_nesting_level.style.clone(),
physical_line_rect,
fragments,
)));
@ -1313,7 +1365,7 @@ impl InlineFormattingContextLayout<'_> {
) {
let inline_advance = glyph_store.total_advance();
let flags = if glyph_store.is_whitespace() {
SegmentContentFlags::from(text_run.parent_style.get_inherited_text())
SegmentContentFlags::from(text_run.inline_styles.style.borrow().get_inherited_text())
} else {
SegmentContentFlags::empty()
};
@ -1398,13 +1450,11 @@ impl InlineFormattingContextLayout<'_> {
TextRunLineItem {
text: vec![glyph_store],
base_fragment_info: text_run.base_fragment_info,
parent_style: text_run.parent_style.clone(),
inline_styles: text_run.inline_styles.clone(),
font_metrics,
font_key: ifc_font_info.key,
text_decoration_line: self.current_inline_container_state().text_decoration_line,
bidi_level,
selection_range,
selected_style: text_run.selected_style.clone(),
},
));
}
@ -1596,7 +1646,6 @@ impl InlineFormattingContext {
pub(super) fn new_with_builder(
builder: InlineFormattingContextBuilder,
layout_context: &LayoutContext,
propagated_data: PropagatedBoxTreeData,
has_first_formatted_line: bool,
is_single_line_text_input: bool,
starting_bidi_level: Level,
@ -1647,7 +1696,11 @@ impl InlineFormattingContext {
inline_items: builder.inline_items,
inline_boxes: builder.inline_boxes,
font_metrics,
text_decoration_line: propagated_data.text_decoration,
shared_inline_styles: builder
.shared_inline_styles_stack
.last()
.expect("Should have at least one SharedInlineStyle for the root of an IFC")
.clone(),
has_first_formatted_line,
contains_floats: builder.contains_floats,
is_single_line_text_input,
@ -1655,6 +1708,11 @@ impl InlineFormattingContext {
}
}
pub(crate) fn repair_style(&self, node: &ServoLayoutNode, new_style: &Arc<ComputedValues>) {
*self.shared_inline_styles.style.borrow_mut() = new_style.clone();
*self.shared_inline_styles.selected.borrow_mut() = node.to_threadsafe().selected_style();
}
pub(super) fn layout(
&self,
layout_context: &LayoutContext,
@ -1712,7 +1770,6 @@ impl InlineFormattingContext {
style.to_arc(),
inline_container_state_flags,
None, /* parent_container */
self.text_decoration_line,
default_font_metrics.as_ref(),
),
inline_box_state_stack: Vec::new(),
@ -1751,7 +1808,7 @@ impl InlineFormattingContext {
InlineItem::EndInlineBox => layout.finish_inline_box(),
InlineItem::TextRun(run) => run.borrow().layout_into_line_items(&mut layout),
InlineItem::Atomic(atomic_formatting_context, offset_in_text, bidi_level) => {
atomic_formatting_context.layout_into_line_items(
atomic_formatting_context.borrow().layout_into_line_items(
&mut layout,
*offset_in_text,
*bidi_level,
@ -1766,7 +1823,7 @@ impl InlineFormattingContext {
));
},
InlineItem::OutOfFlowFloatBox(float_box) => {
float_box.layout_into_line_items(&mut layout);
float_box.borrow().layout_into_line_items(&mut layout);
},
}
}
@ -1810,10 +1867,8 @@ impl InlineContainerState {
style: Arc<ComputedValues>,
flags: InlineContainerStateFlags,
parent_container: Option<&InlineContainerState>,
parent_text_decoration_line: TextDecorationLine,
font_metrics: Option<&FontMetrics>,
) -> Self {
let text_decoration_line = parent_text_decoration_line | style.clone_text_decoration_line();
let font_metrics = font_metrics.cloned().unwrap_or_else(FontMetrics::empty);
let line_height = line_height(
&style,
@ -1850,7 +1905,6 @@ impl InlineContainerState {
style,
flags,
has_content: RefCell::new(false),
text_decoration_line,
nested_strut_block_sizes: nested_block_sizes,
strut_block_sizes,
baseline_offset,
@ -2004,8 +2058,7 @@ impl IndependentFormattingContext {
bidi_level: Level,
) {
// We need to know the inline size of the atomic before deciding whether to do the line break.
let mut child_positioning_context = PositioningContext::new_for_style(self.style())
.unwrap_or_else(|| PositioningContext::new_for_subtree(true));
let mut child_positioning_context = PositioningContext::default();
let IndependentFloatOrAtomicLayoutResult {
mut fragment,
baselines,
@ -2349,10 +2402,10 @@ impl<'layout_data> ContentSizesComputation<'layout_data> {
.auto_is(Au::zero);
let pbm = margin + padding + border;
if inline_box.is_first_fragment {
if inline_box.is_first_split {
self.add_inline_size(pbm.inline_start);
}
if inline_box.is_last_fragment {
if inline_box.is_last_split {
self.ending_inline_pbm_stack.push(pbm.inline_end);
} else {
self.ending_inline_pbm_stack.push(Au::zero());
@ -2364,8 +2417,9 @@ impl<'layout_data> ContentSizesComputation<'layout_data> {
},
InlineItem::TextRun(text_run) => {
let text_run = &*text_run.borrow();
let parent_style = text_run.inline_styles.style.borrow();
for segment in text_run.shaped_text.iter() {
let style_text = text_run.parent_style.get_inherited_text();
let style_text = parent_style.get_inherited_text();
let can_wrap = style_text.text_wrap_mode == TextWrapMode::Wrap;
// TODO: This should take account whether or not the first and last character prevent
@ -2429,7 +2483,7 @@ impl<'layout_data> ContentSizesComputation<'layout_data> {
let InlineContentSizesResult {
sizes: outer,
depends_on_block_constraints,
} = atomic.outer_inline_content_sizes(
} = atomic.borrow().outer_inline_content_sizes(
self.layout_context,
&self.constraint_space.into(),
&LogicalVec2::zero(),

View file

@ -26,7 +26,7 @@ use unicode_script::Script;
use xi_unicode::linebreak_property;
use super::line_breaker::LineBreaker;
use super::{FontKeyAndMetrics, InlineFormattingContextLayout};
use super::{FontKeyAndMetrics, InlineFormattingContextLayout, SharedInlineStyles};
use crate::fragment_tree::BaseFragmentInfo;
// These constants are the xi-unicode line breaking classes that are defined in
@ -37,22 +37,6 @@ pub(crate) const XI_LINE_BREAKING_CLASS_ZW: u8 = 28;
pub(crate) const XI_LINE_BREAKING_CLASS_WJ: u8 = 30;
pub(crate) const XI_LINE_BREAKING_CLASS_ZWJ: u8 = 42;
/// <https://www.w3.org/TR/css-display-3/#css-text-run>
#[derive(Debug, MallocSizeOf)]
pub(crate) struct TextRun {
pub base_fragment_info: BaseFragmentInfo,
#[conditional_malloc_size_of]
pub parent_style: Arc<ComputedValues>,
pub text_range: Range<usize>,
/// The text of this [`TextRun`] with a font selected, broken into unbreakable
/// segments, and shaped.
pub shaped_text: Vec<TextRunSegment>,
pub selection_range: Option<ServoRange<ByteIndex>>,
#[conditional_malloc_size_of]
pub selected_style: Arc<ComputedValues>,
}
// There are two reasons why we might want to break at the start:
//
// 1. The line breaker told us that a break was necessary between two separate
@ -334,21 +318,49 @@ impl TextRunSegment {
}
}
/// A single [`TextRun`] for the box tree. These are all descendants of
/// [`super::InlineBox`] or the root of the [`super::InlineFormattingContext`]. During
/// box tree construction, text is split into [`TextRun`]s based on their font, script,
/// etc. When these are created text is already shaped.
///
/// <https://www.w3.org/TR/css-display-3/#css-text-run>
#[derive(Debug, MallocSizeOf)]
pub(crate) struct TextRun {
/// The [`BaseFragmentInfo`] for this [`TextRun`]. Usually this comes from the
/// original text node in the DOM for the text.
pub base_fragment_info: BaseFragmentInfo,
/// The [`crate::SharedStyle`] from this [`TextRun`]s parent element. This is
/// shared so that incremental layout can simply update the parent element and
/// this [`TextRun`] will be updated automatically.
pub inline_styles: SharedInlineStyles,
/// The range of text in [`super::InlineFormattingContext::text_content`] of the
/// [`super::InlineFormattingContext`] that owns this [`TextRun`]. These are UTF-8 offsets.
pub text_range: Range<usize>,
/// The text of this [`TextRun`] with a font selected, broken into unbreakable
/// segments, and shaped.
pub shaped_text: Vec<TextRunSegment>,
/// The selection range for the DOM text node that originated this [`TextRun`]. This
/// comes directly from the DOM.
pub selection_range: Option<ServoRange<ByteIndex>>,
}
impl TextRun {
pub(crate) fn new(
base_fragment_info: BaseFragmentInfo,
parent_style: Arc<ComputedValues>,
inline_styles: SharedInlineStyles,
text_range: Range<usize>,
selection_range: Option<ServoRange<ByteIndex>>,
selected_style: Arc<ComputedValues>,
) -> Self {
Self {
base_fragment_info,
parent_style,
inline_styles,
text_range,
shaped_text: Vec::new(),
selection_range,
selected_style,
}
}
@ -360,11 +372,12 @@ impl TextRun {
font_cache: &mut Vec<FontKeyAndMetrics>,
bidi_info: &BidiInfo,
) {
let inherited_text_style = self.parent_style.get_inherited_text().clone();
let parent_style = self.inline_styles.style.borrow().clone();
let inherited_text_style = parent_style.get_inherited_text().clone();
let letter_spacing = inherited_text_style
.letter_spacing
.0
.resolve(self.parent_style.clone_font().font_size.computed_size());
.resolve(parent_style.clone_font().font_size.computed_size());
let letter_spacing = if letter_spacing.px() != 0. {
Some(app_units::Au::from(letter_spacing))
} else {
@ -384,7 +397,13 @@ impl TextRun {
let style_word_spacing: Option<Au> = specified_word_spacing.to_length().map(|l| l.into());
let segments = self
.segment_text_by_font(formatting_context_text, font_context, font_cache, bidi_info)
.segment_text_by_font(
formatting_context_text,
font_context,
font_cache,
bidi_info,
&parent_style,
)
.into_iter()
.map(|(mut segment, font)| {
let word_spacing = style_word_spacing.unwrap_or_else(|| {
@ -407,7 +426,7 @@ impl TextRun {
};
segment.shape_text(
&self.parent_style,
&parent_style,
formatting_context_text,
linebreaker,
&shaping_options,
@ -430,8 +449,9 @@ impl TextRun {
font_context: &FontContext,
font_cache: &mut Vec<FontKeyAndMetrics>,
bidi_info: &BidiInfo,
parent_style: &Arc<ComputedValues>,
) -> Vec<(TextRunSegment, FontRef)> {
let font_group = font_context.font_group(self.parent_style.clone_font());
let font_group = font_context.font_group(parent_style.clone_font());
let mut current: Option<(TextRunSegment, FontRef)> = None;
let mut results = Vec::new();

View file

@ -9,9 +9,11 @@ use app_units::{Au, MAX_AU};
use inline::InlineFormattingContext;
use malloc_size_of_derive::MallocSizeOf;
use rayon::iter::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator};
use script::layout_dom::ServoLayoutNode;
use servo_arc::Arc;
use style::Zero;
use style::computed_values::clear::T as StyleClear;
use style::context::SharedStyleContext;
use style::logical_geometry::Direction;
use style::properties::ComputedValues;
use style::servo::selector_parser::PseudoElement;
@ -21,6 +23,7 @@ use style::values::specified::{Display, TextAlignKeyword};
use crate::cell::ArcRefCell;
use crate::context::LayoutContext;
use crate::dom::NodeExt;
use crate::flow::float::{
Clear, ContainingBlockPositionInfo, FloatBox, FloatSide, PlacementAmongFloats,
SequentialLayoutState,
@ -33,8 +36,8 @@ use crate::fragment_tree::{
BaseFragmentInfo, BoxFragment, CollapsedBlockMargins, CollapsedMargin, Fragment, FragmentFlags,
};
use crate::geom::{
AuOrAuto, LogicalRect, LogicalSides, LogicalSides1D, LogicalVec2, PhysicalPoint, PhysicalRect,
PhysicalSides, Size, Sizes, ToLogical, ToLogicalWithContainingBlock,
AuOrAuto, LazySize, LogicalRect, LogicalSides, LogicalSides1D, LogicalVec2, PhysicalPoint,
PhysicalRect, PhysicalSides, Size, Sizes, ToLogical, ToLogicalWithContainingBlock,
};
use crate::layout_box_base::{CacheableLayoutResult, LayoutBoxBase};
use crate::positioned::{AbsolutelyPositionedBox, PositioningContext, PositioningContextLength};
@ -52,7 +55,7 @@ pub mod inline;
mod root;
pub(crate) use construct::BlockContainerBuilder;
pub use root::{BoxTree, CanvasBackground};
pub use root::BoxTree;
#[derive(Debug, MallocSizeOf)]
pub(crate) struct BlockFormattingContext {
@ -75,6 +78,15 @@ impl BlockContainer {
BlockContainer::InlineFormattingContext(context) => context.contains_floats,
}
}
pub(crate) fn repair_style(&mut self, node: &ServoLayoutNode, new_style: &Arc<ComputedValues>) {
match self {
BlockContainer::BlockLevelBoxes(..) => {},
BlockContainer::InlineFormattingContext(inline_formatting_context) => {
inline_formatting_context.repair_style(node, new_style)
},
}
}
}
#[derive(Debug, MallocSizeOf)]
@ -91,6 +103,37 @@ pub(crate) enum BlockLevelBox {
}
impl BlockLevelBox {
pub(crate) fn repair_style(
&mut self,
context: &SharedStyleContext,
node: &ServoLayoutNode,
new_style: &Arc<ComputedValues>,
) {
self.with_base_mut(|base| {
base.repair_style(new_style);
});
match self {
BlockLevelBox::Independent(independent_formatting_context) => {
independent_formatting_context.repair_style(context, node, new_style)
},
BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(positioned_box) => positioned_box
.borrow_mut()
.context
.repair_style(context, node, new_style),
BlockLevelBox::OutOfFlowFloatBox(float_box) => {
float_box.contents.repair_style(context, node, new_style)
},
BlockLevelBox::OutsideMarker(outside_marker) => {
outside_marker.repair_style(context, node, new_style)
},
BlockLevelBox::SameFormattingContextBlock { base, contents, .. } => {
base.repair_style(new_style);
contents.repair_style(node, new_style);
},
}
}
pub(crate) fn invalidate_cached_fragment(&self) {
self.with_base(LayoutBoxBase::invalidate_cached_fragment);
}
@ -113,6 +156,20 @@ impl BlockLevelBox {
}
}
pub(crate) fn with_base_mut<T>(&mut self, callback: impl Fn(&mut LayoutBoxBase) -> T) -> T {
match self {
BlockLevelBox::Independent(independent_formatting_context) => {
callback(&mut independent_formatting_context.base)
},
BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(positioned_box) => {
callback(&mut positioned_box.borrow_mut().context.base)
},
BlockLevelBox::OutOfFlowFloatBox(float_box) => callback(&mut float_box.contents.base),
BlockLevelBox::OutsideMarker(outside_marker) => callback(&mut outside_marker.base),
BlockLevelBox::SameFormattingContextBlock { base, .. } => callback(base),
}
}
fn contains_floats(&self) -> bool {
match self {
BlockLevelBox::SameFormattingContextBlock {
@ -249,7 +306,6 @@ pub(crate) struct CollapsibleWithParentStartMargin(bool);
/// for a list that has `list-style-position: outside`.
#[derive(Debug, MallocSizeOf)]
pub(crate) struct OutsideMarker {
#[conditional_malloc_size_of]
pub list_item_style: Arc<ComputedValues>,
pub base: LayoutBoxBase,
pub block_container: BlockContainer,
@ -361,6 +417,16 @@ impl OutsideMarker {
None,
)))
}
fn repair_style(
&mut self,
context: &SharedStyleContext,
node: &ServoLayoutNode,
new_style: &Arc<ComputedValues>,
) {
self.list_item_style = node.style(context);
self.base.repair_style(new_style);
}
}
impl BlockFormattingContext {
@ -421,6 +487,10 @@ impl BlockFormattingContext {
pub(crate) fn layout_style<'a>(&self, base: &'a LayoutBoxBase) -> LayoutStyle<'a> {
LayoutStyle::Default(&base.style)
}
pub(crate) fn repair_style(&mut self, node: &ServoLayoutNode, new_style: &Arc<ComputedValues>) {
self.contents.repair_style(node, new_style);
}
}
/// Finds the min/max-content inline size of the block-level children of a block container.
@ -689,16 +759,13 @@ fn layout_block_level_children_in_parallel(
placement_state: &mut PlacementState,
ignore_block_margins_for_stretch: LogicalSides1D<bool>,
) -> Vec<Fragment> {
let collects_for_nearest_positioned_ancestor =
positioning_context.collects_for_nearest_positioned_ancestor();
let mut layout_results: Vec<(Fragment, PositioningContext)> =
Vec::with_capacity(child_boxes.len());
child_boxes
.par_iter()
.map(|child_box| {
let mut child_positioning_context =
PositioningContext::new_for_subtree(collects_for_nearest_positioned_ancestor);
let mut child_positioning_context = PositioningContext::default();
let fragment = child_box.borrow().layout(
layout_context,
&mut child_positioning_context,
@ -779,7 +846,7 @@ impl BlockLevelBox {
ArcRefCell::new(positioning_context.layout_maybe_position_relative_fragment(
layout_context,
containing_block,
&base.style,
base,
|positioning_context| {
layout_in_flow_non_replaced_block_level_same_formatting_context(
layout_context,
@ -798,7 +865,7 @@ impl BlockLevelBox {
positioning_context.layout_maybe_position_relative_fragment(
layout_context,
containing_block,
independent.style(),
&independent.base,
|positioning_context| {
independent.layout_in_flow_block_level(
layout_context,
@ -896,6 +963,7 @@ fn layout_in_flow_non_replaced_block_level_same_formatting_context(
block_sizes,
depends_on_block_constraints,
available_block_size,
justify_self,
} = solve_containing_block_padding_and_border_for_in_flow_box(
containing_block,
&layout_style,
@ -909,6 +977,7 @@ fn layout_in_flow_non_replaced_block_level_same_formatting_context(
containing_block,
&pbm,
containing_block_for_children.size.inline,
justify_self,
);
let computed_block_size = style.content_block_size();
@ -1154,6 +1223,7 @@ impl IndependentNonReplacedContents {
block_sizes,
depends_on_block_constraints,
available_block_size,
justify_self,
} = solve_containing_block_padding_and_border_for_in_flow_box(
containing_block,
&layout_style,
@ -1161,6 +1231,15 @@ impl IndependentNonReplacedContents {
ignore_block_margins_for_stretch,
);
let lazy_block_size = LazySize::new(
&block_sizes,
Direction::Block,
Size::FitContent,
Au::zero,
available_block_size,
layout_style.is_table(),
);
let layout = self.layout(
layout_context,
positioning_context,
@ -1168,24 +1247,18 @@ impl IndependentNonReplacedContents {
containing_block,
base,
false, /* depends_on_block_constraints */
&lazy_block_size,
);
let inline_size = layout
.content_inline_size_for_table
.unwrap_or(containing_block_for_children.size.inline);
let block_size = block_sizes.resolve(
Direction::Block,
Size::FitContent,
Au::zero,
available_block_size,
|| layout.content_block_size.into(),
layout_style.is_table(),
);
let block_size = lazy_block_size.resolve(|| layout.content_block_size);
let ResolvedMargins {
margin,
effective_margin_inline_start,
} = solve_margins(containing_block, &pbm, inline_size);
} = solve_margins(containing_block, &pbm, inline_size, justify_self);
let content_rect = LogicalRect {
start_corner: LogicalVec2 {
@ -1300,17 +1373,12 @@ impl IndependentNonReplacedContents {
.sizes
};
// TODO: the automatic inline size should take `justify-self` into account.
let justify_self = resolve_justify_self(style, containing_block.style);
let is_table = self.is_table();
let automatic_inline_size = if is_table {
Size::FitContent
} else {
Size::Stretch
};
let compute_inline_size = |stretch_size| {
content_box_sizes.inline.resolve(
Direction::Inline,
automatic_inline_size,
automatic_inline_size(justify_self, is_table),
Au::zero,
Some(stretch_size),
get_inline_content_sizes,
@ -1318,16 +1386,14 @@ impl IndependentNonReplacedContents {
)
};
let compute_block_size = |layout: &CacheableLayoutResult| {
content_box_sizes.block.resolve(
Direction::Block,
Size::FitContent,
Au::zero,
available_block_size,
|| layout.content_block_size.into(),
is_table,
)
};
let lazy_block_size = LazySize::new(
&content_box_sizes.block,
Direction::Block,
Size::FitContent,
Au::zero,
available_block_size,
is_table,
);
// The final inline size can depend on the available space, which depends on where
// we are placing the box, since floats reduce the available space.
@ -1356,10 +1422,11 @@ impl IndependentNonReplacedContents {
containing_block,
base,
false, /* depends_on_block_constraints */
&lazy_block_size,
);
content_size = LogicalVec2 {
block: compute_block_size(&layout),
block: lazy_block_size.resolve(|| layout.content_block_size),
inline: layout.content_inline_size_for_table.unwrap_or(inline_size),
};
@ -1421,6 +1488,7 @@ impl IndependentNonReplacedContents {
containing_block,
base,
false, /* depends_on_block_constraints */
&lazy_block_size,
);
let inline_size = if let Some(inline_size) = layout.content_inline_size_for_table {
@ -1434,7 +1502,7 @@ impl IndependentNonReplacedContents {
proposed_inline_size
};
content_size = LogicalVec2 {
block: compute_block_size(&layout),
block: lazy_block_size.resolve(|| layout.content_block_size),
inline: inline_size,
};
@ -1472,6 +1540,7 @@ impl IndependentNonReplacedContents {
&pbm,
content_size.inline + pbm.padding_border_sums.inline,
placement_rect,
justify_self,
);
let margin = LogicalSides {
@ -1558,6 +1627,7 @@ impl ReplacedContents {
let effective_margin_inline_start;
let (margin_block_start, margin_block_end) =
solve_block_margins_for_in_flow_block_level(pbm);
let justify_self = resolve_justify_self(&base.style, containing_block.style);
let containing_block_writing_mode = containing_block.style.writing_mode;
let physical_content_size = content_size.to_physical_size(containing_block_writing_mode);
@ -1597,6 +1667,7 @@ impl ReplacedContents {
pbm,
size.inline,
placement_rect,
justify_self,
);
// Clearance prevents margin collapse between this block and previous ones,
@ -1620,6 +1691,7 @@ impl ReplacedContents {
containing_block,
pbm,
content_size.inline,
justify_self,
);
};
@ -1671,6 +1743,7 @@ struct ContainingBlockPaddingAndBorder<'a> {
block_sizes: Sizes,
depends_on_block_constraints: bool,
available_block_size: Option<Au>,
justify_self: AlignFlags,
}
struct ResolvedMargins {
@ -1719,6 +1792,9 @@ fn solve_containing_block_padding_and_border_for_in_flow_box<'a>(
// The available block size may actually be definite, but it should be irrelevant
// since the sizing properties are set to their initial value.
available_block_size: None,
// The initial `justify-self` is `auto`, but use `normal` (behaving as `stretch`).
// This is being discussed in <https://github.com/w3c/csswg-drafts/issues/11461>.
justify_self: AlignFlags::NORMAL,
};
}
@ -1755,16 +1831,11 @@ fn solve_containing_block_padding_and_border_for_in_flow_box<'a>(
None, /* TODO: support preferred aspect ratios on non-replaced boxes */
))
};
// TODO: the automatic inline size should take `justify-self` into account.
let justify_self = resolve_justify_self(style, containing_block.style);
let is_table = layout_style.is_table();
let automatic_inline_size = if is_table {
Size::FitContent
} else {
Size::Stretch
};
let inline_size = content_box_sizes.inline.resolve(
Direction::Inline,
automatic_inline_size,
automatic_inline_size(justify_self, is_table),
Au::zero,
Some(available_inline_size),
get_inline_content_sizes,
@ -1793,6 +1864,7 @@ fn solve_containing_block_padding_and_border_for_in_flow_box<'a>(
block_sizes: content_box_sizes.block,
depends_on_block_constraints,
available_block_size,
justify_self,
}
}
@ -1804,9 +1876,15 @@ fn solve_margins(
containing_block: &ContainingBlock<'_>,
pbm: &PaddingBorderMargin,
inline_size: Au,
justify_self: AlignFlags,
) -> ResolvedMargins {
let (inline_margins, effective_margin_inline_start) =
solve_inline_margins_for_in_flow_block_level(containing_block, pbm, inline_size);
solve_inline_margins_for_in_flow_block_level(
containing_block,
pbm,
inline_size,
justify_self,
);
let block_margins = solve_block_margins_for_in_flow_block_level(pbm);
ResolvedMargins {
margin: LogicalSides {
@ -1829,14 +1907,63 @@ fn solve_block_margins_for_in_flow_block_level(pbm: &PaddingBorderMargin) -> (Au
)
}
/// This is supposed to handle 'justify-self', but no browser supports it on block boxes.
/// Instead, `<center>` and `<div align>` are implemented via internal 'text-align' values.
/// Resolves the `justify-self` value, preserving flags.
fn resolve_justify_self(style: &ComputedValues, parent_style: &ComputedValues) -> AlignFlags {
let is_ltr = |style: &ComputedValues| style.writing_mode.line_left_is_inline_start();
let alignment = match style.clone_justify_self().0.0 {
AlignFlags::AUTO => parent_style.clone_justify_items().computed.0,
alignment => alignment,
};
let alignment_value = match alignment.value() {
AlignFlags::LEFT if is_ltr(parent_style) => AlignFlags::START,
AlignFlags::LEFT => AlignFlags::END,
AlignFlags::RIGHT if is_ltr(parent_style) => AlignFlags::END,
AlignFlags::RIGHT => AlignFlags::START,
AlignFlags::SELF_START if is_ltr(parent_style) == is_ltr(style) => AlignFlags::START,
AlignFlags::SELF_START => AlignFlags::END,
AlignFlags::SELF_END if is_ltr(parent_style) == is_ltr(style) => AlignFlags::END,
AlignFlags::SELF_END => AlignFlags::START,
alignment_value => alignment_value,
};
alignment.flags() | alignment_value
}
/// Determines the automatic size for the inline axis of a block-level box.
/// <https://drafts.csswg.org/css-sizing-3/#automatic-size>
#[inline]
fn automatic_inline_size<T>(justify_self: AlignFlags, is_table: bool) -> Size<T> {
match justify_self {
AlignFlags::STRETCH => Size::Stretch,
AlignFlags::NORMAL if !is_table => Size::Stretch,
_ => Size::FitContent,
}
}
/// Justifies a block-level box, distributing the free space according to `justify-self`.
/// Note `<center>` and `<div align>` are implemented via internal 'text-align' values,
/// which are also handled here.
/// The provided free space should already take margins into account. In particular,
/// it should be zero if there is an auto margin.
/// <https://drafts.csswg.org/css-align/#justify-block>
fn justify_self_alignment(containing_block: &ContainingBlock, free_space: Au) -> Au {
fn justify_self_alignment(
containing_block: &ContainingBlock,
free_space: Au,
justify_self: AlignFlags,
) -> Au {
let mut alignment = justify_self.value();
let is_safe = justify_self.flags() == AlignFlags::SAFE || alignment == AlignFlags::NORMAL;
if is_safe && free_space <= Au::zero() {
alignment = AlignFlags::START
}
match alignment {
AlignFlags::NORMAL => {},
AlignFlags::CENTER => return free_space / 2,
AlignFlags::END => return free_space,
_ => return Au::zero(),
}
// For `justify-self: normal`, fall back to the special 'text-align' values.
let style = containing_block.style;
debug_assert!(free_space >= Au::zero());
match style.clone_text_align() {
TextAlignKeyword::MozCenter => free_space / 2,
TextAlignKeyword::MozLeft if !style.writing_mode.line_left_is_inline_start() => free_space,
@ -1861,6 +1988,7 @@ fn solve_inline_margins_for_in_flow_block_level(
containing_block: &ContainingBlock,
pbm: &PaddingBorderMargin,
inline_size: Au,
justify_self: AlignFlags,
) -> ((Au, Au), Au) {
let free_space = containing_block.size.inline - pbm.padding_border_sums.inline - inline_size;
let mut justification = Au::zero();
@ -1878,8 +2006,8 @@ fn solve_inline_margins_for_in_flow_block_level(
// But here we may still have some free space to perform 'justify-self' alignment.
// This aligns the margin box within the containing block, or in other words,
// aligns the border box within the margin-shrunken containing block.
let free_space = Au::zero().max(free_space - start - end);
justification = justify_self_alignment(containing_block, free_space);
justification =
justify_self_alignment(containing_block, free_space - start - end, justify_self);
(start, end)
},
};
@ -1902,6 +2030,7 @@ fn solve_inline_margins_avoiding_floats(
pbm: &PaddingBorderMargin,
inline_size: Au,
placement_rect: LogicalRect<Au>,
justify_self: AlignFlags,
) -> ((Au, Au), Au) {
let free_space = placement_rect.size.inline - inline_size;
debug_assert!(free_space >= Au::zero());
@ -1922,7 +2051,7 @@ fn solve_inline_margins_avoiding_floats(
// and Blink and WebKit are broken anyways. So we match Gecko instead: this aligns
// the border box within the instersection of the float-shrunken containing-block
// and the margin-shrunken containing-block.
justification = justify_self_alignment(containing_block, free_space);
justification = justify_self_alignment(containing_block, free_space, justify_self);
(start, end)
},
};
@ -2182,7 +2311,9 @@ fn block_size_is_zero_or_intrinsic(size: &StyleSize, containing_block: &Containi
lp.is_definitely_zero() ||
(lp.0.has_percentage() && !containing_block.size.block.is_definite())
},
StyleSize::AnchorSizeFunction(_) => unreachable!("anchor-size() should be disabled"),
StyleSize::AnchorSizeFunction(_) | StyleSize::AnchorContainingCalcFunction(_) => {
unreachable!("anchor-size() should be disabled")
},
}
}
@ -2305,6 +2436,15 @@ impl IndependentFormattingContext {
"Mixed horizontal and vertical writing modes are not supported yet"
);
let lazy_block_size = LazySize::new(
&content_box_sizes_and_pbm.content_box_sizes.block,
Direction::Block,
Size::FitContent,
Au::zero,
available_block_size,
is_table,
);
let independent_layout = non_replaced.layout(
layout_context,
child_positioning_context,
@ -2312,18 +2452,12 @@ impl IndependentFormattingContext {
containing_block,
&self.base,
false, /* depends_on_block_constraints */
&lazy_block_size,
);
let inline_size = independent_layout
.content_inline_size_for_table
.unwrap_or(inline_size);
let block_size = content_box_sizes_and_pbm.content_box_sizes.block.resolve(
Direction::Block,
Size::FitContent,
Au::zero,
available_block_size,
|| independent_layout.content_block_size.into(),
is_table,
);
let block_size = lazy_block_size.resolve(|| independent_layout.content_block_size);
let content_size = LogicalVec2 {
block: block_size,

View file

@ -5,13 +5,16 @@
use app_units::Au;
use atomic_refcell::AtomicRef;
use compositing_traits::display_list::AxesScrollSensitivity;
use euclid::Rect;
use euclid::default::Size2D as UntypedSize2D;
use malloc_size_of_derive::MallocSizeOf;
use script::layout_dom::ServoLayoutNode;
use script_layout_interface::wrapper_traits::{
LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode,
};
use script_layout_interface::{LayoutElementType, LayoutNodeType};
use servo_arc::Arc;
use style::dom::OpaqueNode;
use style::dom::{NodeInfo, TNode};
use style::properties::ComputedValues;
use style::values::computed::Overflow;
use style_traits::CSSPixel;
@ -26,10 +29,10 @@ use crate::flow::inline::InlineItem;
use crate::flow::{BlockContainer, BlockFormattingContext, BlockLevelBox};
use crate::formatting_contexts::IndependentFormattingContext;
use crate::fragment_tree::FragmentTree;
use crate::geom::{LogicalVec2, PhysicalPoint, PhysicalRect, PhysicalSize};
use crate::geom::{LogicalVec2, PhysicalRect, PhysicalSize};
use crate::positioned::{AbsolutelyPositionedBox, PositioningContext};
use crate::replaced::ReplacedContents;
use crate::style_ext::{ComputedValuesExt, Display, DisplayGeneratingBox, DisplayInside};
use crate::style_ext::{Display, DisplayGeneratingBox, DisplayInside};
use crate::taffy::{TaffyItemBox, TaffyItemBoxInner};
use crate::{DefiniteContainingBlock, PropagatedBoxTreeData};
@ -39,18 +42,12 @@ pub struct BoxTree {
/// There may be zero if that element has `display: none`.
root: BlockFormattingContext,
/// <https://drafts.csswg.org/css-backgrounds/#special-backgrounds>
canvas_background: CanvasBackground,
/// Whether or not the viewport should be sensitive to scrolling input events in two axes
viewport_scroll_sensitivity: AxesScrollSensitivity,
}
impl BoxTree {
pub fn construct<'dom, Node>(context: &LayoutContext, root_element: Node) -> Self
where
Node: 'dom + Copy + LayoutNode<'dom> + Send + Sync,
{
pub fn construct(context: &LayoutContext, root_element: ServoLayoutNode<'_>) -> Self {
let boxes = construct_for_root_element(context, root_element);
// Zero box for `:root { display: none }`, one for the root element otherwise.
@ -64,7 +61,7 @@ impl BoxTree {
// > none, user agents must instead apply the overflow-* values of the first such child
// > element to the viewport. The element from which the value is propagated must then have a
// > used overflow value of visible.
let root_style = root_element.style(context);
let root_style = root_element.style(context.shared_context());
let mut viewport_overflow_x = root_style.clone_overflow_x();
let mut viewport_overflow_y = root_style.clone_overflow_y();
@ -81,7 +78,7 @@ impl BoxTree {
continue;
}
let style = child.style(context);
let style = child.style(context.shared_context());
if !style.get_box().display.is_none() {
viewport_overflow_x = style.clone_overflow_x();
viewport_overflow_y = style.clone_overflow_y();
@ -98,7 +95,6 @@ impl BoxTree {
contents,
contains_floats,
},
canvas_background: CanvasBackground::for_root_element(context, root_element),
// From https://www.w3.org/TR/css-overflow-3/#overflow-propagation:
// > If visible is applied to the viewport, it must be interpreted as auto.
// > If clip is applied to the viewport, it must be interpreted as hidden.
@ -129,10 +125,7 @@ impl BoxTree {
/// * how intrinsic content sizes are computed eagerly makes it hard
/// to update those sizes for ancestors of the node from which we
/// made an incremental update.
pub fn update<'dom, Node>(context: &LayoutContext, mut dirty_node: Node) -> bool
where
Node: 'dom + Copy + LayoutNode<'dom> + Send + Sync,
{
pub fn update(context: &LayoutContext, mut dirty_node: ServoLayoutNode<'_>) -> bool {
#[allow(clippy::enum_variant_names)]
enum UpdatePoint {
AbsolutelyPositionedBlockLevelBox(ArcRefCell<BlockLevelBox>),
@ -141,12 +134,9 @@ impl BoxTree {
AbsolutelyPositionedTaffyLevelBox(ArcRefCell<TaffyItemBox>),
}
fn update_point<'dom, Node>(
node: Node,
) -> Option<(Arc<ComputedValues>, DisplayInside, UpdatePoint)>
where
Node: NodeExt<'dom>,
{
fn update_point(
node: ServoLayoutNode<'_>,
) -> Option<(Arc<ComputedValues>, DisplayInside, UpdatePoint)> {
if !node.is_element() {
return None;
}
@ -162,7 +152,7 @@ impl BoxTree {
return None;
}
let layout_data = node.layout_data()?;
let layout_data = NodeExt::layout_data(&node)?;
if layout_data.pseudo_before_box.borrow().is_some() {
return None;
}
@ -186,7 +176,7 @@ impl BoxTree {
let update_point =
match &*AtomicRef::filter_map(layout_data.self_box.borrow(), Option::as_ref)? {
LayoutBox::DisplayContents => return None,
LayoutBox::DisplayContents(..) => return None,
LayoutBox::BlockLevel(block_level_box) => match &*block_level_box.borrow() {
BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(_)
if box_style.position.is_absolutely_positioned() =>
@ -195,16 +185,17 @@ impl BoxTree {
},
_ => return None,
},
LayoutBox::InlineLevel(inline_level_box) => match &*inline_level_box.borrow() {
InlineItem::OutOfFlowAbsolutelyPositionedBox(_, text_offset_index)
if box_style.position.is_absolutely_positioned() =>
{
UpdatePoint::AbsolutelyPositionedInlineLevelBox(
inline_level_box.clone(),
*text_offset_index,
)
},
_ => return None,
LayoutBox::InlineLevel(inline_level_items) => {
let inline_level_box = inline_level_items.first()?;
let InlineItem::OutOfFlowAbsolutelyPositionedBox(_, text_offset_index) =
&*inline_level_box.borrow()
else {
return None;
};
UpdatePoint::AbsolutelyPositionedInlineLevelBox(
inline_level_box.clone(),
*text_offset_index,
)
},
LayoutBox::FlexLevel(flex_level_box) => match &*flex_level_box.borrow() {
FlexLevelBox::OutOfFlowAbsolutelyPositionedBox(_)
@ -300,11 +291,11 @@ impl BoxTree {
}
}
fn construct_for_root_element<'dom>(
fn construct_for_root_element(
context: &LayoutContext,
root_element: impl NodeExt<'dom>,
root_element: ServoLayoutNode<'_>,
) -> Vec<ArcRefCell<BlockLevelBox>> {
let info = NodeAndStyleInfo::new(root_element, root_element.style(context));
let info = NodeAndStyleInfo::new(root_element, root_element.style(context.shared_context()));
let box_style = info.style.get_box();
let display_inside = match Display::from(box_style.display) {
@ -325,7 +316,7 @@ fn construct_for_root_element<'dom>(
let contents = ReplacedContents::for_element(root_element, context)
.map_or_else(|| NonReplacedContents::OfElement.into(), Contents::Replaced);
let propagated_data = PropagatedBoxTreeData::default().union(&info.style);
let propagated_data = PropagatedBoxTreeData::default();
let root_box = if box_style.position.is_absolutely_positioned() {
BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(ArcRefCell::new(
AbsolutelyPositionedBox::construct(context, &info, display_inside, contents),
@ -359,7 +350,7 @@ impl BoxTree {
pub fn layout(
&self,
layout_context: &LayoutContext,
viewport: euclid::Size2D<f32, CSSPixel>,
viewport: UntypedSize2D<Au>,
) -> FragmentTree {
let style = layout_context
.style_context
@ -369,13 +360,8 @@ impl BoxTree {
// FIXME: use the documents mode:
// https://drafts.csswg.org/css-writing-modes/#principal-flow
let physical_containing_block = PhysicalRect::new(
PhysicalPoint::zero(),
PhysicalSize::new(
Au::from_f32_px(viewport.width),
Au::from_f32_px(viewport.height),
),
);
let physical_containing_block: Rect<Au, CSSPixel> =
PhysicalSize::from_untyped(viewport).into();
let initial_containing_block = DefiniteContainingBlock {
size: LogicalVec2 {
inline: physical_containing_block.size.width,
@ -384,8 +370,7 @@ impl BoxTree {
style,
};
let mut positioning_context =
PositioningContext::new_for_containing_block_for_all_descendants();
let mut positioning_context = PositioningContext::default();
let independent_layout = self.root.layout(
layout_context,
&mut positioning_context,
@ -410,7 +395,7 @@ impl BoxTree {
let scrollable_overflow = root_fragments
.iter()
.fold(PhysicalRect::zero(), |acc, child| {
let child_overflow = child.scrollable_overflow();
let child_overflow = child.scrollable_overflow_for_parent();
// https://drafts.csswg.org/css-overflow/#scrolling-direction
// We want to clip scrollable overflow on box-start and inline-start
@ -428,73 +413,12 @@ impl BoxTree {
acc.union(&child_overflow)
});
FragmentTree {
FragmentTree::new(
layout_context,
root_fragments,
scrollable_overflow,
initial_containing_block: physical_containing_block,
canvas_background: self.canvas_background.clone(),
viewport_scroll_sensitivity: self.viewport_scroll_sensitivity,
}
}
}
/// <https://drafts.csswg.org/css-backgrounds/#root-background>
#[derive(Clone, MallocSizeOf)]
pub struct CanvasBackground {
/// DOM node for the root element
pub root_element: OpaqueNode,
/// The element whose style the canvas takes background properties from (see next field).
/// This can be the root element (same as the previous field), or the HTML `<body>` element.
/// See <https://drafts.csswg.org/css-backgrounds/#body-background>
pub from_element: OpaqueNode,
/// The computed styles to take background properties from.
#[conditional_malloc_size_of]
pub style: Option<Arc<ComputedValues>>,
}
impl CanvasBackground {
fn for_root_element<'dom>(context: &LayoutContext, root_element: impl NodeExt<'dom>) -> Self {
let root_style = root_element.style(context);
let mut style = root_style;
let mut from_element = root_element;
// https://drafts.csswg.org/css-backgrounds/#body-background
// “if the computed value of background-image on the root element is none
// and its background-color is transparent”
if style.background_is_transparent() &&
// “For documents whose root element is an HTML `HTML` element
// or an XHTML `html` element”
root_element.type_id() == LayoutNodeType::Element(LayoutElementType::HTMLHtmlElement) &&
// Dont try to access styles for an unstyled subtree
!matches!(style.clone_display().into(), Display::None)
{
// “that elements first HTML `BODY` or XHTML `body` child element”
if let Some(body) = iter_child_nodes(root_element).find(|child| {
child.is_element() &&
child.type_id() ==
LayoutNodeType::Element(LayoutElementType::HTMLBodyElement)
}) {
style = body.style(context);
from_element = body;
}
}
Self {
root_element: root_element.opaque(),
from_element: from_element.opaque(),
// “However, if no boxes are generated for the element
// whose background would be used for the canvas
// (for example, if the root element has display: none),
// then the canvas background is transparent.”
style: if let Display::GeneratingBox(_) = style.clone_display().into() {
Some(style)
} else {
None
},
}
physical_containing_block,
self.viewport_scroll_sensitivity,
)
}
}

View file

@ -4,16 +4,18 @@
use app_units::Au;
use malloc_size_of_derive::MallocSizeOf;
use script::layout_dom::{ServoLayoutElement, ServoLayoutNode};
use servo_arc::Arc;
use style::context::SharedStyleContext;
use style::properties::ComputedValues;
use style::selector_parser::PseudoElement;
use crate::context::LayoutContext;
use crate::dom::NodeExt;
use crate::dom_traversal::{Contents, NodeAndStyleInfo};
use crate::flexbox::FlexContainer;
use crate::flow::BlockFormattingContext;
use crate::fragment_tree::{BaseFragmentInfo, FragmentFlags};
use crate::geom::LazySize;
use crate::layout_box_base::{
CacheableLayoutResult, CacheableLayoutResultAndInputs, LayoutBoxBase,
};
@ -69,9 +71,9 @@ impl Baselines {
}
impl IndependentFormattingContext {
pub fn construct<'dom, Node: NodeExt<'dom>>(
pub fn construct(
context: &LayoutContext,
node_and_style_info: &NodeAndStyleInfo<Node>,
node_and_style_info: &NodeAndStyleInfo,
display_inside: DisplayInside,
contents: Contents,
propagated_data: PropagatedBoxTreeData,
@ -111,11 +113,11 @@ impl IndependentFormattingContext {
let table_grid_style = context
.shared_context()
.stylist
.style_for_anonymous::<Node::ConcreteElement>(
&context.shared_context().guards,
&PseudoElement::ServoTableGrid,
&node_and_style_info.style,
);
.style_for_anonymous::<ServoLayoutElement>(
&context.shared_context().guards,
&PseudoElement::ServoTableGrid,
&node_and_style_info.style,
);
base_fragment_info.flags.insert(FragmentFlags::DO_NOT_PAINT);
IndependentNonReplacedContents::Table(Table::construct(
context,
@ -217,6 +219,21 @@ impl IndependentFormattingContext {
},
}
}
pub(crate) fn repair_style(
&mut self,
context: &SharedStyleContext,
node: &ServoLayoutNode,
new_style: &Arc<ComputedValues>,
) {
self.base.repair_style(new_style);
match &mut self.contents {
IndependentFormattingContextContents::NonReplaced(content) => {
content.repair_style(context, node, new_style);
},
IndependentFormattingContextContents::Replaced(..) => {},
}
}
}
impl IndependentNonReplacedContents {
@ -227,6 +244,7 @@ impl IndependentNonReplacedContents {
containing_block_for_children: &ContainingBlock,
containing_block: &ContainingBlock,
depends_on_block_constraints: bool,
lazy_block_size: &LazySize,
) -> CacheableLayoutResult {
match self {
IndependentNonReplacedContents::Flow(bfc) => bfc.layout(
@ -240,6 +258,7 @@ impl IndependentNonReplacedContents {
positioning_context,
containing_block_for_children,
depends_on_block_constraints,
lazy_block_size,
),
IndependentNonReplacedContents::Grid(fc) => fc.layout(
layout_context,
@ -266,6 +285,7 @@ impl IndependentNonReplacedContents {
level = "trace",
)
)]
#[allow(clippy::too_many_arguments)]
pub fn layout(
&self,
layout_context: &LayoutContext,
@ -274,6 +294,7 @@ impl IndependentNonReplacedContents {
containing_block: &ContainingBlock,
base: &LayoutBoxBase,
depends_on_block_constraints: bool,
lazy_block_size: &LazySize,
) -> CacheableLayoutResult {
if let Some(cache) = base.cached_layout_result.borrow().as_ref() {
let cache = &**cache;
@ -295,16 +316,14 @@ impl IndependentNonReplacedContents {
);
}
let mut child_positioning_context = PositioningContext::new_for_subtree(
positioning_context.collects_for_nearest_positioned_ancestor(),
);
let mut child_positioning_context = PositioningContext::default();
let result = self.layout_without_caching(
layout_context,
&mut child_positioning_context,
containing_block_for_children,
containing_block,
depends_on_block_constraints,
lazy_block_size,
);
*base.cached_layout_result.borrow_mut() = Some(Box::new(CacheableLayoutResultAndInputs {
@ -337,6 +356,26 @@ impl IndependentNonReplacedContents {
pub(crate) fn is_table(&self) -> bool {
matches!(self, Self::Table(_))
}
fn repair_style(
&mut self,
context: &SharedStyleContext,
node: &ServoLayoutNode,
new_style: &Arc<ComputedValues>,
) {
match self {
IndependentNonReplacedContents::Flow(block_formatting_context) => {
block_formatting_context.repair_style(node, new_style);
},
IndependentNonReplacedContents::Flex(flex_container) => {
flex_container.repair_style(new_style)
},
IndependentNonReplacedContents::Grid(taffy_container) => {
taffy_container.repair_style(new_style)
},
IndependentNonReplacedContents::Table(table) => table.repair_style(context, new_style),
}
}
}
impl ComputeInlineContentSizes for IndependentNonReplacedContents {

View file

@ -32,10 +32,8 @@ impl BaseFragment {
}
}
/// Returns true if this fragment is non-anonymous and it is for the given
/// OpaqueNode, regardless of the pseudo element.
pub(crate) fn is_for_node(&self, node: OpaqueNode) -> bool {
self.tag.map(|tag| tag.node == node).unwrap_or(false)
pub(crate) fn is_anonymous(&self) -> bool {
self.tag.is_none()
}
}
@ -132,11 +130,6 @@ impl Tag {
Tag { node, pseudo }
}
/// Returns true if this tag is for a pseudo element.
pub(crate) fn is_pseudo(&self) -> bool {
self.pseudo.is_some()
}
pub(crate) fn to_display_list_fragment_id(self) -> u64 {
combine_id_with_fragment_type(self.node.id(), self.pseudo.into())
}

View file

@ -2,11 +2,12 @@
* 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 app_units::Au;
use app_units::{Au, MAX_AU, MIN_AU};
use atomic_refcell::AtomicRefCell;
use base::print_tree::PrintTree;
use malloc_size_of_derive::MallocSizeOf;
use servo_arc::Arc as ServoArc;
use servo_geometry::f32_rect_to_au_rect;
use style::Zero;
use style::computed_values::border_collapse::T as BorderCollapse;
use style::computed_values::overflow_x::T as ComputedOverflow;
@ -15,7 +16,9 @@ use style::logical_geometry::WritingMode;
use style::properties::ComputedValues;
use style::values::specified::box_::DisplayOutside;
use super::{BaseFragment, BaseFragmentInfo, CollapsedBlockMargins, Fragment};
use super::{BaseFragment, BaseFragmentInfo, CollapsedBlockMargins, Fragment, FragmentFlags};
use crate::SharedStyle;
use crate::display_list::ToWebRender;
use crate::formatting_contexts::Baselines;
use crate::geom::{
AuOrAuto, LengthPercentageOrAuto, PhysicalPoint, PhysicalRect, PhysicalSides, ToLogical,
@ -37,11 +40,9 @@ pub(crate) enum BackgroundMode {
/// Draw the background normally, getting information from the Fragment style.
Normal,
}
#[derive(MallocSizeOf)]
pub(crate) struct ExtraBackground {
#[conditional_malloc_size_of]
pub style: ServoArc<ComputedValues>,
pub style: SharedStyle,
pub rect: PhysicalRect<Au>,
}
@ -57,7 +58,6 @@ pub(crate) enum SpecificLayoutInfo {
pub(crate) struct BoxFragment {
pub base: BaseFragment,
#[conditional_malloc_size_of]
pub style: ServoArc<ComputedValues>,
pub children: Vec<Fragment>,
@ -65,6 +65,10 @@ pub(crate) struct BoxFragment {
/// does not include padding, border, or margin -- it only includes content.
pub content_rect: PhysicalRect<Au>,
/// This [`BoxFragment`]'s containing block rectangle in coordinates relative to
/// the initial containing block, but not taking into account any transforms.
pub cumulative_containing_block_rect: PhysicalRect<Au>,
pub padding: PhysicalSides<Au>,
pub border: PhysicalSides<Au>,
pub margin: PhysicalSides<Au>,
@ -88,7 +92,7 @@ pub(crate) struct BoxFragment {
pub scrollable_overflow_from_children: PhysicalRect<Au>,
/// The resolved box insets if this box is `position: sticky`. These are calculated
/// during stacking context tree construction because they rely on the size of the
/// during `StackingContextTree` construction because they rely on the size of the
/// scroll container.
pub(crate) resolved_sticky_insets: AtomicRefCell<Option<PhysicalSides<AuOrAuto>>>,
@ -112,7 +116,7 @@ impl BoxFragment {
) -> BoxFragment {
let scrollable_overflow_from_children =
children.iter().fold(PhysicalRect::zero(), |acc, child| {
acc.union(&child.scrollable_overflow())
acc.union(&child.scrollable_overflow_for_parent())
});
BoxFragment {
@ -120,6 +124,7 @@ impl BoxFragment {
style,
children,
content_rect,
cumulative_containing_block_rect: Default::default(),
padding,
border,
margin,
@ -195,6 +200,8 @@ impl BoxFragment {
self
}
/// Get the scrollable overflow for this [`BoxFragment`] relative to its
/// containing block.
pub fn scrollable_overflow(&self) -> PhysicalRect<Au> {
let physical_padding_rect = self.padding_rect();
let content_origin = self.content_rect.origin.to_vector();
@ -205,6 +212,14 @@ impl BoxFragment {
)
}
pub(crate) fn set_containing_block(&mut self, containing_block: &PhysicalRect<Au>) {
self.cumulative_containing_block_rect = *containing_block;
}
pub fn offset_by_containing_block(&self, rect: &PhysicalRect<Au>) -> PhysicalRect<Au> {
rect.translate(self.cumulative_containing_block_rect.origin.to_vector())
}
pub(crate) fn padding_rect(&self) -> PhysicalRect<Au> {
self.content_rect.outer_rect(self.padding)
}
@ -221,6 +236,16 @@ impl BoxFragment {
self.margin + self.border + self.padding
}
pub(crate) fn is_root_element(&self) -> bool {
self.base.flags.intersects(FragmentFlags::IS_ROOT_ELEMENT)
}
pub(crate) fn is_body_element_of_html_element_root(&self) -> bool {
self.base
.flags
.intersects(FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT)
}
pub fn print(&self, tree: &mut PrintTree) {
tree.new_level(format!(
"Box\
@ -252,36 +277,99 @@ impl BoxFragment {
pub fn scrollable_overflow_for_parent(&self) -> PhysicalRect<Au> {
let mut overflow = self.border_rect();
if self.style.establishes_scroll_container(self.base.flags) {
return overflow;
if !self.style.establishes_scroll_container(self.base.flags) {
// https://www.w3.org/TR/css-overflow-3/#scrollable
// Only include the scrollable overflow of a child box if it has overflow: visible.
let scrollable_overflow = self.scrollable_overflow();
let bottom_right = PhysicalPoint::new(
overflow.max_x().max(scrollable_overflow.max_x()),
overflow.max_y().max(scrollable_overflow.max_y()),
);
let overflow_style = self.style.effective_overflow(self.base.flags);
if overflow_style.y == ComputedOverflow::Visible {
overflow.origin.y = overflow.origin.y.min(scrollable_overflow.origin.y);
overflow.size.height = bottom_right.y - overflow.origin.y;
}
if overflow_style.x == ComputedOverflow::Visible {
overflow.origin.x = overflow.origin.x.min(scrollable_overflow.origin.x);
overflow.size.width = bottom_right.x - overflow.origin.x;
}
}
// https://www.w3.org/TR/css-overflow-3/#scrollable
// Only include the scrollable overflow of a child box if it has overflow: visible.
let scrollable_overflow = self.scrollable_overflow();
let bottom_right = PhysicalPoint::new(
overflow.max_x().max(scrollable_overflow.max_x()),
overflow.max_y().max(scrollable_overflow.max_y()),
);
let overflow_style = self.style.effective_overflow(self.base.flags);
if overflow_style.y == ComputedOverflow::Visible {
overflow.origin.y = overflow.origin.y.min(scrollable_overflow.origin.y);
overflow.size.height = bottom_right.y - overflow.origin.y;
}
if overflow_style.x == ComputedOverflow::Visible {
overflow.origin.x = overflow.origin.x.min(scrollable_overflow.origin.x);
overflow.size.width = bottom_right.x - overflow.origin.x;
// <https://drafts.csswg.org/css-overflow-3/#scrollable-overflow-region>
// > ...accounting for transforms by projecting each box onto the plane of
// > the element that establishes its 3D rendering context. [CSS3-TRANSFORMS]
// Both boxes and its scrollable overflow (if it is included) should be transformed accordingly.
//
// TODO(stevennovaryo): We are supposed to handle perspective transform and 3d context, but it is yet to happen.
if self
.style
.has_effective_transform_or_perspective(self.base.flags)
{
if let Some(transform) =
self.calculate_transform_matrix(&self.border_rect().to_untyped())
{
if let Some(transformed_overflow_box) =
transform.outer_transformed_rect(&overflow.to_webrender().to_rect())
{
overflow =
f32_rect_to_au_rect(transformed_overflow_box.to_untyped()).cast_unit();
}
}
}
overflow
}
pub(crate) fn calculate_resolved_insets_if_positioned(
/// <https://drafts.csswg.org/css-overflow/#unreachable-scrollable-overflow-region>
/// > area beyond the scroll origin in either axis is considered the unreachable scrollable overflow region
///
/// Return the clipped the scrollable overflow based on its scroll origin, determined by overflow direction.
/// For an element, the clip rect is the padding rect and for viewport, it is the initial containing block.
pub fn clip_unreachable_scrollable_overflow_region(
&self,
containing_block: &PhysicalRect<Au>,
) -> PhysicalSides<AuOrAuto> {
scrollable_overflow: PhysicalRect<Au>,
clipping_rect: PhysicalRect<Au>,
) -> PhysicalRect<Au> {
let scrolling_direction = self.style.overflow_direction();
let mut scrollable_overflow_box = scrollable_overflow.to_box2d();
let mut clipping_box = clipping_rect.to_box2d();
if scrolling_direction.rightward {
clipping_box.max.x = MAX_AU;
} else {
clipping_box.min.x = MIN_AU;
}
if scrolling_direction.downward {
clipping_box.max.y = MAX_AU;
} else {
clipping_box.min.y = MIN_AU;
}
scrollable_overflow_box = scrollable_overflow_box.intersection_unchecked(&clipping_box);
match scrollable_overflow_box.is_negative() {
true => PhysicalRect::zero(),
false => scrollable_overflow_box.to_rect(),
}
}
/// <https://drafts.csswg.org/css-overflow/#unreachable-scrollable-overflow-region>
/// > area beyond the scroll origin in either axis is considered the unreachable scrollable overflow region
///
/// Return the clipped the scrollable overflow based on its scroll origin, determined by overflow direction.
/// This will coincides with the scrollport if the fragment is a scroll container.
pub fn reachable_scrollable_overflow_region(&self) -> PhysicalRect<Au> {
self.clip_unreachable_scrollable_overflow_region(
self.scrollable_overflow(),
self.padding_rect(),
)
}
pub(crate) fn calculate_resolved_insets_if_positioned(&self) -> PhysicalSides<AuOrAuto> {
let position = self.style.get_box().position;
debug_assert_ne!(
position,
@ -309,7 +397,10 @@ impl BoxFragment {
// used value. Otherwise the resolved value is the computed value."
// https://drafts.csswg.org/cssom/#resolved-values
let insets = self.style.physical_box_offsets();
let (cb_width, cb_height) = (containing_block.width(), containing_block.height());
let (cb_width, cb_height) = (
self.cumulative_containing_block_rect.width(),
self.cumulative_containing_block_rect.height(),
);
if position == ComputedPosition::Relative {
let get_resolved_axis = |start: &LengthPercentageOrAuto,
end: &LengthPercentageOrAuto,

View file

@ -7,13 +7,13 @@ use std::sync::Arc;
use app_units::Au;
use base::id::PipelineId;
use base::print_tree::PrintTree;
use euclid::{Point2D, Rect, Size2D, UnknownUnit};
use fonts::{ByteIndex, FontMetrics, GlyphStore};
use malloc_size_of_derive::MallocSizeOf;
use range::Range as ServoRange;
use servo_arc::Arc as ServoArc;
use style::Zero;
use style::properties::ComputedValues;
use style::values::specified::text::TextDecorationLine;
use webrender_api::{FontInstanceKey, ImageKey};
use super::{
@ -21,7 +21,8 @@ use super::{
Tag,
};
use crate::cell::ArcRefCell;
use crate::geom::{LogicalSides, PhysicalRect};
use crate::flow::inline::SharedInlineStyles;
use crate::geom::{LogicalSides, PhysicalPoint, PhysicalRect};
use crate::style_ext::ComputedValuesExt;
#[derive(Clone, MallocSizeOf)]
@ -63,28 +64,21 @@ pub(crate) struct CollapsedMargin {
#[derive(MallocSizeOf)]
pub(crate) struct TextFragment {
pub base: BaseFragment,
#[conditional_malloc_size_of]
pub parent_style: ServoArc<ComputedValues>,
pub inline_styles: SharedInlineStyles,
pub rect: PhysicalRect<Au>,
pub font_metrics: FontMetrics,
pub font_key: FontInstanceKey,
#[conditional_malloc_size_of]
pub glyphs: Vec<Arc<GlyphStore>>,
/// A flag that represents the _used_ value of the text-decoration property.
pub text_decoration_line: TextDecorationLine,
/// Extra space to add for each justification opportunity.
pub justification_adjustment: Au,
pub selection_range: Option<ServoRange<ByteIndex>>,
#[conditional_malloc_size_of]
pub selected_style: ServoArc<ComputedValues>,
}
#[derive(MallocSizeOf)]
pub(crate) struct ImageFragment {
pub base: BaseFragment,
#[conditional_malloc_size_of]
pub style: ServoArc<ComputedValues>,
pub rect: PhysicalRect<Au>,
pub clip: PhysicalRect<Au>,
@ -96,7 +90,6 @@ pub(crate) struct IFrameFragment {
pub base: BaseFragment,
pub pipeline_id: PipelineId,
pub rect: PhysicalRect<Au>,
#[conditional_malloc_size_of]
pub style: ServoArc<ComputedValues>,
}
@ -112,6 +105,7 @@ impl Fragment {
Fragment::Float(fragment) => fragment.borrow().base.clone(),
})
}
pub(crate) fn mutate_content_rect(&mut self, callback: impl FnOnce(&mut PhysicalRect<Au>)) {
match self {
Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => {
@ -124,6 +118,28 @@ impl Fragment {
}
}
pub(crate) fn set_containing_block(&self, containing_block: &PhysicalRect<Au>) {
match self {
Fragment::Box(box_fragment) => box_fragment
.borrow_mut()
.set_containing_block(containing_block),
Fragment::Float(float_fragment) => float_fragment
.borrow_mut()
.set_containing_block(containing_block),
Fragment::Positioning(positioning_fragment) => positioning_fragment
.borrow_mut()
.set_containing_block(containing_block),
Fragment::AbsoluteOrFixedPositioned(hoisted_shared_fragment) => {
if let Some(ref fragment) = hoisted_shared_fragment.borrow().fragment {
fragment.set_containing_block(containing_block);
}
},
Fragment::Text(_) => {},
Fragment::Image(_) => {},
Fragment::IFrame(_) => {},
}
}
pub fn tag(&self) -> Option<Tag> {
self.base().and_then(|base| base.tag)
}
@ -146,17 +162,28 @@ impl Fragment {
}
}
pub fn scrolling_area(&self, containing_block: &PhysicalRect<Au>) -> PhysicalRect<Au> {
pub fn unclipped_scrolling_area(&self) -> PhysicalRect<Au> {
match self {
Fragment::Box(fragment) | Fragment::Float(fragment) => fragment
.borrow()
.scrollable_overflow()
.translate(containing_block.origin.to_vector()),
_ => self.scrollable_overflow(),
Fragment::Box(fragment) | Fragment::Float(fragment) => {
let fragment = fragment.borrow();
fragment.offset_by_containing_block(&fragment.scrollable_overflow())
},
_ => self.scrollable_overflow_for_parent(),
}
}
pub fn scrollable_overflow(&self) -> PhysicalRect<Au> {
pub fn scrolling_area(&self) -> PhysicalRect<Au> {
match self {
Fragment::Box(fragment) | Fragment::Float(fragment) => {
let fragment = fragment.borrow();
fragment
.offset_by_containing_block(&fragment.reachable_scrollable_overflow_region())
},
_ => self.scrollable_overflow_for_parent(),
}
}
pub fn scrollable_overflow_for_parent(&self) -> PhysicalRect<Au> {
match self {
Fragment::Box(fragment) | Fragment::Float(fragment) => {
fragment.borrow().scrollable_overflow_for_parent()
@ -169,6 +196,59 @@ impl Fragment {
}
}
pub(crate) fn cumulative_border_box_rect(&self) -> Option<PhysicalRect<Au>> {
match self {
Fragment::Box(fragment) | Fragment::Float(fragment) => {
let fragment = fragment.borrow();
Some(fragment.offset_by_containing_block(&fragment.border_rect()))
},
Fragment::Positioning(fragment) => {
let fragment = fragment.borrow();
Some(fragment.offset_by_containing_block(&fragment.rect))
},
Fragment::Text(_) |
Fragment::AbsoluteOrFixedPositioned(_) |
Fragment::Image(_) |
Fragment::IFrame(_) => None,
}
}
pub(crate) fn client_rect(&self) -> Rect<i32, UnknownUnit> {
let rect = match self {
Fragment::Box(fragment) | Fragment::Float(fragment) => {
// https://drafts.csswg.org/cssom-view/#dom-element-clienttop
// " If the element has no associated CSS layout box or if the
// CSS layout box is inline, return zero." For this check we
// also explicitly ignore the list item portion of the display
// style.
let fragment = fragment.borrow();
if fragment.is_inline_box() {
return Rect::zero();
}
if fragment.is_table_wrapper() {
// For tables the border actually belongs to the table grid box,
// so we need to include it in the dimension of the table wrapper box.
let mut rect = fragment.border_rect();
rect.origin = PhysicalPoint::zero();
rect
} else {
let mut rect = fragment.padding_rect();
rect.origin = PhysicalPoint::new(fragment.border.left, fragment.border.top);
rect
}
},
_ => return Rect::zero(),
}
.to_untyped();
let rect = Rect::new(
Point2D::new(rect.origin.x.to_f32_px(), rect.origin.y.to_f32_px()),
Size2D::new(rect.size.width.to_f32_px(), rect.size.height.to_f32_px()),
);
rect.round().to_i32()
}
pub(crate) fn find<T>(
&self,
manager: &ContainingBlockManager<PhysicalRect<Au>>,
@ -220,6 +300,25 @@ impl Fragment {
_ => None,
}
}
pub(crate) fn repair_style(&self, style: &ServoArc<ComputedValues>) {
match self {
Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => {
box_fragment.borrow_mut().style = style.clone()
},
Fragment::Positioning(positioning_fragment) => {
positioning_fragment.borrow_mut().style = style.clone();
},
Fragment::AbsoluteOrFixedPositioned(positioned_fragment) => {
if let Some(ref fragment) = positioned_fragment.borrow().fragment {
fragment.repair_style(style);
}
},
Fragment::Text(..) => unreachable!("Should never try to repair style of TextFragment"),
Fragment::Image(image_fragment) => image_fragment.borrow_mut().style = style.clone(),
Fragment::IFrame(iframe_fragment) => iframe_fragment.borrow_mut().style = style.clone(),
}
}
}
impl TextFragment {

View file

@ -5,17 +5,16 @@
use app_units::Au;
use base::print_tree::PrintTree;
use compositing_traits::display_list::AxesScrollSensitivity;
use euclid::default::{Point2D, Rect, Size2D};
use euclid::default::Size2D;
use fxhash::FxHashSet;
use malloc_size_of_derive::MallocSizeOf;
use style::animation::AnimationSetKey;
use style::dom::OpaqueNode;
use webrender_api::units;
use super::{ContainingBlockManager, Fragment, Tag};
use crate::display_list::StackingContext;
use crate::flow::CanvasBackground;
use crate::geom::{PhysicalPoint, PhysicalRect};
use super::{BoxFragment, ContainingBlockManager, Fragment};
use crate::ArcRefCell;
use crate::context::LayoutContext;
use crate::geom::PhysicalRect;
#[derive(MallocSizeOf)]
pub struct FragmentTree {
@ -36,26 +35,59 @@ pub struct FragmentTree {
/// The containing block used in the layout of this fragment tree.
pub(crate) initial_containing_block: PhysicalRect<Au>,
/// <https://drafts.csswg.org/css-backgrounds/#special-backgrounds>
pub(crate) canvas_background: CanvasBackground,
/// Whether or not the viewport is sensitive to scroll input events.
pub viewport_scroll_sensitivity: AxesScrollSensitivity,
}
impl FragmentTree {
pub(crate) fn build_display_list(
&self,
builder: &mut crate::display_list::DisplayListBuilder,
root_stacking_context: &StackingContext,
) {
// Paint the canvas background (if any) before/under everything else
root_stacking_context.build_canvas_background_display_list(
builder,
self,
&self.initial_containing_block,
);
root_stacking_context.build_display_list(builder);
pub(crate) fn new(
layout_context: &LayoutContext,
root_fragments: Vec<Fragment>,
scrollable_overflow: PhysicalRect<Au>,
initial_containing_block: PhysicalRect<Au>,
viewport_scroll_sensitivity: AxesScrollSensitivity,
) -> Self {
let fragment_tree = Self {
root_fragments,
scrollable_overflow,
initial_containing_block,
viewport_scroll_sensitivity,
};
// As part of building the fragment tree, we want to stop animating elements and
// pseudo-elements that used to be animating or had animating images attached to
// them. Create a set of all elements that used to be animating.
let mut animations = layout_context.style_context.animations.sets.write();
let mut invalid_animating_nodes: FxHashSet<_> = animations.keys().cloned().collect();
let mut image_animations = layout_context.node_image_animation_map.write().to_owned();
let mut invalid_image_animating_nodes: FxHashSet<_> = image_animations
.keys()
.cloned()
.map(|node| AnimationSetKey::new(node, None))
.collect();
fragment_tree.find(|fragment, _level, containing_block| {
if let Some(tag) = fragment.tag() {
invalid_animating_nodes.remove(&AnimationSetKey::new(tag.node, tag.pseudo));
invalid_image_animating_nodes.remove(&AnimationSetKey::new(tag.node, tag.pseudo));
}
fragment.set_containing_block(containing_block);
None::<()>
});
// Cancel animations for any elements and pseudo-elements that are no longer found
// in the fragment tree.
for node in &invalid_animating_nodes {
if let Some(state) = animations.get_mut(node) {
state.cancel_all_animations();
}
}
for node in &invalid_image_animating_nodes {
image_animations.remove(&node.node);
}
fragment_tree
}
pub fn print(&self) {
@ -86,109 +118,67 @@ impl FragmentTree {
.find_map(|child| child.find(&info, 0, &mut process_func))
}
pub fn remove_nodes_in_fragment_tree_from_set(&self, set: &mut FxHashSet<AnimationSetKey>) {
self.find(|fragment, _, _| {
let tag = fragment.tag()?;
set.remove(&AnimationSetKey::new(tag.node, tag.pseudo));
None::<()>
});
}
/// Get the vector of rectangles that surrounds the fragments of the node with the given address.
/// This function answers the `getClientRects()` query and the union of the rectangles answers
/// the `getBoundingClientRect()` query.
/// <https://drafts.csswg.org/cssom-view/#scrolling-area>
///
/// TODO: This function is supposed to handle scroll offsets, but that isn't happening at all.
pub fn get_content_boxes_for_node(&self, requested_node: OpaqueNode) -> Vec<Rect<Au>> {
let mut content_boxes = Vec::new();
let tag_to_find = Tag::new(requested_node);
self.find(|fragment, _, containing_block| {
if fragment.tag() != Some(tag_to_find) {
return None::<()>;
}
let fragment_relative_rect = match fragment {
Fragment::Box(fragment) | Fragment::Float(fragment) => {
fragment.borrow().border_rect()
},
Fragment::Positioning(fragment) => fragment.borrow().rect,
Fragment::Text(fragment) => fragment.borrow().rect,
Fragment::AbsoluteOrFixedPositioned(_) |
Fragment::Image(_) |
Fragment::IFrame(_) => return None,
};
let rect = fragment_relative_rect.translate(containing_block.origin.to_vector());
content_boxes.push(rect.to_untyped());
None::<()>
});
content_boxes
}
pub fn get_border_dimensions_for_node(&self, requested_node: OpaqueNode) -> Rect<i32> {
let tag_to_find = Tag::new(requested_node);
self.find(|fragment, _, _containing_block| {
if fragment.tag() != Some(tag_to_find) {
return None;
}
let rect = match fragment {
Fragment::Box(fragment) | Fragment::Float(fragment) => {
// https://drafts.csswg.org/cssom-view/#dom-element-clienttop
// " If the element has no associated CSS layout box or if the
// CSS layout box is inline, return zero." For this check we
// also explicitly ignore the list item portion of the display
// style.
let fragment = fragment.borrow();
if fragment.is_inline_box() {
return Some(Rect::zero());
}
if fragment.is_table_wrapper() {
// For tables the border actually belongs to the table grid box,
// so we need to include it in the dimension of the table wrapper box.
let mut rect = fragment.border_rect();
rect.origin = PhysicalPoint::zero();
rect
} else {
let mut rect = fragment.padding_rect();
rect.origin = PhysicalPoint::new(fragment.border.left, fragment.border.top);
rect
}
},
Fragment::Positioning(fragment) => fragment.borrow().rect.cast_unit(),
Fragment::Text(text_fragment) => text_fragment.borrow().rect,
_ => return None,
};
let rect = Rect::new(
Point2D::new(rect.origin.x.to_f32_px(), rect.origin.y.to_f32_px()),
Size2D::new(rect.size.width.to_f32_px(), rect.size.height.to_f32_px()),
);
Some(rect.round().to_i32().to_untyped())
})
.unwrap_or_else(Rect::zero)
}
/// Scrolling area for a viewport that is clipped according to overflow direction of root element.
pub fn get_scrolling_area_for_viewport(&self) -> PhysicalRect<Au> {
let mut scroll_area = self.initial_containing_block;
for fragment in self.root_fragments.iter() {
scroll_area = fragment
.scrolling_area(&self.initial_containing_block)
.union(&scroll_area);
if let Some(root_fragment) = self.root_fragments.first() {
for fragment in self.root_fragments.iter() {
scroll_area = fragment.unclipped_scrolling_area().union(&scroll_area);
}
match root_fragment {
Fragment::Box(fragment) | Fragment::Float(fragment) => fragment
.borrow()
.clip_unreachable_scrollable_overflow_region(
scroll_area,
self.initial_containing_block,
),
_ => scroll_area,
}
} else {
scroll_area
}
scroll_area
}
pub fn get_scrolling_area_for_node(&self, requested_node: OpaqueNode) -> PhysicalRect<Au> {
let tag_to_find = Tag::new(requested_node);
let scroll_area = self.find(|fragment, _, containing_block| {
if fragment.tag() == Some(tag_to_find) {
Some(fragment.scrolling_area(containing_block))
} else {
None
}
});
scroll_area.unwrap_or_else(PhysicalRect::<Au>::zero)
/// Find the `<body>` element's [`Fragment`], if it exists in this [`FragmentTree`].
pub(crate) fn body_fragment(&self) -> Option<ArcRefCell<BoxFragment>> {
fn find_body(children: &[Fragment]) -> Option<ArcRefCell<BoxFragment>> {
children.iter().find_map(|fragment| {
match fragment {
Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => {
let borrowed_box_fragment = box_fragment.borrow();
if borrowed_box_fragment.is_body_element_of_html_element_root() {
return Some(box_fragment.clone());
}
// The fragment for the `<body>` element is typically a child of the root (though,
// not if it's absolutely positioned), so we need to recurse into the children of
// the root to find it.
//
// Additionally, recurse into any anonymous fragments, as the `<body>` fragment may
// have created anonymous parents (for instance by creating an inline formatting context).
if borrowed_box_fragment.is_root_element() ||
borrowed_box_fragment.base.is_anonymous()
{
find_body(&borrowed_box_fragment.children)
} else {
None
}
},
Fragment::Positioning(positioning_context)
if positioning_context.borrow().base.is_anonymous() =>
{
// If the `<body>` element is a `display: inline` then it might be nested inside of a
// `PositioningFragment` for the purposes of putting it on the first line of the implied
// inline formatting context.
find_body(&positioning_context.borrow().children)
},
_ => None,
}
})
}
find_body(&self.root_fragments)
}
}

View file

@ -20,17 +20,25 @@ pub(crate) struct PositioningFragment {
pub base: BaseFragment,
pub rect: PhysicalRect<Au>,
pub children: Vec<Fragment>,
/// The scrollable overflow of this anonymous fragment's children.
pub scrollable_overflow: PhysicalRect<Au>,
/// If this fragment was created with a style, the style of the fragment.
#[conditional_malloc_size_of]
pub style: Option<ServoArc<ComputedValues>>,
/// The style of the fragment.
pub style: ServoArc<ComputedValues>,
/// This [`PositioningFragment`]'s containing block rectangle in coordinates relative to
/// the initial containing block, but not taking into account any transforms.
pub cumulative_containing_block_rect: PhysicalRect<Au>,
}
impl PositioningFragment {
pub fn new_anonymous(rect: PhysicalRect<Au>, children: Vec<Fragment>) -> ArcRefCell<Self> {
Self::new_with_base_fragment(BaseFragment::anonymous(), None, rect, children)
pub fn new_anonymous(
style: ServoArc<ComputedValues>,
rect: PhysicalRect<Au>,
children: Vec<Fragment>,
) -> ArcRefCell<Self> {
Self::new_with_base_fragment(BaseFragment::anonymous(), style, rect, children)
}
pub fn new_empty(
@ -38,12 +46,12 @@ impl PositioningFragment {
rect: PhysicalRect<Au>,
style: ServoArc<ComputedValues>,
) -> ArcRefCell<Self> {
Self::new_with_base_fragment(base_fragment_info.into(), Some(style), rect, Vec::new())
Self::new_with_base_fragment(base_fragment_info.into(), style, rect, Vec::new())
}
fn new_with_base_fragment(
base: BaseFragment,
style: Option<ServoArc<ComputedValues>>,
style: ServoArc<ComputedValues>,
rect: PhysicalRect<Au>,
children: Vec<Fragment>,
) -> ArcRefCell<Self> {
@ -51,7 +59,7 @@ impl PositioningFragment {
let scrollable_overflow = children.iter().fold(PhysicalRect::zero(), |acc, child| {
acc.union(
&child
.scrollable_overflow()
.scrollable_overflow_for_parent()
.translate(content_origin.to_vector()),
)
});
@ -61,9 +69,18 @@ impl PositioningFragment {
rect,
children,
scrollable_overflow,
cumulative_containing_block_rect: PhysicalRect::zero(),
})
}
pub(crate) fn set_containing_block(&mut self, containing_block: &PhysicalRect<Au>) {
self.cumulative_containing_block_rect = *containing_block;
}
pub fn offset_by_containing_block(&self, rect: &PhysicalRect<Au>) -> PhysicalRect<Au> {
rect.translate(self.cumulative_containing_block_rect.origin.to_vector())
}
pub fn print(&self, tree: &mut PrintTree) {
tree.new_level(format!(
"PositioningFragment\

View file

@ -2,7 +2,7 @@
* 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::cell::LazyCell;
use std::cell::{LazyCell, OnceCell};
use std::convert::From;
use std::fmt;
use std::ops::{Add, AddAssign, Neg, Sub, SubAssign};
@ -767,7 +767,9 @@ impl From<StyleSize> for Size<LengthPercentage> {
StyleSize::FitContent => Size::FitContent,
StyleSize::FitContentFunction(lp) => Size::FitContentFunction(lp.0),
StyleSize::Stretch => Size::Stretch,
StyleSize::AnchorSizeFunction(_) => unreachable!("anchor-size() should be disabled"),
StyleSize::AnchorSizeFunction(_) | StyleSize::AnchorContainingCalcFunction(_) => {
unreachable!("anchor-size() should be disabled")
},
}
}
}
@ -782,7 +784,9 @@ impl From<StyleMaxSize> for Size<LengthPercentage> {
StyleMaxSize::FitContent => Size::FitContent,
StyleMaxSize::FitContentFunction(lp) => Size::FitContentFunction(lp.0),
StyleMaxSize::Stretch => Size::Stretch,
StyleMaxSize::AnchorSizeFunction(_) => unreachable!("anchor-size() should be disabled"),
StyleMaxSize::AnchorSizeFunction(_) | StyleMaxSize::AnchorContainingCalcFunction(_) => {
unreachable!("anchor-size() should be disabled")
},
}
}
}
@ -1098,3 +1102,87 @@ impl Sizes {
)
}
}
struct LazySizeData<'a> {
sizes: &'a Sizes,
axis: Direction,
automatic_size: Size<Au>,
get_automatic_minimum_size: fn() -> Au,
stretch_size: Option<Au>,
is_table: bool,
}
/// Represents a size that can't be fully resolved until the intrinsic size
/// is known. This is useful in the block axis, since the intrinsic size
/// depends on layout, but the other inputs are known beforehand.
pub(crate) struct LazySize<'a> {
result: OnceCell<Au>,
data: Option<LazySizeData<'a>>,
}
impl<'a> LazySize<'a> {
pub(crate) fn new(
sizes: &'a Sizes,
axis: Direction,
automatic_size: Size<Au>,
get_automatic_minimum_size: fn() -> Au,
stretch_size: Option<Au>,
is_table: bool,
) -> Self {
Self {
result: OnceCell::new(),
data: Some(LazySizeData {
sizes,
axis,
automatic_size,
get_automatic_minimum_size,
stretch_size,
is_table,
}),
}
}
/// Creates a [`LazySize`] that will resolve to the intrinsic size.
/// Should be equivalent to [`LazySize::new()`] with default parameters,
/// but avoiding the trouble of getting a reference to a [`Sizes::default()`]
/// which lives long enough.
///
/// TODO: It's not clear what this should do if/when [`LazySize::resolve()`]
/// is changed to accept a [`ContentSizes`] as the intrinsic size.
pub(crate) fn intrinsic() -> Self {
Self {
result: OnceCell::new(),
data: None,
}
}
/// Resolves the [`LazySize`] into [`Au`], caching the result.
/// The argument is a callback that computes the intrinsic size lazily.
///
/// TODO: The intrinsic size should probably be a [`ContentSizes`] instead of [`Au`].
pub(crate) fn resolve(&self, get_content_size: impl FnOnce() -> Au) -> Au {
*self.result.get_or_init(|| {
let Some(ref data) = self.data else {
return get_content_size();
};
data.sizes.resolve(
data.axis,
data.automatic_size,
data.get_automatic_minimum_size,
data.stretch_size,
|| get_content_size().into(),
data.is_table,
)
})
}
}
impl From<Au> for LazySize<'_> {
/// Creates a [`LazySize`] that will resolve to the given [`Au`],
/// ignoring the intrinsic size.
fn from(value: Au) -> Self {
let result = OnceCell::new();
result.set(value).unwrap();
LazySize { result, data: None }
}
}

View file

@ -27,7 +27,6 @@ use crate::{ConstraintSpace, ContainingBlockSize};
#[derive(MallocSizeOf)]
pub(crate) struct LayoutBoxBase {
pub base_fragment_info: BaseFragmentInfo,
#[conditional_malloc_size_of]
pub style: Arc<ComputedValues>,
pub cached_inline_content_size:
AtomicRefCell<Option<Box<(SizeConstraint, InlineContentSizesResult)>>>,
@ -90,6 +89,13 @@ impl LayoutBoxBase {
pub(crate) fn clear_fragments(&self) {
self.fragments.borrow_mut().clear();
}
pub(crate) fn repair_style(&mut self, new_style: &Arc<ComputedValues>) {
self.style = new_style.clone();
for fragment in self.fragments.borrow_mut().iter_mut() {
fragment.repair_style(new_style);
}
}
}
impl Debug for LayoutBoxBase {

View file

@ -4,9 +4,8 @@
#![allow(unsafe_code)]
use std::cell::{Cell, LazyCell, RefCell};
use std::collections::{HashMap, HashSet};
use std::ffi::c_void;
use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use std::fmt::Debug;
use std::process;
use std::sync::{Arc, LazyLock};
@ -16,14 +15,13 @@ use base::Epoch;
use base::id::{PipelineId, WebViewId};
use compositing_traits::CrossProcessCompositorApi;
use constellation_traits::ScrollState;
use embedder_traits::resources::{self, Resource};
use embedder_traits::{UntrustedNodeAddress, ViewportDetails};
use euclid::default::{Point2D as UntypedPoint2D, Rect as UntypedRect, Size2D as UntypedSize2D};
use euclid::default::{Point2D as UntypedPoint2D, Rect as UntypedRect};
use euclid::{Point2D, Scale, Size2D, Vector2D};
use fnv::FnvHashMap;
use fonts::{FontContext, FontContextWebFontMethods};
use fonts_traits::StylesheetWebFontLoadFinishedCallback;
use fxhash::{FxHashMap, FxHashSet};
use fxhash::FxHashMap;
use ipc_channel::ipc::IpcSender;
use log::{debug, error};
use malloc_size_of::{MallocConditionalSizeOf, MallocSizeOf, MallocSizeOfOps};
@ -34,21 +32,22 @@ use profile_traits::time::{
self as profile_time, TimerMetadata, TimerMetadataFrameType, TimerMetadataReflowType,
};
use profile_traits::{path, time_profile};
use script::layout_dom::{ServoLayoutElement, ServoLayoutNode};
use rayon::ThreadPool;
use script::layout_dom::{ServoLayoutDocument, ServoLayoutElement, ServoLayoutNode};
use script_layout_interface::{
ImageAnimationState, Layout, LayoutConfig, LayoutFactory, NodesFromPointQueryType,
OffsetParentResponse, ReflowGoal, ReflowRequest, ReflowResult, TrustedNodeAddress,
Layout, LayoutConfig, LayoutFactory, NodesFromPointQueryType, OffsetParentResponse, ReflowGoal,
ReflowRequest, ReflowResult, TrustedNodeAddress,
};
use script_traits::{DrawAPaintImageResult, PaintWorkletError, Painter, ScriptThreadMessage};
use servo_arc::Arc as ServoArc;
use servo_config::opts::{self, DebugOptions};
use servo_config::pref;
use servo_url::ServoUrl;
use style::animation::{AnimationSetKey, DocumentAnimationSet};
use style::animation::DocumentAnimationSet;
use style::context::{
QuirksMode, RegisteredSpeculativePainter, RegisteredSpeculativePainters, SharedStyleContext,
};
use style::dom::{OpaqueNode, TElement, TNode};
use style::dom::{OpaqueNode, ShowSubtreeDataAndPrimaryValues, TElement, TNode};
use style::error_reporting::RustLogReporter;
use style::font_metrics::FontMetrics;
use style::global_style_data::GLOBAL_STYLE_DATA;
@ -57,7 +56,7 @@ use style::media_queries::{Device, MediaList, MediaType};
use style::properties::style_structs::Font;
use style::properties::{ComputedValues, PropertyId};
use style::queries::values::PrefersColorScheme;
use style::selector_parser::{PseudoElement, SnapshotMap};
use style::selector_parser::{PseudoElement, RestyleDamage, SnapshotMap};
use style::servo::media_queries::FontMetricsProvider;
use style::shared_lock::{SharedRwLock, SharedRwLockReadGuard, StylesheetGuards};
use style::stylesheets::{
@ -69,7 +68,7 @@ use style::traversal::DomTraversal;
use style::traversal_flags::TraversalFlags;
use style::values::computed::font::GenericFontFamily;
use style::values::computed::{CSSPixelLength, FontSize, Length, NonNegativeLength};
use style::values::specified::font::KeywordInfo;
use style::values::specified::font::{KeywordInfo, QueryFontMetricsFlags};
use style::{Zero, driver};
use style_traits::{CSSPixel, SpeculativePainter};
use stylo_atoms::Atom;
@ -78,13 +77,13 @@ use webrender_api::units::{DevicePixel, DevicePoint, LayoutPixel, LayoutPoint, L
use webrender_api::{ExternalScrollId, HitTestFlags};
use crate::context::LayoutContext;
use crate::display_list::{DisplayList, WebRenderImageInfo};
use crate::display_list::{DisplayListBuilder, StackingContextTree, WebRenderImageInfo};
use crate::query::{
get_the_text_steps, process_content_box_request, process_content_boxes_request,
process_node_geometry_request, process_node_scroll_area_request, process_offset_parent_query,
get_the_text_steps, process_client_rect_request, process_content_box_request,
process_content_boxes_request, process_node_scroll_area_request, process_offset_parent_query,
process_resolved_font_style_query, process_resolved_style_request, process_text_index_request,
};
use crate::traversal::RecalcStyle;
use crate::traversal::{RecalcStyle, compute_damage_and_repair_style};
use crate::{BoxTree, FragmentTree};
// This mutex is necessary due to syncronisation issues between two different types of thread-local storage
@ -95,6 +94,18 @@ use crate::{BoxTree, FragmentTree};
static STYLE_THREAD_POOL: Mutex<&style::global_style_data::STYLE_THREAD_POOL> =
Mutex::new(&style::global_style_data::STYLE_THREAD_POOL);
/// A CSS file to style the user agent stylesheet.
static USER_AGENT_CSS: &[u8] = include_bytes!("./stylesheets/user-agent.css");
/// A CSS file to style the Servo browser.
static SERVO_CSS: &[u8] = include_bytes!("./stylesheets/servo.css");
/// A CSS file to style the presentational hints.
static PRESENTATIONAL_HINTS_CSS: &[u8] = include_bytes!("./stylesheets/presentational-hints.css");
/// A CSS file to style the quirks mode.
static QUIRKS_MODE_CSS: &[u8] = include_bytes!("./stylesheets/quirks-mode.css");
/// Information needed by layout.
pub struct LayoutThread {
/// The ID of the pipeline that we belong to.
@ -133,13 +144,12 @@ pub struct LayoutThread {
/// The fragment tree.
fragment_tree: RefCell<Option<Arc<FragmentTree>>>,
/// The [`StackingContextTree`] cached from previous layouts.
stacking_context_tree: RefCell<Option<StackingContextTree>>,
/// A counter for epoch messages
epoch: Cell<Epoch>,
/// The size of the viewport. This may be different from the size of the screen due to viewport
/// constraints.
viewport_size: UntypedSize2D<Au>,
/// Scroll offsets of nodes that scroll.
scroll_offsets: RefCell<HashMap<ExternalScrollId, Vector2D<f32, LayoutPixel>>>,
@ -230,24 +240,27 @@ impl Layout for LayoutThread {
feature = "tracing",
tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace")
)]
fn query_content_box(&self, node: OpaqueNode) -> Option<UntypedRect<Au>> {
process_content_box_request(node, self.fragment_tree.borrow().clone())
fn query_content_box(&self, node: TrustedNodeAddress) -> Option<UntypedRect<Au>> {
let node = unsafe { ServoLayoutNode::new(&node) };
process_content_box_request(node)
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace")
)]
fn query_content_boxes(&self, node: OpaqueNode) -> Vec<UntypedRect<Au>> {
process_content_boxes_request(node, self.fragment_tree.borrow().clone())
fn query_content_boxes(&self, node: TrustedNodeAddress) -> Vec<UntypedRect<Au>> {
let node = unsafe { ServoLayoutNode::new(&node) };
process_content_boxes_request(node)
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace")
)]
fn query_client_rect(&self, node: OpaqueNode) -> UntypedRect<i32> {
process_node_geometry_request(node, self.fragment_tree.borrow().clone())
fn query_client_rect(&self, node: TrustedNodeAddress) -> UntypedRect<i32> {
let node = unsafe { ServoLayoutNode::new(&node) };
process_client_rect_request(node)
}
#[cfg_attr(
@ -292,8 +305,9 @@ impl Layout for LayoutThread {
feature = "tracing",
tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace")
)]
fn query_offset_parent(&self, node: OpaqueNode) -> OffsetParentResponse {
process_offset_parent_query(node, self.fragment_tree.borrow().clone())
fn query_offset_parent(&self, node: TrustedNodeAddress) -> OffsetParentResponse {
let node = unsafe { ServoLayoutNode::new(&node) };
process_offset_parent_query(node).unwrap_or_default()
}
#[cfg_attr(
@ -325,14 +339,7 @@ impl Layout for LayoutThread {
TraversalFlags::empty(),
);
let fragment_tree = self.fragment_tree.borrow().clone();
process_resolved_style_request(
&shared_style_context,
node,
&pseudo,
&property_id,
fragment_tree,
)
process_resolved_style_request(&shared_style_context, node, &pseudo, &property_id)
}
#[cfg_attr(
@ -375,7 +382,8 @@ impl Layout for LayoutThread {
feature = "tracing",
tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace")
)]
fn query_scrolling_area(&self, node: Option<OpaqueNode>) -> UntypedRect<i32> {
fn query_scrolling_area(&self, node: Option<TrustedNodeAddress>) -> UntypedRect<i32> {
let node = node.map(|node| unsafe { ServoLayoutNode::new(&node) });
process_node_scroll_area_request(node, self.fragment_tree.borrow().clone())
}
@ -438,6 +446,8 @@ impl Layout for LayoutThread {
.map(|tree| tree.conditional_size_of(ops))
.unwrap_or_default(),
});
reports.push(self.image_cache.memory_report(formatted_url, ops));
}
fn set_quirks_mode(&mut self, quirks_mode: QuirksMode) {
@ -510,12 +520,9 @@ impl LayoutThread {
first_reflow: Cell::new(true),
box_tree: Default::default(),
fragment_tree: Default::default(),
stacking_context_tree: Default::default(),
// Epoch starts at 1 because of the initial display list for epoch 0 that we send to WR
epoch: Cell::new(Epoch(1)),
viewport_size: Size2D::new(
Au::from_f32_px(config.viewport_details.size.width),
Au::from_f32_px(config.viewport_details.size.height),
),
compositor_api: config.compositor_api,
scroll_offsets: Default::default(),
stylist: Stylist::new(device, QuirksMode::NoQuirks),
@ -545,43 +552,6 @@ impl LayoutThread {
}
}
#[allow(clippy::too_many_arguments)]
// Create a layout context for use in building display lists, hit testing, &c.
#[allow(clippy::too_many_arguments)]
fn build_layout_context<'a>(
&'a self,
guards: StylesheetGuards<'a>,
snapshot_map: &'a SnapshotMap,
reflow_request: &mut ReflowRequest,
use_rayon: bool,
) -> LayoutContext<'a> {
let traversal_flags = match reflow_request.stylesheets_changed {
true => TraversalFlags::ForCSSRuleChanges,
false => TraversalFlags::empty(),
};
LayoutContext {
id: self.id,
origin: reflow_request.origin.clone(),
style_context: self.build_shared_style_context(
guards,
snapshot_map,
reflow_request.animation_timeline_value,
&reflow_request.animations,
traversal_flags,
),
image_cache: self.image_cache.clone(),
font_context: self.font_context.clone(),
webrender_image_cache: self.webrender_image_cache.clone(),
pending_images: Mutex::default(),
node_image_animation_map: Arc::new(RwLock::new(std::mem::take(
&mut reflow_request.node_to_image_animation_map,
))),
iframe_sizes: Mutex::default(),
use_rayon,
}
}
fn load_all_web_fonts_from_stylesheet_with_guard(
&self,
stylesheet: &DocumentStyleSheet,
@ -621,51 +591,132 @@ impl LayoutThread {
return None;
};
// Calculate the actual viewport as per DEVICE-ADAPT § 6
// If the entire flow tree is invalid, then it will be reflowed anyhow.
let document_shared_lock = document.style_shared_lock();
let author_guard = document_shared_lock.read();
let ua_stylesheets = &*UA_STYLESHEETS;
let ua_or_user_guard = ua_stylesheets.shared_lock.read();
let rayon_pool = STYLE_THREAD_POOL.lock();
let rayon_pool = rayon_pool.pool();
let rayon_pool = rayon_pool.as_ref();
let guards = StylesheetGuards {
author: &author_guard,
ua_or_user: &ua_or_user_guard,
};
let had_used_viewport_units = self.stylist.device().used_viewport_units();
let viewport_size_changed = self.viewport_did_change(reflow_request.viewport_details);
let theme_changed = self.theme_did_change(reflow_request.theme);
if viewport_size_changed || theme_changed {
self.update_device(
reflow_request.viewport_details,
reflow_request.theme,
&guards,
);
}
if viewport_size_changed && had_used_viewport_units {
let viewport_changed = self.viewport_did_change(reflow_request.viewport_details);
if self.update_device_if_necessary(&reflow_request, viewport_changed, &guards) {
if let Some(mut data) = root_element.mutate_data() {
data.hint.insert(RestyleHint::recascade_subtree());
}
}
let mut snapshot_map = SnapshotMap::new();
let _snapshot_setter = SnapshotSetter::new(&mut reflow_request, &mut snapshot_map);
self.prepare_stylist_for_reflow(
&reflow_request,
document,
root_element,
&guards,
ua_stylesheets,
&snapshot_map,
);
let mut layout_context = LayoutContext {
id: self.id,
origin: reflow_request.origin.clone(),
style_context: self.build_shared_style_context(
guards,
&snapshot_map,
reflow_request.animation_timeline_value,
&reflow_request.animations,
match reflow_request.stylesheets_changed {
true => TraversalFlags::ForCSSRuleChanges,
false => TraversalFlags::empty(),
},
),
image_cache: self.image_cache.clone(),
font_context: self.font_context.clone(),
webrender_image_cache: self.webrender_image_cache.clone(),
pending_images: Mutex::default(),
node_image_animation_map: Arc::new(RwLock::new(std::mem::take(
&mut reflow_request.node_to_image_animation_map,
))),
iframe_sizes: Mutex::default(),
use_rayon: rayon_pool.is_some(),
highlighted_dom_node: reflow_request.highlighted_dom_node,
};
let did_reflow = self.restyle_and_build_trees(
&reflow_request,
root_element,
rayon_pool,
&mut layout_context,
viewport_changed,
);
self.build_stacking_context_tree(&reflow_request, did_reflow);
self.build_display_list(&reflow_request, &mut layout_context);
self.first_reflow.set(false);
if let ReflowGoal::UpdateScrollNode(scroll_state) = reflow_request.reflow_goal {
self.update_scroll_node_state(&scroll_state);
}
let pending_images = std::mem::take(&mut *layout_context.pending_images.lock());
let iframe_sizes = std::mem::take(&mut *layout_context.iframe_sizes.lock());
let node_to_image_animation_map =
std::mem::take(&mut *layout_context.node_image_animation_map.write());
Some(ReflowResult {
pending_images,
iframe_sizes,
node_to_image_animation_map,
})
}
fn update_device_if_necessary(
&mut self,
reflow_request: &ReflowRequest,
viewport_changed: bool,
guards: &StylesheetGuards,
) -> bool {
let had_used_viewport_units = self.stylist.device().used_viewport_units();
let theme_changed = self.theme_did_change(reflow_request.theme);
if !viewport_changed && !theme_changed {
return false;
}
self.update_device(
reflow_request.viewport_details,
reflow_request.theme,
guards,
);
(viewport_changed && had_used_viewport_units) || theme_changed
}
fn prepare_stylist_for_reflow<'dom>(
&mut self,
reflow_request: &ReflowRequest,
document: ServoLayoutDocument<'dom>,
root_element: ServoLayoutElement<'dom>,
guards: &StylesheetGuards,
ua_stylesheets: &UserAgentStylesheets,
snapshot_map: &SnapshotMap,
) {
if self.first_reflow.get() {
for stylesheet in &ua_stylesheets.user_or_user_agent_stylesheets {
self.stylist
.append_stylesheet(stylesheet.clone(), &ua_or_user_guard);
self.load_all_web_fonts_from_stylesheet_with_guard(stylesheet, &ua_or_user_guard);
.append_stylesheet(stylesheet.clone(), guards.ua_or_user);
self.load_all_web_fonts_from_stylesheet_with_guard(stylesheet, guards.ua_or_user);
}
if self.stylist.quirks_mode() != QuirksMode::NoQuirks {
self.stylist.append_stylesheet(
ua_stylesheets.quirks_mode_stylesheet.clone(),
&ua_or_user_guard,
guards.ua_or_user,
);
self.load_all_web_fonts_from_stylesheet_with_guard(
&ua_stylesheets.quirks_mode_stylesheet,
&ua_or_user_guard,
guards.ua_or_user,
);
}
}
@ -678,153 +729,180 @@ impl LayoutThread {
// Flush shadow roots stylesheets if dirty.
document.flush_shadow_roots_stylesheets(&mut self.stylist, guards.author);
let restyles = std::mem::take(&mut reflow_request.pending_restyles);
debug!("Draining restyles: {}", restyles.len());
let mut map = SnapshotMap::new();
let elements_with_snapshot: Vec<_> = restyles
.iter()
.filter(|r| r.1.snapshot.is_some())
.map(|r| unsafe { ServoLayoutNode::new(&r.0).as_element().unwrap() })
.collect();
for (el, restyle) in restyles {
let el = unsafe { ServoLayoutNode::new(&el).as_element().unwrap() };
// If we haven't styled this node yet, we don't need to track a
// restyle.
let mut style_data = match el.mutate_data() {
Some(d) => d,
None => {
unsafe { el.unset_snapshot_flags() };
continue;
},
};
if let Some(s) = restyle.snapshot {
unsafe { el.set_has_snapshot() };
map.insert(el.as_node().opaque(), s);
}
// Stash the data on the element for processing by the style system.
style_data.hint.insert(restyle.hint);
style_data.damage = restyle.damage;
debug!("Noting restyle for {:?}: {:?}", el, style_data);
}
self.stylist.flush(&guards, Some(root_element), Some(&map));
let rayon_pool = STYLE_THREAD_POOL.lock();
let rayon_pool = rayon_pool.pool();
let rayon_pool = rayon_pool.as_ref();
// Create a layout context for use throughout the following passes.
let mut layout_context = self.build_layout_context(
guards.clone(),
&map,
&mut reflow_request,
rayon_pool.is_some(),
);
self.stylist
.flush(guards, Some(root_element), Some(snapshot_map));
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace")
)]
fn restyle_and_build_trees(
&self,
reflow_request: &ReflowRequest,
root_element: ServoLayoutElement<'_>,
rayon_pool: Option<&ThreadPool>,
layout_context: &mut LayoutContext<'_>,
viewport_changed: bool,
) -> bool {
let dirty_root = unsafe {
ServoLayoutNode::new(&reflow_request.dirty_root.unwrap())
.as_element()
.unwrap()
};
let traversal = RecalcStyle::new(layout_context);
let recalc_style_traversal = RecalcStyle::new(layout_context);
let token = {
let shared = DomTraversal::<ServoLayoutElement>::shared_context(&traversal);
let shared =
DomTraversal::<ServoLayoutElement>::shared_context(&recalc_style_traversal);
RecalcStyle::pre_traverse(dirty_root, shared)
};
if token.should_traverse() {
#[cfg(feature = "tracing")]
let _span =
tracing::trace_span!("driver::traverse_dom", servo_profiling = true).entered();
let dirty_root: ServoLayoutNode =
driver::traverse_dom(&traversal, token, rayon_pool).as_node();
let root_node = root_element.as_node();
let mut box_tree = self.box_tree.borrow_mut();
let box_tree = &mut *box_tree;
let mut build_box_tree = || {
if !BoxTree::update(traversal.context(), dirty_root) {
*box_tree = Some(Arc::new(BoxTree::construct(traversal.context(), root_node)));
}
};
if let Some(pool) = rayon_pool {
pool.install(build_box_tree)
} else {
build_box_tree()
};
let viewport_size = Size2D::new(
self.viewport_size.width.to_f32_px(),
self.viewport_size.height.to_f32_px(),
);
let run_layout = || {
box_tree
.as_ref()
.unwrap()
.layout(traversal.context(), viewport_size)
};
let fragment_tree = Arc::new(if let Some(pool) = rayon_pool {
pool.install(run_layout)
} else {
run_layout()
});
*self.fragment_tree.borrow_mut() = Some(fragment_tree);
if !token.should_traverse() {
layout_context.style_context.stylist.rule_tree().maybe_gc();
return false;
}
layout_context = traversal.destroy();
let dirty_root: ServoLayoutNode =
driver::traverse_dom(&recalc_style_traversal, token, rayon_pool).as_node();
for element in elements_with_snapshot {
unsafe { element.unset_snapshot_flags() }
let root_node = root_element.as_node();
let damage = compute_damage_and_repair_style(layout_context.shared_context(), root_node);
if !viewport_changed && (damage.is_empty() || damage == RestyleDamage::REPAINT) {
layout_context.style_context.stylist.rule_tree().maybe_gc();
return false;
}
let mut box_tree = self.box_tree.borrow_mut();
let box_tree = &mut *box_tree;
let mut build_box_tree = || {
if !BoxTree::update(recalc_style_traversal.context(), dirty_root) {
*box_tree = Some(Arc::new(BoxTree::construct(
recalc_style_traversal.context(),
root_node,
)));
}
};
if let Some(pool) = rayon_pool {
pool.install(build_box_tree)
} else {
build_box_tree()
};
let viewport_size = self.stylist.device().au_viewport_size();
let run_layout = || {
box_tree
.as_ref()
.unwrap()
.layout(recalc_style_traversal.context(), viewport_size)
};
let fragment_tree = Arc::new(if let Some(pool) = rayon_pool {
pool.install(run_layout)
} else {
run_layout()
});
if self.debug.dump_flow_tree {
fragment_tree.print();
}
*self.fragment_tree.borrow_mut() = Some(fragment_tree);
// The FragmentTree has been updated, so any existing StackingContext tree that layout
// had is now out of date and should be rebuilt.
*self.stacking_context_tree.borrow_mut() = None;
if self.debug.dump_style_tree {
println!(
"{:?}",
style::dom::ShowSubtreeDataAndPrimaryValues(root_element.as_node())
ShowSubtreeDataAndPrimaryValues(root_element.as_node())
);
}
if self.debug.dump_rule_tree {
layout_context
recalc_style_traversal
.context()
.style_context
.stylist
.rule_tree()
.dump_stdout(&guards);
.dump_stdout(&layout_context.shared_context().guards);
}
// GC the rule tree if some heuristics are met.
layout_context.style_context.stylist.rule_tree().maybe_gc();
true
}
// Perform post-style recalculation layout passes.
if let Some(root) = &*self.fragment_tree.borrow() {
self.perform_post_style_recalc_layout_passes(
root.clone(),
&reflow_request.reflow_goal,
&mut layout_context,
);
fn build_stacking_context_tree(&self, reflow_request: &ReflowRequest, did_reflow: bool) {
if !reflow_request.reflow_goal.needs_display_list() &&
!reflow_request.reflow_goal.needs_display()
{
return;
}
let Some(fragment_tree) = &*self.fragment_tree.borrow() else {
return;
};
if !did_reflow && self.stacking_context_tree.borrow().is_some() {
return;
}
self.first_reflow.set(false);
let viewport_size = self.stylist.device().au_viewport_size();
let viewport_size = LayoutSize::new(
viewport_size.width.to_f32_px(),
viewport_size.height.to_f32_px(),
);
if let ReflowGoal::UpdateScrollNode(scroll_state) = reflow_request.reflow_goal {
self.update_scroll_node_state(&scroll_state);
// Build the StackingContextTree. This turns the `FragmentTree` into a
// tree of fragments in CSS painting order and also creates all
// applicable spatial and clip nodes.
*self.stacking_context_tree.borrow_mut() = Some(StackingContextTree::new(
fragment_tree,
viewport_size,
fragment_tree.scrollable_overflow(),
self.id.into(),
fragment_tree.viewport_scroll_sensitivity,
self.first_reflow.get(),
&self.debug,
));
}
fn build_display_list(
&self,
reflow_request: &ReflowRequest,
layout_context: &mut LayoutContext<'_>,
) {
if !reflow_request.reflow_goal.needs_display() {
return;
}
let Some(fragment_tree) = &*self.fragment_tree.borrow() else {
return;
};
let pending_images = std::mem::take(&mut *layout_context.pending_images.lock());
let iframe_sizes = std::mem::take(&mut *layout_context.iframe_sizes.lock());
let node_to_image_animation_map =
std::mem::take(&mut *layout_context.node_image_animation_map.write());
Some(ReflowResult {
pending_images,
iframe_sizes,
node_to_image_animation_map,
})
let mut stacking_context_tree = self.stacking_context_tree.borrow_mut();
let Some(stacking_context_tree) = stacking_context_tree.as_mut() else {
return;
};
let mut epoch = self.epoch.get();
epoch.next();
self.epoch.set(epoch);
stacking_context_tree.compositor_info.epoch = epoch.into();
let built_display_list = DisplayListBuilder::build(
layout_context,
stacking_context_tree,
fragment_tree,
&self.debug,
);
self.compositor_api.send_display_list(
self.webview_id,
&stacking_context_tree.compositor_info,
built_display_list,
);
let (keys, instance_keys) = self
.font_context
.collect_unused_webrender_resources(false /* all */);
self.compositor_api
.remove_unused_font_resources(keys, instance_keys)
}
fn update_scroll_node_state(&self, state: &ScrollState) {
@ -840,84 +918,6 @@ impl LayoutThread {
);
}
fn perform_post_style_recalc_layout_passes(
&self,
fragment_tree: Arc<FragmentTree>,
reflow_goal: &ReflowGoal,
context: &mut LayoutContext,
) {
Self::cancel_animations_for_nodes_not_in_fragment_tree(
&context.style_context.animations,
&fragment_tree,
);
Self::cancel_image_animation_for_nodes_not_in_fragment_tree(
context.node_image_animation_map.clone(),
&fragment_tree,
);
if !reflow_goal.needs_display_list() {
return;
}
let mut epoch = self.epoch.get();
epoch.next();
self.epoch.set(epoch);
let viewport_size = LayoutSize::from_untyped(Size2D::new(
self.viewport_size.width.to_f32_px(),
self.viewport_size.height.to_f32_px(),
));
let mut display_list = DisplayList::new(
viewport_size,
fragment_tree.scrollable_overflow(),
self.id.into(),
epoch.into(),
fragment_tree.viewport_scroll_sensitivity,
self.first_reflow.get(),
);
display_list.wr.begin();
// `dump_serialized_display_list` doesn't actually print anything. It sets up
// the display list for printing the serialized version when `finalize()` is called.
// We need to call this before adding any display items so that they are printed
// during `finalize()`.
if self.debug.dump_display_list {
display_list.wr.dump_serialized_display_list();
}
// Build the root stacking context. This turns the `FragmentTree` into a
// tree of fragments in CSS painting order and also creates all
// applicable spatial and clip nodes.
let root_stacking_context =
display_list.build_stacking_context_tree(&fragment_tree, &self.debug);
// Build the rest of the display list which inclues all of the WebRender primitives.
display_list.build(context, &fragment_tree, &root_stacking_context);
if self.debug.dump_flow_tree {
fragment_tree.print();
}
if self.debug.dump_stacking_context_tree {
root_stacking_context.debug_print();
}
debug!("Layout done!");
if reflow_goal.needs_display() {
self.compositor_api.send_display_list(
self.webview_id,
display_list.compositor_info,
display_list.wr.end().1,
);
let (keys, instance_keys) = self
.font_context
.collect_unused_webrender_resources(false /* all */);
self.compositor_api
.remove_unused_font_resources(keys, instance_keys)
}
}
/// Returns profiling information which is passed to the time profiler.
fn profiler_metadata(&self) -> Option<TimerMetadata> {
Some(TimerMetadata {
@ -935,42 +935,6 @@ impl LayoutThread {
})
}
/// Cancel animations for any nodes which have been removed from fragment tree.
/// TODO(mrobinson): We should look into a way of doing this during flow tree construction.
/// This also doesn't yet handles nodes that have been reparented.
fn cancel_animations_for_nodes_not_in_fragment_tree(
animations: &DocumentAnimationSet,
root: &FragmentTree,
) {
// Assume all nodes have been removed until proven otherwise.
let mut animations = animations.sets.write();
let mut invalid_nodes = animations.keys().cloned().collect();
root.remove_nodes_in_fragment_tree_from_set(&mut invalid_nodes);
// Cancel animations for any nodes that are no longer in the fragment tree.
for node in &invalid_nodes {
if let Some(state) = animations.get_mut(node) {
state.cancel_all_animations();
}
}
}
fn cancel_image_animation_for_nodes_not_in_fragment_tree(
image_animation_set: Arc<RwLock<FxHashMap<OpaqueNode, ImageAnimationState>>>,
root: &FragmentTree,
) {
let mut image_animations = image_animation_set.write().to_owned();
let mut invalid_nodes: FxHashSet<AnimationSetKey> = image_animations
.keys()
.cloned()
.map(|node| AnimationSetKey::new(node, None))
.collect();
root.remove_nodes_in_fragment_tree_from_set(&mut invalid_nodes);
for node in &invalid_nodes {
image_animations.remove(&node.node);
}
}
fn viewport_did_change(&mut self, viewport_details: ViewportDetails) -> bool {
let new_pixel_ratio = viewport_details.hidpi_scale_factor.get();
let new_viewport_size = Size2D::new(
@ -978,9 +942,6 @@ impl LayoutThread {
Au::from_f32_px(viewport_details.size.height),
);
// TODO: eliminate self.viewport_size in favour of using self.device.au_viewport_size()
self.viewport_size = new_viewport_size;
let device = self.stylist.device();
let size_did_change = device.au_viewport_size() != new_viewport_size;
let pixel_ratio_did_change = device.device_pixel_ratio().get() != new_pixel_ratio;
@ -1046,20 +1007,12 @@ fn get_ua_stylesheets() -> Result<UserAgentStylesheets, &'static str> {
// FIXME: presentational-hints.css should be at author origin with zero specificity.
// (Does it make a difference?)
let mut user_or_user_agent_stylesheets = vec![
parse_ua_stylesheet(
shared_lock,
"user-agent.css",
&resources::read_bytes(Resource::UserAgentCSS),
)?,
parse_ua_stylesheet(
shared_lock,
"servo.css",
&resources::read_bytes(Resource::ServoCSS),
)?,
parse_ua_stylesheet(shared_lock, "user-agent.css", USER_AGENT_CSS)?,
parse_ua_stylesheet(shared_lock, "servo.css", SERVO_CSS)?,
parse_ua_stylesheet(
shared_lock,
"presentational-hints.css",
&resources::read_bytes(Resource::PresentationalHintsCSS),
PRESENTATIONAL_HINTS_CSS,
)?,
];
@ -1080,11 +1033,8 @@ fn get_ua_stylesheets() -> Result<UserAgentStylesheets, &'static str> {
)));
}
let quirks_mode_stylesheet = parse_ua_stylesheet(
shared_lock,
"quirks-mode.css",
&resources::read_bytes(Resource::QuirksModeCSS),
)?;
let quirks_mode_stylesheet =
parse_ua_stylesheet(shared_lock, "quirks-mode.css", QUIRKS_MODE_CSS)?;
Ok(UserAgentStylesheets {
shared_lock: shared_lock.clone(),
@ -1160,8 +1110,7 @@ impl FontMetricsProvider for LayoutFontMetricsProvider {
_vertical: bool,
font: &Font,
base_size: CSSPixelLength,
_in_media_query: bool,
_retrieve_math_scales: bool,
_flags: QueryFontMetricsFlags,
) -> FontMetrics {
let font_context = &self.0;
let font_group = self
@ -1230,6 +1179,54 @@ impl Debug for LayoutFontMetricsProvider {
}
}
thread_local!(static SEEN_POINTERS: LazyCell<RefCell<HashSet<*const c_void>>> = const {
LazyCell::new(|| RefCell::new(HashSet::new()))
});
struct SnapshotSetter<'dom> {
elements_with_snapshot: Vec<ServoLayoutElement<'dom>>,
}
impl SnapshotSetter<'_> {
fn new(reflow_request: &mut ReflowRequest, snapshot_map: &mut SnapshotMap) -> Self {
debug!(
"Draining restyles: {}",
reflow_request.pending_restyles.len()
);
let restyles = std::mem::take(&mut reflow_request.pending_restyles);
let elements_with_snapshot: Vec<_> = restyles
.iter()
.filter(|r| r.1.snapshot.is_some())
.map(|r| unsafe { ServoLayoutNode::new(&r.0).as_element().unwrap() })
.collect();
for (element, restyle) in restyles {
let element = unsafe { ServoLayoutNode::new(&element).as_element().unwrap() };
// If we haven't styled this node yet, we don't need to track a
// restyle.
let Some(mut style_data) = element.mutate_data() else {
unsafe { element.unset_snapshot_flags() };
continue;
};
debug!("Noting restyle for {:?}: {:?}", element, style_data);
if let Some(s) = restyle.snapshot {
unsafe { element.set_has_snapshot() };
snapshot_map.insert(element.as_node().opaque(), s);
}
// Stash the data on the element for processing by the style system.
style_data.hint.insert(restyle.hint);
style_data.damage = restyle.damage;
}
Self {
elements_with_snapshot,
}
}
}
impl Drop for SnapshotSetter<'_> {
fn drop(&mut self) {
for element in &self.elements_with_snapshot {
unsafe { element.unset_snapshot_flags() }
}
}
}

View file

@ -38,13 +38,23 @@ pub use flow::BoxTree;
pub use fragment_tree::FragmentTree;
pub use layout_impl::LayoutFactoryImpl;
use malloc_size_of_derive::MallocSizeOf;
use servo_arc::Arc as ServoArc;
use style::logical_geometry::WritingMode;
use style::properties::ComputedValues;
use style::values::computed::TextDecorationLine;
use crate::geom::{LogicalVec2, SizeConstraint};
use crate::style_ext::AspectRatio;
/// At times, a style is "owned" by more than one layout object. For example, text
/// fragments need a handle on their parent inline box's style. In order to make
/// incremental layout easier to implement, another layer of shared ownership is added via
/// [`SharedStyle`]. This allows updating the style in originating layout object and
/// having all "depdendent" objects update automatically.
///
/// Note that this is not a cost-free data structure, so should only be
/// used when necessary.
pub(crate) type SharedStyle = ArcRefCell<ServoArc<ComputedValues>>;
/// Represents the set of constraints that we use when computing the min-content
/// and max-content inline sizes of an element.
pub(crate) struct ConstraintSpace {
@ -152,39 +162,20 @@ impl<'a> From<&'_ DefiniteContainingBlock<'a>> for ContainingBlock<'a> {
/// propoagation, but only during `BoxTree` construction.
#[derive(Clone, Copy, Debug)]
struct PropagatedBoxTreeData {
text_decoration: TextDecorationLine,
allow_percentage_column_in_tables: bool,
}
impl Default for PropagatedBoxTreeData {
fn default() -> Self {
Self {
text_decoration: Default::default(),
allow_percentage_column_in_tables: true,
}
}
}
impl PropagatedBoxTreeData {
pub(crate) fn union(&self, style: &ComputedValues) -> Self {
Self {
// FIXME(#31736): This is only taking into account the line style and not the decoration
// color. This should collect information about both so that they can be rendered properly.
text_decoration: self.text_decoration | style.clone_text_decoration_line(),
allow_percentage_column_in_tables: self.allow_percentage_column_in_tables,
}
}
pub(crate) fn without_text_decorations(&self) -> Self {
Self {
text_decoration: TextDecorationLine::NONE,
allow_percentage_column_in_tables: self.allow_percentage_column_in_tables,
}
}
fn disallowing_percentage_table_columns(&self) -> PropagatedBoxTreeData {
Self {
text_decoration: self.text_decoration,
allow_percentage_column_in_tables: false,
}
}

View file

@ -7,18 +7,14 @@ use style::properties::style_structs;
use style::values::computed::Image;
use crate::context::LayoutContext;
use crate::dom::NodeExt;
use crate::dom_traversal::{NodeAndStyleInfo, PseudoElementContentItem};
use crate::replaced::ReplacedContents;
/// <https://drafts.csswg.org/css-lists/#content-property>
pub(crate) fn make_marker<'dom, Node>(
pub(crate) fn make_marker<'dom>(
context: &LayoutContext,
info: &NodeAndStyleInfo<Node>,
) -> Option<(NodeAndStyleInfo<Node>, Vec<PseudoElementContentItem>)>
where
Node: NodeExt<'dom>,
{
info: &NodeAndStyleInfo<'dom>,
) -> Option<(NodeAndStyleInfo<'dom>, Vec<PseudoElementContentItem>)> {
let marker_info = info.pseudo(context, style::selector_parser::PseudoElement::Marker)?;
let style = &marker_info.style;
let list_style = style.get_list();

View file

@ -16,7 +16,6 @@ use style::values::specified::align::AlignFlags;
use crate::cell::ArcRefCell;
use crate::context::LayoutContext;
use crate::dom::NodeExt;
use crate::dom_traversal::{Contents, NodeAndStyleInfo};
use crate::formatting_contexts::{
IndependentFormattingContext, IndependentFormattingContextContents,
@ -25,11 +24,11 @@ use crate::fragment_tree::{
BoxFragment, Fragment, FragmentFlags, HoistedSharedFragment, SpecificLayoutInfo,
};
use crate::geom::{
AuOrAuto, LengthPercentageOrAuto, LogicalRect, LogicalSides, LogicalSides1D, LogicalVec2,
PhysicalPoint, PhysicalRect, PhysicalSides, PhysicalSize, PhysicalVec, Size, Sizes, ToLogical,
ToLogicalWithContainingBlock,
AuOrAuto, LazySize, LengthPercentageOrAuto, LogicalRect, LogicalSides, LogicalSides1D,
LogicalVec2, PhysicalPoint, PhysicalRect, PhysicalSides, PhysicalSize, PhysicalVec, Size,
Sizes, ToLogical, ToLogicalWithContainingBlock,
};
use crate::sizing::ContentSizes;
use crate::layout_box_base::LayoutBoxBase;
use crate::style_ext::{Clamp, ComputedValuesExt, ContentBoxSizesAndPBM, DisplayInside};
use crate::{
ConstraintSpace, ContainingBlock, ContainingBlockSize, DefiniteContainingBlock,
@ -41,16 +40,6 @@ pub(crate) struct AbsolutelyPositionedBox {
pub context: IndependentFormattingContext,
}
#[derive(Clone, MallocSizeOf)]
pub(crate) struct PositioningContext {
for_nearest_positioned_ancestor: Option<Vec<HoistedAbsolutelyPositionedBox>>,
// For nearest `containing block for all descendants` as defined by the CSS transforms
// spec.
// https://www.w3.org/TR/css-transforms-1/#containing-block-for-all-descendants
for_nearest_containing_block_for_all_descendants: Vec<HoistedAbsolutelyPositionedBox>,
}
#[derive(Clone, MallocSizeOf)]
pub(crate) struct HoistedAbsolutelyPositionedBox {
absolutely_positioned_box: ArcRefCell<AbsolutelyPositionedBox>,
@ -66,9 +55,9 @@ impl AbsolutelyPositionedBox {
Self { context }
}
pub fn construct<'dom>(
pub fn construct(
context: &LayoutContext,
node_info: &NodeAndStyleInfo<impl NodeExt<'dom>>,
node_info: &NodeAndStyleInfo,
display_inside: DisplayInside,
contents: Contents,
) -> Self {
@ -103,45 +92,26 @@ impl AbsolutelyPositionedBox {
}
}
#[derive(Clone, Default, MallocSizeOf)]
pub(crate) struct PositioningContext {
absolutes: Vec<HoistedAbsolutelyPositionedBox>,
}
impl PositioningContext {
pub(crate) fn new_for_containing_block_for_all_descendants() -> Self {
Self {
for_nearest_positioned_ancestor: None,
for_nearest_containing_block_for_all_descendants: Vec::new(),
}
#[inline]
pub(crate) fn new_for_layout_box_base(layout_box_base: &LayoutBoxBase) -> Option<Self> {
Self::new_for_style_and_fragment_flags(
&layout_box_base.style,
&layout_box_base.base_fragment_info.flags,
)
}
/// Create a [PositioningContext] to use for laying out a subtree. The idea is that
/// when subtree layout is finished, the newly hoisted boxes can be processed
/// (normally adjusting their static insets) and then appended to the parent
/// [PositioningContext].
pub(crate) fn new_for_subtree(collects_for_nearest_positioned_ancestor: bool) -> Self {
Self {
for_nearest_positioned_ancestor: if collects_for_nearest_positioned_ancestor {
Some(Vec::new())
} else {
None
},
for_nearest_containing_block_for_all_descendants: Vec::new(),
}
}
pub(crate) fn collects_for_nearest_positioned_ancestor(&self) -> bool {
self.for_nearest_positioned_ancestor.is_some()
}
pub(crate) fn new_for_style(style: &ComputedValues) -> Option<Self> {
// NB: We never make PositioningContexts for replaced elements, which is why we always
// pass false here.
if style.establishes_containing_block_for_all_descendants(FragmentFlags::empty()) {
Some(Self::new_for_containing_block_for_all_descendants())
} else if style
.establishes_containing_block_for_absolute_descendants(FragmentFlags::empty())
{
Some(Self {
for_nearest_positioned_ancestor: Some(Vec::new()),
for_nearest_containing_block_for_all_descendants: Vec::new(),
})
fn new_for_style_and_fragment_flags(
style: &ComputedValues,
flags: &FragmentFlags,
) -> Option<Self> {
if style.establishes_containing_block_for_absolute_descendants(*flags) {
Some(Self::default())
} else {
None
}
@ -184,20 +154,9 @@ impl PositioningContext {
offset: &PhysicalVec<Au>,
index: PositioningContextLength,
) {
if let Some(hoisted_boxes) = self.for_nearest_positioned_ancestor.as_mut() {
hoisted_boxes
.iter_mut()
.skip(index.for_nearest_positioned_ancestor)
.for_each(|hoisted_fragment| {
hoisted_fragment
.fragment
.borrow_mut()
.adjust_offsets(offset)
})
}
self.for_nearest_containing_block_for_all_descendants
self.absolutes
.iter_mut()
.skip(index.for_nearest_containing_block_for_all_descendants)
.skip(index.0)
.for_each(|hoisted_fragment| {
hoisted_fragment
.fragment
@ -213,38 +172,89 @@ impl PositioningContext {
&mut self,
layout_context: &LayoutContext,
containing_block: &ContainingBlock,
style: &ComputedValues,
base: &LayoutBoxBase,
fragment_layout_fn: impl FnOnce(&mut Self) -> BoxFragment,
) -> BoxFragment {
// Try to create a context, but if one isn't necessary, simply create the fragment
// using the given closure and the current `PositioningContext`.
let mut new_context = match Self::new_for_style(style) {
Some(new_context) => new_context,
None => return fragment_layout_fn(self),
};
// If a new `PositioningContext` isn't necessary, simply create the fragment using
// the given closure and the current `PositioningContext`.
let establishes_containing_block_for_absolutes = base
.style
.establishes_containing_block_for_absolute_descendants(base.base_fragment_info.flags);
if !establishes_containing_block_for_absolutes {
return fragment_layout_fn(self);
}
let mut new_context = PositioningContext::default();
let mut new_fragment = fragment_layout_fn(&mut new_context);
new_context.layout_collected_children(layout_context, &mut new_fragment);
// If the new context has any hoisted boxes for the nearest containing block for
// pass them up the tree.
// Lay out all of the absolutely positioned children for this fragment, and, if it
// isn't a containing block for fixed elements, then pass those up to the parent.
new_context.layout_collected_children(layout_context, &mut new_fragment);
self.append(new_context);
if style.clone_position() == Position::Relative {
new_fragment.content_rect.origin += relative_adjustement(style, containing_block)
if base.style.clone_position() == Position::Relative {
new_fragment.content_rect.origin += relative_adjustement(&base.style, containing_block)
.to_physical_vector(containing_block.style.writing_mode)
}
new_fragment
}
fn take_boxes_for_fragment(
&mut self,
new_fragment: &BoxFragment,
boxes_to_layout_out: &mut Vec<HoistedAbsolutelyPositionedBox>,
boxes_to_continue_hoisting_out: &mut Vec<HoistedAbsolutelyPositionedBox>,
) {
debug_assert!(
new_fragment
.style
.establishes_containing_block_for_absolute_descendants(new_fragment.base.flags)
);
if new_fragment
.style
.establishes_containing_block_for_all_descendants(new_fragment.base.flags)
{
boxes_to_layout_out.append(&mut self.absolutes);
return;
}
// TODO: This could potentially use `extract_if` when that is stabilized.
let (mut boxes_to_layout, mut boxes_to_continue_hoisting) = self
.absolutes
.drain(..)
.partition(|hoisted_box| hoisted_box.position() != Position::Fixed);
boxes_to_layout_out.append(&mut boxes_to_layout);
boxes_to_continue_hoisting_out.append(&mut boxes_to_continue_hoisting);
}
// Lay out the hoisted boxes collected into this `PositioningContext` and add them
// to the given `BoxFragment`.
pub fn layout_collected_children(
pub(crate) fn layout_collected_children(
&mut self,
layout_context: &LayoutContext,
new_fragment: &mut BoxFragment,
) {
if self.absolutes.is_empty() {
return;
}
// Sometimes we create temporary PositioningContexts just to collect hoisted absolutes and
// then these are processed later. In that case and if this fragment doesn't establish a
// containing block for absolutes at all, we just do nothing. All hoisted fragments will
// later be passed up to a parent PositioningContext.
//
// Handling this case here, when the PositioningContext is completely ineffectual other than
// as a temporary container for hoisted boxes, means that callers can execute less conditional
// code.
if !new_fragment
.style
.establishes_containing_block_for_absolute_descendants(new_fragment.base.flags)
{
return;
}
let padding_rect = PhysicalRect::new(
// Ignore the content rects position in its own containing block:
PhysicalPoint::origin(),
@ -258,83 +268,58 @@ impl PositioningContext {
style: &new_fragment.style,
};
let take_hoisted_boxes_pending_layout =
|context: &mut Self| match context.for_nearest_positioned_ancestor.as_mut() {
Some(fragments) => mem::take(fragments),
None => mem::take(&mut context.for_nearest_containing_block_for_all_descendants),
};
let mut fixed_position_boxes_to_hoist = Vec::new();
let mut boxes_to_layout = Vec::new();
self.take_boxes_for_fragment(
new_fragment,
&mut boxes_to_layout,
&mut fixed_position_boxes_to_hoist,
);
// Loop because its possible that we discover (the static position of)
// more absolutely-positioned boxes while doing layout for others.
let mut hoisted_boxes = take_hoisted_boxes_pending_layout(self);
let mut laid_out_child_fragments = Vec::new();
while !hoisted_boxes.is_empty() {
// Laying out a `position: absolute` child (which only establishes a containing block for
// `position: absolute` descendants) can result in more `position: fixed` descendants
// collecting in `self.absolutes`. We need to loop here in order to keep either laying them
// out or putting them into `fixed_position_boxes_to_hoist`. We know there aren't any more
// when `self.absolutes` is empty.
while !boxes_to_layout.is_empty() {
HoistedAbsolutelyPositionedBox::layout_many(
layout_context,
&mut hoisted_boxes,
&mut laid_out_child_fragments,
&mut self.for_nearest_containing_block_for_all_descendants,
std::mem::take(&mut boxes_to_layout),
&mut new_fragment.children,
&mut self.absolutes,
&containing_block,
new_fragment.padding,
);
hoisted_boxes = take_hoisted_boxes_pending_layout(self);
self.take_boxes_for_fragment(
new_fragment,
&mut boxes_to_layout,
&mut fixed_position_boxes_to_hoist,
);
}
new_fragment.children.extend(laid_out_child_fragments);
// We replace here instead of simply preserving these in `take_boxes_for_fragment`
// so that we don't have to continually re-iterate over them when laying out in the
// loop above.
self.absolutes = fixed_position_boxes_to_hoist;
}
pub(crate) fn push(&mut self, box_: HoistedAbsolutelyPositionedBox) {
if let Some(nearest) = &mut self.for_nearest_positioned_ancestor {
let position = box_
.absolutely_positioned_box
.borrow()
.context
.style()
.clone_position();
match position {
Position::Fixed => {}, // fall through
Position::Absolute => return nearest.push(box_),
Position::Static | Position::Relative | Position::Sticky => unreachable!(),
}
}
self.for_nearest_containing_block_for_all_descendants
.push(box_)
pub(crate) fn push(&mut self, hoisted_box: HoistedAbsolutelyPositionedBox) {
debug_assert!(matches!(
hoisted_box.position(),
Position::Absolute | Position::Fixed
));
self.absolutes.push(hoisted_box);
}
pub(crate) fn is_empty(&self) -> bool {
self.for_nearest_containing_block_for_all_descendants
.is_empty() &&
self.for_nearest_positioned_ancestor
.as_ref()
.is_none_or(|vector| vector.is_empty())
}
pub(crate) fn append(&mut self, other: Self) {
if other.is_empty() {
pub(crate) fn append(&mut self, mut other: Self) {
if other.absolutes.is_empty() {
return;
}
vec_append_owned(
&mut self.for_nearest_containing_block_for_all_descendants,
other.for_nearest_containing_block_for_all_descendants,
);
match (
self.for_nearest_positioned_ancestor.as_mut(),
other.for_nearest_positioned_ancestor,
) {
(Some(us), Some(them)) => vec_append_owned(us, them),
(None, Some(them)) => {
// This is the case where we have laid out the absolute children in a containing
// block for absolutes and we then are passing up the fixed-position descendants
// to the containing block for all descendants.
vec_append_owned(
&mut self.for_nearest_containing_block_for_all_descendants,
them,
);
},
(None, None) => {},
_ => unreachable!(),
if self.absolutes.is_empty() {
self.absolutes = other.absolutes;
} else {
self.absolutes.append(&mut other.absolutes)
}
}
@ -344,19 +329,16 @@ impl PositioningContext {
initial_containing_block: &DefiniteContainingBlock,
fragments: &mut Vec<Fragment>,
) {
debug_assert!(self.for_nearest_positioned_ancestor.is_none());
// Loop because its possible that we discover (the static position of)
// more absolutely-positioned boxes while doing layout for others.
while !self
.for_nearest_containing_block_for_all_descendants
.is_empty()
{
// Laying out a `position: absolute` child (which only establishes a containing block for
// `position: absolute` descendants) can result in more `position: fixed` descendants
// collecting in `self.absolutes`. We need to loop here in order to keep laying them out. We
// know there aren't any more when `self.absolutes` is empty.
while !self.absolutes.is_empty() {
HoistedAbsolutelyPositionedBox::layout_many(
layout_context,
&mut mem::take(&mut self.for_nearest_containing_block_for_all_descendants),
mem::take(&mut self.absolutes),
fragments,
&mut self.for_nearest_containing_block_for_all_descendants,
&mut self.absolutes,
initial_containing_block,
Default::default(),
)
@ -365,58 +347,46 @@ impl PositioningContext {
/// Get the length of this [PositioningContext].
pub(crate) fn len(&self) -> PositioningContextLength {
PositioningContextLength {
for_nearest_positioned_ancestor: self
.for_nearest_positioned_ancestor
.as_ref()
.map_or(0, |vec| vec.len()),
for_nearest_containing_block_for_all_descendants: self
.for_nearest_containing_block_for_all_descendants
.len(),
}
PositioningContextLength(self.absolutes.len())
}
/// Truncate this [PositioningContext] to the given [PositioningContextLength]. This
/// is useful for "unhoisting" boxes in this context and returning it to the state at
/// the time that [`PositioningContext::len()`] was called.
pub(crate) fn truncate(&mut self, length: &PositioningContextLength) {
if let Some(vec) = self.for_nearest_positioned_ancestor.as_mut() {
vec.truncate(length.for_nearest_positioned_ancestor);
}
self.for_nearest_containing_block_for_all_descendants
.truncate(length.for_nearest_containing_block_for_all_descendants);
self.absolutes.truncate(length.0)
}
}
/// A data structure which stores the size of a positioning context.
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) struct PositioningContextLength {
/// The number of boxes that will be hoisted the the nearest positioned ancestor for
/// layout.
for_nearest_positioned_ancestor: usize,
/// The number of boxes that will be hoisted the the nearest ancestor which
/// establishes a containing block for all descendants for layout.
for_nearest_containing_block_for_all_descendants: usize,
}
pub(crate) struct PositioningContextLength(usize);
impl Zero for PositioningContextLength {
fn zero() -> Self {
PositioningContextLength {
for_nearest_positioned_ancestor: 0,
for_nearest_containing_block_for_all_descendants: 0,
}
Self(0)
}
fn is_zero(&self) -> bool {
self.for_nearest_positioned_ancestor == 0 &&
self.for_nearest_containing_block_for_all_descendants == 0
self.0.is_zero()
}
}
impl HoistedAbsolutelyPositionedBox {
fn position(&self) -> Position {
let position = self
.absolutely_positioned_box
.borrow()
.context
.style()
.clone_position();
assert!(position == Position::Fixed || position == Position::Absolute);
position
}
pub(crate) fn layout_many(
layout_context: &LayoutContext,
boxes: &mut [Self],
mut boxes: Vec<Self>,
fragments: &mut Vec<Fragment>,
for_nearest_containing_block_for_all_descendants: &mut Vec<HoistedAbsolutelyPositionedBox>,
containing_block: &DefiniteContainingBlock,
@ -463,7 +433,7 @@ impl HoistedAbsolutelyPositionedBox {
pub(crate) fn layout(
&mut self,
layout_context: &LayoutContext,
for_nearest_containing_block_for_all_descendants: &mut Vec<HoistedAbsolutelyPositionedBox>,
hoisted_absolutes_from_children: &mut Vec<HoistedAbsolutelyPositionedBox>,
containing_block: &DefiniteContainingBlock,
containing_block_padding: PhysicalSides<Au>,
) -> Fragment {
@ -502,7 +472,7 @@ impl HoistedAbsolutelyPositionedBox {
false => shared_fragment.resolved_alignment.inline,
};
let mut inline_axis_solver = AbsoluteAxisSolver {
let inline_axis_solver = AbsoluteAxisSolver {
axis: Direction::Inline,
containing_size: cbis,
padding_border_sum: pbm.padding_border_sums.inline,
@ -525,7 +495,7 @@ impl HoistedAbsolutelyPositionedBox {
true => style.clone_align_self().0.0,
false => shared_fragment.resolved_alignment.block,
};
let mut block_axis_solver = AbsoluteAxisSolver {
let block_axis_solver = AbsoluteAxisSolver {
axis: Direction::Block,
containing_size: cbbs,
padding_border_sum: pbm.padding_border_sums.block,
@ -540,53 +510,7 @@ impl HoistedAbsolutelyPositionedBox {
is_table,
};
if let IndependentFormattingContextContents::Replaced(replaced) = &context.contents {
// https://drafts.csswg.org/css2/visudet.html#abs-replaced-width
// https://drafts.csswg.org/css2/visudet.html#abs-replaced-height
let inset_sums = LogicalVec2 {
inline: inline_axis_solver.inset_sum(),
block: block_axis_solver.inset_sum(),
};
let automatic_size = |alignment: AlignFlags, offsets: &LogicalSides1D<_>| {
if alignment.value() == AlignFlags::STRETCH && !offsets.either_auto() {
Size::Stretch
} else {
Size::FitContent
}
};
let used_size = replaced.used_size_as_if_inline_element_from_content_box_sizes(
containing_block,
&style,
context.preferred_aspect_ratio(&pbm.padding_border_sums),
LogicalVec2 {
inline: &inline_axis_solver.computed_sizes,
block: &block_axis_solver.computed_sizes,
},
LogicalVec2 {
inline: automatic_size(inline_alignment, &inline_axis_solver.box_offsets),
block: automatic_size(block_alignment, &block_axis_solver.box_offsets),
},
pbm.padding_border_sums + pbm.margin.auto_is(Au::zero).sum() + inset_sums,
);
inline_axis_solver.override_size(used_size.inline);
block_axis_solver.override_size(used_size.block);
}
// The block axis can depend on layout results, so we only solve it tentatively,
// we may have to resolve it properly later on.
let mut block_axis = block_axis_solver.solve_tentatively();
// The inline axis can be fully resolved, computing intrinsic sizes using the
// tentative block size.
let mut inline_axis = inline_axis_solver.solve(Some(|| {
let ratio = context.preferred_aspect_ratio(&pbm.padding_border_sums);
let constraint_space = ConstraintSpace::new(block_axis.size, style.writing_mode, ratio);
context
.inline_content_sizes(layout_context, &constraint_space)
.sizes
}));
let mut positioning_context = PositioningContext::new_for_style(&style).unwrap();
let mut positioning_context = PositioningContext::default();
let mut new_fragment = {
let content_size: LogicalVec2<Au>;
let fragments;
@ -595,10 +519,34 @@ impl HoistedAbsolutelyPositionedBox {
IndependentFormattingContextContents::Replaced(replaced) => {
// https://drafts.csswg.org/css2/visudet.html#abs-replaced-width
// https://drafts.csswg.org/css2/visudet.html#abs-replaced-height
content_size = LogicalVec2 {
inline: inline_axis.size.to_definite().unwrap(),
block: block_axis.size.to_definite().unwrap(),
let inset_sums = LogicalVec2 {
inline: inline_axis_solver.inset_sum(),
block: block_axis_solver.inset_sum(),
};
let automatic_size = |alignment: AlignFlags, offsets: &LogicalSides1D<_>| {
if alignment.value() == AlignFlags::STRETCH && !offsets.either_auto() {
Size::Stretch
} else {
Size::FitContent
}
};
content_size = replaced.used_size_as_if_inline_element_from_content_box_sizes(
containing_block,
&style,
context.preferred_aspect_ratio(&pbm.padding_border_sums),
LogicalVec2 {
inline: &inline_axis_solver.computed_sizes,
block: &block_axis_solver.computed_sizes,
},
LogicalVec2 {
inline: automatic_size(
inline_alignment,
&inline_axis_solver.box_offsets,
),
block: automatic_size(block_alignment, &block_axis_solver.box_offsets),
},
pbm.padding_border_sums + pbm.margin.auto_is(Au::zero).sum() + inset_sums,
);
fragments = replaced.make_fragments(
layout_context,
&style,
@ -608,11 +556,40 @@ impl HoistedAbsolutelyPositionedBox {
IndependentFormattingContextContents::NonReplaced(non_replaced) => {
// https://drafts.csswg.org/css2/visudet.html#abs-non-replaced-width
// https://drafts.csswg.org/css2/visudet.html#abs-non-replaced-height
let inline_size = inline_axis.size.to_definite().unwrap();
// The block size can depend on layout results, so we only solve it extrinsically,
// we may have to resolve it properly later on.
let block_automatic_size = block_axis_solver.automatic_size();
let block_stretch_size = Some(block_axis_solver.stretch_size());
let extrinsic_block_size = block_axis_solver.computed_sizes.resolve_extrinsic(
block_automatic_size,
Au::zero(),
block_stretch_size,
);
// The inline axis can be fully resolved, computing intrinsic sizes using the
// extrinsic block size.
let get_inline_content_size = || {
let ratio = context.preferred_aspect_ratio(&pbm.padding_border_sums);
let constraint_space =
ConstraintSpace::new(extrinsic_block_size, style.writing_mode, ratio);
context
.inline_content_sizes(layout_context, &constraint_space)
.sizes
};
let inline_size = inline_axis_solver.computed_sizes.resolve(
Direction::Inline,
inline_axis_solver.automatic_size(),
Au::zero,
Some(inline_axis_solver.stretch_size()),
get_inline_content_size,
is_table,
);
let containing_block_for_children = ContainingBlock {
size: ContainingBlockSize {
inline: inline_size,
block: block_axis.size,
block: extrinsic_block_size,
},
style: &style,
};
@ -623,6 +600,14 @@ impl HoistedAbsolutelyPositionedBox {
"Mixed horizontal and vertical writing modes are not supported yet"
);
let lazy_block_size = LazySize::new(
&block_axis_solver.computed_sizes,
Direction::Block,
block_automatic_size,
Au::zero,
block_stretch_size,
is_table,
);
let independent_layout = non_replaced.layout(
layout_context,
&mut positioning_context,
@ -630,24 +615,17 @@ impl HoistedAbsolutelyPositionedBox {
containing_block,
&context.base,
false, /* depends_on_block_constraints */
&lazy_block_size,
);
let inline_size = if let Some(inline_size) =
independent_layout.content_inline_size_for_table
{
// Tables can become narrower than predicted due to collapsed columns,
// so we need to solve again to update margins.
inline_axis_solver.override_size(inline_size);
inline_axis = inline_axis_solver.solve_tentatively();
inline_size
} else {
inline_size
};
// Tables can become narrower than predicted due to collapsed columns
let inline_size = independent_layout
.content_inline_size_for_table
.unwrap_or(inline_size);
// Now we can properly solve the block size.
block_axis = block_axis_solver
.solve(Some(|| independent_layout.content_block_size.into()));
let block_size = block_axis.size.to_definite().unwrap();
let block_size =
lazy_block_size.resolve(|| independent_layout.content_block_size);
content_size = LogicalVec2 {
inline: inline_size,
@ -658,11 +636,13 @@ impl HoistedAbsolutelyPositionedBox {
},
};
let inline_margins = inline_axis_solver.solve_margins(content_size.inline);
let block_margins = block_axis_solver.solve_margins(content_size.block);
let margin = LogicalSides {
inline_start: inline_axis.margin_start,
inline_end: inline_axis.margin_end,
block_start: block_axis.margin_start,
block_end: block_axis.margin_end,
inline_start: inline_margins.start,
inline_end: inline_margins.end,
block_start: block_margins.start,
block_end: block_margins.end,
};
let pb = pbm.padding + pbm.border;
@ -699,6 +679,10 @@ impl HoistedAbsolutelyPositionedBox {
)
.with_specific_layout_info(specific_layout_info)
};
// This is an absolutely positioned element, which means it also establishes a
// containing block for absolutes. We lay out any absolutely positioned children
// here and pass the rest to `hoisted_absolutes_from_children.`
positioning_context.layout_collected_children(layout_context, &mut new_fragment);
// Any hoisted boxes that remain in this positioning context are going to be hoisted
@ -711,8 +695,7 @@ impl HoistedAbsolutelyPositionedBox {
PositioningContextLength::zero(),
);
for_nearest_containing_block_for_all_descendants
.extend(positioning_context.for_nearest_containing_block_for_all_descendants);
hoisted_absolutes_from_children.extend(positioning_context.absolutes);
let fragment = Fragment::Box(ArcRefCell::new(new_fragment));
context.base.set_fragment(fragment.clone());
@ -741,12 +724,6 @@ impl LogicalRect<Au> {
}
}
struct AxisResult {
size: SizeConstraint,
margin_start: Au,
margin_end: Au,
}
struct AbsoluteAxisSolver<'a> {
axis: Direction,
containing_size: Au,
@ -789,101 +766,56 @@ impl AbsoluteAxisSolver<'_> {
}
}
/// This unifies some of the parts in common in:
///
/// * <https://drafts.csswg.org/css2/visudet.html#abs-non-replaced-width>
/// * <https://drafts.csswg.org/css2/visudet.html#abs-non-replaced-height>
///
/// … and:
///
/// * <https://drafts.csswg.org/css2/visudet.html#abs-replaced-width>
/// * <https://drafts.csswg.org/css2/visudet.html#abs-replaced-height>
///
/// In the replaced case, `size` is never `Auto`.
fn solve(&self, get_content_size: Option<impl FnOnce() -> ContentSizes>) -> AxisResult {
let solve_size = |initial_behavior, stretch_size: Au| -> SizeConstraint {
let stretch_size = stretch_size.max(Au::zero());
if let Some(get_content_size) = get_content_size {
SizeConstraint::Definite(self.computed_sizes.resolve(
self.axis,
initial_behavior,
Au::zero,
Some(stretch_size),
get_content_size,
self.is_table,
))
} else {
self.computed_sizes.resolve_extrinsic(
initial_behavior,
Au::zero(),
Some(stretch_size),
)
}
};
if self.box_offsets.either_auto() {
let margin_start = self.computed_margin_start.auto_is(Au::zero);
let margin_end = self.computed_margin_end.auto_is(Au::zero);
let stretch_size = self.containing_size -
self.inset_sum() -
self.padding_border_sum -
margin_start -
margin_end;
let size = solve_size(Size::FitContent, stretch_size);
AxisResult {
size,
margin_start,
margin_end,
}
} else {
let mut free_space = self.containing_size - self.inset_sum() - self.padding_border_sum;
let stretch_size = free_space -
self.computed_margin_start.auto_is(Au::zero) -
self.computed_margin_end.auto_is(Au::zero);
let initial_behavior = match self.alignment.value() {
AlignFlags::NORMAL | AlignFlags::AUTO if !self.is_table => Size::Stretch,
AlignFlags::STRETCH => Size::Stretch,
_ => Size::FitContent,
};
let size = solve_size(initial_behavior, stretch_size);
if let Some(used_size) = size.to_definite() {
free_space -= used_size;
} else {
free_space = Au::zero();
}
let (margin_start, margin_end) =
match (self.computed_margin_start, self.computed_margin_end) {
(AuOrAuto::Auto, AuOrAuto::Auto) => {
if self.avoid_negative_margin_start && free_space < Au::zero() {
(Au::zero(), free_space)
} else {
let margin_start = free_space / 2;
(margin_start, free_space - margin_start)
}
},
(AuOrAuto::Auto, AuOrAuto::LengthPercentage(end)) => (free_space - end, end),
(AuOrAuto::LengthPercentage(start), AuOrAuto::Auto) => {
(start, free_space - start)
},
(AuOrAuto::LengthPercentage(start), AuOrAuto::LengthPercentage(end)) => {
(start, end)
},
};
AxisResult {
size,
margin_start,
margin_end,
}
#[inline]
fn automatic_size(&self) -> Size<Au> {
match self.alignment.value() {
_ if self.box_offsets.either_auto() => Size::FitContent,
AlignFlags::NORMAL | AlignFlags::AUTO if !self.is_table => Size::Stretch,
AlignFlags::STRETCH => Size::Stretch,
_ => Size::FitContent,
}
}
fn solve_tentatively(&mut self) -> AxisResult {
self.solve(None::<fn() -> ContentSizes>)
#[inline]
fn stretch_size(&self) -> Au {
Au::zero().max(
self.containing_size -
self.inset_sum() -
self.padding_border_sum -
self.computed_margin_start.auto_is(Au::zero) -
self.computed_margin_end.auto_is(Au::zero),
)
}
fn override_size(&mut self, size: Au) {
self.computed_sizes.preferred = Size::Numeric(size);
self.computed_sizes.min = Size::default();
self.computed_sizes.max = Size::default();
fn solve_margins(&self, size: Au) -> LogicalSides1D<Au> {
if self.box_offsets.either_auto() {
LogicalSides1D::new(
self.computed_margin_start.auto_is(Au::zero),
self.computed_margin_end.auto_is(Au::zero),
)
} else {
let free_space =
self.containing_size - self.inset_sum() - self.padding_border_sum - size;
match (self.computed_margin_start, self.computed_margin_end) {
(AuOrAuto::Auto, AuOrAuto::Auto) => {
if self.avoid_negative_margin_start && free_space < Au::zero() {
LogicalSides1D::new(Au::zero(), free_space)
} else {
let margin_start = free_space / 2;
LogicalSides1D::new(margin_start, free_space - margin_start)
}
},
(AuOrAuto::Auto, AuOrAuto::LengthPercentage(end)) => {
LogicalSides1D::new(free_space - end, end)
},
(AuOrAuto::LengthPercentage(start), AuOrAuto::Auto) => {
LogicalSides1D::new(start, free_space - start)
},
(AuOrAuto::LengthPercentage(start), AuOrAuto::LengthPercentage(end)) => {
LogicalSides1D::new(start, end)
},
}
}
}
fn origin_for_margin_box(
@ -1014,14 +946,6 @@ impl AbsoluteAxisSolver<'_> {
}
}
fn vec_append_owned<T>(a: &mut Vec<T>, mut b: Vec<T>) {
if a.is_empty() {
*a = b
} else {
a.append(&mut b)
}
}
/// <https://drafts.csswg.org/css2/visuren.html#relative-positioning>
pub(crate) fn relative_adjustement(
style: &ComputedValues,

View file

@ -7,8 +7,9 @@ use std::sync::Arc;
use app_units::Au;
use euclid::default::{Point2D, Rect};
use euclid::{SideOffsets2D, Size2D, Vector2D};
use euclid::{SideOffsets2D, Size2D};
use itertools::Itertools;
use script::layout_dom::ServoLayoutNode;
use script_layout_interface::wrapper_traits::{
LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode,
};
@ -20,7 +21,7 @@ use style::computed_values::position::T as Position;
use style::computed_values::visibility::T as Visibility;
use style::computed_values::white_space_collapse::T as WhiteSpaceCollapseValue;
use style::context::{QuirksMode, SharedStyleContext, StyleContext, ThreadLocalStyleContext};
use style::dom::{OpaqueNode, TElement};
use style::dom::{NodeInfo, OpaqueNode, TElement, TNode};
use style::properties::style_structs::Font;
use style::properties::{
ComputedValues, Importance, LonghandId, PropertyDeclarationBlock, PropertyDeclarationId,
@ -38,59 +39,60 @@ use style::values::specified::GenericGridTemplateComponent;
use style::values::specified::box_::DisplayInside;
use style_traits::{ParsingMode, ToCss};
use crate::ArcRefCell;
use crate::dom::NodeExt;
use crate::flow::inline::construct::{TextTransformation, WhitespaceCollapse};
use crate::fragment_tree::{
BoxFragment, Fragment, FragmentFlags, FragmentTree, SpecificLayoutInfo, Tag,
BoxFragment, Fragment, FragmentFlags, FragmentTree, SpecificLayoutInfo,
};
use crate::geom::{PhysicalRect, PhysicalVec};
use crate::taffy::SpecificTaffyGridInfo;
pub fn process_content_box_request(
requested_node: OpaqueNode,
fragment_tree: Option<Arc<FragmentTree>>,
) -> Option<Rect<Au>> {
let rects = fragment_tree?.get_content_boxes_for_node(requested_node);
pub fn process_content_box_request(node: ServoLayoutNode<'_>) -> Option<Rect<Au>> {
let rects: Vec<_> = node
.fragments_for_pseudo(None)
.iter()
.filter_map(Fragment::cumulative_border_box_rect)
.collect();
if rects.is_empty() {
return None;
}
Some(
rects
.iter()
.fold(Rect::zero(), |unioned_rect, rect| rect.union(&unioned_rect)),
)
Some(rects.iter().fold(Rect::zero(), |unioned_rect, rect| {
rect.to_untyped().union(&unioned_rect)
}))
}
pub fn process_content_boxes_request(
requested_node: OpaqueNode,
fragment_tree: Option<Arc<FragmentTree>>,
) -> Vec<Rect<Au>> {
fragment_tree
.map(|tree| tree.get_content_boxes_for_node(requested_node))
pub fn process_content_boxes_request(node: ServoLayoutNode<'_>) -> Vec<Rect<Au>> {
node.fragments_for_pseudo(None)
.iter()
.filter_map(Fragment::cumulative_border_box_rect)
.map(|rect| rect.to_untyped())
.collect()
}
pub fn process_client_rect_request(node: ServoLayoutNode<'_>) -> Rect<i32> {
node.fragments_for_pseudo(None)
.first()
.map(Fragment::client_rect)
.unwrap_or_default()
}
pub fn process_node_geometry_request(
requested_node: OpaqueNode,
fragment_tree: Option<Arc<FragmentTree>>,
) -> Rect<i32> {
if let Some(fragment_tree) = fragment_tree {
fragment_tree.get_border_dimensions_for_node(requested_node)
} else {
Rect::zero()
}
}
/// <https://drafts.csswg.org/cssom-view/#scrolling-area>
pub fn process_node_scroll_area_request(
requested_node: Option<OpaqueNode>,
requested_node: Option<ServoLayoutNode<'_>>,
fragment_tree: Option<Arc<FragmentTree>>,
) -> Rect<i32> {
let rect = match (fragment_tree, requested_node) {
(Some(tree), Some(node)) => tree.get_scrolling_area_for_node(node),
(Some(tree), None) => tree.get_scrolling_area_for_viewport(),
_ => return Rect::zero(),
let Some(tree) = fragment_tree else {
return Rect::zero();
};
let rect = match requested_node {
Some(node) => node
.fragments_for_pseudo(None)
.first()
.map(Fragment::scrolling_area)
.unwrap_or_default(),
None => tree.get_scrolling_area_for_viewport(),
};
Rect::new(
@ -104,12 +106,11 @@ pub fn process_node_scroll_area_request(
/// Return the resolved value of property for a given (pseudo)element.
/// <https://drafts.csswg.org/cssom/#resolved-value>
pub fn process_resolved_style_request<'dom>(
pub fn process_resolved_style_request(
context: &SharedStyleContext,
node: impl LayoutNode<'dom> + 'dom,
node: ServoLayoutNode<'_>,
pseudo: &Option<PseudoElement>,
property: &PropertyId,
fragment_tree: Option<Arc<FragmentTree>>,
) -> String {
if !node.as_element().unwrap().has_data() {
return process_resolved_style_request_for_unstyled_node(context, node, pseudo, property);
@ -161,8 +162,6 @@ pub fn process_resolved_style_request<'dom>(
_ => style.computed_value_to_string(PropertyDeclarationId::Longhand(longhand_id)),
};
let tag_to_find = Tag::new_pseudo(node.opaque(), *pseudo);
// https://drafts.csswg.org/cssom/#dom-window-getcomputedstyle
// Here we are trying to conform to the specification that says that getComputedStyle
// should return the used values in certain circumstances. For size and positional
@ -191,107 +190,87 @@ pub fn process_resolved_style_request<'dom>(
return computed_style(None);
}
let resolve_for_fragment =
|fragment: &Fragment, containing_block: Option<&PhysicalRect<Au>>| {
let (content_rect, margins, padding, specific_layout_info) = match fragment {
Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => {
let box_fragment = box_fragment.borrow();
if style.get_box().position != Position::Static {
let resolved_insets = || {
box_fragment
.calculate_resolved_insets_if_positioned(containing_block.unwrap())
};
match longhand_id {
LonghandId::Top => return resolved_insets().top.to_css_string(),
LonghandId::Right => {
return resolved_insets().right.to_css_string();
},
LonghandId::Bottom => {
return resolved_insets().bottom.to_css_string();
},
LonghandId::Left => {
return resolved_insets().left.to_css_string();
},
_ => {},
}
}
let content_rect = box_fragment.content_rect;
let margins = box_fragment.margin;
let padding = box_fragment.padding;
let specific_layout_info = box_fragment.specific_layout_info.clone();
(content_rect, margins, padding, specific_layout_info)
},
Fragment::Positioning(positioning_fragment) => {
let content_rect = positioning_fragment.borrow().rect;
(
content_rect,
SideOffsets2D::zero(),
SideOffsets2D::zero(),
None,
)
},
_ => return computed_style(Some(fragment)),
};
// https://drafts.csswg.org/css-grid/#resolved-track-list
// > The grid-template-rows and grid-template-columns properties are
// > resolved value special case properties.
//
// > When an element generates a grid container box...
if display.inside() == DisplayInside::Grid {
if let Some(SpecificLayoutInfo::Grid(info)) = specific_layout_info {
if let Some(value) = resolve_grid_template(&info, style, longhand_id) {
return value;
let resolve_for_fragment = |fragment: &Fragment| {
let (content_rect, margins, padding, specific_layout_info) = match fragment {
Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => {
let box_fragment = box_fragment.borrow();
if style.get_box().position != Position::Static {
let resolved_insets = || box_fragment.calculate_resolved_insets_if_positioned();
match longhand_id {
LonghandId::Top => return resolved_insets().top.to_css_string(),
LonghandId::Right => {
return resolved_insets().right.to_css_string();
},
LonghandId::Bottom => {
return resolved_insets().bottom.to_css_string();
},
LonghandId::Left => {
return resolved_insets().left.to_css_string();
},
_ => {},
}
}
}
// https://drafts.csswg.org/cssom/#resolved-value-special-case-property-like-height
// > If the property applies to the element or pseudo-element and the resolved value of the
// > display property is not none or contents, then the resolved value is the used value.
// > Otherwise the resolved value is the computed value.
//
// However, all browsers ignore that for margin and padding properties, and resolve to a length
// even if the property doesn't apply: https://github.com/w3c/csswg-drafts/issues/10391
match longhand_id {
LonghandId::Width if resolved_size_should_be_used_value(fragment) => {
content_rect.size.width
},
LonghandId::Height if resolved_size_should_be_used_value(fragment) => {
content_rect.size.height
},
LonghandId::MarginBottom => margins.bottom,
LonghandId::MarginTop => margins.top,
LonghandId::MarginLeft => margins.left,
LonghandId::MarginRight => margins.right,
LonghandId::PaddingBottom => padding.bottom,
LonghandId::PaddingTop => padding.top,
LonghandId::PaddingLeft => padding.left,
LonghandId::PaddingRight => padding.right,
_ => return computed_style(Some(fragment)),
}
.to_css_string()
let content_rect = box_fragment.content_rect;
let margins = box_fragment.margin;
let padding = box_fragment.padding;
let specific_layout_info = box_fragment.specific_layout_info.clone();
(content_rect, margins, padding, specific_layout_info)
},
Fragment::Positioning(positioning_fragment) => {
let content_rect = positioning_fragment.borrow().rect;
(
content_rect,
SideOffsets2D::zero(),
SideOffsets2D::zero(),
None,
)
},
_ => return computed_style(Some(fragment)),
};
if !matches!(
longhand_id,
LonghandId::Top | LonghandId::Bottom | LonghandId::Left | LonghandId::Right
) {
if let Some(fragment) = node.fragments_for_pseudo(*pseudo).first() {
return resolve_for_fragment(fragment, None);
}
}
fragment_tree
.and_then(|fragment_tree| {
fragment_tree.find(|fragment, _, containing_block| {
if Some(tag_to_find) == fragment.tag() {
Some(resolve_for_fragment(fragment, Some(containing_block)))
} else {
None
// https://drafts.csswg.org/css-grid/#resolved-track-list
// > The grid-template-rows and grid-template-columns properties are
// > resolved value special case properties.
//
// > When an element generates a grid container box...
if display.inside() == DisplayInside::Grid {
if let Some(SpecificLayoutInfo::Grid(info)) = specific_layout_info {
if let Some(value) = resolve_grid_template(&info, style, longhand_id) {
return value;
}
})
})
}
}
// https://drafts.csswg.org/cssom/#resolved-value-special-case-property-like-height
// > If the property applies to the element or pseudo-element and the resolved value of the
// > display property is not none or contents, then the resolved value is the used value.
// > Otherwise the resolved value is the computed value.
//
// However, all browsers ignore that for margin and padding properties, and resolve to a length
// even if the property doesn't apply: https://github.com/w3c/csswg-drafts/issues/10391
match longhand_id {
LonghandId::Width if resolved_size_should_be_used_value(fragment) => {
content_rect.size.width
},
LonghandId::Height if resolved_size_should_be_used_value(fragment) => {
content_rect.size.height
},
LonghandId::MarginBottom => margins.bottom,
LonghandId::MarginTop => margins.top,
LonghandId::MarginLeft => margins.left,
LonghandId::MarginRight => margins.right,
LonghandId::PaddingBottom => padding.bottom,
LonghandId::PaddingTop => padding.top,
LonghandId::PaddingLeft => padding.left,
LonghandId::PaddingRight => padding.right,
_ => return computed_style(Some(fragment)),
}
.to_css_string()
};
node.fragments_for_pseudo(*pseudo)
.first()
.map(resolve_for_fragment)
.unwrap_or_else(|| computed_style(None))
}
@ -383,9 +362,9 @@ fn resolve_grid_template(
}
}
pub fn process_resolved_style_request_for_unstyled_node<'dom>(
pub fn process_resolved_style_request_for_unstyled_node(
context: &SharedStyleContext,
node: impl LayoutNode<'dom>,
node: ServoLayoutNode<'_>,
pseudo: &Option<PseudoElement>,
property: &PropertyId,
) -> String {
@ -450,233 +429,155 @@ fn shorthand_to_css_string(
}
}
pub fn process_offset_parent_query(
node: OpaqueNode,
fragment_tree: Option<Arc<FragmentTree>>,
) -> OffsetParentResponse {
process_offset_parent_query_inner(node, fragment_tree).unwrap_or_default()
struct OffsetParentFragments {
parent: ArcRefCell<BoxFragment>,
grandparent: Option<Fragment>,
}
/// <https://www.w3.org/TR/2016/WD-cssom-view-1-20160317/#dom-htmlelement-offsetparent>
fn offset_parent_fragments(node: ServoLayoutNode<'_>) -> Option<OffsetParentFragments> {
// 1. If any of the following holds true return null and terminate this algorithm:
// * The element does not have an associated CSS layout box.
// * The element is the root element.
// * The element is the HTML body element.
// * The elements computed value of the position property is fixed.
let fragment = node.fragments_for_pseudo(None).first().cloned()?;
let flags = fragment.base()?.flags;
if flags.intersects(
FragmentFlags::IS_ROOT_ELEMENT | FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT,
) {
return None;
}
if matches!(
fragment, Fragment::Box(fragment) if fragment.borrow().style.get_box().position == Position::Fixed
) {
return None;
}
// 2. Return the nearest ancestor element of the element for which at least one of
// the following is true and terminate this algorithm if such an ancestor is found:
// * The computed value of the position property is not static.
// * It is the HTML body element.
// * The computed value of the position property of the element is static and the
// ancestor is one of the following HTML elements: td, th, or table.
let mut maybe_parent_node = node.parent_node();
while let Some(parent_node) = maybe_parent_node {
maybe_parent_node = parent_node.parent_node();
if let Some(parent_fragment) = parent_node.fragments_for_pseudo(None).first() {
let parent_fragment = match parent_fragment {
Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => box_fragment,
_ => continue,
};
let grandparent_fragment =
maybe_parent_node.and_then(|node| node.fragments_for_pseudo(None).first().cloned());
if parent_fragment.borrow().style.get_box().position != Position::Static {
return Some(OffsetParentFragments {
parent: parent_fragment.clone(),
grandparent: grandparent_fragment,
});
}
let flags = parent_fragment.borrow().base.flags;
if flags.intersects(
FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT |
FragmentFlags::IS_TABLE_TH_OR_TD_ELEMENT,
) {
return Some(OffsetParentFragments {
parent: parent_fragment.clone(),
grandparent: grandparent_fragment,
});
}
}
}
None
}
#[inline]
fn process_offset_parent_query_inner(
node: OpaqueNode,
fragment_tree: Option<Arc<FragmentTree>>,
) -> Option<OffsetParentResponse> {
let fragment_tree = fragment_tree?;
pub fn process_offset_parent_query(node: ServoLayoutNode<'_>) -> Option<OffsetParentResponse> {
// Only consider the first fragment of the node found as per a
// possible interpretation of the specification: "[...] return the
// y-coordinate of the top border edge of the first CSS layout box
// associated with the element [...]"
//
// FIXME: Browsers implement this all differently (e.g., [1]) -
// Firefox does returns the union of all layout elements of some
// sort. Chrome returns the first fragment for a block element (the
// same as ours) or the union of all associated fragments in the
// first containing block fragment for an inline element. We could
// implement Chrome's behavior, but our fragment tree currently
// provides insufficient information.
//
// [1]: https://github.com/w3c/csswg-drafts/issues/4541
// > 1. If the element is the HTML body element or does not have any associated CSS
// layout box return zero and terminate this algorithm.
let fragment = node.fragments_for_pseudo(None).first().cloned()?;
let mut border_box = fragment.cumulative_border_box_rect()?;
struct NodeOffsetBoxInfo {
border_box: Rect<Au>,
offset_parent_node_address: Option<OpaqueNode>,
is_static_body_element: bool,
}
// https://www.w3.org/TR/2016/WD-cssom-view-1-20160317/#extensions-to-the-htmlelement-interface
let mut parent_node_addresses: Vec<Option<(OpaqueNode, bool)>> = Vec::new();
let tag_to_find = Tag::new(node);
let node_offset_box = fragment_tree.find(|fragment, level, containing_block| {
let base = fragment.base()?;
let is_body_element = base
.flags
.contains(FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT);
if fragment.tag() == Some(tag_to_find) {
// Only consider the first fragment of the node found as per a
// possible interpretation of the specification: "[...] return the
// y-coordinate of the top border edge of the first CSS layout box
// associated with the element [...]"
//
// FIXME: Browsers implement this all differently (e.g., [1]) -
// Firefox does returns the union of all layout elements of some
// sort. Chrome returns the first fragment for a block element (the
// same as ours) or the union of all associated fragments in the
// first containing block fragment for an inline element. We could
// implement Chrome's behavior, but our fragment tree currently
// provides insufficient information.
//
// [1]: https://github.com/w3c/csswg-drafts/issues/4541
let fragment_relative_rect = match fragment {
Fragment::Box(fragment) | Fragment::Float(fragment) => fragment.borrow().border_rect(),
Fragment::Text(fragment) => fragment.borrow().rect,
Fragment::Positioning(fragment) => fragment.borrow().rect,
Fragment::AbsoluteOrFixedPositioned(_) |
Fragment::Image(_) |
Fragment::IFrame(_) => unreachable!(),
};
let mut border_box = fragment_relative_rect.translate(containing_block.origin.to_vector()).to_untyped();
// "If any of the following holds true return null and terminate
// this algorithm: [...] The elements computed value of the
// `position` property is `fixed`."
let is_fixed = matches!(
fragment, Fragment::Box(fragment) if fragment.borrow().style.get_box().position == Position::Fixed
);
if is_body_element {
// "If the element is the HTML body element or [...] return zero
// and terminate this algorithm."
border_box.origin = Point2D::zero();
}
let offset_parent_node = if is_fixed {
None
} else {
// Find the nearest ancestor element eligible as `offsetParent`.
parent_node_addresses[..level]
.iter()
.rev()
.cloned()
.find_map(std::convert::identity)
};
Some(NodeOffsetBoxInfo {
border_box,
offset_parent_node_address: offset_parent_node.map(|node| node.0),
is_static_body_element: offset_parent_node.is_some_and(|node| node.1),
})
} else {
// Record the paths of the nodes being traversed.
let parent_node_address = match fragment {
Fragment::Box(fragment) | Fragment::Float(fragment) => {
let fragment = &*fragment.borrow();
let is_eligible_parent = is_eligible_parent(fragment);
let is_static_body_element = is_body_element &&
fragment.style.get_box().position == Position::Static;
match base.tag {
Some(tag) if is_eligible_parent && !tag.is_pseudo() => {
Some((tag.node, is_static_body_element))
},
_ => None,
}
},
Fragment::AbsoluteOrFixedPositioned(_) |
Fragment::IFrame(_) |
Fragment::Image(_) |
Fragment::Positioning(_) |
Fragment::Text(_) => None,
};
while parent_node_addresses.len() <= level {
parent_node_addresses.push(None);
}
parent_node_addresses[level] = parent_node_address;
None
}
});
// Bail out if the element doesn't have an associated fragment.
// "If any of the following holds true return null and terminate this
// algorithm: [...] The element does not have an associated CSS layout box."
// (`offsetParent`) "If the element is the HTML body element [...] return
// zero and terminate this algorithm." (others)
let node_offset_box = node_offset_box?;
let offset_parent_padding_box_corner = if let Some(offset_parent_node_address) =
node_offset_box.offset_parent_node_address
{
// The spec (https://www.w3.org/TR/cssom-view-1/#extensions-to-the-htmlelement-interface)
// says that offsetTop/offsetLeft are always relative to the padding box of the offsetParent.
// However, in practice this is not true in major browsers in the case that the offsetParent is the body
// element and the body element is position:static. In that case offsetLeft/offsetTop are computed
// relative to the root node's border box.
if node_offset_box.is_static_body_element {
fn extract_box_fragment(
fragment: &Fragment,
containing_block: &PhysicalRect<Au>,
) -> PhysicalVec<Au> {
let (Fragment::Box(fragment) | Fragment::Float(fragment)) = fragment else {
unreachable!();
};
// Again, take the *first* associated CSS layout box.
fragment.borrow().border_rect().origin.to_vector() +
containing_block.origin.to_vector()
}
let containing_block = &fragment_tree.initial_containing_block;
let fragment = &fragment_tree.root_fragments[0];
if let Fragment::AbsoluteOrFixedPositioned(shared_fragment) = fragment {
let shared_fragment = &*shared_fragment.borrow();
let fragment = shared_fragment.fragment.as_ref().unwrap();
extract_box_fragment(fragment, containing_block)
} else {
extract_box_fragment(fragment, containing_block)
}
} else {
// Find the top and left padding edges of "the first CSS layout box
// associated with the `offsetParent` of the element".
//
// Since we saw `offset_parent_node_address` once, we should be able
// to find it again.
let offset_parent_node_tag = Tag::new(offset_parent_node_address);
fragment_tree
.find(|fragment, _, containing_block| {
match fragment {
Fragment::Box(fragment) | Fragment::Float(fragment) => {
let fragment = fragment.borrow();
if fragment.base.tag == Some(offset_parent_node_tag) {
// Again, take the *first* associated CSS layout box.
let padding_box_corner = fragment.padding_rect().origin.to_vector()
+ containing_block.origin.to_vector();
Some(padding_box_corner)
} else {
None
}
},
Fragment::AbsoluteOrFixedPositioned(_)
| Fragment::Text(_)
| Fragment::Image(_)
| Fragment::IFrame(_)
| Fragment::Positioning(_) => None,
}
})
.unwrap()
}
} else {
// "If the offsetParent of the element is null," subtract zero in the
// following step.
Vector2D::zero()
// 2. If the offsetParent of the element is null return the x-coordinate of the left
// border edge of the first CSS layout box associated with the element, relative to
// the initial containing block origin, ignoring any transforms that apply to the
// element and its ancestors, and terminate this algorithm.
let Some(offset_parent_fragment) = offset_parent_fragments(node) else {
return Some(OffsetParentResponse {
node_address: None,
rect: border_box.to_untyped(),
});
};
let parent_fragment = offset_parent_fragment.parent.borrow();
let parent_is_static_body_element = parent_fragment
.base
.flags
.contains(FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT) &&
parent_fragment.style.get_box().position == Position::Static;
// For `offsetLeft`:
// 3. Return the result of subtracting the y-coordinate of the top padding edge of the
// first CSS layout box associated with the offsetParent of the element from the
// y-coordinate of the top border edge of the first CSS layout box associated with the
// element, relative to the initial containing block origin, ignoring any transforms
// that apply to the element and its ancestors.
//
// We generalize this for `offsetRight` as described in the specification.
let grandparent_box_fragment = || match offset_parent_fragment.grandparent {
Some(Fragment::Box(box_fragment)) | Some(Fragment::Float(box_fragment)) => {
Some(box_fragment)
},
_ => None,
};
// The spec (https://www.w3.org/TR/cssom-view-1/#extensions-to-the-htmlelement-interface)
// says that offsetTop/offsetLeft are always relative to the padding box of the offsetParent.
// However, in practice this is not true in major browsers in the case that the offsetParent is the body
// element and the body element is position:static. In that case offsetLeft/offsetTop are computed
// relative to the root node's border box.
//
// See <https://github.com/w3c/csswg-drafts/issues/10549>.
let parent_offset_rect = if parent_is_static_body_element {
if let Some(grandparent_fragment) = grandparent_box_fragment() {
let grandparent_fragment = grandparent_fragment.borrow();
grandparent_fragment.offset_by_containing_block(&grandparent_fragment.border_rect())
} else {
parent_fragment.offset_by_containing_block(&parent_fragment.padding_rect())
}
} else {
parent_fragment.offset_by_containing_block(&parent_fragment.padding_rect())
};
border_box = border_box.translate(-parent_offset_rect.origin.to_vector());
Some(OffsetParentResponse {
node_address: node_offset_box.offset_parent_node_address.map(Into::into),
// "Return the result of subtracting the x-coordinate of the left
// padding edge of the first CSS layout box associated with the
// `offsetParent` of the element from the x-coordinate of the left
// border edge of the first CSS layout box associated with the element,
// relative to the initial containing block origin, ignoring any
// transforms that apply to the element and its ancestors." (and vice
// versa for the top border edge)
rect: node_offset_box
.border_box
.translate(-offset_parent_padding_box_corner.to_untyped()),
node_address: parent_fragment.base.tag.map(|tag| tag.node.into()),
rect: border_box.to_untyped(),
})
}
/// Returns whether or not the element with the given style and body element determination
/// is eligible to be a parent element for offset* queries.
///
/// From <https://www.w3.org/TR/cssom-view-1/#dom-htmlelement-offsetparent>:
///
/// > Return the nearest ancestor element of the element for which at least one of the following is
/// > true and terminate this algorithm if such an ancestor is found:
/// > 1. The computed value of the position property is not static.
/// > 2. It is the HTML body element.
/// > 3. The computed value of the position property of the element is static and the ancestor is
/// > one of the following HTML elements: td, th, or table.
fn is_eligible_parent(fragment: &BoxFragment) -> bool {
fragment
.base
.flags
.contains(FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT) ||
fragment.style.get_box().position != Position::Static ||
fragment
.base
.flags
.contains(FragmentFlags::IS_TABLE_TH_OR_TD_ELEMENT)
}
/// <https://html.spec.whatwg.org/multipage/#get-the-text-steps>
pub fn get_the_text_steps<'dom>(node: impl LayoutNode<'dom>) -> String {
pub fn get_the_text_steps(node: ServoLayoutNode<'_>) -> String {
// Step 1: If element is not being rendered or if the user agent is a non-CSS user agent, then
// return element's descendant text content.
// This is taken care of in HTMLElemnent code
@ -764,8 +665,8 @@ impl Default for RenderedTextCollectionState {
}
/// <https://html.spec.whatwg.org/multipage/#rendered-text-collection-steps>
fn rendered_text_collection_steps<'dom>(
node: impl LayoutNode<'dom>,
fn rendered_text_collection_steps(
node: ServoLayoutNode<'_>,
state: &mut RenderedTextCollectionState,
) -> Vec<InnerOrOuterTextItem> {
// Step 1. Let items be the result of running the rendered text collection

View file

@ -3,7 +3,6 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::cell::LazyCell;
use std::fmt;
use std::sync::Arc;
use app_units::Au;
@ -14,10 +13,12 @@ use euclid::{Scale, Size2D};
use malloc_size_of_derive::MallocSizeOf;
use net_traits::image_cache::{ImageOrMetadataAvailable, UsePlaceholder};
use pixels::Image;
use script::layout_dom::ServoLayoutNode;
use script_layout_interface::IFrameSize;
use servo_arc::Arc as ServoArc;
use style::Zero;
use style::computed_values::object_fit::T as ObjectFit;
use style::dom::TNode;
use style::logical_geometry::{Direction, WritingMode};
use style::properties::ComputedValues;
use style::servo::url::ComputedUrl;
@ -96,33 +97,9 @@ impl NaturalSizes {
}
}
#[derive(MallocSizeOf)]
pub(crate) enum CanvasSource {
WebGL(ImageKey),
Image(ImageKey),
WebGPU(ImageKey),
/// transparent black
Empty,
}
impl fmt::Debug for CanvasSource {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}",
match *self {
CanvasSource::WebGL(_) => "WebGL",
CanvasSource::Image(_) => "Image",
CanvasSource::WebGPU(_) => "WebGPU",
CanvasSource::Empty => "Empty",
}
)
}
}
#[derive(Debug, MallocSizeOf)]
pub(crate) struct CanvasInfo {
pub source: CanvasSource,
pub source: Option<ImageKey>,
}
#[derive(Debug, MallocSizeOf)]
@ -145,7 +122,7 @@ pub(crate) enum ReplacedContentKind {
}
impl ReplacedContents {
pub fn for_element<'dom>(element: impl NodeExt<'dom>, context: &LayoutContext) -> Option<Self> {
pub fn for_element(element: ServoLayoutNode<'_>, context: &LayoutContext) -> Option<Self> {
if let Some(ref data_attribute_string) = element.as_typeless_object_with_data_attribute() {
if let Some(url) = try_to_parse_image_data_url(data_attribute_string) {
return Self::from_image_url(
@ -209,8 +186,8 @@ impl ReplacedContents {
})
}
pub fn from_image_url<'dom>(
element: impl NodeExt<'dom>,
pub fn from_image_url(
element: ServoLayoutNode<'_>,
context: &LayoutContext,
image_url: &ComputedUrl,
) -> Option<Self> {
@ -220,13 +197,13 @@ impl ReplacedContents {
image_url.clone().into(),
UsePlaceholder::No,
) {
Some(ImageOrMetadataAvailable::ImageAvailable { image, .. }) => {
Ok(ImageOrMetadataAvailable::ImageAvailable { image, .. }) => {
(Some(image.clone()), image.width as f32, image.height as f32)
},
Some(ImageOrMetadataAvailable::MetadataAvailable(metadata, _id)) => {
Ok(ImageOrMetadataAvailable::MetadataAvailable(metadata, _id)) => {
(None, metadata.width as f32, metadata.height as f32)
},
None => return None,
Err(_) => return None,
};
return Some(Self {
@ -238,8 +215,8 @@ impl ReplacedContents {
None
}
pub fn from_image<'dom>(
element: impl NodeExt<'dom>,
pub fn from_image(
element: ServoLayoutNode<'_>,
context: &LayoutContext,
image: &ComputedImage,
) -> Option<Self> {
@ -388,12 +365,10 @@ impl ReplacedContents {
return vec![];
}
let image_key = match canvas_info.source {
CanvasSource::WebGL(image_key) => image_key,
CanvasSource::WebGPU(image_key) => image_key,
CanvasSource::Image(image_key) => image_key,
CanvasSource::Empty => return vec![],
let Some(image_key) = canvas_info.source else {
return vec![];
};
vec![Fragment::Image(ArcRefCell::new(ImageFragment {
base: self.base_fragment_info.into(),
style: style.clone(),

View file

@ -12,7 +12,7 @@ use style::computed_values::mix_blend_mode::T as ComputedMixBlendMode;
use style::computed_values::position::T as ComputedPosition;
use style::computed_values::transform_style::T as ComputedTransformStyle;
use style::computed_values::unicode_bidi::T as UnicodeBidi;
use style::logical_geometry::{Direction as AxisDirection, WritingMode};
use style::logical_geometry::{Direction as AxisDirection, PhysicalSide, WritingMode};
use style::properties::ComputedValues;
use style::properties::longhands::backface_visibility::computed_value::T as BackfaceVisiblity;
use style::properties::longhands::box_sizing::computed_value::T as BoxSizing;
@ -280,6 +280,16 @@ impl Default for BorderStyleColor {
}
}
/// <https://drafts.csswg.org/cssom-view/#overflow-directions>
/// > A scrolling box of a viewport or element has two overflow directions,
/// > which are the block-end and inline-end directions for that viewport or element.
pub(crate) struct OverflowDirection {
/// Whether block-end or inline-end direction is [PhysicalSide::Right].
pub rightward: bool,
/// Whether block-end or inline-end direction is [PhysicalSide::Bottom].
pub downward: bool,
}
pub(crate) trait ComputedValuesExt {
fn physical_box_offsets(&self) -> PhysicalSides<LengthPercentageOrAuto<'_>>;
fn box_offsets(&self, writing_mode: WritingMode) -> LogicalSides<LengthPercentageOrAuto<'_>>;
@ -320,7 +330,8 @@ pub(crate) trait ComputedValuesExt {
containing_block_writing_mode: WritingMode,
) -> LogicalSides<LengthPercentageOrAuto<'_>>;
fn is_transformable(&self, fragment_flags: FragmentFlags) -> bool;
fn has_transform_or_perspective(&self, fragment_flags: FragmentFlags) -> bool;
fn has_transform_or_perspective_style(&self) -> bool;
fn has_effective_transform_or_perspective(&self, fragment_flags: FragmentFlags) -> bool;
fn z_index_applies(&self, fragment_flags: FragmentFlags) -> bool;
fn effective_z_index(&self, fragment_flags: FragmentFlags) -> i32;
fn effective_overflow(&self, fragment_flags: FragmentFlags) -> AxesOverflow;
@ -353,6 +364,7 @@ pub(crate) trait ComputedValuesExt {
writing_mode: WritingMode,
) -> bool;
fn is_inline_box(&self, fragment_flags: FragmentFlags) -> bool;
fn overflow_direction(&self) -> OverflowDirection;
}
impl ComputedValuesExt for ComputedValues {
@ -363,6 +375,9 @@ impl ComputedValuesExt for ComputedValues {
Inset::Auto => LengthPercentageOrAuto::Auto,
Inset::AnchorFunction(_) => unreachable!("anchor() should be disabled"),
Inset::AnchorSizeFunction(_) => unreachable!("anchor-size() should be disabled"),
Inset::AnchorContainingCalcFunction(_) => {
unreachable!("anchor() and anchor-size() should be disabled")
},
}
}
let position = self.get_position();
@ -483,7 +498,9 @@ impl ComputedValuesExt for ComputedValues {
match inset {
Margin::LengthPercentage(v) => LengthPercentageOrAuto::LengthPercentage(v),
Margin::Auto => LengthPercentageOrAuto::Auto,
Margin::AnchorSizeFunction(_) => unreachable!("anchor-size() should be disabled"),
Margin::AnchorSizeFunction(_) | Margin::AnchorContainingCalcFunction(_) => {
unreachable!("anchor-size() should be disabled")
},
}
}
let margin = self.get_margin();
@ -522,15 +539,20 @@ impl ComputedValuesExt for ComputedValues {
!self.is_inline_box(fragment_flags)
}
/// Returns true if this style has a transform, or perspective property set and
/// Returns true if this style has a transform or perspective property set.
fn has_transform_or_perspective_style(&self) -> bool {
!self.get_box().transform.0.is_empty() ||
self.get_box().scale != GenericScale::None ||
self.get_box().rotate != GenericRotate::None ||
self.get_box().translate != GenericTranslate::None ||
self.get_box().perspective != Perspective::None
}
/// Returns true if this style has a transform or perspective property set, and
/// it applies to this element.
fn has_transform_or_perspective(&self, fragment_flags: FragmentFlags) -> bool {
self.is_transformable(fragment_flags) &&
(!self.get_box().transform.0.is_empty() ||
self.get_box().scale != GenericScale::None ||
self.get_box().rotate != GenericRotate::None ||
self.get_box().translate != GenericTranslate::None ||
self.get_box().perspective != Perspective::None)
#[inline]
fn has_effective_transform_or_perspective(&self, fragment_flags: FragmentFlags) -> bool {
self.is_transformable(fragment_flags) && self.has_transform_or_perspective_style()
}
/// Whether the `z-index` property applies to this fragment.
@ -695,17 +717,20 @@ impl ComputedValuesExt for ComputedValues {
// From <https://www.w3.org/TR/css-transforms-1/#transform-rendering>
// > For elements whose layout is governed by the CSS box model, any value other than
// > `none` for the `transform` property results in the creation of a stacking context.
//
// From <https://www.w3.org/TR/css-transforms-2/#individual-transforms>
// > all other values […] create a stacking context and containing block for all
// > descendants, per usual for transforms.
//
// From <https://www.w3.org/TR/css-transforms-2/#perspective-property>
// > any value other than none establishes a stacking context.
//
// From <https://www.w3.org/TR/css-transforms-2/#transform-style-property>
// > A computed value of `preserve-3d` for `transform-style` on a transformable element
// > establishes both a stacking context and a containing block for all descendants.
// From <https://www.w3.org/TR/css-transforms-2/#perspective-property>
// > any value other than none establishes a stacking context.
// TODO: handle individual transform properties (`translate`, `scale` and `rotate`).
// <https://www.w3.org/TR/css-transforms-2/#individual-transforms>
if self.is_transformable(fragment_flags) &&
(!self.get_box().transform.0.is_empty() ||
(self.has_transform_or_perspective_style() ||
self.get_box().transform_style == ComputedTransformStyle::Preserve3d ||
self.get_box().perspective != Perspective::None ||
will_change_bits
.intersects(WillChangeBits::TRANSFORM | WillChangeBits::PERSPECTIVE))
{
@ -795,29 +820,43 @@ impl ComputedValuesExt for ComputedValues {
&self,
fragment_flags: FragmentFlags,
) -> bool {
if self.has_transform_or_perspective(fragment_flags) {
return true;
}
if !self.get_effects().filter.0.is_empty() {
return true;
}
// See <https://drafts.csswg.org/css-transforms-2/#transform-style-property>.
if self.is_transformable(fragment_flags) &&
self.get_box().transform_style == ComputedTransformStyle::Preserve3d
{
return true;
}
// From <https://www.w3.org/TR/css-will-change/#valdef-will-change-custom-ident>:
// > If any non-initial value of a property would cause the element to generate a
// > containing block for fixed positioned elements, specifying that property in will-change
// > must cause the element to generate a containing block for fixed positioned elements.
let will_change_bits = self.clone_will_change().bits;
if will_change_bits.intersects(WillChangeBits::FIXPOS_CB_NON_SVG) ||
(will_change_bits
.intersects(WillChangeBits::TRANSFORM | WillChangeBits::PERSPECTIVE) &&
self.is_transformable(fragment_flags))
// From <https://drafts.csswg.org/css-transforms-1/#transform-rendering>:
// > any value other than `none` for the `transform` property also causes the element
// > to establish a containing block for all descendants.
//
// From <https://www.w3.org/TR/css-transforms-2/#individual-transforms>
// > all other values […] create a stacking context and containing block for all
// > descendants, per usual for transforms.
//
// From <https://drafts.csswg.org/css-transforms-2/#perspective-property>:
// > The use of this property with any value other than `none` […] establishes a
// > containing block for all descendants, just like the `transform` property does.
//
// From <https://drafts.csswg.org/css-transforms-2/#transform-style-property>:
// > A computed value of `preserve-3d` for `transform-style` on a transformable element
// > establishes both a stacking context and a containing block for all descendants.
if self.is_transformable(fragment_flags) &&
(self.has_transform_or_perspective_style() ||
self.get_box().transform_style == ComputedTransformStyle::Preserve3d ||
will_change_bits
.intersects(WillChangeBits::TRANSFORM | WillChangeBits::PERSPECTIVE))
{
return true;
}
// From <https://www.w3.org/TR/filter-effects-1/#propdef-filter>:
// > A value other than none for the filter property results in the creation of a containing
// > block for absolute and fixed positioned descendants unless the element it applies to is
// > a document root element in the current browsing context.
if !fragment_flags.contains(FragmentFlags::IS_ROOT_ELEMENT) &&
(!self.get_effects().filter.0.is_empty() ||
will_change_bits.intersects(WillChangeBits::FIXPOS_CB_NON_SVG))
{
return true;
}
@ -961,6 +1000,23 @@ impl ComputedValuesExt for ComputedValues {
};
has_percentage(box_offsets.block_start) || has_percentage(box_offsets.block_end)
}
// <https://drafts.csswg.org/cssom-view/#overflow-directions>
fn overflow_direction(&self) -> OverflowDirection {
let inline_end_direction = self.writing_mode.inline_end_physical_side();
let block_end_direction = self.writing_mode.block_end_physical_side();
let rightward = inline_end_direction == PhysicalSide::Right ||
block_end_direction == PhysicalSide::Right;
let downward = inline_end_direction == PhysicalSide::Bottom ||
block_end_direction == PhysicalSide::Bottom;
// TODO(stevennovaryo): We should consider the flex-container's CSS (e.g. flow-direction: column-reverse).
OverflowDirection {
rightward,
downward,
}
}
}
pub(crate) enum LayoutStyle<'a> {

View file

@ -87,6 +87,15 @@ input[type="file"] {
border-style: none;
}
input[type="color"] {
padding: 6px;
width: 64px;
height: 32px;
border-radius: 2px;
background: lightgrey;
border: 1px solid gray;
}
td[align="left"] { text-align: left; }
td[align="center"] { text-align: center; }
td[align="right"] { text-align: right; }
@ -141,64 +150,12 @@ svg > * {
display: none;
}
/*
* For most (but not all) anon-boxes, we inherit all values from the
* parent.
*
* Anonymous table flows shouldn't inherit their parents properties in order
* to avoid doubling up styles such as transformations. Same for text and such.
*
* For tables, we do want style to inherit, because TableWrapper is
* responsible for handling clipping and scrolling, while Table is
* responsible for creating stacking contexts.
*
* StackingContextCollectionFlags makes sure this is processed
* properly.
*
* FIXME(emilio): inheriting all is a very strong hammer, and it's likely
* broken for stuff like table backgrounds and such. Gecko explicitly inherits
* what it wants, which seems a bit better off-hand.
*/
*|*::-servo-legacy-anonymous-table,
*|*::-servo-legacy-anonymous-table-wrapper,
*|*::-servo-legacy-table-wrapper,
*|*::-servo-legacy-anonymous-block,
*|*::-servo-legacy-inline-block-wrapper,
*|*::-servo-legacy-inline-absolute {
all: inherit;
}
*|*::-servo-anonymous-box {
unicode-bidi: inherit;
direction: inherit;
writing-mode: inherit;
}
*|*::-servo-legacy-table-wrapper {
display: table;
border: none;
}
*|*::-servo-legacy-anonymous-table-wrapper {
position: static;
margin: 0;
counter-increment: none;
/* We don't want anonymous table parts to inherit hidden overflow, because
* they will create extra unnecessary ClipScrollNodes which also throws
* off assignment of contained flows. */
overflow: visible;
}
*|*::-servo-legacy-anonymous-table {
display: table;
position: static;
border: none;
padding: 0;
counter-increment: none;
overflow: visible;
}
*|*::-servo-anonymous-table {
display: table;
}
@ -249,32 +206,6 @@ svg > * {
display: table;
}
*|*::-servo-legacy-anonymous-block {
display: block;
position: static;
border: none;
padding: 0;
margin: 0;
width: auto;
height: auto;
}
/* The outer fragment wrapper of an inline-block. */
*|*::-servo-legacy-inline-block-wrapper {
position: static;
border: none;
padding: 0;
margin: 0;
}
/* The outer fragment wrapper of an inline absolute hypothetical fragment. */
*|*::-servo-legacy-inline-absolute {
clip: auto;
border: none;
padding: 0;
margin: 0;
}
meter {
display: inline-block;
width: 100px;

View file

@ -8,7 +8,7 @@ use std::iter::repeat;
use atomic_refcell::AtomicRef;
use log::warn;
use script_layout_interface::wrapper_traits::ThreadSafeLayoutNode;
use script_layout_interface::wrapper_traits::{LayoutNode, ThreadSafeLayoutNode};
use servo_arc::Arc;
use style::properties::ComputedValues;
use style::properties::style_structs::Font;
@ -19,10 +19,9 @@ use super::{
Table, TableCaption, TableLevelBox, TableSlot, TableSlotCell, TableSlotCoordinates,
TableSlotOffset, TableTrack, TableTrackGroup, TableTrackGroupType,
};
use crate::PropagatedBoxTreeData;
use crate::cell::ArcRefCell;
use crate::context::LayoutContext;
use crate::dom::{BoxSlot, LayoutBox, NodeExt};
use crate::dom::{BoxSlot, LayoutBox};
use crate::dom_traversal::{Contents, NodeAndStyleInfo, NonReplacedContents, TraversalHandler};
use crate::flow::{BlockContainerBuilder, BlockFormattingContext};
use crate::formatting_contexts::{
@ -32,6 +31,7 @@ use crate::formatting_contexts::{
use crate::fragment_tree::BaseFragmentInfo;
use crate::layout_box_base::LayoutBoxBase;
use crate::style_ext::{DisplayGeneratingBox, DisplayLayoutInternal};
use crate::{PropagatedBoxTreeData, SharedStyle};
/// A reference to a slot and its coordinates in the table
#[derive(Debug)]
@ -50,17 +50,17 @@ impl ResolvedSlotAndLocation<'_> {
}
}
pub(crate) enum AnonymousTableContent<'dom, Node> {
Text(NodeAndStyleInfo<Node>, Cow<'dom, str>),
pub(crate) enum AnonymousTableContent<'dom> {
Text(NodeAndStyleInfo<'dom>, Cow<'dom, str>),
Element {
info: NodeAndStyleInfo<Node>,
info: NodeAndStyleInfo<'dom>,
display: DisplayGeneratingBox,
contents: Contents,
box_slot: BoxSlot<'dom>,
},
}
impl<Node> AnonymousTableContent<'_, Node> {
impl AnonymousTableContent<'_> {
fn is_whitespace_only(&self) -> bool {
match self {
Self::Element { .. } => false,
@ -74,32 +74,24 @@ impl<Node> AnonymousTableContent<'_, Node> {
}
impl Table {
pub(crate) fn construct<'dom>(
pub(crate) fn construct(
context: &LayoutContext,
info: &NodeAndStyleInfo<impl NodeExt<'dom>>,
info: &NodeAndStyleInfo,
grid_style: Arc<ComputedValues>,
contents: NonReplacedContents,
propagated_data: PropagatedBoxTreeData,
) -> Self {
let mut traversal = TableBuilderTraversal::new(
context,
info,
grid_style,
propagated_data.union(&info.style),
);
let mut traversal = TableBuilderTraversal::new(context, info, grid_style, propagated_data);
contents.traverse(context, info, &mut traversal);
traversal.finish()
}
pub(crate) fn construct_anonymous<'dom, Node>(
pub(crate) fn construct_anonymous<'dom>(
context: &LayoutContext,
parent_info: &NodeAndStyleInfo<Node>,
contents: Vec<AnonymousTableContent<'dom, Node>>,
parent_info: &NodeAndStyleInfo<'dom>,
contents: Vec<AnonymousTableContent<'dom>>,
propagated_data: PropagatedBoxTreeData,
) -> (NodeAndStyleInfo<Node>, IndependentFormattingContext)
where
Node: crate::dom::NodeExt<'dom>,
{
) -> (NodeAndStyleInfo<'dom>, IndependentFormattingContext) {
let table_info = parent_info
.pseudo(context, PseudoElement::ServoAnonymousTable)
.expect("Should never fail to create anonymous table info.");
@ -645,9 +637,9 @@ impl TableBuilder {
}
}
pub(crate) struct TableBuilderTraversal<'style, 'dom, Node> {
pub(crate) struct TableBuilderTraversal<'style, 'dom> {
context: &'style LayoutContext<'style>,
info: &'style NodeAndStyleInfo<Node>,
info: &'style NodeAndStyleInfo<'dom>,
/// The value of the [`PropagatedBoxTreeData`] to use, either for the row group
/// if processing one or for the table itself if outside a row group.
@ -657,19 +649,16 @@ pub(crate) struct TableBuilderTraversal<'style, 'dom, Node> {
/// into another struct so that we can write unit tests against the builder.
builder: TableBuilder,
current_anonymous_row_content: Vec<AnonymousTableContent<'dom, Node>>,
current_anonymous_row_content: Vec<AnonymousTableContent<'dom>>,
/// The index of the current row group, if there is one.
current_row_group_index: Option<usize>,
}
impl<'style, 'dom, Node> TableBuilderTraversal<'style, 'dom, Node>
where
Node: NodeExt<'dom>,
{
impl<'style, 'dom> TableBuilderTraversal<'style, 'dom> {
pub(crate) fn new(
context: &'style LayoutContext<'style>,
info: &'style NodeAndStyleInfo<Node>,
info: &'style NodeAndStyleInfo<'dom>,
grid_style: Arc<ComputedValues>,
propagated_data: PropagatedBoxTreeData,
) -> Self {
@ -728,9 +717,10 @@ where
let style = anonymous_info.style.clone();
self.push_table_row(ArcRefCell::new(TableTrack {
base: LayoutBoxBase::new((&anonymous_info).into(), style),
base: LayoutBoxBase::new((&anonymous_info).into(), style.clone()),
group_index: self.current_row_group_index,
is_anonymous: true,
shared_background_style: SharedStyle::new(style),
}));
}
@ -745,11 +735,8 @@ where
}
}
impl<'dom, Node: 'dom> TraversalHandler<'dom, Node> for TableBuilderTraversal<'_, 'dom, Node>
where
Node: NodeExt<'dom>,
{
fn handle_text(&mut self, info: &NodeAndStyleInfo<Node>, text: Cow<'dom, str>) {
impl<'dom> TraversalHandler<'dom> for TableBuilderTraversal<'_, 'dom> {
fn handle_text(&mut self, info: &NodeAndStyleInfo<'dom>, text: Cow<'dom, str>) {
self.current_anonymous_row_content
.push(AnonymousTableContent::Text(info.clone(), text));
}
@ -757,7 +744,7 @@ where
/// <https://html.spec.whatwg.org/multipage/#forming-a-table>
fn handle_element(
&mut self,
info: &NodeAndStyleInfo<Node>,
info: &NodeAndStyleInfo<'dom>,
display: DisplayGeneratingBox,
contents: Contents,
box_slot: BoxSlot<'dom>,
@ -775,12 +762,10 @@ where
base: LayoutBoxBase::new(info.into(), info.style.clone()),
group_type: internal.into(),
track_range: next_row_index..next_row_index,
shared_background_style: SharedStyle::new(info.style.clone()),
});
self.builder.table.row_groups.push(row_group.clone());
let previous_propagated_data = self.current_propagated_data;
self.current_propagated_data = self.current_propagated_data.union(&info.style);
let new_row_group_index = self.builder.table.row_groups.len() - 1;
self.current_row_group_index = Some(new_row_group_index);
@ -792,7 +777,6 @@ where
self.finish_anonymous_row_if_needed();
self.current_row_group_index = None;
self.current_propagated_data = previous_propagated_data;
self.builder.incoming_rowspans.clear();
box_slot.set(LayoutBox::TableLevelBox(TableLevelBox::TrackGroup(
@ -817,6 +801,7 @@ where
base: LayoutBoxBase::new(info.into(), info.style.clone()),
group_index: self.current_row_group_index,
is_anonymous: false,
shared_background_style: SharedStyle::new(info.style.clone()),
});
self.push_table_row(row.clone());
box_slot.set(LayoutBox::TableLevelBox(TableLevelBox::Track(row)));
@ -862,6 +847,7 @@ where
base: LayoutBoxBase::new(info.into(), info.style.clone()),
group_type: internal.into(),
track_range: first_column..self.builder.table.columns.len(),
shared_background_style: SharedStyle::new(info.style.clone()),
});
self.builder.table.column_groups.push(column_group.clone());
box_slot.set(LayoutBox::TableLevelBox(TableLevelBox::TrackGroup(
@ -916,26 +902,23 @@ where
}
}
struct TableRowBuilder<'style, 'builder, 'dom, 'a, Node> {
table_traversal: &'builder mut TableBuilderTraversal<'style, 'dom, Node>,
struct TableRowBuilder<'style, 'builder, 'dom, 'a> {
table_traversal: &'builder mut TableBuilderTraversal<'style, 'dom>,
/// The [`NodeAndStyleInfo`] of this table row, which we use to
/// construct anonymous table cells.
info: &'a NodeAndStyleInfo<Node>,
info: &'a NodeAndStyleInfo<'dom>,
current_anonymous_cell_content: Vec<AnonymousTableContent<'dom, Node>>,
current_anonymous_cell_content: Vec<AnonymousTableContent<'dom>>,
/// The [`PropagatedBoxTreeData`] to use for all children of this row.
propagated_data: PropagatedBoxTreeData,
}
impl<'style, 'builder, 'dom, 'a, Node: 'dom> TableRowBuilder<'style, 'builder, 'dom, 'a, Node>
where
Node: NodeExt<'dom>,
{
impl<'style, 'builder, 'dom, 'a> TableRowBuilder<'style, 'builder, 'dom, 'a> {
fn new(
table_traversal: &'builder mut TableBuilderTraversal<'style, 'dom, Node>,
info: &'a NodeAndStyleInfo<Node>,
table_traversal: &'builder mut TableBuilderTraversal<'style, 'dom>,
info: &'a NodeAndStyleInfo<'dom>,
propagated_data: PropagatedBoxTreeData,
) -> Self {
table_traversal.builder.start_row();
@ -944,7 +927,7 @@ where
table_traversal,
info,
current_anonymous_cell_content: Vec::new(),
propagated_data: propagated_data.union(&info.style),
propagated_data,
}
}
@ -996,11 +979,8 @@ where
}
}
impl<'dom, Node: 'dom> TraversalHandler<'dom, Node> for TableRowBuilder<'_, '_, 'dom, '_, Node>
where
Node: NodeExt<'dom>,
{
fn handle_text(&mut self, info: &NodeAndStyleInfo<Node>, text: Cow<'dom, str>) {
impl<'dom> TraversalHandler<'dom> for TableRowBuilder<'_, '_, 'dom, '_> {
fn handle_text(&mut self, info: &NodeAndStyleInfo<'dom>, text: Cow<'dom, str>) {
self.current_anonymous_cell_content
.push(AnonymousTableContent::Text(info.clone(), text));
}
@ -1008,7 +988,7 @@ where
/// <https://html.spec.whatwg.org/multipage/#algorithm-for-processing-rows>
fn handle_element(
&mut self,
info: &NodeAndStyleInfo<Node>,
info: &NodeAndStyleInfo<'dom>,
display: DisplayGeneratingBox,
contents: Contents,
box_slot: BoxSlot<'dom>,
@ -1019,15 +999,16 @@ where
DisplayLayoutInternal::TableCell => {
// This value will already have filtered out rowspan=0
// in quirks mode, so we don't have to worry about that.
//
// The HTML specification limits the parsed value of `rowspan` to
// 65534 and `colspan` to 1000, so we also enforce the same limits
// when dealing with arbitrary DOM elements (perhaps created via
// script).
let (rowspan, colspan) = if info.pseudo_element_type.is_none() {
let node = info.node.to_threadsafe();
let rowspan = node.get_rowspan().unwrap_or(1).min(65534) as usize;
let colspan = node.get_colspan().unwrap_or(1).min(1000) as usize;
let rowspan = node.get_rowspan().unwrap_or(1) as usize;
let colspan = node.get_colspan().unwrap_or(1) as usize;
// The HTML specification clamps value of `rowspan` to [0, 65534] and
// `colspan` to [1, 1000].
assert!((1..=1000).contains(&colspan));
assert!((0..=65534).contains(&rowspan));
(rowspan, colspan)
} else {
(1, 1)
@ -1090,14 +1071,11 @@ struct TableColumnGroupBuilder {
columns: Vec<ArcRefCell<TableTrack>>,
}
impl<'dom, Node: 'dom> TraversalHandler<'dom, Node> for TableColumnGroupBuilder
where
Node: NodeExt<'dom>,
{
fn handle_text(&mut self, _info: &NodeAndStyleInfo<Node>, _text: Cow<'dom, str>) {}
impl<'dom> TraversalHandler<'dom> for TableColumnGroupBuilder {
fn handle_text(&mut self, _info: &NodeAndStyleInfo<'dom>, _text: Cow<'dom, str>) {}
fn handle_element(
&mut self,
info: &NodeAndStyleInfo<Node>,
info: &NodeAndStyleInfo<'dom>,
display: DisplayGeneratingBox,
_contents: Contents,
box_slot: BoxSlot<'dom>,
@ -1133,28 +1111,27 @@ impl From<DisplayLayoutInternal> for TableTrackGroupType {
}
}
fn add_column<'dom, Node: NodeExt<'dom>>(
fn add_column(
collection: &mut Vec<ArcRefCell<TableTrack>>,
column_info: &NodeAndStyleInfo<Node>,
column_info: &NodeAndStyleInfo,
group_index: Option<usize>,
is_anonymous: bool,
) -> ArcRefCell<TableTrack> {
let span = if column_info.pseudo_element_type.is_none() {
column_info
.node
.to_threadsafe()
.get_span()
.unwrap_or(1)
.min(1000) as usize
column_info.node.to_threadsafe().get_span().unwrap_or(1)
} else {
1
};
// The HTML specification clamps value of `span` for `<col>` to [1, 1000].
assert!((1..=1000).contains(&span));
let column = ArcRefCell::new(TableTrack {
base: LayoutBoxBase::new(column_info.into(), column_info.style.clone()),
group_index,
is_anonymous,
shared_background_style: SharedStyle::new(column_info.style.clone()),
});
collection.extend(repeat(column.clone()).take(span));
collection.extend(repeat(column.clone()).take(span as usize));
column
}

View file

@ -1068,7 +1068,6 @@ impl<'a> TableLayout<'a> {
&mut self,
layout_context: &LayoutContext,
containing_block_for_table: &ContainingBlock,
parent_positioning_context: &mut PositioningContext,
) {
self.cells_laid_out = self
.table
@ -1076,30 +1075,6 @@ impl<'a> TableLayout<'a> {
.par_iter()
.enumerate()
.map(|(row_index, row_slots)| {
// When building the PositioningContext for this cell, we want it to have the same
// configuration for whatever PositioningContext the contents are ultimately added to.
let collect_for_nearest_positioned_ancestor = parent_positioning_context
.collects_for_nearest_positioned_ancestor() ||
self.table.rows.get(row_index).is_some_and(|row| {
let row = row.borrow();
let row_group_collects_for_nearest_positioned_ancestor =
row.group_index.is_some_and(|group_index| {
self.table.row_groups[group_index]
.borrow()
.base
.style
.establishes_containing_block_for_absolute_descendants(
FragmentFlags::empty(),
)
});
row_group_collects_for_nearest_positioned_ancestor ||
row.base
.style
.establishes_containing_block_for_absolute_descendants(
FragmentFlags::empty(),
)
});
row_slots
.par_iter()
.enumerate()
@ -1141,10 +1116,7 @@ impl<'a> TableLayout<'a> {
style: &cell.base.style,
};
let mut positioning_context = PositioningContext::new_for_subtree(
collect_for_nearest_positioned_ancestor,
);
let mut positioning_context = PositioningContext::default();
let layout = cell.contents.layout(
layout_context,
&mut positioning_context,
@ -1503,7 +1475,6 @@ impl<'a> TableLayout<'a> {
layout_context: &LayoutContext,
parent_positioning_context: &mut PositioningContext,
) -> BoxFragment {
let mut positioning_context = PositioningContext::new_for_style(caption.context.style());
let containing_block = &ContainingBlock {
size: ContainingBlockSize {
inline: self.table_width + self.pbm.padding_border_sums.inline,
@ -1517,6 +1488,8 @@ impl<'a> TableLayout<'a> {
// stretch block size. https://drafts.csswg.org/css-sizing-4/#stretch-fit-sizing
let ignore_block_margins_for_stretch = LogicalSides1D::new(false, false);
let mut positioning_context =
PositioningContext::new_for_layout_box_base(&caption.context.base);
let mut box_fragment = caption.context.layout_in_flow_block_level(
layout_context,
positioning_context
@ -1769,11 +1742,7 @@ impl<'a> TableLayout<'a> {
) -> BoxFragment {
self.distributed_column_widths =
Self::distribute_width_to_columns(self.assignable_width, &self.columns);
self.layout_cells_in_row(
layout_context,
containing_block_for_children,
positioning_context,
);
self.layout_cells_in_row(layout_context, containing_block_for_children);
let table_writing_mode = containing_block_for_children.style.writing_mode;
let first_layout_row_heights = self.do_first_row_layout(table_writing_mode);
self.compute_table_height_and_final_row_heights(
@ -2094,7 +2063,7 @@ impl<'a> TableLayout<'a> {
let column_group = column_group.borrow();
let rect = make_relative_to_row_start(dimensions.get_column_group_rect(&column_group));
fragment.add_extra_background(ExtraBackground {
style: column_group.base.style.clone(),
style: column_group.shared_background_style.clone(),
rect,
})
}
@ -2103,7 +2072,7 @@ impl<'a> TableLayout<'a> {
if !column.is_anonymous {
let rect = make_relative_to_row_start(dimensions.get_column_rect(column_index));
fragment.add_extra_background(ExtraBackground {
style: column.base.style.clone(),
style: column.shared_background_style.clone(),
rect,
})
}
@ -2116,7 +2085,7 @@ impl<'a> TableLayout<'a> {
let rect =
make_relative_to_row_start(dimensions.get_row_group_rect(&row_group.borrow()));
fragment.add_extra_background(ExtraBackground {
style: row_group.borrow().base.style.clone(),
style: row_group.borrow().shared_background_style.clone(),
rect,
})
}
@ -2124,7 +2093,7 @@ impl<'a> TableLayout<'a> {
let row = row.borrow();
let rect = make_relative_to_row_start(row_fragment_layout.rect);
fragment.add_extra_background(ExtraBackground {
style: row.base.style.clone(),
style: row.shared_background_style.clone(),
rect,
})
}
@ -2142,23 +2111,27 @@ impl<'a> TableLayout<'a> {
for column_group in self.table.column_groups.iter() {
let column_group = column_group.borrow();
if !column_group.is_empty() {
fragments.push(Fragment::Positioning(PositioningFragment::new_empty(
let fragment = Fragment::Positioning(PositioningFragment::new_empty(
column_group.base.base_fragment_info,
dimensions
.get_column_group_rect(&column_group)
.as_physical(None),
column_group.base.style.clone(),
)));
));
column_group.base.set_fragment(fragment.clone());
fragments.push(fragment);
}
}
for (column_index, column) in self.table.columns.iter().enumerate() {
let column = column.borrow();
fragments.push(Fragment::Positioning(PositioningFragment::new_empty(
let fragment = Fragment::Positioning(PositioningFragment::new_empty(
column.base.base_fragment_info,
dimensions.get_column_rect(column_index).as_physical(None),
column.base.style.clone(),
)));
));
column.base.set_fragment(fragment.clone());
fragments.push(fragment);
}
}
@ -2321,7 +2294,7 @@ impl<'a> RowFragmentLayout<'a> {
Self {
row: table_row,
rect,
positioning_context: PositioningContext::new_for_style(&table_row.base.style),
positioning_context: PositioningContext::new_for_layout_box_base(&table_row.base),
containing_block,
fragments: Vec::new(),
}
@ -2375,11 +2348,11 @@ impl<'a> RowFragmentLayout<'a> {
if let Some(mut row_positioning_context) = self.positioning_context.take() {
row_positioning_context.layout_collected_children(layout_context, &mut row_fragment);
let positioning_context = row_group_fragment_layout
let parent_positioning_context = row_group_fragment_layout
.as_mut()
.and_then(|layout| layout.positioning_context.as_mut())
.unwrap_or(table_positioning_context);
positioning_context.append(row_positioning_context);
parent_positioning_context.append(row_positioning_context);
}
let fragment = Fragment::Box(ArcRefCell::new(row_fragment));
@ -2406,7 +2379,7 @@ impl RowGroupFragmentLayout {
let row_group = row_group.borrow();
(
dimensions.get_row_group_rect(&row_group),
PositioningContext::new_for_style(&row_group.base.style),
PositioningContext::new_for_layout_box_base(&row_group.base),
)
};
Self {
@ -2894,6 +2867,7 @@ impl TableSlotCell {
block: vertical_align_offset,
};
let vertical_align_fragment = PositioningFragment::new_anonymous(
self.base.style.clone(),
vertical_align_fragment_rect.as_physical(None),
layout.layout.fragments,
);

View file

@ -76,12 +76,16 @@ pub(crate) use construct::AnonymousTableContent;
pub use construct::TableBuilder;
use euclid::{Point2D, Size2D, UnknownUnit, Vector2D};
use malloc_size_of_derive::MallocSizeOf;
use script::layout_dom::{ServoLayoutElement, ServoLayoutNode};
use servo_arc::Arc;
use style::context::SharedStyleContext;
use style::properties::ComputedValues;
use style::properties::style_structs::Font;
use style::selector_parser::PseudoElement;
use style_traits::dom::OpaqueNode;
use super::flow::BlockFormattingContext;
use crate::SharedStyle;
use crate::cell::ArcRefCell;
use crate::flow::BlockContainer;
use crate::formatting_contexts::IndependentFormattingContext;
@ -98,12 +102,10 @@ pub struct Table {
/// The style of this table. These are the properties that apply to the "wrapper" ie the element
/// that contains both the grid and the captions. Not all properties are actually used on the
/// wrapper though, such as background and borders, which apply to the grid.
#[conditional_malloc_size_of]
style: Arc<ComputedValues>,
/// The style of this table's grid. This is an anonymous style based on the table's style, but
/// eliminating all the properties handled by the "wrapper."
#[conditional_malloc_size_of]
grid_style: Arc<ComputedValues>,
/// The [`BaseFragmentInfo`] for this table's grid. This is necessary so that when the
@ -192,6 +194,19 @@ impl Table {
),
}
}
pub(crate) fn repair_style(
&mut self,
context: &SharedStyleContext,
new_style: &Arc<ComputedValues>,
) {
self.style = new_style.clone();
self.grid_style = context.stylist.style_for_anonymous::<ServoLayoutElement>(
&context.guards,
&PseudoElement::ServoTableGrid,
new_style,
);
}
}
type TableSlotCoordinates = Point2D<usize, UnknownUnit>;
@ -233,6 +248,10 @@ impl TableSlotCell {
pub fn node_id(&self) -> usize {
self.base.base_fragment_info.tag.map_or(0, |tag| tag.node.0)
}
fn repair_style(&mut self, new_style: &Arc<ComputedValues>) {
self.base.repair_style(new_style);
}
}
/// A single table slot. It may be an actual cell, or a reference
@ -288,6 +307,18 @@ pub struct TableTrack {
/// Whether or not this [`TableTrack`] was anonymous, for instance created due to
/// a `span` attribute set on a parent `<colgroup>`.
is_anonymous: bool,
/// A shared container for this track's style, used to share the style for the purposes
/// of drawing backgrounds in individual cells. This allows updating the style in a
/// single place and having it affect all cell `Fragment`s.
shared_background_style: SharedStyle,
}
impl TableTrack {
fn repair_style(&mut self, new_style: &Arc<ComputedValues>) {
self.base.repair_style(new_style);
self.shared_background_style = SharedStyle::new(new_style.clone());
}
}
#[derive(Debug, MallocSizeOf, PartialEq)]
@ -308,12 +339,22 @@ pub struct TableTrackGroup {
/// The range of tracks in this [`TableTrackGroup`].
track_range: Range<usize>,
/// A shared container for this track's style, used to share the style for the purposes
/// of drawing backgrounds in individual cells. This allows updating the style in a
/// single place and having it affect all cell `Fragment`s.
shared_background_style: SharedStyle,
}
impl TableTrackGroup {
pub(super) fn is_empty(&self) -> bool {
self.track_range.is_empty()
}
fn repair_style(&mut self, new_style: &Arc<ComputedValues>) {
self.base.repair_style(new_style);
self.shared_background_style = SharedStyle::new(new_style.clone());
}
}
#[derive(Debug, MallocSizeOf)]
@ -346,6 +387,7 @@ pub(crate) struct TableLayoutStyle<'a> {
/// Table parts that are stored in the DOM. This is used in order to map from
/// the DOM to the box tree and will eventually be important for incremental
/// layout.
#[derive(MallocSizeOf)]
pub(crate) enum TableLevelBox {
Caption(ArcRefCell<TableCaption>),
Cell(ArcRefCell<TableSlotCell>),
@ -379,4 +421,23 @@ impl TableLevelBox {
TableLevelBox::Track(track) => track.borrow().base.fragments(),
}
}
pub(crate) fn repair_style(
&self,
context: &SharedStyleContext<'_>,
node: &ServoLayoutNode,
new_style: &Arc<ComputedValues>,
) {
match self {
TableLevelBox::Caption(caption) => caption
.borrow_mut()
.context
.repair_style(context, node, new_style),
TableLevelBox::Cell(cell) => cell.borrow_mut().repair_style(new_style),
TableLevelBox::TrackGroup(track_group) => {
track_group.borrow_mut().repair_style(new_style);
},
TableLevelBox::Track(track) => track.borrow_mut().repair_style(new_style),
}
}
}

View file

@ -23,13 +23,13 @@ use crate::fragment_tree::{
BoxFragment, CollapsedBlockMargins, Fragment, FragmentFlags, SpecificLayoutInfo,
};
use crate::geom::{
LogicalVec2, PhysicalPoint, PhysicalRect, PhysicalSides, PhysicalSize, Size, SizeConstraint,
Sizes,
LazySize, LogicalVec2, PhysicalPoint, PhysicalRect, PhysicalSides, PhysicalSize, Size,
SizeConstraint, Sizes,
};
use crate::layout_box_base::CacheableLayoutResult;
use crate::positioned::{AbsolutelyPositionedBox, PositioningContext, PositioningContextLength};
use crate::sizing::{ComputeInlineContentSizes, ContentSizes, InlineContentSizesResult};
use crate::style_ext::{ComputedValuesExt, LayoutStyle};
use crate::style_ext::LayoutStyle;
use crate::{ConstraintSpace, ContainingBlock, ContainingBlockSize};
const DUMMY_NODE_ID: taffy::NodeId = taffy::NodeId::new(u64::MAX);
@ -250,34 +250,30 @@ impl taffy::LayoutPartialTree for TaffyContainerContext<'_> {
},
style,
};
let layout = {
let mut child_positioning_context =
PositioningContext::new_for_style(style).unwrap_or_else(|| {
PositioningContext::new_for_subtree(
self.positioning_context
.collects_for_nearest_positioned_ancestor(),
)
});
let layout = non_replaced.layout_without_caching(
self.layout_context,
&mut child_positioning_context,
&content_box_size_override,
containing_block,
false, /* depends_on_block_constraints */
);
// Store layout data on child for later access
child.positioning_context = child_positioning_context;
layout
let lazy_block_size = match content_box_known_dimensions.height {
// FIXME: use the correct min/max sizes.
None => LazySize::intrinsic(),
Some(height) => Au::from_f32_px(height).into(),
};
child.positioning_context = PositioningContext::default();
let layout = non_replaced.layout_without_caching(
self.layout_context,
&mut child.positioning_context,
&content_box_size_override,
containing_block,
false, /* depends_on_block_constraints */
&lazy_block_size,
);
child.child_fragments = layout.fragments;
self.child_specific_layout_infos[usize::from(node_id)] =
layout.specific_layout_info;
let block_size = layout.content_block_size.to_f32_px();
let block_size = lazy_block_size
.resolve(|| layout.content_block_size)
.to_f32_px();
let computed_size = taffy::Size {
width: inline_size + pb_sum.inline,
@ -372,8 +368,7 @@ impl ComputeInlineContentSizes for TaffyContainer {
let mut grid_context = TaffyContainerContext {
layout_context,
positioning_context:
&mut PositioningContext::new_for_containing_block_for_all_descendants(),
positioning_context: &mut PositioningContext::default(),
content_box_size_override: containing_block,
style,
source_child_nodes: &self.children,
@ -539,17 +534,6 @@ impl TaffyContainer {
let child_specific_layout_info: Option<SpecificLayoutInfo> =
std::mem::take(&mut container_ctx.child_specific_layout_infos[child_id]);
let establishes_containing_block_for_absolute_descendants =
if let TaffyItemBoxInner::InFlowBox(independent_box) = &child.taffy_level_box {
child
.style
.establishes_containing_block_for_absolute_descendants(
independent_box.base_fragment_info().flags,
)
} else {
false
};
let fragment = match &mut child.taffy_level_box {
TaffyItemBoxInner::InFlowBox(independent_box) => {
let mut fragment_info = independent_box.base_fragment_info();
@ -572,29 +556,21 @@ impl TaffyContainer {
})
.with_specific_layout_info(child_specific_layout_info);
if establishes_containing_block_for_absolute_descendants {
child.positioning_context.layout_collected_children(
container_ctx.layout_context,
&mut box_fragment,
);
}
let fragment = Fragment::Box(ArcRefCell::new(box_fragment));
child.positioning_context.layout_collected_children(
container_ctx.layout_context,
&mut box_fragment,
);
child
.positioning_context
.adjust_static_position_of_hoisted_fragments(
&fragment,
.adjust_static_position_of_hoisted_fragments_with_offset(
&box_fragment.content_rect.origin.to_vector(),
PositioningContextLength::zero(),
);
let child_positioning_context = std::mem::replace(
&mut child.positioning_context,
PositioningContext::new_for_containing_block_for_all_descendants(),
);
container_ctx
.positioning_context
.append(child_positioning_context);
fragment
.append(std::mem::take(&mut child.positioning_context));
Fragment::Box(ArcRefCell::new(box_fragment))
},
TaffyItemBoxInner::OutOfFlowAbsolutelyPositionedBox(abs_pos_box) => {
fn resolve_alignment(value: AlignFlags, auto: AlignFlags) -> AlignFlags {

View file

@ -7,7 +7,9 @@ use std::fmt;
use app_units::Au;
use malloc_size_of_derive::MallocSizeOf;
use script::layout_dom::ServoLayoutNode;
use servo_arc::Arc;
use style::context::SharedStyleContext;
use style::properties::ComputedValues;
use stylo_taffy::TaffyStyloStyle;
@ -15,7 +17,7 @@ use crate::PropagatedBoxTreeData;
use crate::cell::ArcRefCell;
use crate::construct_modern::{ModernContainerBuilder, ModernItemKind};
use crate::context::LayoutContext;
use crate::dom::{LayoutBox, NodeExt};
use crate::dom::LayoutBox;
use crate::dom_traversal::{NodeAndStyleInfo, NonReplacedContents};
use crate::formatting_contexts::IndependentFormattingContext;
use crate::fragment_tree::Fragment;
@ -24,19 +26,17 @@ use crate::positioned::{AbsolutelyPositionedBox, PositioningContext};
#[derive(Debug, MallocSizeOf)]
pub(crate) struct TaffyContainer {
children: Vec<ArcRefCell<TaffyItemBox>>,
#[conditional_malloc_size_of]
style: Arc<ComputedValues>,
}
impl TaffyContainer {
pub fn construct<'dom>(
pub fn construct(
context: &LayoutContext,
info: &NodeAndStyleInfo<impl NodeExt<'dom>>,
info: &NodeAndStyleInfo,
contents: NonReplacedContents,
propagated_data: PropagatedBoxTreeData,
) -> Self {
let mut builder =
ModernContainerBuilder::new(context, info, propagated_data.union(&info.style));
let mut builder = ModernContainerBuilder::new(context, info, propagated_data);
contents.traverse(context, info, &mut builder);
let items = builder.finish();
@ -69,6 +69,10 @@ impl TaffyContainer {
style: info.style.clone(),
}
}
pub(crate) fn repair_style(&mut self, new_style: &Arc<ComputedValues>) {
self.style = new_style.clone();
}
}
#[derive(MallocSizeOf)]
@ -76,7 +80,6 @@ pub(crate) struct TaffyItemBox {
pub(crate) taffy_layout: taffy::Layout,
pub(crate) child_fragments: Vec<Fragment>,
pub(crate) positioning_context: PositioningContext,
#[conditional_malloc_size_of]
pub(crate) style: Arc<ComputedValues>,
pub(crate) taffy_level_box: TaffyItemBoxInner,
}
@ -110,7 +113,7 @@ impl TaffyItemBox {
Self {
taffy_layout: Default::default(),
child_fragments: Vec::new(),
positioning_context: PositioningContext::new_for_containing_block_for_all_descendants(),
positioning_context: PositioningContext::default(),
style,
taffy_level_box: inner,
}
@ -118,8 +121,7 @@ impl TaffyItemBox {
pub(crate) fn invalidate_cached_fragment(&mut self) {
self.taffy_layout = Default::default();
self.positioning_context =
PositioningContext::new_for_containing_block_for_all_descendants();
self.positioning_context = PositioningContext::default();
match self.taffy_level_box {
TaffyItemBoxInner::InFlowBox(ref independent_formatting_context) => {
independent_formatting_context
@ -146,6 +148,24 @@ impl TaffyItemBox {
},
}
}
pub(crate) fn repair_style(
&mut self,
context: &SharedStyleContext,
node: &ServoLayoutNode,
new_style: &Arc<ComputedValues>,
) {
self.style = new_style.clone();
match &mut self.taffy_level_box {
TaffyItemBoxInner::InFlowBox(independent_formatting_context) => {
independent_formatting_context.repair_style(context, node, new_style)
},
TaffyItemBoxInner::OutOfFlowAbsolutelyPositionedBox(positioned_box) => positioned_box
.borrow_mut()
.context
.repair_style(context, node, new_style),
}
}
}
/// Details from Taffy grid layout that will be stored

View file

@ -59,6 +59,7 @@ pub fn dimension(val: &stylo::Size) -> taffy::Dimension {
// Anchor positioning will be flagged off for time being
stylo::Size::AnchorSizeFunction(_) => unreachable!(),
stylo::Size::AnchorContainingCalcFunction(_) => unreachable!(),
}
}
@ -77,6 +78,7 @@ pub fn max_size_dimension(val: &stylo::MaxSize) -> taffy::Dimension {
// Anchor positioning will be flagged off for time being
stylo::MaxSize::AnchorSizeFunction(_) => unreachable!(),
stylo::MaxSize::AnchorContainingCalcFunction(_) => unreachable!(),
}
}
@ -88,6 +90,7 @@ pub fn margin(val: &stylo::MarginVal) -> taffy::LengthPercentageAuto {
// Anchor positioning will be flagged off for time being
stylo::MarginVal::AnchorSizeFunction(_) => unreachable!(),
stylo::MarginVal::AnchorContainingCalcFunction(_) => unreachable!(),
}
}
@ -100,6 +103,7 @@ pub fn inset(val: &stylo::InsetVal) -> taffy::LengthPercentageAuto {
// Anchor positioning will be flagged off for time being
stylo::InsetVal::AnchorSizeFunction(_) => unreachable!(),
stylo::InsetVal::AnchorFunction(_) => unreachable!(),
stylo::InsetVal::AnchorContainingCalcFunction(_) => unreachable!(),
}
}

View file

@ -2,29 +2,29 @@
* 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 script::layout_dom::ServoLayoutNode;
use script_layout_interface::wrapper_traits::LayoutNode;
use style::context::{SharedStyleContext, StyleContext};
use style::data::ElementData;
use style::dom::{NodeInfo, TElement, TNode};
use style::selector_parser::RestyleDamage;
use style::traversal::{DomTraversal, PerLevelTraversalData, recalc_style_at};
use style::values::computed::Display;
use crate::context::LayoutContext;
use crate::dom::DOMLayoutData;
use crate::dom::{DOMLayoutData, NodeExt};
use crate::dom_traversal::iter_child_nodes;
pub struct RecalcStyle<'a> {
context: LayoutContext<'a>,
context: &'a LayoutContext<'a>,
}
impl<'a> RecalcStyle<'a> {
pub fn new(context: LayoutContext<'a>) -> Self {
pub fn new(context: &'a LayoutContext<'a>) -> Self {
RecalcStyle { context }
}
pub fn context(&self) -> &LayoutContext<'a> {
&self.context
}
pub fn destroy(self) -> LayoutContext<'a> {
self.context
}
}
@ -44,14 +44,33 @@ where
) where
F: FnMut(E::ConcreteNode),
{
if node.is_text_node() {
return;
}
let had_style_data = node.style_data().is_some();
unsafe {
node.initialize_style_and_layout_data::<DOMLayoutData>();
if !node.is_text_node() {
let el = node.as_element().unwrap();
let mut data = el.mutate_data().unwrap();
recalc_style_at(self, traversal_data, context, el, &mut data, note_child);
el.unset_dirty_descendants();
}
}
let element = node.as_element().unwrap();
let mut element_data = element.mutate_data().unwrap();
if !had_style_data {
element_data.damage = RestyleDamage::reconstruct();
}
recalc_style_at(
self,
traversal_data,
context,
element,
&mut element_data,
note_child,
);
unsafe {
element.unset_dirty_descendants();
}
}
@ -72,3 +91,49 @@ where
&self.context.style_context
}
}
pub(crate) fn compute_damage_and_repair_style(
context: &SharedStyleContext,
node: ServoLayoutNode<'_>,
) -> RestyleDamage {
compute_damage_and_repair_style_inner(context, node, RestyleDamage::empty())
}
pub(crate) fn compute_damage_and_repair_style_inner(
context: &SharedStyleContext,
node: ServoLayoutNode<'_>,
parent_restyle_damage: RestyleDamage,
) -> RestyleDamage {
let original_damage;
let damage;
{
let mut element_data = node
.style_data()
.expect("Should not run `compute_damage` before styling.")
.element_data
.borrow_mut();
original_damage = std::mem::take(&mut element_data.damage);
damage = original_damage | parent_restyle_damage;
if let Some(ref style) = element_data.styles.primary {
if style.get_box().display == Display::None {
return damage;
}
}
}
let mut propagated_damage = damage;
for child in iter_child_nodes(node) {
if child.is_element() {
propagated_damage |= compute_damage_and_repair_style_inner(context, child, damage);
}
}
if propagated_damage == RestyleDamage::REPAINT && original_damage == RestyleDamage::REPAINT {
node.repair_style(context);
}
propagated_damage
}

View file

@ -35,6 +35,7 @@ tokio = { workspace = true, features = ["sync"] }
unicode-bidi = { workspace = true }
unicode-script = { workspace = true }
url = { workspace = true }
urlpattern = { workspace = true }
uuid = { workspace = true }
webrender_api = { workspace = true }
wr_malloc_size_of = { workspace = true }

View file

@ -50,8 +50,10 @@ use std::cell::OnceCell;
use std::collections::BinaryHeap;
use std::hash::{BuildHasher, Hash};
use std::ops::Range;
use std::rc::Rc;
use std::sync::Arc;
use style::properties::ComputedValues;
use style::values::generics::length::GenericLengthPercentageOrAuto;
pub use stylo_malloc_size_of::MallocSizeOfOps;
use uuid::Uuid;
@ -577,6 +579,28 @@ impl<T: MallocSizeOf> MallocConditionalSizeOf for Arc<T> {
}
}
impl<T> MallocUnconditionalShallowSizeOf for Rc<T> {
fn unconditional_shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
unsafe { ops.malloc_size_of(Rc::as_ptr(self)) }
}
}
impl<T: MallocSizeOf> MallocUnconditionalSizeOf for Rc<T> {
fn unconditional_size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
self.unconditional_shallow_size_of(ops) + (**self).size_of(ops)
}
}
impl<T: MallocSizeOf> MallocConditionalSizeOf for Rc<T> {
fn conditional_size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
if ops.have_seen_ptr(Rc::as_ptr(self)) {
0
} else {
self.unconditional_size_of(ops)
}
}
}
/// If a mutex is stored directly as a member of a data type that is being measured,
/// it is the unique owner of its contents and deserves to be measured.
///
@ -709,6 +733,12 @@ impl<T> MallocSizeOf for ipc_channel::ipc::IpcSender<T> {
}
}
impl<T> MallocSizeOf for ipc_channel::ipc::IpcReceiver<T> {
fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
0
}
}
impl MallocSizeOf for ipc_channel::ipc::IpcSharedMemory {
fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
self.len()
@ -721,6 +751,12 @@ impl<T: MallocSizeOf> MallocSizeOf for accountable_refcell::RefCell<T> {
}
}
impl MallocSizeOf for servo_arc::Arc<ComputedValues> {
fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
self.conditional_size_of(ops)
}
}
malloc_size_of_hash_map!(indexmap::IndexMap<K, V, S>);
malloc_size_of_hash_set!(indexmap::IndexSet<T, S>);
@ -746,12 +782,17 @@ malloc_size_of_is_0!(std::sync::atomic::AtomicUsize);
malloc_size_of_is_0!(std::time::Duration);
malloc_size_of_is_0!(std::time::Instant);
malloc_size_of_is_0!(std::time::SystemTime);
malloc_size_of_is_0!(style::data::ElementData);
malloc_size_of_is_0!(style::font_face::SourceList);
malloc_size_of_is_0!(style::properties::ComputedValues);
malloc_size_of_is_0!(style::properties::declaration_block::PropertyDeclarationBlock);
malloc_size_of_is_0!(style::queries::values::PrefersColorScheme);
malloc_size_of_is_0!(style::stylesheets::Stylesheet);
malloc_size_of_is_0!(style::values::specified::source_size_list::SourceSizeList);
malloc_size_of_is_0!(taffy::Layout);
malloc_size_of_is_0!(unicode_bidi::Level);
malloc_size_of_is_0!(unicode_script::Script);
malloc_size_of_is_0!(urlpattern::UrlPattern);
macro_rules! malloc_size_of_is_webrender_malloc_size_of(
($($ty:ty),+) => (
@ -771,6 +812,7 @@ malloc_size_of_is_webrender_malloc_size_of!(webrender_api::BorderStyle);
malloc_size_of_is_webrender_malloc_size_of!(webrender_api::BoxShadowClipMode);
malloc_size_of_is_webrender_malloc_size_of!(webrender_api::ColorF);
malloc_size_of_is_webrender_malloc_size_of!(webrender_api::ExtendMode);
malloc_size_of_is_webrender_malloc_size_of!(webrender_api::FontKey);
malloc_size_of_is_webrender_malloc_size_of!(webrender_api::FontInstanceKey);
malloc_size_of_is_webrender_malloc_size_of!(webrender_api::GlyphInstance);
malloc_size_of_is_webrender_malloc_size_of!(webrender_api::GradientStop);
@ -815,6 +857,14 @@ where
}
}
impl<T> MallocSizeOf for style::shared_lock::Locked<T> {
fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
// TODO: fix this implementation when Locked derives MallocSizeOf.
0
//<style::shared_lock::Locked<T> as stylo_malloc_size_of::MallocSizeOf>::size_of(self, ops)
}
}
impl<T: MallocSizeOf> MallocSizeOf for atomic_refcell::AtomicRefCell<T> {
fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
self.borrow().size_of(ops)

Some files were not shown because too many files have changed in this diff Show more