-
Notifications
You must be signed in to change notification settings - Fork 6k
[web] Glue code for CanvasKit Chromium #39592
Conversation
yjbanov
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also recommend running an A/B test using some of our text benchmarks.
| @override | ||
| Uint32List fragment() { | ||
| final DomIteratorWrapper<DomSegment> iterator = | ||
| createIntlSegmenter(granularity: _granularityString) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same question here. Is IntlSegmenter stateful? Can we memoize one for each granularity and avoid creating them on every string? This will become more relevant with wasm, where we probably want to avoid too many wasm-js hops.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
Regarding the wasm thing: I don't think this memoization makes a difference. The majority of chatting with js happens after creating the Intl.Segmenter (calling segment() and iterating over the result). If it becomes an issue, we can implement a JS function that wraps this entire block of code and does all the chatting on the JS side and returns the final result (i.e. Uint32List) to wasm. Not sure how this can be done in practice with dart2wasm.
cc @eyebrowsoffire to keep in mind that this might be needed for the skwasm project. It would be nice to have some kind of annotation on methods to tell the compiler that it's better to keep the entire method in JS and not compile it to wasm. I may write some thoughts around this in a new issue.
| final List<LineBreakFragment> fragments = _fragmenter.fragment(); | ||
|
|
||
| final int size = (fragments.length + 1) * 2; | ||
| final Uint32List typedArray = Uint32List(size); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider using CanvasKit.Malloc to avoid creating a temporary array and extra copying into wasm.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Up until today, I didn't really understand the benefit of using CK's Malloc. It seemed to me like the CK bindings always end up copying the arrays anyway.
But I took a closer look now, and I think I understand where this optimization is happening on the CK side:
https://github.com/google/skia/blob/deb1cf8d127986fee464946a12b248d6f17fb629/modules/canvaskit/memory.js#L126-L128
What threw me off was the name copy1dArray. But it turns out this function doesn't always copy. It first checks if the array was malloc'd and avoids the copying in that case.
lib/web_ui/lib/src/engine/dom.dart
Outdated
|
|
||
| /// Similar to [js_util.callMethod] but allows for providing a non-string method | ||
| /// name. | ||
| T jsCallMethod<T>(Object o, Object method, [List<Object?> args = const <Object?>[]]) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this be moved to safe_browser_api.dart and wrapped as to not be publicly available to all of the engine code?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it's too difficult to move, can it stay private to this file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand the benefit of making it private, so I'll make it private.
Would you mind clarifying what's the benefit of moving it to safe_browser_api.dart?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function can execute arbitrary code. It's good to avoid ad hoc calls out to JS from all over the codebase. So libraries like dom.dart and safe_browser_api.dart should provide a safe subset of the API to the rest of the engine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with making it private so it doesn't get called everywhere.
Does it make a difference if we put it in dom.dart vs safe_browser_api.dart? Like is there a special handling for that file or something?
Agreed. @eyebrowsoffire is working on a PR to run tests against skwasm as well as the chromium variant of CanvasKit. That said, all of this code is behind a flag that's not enabled yet, but we definitely need to land those tests before opening this up to users. |
| /// Builds the CkParagraph with the builder and deletes the builder. | ||
| SkParagraph _buildSkParagraph() { | ||
| if (browserSupportsCanvaskitChromium) { | ||
| final String text = _paragraphBuilder.getText(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need to read the text string back? Don't we already have it?
Now that I'm looking at the Skia API, it feels a little too chatty. I feel like we should be able to provide all this information - text, word breaks, graphemes, line breaks - in a single method call.
But maybe we can revisit this later. If this works, let's keep it. It's not public API anyway. Or maybe I just need some likbez on why this should be this way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For anyone curious: https://en.wikipedia.org/wiki/Likbez
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @hterkelsen for the link 😄 I was wondering about that word myself, and thought it could've been a typo.
The reason we need to ask Skia to give us the string is because SkParagraphBuilder inserts extra text into the string. E.g. when we call addPlaceholder(...), SkParagraphBuilder inserts a magical character into the string (and this is an implementation detail that could change or even go away in the future).
A little bit of history to explain how the API became chatty:
Initially, we had a single method called buildWithClientInfo() (to be called instead of build()) which takes parameters for all the information it needs (i.e. line breaks, word breaks, graphemes, (and bidi at the time)).
Then we started playing with ICU4X for the bidi information. ICU4X operates on UTF8, but browser APIs operate on UTF16. So we needed a way to indicate to Skia whether the array we are passing is UTF8 or UTF16 indices. That's how the set***Utf8() and set***Utf16() APIs were born.
yjbanov
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
harryterkelsen
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
joshualitt
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
JS Interop LGTM

This PR provides all client-ICU data to CanvasKit from browser APIs:
v8BreakIterator(only available in Chromium).Intl.Segmenter(available in all browsers except Firefox).Intl.Segmenter(available in all browsers except Firefox).Fixes flutter/flutter#118801