Semantics in Flutter - under the hood
Thursday, 30 November 2023
This post is WIP. I might or might not come back to this rabbit hole and finish it.
Findings:
SemanticsNodealready has a Key in it!- Could it just be uploaded to the engine?
- Nah, because this key is associated with the
Semanticsitself???
- PRs that added support for
tooltipin semantics. Something similar could be done withidentifier.- framework part https://github.com/flutter/flutter/pull/87684
- engine part
- relanded https://github.com/flutter/engine/pull/28211
- PR that optimized semantics. Has some stats about how many “semantics” there are in big apps.
How does semantics flow from the Framework to the Engine?
In the framework
- Developer creates a
Semanticswidget, passesSemanticsPropertiesto it - The Semantics widget creates its render object – RenderSemanticsAnnotations
- The
RenderSemanticsAnnotationsrender object callsmarkNeedsSemanticsUpdate(). This method is inherited from the RenderObject abstract class markNeedsSemanticsUpdate()adds the render object toPipelineOwner._nodesNeedingSemantics.
Also, every RenderObject manages its own SemanticConfiguration.
- when somebody wants the
RenderObjectto tell them about its semantics, it callsdescribeSemanticsConfigurationand passes (TODO)
At some later point, a new frame is scheduled by the engine. This is handled by
RendererBinding in the
persistent frame callback
that
it registered.
This callback calls RendererBinding’s drawFrame(), which drives
the frame rendering pipeline. In one of the last stages of this pipeline, it
callsPipelineOwner.flushSemantics().
This gathers _nodesNeedingSemantics.
Finally, SemanticsOwner.sendSemanticsUpdate()
is called.
This function does (quite some stuff), but most importantly, it finally
calls
SemanticsUpdateBuilder.build() to obtain a
SemanticsUpdate.
💡 SemanticsUpdateBuilder is an abstract class from the engine. It’s implemented in C++ (header) (source).
Then, the
SemanticsOwner.onSemanticsUpdate
callback is called and the SemanticsUpdate is passed to it as an argument.
(omitted some calls)
Then that SemanticsUpdate is passed to the engine by
calling
RenderView.updateSemantics().
In the engine
TODO, but generally:
- first, the SemanticsUpdate lands in Engine’s C++ code, and then
- it is delivered to the appropriate embedder APIs
- on Android, JNI is heavily used
Naming observations
- Methods that start with “handle”, e.g.
handlePlatformBrightnessChanged, are callbacks to be run when the engine says so
What happens when accessibility is enabled?
“how Flutter learns that it has to start sending accessibility info”
iOS-specific
-
PlatformConfiguration.cc | UpdateSemanticsEnabled (crossing C++/Dart boundary)
-
This callbacks gets invoked: PlatformDispatcher.onSemanticsEnabledChanged
-
That callback is set up by
SemanticsBinding: link
The end :)