diff --git a/.isu/issues.json b/.isu/issues.json
index b410b62..475945e 100644
--- a/.isu/issues.json
+++ b/.isu/issues.json
@@ -1,5 +1,5 @@
{
- "next_id": 305,
+ "next_id": 306,
"issues": [
{
"id": 1,
@@ -3693,6 +3693,19 @@
"author": "piefev",
"state": "open",
"created_at": "2026-05-28T18:03:52Z"
+ },
+ {
+ "id": 305,
+ "repo": "we",
+ "title": "Bing real-web snapshot and Chromium golden are out of sync",
+ "body": "The current bing.com offline fixture and Chromium golden are not comparing the same page state, which blocks closing isu issue 280 on engine changes alone.\n\nRepro:\ncargo run -p we-e2e -- --scenario crates/e2e/scenarios/real-web/bing.com.we --out-dir crates/e2e/artifacts\n\nObserved on 2026-05-28 after the Bing rendering fixes in branch bing-parity-280:\n- desktop: 2.65% match (1195912/1228500 px differ, tol=4, max_diff=0.1000%)\n- mobile: 5.66% match (310529/329160 px differ, tol=4, max_diff=0.1000%)\n\nThe offline scenario now loads crates/e2e/real-web/snapshots/bing.com/index.html plus a seeded Sichuan Tea background image:\n- crates/e2e/real-web/snapshots/bing.com/www.bing.com/th-OHR.SichuanTea_ROW1006834324_1920x1080.jpg\n\nThe committed Chromium desktop golden shows a different Bing daily background/page state (Jaipur/news-card page), so most pixels differ before remaining engine gaps are considered:\n- crates/e2e/scenarios/real-web/bing.com.desktop.chromium.expected.png\n- crates/e2e/scenarios/real-web/bing.com.mobile.chromium.expected.png\n\nCurrent artifacts:\n- crates/e2e/artifacts/real-web/bing.com/desktop.png\n- crates/e2e/artifacts/real-web/bing.com/desktop.png.diff.png\n- crates/e2e/artifacts/real-web/bing.com/mobile.png\n- crates/e2e/artifacts/real-web/bing.com/mobile.png.diff.png\n\nExpected: rebaseline the Bing snapshot and Chromium goldens from the same page state, or adjust the golden capture policy for this scenario so the offline fixture and asserted Chromium reference are comparable. After that, remaining JS/layout differences can be tracked separately and issue 280 can be closed only when the xfail is removed.",
+ "labels": [
+ "real-web"
+ ],
+ "assigned": [],
+ "author": "piefev",
+ "state": "open",
+ "created_at": "2026-05-28T23:03:46Z"
}
]
}
diff --git a/crates/browser/src/img_loader.rs b/crates/browser/src/img_loader.rs
index f62d0a2..e228f6b 100644
--- a/crates/browser/src/img_loader.rs
+++ b/crates/browser/src/img_loader.rs
@@ -12,6 +12,7 @@ use we_image::jpeg::decode_jpeg;
use we_image::pixel::{Image, ImageError};
use we_image::png::decode_png;
use we_net::referrer::ReferrerPolicy;
+use we_style::computed::StyledNode;
use we_url::Url;
use crate::loader::{LoadError, Resource, ResourceLoader};
@@ -170,6 +171,56 @@ pub fn collect_images(doc: &Document, loader: &mut ResourceLoader, base_url: &Ur
store
}
+/// Fetch and decode CSS background images referenced by the computed style tree.
+pub fn collect_background_images(
+ doc: &Document,
+ styled: &StyledNode,
+ loader: &mut ResourceLoader,
+ base_url: &Url,
+ store: &mut ImageStore,
+) {
+ collect_background_images_for_node(doc, styled, loader, base_url, store);
+}
+
+fn collect_background_images_for_node(
+ doc: &Document,
+ styled: &StyledNode,
+ loader: &mut ResourceLoader,
+ base_url: &Url,
+ store: &mut ImageStore,
+) {
+ if let Some(url) = styled.style.background_image.as_ref() {
+ let src = url.to_string();
+ match fetch_and_decode(loader, &src, base_url, None) {
+ Ok(image) => {
+ store.insert(
+ styled.node,
+ ImageResource {
+ display_width: 0.0,
+ display_height: 0.0,
+ alt: String::new(),
+ image: Some(image),
+ },
+ );
+ }
+ Err(_) => {
+ store.entry(styled.node).or_insert_with(|| ImageResource {
+ image: None,
+ display_width: 0.0,
+ display_height: 0.0,
+ alt: String::new(),
+ });
+ }
+ }
+ }
+
+ for child in &styled.children {
+ if matches!(doc.node_data(child.node), NodeData::Element { .. }) {
+ collect_background_images_for_node(doc, child, loader, base_url, store);
+ }
+ }
+}
+
/// Walk the DOM in document order and collect `
` element nodes.
fn collect_img_nodes(doc: &Document, node: NodeId, result: &mut Vec) {
if let NodeData::Element { tag_name, .. } = doc.node_data(node) {
diff --git a/crates/browser/src/main.rs b/crates/browser/src/main.rs
index 9536416..1b67cd3 100644
--- a/crates/browser/src/main.rs
+++ b/crates/browser/src/main.rs
@@ -11,7 +11,7 @@ use we_browser::form_submission::{
use we_browser::img_loader::{collect_images, ImageStore};
use we_browser::loader::{LoadError, Resource, ResourceLoader};
use we_browser::navigation_history::NavigationHistory;
-use we_browser::script_loader::execute_scripts_into_vm;
+use we_browser::script_loader::{drain_event_loop_and_dynamic_scripts, execute_scripts_into_vm};
use we_css::parser::Stylesheet;
use we_css::values::Color;
use we_dom::{Document, NodeData, NodeId};
@@ -3915,8 +3915,9 @@ fn load_page(loaded: LoadedHtml) -> PageState {
let document_origin = loaded.base_url.origin();
let mut vm = execute_scripts_into_vm(doc, &mut loader, &loaded.base_url, &document_origin);
- // Borrow the (possibly script-modified) document to load subresources.
- let (stylesheet, font_registry, images) = {
+ // Borrow the (possibly script-modified) document to load subresources
+ // before the page load event fires.
+ let initial_images = {
let doc_ref = vm.borrow_document().expect("document still attached");
// Fetch external stylesheets and merge with inline
+Box
+