Fetch <object data="..."> resources using the same pipeline as <img src="...">. When the resource loads successfully, render it as a replaced element and suppress children (fallback content). When loading fails, children render normally as fallback. This enables the Acid2 3-level <object> fallback chain pattern. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
185 lines
6.2 KiB
Rust
185 lines
6.2 KiB
Rust
//! Utility to regenerate all golden expected files.
|
|
//! Run with: cargo test --test regen_goldens -- --ignored
|
|
|
|
use std::fs;
|
|
use std::path::PathBuf;
|
|
|
|
mod pipeline_test {
|
|
use display_list::build_display_list;
|
|
use html::HtmlParser;
|
|
use image::{ImagePipeline, ImageStore};
|
|
use layout::LayoutEngine;
|
|
use std::collections::HashMap;
|
|
use std::path::Path;
|
|
use style::{Display, StyleContext};
|
|
|
|
pub fn run_pipeline(html: &str) -> (String, String) {
|
|
let html_parser = HtmlParser::new();
|
|
let style_context = StyleContext::new();
|
|
let layout_engine = LayoutEngine::new_proportional();
|
|
|
|
let document = html_parser.parse(html);
|
|
let stylesheets = style_context.extract_stylesheets(&document);
|
|
let inline_styles = style_context.extract_inline_styles(&document);
|
|
let computed_styles = style_context.compute_styles(
|
|
&document,
|
|
&stylesheets,
|
|
&inline_styles,
|
|
Some(&css::Viewport::new(800.0, 600.0)),
|
|
);
|
|
|
|
let layout_tree = layout_engine.layout(&document, &computed_styles, 800.0, 600.0);
|
|
let display_list = build_display_list(&layout_tree);
|
|
|
|
(layout_tree.dump(), display_list.dump())
|
|
}
|
|
|
|
pub fn run_pipeline_with_images(html: &str, fixture_dir: &Path) -> (String, String) {
|
|
let html_parser = HtmlParser::new();
|
|
let style_context = StyleContext::new();
|
|
let layout_engine = LayoutEngine::new_proportional();
|
|
let image_pipeline = ImagePipeline::new();
|
|
|
|
let document = html_parser.parse(html);
|
|
let stylesheets = style_context.extract_stylesheets(&document);
|
|
let inline_styles = style_context.extract_inline_styles(&document);
|
|
let computed_styles = style_context.compute_styles(
|
|
&document,
|
|
&stylesheets,
|
|
&inline_styles,
|
|
Some(&css::Viewport::new(800.0, 600.0)),
|
|
);
|
|
|
|
let mut image_store = ImageStore::new();
|
|
let mut image_map = HashMap::new();
|
|
|
|
for node_id in document.get_elements_by_tag_name("img") {
|
|
if let Some(styles) = computed_styles.get(&node_id) {
|
|
if styles.display == Display::None {
|
|
continue;
|
|
}
|
|
}
|
|
if let Some(src) = document.get_attribute(node_id, "src") {
|
|
let image_path = fixture_dir.join(src);
|
|
if let Ok(bytes) = std::fs::read(&image_path) {
|
|
if let Ok(decoded) = image_pipeline.decode(&bytes, None) {
|
|
let img_id = image_store.insert(decoded);
|
|
image_map.insert(node_id, img_id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Load images from <object> elements' data attributes
|
|
let network = net::NetworkStack::new();
|
|
for node_id in document.get_elements_by_tag_name("object") {
|
|
if let Some(styles) = computed_styles.get(&node_id) {
|
|
if styles.display == Display::None {
|
|
continue;
|
|
}
|
|
}
|
|
if let Some(data) = document.get_attribute(node_id, "data") {
|
|
let bytes_and_mime = if data.starts_with("data:") {
|
|
shared::BrowserUrl::parse(data)
|
|
.ok()
|
|
.and_then(|url| network.load_sync(&url).ok())
|
|
.filter(|resp| resp.is_success())
|
|
.map(|resp| (resp.body, Some(resp.content_type.mime_type())))
|
|
} else {
|
|
let image_path = fixture_dir.join(data);
|
|
std::fs::read(&image_path).ok().map(|b| (b, None))
|
|
};
|
|
|
|
if let Some((bytes, mime)) = bytes_and_mime {
|
|
if let Ok(decoded) = image_pipeline.decode(&bytes, mime.as_deref()) {
|
|
let img_id = image_store.insert(decoded);
|
|
image_map.insert(node_id, img_id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
let layout_tree = layout_engine.layout_with_images(
|
|
&document,
|
|
&computed_styles,
|
|
800.0,
|
|
600.0,
|
|
&image_map,
|
|
&image_store,
|
|
);
|
|
let display_list = build_display_list(&layout_tree);
|
|
|
|
(layout_tree.dump(), display_list.dump())
|
|
}
|
|
}
|
|
|
|
fn get_goldens_dir() -> PathBuf {
|
|
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/goldens")
|
|
}
|
|
|
|
// Image fixtures that need the with_images pipeline
|
|
const IMAGE_FIXTURES: &[&str] = &[
|
|
"125-img-basic",
|
|
"126-img-width-attr",
|
|
"127-img-height-attr",
|
|
"128-img-both-attrs",
|
|
"129-img-css-width",
|
|
"130-img-css-both",
|
|
"131-img-in-div",
|
|
"132-img-inline",
|
|
"133-img-missing-src",
|
|
"134-img-broken-src",
|
|
"135-img-with-border",
|
|
"136-img-display-block",
|
|
"142-img-in-inline-wrapper",
|
|
"143-img-link-in-table",
|
|
"169-img-svg-basic",
|
|
"174-object-fallback",
|
|
"175-object-data-uri",
|
|
"176-object-fallback-chain",
|
|
];
|
|
|
|
#[test]
|
|
#[ignore]
|
|
fn regenerate_all_golden_files() {
|
|
let goldens_dir = get_goldens_dir();
|
|
let fixtures_dir = goldens_dir.join("fixtures");
|
|
let expected_dir = goldens_dir.join("expected");
|
|
|
|
let mut entries: Vec<_> = fs::read_dir(&fixtures_dir)
|
|
.unwrap()
|
|
.filter_map(|e| e.ok())
|
|
.filter(|e| e.path().extension().is_some_and(|ext| ext == "html"))
|
|
.collect();
|
|
entries.sort_by_key(|e| e.file_name());
|
|
|
|
let mut count = 0;
|
|
for entry in entries {
|
|
let fixture_name = entry
|
|
.path()
|
|
.file_stem()
|
|
.unwrap()
|
|
.to_string_lossy()
|
|
.to_string();
|
|
|
|
let html = fs::read_to_string(entry.path()).unwrap();
|
|
|
|
let (layout, dl) = if IMAGE_FIXTURES.contains(&fixture_name.as_str()) {
|
|
pipeline_test::run_pipeline_with_images(&html, &fixtures_dir)
|
|
} else {
|
|
pipeline_test::run_pipeline(&html)
|
|
};
|
|
|
|
fs::write(
|
|
expected_dir.join(format!("{}.layout.txt", fixture_name)),
|
|
&layout,
|
|
)
|
|
.unwrap();
|
|
fs::write(expected_dir.join(format!("{}.dl.txt", fixture_name)), &dl).unwrap();
|
|
|
|
count += 1;
|
|
}
|
|
|
|
println!("Regenerated {} golden test expected files", count);
|
|
}
|