mirror of
https://github.com/servo/servo.git
synced 2025-08-03 20:50:07 +01:00
feat(layout_2020): implement offset{Parent, Top, Left, Width, Height}
This commit is contained in:
parent
638941ac43
commit
bc63187340
2 changed files with 201 additions and 3 deletions
|
@ -375,8 +375,205 @@ pub fn process_resolved_style_request_for_unstyled_node<'dom>(
|
||||||
style.computed_value_to_string(PropertyDeclarationId::Longhand(longhand_id))
|
style.computed_value_to_string(PropertyDeclarationId::Longhand(longhand_id))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_offset_parent_query(_requested_node: OpaqueNode) -> OffsetParentResponse {
|
pub fn process_offset_parent_query(
|
||||||
OffsetParentResponse::empty()
|
node: OpaqueNode,
|
||||||
|
fragment_tree: Option<Arc<FragmentTree>>,
|
||||||
|
) -> OffsetParentResponse {
|
||||||
|
process_offset_parent_query_inner(node, fragment_tree)
|
||||||
|
.unwrap_or_else(OffsetParentResponse::empty)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn process_offset_parent_query_inner(
|
||||||
|
node: OpaqueNode,
|
||||||
|
fragment_tree: Option<Arc<FragmentTree>>,
|
||||||
|
) -> Option<OffsetParentResponse> {
|
||||||
|
let fragment_tree = fragment_tree?;
|
||||||
|
|
||||||
|
struct NodeOffsetBoxInfo {
|
||||||
|
border_box: Rect<Au>,
|
||||||
|
offset_parent_node_address: Option<OpaqueNode>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://www.w3.org/TR/2016/WD-cssom-view-1-20160317/#extensions-to-the-htmlelement-interface
|
||||||
|
let mut parent_node_addresses = Vec::new();
|
||||||
|
let node_offset_box = fragment_tree.find(|fragment, level, containing_block| {
|
||||||
|
// FIXME: Is there a less fragile way of checking whether this
|
||||||
|
// fragment is the body element, rather than just checking that
|
||||||
|
// it's at level 1 (below the root node)?
|
||||||
|
let is_body_element = level == 1;
|
||||||
|
|
||||||
|
if fragment.tag() == Some(Tag::Node(node)) {
|
||||||
|
// 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
|
||||||
|
.border_rect()
|
||||||
|
.to_physical(fragment.style.writing_mode, &containing_block),
|
||||||
|
Fragment::Text(fragment) => fragment
|
||||||
|
.rect
|
||||||
|
.to_physical(fragment.parent_style.writing_mode, &containing_block),
|
||||||
|
Fragment::AbsoluteOrFixedPositioned(_) |
|
||||||
|
Fragment::Image(_) |
|
||||||
|
Fragment::Anonymous(_) => unreachable!(),
|
||||||
|
};
|
||||||
|
let border_box = fragment_relative_rect.translate(containing_block.origin.to_vector());
|
||||||
|
|
||||||
|
let mut border_box = Rect::new(
|
||||||
|
Point2D::new(
|
||||||
|
Au::from_f32_px(border_box.origin.x.px()),
|
||||||
|
Au::from_f32_px(border_box.origin.y.px()),
|
||||||
|
),
|
||||||
|
Size2D::new(
|
||||||
|
Au::from_f32_px(border_box.size.width.px()),
|
||||||
|
Au::from_f32_px(border_box.size.height.px()),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// "If any of the following holds true return null and terminate
|
||||||
|
// this algorithm: [...] The element’s computed value of the
|
||||||
|
// `position` property is `fixed`."
|
||||||
|
let is_fixed = match fragment {
|
||||||
|
Fragment::Box(fragment) if fragment.style.get_box().position == Position::Fixed => {
|
||||||
|
true
|
||||||
|
},
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
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_address = 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,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// Record the paths of the nodes being traversed.
|
||||||
|
let parent_node_address = match fragment {
|
||||||
|
Fragment::Box(fragment) => {
|
||||||
|
let is_eligible_parent =
|
||||||
|
match (is_body_element, fragment.style.get_box().position) {
|
||||||
|
// Spec says the element is eligible as `offsetParent` if any of
|
||||||
|
// these are true:
|
||||||
|
// 1) Is the body element
|
||||||
|
// 2) Is static position *and* is a table or table cell
|
||||||
|
// 3) Is not static position
|
||||||
|
// TODO: Handle case 2
|
||||||
|
(true, _) |
|
||||||
|
(false, Position::Absolute) |
|
||||||
|
(false, Position::Relative) |
|
||||||
|
(false, Position::Fixed) => true,
|
||||||
|
|
||||||
|
// Otherwise, it's not a valid parent
|
||||||
|
(false, Position::Static) => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Tag::Node(node_address) = fragment.tag {
|
||||||
|
is_eligible_parent.then(|| node_address)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Fragment::AbsoluteOrFixedPositioned(_) |
|
||||||
|
Fragment::Text(_) |
|
||||||
|
Fragment::Image(_) |
|
||||||
|
Fragment::Anonymous(_) => 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 = node_offset_box
|
||||||
|
.offset_parent_node_address
|
||||||
|
.map(|offset_parent_node_address| {
|
||||||
|
// 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.
|
||||||
|
fragment_tree
|
||||||
|
.find(|fragment, _, containing_block| {
|
||||||
|
match fragment {
|
||||||
|
Fragment::Box(fragment)
|
||||||
|
if fragment.tag == Tag::Node(offset_parent_node_address) =>
|
||||||
|
{
|
||||||
|
// Again, take the *first* associated CSS layout box.
|
||||||
|
let padding_box_corner = fragment
|
||||||
|
.padding_rect()
|
||||||
|
.to_physical(fragment.style.writing_mode, &containing_block)
|
||||||
|
.origin
|
||||||
|
.to_vector() +
|
||||||
|
containing_block.origin.to_vector();
|
||||||
|
let padding_box_corner = Vector2D::new(
|
||||||
|
Au::from_f32_px(padding_box_corner.x.px()),
|
||||||
|
Au::from_f32_px(padding_box_corner.y.px()),
|
||||||
|
);
|
||||||
|
Some(padding_box_corner)
|
||||||
|
}
|
||||||
|
Fragment::AbsoluteOrFixedPositioned(_) |
|
||||||
|
Fragment::Box(_) |
|
||||||
|
Fragment::Text(_) |
|
||||||
|
Fragment::Image(_) |
|
||||||
|
Fragment::Anonymous(_) => None,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap()
|
||||||
|
})
|
||||||
|
// "If the offsetParent of the element is null," subtract zero in the
|
||||||
|
// following step.
|
||||||
|
.unwrap_or(Vector2D::zero());
|
||||||
|
|
||||||
|
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),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#the-innertext-idl-attribute
|
// https://html.spec.whatwg.org/multipage/#the-innertext-idl-attribute
|
||||||
|
|
|
@ -1212,7 +1212,8 @@ impl LayoutThread {
|
||||||
process_resolved_font_style_query(node, property, value);
|
process_resolved_font_style_query(node, property, value);
|
||||||
},
|
},
|
||||||
&QueryMsg::OffsetParentQuery(node) => {
|
&QueryMsg::OffsetParentQuery(node) => {
|
||||||
rw_data.offset_parent_response = process_offset_parent_query(node);
|
rw_data.offset_parent_response =
|
||||||
|
process_offset_parent_query(node, self.fragment_tree.borrow().clone());
|
||||||
},
|
},
|
||||||
&QueryMsg::StyleQuery => {},
|
&QueryMsg::StyleQuery => {},
|
||||||
&QueryMsg::NodesFromPointQuery(client_point, ref reflow_goal) => {
|
&QueryMsg::NodesFromPointQuery(client_point, ref reflow_goal) => {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue