Skip to content
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

El probe #2210

Merged
merged 7 commits into from May 29, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions modules/angular2/debug.ts
@@ -0,0 +1,2 @@
export * from './src/debug/debug_element';
export {inspectDomElement, ELEMENT_PROBE_CONFIG} from './src/debug/debug_element_view_listener';
2 changes: 2 additions & 0 deletions modules/angular2/src/core/application.ts
Expand Up @@ -50,6 +50,7 @@ import {TestabilityRegistry, Testability} from 'angular2/src/core/testability/te
import {AppViewPool, APP_VIEW_POOL_CAPACITY} from 'angular2/src/core/compiler/view_pool';
import {AppViewManager} from 'angular2/src/core/compiler/view_manager';
import {AppViewManagerUtils} from 'angular2/src/core/compiler/view_manager_utils';
import {AppViewListener} from 'angular2/src/core/compiler/view_listener';
import {ProtoViewFactory} from 'angular2/src/core/compiler/proto_view_factory';
import {Renderer, RenderCompiler} from 'angular2/src/render/api';
import {DomRenderer, DOCUMENT_TOKEN} from 'angular2/src/render/dom/dom_renderer';
Expand Down Expand Up @@ -112,6 +113,7 @@ function _injectorBindings(appComponentType): List<Type | Binding | List<any>> {
bind(APP_VIEW_POOL_CAPACITY).toValue(10000),
AppViewManager,
AppViewManagerUtils,
AppViewListener,
Compiler,
CompilerCache,
TemplateResolver,
Expand Down
11 changes: 11 additions & 0 deletions modules/angular2/src/core/compiler/view_listener.ts
@@ -0,0 +1,11 @@
import {Injectable} from 'angular2/di';
import * as viewModule from './view';

/**
* Listener for view creation / destruction.
*/
@Injectable()
export class AppViewListener {
viewCreated(view: viewModule.AppView) {}
viewDestroyed(view: viewModule.AppView) {}
}
18 changes: 14 additions & 4 deletions modules/angular2/src/core/compiler/view_manager.ts
Expand Up @@ -7,6 +7,7 @@ import {ViewContainerRef} from './view_container_ref';
import {Renderer, RenderViewRef} from 'angular2/src/render/api';
import {AppViewManagerUtils} from './view_manager_utils';
import {AppViewPool} from './view_pool';
import {AppViewListener} from './view_listener';

/**
* Entry point for creating, moving views in the view hierarchy and destroying views.
Expand All @@ -16,13 +17,16 @@ import {AppViewPool} from './view_pool';
@Injectable()
export class AppViewManager {
_viewPool: AppViewPool;
_viewListener: AppViewListener;
_utils: AppViewManagerUtils;
_renderer: Renderer;

constructor(viewPool: AppViewPool, utils: AppViewManagerUtils, renderer: Renderer) {
this._renderer = renderer;
constructor(viewPool: AppViewPool, viewListener: AppViewListener, utils: AppViewManagerUtils,
renderer: Renderer) {
this._viewPool = viewPool;
this._viewListener = viewListener;
this._utils = utils;
this._renderer = renderer;
}

getComponentView(hostLocation: ElementRef): ViewRef {
Expand Down Expand Up @@ -74,6 +78,7 @@ export class AppViewManager {
var hostView = this._utils.createView(hostProtoView, renderView, this, this._renderer);
this._renderer.setEventDispatcher(hostView.render, hostView);
this._createViewRecurse(hostView);
this._viewListener.viewCreated(hostView);

this._utils.hydrateRootHostView(hostView, injector);
this._viewHydrateRecurse(hostView);
Expand All @@ -88,6 +93,7 @@ export class AppViewManager {
// We do want to destroy the component view though.
this._viewDehydrateRecurse(hostView, true);
this._renderer.destroyView(hostView.render);
this._viewListener.viewDestroyed(hostView);
}

createFreeHostView(parentComponentLocation: ElementRef, hostProtoViewRef: ProtoViewRef,
Expand Down Expand Up @@ -175,6 +181,7 @@ export class AppViewManager {
this._renderer);
this._renderer.setEventDispatcher(view.render, view);
this._createViewRecurse(view);
this._viewListener.viewCreated(view);
}
return view;
}
Expand All @@ -192,8 +199,11 @@ export class AppViewManager {
}

_destroyPooledView(view: viewModule.AppView) {
// TODO: if the pool is full, call renderer.destroyView as well!
this._viewPool.returnView(view);
var wasReturned = this._viewPool.returnView(view);
if (!wasReturned) {
this._renderer.destroyView(view.render);
this._viewListener.viewDestroyed(view);
}
}

_destroyViewInContainer(parentView, boundElementIndex, atIndex: number) {
Expand Down
6 changes: 4 additions & 2 deletions modules/angular2/src/core/compiler/view_pool.ts
Expand Up @@ -27,15 +27,17 @@ export class AppViewPool {
return null;
}

returnView(view: viewModule.AppView) {
returnView(view: viewModule.AppView): boolean {
var protoView = view.proto;
var pooledViews = MapWrapper.get(this._pooledViewsPerProtoView, protoView);
if (isBlank(pooledViews)) {
pooledViews = [];
MapWrapper.set(this._pooledViewsPerProtoView, protoView, pooledViews);
}
if (pooledViews.length < this._poolCapacityPerProtoView) {
var haveRemainingCapacity = pooledViews.length < this._poolCapacityPerProtoView;
if (haveRemainingCapacity) {
ListWrapper.push(pooledViews, view);
}
return haveRemainingCapacity;
}
}
203 changes: 203 additions & 0 deletions modules/angular2/src/debug/debug_element.ts
@@ -0,0 +1,203 @@
import {Type, isPresent, BaseException, isBlank} from 'angular2/src/facade/lang';
import {List, ListWrapper, MapWrapper} from 'angular2/src/facade/collection';

import {DOM} from 'angular2/src/dom/dom_adapter';

import {ElementInjector} from 'angular2/src/core/compiler/element_injector';
import {AppView} from 'angular2/src/core/compiler/view';
import {internalView} from 'angular2/src/core/compiler/view_ref';
import {ElementRef} from 'angular2/src/core/compiler/element_ref';

import {resolveInternalDomView} from 'angular2/src/render/dom/view/view';

/**
* @exportedAs angular2/test
*
* An DebugElement contains information from the Angular compiler about an
* element and provides access to the corresponding ElementInjector and
* underlying dom Element, as well as a way to query for children.
*/
export class DebugElement {
_elementInjector: ElementInjector;

constructor(private _parentView: AppView, private _boundElementIndex: number) {
this._elementInjector = this._parentView.elementInjectors[this._boundElementIndex];
}

static create(elementRef: ElementRef): DebugElement {
return new DebugElement(internalView(elementRef.parentView), elementRef.boundElementIndex);
}

get componentInstance(): any {
if (!isPresent(this._elementInjector)) {
return null;
}
return this._elementInjector.getComponent();
}

get dynamicallyCreatedComponentInstance(): any {
if (!isPresent(this._elementInjector)) {
return null;
}
return this._elementInjector.getDynamicallyLoadedComponent();
}

get domElement(): any {
return resolveInternalDomView(this._parentView.render).boundElements[this._boundElementIndex];
}

getDirectiveInstance(directiveIndex: number): any {
return this._elementInjector.getDirectiveAtIndex(directiveIndex);
}

/**
* Get child DebugElements from within the Light DOM.
*
* @return {List<DebugElement>}
*/
get children(): List<DebugElement> {
var thisElementBinder = this._parentView.proto.elementBinders[this._boundElementIndex];

return this._getChildElements(this._parentView, thisElementBinder.index);
}

/**
* Get the root DebugElement children of a component. Returns an empty
* list if the current DebugElement is not a component root.
*
* @return {List<DebugElement>}
*/
get componentViewChildren(): List<DebugElement> {
var shadowView = this._parentView.componentChildViews[this._boundElementIndex];

if (!isPresent(shadowView)) {
// The current element is not a component.
return ListWrapper.create();
}

return this._getChildElements(shadowView, null);
}

triggerEventHandler(eventName, eventObj): void {
this._parentView.triggerEventHandlers(eventName, eventObj, this._boundElementIndex);
}

hasDirective(type: Type): boolean {
if (!isPresent(this._elementInjector)) {
return false;
}
return this._elementInjector.hasDirective(type);
}

inject(type: Type): any {
if (!isPresent(this._elementInjector)) {
return null;
}
return this._elementInjector.get(type);
}

/**
* Return the first descendant TestElememt matching the given predicate
* and scope.
*
* @param {Function: boolean} predicate
* @param {Scope} scope
*
* @return {DebugElement}
*/
query(predicate: Function, scope = Scope.all): DebugElement {
var results = this.queryAll(predicate, scope);
return results.length > 0 ? results[0] : null;
}

/**
* Return descendant TestElememts matching the given predicate
* and scope.
*
* @param {Function: boolean} predicate
* @param {Scope} scope
*
* @return {List<DebugElement>}
*/
queryAll(predicate: Function, scope = Scope.all): List<DebugElement> {
var elementsInScope = scope(this);

return ListWrapper.filter(elementsInScope, predicate);
}

_getChildElements(view: AppView, parentBoundElementIndex: number): List<DebugElement> {
var els = ListWrapper.create();
var parentElementBinder = null;
if (isPresent(parentBoundElementIndex)) {
parentElementBinder = view.proto.elementBinders[parentBoundElementIndex];
}
for (var i = 0; i < view.proto.elementBinders.length; ++i) {
var binder = view.proto.elementBinders[i];
if (binder.parent == parentElementBinder) {
ListWrapper.push(els, new DebugElement(view, i));

var views = view.viewContainers[i];
if (isPresent(views)) {
ListWrapper.forEach(views.views, (nextView) => {
els = ListWrapper.concat(els, this._getChildElements(nextView, null));
});
}
}
}
return els;
}
}

export function inspectElement(elementRef: ElementRef): DebugElement {
return DebugElement.create(elementRef);
}

/**
* @exportedAs angular2/test
*/
export class Scope {
static all(debugElement): List<DebugElement> {
var scope = ListWrapper.create();
ListWrapper.push(scope, debugElement);

ListWrapper.forEach(debugElement.children,
(child) => { scope = ListWrapper.concat(scope, Scope.all(child)); });

ListWrapper.forEach(debugElement.componentViewChildren,
(child) => { scope = ListWrapper.concat(scope, Scope.all(child)); });

return scope;
}
static light(debugElement): List<DebugElement> {
var scope = ListWrapper.create();
ListWrapper.forEach(debugElement.children, (child) => {
ListWrapper.push(scope, child);
scope = ListWrapper.concat(scope, Scope.light(child));
});
return scope;
}

static view(debugElement): List<DebugElement> {
var scope = ListWrapper.create();

ListWrapper.forEach(debugElement.componentViewChildren, (child) => {
ListWrapper.push(scope, child);
scope = ListWrapper.concat(scope, Scope.light(child));
});
return scope;
}
}

/**
* @exportedAs angular2/test
*/
export class By {
static all(): Function { return (debugElement) => true; }

static css(selector: string): Function {
return (debugElement) => { return DOM.elementMatches(debugElement.domElement, selector); };
}
static directive(type: Type): Function {
return (debugElement) => { return debugElement.hasDirective(type); };
}
}
78 changes: 78 additions & 0 deletions modules/angular2/src/debug/debug_element_view_listener.ts
@@ -0,0 +1,78 @@
import {
CONST_EXPR,
isPresent,
NumberWrapper,
StringWrapper,
RegExpWrapper
} from 'angular2/src/facade/lang';
import {MapWrapper, Map, ListWrapper, List} from 'angular2/src/facade/collection';
import {Injectable, bind, Binding} from 'angular2/di';
import {AppViewListener} from 'angular2/src/core/compiler/view_listener';
import {AppView} from 'angular2/src/core/compiler/view';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {resolveInternalDomView} from 'angular2/src/render/dom/view/view';
import {DebugElement} from './debug_element';

const NG_ID_PROPERTY = 'ngid';
const INSPECT_GLOBAL_NAME = 'ngProbe';

var NG_ID_SEPARATOR_RE = RegExpWrapper.create('#');
var NG_ID_SEPARATOR = '#';

// Need to keep the views in a global Map so that multiple angular apps are supported
var _allIdsByView: Map<AppView, number> = CONST_EXPR(MapWrapper.create());
var _allViewsById: Map<number, AppView> = CONST_EXPR(MapWrapper.create());
var _nextId = 0;

function _setElementId(element, indices: List<number>) {
if (isPresent(element)) {
DOM.setData(element, NG_ID_PROPERTY, ListWrapper.join(indices, NG_ID_SEPARATOR));
}
}

function _getElementId(element): List<number> {
var elId = DOM.getData(element, NG_ID_PROPERTY);
if (isPresent(elId)) {
return ListWrapper.map(StringWrapper.split(elId, NG_ID_SEPARATOR_RE),
(partStr) => NumberWrapper.parseInt(partStr, 10));
} else {
return null;
}
}

export function inspectDomElement(element): DebugElement {
var elId = _getElementId(element);
if (isPresent(elId)) {
var view = MapWrapper.get(_allViewsById, elId[0]);
if (isPresent(view)) {
return new DebugElement(view, elId[1]);
}
}
return null;
}

@Injectable()
export class DebugElementViewListener implements AppViewListener {
constructor() { DOM.setGlobalVar(INSPECT_GLOBAL_NAME, inspectDomElement); }

viewCreated(view: AppView) {
var viewId = _nextId++;
MapWrapper.set(_allViewsById, viewId, view);
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]);
}
}

viewDestroyed(view: AppView) {
var viewId = MapWrapper.get(_allIdsByView, view);
MapWrapper.delete(_allIdsByView, view);
MapWrapper.delete(_allViewsById, viewId);
}
}

export var ELEMENT_PROBE_CONFIG = [
DebugElementViewListener,
bind(AppViewListener).toAlias(DebugElementViewListener),
];