Forráskód Böngészése

LibWeb: Flesh out "scroll a target into view" spec implementation

This change fixes regressed Acid2 test and now we at least can see
the face after clicking "Take The Acid2 Test" link.
Aliaksandr Kalenik 1 éve
szülő
commit
ef71f46da6
1 módosított fájl, 151 hozzáadás és 24 törlés
  1. 151 24
      Userland/Libraries/LibWeb/DOM/Element.cpp

+ 151 - 24
Userland/Libraries/LibWeb/DOM/Element.cpp

@@ -1518,37 +1518,164 @@ WebIDL::ExceptionOr<void> Element::insert_adjacent_text(String const& where, Str
 }
 
 // https://w3c.github.io/csswg-drafts/cssom-view-1/#scroll-an-element-into-view
-static ErrorOr<void> scroll_an_element_into_view(DOM::Element& element, Bindings::ScrollBehavior behavior, Bindings::ScrollLogicalPosition block, Bindings::ScrollLogicalPosition inline_)
-{
-    // FIXME: The below is ad-hoc, since we don't yet have scrollable elements.
-    //        Return here and implement this according to spec once all overflow is made scrollable.
+static ErrorOr<void> scroll_an_element_into_view(DOM::Element& target, Bindings::ScrollBehavior behavior, Bindings::ScrollLogicalPosition block, Bindings::ScrollLogicalPosition inline_)
+{
+    // To scroll a target into view target, which is an Element or Range, with a scroll behavior behavior, a block flow
+    // direction position block, and an inline base direction position inline, means to run these steps for each ancestor
+    // element or viewport that establishes a scrolling box scrolling box, in order of innermost to outermost scrolling box:
+    auto ancestor = target.parent();
+    Vector<DOM::Node&> scrollable_nodes;
+    while (ancestor) {
+        if (ancestor->paintable_box() && ancestor->paintable_box()->has_scrollable_overflow())
+            scrollable_nodes.append(*static_cast<DOM::Element*>(ancestor));
+        ancestor = ancestor->parent();
+    }
 
-    (void)behavior;
-    (void)block;
-    (void)inline_;
+    for (auto& scrollable_node : scrollable_nodes) {
+        // 1. If the Document associated with target is not same origin with the Document
+        //    associated with the element or viewport associated with scrolling box, terminate these steps.
+        if (target.document().origin() != scrollable_node.document().origin()) {
+            break;
+        }
 
-    if (!element.document().browsing_context())
-        return Error::from_string_view("Element has no browsing context."sv);
+        CSSPixelRect scrolling_box = *scrollable_node.paintable_box()->scrollable_overflow_rect();
+
+        // 2. Let target bounding border box be the box represented by the return value of invoking Element’s
+        //    getBoundingClientRect(), if target is an Element, or Range’s getBoundingClientRect(),
+        //    if target is a Range.
+        auto target_bounding_border_box = target.get_bounding_client_rect();
+
+        // 3. Let scrolling box edge A be the beginning edge in the block flow direction of scrolling box, and
+        //    let element edge A be target bounding border box’s edge on the same physical side as that of
+        //    scrolling box edge A.
+        CSSPixels element_edge_a = CSSPixels::nearest_value_for(target_bounding_border_box->top());
+        CSSPixels scrolling_box_edge_a = scrolling_box.top();
+
+        // 4. Let scrolling box edge B be the ending edge in the block flow direction of scrolling box, and let
+        //    element edge B be target bounding border box’s edge on the same physical side as that of scrolling
+        //    box edge B.
+        CSSPixels element_edge_b = CSSPixels::nearest_value_for(target_bounding_border_box->bottom());
+        CSSPixels scrolling_box_edge_b = scrolling_box.bottom();
+
+        // 5. Let scrolling box edge C be the beginning edge in the inline base direction of scrolling box, and
+        //    let element edge C be target bounding border box’s edge on the same physical side as that of scrolling
+        //    box edge C.
+        CSSPixels element_edge_c = CSSPixels::nearest_value_for(target_bounding_border_box->left());
+        CSSPixels scrolling_box_edge_c = scrolling_box.left();
+
+        // 6. Let scrolling box edge D be the ending edge in the inline base direction of scrolling box, and let element
+        //    edge D be target bounding border box’s edge on the same physical side as that of scrolling box edge D.
+        CSSPixels element_edge_d = CSSPixels::nearest_value_for(target_bounding_border_box->right());
+        CSSPixels scrolling_box_edge_d = scrolling_box.right();
+
+        // 7. Let element height be the distance between element edge A and element edge B.
+        CSSPixels element_height = element_edge_b - element_edge_a;
+
+        // 8. Let scrolling box height be the distance between scrolling box edge A and scrolling box edge B.
+        CSSPixels scrolling_box_height = scrolling_box_edge_b - scrolling_box_edge_a;
+
+        // 9. Let element width be the distance between element edge C and element edge D.
+        CSSPixels element_width = element_edge_d - element_edge_c;
+
+        // 10. Let scrolling box width be the distance between scrolling box edge C and scrolling box edge D.
+        CSSPixels scrolling_box_width = scrolling_box_edge_d - scrolling_box_edge_c;
+
+        // 11. Let position be the scroll position scrolling box would have by following these steps:
+        auto position = [&]() -> CSSPixelRect {
+            auto result_edge_a = scrolling_box_edge_a;
+            auto result_edge_b = scrolling_box_edge_b;
+            auto result_edge_c = scrolling_box_edge_c;
+            auto result_edge_d = scrolling_box_edge_d;
+
+            // 1. If block is "start", then align element edge A with scrolling box edge A.
+            if (block == Bindings::ScrollLogicalPosition::Start) {
+                result_edge_a = element_edge_a;
+                result_edge_d = scrolling_box_edge_a + scrolling_box_height;
+            }
+            // 2. Otherwise, if block is "end", then align element edge B with scrolling box edge B.
+            else if (block == Bindings::ScrollLogicalPosition::End) {
+                result_edge_b = element_edge_b;
+                result_edge_a = scrolling_box_edge_b - scrolling_box_height;
+            }
+            // 3. Otherwise, if block is "center", then align the center of target bounding border box with the center of scrolling box in scrolling box’s block flow direction.
+            else if (block == Bindings::ScrollLogicalPosition::Center) {
+                TODO();
+            }
+            // 4. Otherwise, block is "nearest":
+            else {
+                // If element edge A and element edge B are both outside scrolling box edge A and scrolling box edge B
+                if (element_edge_a >= scrolling_box_edge_a && element_edge_b <= scrolling_box_edge_b) {
+                    // Do nothing.
+                }
+                // If element edge A is outside scrolling box edge A and element height is less than scrolling box height
+                // If element edge B is outside scrolling box edge B and element height is greater than scrolling box height
+                if ((element_edge_a >= scrolling_box_edge_a && element_height < scrolling_box_height)
+                    || (element_edge_b <= scrolling_box_edge_b && element_height > scrolling_box_height)) {
+                    // Align element edge A with scrolling box edge A.
+                    result_edge_a = element_edge_a;
+                    result_edge_b = scrolling_box_edge_a + scrolling_box_height;
+                }
+                // If element edge A is outside scrolling box edge A and element height is greater than scrolling box height
+                // If element edge B is outside scrolling box edge B and element height is less than scrolling box height
+                if ((element_edge_a <= scrolling_box_edge_a && element_height > scrolling_box_height)
+                    || (element_edge_b >= scrolling_box_edge_b && element_height < scrolling_box_height)) {
+                    // Align element edge B with scrolling box edge B.
+                    result_edge_b = element_edge_b;
+                    result_edge_a = scrolling_box_edge_b - scrolling_box_height;
+                }
+            }
 
-    auto* page = element.document().browsing_context()->page();
-    if (!page)
-        return Error::from_string_view("Element has no page."sv);
+            if (inline_ == Bindings::ScrollLogicalPosition::Nearest) {
+                // If element edge C and element edge D are both outside scrolling box edge C and scrolling box edge D
+                if (element_edge_c >= scrolling_box_edge_c && element_edge_d <= scrolling_box_edge_d) {
+                    // Do nothing.
+                }
+                // If element edge C is outside scrolling box edge C and element width is less than scrolling box width
+                // If element edge D is outside scrolling box edge D and element width is greater than scrolling box width
+                if ((element_edge_c >= scrolling_box_edge_c && element_width < scrolling_box_width)
+                    || (element_edge_d <= scrolling_box_edge_d && element_width > scrolling_box_width)) {
+                    // Align element edge C with scrolling box edge C.
+                    result_edge_c = element_edge_c;
+                    result_edge_d = scrolling_box_edge_c + scrolling_box_width;
+                }
+
+                // If element edge C is outside scrolling box edge C and element width is greater than scrolling box width
+                // If element edge D is outside scrolling box edge D and element width is less than scrolling box width
+                if ((element_edge_c <= scrolling_box_edge_c && element_width > scrolling_box_width)
+                    || (element_edge_d >= scrolling_box_edge_d && element_width < scrolling_box_width)) {
+                    // Align element edge D with scrolling box edge D.
+                    result_edge_d = element_edge_d;
+                    result_edge_c = scrolling_box_edge_d - scrolling_box_width;
+                }
+            }
 
-    // If this element doesn't have a layout node, we can't scroll it into view.
-    element.document().update_layout();
-    if (!element.layout_node())
-        return Error::from_string_view("Element has no layout node."sv);
+            auto result_width = result_edge_d - result_edge_c;
+            auto result_height = result_edge_b - result_edge_a;
+            return { result_edge_c, result_edge_a, result_width, result_height };
+        }();
 
-    // Find the nearest layout node that is a box (since we need a box to get a usable rect)
-    auto* layout_node = element.layout_node();
-    while (layout_node && !layout_node->is_box())
-        layout_node = layout_node->parent();
+        // FIXME: 12. If position is the same as scrolling box’s current scroll position, and scrolling box does not
+        //            have an ongoing smooth scroll, then return.
 
-    if (!layout_node)
-        return Error::from_string_view("Element has no parent layout node that is a box."sv);
+        // 13. If scrolling box is associated with a viewport
+        if (scrollable_node.is_document()) {
+            // 1. Let document be the viewport’s associated Document.
+            auto& document = static_cast<DOM::Document&>(scrollable_node);
 
-    if (auto navigable = element.document().navigable()) {
-        element.document().navigable()->traversable_navigable()->page()->client().page_did_request_scroll_into_view(verify_cast<Layout::Box>(*layout_node).paintable_box()->absolute_padding_box_rect());
+            // FIXME: 2. Let root element be document’s root element, if there is one, or null otherwise.
+            // FIXME: 3. Perform a scroll of the viewport to position, with root element as the associated element and behavior as the scroll behavior.
+            (void)behavior;
+
+            // AD-HOC:
+            auto* page = document.navigable()->traversable_navigable()->page();
+            auto scale = page->client().device_pixels_per_css_pixel();
+            page->client().page_did_request_scroll_to({ position.left() * scale, position.top() * scale });
+        }
+        // If scrolling box is associated with an element
+        else {
+            // FIXME: Perform a scroll of the element’s scrolling box to position, with the element as the associated
+            //        element and behavior as the scroll behavior.
+        }
     }
 
     return {};