-
Notifications
You must be signed in to change notification settings - Fork 45
Mutable List interface for children and childNodes
#490
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
base: main
Are you sure you want to change the base?
Conversation
Summary of ChangesHello @fsw, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request enhances the Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
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.
Code Review
This pull request introduces a very useful feature, providing a mutable List interface for childNodes and children, similar to the old dart:html API. The overall approach of using a wrapper class is solid.
My review includes a critical fix for a type safety issue in JSLiveNodeListWrapper that could lead to incorrect behavior when manipulating lists of elements. I've also included a few medium-severity suggestions to improve documentation, robustness, and test coverage.
Regarding your questions:
- Extending
JSImmutableListWrapperis a good approach to reuse code. - Your chosen implementation is more powerful than adding extension methods like
removeWheredirectly, as it provides the full mutableListAPI. - Adding an
asImmutableListfor staticNodeLists is a great idea for API consistency and could be a good follow-up. ListMixinis not deprecated and is the correct tool for this job.
Co-authored-by: Kevin Moore <[email protected]>
For clarification, I am talking about this comments: https://github.com/dart-lang/sdk/blob/9a049d5b2defab33aa953e7882492639ce1f677b/sdk/lib/collection/list.dart#L33 |
srujzs
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.
Does this implementation make sense and matches ideas behind helpers in this module? JSLiveNodeListWrapper don't have to extend JSImmutableListWrapper but have its own implementation but it kinda felt it makes sense this way.
I think so! I'll say that it might better if we started from the dart:html implementations instead. I say this mostly because if users are trying to use these helpers to bridge gaps, divergence in behavior would be painful to discover. See _ChildNodeListLazy and _ChildrenElementList. It's fine if we think some of the dart:html implementation methods need to be updated, though.
could/should NodeList (returned f.e. from querySelector) be extended with asImmutableList via extension? I think it would be more similar to above and dart:html interfaces and JSImmutableListWrapper and JSLiveNodeListWrapper could then be used internally or even marked as private.
Can you elaborate on what you mean here? Do you mean only offer an extension that does the wrapping?
While doing this I noticed ListMixin and ListBase is going to be deprecated in core? Could implementing List interface directly by both those wrappers while making changes here give any benefits?
I don't think they are but those comments are indeed suspect. @lrhn, is this something we should worry about?
Hmm, I was trying to reuse what we have in JSImmutableListWrapper and put both in single implementation.
Precisely. Something like: To have a somehow unified interface. (field is native, fieldAsList implements list, querySelector(X) is native and querySelector(X).asList implements list) |
|
If we have |
srujzs
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.
Hmm, I was trying to reuse what we have in JSImmutableListWrapper and put both in single implementation. But having separate implementation for both cases can indeed be beneficial. I will try to rewrite this.
Yeah, I think they're mildly different enough that splitting it makes sense. Trying to reach parity with dart:html semantics would make migrations easier. I'll re-review whenever you get around to it (no rush).
To have a somehow unified interface. (field is native, fieldAsList implements list, querySelector(X) is native and querySelector(X).asList implements list)
Sure, that's a great goal and will help make these helpers more organized. I'm okay with still exposing the lists for whatever reason a user might want them for (maybe even extending them?) though.
As I'm working on other migrations, I'll clean up the helpers here anyways to make external migrations easier.
If we have List<JSAny?>.toJSProxyOrRef can we have JSArray.toDartProxyOrRef?
That's what toDart more or less already does :D. toJSProxyOrRef is really a performance cliff though, I would be very careful with that.
/// Converts this [JSArray] to a [List] by either casting or wrapping it.
///
/// > [!NOTE]
/// > Depending on whether code is compiled to JavaScript or Wasm, this
/// > conversion will have different semantics.
///
/// When compiling to JavaScript, core [List]s are `Array`s and therefore, if
/// the [JSArray] was already a <code>[List]<T></code> converted via
/// [ListToJSArray.toJS], this getter simply casts the `Array`. Otherwise, it
/// wraps the `Array` with a [List] that casts the elements to [T] to ensure
/// soundness.
///
/// When compiling to Wasm, the [JSArray] is wrapped with a [List]
/// implementation and the wrapper is returned.
///
/// Modifications to this [JSArray] will affect the returned [List] and vice
/// versa.
|
As suggested by @srujzs, I have rewritten this to be based on current implementation in I have added a Notable differences from
|
srujzs
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.
Looking good, thanks! Mostly a set of nits.
For example addAll in case of _ChildrenElementList was handling a case of adding elements from another or same live NodeList differently than _ChildNodeListLazy.
That's reasonable. The one thing that's slightly different when conflating the two is if you're doing is checks for _LiveNodeListMixin. For example HTMLCollectionListWrapper.addAll with a HTMLCollectionListWrapper as an argument would pass that is check, but the strictEquals checks ends up making the behavior the same anyways.
children.clear() was removing all nodes (including text nodes). I assumed this is not expected / bug and in this implementation this will remove only element nodes.
Yeah, that seems wrong. I'd just explain the discrepancy as a comment though because it may be a surprise later.
|
|
||
| @override | ||
| Iterator<Element> get iterator => | ||
| _HTMLCollectionIterator(_parent.firstElementChild); |
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.
nit: Do we need a separate iterator class vs toList().iterator like dart:html does instead?
| P get _parent; | ||
| _JSList<U> get _list; | ||
|
|
||
| bool contains(Object? element) { |
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'm guessing this is an optimization over having to check the contents of the list.
| if (iterable is _LiveNodeListMixin) { | ||
| final otherList = iterable as _LiveNodeListMixin; | ||
| if (otherList._parent.strictEquals(_parent).toDart) { | ||
| throw ArgumentError('Cannot add nodes from same parent'); |
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 is a no-op in _ChildNodeListLazy. Maybe we should return?
| void _filter(bool Function(U element) test, bool removeMatching) { | ||
| // This implementation of removeWhere/retainWhere is more efficient | ||
| // than the default in ListBase. Child nodes can be removed in constant | ||
| // time. |
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 think we can just short-circuit if removeMatching is false?
| NodeListListWrapper(this._parent, this._nodeList); | ||
|
|
||
| @override | ||
| Iterator<Node> get iterator => _NodeListIterator(_parent.firstChild); |
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 comment as above.
dart:htmlallowed modifying child elements and nodes using List interface:This was a pretty clear and handy interface IMHO.
As discussed in this PR dart-lang/site-www#7040 with @srujzs it might be useful to implement something similar in
package:web. This PR is my attempt at doing so.It adds
JSLiveNodeListWrapperthat requires a container node and aHTMLCollectionorNodeListthat is live and implements mutableListmethods.For easier usage it also adds
childrenAsListandchildNodesAsListvia extension so you can do:Questions/Notes:
Does this implementation make sense and matches ideas behind helpers in this module?
JSLiveNodeListWrapperdon't have to extendJSImmutableListWrapperbut have its own implementation but it kinda felt it makes sense this way.Alternatively handy methods like
removeWherecould be added via extension directly toHTMLCollectionandNodeListbut methods likeadd()might not be possible as those require a parent node to add elements to. This would require knowing ifNodeListwas returned fromquerySelectororchildNodesand in further case accessing parent somehow. Also implementing fullListinterface might have other pros.could/should
NodeList(returned f.e. fromquerySelector) be extended withasImmutableListvia extension? I think it would be more similar to above anddart:htmlinterfaces andJSImmutableListWrapperandJSLiveNodeListWrappercould then be used internally or even marked as private.While doing this I noticed
ListMixinandListBaseis going to be deprecated in core? Could implementingListinterface directly by both those wrappers while making changes here give any benefits?