Improve MouseTracker lifecycle: Move checks to post-frame#44631
Improve MouseTracker lifecycle: Move checks to post-frame#44631dkwingsmt merged 63 commits intoflutter:masterfrom
Conversation
|
Why can't there just be Also, when would I want to listen to |
|
Looks like cirrus is unhappy on this PR. |
| // | ||
| // The old states are collected on `oldWidget` if it is provided, or the | ||
| // current widget (before doing `task`) otherwise. | ||
| void _mayTriggerCallback({VoidCallback task, FocusableActionDetector oldWidget}) { |
There was a problem hiding this comment.
I think task here can just be a positional argument.
There was a problem hiding this comment.
The _mayTriggerCallback called in didUpdateWidget does not have a task, but an oldWidget instead.
| final bool didShowHoverHighlight = _shouldShowHoverHighlight(target: oldTarget); | ||
| final bool didShowFocusHighlight = _shouldShowFocusHighlight(target: oldTarget); | ||
| if (task != null) | ||
| task(); |
There was a problem hiding this comment.
If the task is calling setState, isn't it possible that it won't actually execute the contents of the setState function arg right away? Although, I'm not sure anything needs to call setState in the task anymore, so it may be moot.
There was a problem hiding this comment.
I think according to the doc of setState the content is run synchronously.
| _handleShowFocusHighlight(); | ||
| widget.onFocusChange?.call(_focused); | ||
| _mayTriggerCallback(task: () { | ||
| setState(() { |
There was a problem hiding this comment.
Now that I look at this, I'm not sure that it even needs to call setState, since it doesn't use the value of _focused in its build function.
There was a problem hiding this comment.
I was thinking about this too, although setState does mark its children as dirty, so you might want this?
There was a problem hiding this comment.
Also, if none of the properties are states, is there really a reason to make FocusableActionDetector a widget any more?
There was a problem hiding this comment.
No, I think that it definitely is a good convenience widget to combine several concepts for desktop support, so it still has value.
|
Looks like microbenchmarks stock_layout_iteration regressed on this commit. |
|
@Hixie I'm taking a look |
Description
This PR rewrites MouseTracker's lifecycle, so that mouse callbacks are all triggered in post frame, instead of the current one where some are triggered during the build phase. This change fixes a few issues of mouse events without increasing the frames it takes for a change to take effect.
This PR also changes the
onExitcallback toMouseRegion,RenderMouseRegion, andMouseTrackerAnnotation, so that it is no longer triggered on dispose, which is a breaking change.Background
Currently MouseTracker's collection process happens in the following way:
setStateduringbuild)This process has the following issues:
Element.deactivateandElement.activaterespectively, which can be called when the element is simply moved to a different position of the render tree without actual location change. The result is that theonExitandonEnterof the same annotation can be called back-to-back within a single frame, which is counter-intuitive. This fix is reflected by this test.setStateduring build. Although this doesn't cause crash through the delicate bypass, it is neither an intended design, nor is this complex process beneficial, since the effect of the state change will no go live until the next frame anyway (this test passes both before and after PR). Moreover, detaching attachment can still cause crash (this test fails before PR).Changes
MouseTracker's lifecycle
In this PR MouseTracker's collection process happens in the following way:
It fixes the aforementioned issues because:
setStateare called either in a post-frame callback, or in the immediate callback of a pointer event, which is also post-frame. In fact, this PR adds an assertion to make sure the collection does not take place in the drawing phase.As a result of this change,
attachAnnotationanddetachAnnotationno longer triggers any immediate effects, but serve as a state keeper that keeps "whether an annotation is attached" and "whether the owner object of the annotation is mounted" in sync, which is used in the next section.The change to onExit
Since an annotation's
onExitis now officially called after the annotation's removal, it's extremely easy to callsetStatein it and cause exceptions, because an annotation's exit can be triggered by unmounting its owner widget, which means we're essentially callingsetStateinWidget.dispose. However, we have to consider that it's a necessary demand and popular code pattern to callsetStateinonExit.As a compromise, this PR changes
onExitso that it is only called when the annotation and the pointer stops contact while the annotation is still attached. Despite the breakage, some code will not be affected by this change, while most of the others need a small degree of migration. For more information, see #44957.TestGesture maintains "added"
This PR also changes
TestGestureso that it keeps track ofPointerAddedEvents andPointerRemovedEvents, so that when an event is called on a gesture that has not added pointer,This is because many tests are written without the proper calling sequence of events, which is heavily relied upon by
MouseTracker. Instead of fixing all the tests (including those of third-party), maybe it's better to make testing utilities safer.Related Issues
Tests
I added the following tests:
Checklist
Before you create this PR confirm that it meets all requirements listed below by checking the relevant checkboxes (
[x]). This will ensure a smooth and quick review process.///).flutter analyze --flutter-repo) does not report any problems on my PR.Breaking Change
Does your PR require Flutter developers to manually update their apps to accommodate your change?