diff --git a/fixtures/html/dom/document_hit_testing_on_overflow.html b/fixtures/html/dom/document_hit_testing_on_overflow.html
new file mode 100644
index 000000000..b3c32b014
--- /dev/null
+++ b/fixtures/html/dom/document_hit_testing_on_overflow.html
@@ -0,0 +1,187 @@
+
+
+
+
+
+ Overflow Hit Testing Test
+
+
+
+
+
+
Overflow HitTest Test
+
Goal: verify an overflowed child (overflow:visible on parent) remains hit-testable outside the parent's
+ visual bounds.
+
+
+
+
Container
+
+
+
+
+
+
+
Logs / Results
+
+
+
Manual test steps:
+
+ - Move the pointer onto the "Overflow Button" text (rendered below the parent).
+ - Click it: console should show
[CLICK] overflow button
and a PASS entry "Manual click
+ event fired".
+ - If it cannot be clicked, overflow visible hit-testing is broken.
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/client/layout/layout_box.cpp b/src/client/layout/layout_box.cpp
index 3aabb42e0..2aae19307 100644
--- a/src/client/layout/layout_box.cpp
+++ b/src/client/layout/layout_box.cpp
@@ -12,6 +12,7 @@ namespace client_layout
LayoutBox::LayoutBox(shared_ptr node)
: LayoutBoxModelObject(node)
+ , hit_testable_bounding_box_{glm::vec3(0.0f), glm::vec3(0.0f), glm::mat4(1.0f)}
{
}
@@ -318,9 +319,7 @@ namespace client_layout
// TODO(yorkie): handle the hit test for the box with overflow.
}
else
- {
- overflowBox = physicalBorderBoxRect();
- }
+ overflowBox = getHitTestableBoundingBox();
if (overflowBox.has_value())
{
@@ -360,12 +359,16 @@ namespace client_layout
{
LayoutBoxModelObject::didComputeLayoutOnce(availableSpace);
+ // Update the scrollable overflow from the layout results.
setScrollableOverflowFromLayoutResults();
if (isScrollContainer())
{
getScrollableArea()
->updateAfterLayout(formattingContext().liveFragment());
}
+
+ // Update the hit-testable bounding box.
+ updateHitTestableBoundingBox();
}
void LayoutBox::updateFromStyle()
@@ -405,4 +408,47 @@ namespace client_layout
setHasValidCachedGeometry(false);
// TODO(yorkie): invalidate the cached geometry of the parent.
}
+
+ vector> LayoutBox::getChildBoxes() const
+ {
+ auto children = virtualChildren();
+ if (!children)
+ return {};
+
+ vector> childBoxes;
+ for (const auto &child : *children)
+ {
+ if (child->isBox())
+ childBoxes.push_back(static_pointer_cast(child));
+ }
+ return childBoxes;
+ }
+
+ void LayoutBox::updateHitTestableBoundingBox()
+ {
+ auto selfBoundingBox = physicalBorderBoxRect();
+ glm::vec3 unionMin = selfBoundingBox.minimumWorld;
+ glm::vec3 unionMax = selfBoundingBox.maximumWorld;
+
+ for (const auto &childBox : getChildBoxes())
+ {
+ if (!childBox->visible())
+ continue;
+
+ const geometry::BoundingBox &childBoundingBox = childBox->getHitTestableBoundingBox();
+ unionMin.x = min(unionMin.x, childBoundingBox.minimumWorld.x);
+ unionMin.y = min(unionMin.y, childBoundingBox.minimumWorld.y);
+ unionMin.z = min(unionMin.z, childBoundingBox.minimumWorld.z);
+ unionMax.x = max(unionMax.x, childBoundingBox.maximumWorld.x);
+ unionMax.y = max(unionMax.y, childBoundingBox.maximumWorld.y);
+ unionMax.z = max(unionMax.z, childBoundingBox.maximumWorld.z);
+ }
+ hit_testable_bounding_box_ = geometry::BoundingBox(unionMin, unionMax, glm::mat4(1.0f));
+
+ // Notify the parent box to update its hit-testable bounding box.
+ if (parent() && parent()->isBox())
+ {
+ parentBox()->updateHitTestableBoundingBox();
+ }
+ }
}
diff --git a/src/client/layout/layout_box.hpp b/src/client/layout/layout_box.hpp
index 6d942cd51..6e0a22802 100644
--- a/src/client/layout/layout_box.hpp
+++ b/src/client/layout/layout_box.hpp
@@ -215,8 +215,15 @@ namespace client_layout
{
return overflow_ != nullptr && overflow_->visualOverflow;
}
+ inline geometry::BoundingBox getHitTestableBoundingBox() const
+ {
+ return hit_testable_bounding_box_;
+ }
+ void updateHitTestableBoundingBox();
glm::vec3 computeSize() const;
+ vector> getChildBoxes() const;
+
void invalidateCachedGeometry();
protected:
@@ -224,5 +231,6 @@ namespace client_layout
private:
std::shared_ptr overflow_;
+ geometry::BoundingBox hit_testable_bounding_box_;
};
}