[html] Better code for Element / _ChildrenElementList method

Two tricks let us compile this more efficiently:

    e.children.addAll(cs);

Normally, get$children is inlined, returning a _ChildrenElementList
wrapper, generating:

     new W._ChildrenElementList(e, e.children).addAll$1(0, cs);

The two tricks:

1. Split _ChildElementList.addAll into an 'unwrap' that then calls the
   logic in '_addAll'

2. Add information about the properties of e.children that allow it to
   be removed.

With these tricks, dart2js can optimize the code to this version that
avoids allocating a wrapper or accessing the 'children' property:

     W._ChildrenElementList__addAll(e, cs);

Change-Id: Ifdf533ac4f9790f09f87302e67304b5696097266
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/153904
Reviewed-by: Srujan Gaddam <srujzs@google.com>
Commit-Queue: Stephen Adams <sra@google.com>
This commit is contained in:
Stephen Adams 2020-07-13 20:33:19 +00:00 committed by commit-bot@chromium.org
parent fdf4e364bf
commit f997d62a6d
3 changed files with 44 additions and 8 deletions

View file

@ -11722,6 +11722,10 @@ class _ChildrenElementList extends ListBase<Element>
Iterator<Element> get iterator => toList().iterator;
void addAll(Iterable<Element> iterable) {
_addAll(_element, iterable);
}
static void _addAll(Element _element, Iterable<Element> iterable) {
if (iterable is _ChildNodeListLazy) {
iterable = new List.from(iterable);
}
@ -11775,6 +11779,10 @@ class _ChildrenElementList extends ListBase<Element>
}
bool remove(Object? object) {
return _remove(_element, object);
}
static bool _remove(Element _element, Object? object) {
if (object is Element) {
Element element = object;
if (identical(element.parentNode, _element)) {
@ -11823,7 +11831,10 @@ class _ChildrenElementList extends ListBase<Element>
return result;
}
Element get first {
Element get first => _first(_element);
@pragma('dart2js:noInline')
static Element _first(Element _element) {
Element? result = _element._firstElementChild;
if (result == null) throw new StateError("No elements");
return result;
@ -12965,6 +12976,16 @@ class Element extends Node
*/
List<Element> get children => new _ChildrenElementList._wrap(this);
List<Node> get _children =>
// Element.children always returns the same list-like object which is a
// live view on the underlying DOM tree. So we can GVN it and remove it if
// unused.
JS(
'returns:HtmlCollection;creates:HtmlCollection;'
'depends:none;effects:none;gvn:true',
'#.children',
this);
set children(List<Element> value) {
// Copy list first since we don't want liveness during iteration.
var copy = value.toList();
@ -14824,11 +14845,6 @@ class Element extends Node
@JSName('childElementCount')
int get _childElementCount native;
@JSName('children')
@Returns('HtmlCollection')
@Creates('HtmlCollection')
List<Node> get _children native;
@JSName('firstElementChild')
Element? get _firstElementChild native;

View file

@ -387,7 +387,6 @@ private_html_members = monitored.Set(
# Not prefixed but requires custom implementation for cross-browser compatibility.
'Document.visibilityState',
'Element.animate',
'Element.children',
'Element.childElementCount',
'Element.firstElementChild',
'Element.getClientRects',
@ -791,6 +790,7 @@ removed_html_members = monitored.Set(
'DOMException.WRONG_DOCUMENT_ERR',
'Element.accessKey',
'Element.append',
'Element.children',
'Element.dataset',
'Element.get:classList',
'Element.getAttributeNode',

View file

@ -46,6 +46,10 @@ class _ChildrenElementList extends ListBase<Element>
Iterator<Element> get iterator => toList().iterator;
void addAll(Iterable<Element> iterable) {
_addAll(_element, iterable);
}
static void _addAll(Element _element, Iterable<Element> iterable) {
if (iterable is _ChildNodeListLazy) {
iterable = new List.from(iterable);
}
@ -99,6 +103,10 @@ class _ChildrenElementList extends ListBase<Element>
}
bool remove(Object$NULLABLE object) {
return _remove(_element, object);
}
static bool _remove(Element _element, Object$NULLABLE object) {
if (object is Element) {
Element element = object;
if (identical(element.parentNode, _element)) {
@ -149,7 +157,10 @@ class _ChildrenElementList extends ListBase<Element>
return result;
}
Element get first {
Element get first => _first(_element);
@pragma('dart2js:noInline')
static Element _first(Element _element) {
Element$NULLABLE result = _element._firstElementChild;
if (result == null) throw new StateError("No elements");
return result;
@ -667,6 +678,15 @@ $endif
*/
List<Element> get children => new _ChildrenElementList._wrap(this);
List<Node> get _children =>
// Element.children always returns the same list-like object which is a
// live view on the underlying DOM tree. So we can GVN it and remove it if
// unused.
JS('returns:HtmlCollection;creates:HtmlCollection;'
'depends:none;effects:none;gvn:true',
'#.children',
this);
set children(List<Element> value) {
// Copy list first since we don't want liveness during iteration.
var copy = value.toList();