fix(shadowdom): remove unused nodes on redistribute
Previously, light dom nodes that were not used by any content tag were not removed from a view on redistribute. This lead to a bug when reusing a view from the view pool, as it still contained stale reprojected nodes. Fixes #1416
This commit is contained in:
parent
02997f473a
commit
64ad74acbe
@ -34,7 +34,6 @@ export class EmulatedUnscopedShadowDomStrategy extends ShadowDomStrategy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
attachTemplate(el, view:viewModule.RenderView) {
|
attachTemplate(el, view:viewModule.RenderView) {
|
||||||
DOM.clearNodes(el);
|
|
||||||
moveViewNodesIntoParent(el, view);
|
moveViewNodesIntoParent(el, view);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,10 +37,7 @@ export class LightDom {
|
|||||||
}
|
}
|
||||||
|
|
||||||
redistribute() {
|
redistribute() {
|
||||||
var tags = this.contentTags();
|
redistributeNodes(this.contentTags(), this.expandedDomNodes());
|
||||||
if (tags.length > 0) {
|
|
||||||
redistributeNodes(tags, this.expandedDomNodes());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
contentTags(): List<Content> {
|
contentTags(): List<Content> {
|
||||||
@ -122,16 +119,22 @@ function redistributeNodes(contents:List<Content>, nodes:List) {
|
|||||||
for (var i = 0; i < contents.length; ++i) {
|
for (var i = 0; i < contents.length; ++i) {
|
||||||
var content = contents[i];
|
var content = contents[i];
|
||||||
var select = content.select;
|
var select = content.select;
|
||||||
var matchSelector = (n) => DOM.elementMatches(n, select);
|
|
||||||
|
|
||||||
// Empty selector is identical to <content/>
|
// Empty selector is identical to <content/>
|
||||||
if (select.length === 0) {
|
if (select.length === 0) {
|
||||||
content.insert(nodes);
|
content.insert(ListWrapper.clone(nodes));
|
||||||
ListWrapper.clear(nodes);
|
ListWrapper.clear(nodes);
|
||||||
} else {
|
} else {
|
||||||
|
var matchSelector = (n) => DOM.elementMatches(n, select);
|
||||||
var matchingNodes = ListWrapper.filter(nodes, matchSelector);
|
var matchingNodes = ListWrapper.filter(nodes, matchSelector);
|
||||||
content.insert(matchingNodes);
|
content.insert(matchingNodes);
|
||||||
ListWrapper.removeAll(nodes, 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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,14 +46,13 @@ export function main() {
|
|||||||
|
|
||||||
it('should attach the view nodes as child of the host element', () => {
|
it('should attach the view nodes as child of the host element', () => {
|
||||||
var host = el('<div><span>original content</span></div>');
|
var host = el('<div><span>original content</span></div>');
|
||||||
|
var originalChild = DOM.childNodes(host)[0];
|
||||||
var nodes = el('<div>view</div>');
|
var nodes = el('<div>view</div>');
|
||||||
var view = new RenderView(null, [nodes], [], [], []);
|
var view = new RenderView(null, [nodes], [], [], []);
|
||||||
|
|
||||||
strategy.attachTemplate(host, view);
|
strategy.attachTemplate(host, view);
|
||||||
var firstChild = DOM.firstChild(host);
|
expect(DOM.childNodes(host)[0]).toBe(originalChild);
|
||||||
expect(DOM.tagName(firstChild).toLowerCase()).toEqual('div');
|
expect(DOM.childNodes(host)[1]).toBe(nodes);
|
||||||
expect(firstChild).toHaveText('view');
|
|
||||||
expect(host).toHaveText('view');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should rewrite style urls', () => {
|
it('should rewrite style urls', () => {
|
||||||
|
@ -41,14 +41,13 @@ export function main() {
|
|||||||
|
|
||||||
it('should attach the view nodes as child of the host element', () => {
|
it('should attach the view nodes as child of the host element', () => {
|
||||||
var host = el('<div><span>original content</span></div>');
|
var host = el('<div><span>original content</span></div>');
|
||||||
|
var originalChild = DOM.childNodes(host)[0];
|
||||||
var nodes = el('<div>view</div>');
|
var nodes = el('<div>view</div>');
|
||||||
var view = new RenderView(null, [nodes], [], [], []);
|
var view = new RenderView(null, [nodes], [], [], []);
|
||||||
|
|
||||||
strategy.attachTemplate(host, view);
|
strategy.attachTemplate(host, view);
|
||||||
var firstChild = DOM.firstChild(host);
|
expect(DOM.childNodes(host)[0]).toBe(originalChild);
|
||||||
expect(DOM.tagName(firstChild).toLowerCase()).toEqual('div');
|
expect(DOM.childNodes(host)[1]).toBe(nodes);
|
||||||
expect(firstChild).toHaveText('view');
|
|
||||||
expect(host).toHaveText('view');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should rewrite style urls', () => {
|
it('should rewrite style urls', () => {
|
||||||
|
@ -84,7 +84,7 @@ class FakeContentTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
insert(nodes){
|
insert(nodes){
|
||||||
this._nodes = ListWrapper.clone(nodes);
|
this._nodes = nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
nodes() {
|
nodes() {
|
||||||
@ -215,6 +215,30 @@ export function main() {
|
|||||||
expect(toHtml(wildcard.nodes())).toEqual(["<a>1</a>", "<b>2</b>", "<a>3</a>"]);
|
expect(toHtml(wildcard.nodes())).toEqual(["<a>1</a>", "<b>2</b>", "<a>3</a>"]);
|
||||||
expect(toHtml(contentB.nodes())).toEqual([]);
|
expect(toHtml(contentB.nodes())).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should remove all nodes if there are no content tags", () => {
|
||||||
|
var lightDomEl = el("<div><a>1</a><b>2</b><a>3</a></div>")
|
||||||
|
|
||||||
|
var lightDom = new LightDom(lightDomView, new FakeView([]), lightDomEl);
|
||||||
|
|
||||||
|
lightDom.redistribute();
|
||||||
|
|
||||||
|
expect(DOM.childNodes(lightDomEl).length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should remove all not projected nodes", () => {
|
||||||
|
var lightDomEl = el("<div><a>1</a><b>2</b><a>3</a></div>");
|
||||||
|
var bNode = DOM.childNodes(lightDomEl)[1];
|
||||||
|
|
||||||
|
var lightDom = new LightDom(lightDomView, new FakeView([
|
||||||
|
new FakeContentTag(null, "a")
|
||||||
|
]), lightDomEl);
|
||||||
|
|
||||||
|
lightDom.redistribute();
|
||||||
|
|
||||||
|
expect(bNode.parentNode).toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -54,10 +54,11 @@ export function main() {
|
|||||||
|
|
||||||
var testbed, renderer, rootEl, compile, compileRoot;
|
var testbed, renderer, rootEl, compile, compileRoot;
|
||||||
|
|
||||||
function createRenderer({templates}) {
|
function createRenderer({templates, viewCacheCapacity}) {
|
||||||
testbed = new IntegrationTestbed({
|
testbed = new IntegrationTestbed({
|
||||||
shadowDomStrategy: strategyFactory(),
|
shadowDomStrategy: strategyFactory(),
|
||||||
templates: ListWrapper.concat(templates, componentTemplates)
|
templates: ListWrapper.concat(templates, componentTemplates),
|
||||||
|
viewCacheCapacity: viewCacheCapacity
|
||||||
});
|
});
|
||||||
renderer = testbed.renderer;
|
renderer = testbed.renderer;
|
||||||
compileRoot = (rootEl) => testbed.compileRoot(rootEl);
|
compileRoot = (rootEl) => testbed.compileRoot(rootEl);
|
||||||
@ -87,6 +88,25 @@ export function main() {
|
|||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
it('should not show the light dom event if there is not content tag', inject([AsyncTestCompleter], (async) => {
|
||||||
|
createRenderer({
|
||||||
|
templates: [new ViewDefinition({
|
||||||
|
componentId: 'main',
|
||||||
|
template: '<empty>' +
|
||||||
|
'<div>A</div>' +
|
||||||
|
'</empty>',
|
||||||
|
directives: [empty]
|
||||||
|
})]
|
||||||
|
});
|
||||||
|
compileRoot('main').then( (pv) => {
|
||||||
|
renderer.createInPlaceHostView(null, rootEl, pv.render);
|
||||||
|
|
||||||
|
expect(rootEl).toHaveText('');
|
||||||
|
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('should support dynamic components', inject([AsyncTestCompleter], (async) => {
|
it('should support dynamic components', inject([AsyncTestCompleter], (async) => {
|
||||||
createRenderer({
|
createRenderer({
|
||||||
templates: [new ViewDefinition({
|
templates: [new ViewDefinition({
|
||||||
@ -289,6 +309,46 @@ export function main() {
|
|||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
it("should support tabs with view caching", inject([AsyncTestCompleter], (async) => {
|
||||||
|
createRenderer({
|
||||||
|
templates: [new ViewDefinition({
|
||||||
|
componentId: 'main',
|
||||||
|
template:
|
||||||
|
'(<tab><span>0</span></tab>'+
|
||||||
|
'<tab><span>1</span></tab>'+
|
||||||
|
'<tab><span>2</span></tab>)',
|
||||||
|
directives: [tabComponent]
|
||||||
|
})],
|
||||||
|
viewCacheCapacity: 5
|
||||||
|
});
|
||||||
|
compileRoot('main').then( (pv) => {
|
||||||
|
var viewRefs = renderer.createInPlaceHostView(null, rootEl, pv.render);
|
||||||
|
var vcRef0 = new ViewContainerRef(viewRefs[2], 0);
|
||||||
|
var vcRef1 = new ViewContainerRef(viewRefs[3], 0);
|
||||||
|
var vcRef2 = new ViewContainerRef(viewRefs[4], 0);
|
||||||
|
var mainPv = pv.elementBinders[0].nestedProtoView;
|
||||||
|
var pvRef = mainPv.elementBinders[0].nestedProtoView.elementBinders[0].nestedProtoView.render;
|
||||||
|
|
||||||
|
expect(rootEl).toHaveText('()');
|
||||||
|
|
||||||
|
renderer.createViewInContainer(vcRef0, 0, pvRef);
|
||||||
|
|
||||||
|
expect(rootEl).toHaveText('(TAB(0))');
|
||||||
|
|
||||||
|
renderer.destroyViewInContainer(vcRef0, 0);
|
||||||
|
renderer.createViewInContainer(vcRef1, 0, pvRef);
|
||||||
|
|
||||||
|
expect(rootEl).toHaveText('(TAB(1))');
|
||||||
|
|
||||||
|
renderer.destroyViewInContainer(vcRef1, 0);
|
||||||
|
renderer.createViewInContainer(vcRef2, 0, pvRef);
|
||||||
|
|
||||||
|
expect(rootEl).toHaveText('(TAB(2))');
|
||||||
|
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
//Implement once NgElement support changing a class
|
//Implement once NgElement support changing a class
|
||||||
//it("should redistribute when a class has been added or removed");
|
//it("should redistribute when a class has been added or removed");
|
||||||
//it('should not lose focus', () => {
|
//it('should not lose focus', () => {
|
||||||
@ -318,6 +378,12 @@ var simple = new DirectiveMetadata({
|
|||||||
type: DirectiveMetadata.COMPONENT_TYPE
|
type: DirectiveMetadata.COMPONENT_TYPE
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var empty = new DirectiveMetadata({
|
||||||
|
selector: 'empty',
|
||||||
|
id: 'empty',
|
||||||
|
type: DirectiveMetadata.COMPONENT_TYPE
|
||||||
|
});
|
||||||
|
|
||||||
var dynamicComponent = new DirectiveMetadata({
|
var dynamicComponent = new DirectiveMetadata({
|
||||||
selector: 'dynamic',
|
selector: 'dynamic',
|
||||||
id: 'dynamic',
|
id: 'dynamic',
|
||||||
@ -372,12 +438,29 @@ var autoViewportDirective = new DirectiveMetadata({
|
|||||||
type: DirectiveMetadata.VIEWPORT_TYPE
|
type: DirectiveMetadata.VIEWPORT_TYPE
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var tabGroupComponent = new DirectiveMetadata({
|
||||||
|
selector: 'tab-group',
|
||||||
|
id: 'tab-group',
|
||||||
|
type: DirectiveMetadata.COMPONENT_TYPE
|
||||||
|
});
|
||||||
|
|
||||||
|
var tabComponent = new DirectiveMetadata({
|
||||||
|
selector: 'tab',
|
||||||
|
id: 'tab',
|
||||||
|
type: DirectiveMetadata.COMPONENT_TYPE
|
||||||
|
});
|
||||||
|
|
||||||
var componentTemplates = [
|
var componentTemplates = [
|
||||||
new ViewDefinition({
|
new ViewDefinition({
|
||||||
componentId: 'simple',
|
componentId: 'simple',
|
||||||
template: 'SIMPLE(<content></content>)',
|
template: 'SIMPLE(<content></content>)',
|
||||||
directives: []
|
directives: []
|
||||||
}),
|
}),
|
||||||
|
new ViewDefinition({
|
||||||
|
componentId: 'empty',
|
||||||
|
template: '',
|
||||||
|
directives: []
|
||||||
|
}),
|
||||||
new ViewDefinition({
|
new ViewDefinition({
|
||||||
componentId: 'multiple-content-tags',
|
componentId: 'multiple-content-tags',
|
||||||
template: '(<content select=".left"></content>, <content></content>)',
|
template: '(<content select=".left"></content>, <content></content>)',
|
||||||
@ -407,5 +490,15 @@ var componentTemplates = [
|
|||||||
componentId: 'conditional-content',
|
componentId: 'conditional-content',
|
||||||
template: '<div>(<div *auto="cond"><content select=".left"></content></div>, <content></content>)</div>',
|
template: '<div>(<div *auto="cond"><content select=".left"></content></div>, <content></content>)</div>',
|
||||||
directives: [autoViewportDirective]
|
directives: [autoViewportDirective]
|
||||||
|
}),
|
||||||
|
new ViewDefinition({
|
||||||
|
componentId: 'tab-group',
|
||||||
|
template: 'GROUP(<content></content>)',
|
||||||
|
directives: []
|
||||||
|
}),
|
||||||
|
new ViewDefinition({
|
||||||
|
componentId: 'tab',
|
||||||
|
template: '<div><div *auto="cond">TAB(<content></content>)</div></div>',
|
||||||
|
directives: [autoViewportDirective]
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
|
Loading…
x
Reference in New Issue
Block a user