Largetable benchmark with `interpolationAttr` and 200 rows / 20 columns: Time for destroy/create pair dropped from about 1260ms to about 150ms. Related to #2298, but does not really fix it as we are still slow if people are using `<content>`. Closes #2297
152 lines
4.6 KiB
TypeScript
152 lines
4.6 KiB
TypeScript
import {DOM} from 'angular2/src/dom/dom_adapter';
|
|
import {List, ListWrapper} from 'angular2/src/facade/collection';
|
|
import {isBlank, isPresent} from 'angular2/src/facade/lang';
|
|
|
|
import * as viewModule from '../view/view';
|
|
import {Content} from './content_tag';
|
|
|
|
export class DestinationLightDom {}
|
|
|
|
class _Root {
|
|
node;
|
|
boundElementIndex: number;
|
|
|
|
constructor(node, boundElementIndex) {
|
|
this.node = node;
|
|
this.boundElementIndex = boundElementIndex;
|
|
}
|
|
}
|
|
|
|
// TODO: LightDom should implement DestinationLightDom
|
|
// once interfaces are supported
|
|
export class LightDom {
|
|
// The light DOM of the element is enclosed inside the lightDomView
|
|
lightDomView: viewModule.DomView;
|
|
// The shadow DOM
|
|
shadowDomView: viewModule.DomView;
|
|
// The nodes of the light DOM
|
|
nodes: List</*node*/ any>;
|
|
private _roots: List<_Root>;
|
|
|
|
constructor(lightDomView: viewModule.DomView, element) {
|
|
this.lightDomView = lightDomView;
|
|
this.nodes = DOM.childNodesAsList(element);
|
|
|
|
this._roots = null;
|
|
this.shadowDomView = null;
|
|
}
|
|
|
|
attachShadowDomView(shadowDomView: viewModule.DomView) { this.shadowDomView = shadowDomView; }
|
|
|
|
detachShadowDomView() { this.shadowDomView = null; }
|
|
|
|
redistribute() { redistributeNodes(this.contentTags(), this.expandedDomNodes()); }
|
|
|
|
contentTags(): List<Content> {
|
|
if (isPresent(this.shadowDomView)) {
|
|
return this._collectAllContentTags(this.shadowDomView, []);
|
|
} else {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
// Collects the Content directives from the view and all its child views
|
|
private _collectAllContentTags(view: viewModule.DomView, acc: List<Content>): List<Content> {
|
|
// Note: exiting early here is important as we call this function for every view
|
|
// that is added, so we have O(n^2) runtime.
|
|
// TODO(tbosch): fix the root problem, see
|
|
// https://github.com/angular/angular/issues/2298
|
|
if (view.proto.transitiveContentTagCount === 0) {
|
|
return acc;
|
|
}
|
|
var contentTags = view.contentTags;
|
|
var vcs = view.viewContainers;
|
|
for (var i = 0; i < vcs.length; i++) {
|
|
var vc = vcs[i];
|
|
var contentTag = contentTags[i];
|
|
if (isPresent(contentTag)) {
|
|
ListWrapper.push(acc, contentTag);
|
|
}
|
|
if (isPresent(vc)) {
|
|
ListWrapper.forEach(vc.contentTagContainers(),
|
|
(view) => { this._collectAllContentTags(view, acc); });
|
|
}
|
|
}
|
|
return acc;
|
|
}
|
|
|
|
// Collects the nodes of the light DOM by merging:
|
|
// - nodes from enclosed ViewContainers,
|
|
// - nodes from enclosed content tags,
|
|
// - plain DOM nodes
|
|
expandedDomNodes(): List</*node*/ any> {
|
|
var res = [];
|
|
|
|
var roots = this._findRoots();
|
|
for (var i = 0; i < roots.length; ++i) {
|
|
var root = roots[i];
|
|
if (isPresent(root.boundElementIndex)) {
|
|
var vc = this.lightDomView.viewContainers[root.boundElementIndex];
|
|
var content = this.lightDomView.contentTags[root.boundElementIndex];
|
|
if (isPresent(vc)) {
|
|
res = ListWrapper.concat(res, vc.nodes());
|
|
} else if (isPresent(content)) {
|
|
res = ListWrapper.concat(res, content.nodes());
|
|
} else {
|
|
ListWrapper.push(res, root.node);
|
|
}
|
|
} else {
|
|
ListWrapper.push(res, root.node);
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
// Returns a list of Roots for all the nodes of the light DOM.
|
|
// The Root object contains the DOM node and its corresponding boundElementIndex
|
|
private _findRoots() {
|
|
if (isPresent(this._roots)) return this._roots;
|
|
|
|
var boundElements = this.lightDomView.boundElements;
|
|
|
|
this._roots = ListWrapper.map(this.nodes, (n) => {
|
|
var boundElementIndex = null;
|
|
for (var i = 0; i < boundElements.length; i++) {
|
|
var boundEl = boundElements[i];
|
|
if (isPresent(boundEl) && boundEl === n) {
|
|
boundElementIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
return new _Root(n, boundElementIndex);
|
|
});
|
|
|
|
return this._roots;
|
|
}
|
|
}
|
|
|
|
// Projects the light DOM into the shadow DOM
|
|
function redistributeNodes(contents: List<Content>, nodes: List</*node*/ any>) {
|
|
for (var i = 0; i < contents.length; ++i) {
|
|
var content = contents[i];
|
|
var select = content.select;
|
|
|
|
// Empty selector is identical to <content/>
|
|
if (select.length === 0) {
|
|
content.insert(ListWrapper.clone(nodes));
|
|
ListWrapper.clear(nodes);
|
|
} else {
|
|
var matchSelector = (n) => DOM.elementMatches(n, select);
|
|
var matchingNodes = ListWrapper.filter(nodes, matchSelector);
|
|
content.insert(matchingNodes);
|
|
ListWrapper.removeAll(nodes, matchingNodes);
|
|
}
|
|
}
|
|
for (var i = 0; i < nodes.length; i++) {
|
|
var node = nodes[i];
|
|
if (isPresent(node.parentNode)) {
|
|
DOM.remove(nodes[i]);
|
|
}
|
|
}
|
|
}
|