diff --git a/Tests/LibWeb/Text/expected/HTML/HTMLElement-offsetFoo-in-table-cell.txt b/Tests/LibWeb/Text/expected/HTML/HTMLElement-offsetFoo-in-table-cell.txt new file mode 100644 index 0000000000..7f9f5fb941 --- /dev/null +++ b/Tests/LibWeb/Text/expected/HTML/HTMLElement-offsetFoo-in-table-cell.txt @@ -0,0 +1,21 @@ + +nodeName: CANVAS +offsetTop: 0 +offsetLeft: 0 +offsetParent: [object HTMLTableCellElement] + +nodeName: TD +offsetTop: 2 +offsetLeft: 2 +offsetParent: [object HTMLTableElement] + +nodeName: TABLE +offsetTop: 100 +offsetLeft: 50 +offsetParent: [object HTMLBodyElement] + +nodeName: BODY +offsetTop: 0 +offsetLeft: 0 +offsetParent: null + diff --git a/Tests/LibWeb/Text/input/HTML/HTMLElement-offsetFoo-in-table-cell.html b/Tests/LibWeb/Text/input/HTML/HTMLElement-offsetFoo-in-table-cell.html new file mode 100644 index 0000000000..56eb91e2de --- /dev/null +++ b/Tests/LibWeb/Text/input/HTML/HTMLElement-offsetFoo-in-table-cell.html @@ -0,0 +1,26 @@ +
+ + diff --git a/Userland/Libraries/LibWeb/HTML/HTMLElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLElement.cpp index 6dbae57c59..363bd830e5 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLElement.cpp +++ b/Userland/Libraries/LibWeb/HTML/HTMLElement.cpp @@ -155,30 +155,111 @@ String HTMLElement::inner_text() return MUST(builder.to_string()); } -// // https://drafts.csswg.org/cssom-view/#dom-htmlelement-offsettop -int HTMLElement::offset_top() const +// https://www.w3.org/TR/cssom-view-1/#dom-htmlelement-offsetparent +JS::GCPtr HTMLElement::offset_parent() const { - // NOTE: Ensure that layout is up-to-date before looking at metrics. const_cast(document()).update_layout(); - if (is(this) || !layout_node() || !parent_element() || !parent_element()->layout_node()) - return 0; - auto position = layout_node()->box_type_agnostic_position(); - auto parent_position = parent_element()->layout_node()->box_type_agnostic_position(); - return position.y().to_int() - parent_position.y().to_int(); + // 1. If any of the following holds true return null and terminate this algorithm: + // - The element does not have an associated CSS layout box. + // - The element is the root element. + // - The element is the HTML body element. + // - The element’s computed value of the position property is fixed. + if (!layout_node()) + return nullptr; + if (is_document_element()) + return nullptr; + if (is(*this)) + return nullptr; + if (layout_node()->is_fixed_position()) + return nullptr; + + // 2. Return the nearest ancestor element of the element for which at least one of the following is true + // and terminate this algorithm if such an ancestor is found: + // - The computed value of the position property is not static. + // - It is the HTML body element. + // - The computed value of the position property of the element is static + // and the ancestor is one of the following HTML elements: td, th, or table. + + for (auto* ancestor = parent_element(); ancestor; ancestor = ancestor->parent_element()) { + if (!ancestor->layout_node()) + continue; + if (ancestor->layout_node()->is_positioned()) + return const_cast(ancestor); + if (is(*ancestor)) + return const_cast(ancestor); + if (!ancestor->layout_node()->is_positioned() && ancestor->local_name().is_one_of(HTML::TagNames::td, HTML::TagNames::th, HTML::TagNames::table)) + return const_cast(ancestor); + } + + VERIFY_NOT_REACHED(); } -// https://drafts.csswg.org/cssom-view/#dom-htmlelement-offsetleft -int HTMLElement::offset_left() const +// https://www.w3.org/TR/cssom-view-1/#dom-htmlelement-offsettop +int HTMLElement::offset_top() const { + // 1. If the element is the HTML body element or does not have any associated CSS layout box + // return zero and terminate this algorithm. + if (is(*this)) + return 0; + // NOTE: Ensure that layout is up-to-date before looking at metrics. const_cast(document()).update_layout(); - if (is(this) || !layout_node() || !parent_element() || !parent_element()->layout_node()) + if (!layout_node()) return 0; + + // 2. If the offsetParent of the element is null + // return the y-coordinate of the top 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 terminate this algorithm. + auto offset_parent = this->offset_parent(); + if (!offset_parent || !offset_parent->layout_node()) { + auto position = layout_node()->box_type_agnostic_position(); + return position.y().to_int(); + } + + // 3. Return the result of subtracting the y-coordinate of the top padding edge + // of the first box associated with the offsetParent of the element + // from the y-coordinate of the top border edge of the first box associated with the element, + // relative to the initial containing block origin, + // ignoring any transforms that apply to the element and its ancestors. + auto offset_parent_position = offset_parent->layout_node()->box_type_agnostic_position(); auto position = layout_node()->box_type_agnostic_position(); - auto parent_position = parent_element()->layout_node()->box_type_agnostic_position(); - return position.x().to_int() - parent_position.x().to_int(); + return position.y().to_int() - offset_parent_position.y().to_int(); +} + +// https://www.w3.org/TR/cssom-view-1/#dom-htmlelement-offsetleft +int HTMLElement::offset_left() const +{ + // 1. If the element is the HTML body element or does not have any associated CSS layout box return zero and terminate this algorithm. + if (is(*this)) + return 0; + + // NOTE: Ensure that layout is up-to-date before looking at metrics. + const_cast(document()).update_layout(); + + if (!layout_node()) + return 0; + + // 2. If the offsetParent of the element is null + // return 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 terminate this algorithm. + auto offset_parent = this->offset_parent(); + if (!offset_parent || !offset_parent->layout_node()) { + auto position = layout_node()->box_type_agnostic_position(); + return position.x().to_int(); + } + + // 3. 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. + auto offset_parent_position = offset_parent->layout_node()->box_type_agnostic_position(); + auto position = layout_node()->box_type_agnostic_position(); + return position.x().to_int() - offset_parent_position.x().to_int(); } // https://drafts.csswg.org/cssom-view/#dom-htmlelement-offsetwidth diff --git a/Userland/Libraries/LibWeb/HTML/HTMLElement.h b/Userland/Libraries/LibWeb/HTML/HTMLElement.h index 98a78cfda3..abe0391a3a 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLElement.h +++ b/Userland/Libraries/LibWeb/HTML/HTMLElement.h @@ -44,6 +44,7 @@ public: int offset_left() const; int offset_width() const; int offset_height() const; + JS::GCPtr offset_parent() const; bool cannot_navigate() const; diff --git a/Userland/Libraries/LibWeb/HTML/HTMLElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLElement.idl index a34aab3b13..681942aedf 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLElement.idl +++ b/Userland/Libraries/LibWeb/HTML/HTMLElement.idl @@ -37,7 +37,7 @@ interface HTMLElement : Element { // FIXME: [CEReactions] attribute DOMString? popover; // https://drafts.csswg.org/cssom-view/#extensions-to-the-htmlelement-interface - // FIXME: readonly attribute Element? offsetParent; + readonly attribute Element? offsetParent; readonly attribute long offsetTop; readonly attribute long offsetLeft; readonly attribute long offsetWidth;