Skip to content

Drag snapshot does not paint for normal flow content #47192

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

nullhook
Copy link
Contributor

@nullhook nullhook commented Jun 25, 2025

f220997

Drag snapshot does not paint for normal flow content
rdar://154202408

Reviewed by NOBODY (OOPS!).

Drag snapshots did not work for normal flow
content because enclosingLayer found the
nearest layer as the HTML root layer.
As a result, the layer used for painting
decisions was a much higher ancestor. We now
restrict this with a hasLayer check.

For non-layer roots, we exclude positioned
descendants from the drag image if they extend
outside the containing block.

All the special inclusion/exclusion
logic is now guraded behind a new paint behavior
DraggableSnapshot.

* Source/WebCore/testing/Internals.cpp:
(WebCore::Internals::snapshotNode):

Our snapshot tests only draggable
paint behavior.

f220997

Misc iOS, visionOS, tvOS & watchOS macOS Linux Windows
✅ 🧪 style ✅ 🛠 ios ✅ 🛠 mac ✅ 🛠 wpe ✅ 🛠 win
✅ 🧪 bindings ✅ 🛠 ios-sim ✅ 🛠 mac-AS-debug ✅ 🧪 wpe-wk2 ⏳ 🧪 win-tests
✅ 🧪 webkitperl ✅ 🧪 ios-wk2 ✅ 🧪 api-mac ✅ 🧪 api-wpe
✅ 🧪 ios-wk2-wpt ✅ 🧪 mac-wk1 ✅ 🛠 wpe-cairo
✅ 🧪 api-ios ✅ 🧪 mac-wk2 ✅ 🛠 gtk
✅ 🛠 vision ✅ 🧪 mac-AS-debug-wk2 ✅ 🧪 gtk-wk2
✅ 🛠 vision-sim ✅ 🧪 mac-wk2-stress ❌ 🧪 api-gtk
✅ 🧪 vision-wk2 ✅ 🧪 mac-intel-wk2 ✅ 🛠 playstation
✅ 🛠 tv ✅ 🛠 mac-safer-cpp
✅ 🛠 tv-sim
✅ 🛠 watch
✅ 🛠 watch-sim

@nullhook nullhook requested a review from cdumez as a code owner June 25, 2025 17:32
@nullhook nullhook self-assigned this Jun 25, 2025
@nullhook nullhook requested a review from smfr June 25, 2025 17:33
Comment on lines +3767 to +3768
} else {
if (renderer().isAbsolutelyPositioned() && &renderer() != paintingInfo.subtreePaintRoot) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
} else {
if (renderer().isAbsolutelyPositioned() && &renderer() != paintingInfo.subtreePaintRoot) {
} else if (renderer().isAbsolutelyPositioned() && &renderer() != paintingInfo.subtreePaintRoot) {

Copy link
Contributor

@smfr smfr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please wrap the commit message to something sensible like 80 or 90 columns.

@@ -120,7 +120,11 @@ static DragImageRef createDragImageFromSnapshot(RefPtr<ImageBuffer> snapshot, No
DragImageRef createDragImageForNode(LocalFrame& frame, Node& node)
{
ScopedNodeDragEnabler enableDrag(frame, node);
return createDragImageFromSnapshot(snapshotNode(frame, node, { { }, ImageBufferPixelFormat::BGRA8, DestinationColorSpace::SRGB() }), &node);

SnapshotOptions options { { }, ImageBufferPixelFormat::BGRA8, DestinationColorSpace::SRGB() };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider:

 auto options =  SnapshotOptions options { { SnapshotFlags::DraggableElement }, ImageBufferPixelFormat::BGRA8, DestinationColorSpace::SRGB() }; { { }, ImageBufferPixelFormat::BGRA8, DestinationColorSpace::SRGB() };

@@ -218,7 +222,10 @@ DragImageRef createDragImageForImage(LocalFrame& frame, Node& node, IntRect& ima
elementRect = snappedIntRect(topLevelRect);
imageRect = paintingRect;

return createDragImageFromSnapshot(snapshotNode(frame, node, { { }, ImageBufferPixelFormat::BGRA8, DestinationColorSpace::SRGB() }), &node);
SnapshotOptions options { { }, ImageBufferPixelFormat::BGRA8, DestinationColorSpace::SRGB() };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto

bool isLayerInSubtree = (this == subtreeRootLayer) || isDescendantOf(*subtreeRootLayer);

if (isLayerInSubtree) {
if (paintingInfo.subtreePaintRoot != &renderer()) {
if (&renderer() != paintingInfo.subtreePaintRoot) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to flip this.

if (CheckedPtr rootAsBlock = dynamicDowncast<RenderBlock>(paintingInfo.subtreePaintRoot)) {
if (!rootAsBlock->isContainingBlockAncestorFor(renderer()))
shouldPaintContent = false;
}
}
} else
shouldPaintContent = false;
} else {
if (renderer().isAbsolutelyPositioned() && &renderer() != paintingInfo.subtreePaintRoot) {
if (CheckedPtr rootAsBlock = dynamicDowncast<RenderBlock>(paintingInfo.subtreePaintRoot)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this branch also behaves correctly for the case where paintingInfo.subtreePaintRoot->hasLayer() is true, so consider removing the first branch.

If you do keep both, factor the isContainingBlockAncestorFor chunk into a lambda to avoid repetition.

@@ -2119,6 +2119,7 @@ ExceptionOr<RefPtr<ImageData>> Internals::snapshotNode(Node& node)
return Exception { ExceptionCode::InvalidAccessError };

SnapshotOptions options { { }, ImageBufferPixelFormat::BGRA8, DestinationColorSpace::SRGB() };
options.flags.add(SnapshotFlags::DraggableElement);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto.

}

window.addEventListener('load', () => {
setTimeout(main, 200);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't use a 200ms timeout. If every one of our thousands of layout tests took 200ms, they would take hours to run.

return;
}

const imageData = internals.snapshotNode(box);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider moving this shared JS into a resources/snapshot-helper.js file.

rdar://154202408

Reviewed by NOBODY (OOPS!).

Drag snapshots did not work for normal flow
content because enclosingLayer found the
nearest layer as the HTML root layer.
As a result, the layer used for painting
decisions was a much higher ancestor. We now
restrict this with a hasLayer check.

For non-layer roots, we exclude positioned
descendants from the drag image if they extend
outside the containing block.

All the special inclusion/exclusion
logic is now guraded behind a new paint behavior
DraggableSnapshot.

* Source/WebCore/testing/Internals.cpp:
(WebCore::Internals::snapshotNode):

Our snapshot tests only draggable
paint behavior.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants