mirror of
https://github.com/lexmount/moli.git
synced 2026-10-03 08:00:49 +00:00
Model embedded frame base backgrounds
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
use crate::{LayoutError, LayoutPoint, LayoutRect, LayoutSize, LayoutTransform2D, LayoutViewport};
|
||||
use crate::{
|
||||
LayoutError, LayoutPoint, LayoutRect, LayoutSize, LayoutTransform2D, LayoutViewport, PaintColor,
|
||||
};
|
||||
|
||||
/// Exclusive CSS-pixel limit for either side of an automatic full-document
|
||||
/// capture, matching Chromium's `Page.captureScreenshot` boundary.
|
||||
@@ -31,6 +33,12 @@ pub struct PaintCaptureRequest {
|
||||
/// distinction outside [`PaintCaptureRegion`], because both use the same
|
||||
/// document-coordinate raster rectangle.
|
||||
pub include_viewport_controls: bool,
|
||||
/// Color behind the document canvas before CSS canvas propagation.
|
||||
///
|
||||
/// Main-frame captures use an opaque white browser surface. Embedded
|
||||
/// browsing contexts use transparent black so their owner element remains
|
||||
/// visible unless the child document supplies its own canvas background.
|
||||
pub base_background_color: PaintColor,
|
||||
/// Maximum encoded image width in device pixels.
|
||||
pub max_width: Option<u32>,
|
||||
/// Maximum encoded image height in device pixels.
|
||||
@@ -44,6 +52,7 @@ impl PaintCaptureRequest {
|
||||
region: PaintCaptureRegion::Viewport,
|
||||
include_backgrounds: true,
|
||||
include_viewport_controls: true,
|
||||
base_background_color: PaintColor::WHITE,
|
||||
max_width: None,
|
||||
max_height: None,
|
||||
}
|
||||
@@ -55,6 +64,7 @@ impl PaintCaptureRequest {
|
||||
region: PaintCaptureRegion::Viewport,
|
||||
include_backgrounds: true,
|
||||
include_viewport_controls: true,
|
||||
base_background_color: PaintColor::WHITE,
|
||||
max_width,
|
||||
max_height,
|
||||
}
|
||||
@@ -66,6 +76,7 @@ impl PaintCaptureRequest {
|
||||
region: PaintCaptureRegion::FullDocument,
|
||||
include_backgrounds: true,
|
||||
include_viewport_controls: false,
|
||||
base_background_color: PaintColor::WHITE,
|
||||
max_width: None,
|
||||
max_height: None,
|
||||
}
|
||||
@@ -77,6 +88,7 @@ impl PaintCaptureRequest {
|
||||
region: PaintCaptureRegion::PageClip { rect, scale },
|
||||
include_backgrounds: true,
|
||||
include_viewport_controls: false,
|
||||
base_background_color: PaintColor::WHITE,
|
||||
max_width: None,
|
||||
max_height: None,
|
||||
}
|
||||
@@ -125,6 +137,7 @@ pub(crate) struct ResolvedPaintCapture {
|
||||
/// document and page-clip captures paint page content only, matching
|
||||
/// Chromium's compositor capture behavior.
|
||||
pub(crate) paint_root_scrollbars: bool,
|
||||
pub(crate) base_background_color: PaintColor,
|
||||
}
|
||||
|
||||
impl PaintCaptureRequest {
|
||||
@@ -201,6 +214,7 @@ impl PaintCaptureRequest {
|
||||
),
|
||||
include_backgrounds: self.include_backgrounds,
|
||||
paint_root_scrollbars,
|
||||
base_background_color: self.base_background_color,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -289,6 +303,22 @@ mod tests {
|
||||
LayoutPoint::ZERO
|
||||
);
|
||||
assert!(!capture.paint_root_scrollbars);
|
||||
assert_eq!(capture.base_background_color, PaintColor::WHITE);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn embedded_capture_can_select_a_transparent_base_background() {
|
||||
let mut request = PaintCaptureRequest::viewport();
|
||||
request.base_background_color = PaintColor::TRANSPARENT;
|
||||
let capture = request
|
||||
.resolve(
|
||||
LayoutViewport::new(100, 80, 1.0),
|
||||
LayoutPoint::ZERO,
|
||||
LayoutSize::new(100.0, 80.0),
|
||||
)
|
||||
.expect("embedded viewport capture");
|
||||
|
||||
assert_eq!(capture.base_background_color, PaintColor::TRANSPARENT);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -300,6 +330,7 @@ mod tests {
|
||||
},
|
||||
include_backgrounds: true,
|
||||
include_viewport_controls: false,
|
||||
base_background_color: PaintColor::WHITE,
|
||||
max_width: Some(500),
|
||||
max_height: Some(200),
|
||||
}
|
||||
|
||||
@@ -79,9 +79,9 @@ where
|
||||
{
|
||||
let root = projection.world.root;
|
||||
let (canvas_color, propagated_background) = if capture.include_backgrounds {
|
||||
canvas_background(projection.world, root)
|
||||
canvas_background(projection.world, root, capture.base_background_color)
|
||||
} else {
|
||||
(PaintColor::WHITE, None)
|
||||
(capture.base_background_color, None)
|
||||
};
|
||||
let mut snapshot = PaintSnapshot::new(projection.viewport, canvas_color);
|
||||
snapshot.surface = capture.surface;
|
||||
@@ -421,6 +421,7 @@ fn pop_clips(count: usize, snapshot: &mut PaintSnapshot) {
|
||||
fn canvas_background<N>(
|
||||
world: &LayoutWorld<N>,
|
||||
root: LayoutBoxId,
|
||||
base_background_color: PaintColor,
|
||||
) -> (PaintColor, Option<LayoutBoxId>)
|
||||
where
|
||||
N: Copy + Debug + Eq + Hash,
|
||||
@@ -430,7 +431,7 @@ where
|
||||
.element_semantics()
|
||||
.is_some_and(|element| element.is_html_element("html"));
|
||||
if !is_html_root {
|
||||
return (PaintColor::WHITE, None);
|
||||
return (base_background_color, None);
|
||||
}
|
||||
if style_has_canvas_background(&root_box.style) {
|
||||
return (root_box.style.background_color(), Some(root));
|
||||
@@ -450,7 +451,7 @@ where
|
||||
}
|
||||
stack.extend(layout_box.children.iter().rev().copied());
|
||||
}
|
||||
(PaintColor::WHITE, None)
|
||||
(base_background_color, None)
|
||||
}
|
||||
|
||||
fn style_has_canvas_background(style: &crate::ResolvedLayoutStyle) -> bool {
|
||||
|
||||
@@ -1020,11 +1020,13 @@ impl PaintSnapshot {
|
||||
shape: PaintShape::Rect(clip),
|
||||
transform: local_to_surface,
|
||||
});
|
||||
self.push_fragment(PaintFragment::Fill {
|
||||
shape: PaintShape::Rect(clip),
|
||||
brush: PaintBrush::Solid(child.canvas_color),
|
||||
transform: local_to_surface,
|
||||
});
|
||||
if child.canvas_color.alpha > 0.0 {
|
||||
self.push_fragment(PaintFragment::Fill {
|
||||
shape: PaintShape::Rect(clip),
|
||||
brush: PaintBrush::Solid(child.canvas_color),
|
||||
transform: local_to_surface,
|
||||
});
|
||||
}
|
||||
for fragment in child.fragments {
|
||||
if let Some(fragment) = rebase_embedded_fragment(
|
||||
fragment,
|
||||
|
||||
@@ -113,6 +113,7 @@ impl EmbeddedFrameRenderer<DomHandle> for NativeEmbeddedFrameRenderer<'_> {
|
||||
let request = if self.capture_paint {
|
||||
let mut capture = moli_layout::PaintCaptureRequest::viewport();
|
||||
capture.include_backgrounds = self.include_backgrounds;
|
||||
capture.base_background_color = moli_layout::PaintColor::TRANSPARENT;
|
||||
LayoutPassRequest::with_capture(viewport, self.reason, capture)
|
||||
} else {
|
||||
LayoutPassRequest::new(viewport, self.reason)
|
||||
|
||||
@@ -213,6 +213,7 @@ impl PageVm {
|
||||
region: moli_layout::PaintCaptureRegion::Viewport,
|
||||
include_backgrounds: true,
|
||||
include_viewport_controls: true,
|
||||
base_background_color: moli_layout::PaintColor::WHITE,
|
||||
max_width: request.max_width,
|
||||
max_height: request.max_height,
|
||||
};
|
||||
@@ -356,6 +357,7 @@ impl RendererCaptureScreenshotRequest {
|
||||
RendererScreenshotPurpose::Screenshot => true,
|
||||
},
|
||||
include_viewport_controls,
|
||||
base_background_color: moli_layout::PaintColor::WHITE,
|
||||
max_width: self.max_width,
|
||||
max_height: self.max_height,
|
||||
})
|
||||
|
||||
@@ -799,6 +799,56 @@ nestedDocument.body.appendChild(nestedViewportSized);
|
||||
.expect("iframe snapshot composition fixture should run");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn screenshot_composites_transparent_iframe_canvas_over_its_owner_background() {
|
||||
run_page_vm_async_test(async move {
|
||||
let loader =
|
||||
crate::network::ResourceRequestClient::new(&FetchConfig::default()).expect("loader");
|
||||
let mut page_vm = test_page_vm_with_loader_and_document_url(
|
||||
&loader,
|
||||
Vec::new(),
|
||||
Url::parse("https://example.com/transparent-iframe-canvas.html")?,
|
||||
);
|
||||
page_vm.vm_mut().eval(
|
||||
r#"
|
||||
document.documentElement.style.cssText = 'margin:0;padding:0;background:white';
|
||||
document.body.style.cssText = 'margin:0;padding:0';
|
||||
document.body.innerHTML = `
|
||||
<iframe id=transparent style="position:absolute;left:0;top:0;display:block;width:100px;aspect-ratio:1/1;border:0;background:green"></iframe>
|
||||
<iframe id=colored style="position:absolute;left:110px;top:0;display:block;width:100px;aspect-ratio:1/1;border:0;background:green"></iframe>`;
|
||||
const colored = document.getElementById('colored').contentDocument;
|
||||
colored.documentElement.style.cssText = 'margin:0;padding:0;background:blue';
|
||||
'installed'
|
||||
"#,
|
||||
)?;
|
||||
page_vm.vm_mut().sync_live_document_style_sources();
|
||||
|
||||
let snapshot = page_vm
|
||||
.vm_mut()
|
||||
.screenshot_layout_snapshot(moli_layout::PaintViewport::new(210, 100, 1.0))?
|
||||
.expect("iframe canvas fixture must retain a root");
|
||||
let image = moli_paint::raster_snapshot(&snapshot)?;
|
||||
let pixel = |x: u32, y: u32| {
|
||||
let index = ((y * image.width + x) * 4) as usize;
|
||||
&image.rgba[index..index + 4]
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
pixel(50, 50),
|
||||
[0, 128, 0, 255],
|
||||
"a transparent child canvas must expose the iframe owner's background"
|
||||
);
|
||||
assert_eq!(
|
||||
pixel(160, 50),
|
||||
[0, 0, 255, 255],
|
||||
"a child document's own canvas background must still cover the owner"
|
||||
);
|
||||
Ok::<_, anyhow::Error>(())
|
||||
})
|
||||
.await
|
||||
.expect("transparent iframe canvas fixture should run");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn screenshot_and_screencast_paint_fresh_canvas_2d_backing_stores() {
|
||||
run_page_vm_async_test(async move {
|
||||
|
||||
Reference in New Issue
Block a user