Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions moli-layout/src/projection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -784,6 +784,25 @@ where
for fragment in self.owner_paint_fragments[index].clone() {
self.assign_fragment_paint_metadata(fragment, self.content_clips[index]);
}
// An atomic inline-level box (a replaced element, form
// control, or inline-block wrapper consumed by its owner
// IFC) is painted and hit-tested as line content, in front
// of the enclosing inline boxes' fragments. Promote its own
// box fragment to this contents phase so a point inside the
// atomic's box resolves to the atomic rather than to an
// inline ancestor (e.g. a wrapping `<label>`). `paint_order`
// is consumed only by hit testing, never by painting, so
// this does not alter rendering. Use the outer (background)
// clip: the atomic's own box spans its full border box, so
// it must not be constrained by the box's internal content
// clip, or edge points (e.g. a caret at an input's left
// border) fall through to the enclosing inline.
if self.world.boxes[index].inline_context_owner.is_some()
&& !self.world.boxes[index].inline_flattened
&& let Some(direct) = self.direct_fragments[index]
{
self.assign_fragment_paint_metadata(direct, self.background_clips[index]);
}
}
PaintOrderEvent::BoxOutline(id) => {
let index = id.index();
Expand Down
78 changes: 78 additions & 0 deletions moli-protocol/src/domains/dom/tests/geometry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1332,6 +1332,84 @@ async fn get_node_for_location_uses_real_layout_hit_testing() {
);
}

#[tokio::test(flavor = "multi_thread")]
async fn get_node_for_location_hits_atomic_input_in_front_of_wrapping_label() {
let mut ctx = TestContext::new();
load_bc(&mut ctx, "BID-A");
navigate_to_data_html_async(
&mut ctx,
1,
"<!doctype html><html><body><form><p><label>Customer name: <input name=custname></label></p></form></body></html>",
)
.await;

let input_backend = renderer_backend_node_id_for_live_expression(
&mut ctx,
2,
3,
"document.querySelector('input')",
)
.await;
let label_backend = renderer_backend_node_id_for_live_expression(
&mut ctx,
4,
5,
"document.querySelector('label')",
)
.await;

ctx.process_async(json!({
"id": 6,
"method": "DOM.getContentQuads",
"params": { "backendNodeId": input_backend }
}))
.await;
let quad = take_response_by_id(&mut ctx, 6)["result"]["quads"][0]
.as_array()
.expect("the input has one content quad")
.clone();
let x1 = quad[0].as_f64().unwrap_or_default();
let y1 = quad[1].as_f64().unwrap_or_default();
let x2 = quad[4].as_f64().unwrap_or_default();
let y2 = quad[5].as_f64().unwrap_or_default();
assert!(
x2 > x1 && y2 > y1 + 2.0,
"the input box must be non-trivially sized for the baseline repro, got x {x1}..{x2}, y {y1}..{y2}"
);

let cx = ((x1 + x2) / 2.0).round() as i64;
let mid_y = ((y1 + y2) / 2.0).round() as i64;
let mut id = 10;
for y in [(y1 + 1.0).round() as i64, mid_y, (y2 - 1.0).round() as i64] {
ctx.process_async(json!({
"id": id,
"method": "DOM.getNodeForLocation",
"params": { "x": cx, "y": y }
}))
.await;
let hit = take_response_by_id(&mut ctx, id);
assert_eq!(
hit["result"]["backendNodeId"],
json!(input_backend),
"a point inside the input box at y={y} must hit the input, not the wrapping label"
);
id += 1;
}

ctx.process_async(json!({
"id": id,
"method": "DOM.getNodeForLocation",
"params": { "x": (x1 - 4.0).max(0.0).round() as i64, "y": mid_y }
}))
.await;
let hit = take_response_by_id(&mut ctx, id);
assert_eq!(
hit["result"]["backendNodeId"],
json!(label_backend),
"a point over the label text to the left of the input must still hit the wrapping label"
);
}

#[tokio::test(flavor = "multi_thread")]
async fn get_node_for_location_mock_policy_uses_compatibility_geometry() {
let mut ctx = TestContext::new_with_layout_policy(LayoutPolicy::Mock);
Expand Down
Loading