Skip to content

Conversation

@dnfield
Copy link
Contributor

@dnfield dnfield commented May 16, 2022

Very related to #103748

These are more RO implementations that are ignoring Clip.none.

I've added a framework assertion so that if someone tries to call PaintingContext.pushClipFoo and incorrectly implements describeApproximatePaintClip, they'll get an error now. That makes some of these ROs fail without new tests. However, some ROs already try to check for this case in their own paint methods and bypass pushClipFoo when their clip behavior is Clip.none anyway, so it's not foolproof.

Another one for the visibility_detector refactor.

@flutter-dashboard flutter-dashboard bot added a: accessibility Accessibility, e.g. VoiceOver or TalkBack. (aka a11y) f: scrolling Viewports, list views, slivers, etc. framework flutter/packages/flutter repository. See also f: labels. labels May 16, 2022
@dnfield
Copy link
Contributor Author

dnfield commented May 16, 2022

The previous patch led to some internal tests breaking for two reasons:

  • Some tests are doing image diffing based on a graphical representation of the semantics tree. They will likely need to be updated for these cases too.
  • Some tests were asserting a11y guidelines around now larger areas because of a newly expanded clip. They may need updating.

return PaintingContext(childLayer, bounds);
}

void _checkForClipBehavior(String type) {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: _debugCheckForClipBehavior

Copy link
Contributor

Choose a reason for hiding this comment

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

Alternatively, remove the assert from this method and make it a bool Function() that could be passed directly to assert where it is called

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

Copy link
Contributor

Choose a reason for hiding this comment

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

or I guess, more like a bool Function() Function(String)

Copy link
Contributor

@jonahwilliams jonahwilliams left a comment

Choose a reason for hiding this comment

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

LGTM

assert(() {
final RenderObject active = RenderObject.debugActivePaint!;
active.visitChildren((RenderObject child) {
final Rect? approximatePaintClip = active.describeApproximatePaintClip(child);
Copy link
Member

Choose a reason for hiding this comment

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

What if I have two children, but I only want to clip one in my current config and to do that I call pushClipRect(clipBehavior: Clip.none) for one child and pushClipRect(clipBehavior: Clip.hardEdge) for the other one. describeApproximatePaintClip would return null for one of the children, but non-null for the other.

In that case, wouldn't I incorrectly see this error?

Copy link
Member

Choose a reason for hiding this comment

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

(I didn't see a test proving that this would work)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think this error should work in that case, but I will add a test.

This seems like a very strange thing to do though, and it's not how any of the framework ROs are implemented today.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added another test for this in object_test.dart.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't really understand how this can work for that case... the _debugCheckForClipBehavior method checks all the children, but is called for each pushed clip layer, which might only involve one child.

In general I don't think the PaintingContext has enough information to make this assert work, since it doesn't know which children are being painted in which layer. Some children might not be painted at all, some might be painted in one layer, some another, etc.

@goderbauer
Copy link
Member

(I removed the merge label pending my question above)

object.owner!.flushCompositingBits();
expect(
() => PaintingContext(ContainerLayer(), Rect.largest).paintChild(object, Offset.zero),
returnsNormally,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@goderbauer specifically the RO I added below does what I think you were asking for, and makes sure to return the right response from describeApproximatePaintClip based on which child you ask for.

final RenderSizedBox child = RenderSizedBox(const Size(300, 300));
final RenderSizedBox specialChild = RenderSizedBox(const Size(200, 200));
final TestMultiClipBehaviorClipRenderObject object = TestMultiClipBehaviorClipRenderObject(child, specialChild);
layout(object);
Copy link
Member

Choose a reason for hiding this comment

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

My understanding is that this layout call doesn't actually execute the paint method of TestMultiClipBehaviorClipRenderObject because the phase parameter of layout defaults to EnginePhase.layout. To execute paint and exercise the code path in _debugCheckForClipBehavior that I am concerned about, can you change this line to:

Suggested change
layout(object);
layout(object, phase: EnginePhase.paint);

I would expect that this test would now fail unexpectedly with the FlutterError thrown in _debugCheckForClipBehavior.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm calling the paint code explicitly in the next line.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've tested this locally and the test still passes - would you prefer I update the test to expect that call returns normally instead of the more explicit call on PaintingContext.paintChild below?

Copy link
Member

Choose a reason for hiding this comment

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

You are of course right. I miss-read the next lines and paint is executed as expected. Apologies for that....

However, I still think the bug is there. Re-reading this test again, I think it is just hidden by the fact that TestMultiClipBehaviorClipRenderObject doesn't implement visitChildren correctly (it does not visit the specialChild). If you fix that and implement visitChildren in TestMultiClipBehaviorClipRenderObject correctly (i.e. as shown below) I am pretty sure that this test will fail.

  @override void visitChildren(RenderObjectVisitor visitor) {
    super.visitChildren(visitor);
    visitor(specialChild);
  }

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hah oops. I'll take a look

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah.

I think the two options are: remove the assert, or make it a (new) rule (that probably no one is breaking?) that RenderObjects must clip using the same clip behavior, and if you really want to clip using multiple clipBehaviors you have to create two separate RenderObjects to do it.

That seems like a slightly confusing rule, but it comes with the benefit of having a better error message when you mess things up in the much more common case of incorrectly applying your one and only clip behavior to children, and we could include details about it in the error message.

if (approximatePaintClip != null) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('A render object pushes a clip $type with clipBehavior Clip.none, but describes a non-null '
'approximate clip for at least one of its children.'),
Copy link
Contributor

Choose a reason for hiding this comment

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

pushed/described?

return PaintingContext(childLayer, bounds);
}

void _debugCheckForClipBehavior(String type) {
Copy link
Contributor

Choose a reason for hiding this comment

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

this might be better named _debugCheckForNoneClipBehavior or some such, i was a bit confused at first as to why it was assuming the clip was "none" until i looked at all the call sites

@dnfield
Copy link
Contributor Author

dnfield commented May 17, 2022

Removed the assert. I think it may be worth seeing if we can put some kind of restriction like this on render objects, but don't want this to get held up by that.

Copy link
Member

@goderbauer goderbauer left a comment

Choose a reason for hiding this comment

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

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

a: accessibility Accessibility, e.g. VoiceOver or TalkBack. (aka a11y) f: scrolling Viewports, list views, slivers, etc. framework flutter/packages/flutter repository. See also f: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants