refactor(render): add `DomElement`

Replaces the multiple arrays of `DomView`
by a single array with `DomElement`s.

Note: this commit does not show a performance regression
(tested against the tree benchmark locally).
This commit is contained in:
Tobias Bosch 2015-06-02 10:15:16 -07:00
parent 0a50a3f564
commit 827841ec5b
11 changed files with 86 additions and 95 deletions

View File

@ -80,7 +80,7 @@ function _injectorBindings(appComponentType): List<Type | Binding | List<any>> {
var domView = resolveInternalDomView(componentRef.hostView.render);
// We need to do this here to ensure that we create Testability and
// it's ready on the window for users.
registry.registerApplication(domView.boundElements[0], testability);
registry.registerApplication(domView.boundElements[0].element, testability);
return componentRef;
});

View File

@ -23,7 +23,9 @@ export class ElementRef {
// We need a more general way to read/write to the DOM element
// via a proper abstraction in the render layer
get domElement() {
return resolveInternalDomView(this.parentView.render).boundElements[this.boundElementIndex];
return resolveInternalDomView(this.parentView.render)
.boundElements[this.boundElementIndex]
.element;
}
/**

View File

@ -43,7 +43,9 @@ export class DebugElement {
}
get domElement(): any {
return resolveInternalDomView(this._parentView.render).boundElements[this._boundElementIndex];
return resolveInternalDomView(this._parentView.render)
.boundElements[this._boundElementIndex]
.element;
}
getDirectiveInstance(directiveIndex: number): any {

View File

@ -61,7 +61,7 @@ export class DebugElementViewListener implements AppViewListener {
MapWrapper.set(_allIdsByView, view, viewId);
var renderView = resolveInternalDomView(view.render);
for (var i = 0; i < renderView.boundElements.length; i++) {
_setElementId(renderView.boundElements[i], [viewId, i]);
_setElementId(renderView.boundElements[i].element, [viewId, i]);
}
}

View File

@ -16,6 +16,7 @@ import {EventManager} from './events/event_manager';
import {DomProtoView, DomProtoViewRef, resolveInternalDomProtoView} from './view/proto_view';
import {DomView, DomViewRef, resolveInternalDomView} from './view/view';
import {DomElement} from './view/element';
import {DomViewContainer} from './view/view_container';
import {NG_BINDING_CLASS_SELECTOR, NG_BINDING_CLASS} from './util';
@ -65,8 +66,8 @@ export class DomRenderer extends Renderer {
componentViewRef: RenderViewRef) {
var hostView = resolveInternalDomView(hostViewRef);
var componentView = resolveInternalDomView(componentViewRef);
var element = hostView.boundElements[elementIndex];
var lightDom = hostView.lightDoms[elementIndex];
var element = hostView.boundElements[elementIndex].element;
var lightDom = hostView.boundElements[elementIndex].lightDom;
if (isPresent(lightDom)) {
lightDom.attachShadowDomView(componentView);
}
@ -92,7 +93,7 @@ export class DomRenderer extends Renderer {
var hostView = resolveInternalDomView(hostViewRef);
var componentView = resolveInternalDomView(componentViewRef);
this._removeViewNodes(componentView);
var lightDom = hostView.lightDoms[boundElementIndex];
var lightDom = hostView.boundElements[boundElementIndex].lightDom;
if (isPresent(lightDom)) {
lightDom.detachShadowDomView();
}
@ -108,11 +109,11 @@ export class DomRenderer extends Renderer {
ListWrapper.insert(viewContainer.views, atIndex, view);
view.hostLightDom = parentView.hostLightDom;
var directParentLightDom = parentView.getDirectParentLightDom(boundElementIndex);
var directParentLightDom = this._directParentLightDom(parentView, boundElementIndex);
if (isBlank(directParentLightDom)) {
var siblingToInsertAfter;
if (atIndex == 0) {
siblingToInsertAfter = parentView.boundElements[boundElementIndex];
siblingToInsertAfter = parentView.boundElements[boundElementIndex].element;
} else {
siblingToInsertAfter = ListWrapper.last(viewContainer.views[atIndex - 1].rootNodes);
}
@ -130,10 +131,10 @@ export class DomRenderer extends Renderer {
viewRef: RenderViewRef) {
var parentView = resolveInternalDomView(parentViewRef);
var view = resolveInternalDomView(viewRef);
var viewContainer = parentView.viewContainers[boundElementIndex];
var viewContainer = parentView.boundElements[boundElementIndex].viewContainer;
var detachedView = viewContainer.views[atIndex];
ListWrapper.removeAt(viewContainer.views, atIndex);
var directParentLightDom = parentView.getDirectParentLightDom(boundElementIndex);
var directParentLightDom = this._directParentLightDom(parentView, boundElementIndex);
if (isBlank(directParentLightDom)) {
this._removeViewNodes(detachedView);
} else {
@ -151,8 +152,8 @@ export class DomRenderer extends Renderer {
if (view.hydrated) throw new BaseException('The view is already hydrated.');
view.hydrated = true;
for (var i = 0; i < view.lightDoms.length; ++i) {
var lightDom = view.lightDoms[i];
for (var i = 0; i < view.boundElements.length; ++i) {
var lightDom = view.boundElements[i].lightDom;
if (isPresent(lightDom)) {
lightDom.redistribute();
}
@ -244,7 +245,6 @@ export class DomRenderer extends Renderer {
var binders = protoView.elementBinders;
var boundTextNodes = [];
var boundElements = ListWrapper.createFixedSize(binders.length);
var contentTags = ListWrapper.createFixedSize(binders.length);
for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) {
var binder = binders[binderIdx];
@ -261,7 +261,6 @@ export class DomRenderer extends Renderer {
element = elementsWithBindings[binderIdx - protoView.rootBindingOffset];
childNodes = DOM.childNodes(element);
}
boundElements[binderIdx] = element;
// boundTextNodes
var textNodeIndices = binder.textNodeIndices;
@ -274,10 +273,10 @@ export class DomRenderer extends Renderer {
if (isPresent(binder.contentTagSelector)) {
contentTag = new Content(element, binder.contentTagSelector);
}
contentTags[binderIdx] = contentTag;
boundElements[binderIdx] = new DomElement(binder, element, contentTag);
}
var view = new DomView(protoView, viewRootNodes, boundTextNodes, boundElements, contentTags);
var view = new DomView(protoView, viewRootNodes, boundTextNodes, boundElements);
for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) {
var binder = binders[binderIdx];
@ -286,21 +285,21 @@ export class DomRenderer extends Renderer {
// lightDoms
var lightDom = null;
if (isPresent(binder.componentId)) {
lightDom = this._shadowDomStrategy.constructLightDom(view, boundElements[binderIdx]);
lightDom = this._shadowDomStrategy.constructLightDom(view, element.element);
}
view.lightDoms[binderIdx] = lightDom;
element.lightDom = lightDom;
// init contentTags
var contentTag = contentTags[binderIdx];
var contentTag = element.contentTag;
if (isPresent(contentTag)) {
var destLightDom = view.getDirectParentLightDom(binderIdx);
contentTag.init(destLightDom);
var directParentLightDom = this._directParentLightDom(view, binderIdx);
contentTag.init(directParentLightDom);
}
// events
if (isPresent(binder.eventLocals) && isPresent(binder.localEvents)) {
for (var i = 0; i < binder.localEvents.length; i++) {
this._createEventListener(view, element, binderIdx, binder.localEvents[i].name,
this._createEventListener(view, element.element, binderIdx, binder.localEvents[i].name,
binder.eventLocals);
}
}
@ -337,14 +336,20 @@ export class DomRenderer extends Renderer {
}
_getOrCreateViewContainer(parentView: DomView, boundElementIndex) {
var vc = parentView.viewContainers[boundElementIndex];
var el = parentView.boundElements[boundElementIndex];
var vc = el.viewContainer;
if (isBlank(vc)) {
vc = new DomViewContainer();
parentView.viewContainers[boundElementIndex] = vc;
el.viewContainer = vc;
}
return vc;
}
_directParentLightDom(view: DomView, boundElementIndex: number) {
var directParentEl = view.getDirectParentElement(boundElementIndex);
return isPresent(directParentEl) ? directParentEl.lightDom : null;
}
_createGlobalEventListener(view, elementIndex, eventName, eventTarget, fullName): Function {
return this._eventManager.addGlobalEventListener(
eventTarget, eventName, (event) => { view.dispatchEvent(elementIndex, fullName, event); });

View File

@ -3,18 +3,13 @@ import {List, ListWrapper} from 'angular2/src/facade/collection';
import {isBlank, isPresent} from 'angular2/src/facade/lang';
import * as viewModule from '../view/view';
import * as elModule from '../view/element';
import {Content} from './content_tag';
export class DestinationLightDom {}
class _Root {
node;
boundElementIndex: number;
constructor(node, boundElementIndex) {
this.node = node;
this.boundElementIndex = boundElementIndex;
}
constructor(public node, public boundElement: elModule.DomElement) {}
}
// TODO: LightDom should implement DestinationLightDom
@ -59,16 +54,14 @@ export class LightDom {
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);
var els = view.boundElements;
for (var i = 0; i < els.length; i++) {
var el = els[i];
if (isPresent(el.contentTag)) {
ListWrapper.push(acc, el.contentTag);
}
if (isPresent(vc)) {
ListWrapper.forEach(vc.contentTagContainers(),
if (isPresent(el.viewContainer)) {
ListWrapper.forEach(el.viewContainer.contentTagContainers(),
(view) => { this._collectAllContentTags(view, acc); });
}
}
@ -85,9 +78,9 @@ export class LightDom {
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(root.boundElement)) {
var vc = root.boundElement.viewContainer;
var content = root.boundElement.contentTag;
if (isPresent(vc)) {
res = ListWrapper.concat(res, vc.nodes());
} else if (isPresent(content)) {
@ -103,22 +96,22 @@ export class LightDom {
}
// Returns a list of Roots for all the nodes of the light DOM.
// The Root object contains the DOM node and its corresponding boundElementIndex
// The Root object contains the DOM node and its corresponding boundElement
private _findRoots() {
if (isPresent(this._roots)) return this._roots;
var boundElements = this.lightDomView.boundElements;
this._roots = ListWrapper.map(this.nodes, (n) => {
var boundElementIndex = null;
var boundElement = null;
for (var i = 0; i < boundElements.length; i++) {
var boundEl = boundElements[i];
if (isPresent(boundEl) && boundEl === n) {
boundElementIndex = i;
if (isPresent(boundEl) && boundEl.element === n) {
boundElement = boundEl;
break;
}
}
return new _Root(n, boundElementIndex);
return new _Root(n, boundElement);
});
return this._roots;

View File

@ -0,0 +1,11 @@
import {ElementBinder} from './element_binder';
import {DomViewContainer} from './view_container';
import {LightDom} from '../shadow_dom/light_dom';
import {Content} from '../shadow_dom/content_tag';
export class DomElement {
viewContainer: DomViewContainer;
lightDom: LightDom;
constructor(public proto: ElementBinder, public element: any /* element */,
public contentTag: Content) {}
}

View File

@ -3,10 +3,9 @@ import {ListWrapper, MapWrapper, Map, StringMapWrapper, List} from 'angular2/src
import {Locals} from 'angular2/change_detection';
import {isPresent, isBlank, BaseException} from 'angular2/src/facade/lang';
import {DomViewContainer} from './view_container';
import {DomProtoView} from './proto_view';
import {LightDom} from '../shadow_dom/light_dom';
import {Content} from '../shadow_dom/content_tag';
import {DomElement} from './element';
import {RenderViewRef, EventDispatcher} from '../../api';
@ -29,10 +28,6 @@ const NG_BINDING_CLASS = 'ng-binding';
* Const of making objects: http://jsperf.com/instantiate-size-of-object
*/
export class DomView {
// TODO(tbosch): move componentChildViews, viewContainers, contentTags, lightDoms into
// a single array with records inside
viewContainers: List<DomViewContainer>;
lightDoms: List<LightDom>;
hostLightDom: LightDom;
shadowRoot;
hydrated: boolean;
@ -40,10 +35,7 @@ export class DomView {
eventHandlerRemovers: List<Function>;
constructor(public proto: DomProtoView, public rootNodes: List</*node*/ any>,
public boundTextNodes: List</*node*/ any>,
public boundElements: List</*element*/ any>, public contentTags: List<Content>) {
this.viewContainers = ListWrapper.createFixedSize(boundElements.length);
this.lightDoms = ListWrapper.createFixedSize(boundElements.length);
public boundTextNodes: List</*node*/ any>, public boundElements: List<DomElement>) {
this.hostLightDom = null;
this.hydrated = false;
this.eventHandlerRemovers = [];
@ -51,25 +43,25 @@ export class DomView {
this.shadowRoot = null;
}
getDirectParentLightDom(boundElementIndex: number) {
getDirectParentElement(boundElementIndex: number): DomElement {
var binder = this.proto.elementBinders[boundElementIndex];
var destLightDom = null;
var parent = null;
if (binder.parentIndex !== -1 && binder.distanceToParent === 1) {
destLightDom = this.lightDoms[binder.parentIndex];
parent = this.boundElements[binder.parentIndex];
}
return destLightDom;
return parent;
}
setElementProperty(elementIndex: number, propertyName: string, value: any) {
var setter =
MapWrapper.get(this.proto.elementBinders[elementIndex].propertySetters, propertyName);
setter(this.boundElements[elementIndex], value);
setter(this.boundElements[elementIndex].element, value);
}
callAction(elementIndex: number, actionExpression: string, actionArgs: any) {
var binder = this.proto.elementBinders[elementIndex];
var hostAction = MapWrapper.get(binder.hostActions, actionExpression);
hostAction.eval(this.boundElements[elementIndex], this._localsWithAction(actionArgs));
hostAction.eval(this.boundElements[elementIndex].element, this._localsWithAction(actionArgs));
}
_localsWithAction(action: Object): Locals {

View File

@ -123,7 +123,7 @@ export class DomTestbed {
}
triggerEvent(viewRef: RenderViewRef, boundElementIndex: number, eventName: string) {
var element = resolveInternalDomView(viewRef).boundElements[boundElementIndex];
var element = resolveInternalDomView(viewRef).boundElements[boundElementIndex].element;
dispatchEvent(element, eventName);
}
}

View File

@ -18,6 +18,7 @@ import {LightDom} from 'angular2/src/render/dom/shadow_dom/light_dom';
import {DomView} from 'angular2/src/render/dom/view/view';
import {DomProtoView} from 'angular2/src/render/dom/view/proto_view';
import {DomViewContainer} from 'angular2/src/render/dom/view/view_container';
import {DomElement} from 'angular2/src/render/dom/view/element';
@proxy
@IMPLEMENTS(DomProtoView)
@ -31,31 +32,27 @@ class FakeProtoView extends SpyObject {
@IMPLEMENTS(DomView)
class FakeView extends SpyObject {
boundElements;
contentTags;
viewContainers;
proto;
constructor(containers = null, transitiveContentTagCount: number = 1) {
super(DomView);
this.proto = new FakeProtoView(transitiveContentTagCount);
this.boundElements = [];
this.contentTags = [];
this.viewContainers = [];
if (isPresent(containers)) {
ListWrapper.forEach(containers, (c) => {
var boundElement = null;
var element = null;
var contentTag = null;
var vc = null;
if (c instanceof FakeContentTag) {
contentTag = c;
boundElement = c.contentStartElement;
element = c.contentStartElement;
}
if (c instanceof FakeViewContainer) {
vc = c;
boundElement = c.templateElement;
element = c.templateElement;
}
ListWrapper.push(this.contentTags, contentTag);
ListWrapper.push(this.viewContainers, vc);
var boundElement = new DomElement(null, element, contentTag);
boundElement.viewContainer = vc;
ListWrapper.push(this.boundElements, boundElement);
});
}

View File

@ -15,13 +15,13 @@ import {
SpyObject,
proxy
} from 'angular2/test_lib';
import {IMPLEMENTS, isBlank} from 'angular2/src/facade/lang';
import {isBlank} from 'angular2/src/facade/lang';
import {ListWrapper} from 'angular2/src/facade/collection';
import {DomProtoView} from 'angular2/src/render/dom/view/proto_view';
import {ElementBinder} from 'angular2/src/render/dom/view/element_binder';
import {DomView} from 'angular2/src/render/dom/view/view';
import {LightDom} from 'angular2/src/render/dom/shadow_dom/light_dom';
import {DomElement} from 'angular2/src/render/dom/view/element';
import {DOM} from 'angular2/src/dom/dom_adapter';
export function main() {
@ -42,20 +42,19 @@ export function main() {
var root = el('<div><div></div></div>');
var boundElements = [];
for (var i = 0; i < boundElementCount; i++) {
ListWrapper.push(boundElements, el('<span></span'));
ListWrapper.push(boundElements,
new DomElement(pv.elementBinders[i], el('<span></span'), null));
}
return new DomView(pv, [DOM.childNodes(root)[0]], [], boundElements, []);
return new DomView(pv, [DOM.childNodes(root)[0]], [], boundElements);
}
describe('getDirectParentLightDom', () => {
describe('getDirectParentElement', () => {
it('should return the LightDom of the direct parent', () => {
it('should return the DomElement of the direct parent', () => {
var pv = createProtoView(
[new ElementBinder(), new ElementBinder({parentIndex: 0, distanceToParent: 1})]);
var view = createView(pv, 2);
view.lightDoms[0] = <any>new SpyLightDom();
view.lightDoms[1] = <any>new SpyLightDom();
expect(view.getDirectParentLightDom(1)).toBe(view.lightDoms[0]);
expect(view.getDirectParentElement(1)).toBe(view.boundElements[0]);
});
it('should return null if the direct parent is not bound', () => {
@ -65,20 +64,10 @@ export function main() {
new ElementBinder({parentIndex: 0, distanceToParent: 2})
]);
var view = createView(pv, 3);
view.lightDoms[0] = <any>new SpyLightDom();
view.lightDoms[1] = <any>new SpyLightDom();
view.lightDoms[2] = <any>new SpyLightDom();
expect(view.getDirectParentLightDom(2)).toBe(null);
expect(view.getDirectParentElement(2)).toBe(null);
});
});
});
}
@proxy
@IMPLEMENTS(LightDom)
class SpyLightDom extends SpyObject {
constructor() { super(LightDom); }
noSuchMethod(m) { return super.noSuchMethod(m) }
}