fix(perf): don’t use `try/catch` in production mode

The previous code that had `try/catch` statements in methods could not be optimized by Chrome. 

This change separates `AppView` (no `try/catch`) form `DebugAppView` (always `try/catch`). Our codegen will use `AppView` in production mode and `DebugAppView` in debug mode.

Closes #8338
This commit is contained in:
Tobias Bosch 2016-04-29 09:11:57 -07:00
parent 5297c9d9cc
commit b1a9e445b3
5 changed files with 113 additions and 92 deletions

View File

@ -1,5 +1,5 @@
import {CompileIdentifierMetadata, CompileTokenMetadata} from './compile_metadata'; import {CompileIdentifierMetadata, CompileTokenMetadata} from './compile_metadata';
import {AppView} from 'angular2/src/core/linker/view'; import {AppView, DebugAppView} from 'angular2/src/core/linker/view';
import {StaticNodeDebugInfo, DebugContext} from 'angular2/src/core/linker/debug_context'; import {StaticNodeDebugInfo, DebugContext} from 'angular2/src/core/linker/debug_context';
import { import {
ViewUtils, ViewUtils,
@ -47,6 +47,7 @@ var CD_MODULE_URL = 'asset:angular2/lib/src/core/change_detection/change_detecti
// (only needed for Dart). // (only needed for Dart).
var impViewUtils = ViewUtils; var impViewUtils = ViewUtils;
var impAppView = AppView; var impAppView = AppView;
var impDebugAppView = DebugAppView;
var impDebugContext = DebugContext; var impDebugContext = DebugContext;
var impAppElement = AppElement; var impAppElement = AppElement;
var impElementRef = ElementRef; var impElementRef = ElementRef;
@ -80,6 +81,8 @@ export class Identifiers {
}); });
static AppView = new CompileIdentifierMetadata( static AppView = new CompileIdentifierMetadata(
{name: 'AppView', moduleUrl: APP_VIEW_MODULE_URL, runtime: impAppView}); {name: 'AppView', moduleUrl: APP_VIEW_MODULE_URL, runtime: impAppView});
static DebugAppView = new CompileIdentifierMetadata(
{name: 'DebugAppView', moduleUrl: APP_VIEW_MODULE_URL, runtime: impDebugAppView});
static AppElement = new CompileIdentifierMetadata({ static AppElement = new CompileIdentifierMetadata({
name: 'AppElement', name: 'AppElement',
moduleUrl: 'asset:angular2/lib/src/core/linker/element' + MODULE_SUFFIX, moduleUrl: 'asset:angular2/lib/src/core/linker/element' + MODULE_SUFFIX,

View File

@ -1,5 +1,5 @@
import {isPresent} from 'angular2/src/facade/lang'; import {isPresent} from 'angular2/src/facade/lang';
import {AppView} from 'angular2/src/core/linker/view'; import {AppView, DebugAppView} from 'angular2/src/core/linker/view';
import {AppElement} from 'angular2/src/core/linker/element'; import {AppElement} from 'angular2/src/core/linker/element';
import {BaseException} from 'angular2/src/facade/exceptions'; import {BaseException} from 'angular2/src/facade/exceptions';
import {InstanceFactory, DynamicInstance} from './output_interpreter'; import {InstanceFactory, DynamicInstance} from './output_interpreter';
@ -8,13 +8,19 @@ export class InterpretiveAppViewInstanceFactory implements InstanceFactory {
createInstance(superClass: any, clazz: any, args: any[], props: Map<string, any>, createInstance(superClass: any, clazz: any, args: any[], props: Map<string, any>,
getters: Map<string, Function>, methods: Map<string, Function>): any { getters: Map<string, Function>, methods: Map<string, Function>): any {
if (superClass === AppView) { if (superClass === AppView) {
// We are always using DebugAppView as parent.
// However, in prod mode we generate a constructor call that does
// not have the argument for the debugNodeInfos.
args = args.concat([null]);
return new _InterpretiveAppView(args, props, getters, methods);
} else if (superClass === DebugAppView) {
return new _InterpretiveAppView(args, props, getters, methods); return new _InterpretiveAppView(args, props, getters, methods);
} }
throw new BaseException(`Can't instantiate class ${superClass} in interpretative mode`); throw new BaseException(`Can't instantiate class ${superClass} in interpretative mode`);
} }
} }
class _InterpretiveAppView extends AppView<any> implements DynamicInstance { class _InterpretiveAppView extends DebugAppView<any> implements DynamicInstance {
constructor(args: any[], public props: Map<string, any>, public getters: Map<string, Function>, constructor(args: any[], public props: Map<string, any>, public getters: Map<string, Function>,
public methods: Map<string, Function>) { public methods: Map<string, Function>) {
super(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]); super(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]);

View File

@ -398,19 +398,20 @@ function createViewClass(view: CompileView, renderCompTypeVar: o.ReadVarExpr,
new o.FnParam(ViewConstructorVars.parentInjector.name, o.importType(Identifiers.Injector)), new o.FnParam(ViewConstructorVars.parentInjector.name, o.importType(Identifiers.Injector)),
new o.FnParam(ViewConstructorVars.declarationEl.name, o.importType(Identifiers.AppElement)) new o.FnParam(ViewConstructorVars.declarationEl.name, o.importType(Identifiers.AppElement))
]; ];
var viewConstructor = new o.ClassMethod(null, viewConstructorArgs, [ var superConstructorArgs = [
o.SUPER_EXPR.callFn([ o.variable(view.className),
o.variable(view.className), renderCompTypeVar,
renderCompTypeVar, ViewTypeEnum.fromValue(view.viewType),
ViewTypeEnum.fromValue(view.viewType), ViewConstructorVars.viewUtils,
ViewConstructorVars.viewUtils, ViewConstructorVars.parentInjector,
ViewConstructorVars.parentInjector, ViewConstructorVars.declarationEl,
ViewConstructorVars.declarationEl, ChangeDetectionStrategyEnum.fromValue(getChangeDetectionMode(view))
ChangeDetectionStrategyEnum.fromValue(getChangeDetectionMode(view)), ];
nodeDebugInfosVar if (view.genConfig.genDebugInfo) {
]) superConstructorArgs.push(nodeDebugInfosVar);
.toStmt() }
]); var viewConstructor = new o.ClassMethod(null, viewConstructorArgs,
[o.SUPER_EXPR.callFn(superConstructorArgs).toStmt()]);
var viewMethods = [ var viewMethods = [
new o.ClassMethod('createInternal', [new o.FnParam(rootSelectorVar.name, o.STRING_TYPE)], new o.ClassMethod('createInternal', [new o.FnParam(rootSelectorVar.name, o.STRING_TYPE)],
@ -431,9 +432,10 @@ function createViewClass(view: CompileView, renderCompTypeVar: o.ReadVarExpr,
new o.ClassMethod('dirtyParentQueriesInternal', [], view.dirtyParentQueriesMethod.finish()), new o.ClassMethod('dirtyParentQueriesInternal', [], view.dirtyParentQueriesMethod.finish()),
new o.ClassMethod('destroyInternal', [], view.destroyMethod.finish()) new o.ClassMethod('destroyInternal', [], view.destroyMethod.finish())
].concat(view.eventHandlerMethods); ].concat(view.eventHandlerMethods);
var viewClass = new o.ClassStmt( var superClass = view.genConfig.genDebugInfo ? Identifiers.DebugAppView : Identifiers.AppView;
view.className, o.importExpr(Identifiers.AppView, [getContextType(view)]), view.fields, var viewClass = new o.ClassStmt(view.className, o.importExpr(superClass, [getContextType(view)]),
view.getters, viewConstructor, viewMethods.filter((method) => method.body.length > 0)); view.fields, view.getters, viewConstructor,
viewMethods.filter((method) => method.body.length > 0));
return viewClass; return viewClass;
} }

View File

@ -2,7 +2,7 @@ import {isPresent, isBlank, CONST} from 'angular2/src/facade/lang';
import {ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection'; import {ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
import {Injector} from 'angular2/src/core/di'; import {Injector} from 'angular2/src/core/di';
import {RenderDebugInfo} from 'angular2/src/core/render/api'; import {RenderDebugInfo} from 'angular2/src/core/render/api';
import {AppView} from './view'; import {DebugAppView} from './view';
import {ViewType} from './view_type'; import {ViewType} from './view_type';
@CONST() @CONST()
@ -12,7 +12,7 @@ export class StaticNodeDebugInfo {
} }
export class DebugContext implements RenderDebugInfo { export class DebugContext implements RenderDebugInfo {
constructor(private _view: AppView<any>, private _nodeIndex: number, private _tplRow: number, constructor(private _view: DebugAppView<any>, private _nodeIndex: number, private _tplRow: number,
private _tplCol: number) {} private _tplCol: number) {}
private get _staticNodeInfo(): StaticNodeDebugInfo { private get _staticNodeInfo(): StaticNodeDebugInfo {
@ -31,7 +31,7 @@ export class DebugContext implements RenderDebugInfo {
var componentView = this._view; var componentView = this._view;
while (isPresent(componentView.declarationAppElement) && while (isPresent(componentView.declarationAppElement) &&
componentView.type !== ViewType.COMPONENT) { componentView.type !== ViewType.COMPONENT) {
componentView = componentView.declarationAppElement.parentView; componentView = <DebugAppView<any>>componentView.declarationAppElement.parentView;
} }
return isPresent(componentView.declarationAppElement) ? return isPresent(componentView.declarationAppElement) ?
componentView.declarationAppElement.nativeElement : componentView.declarationAppElement.nativeElement :

View File

@ -24,7 +24,12 @@ import {
} from 'angular2/src/facade/lang'; } from 'angular2/src/facade/lang';
import {ObservableWrapper} from 'angular2/src/facade/async'; import {ObservableWrapper} from 'angular2/src/facade/async';
import {Renderer, RootRenderer, RenderComponentType} from 'angular2/src/core/render/api'; import {
Renderer,
RootRenderer,
RenderComponentType,
RenderDebugInfo
} from 'angular2/src/core/render/api';
import {ViewRef_} from './view_ref'; import {ViewRef_} from './view_ref';
import {ViewType} from './view_type'; import {ViewType} from './view_type';
@ -78,16 +83,13 @@ export abstract class AppView<T> {
renderer: Renderer; renderer: Renderer;
private _currentDebugContext: DebugContext = null;
private _hasExternalHostElement: boolean; private _hasExternalHostElement: boolean;
public context: T; public context: T;
constructor(public clazz: any, public componentType: RenderComponentType, public type: ViewType, constructor(public clazz: any, public componentType: RenderComponentType, public type: ViewType,
public viewUtils: ViewUtils, public parentInjector: Injector, public viewUtils: ViewUtils, public parentInjector: Injector,
public declarationAppElement: AppElement, public cdMode: ChangeDetectionStrategy, public declarationAppElement: AppElement, public cdMode: ChangeDetectionStrategy) {
public staticNodeDebugInfos: StaticNodeDebugInfo[]) {
this.ref = new ViewRef_(this); this.ref = new ViewRef_(this);
if (type === ViewType.COMPONENT || type === ViewType.HOST) { if (type === ViewType.COMPONENT || type === ViewType.HOST) {
this.renderer = viewUtils.renderComponent(componentType); this.renderer = viewUtils.renderComponent(componentType);
@ -115,17 +117,7 @@ export abstract class AppView<T> {
} }
this._hasExternalHostElement = isPresent(rootSelectorOrNode); this._hasExternalHostElement = isPresent(rootSelectorOrNode);
this.projectableNodes = projectableNodes; this.projectableNodes = projectableNodes;
if (this.debugMode) { return this.createInternal(rootSelectorOrNode);
this._resetDebug();
try {
return this.createInternal(rootSelectorOrNode);
} catch (e) {
this._rethrowWithContext(e, e.stack);
throw e;
}
} else {
return this.createInternal(rootSelectorOrNode);
}
} }
/** /**
@ -150,28 +142,18 @@ export abstract class AppView<T> {
} }
selectOrCreateHostElement(elementName: string, rootSelectorOrNode: string | any, selectOrCreateHostElement(elementName: string, rootSelectorOrNode: string | any,
debugCtx: DebugContext): any { debugInfo: RenderDebugInfo): any {
var hostElement; var hostElement;
if (isPresent(rootSelectorOrNode)) { if (isPresent(rootSelectorOrNode)) {
hostElement = this.renderer.selectRootElement(rootSelectorOrNode, debugCtx); hostElement = this.renderer.selectRootElement(rootSelectorOrNode, debugInfo);
} else { } else {
hostElement = this.renderer.createElement(null, elementName, debugCtx); hostElement = this.renderer.createElement(null, elementName, debugInfo);
} }
return hostElement; return hostElement;
} }
injectorGet(token: any, nodeIndex: number, notFoundResult: any): any { injectorGet(token: any, nodeIndex: number, notFoundResult: any): any {
if (this.debugMode) { return this.injectorGetInternal(token, nodeIndex, notFoundResult);
this._resetDebug();
try {
return this.injectorGetInternal(token, nodeIndex, notFoundResult);
} catch (e) {
this._rethrowWithContext(e, e.stack);
throw e;
}
} else {
return this.injectorGetInternal(token, nodeIndex, notFoundResult);
}
} }
/** /**
@ -210,22 +192,12 @@ export abstract class AppView<T> {
for (var i = 0; i < children.length; i++) { for (var i = 0; i < children.length; i++) {
children[i]._destroyRecurse(); children[i]._destroyRecurse();
} }
if (this.debugMode) { this.destroyLocal();
this._resetDebug();
try {
this._destroyLocal();
} catch (e) {
this._rethrowWithContext(e, e.stack);
throw e;
}
} else {
this._destroyLocal();
}
this.destroyed = true; this.destroyed = true;
} }
private _destroyLocal() { destroyLocal() {
var hostElement = var hostElement =
this.type === ViewType.COMPONENT ? this.declarationAppElement.nativeElement : null; this.type === ViewType.COMPONENT ? this.declarationAppElement.nativeElement : null;
for (var i = 0; i < this.disposables.length; i++) { for (var i = 0; i < this.disposables.length; i++) {
@ -250,8 +222,6 @@ export abstract class AppView<T> {
*/ */
destroyInternal(): void {} destroyInternal(): void {}
get debugMode(): boolean { return isPresent(this.staticNodeDebugInfos); }
get changeDetectorRef(): ChangeDetectorRef { return this.ref; } get changeDetectorRef(): ChangeDetectorRef { return this.ref; }
get parent(): AppView<any> { get parent(): AppView<any> {
@ -293,17 +263,7 @@ export abstract class AppView<T> {
if (this.destroyed) { if (this.destroyed) {
this.throwDestroyedError('detectChanges'); this.throwDestroyedError('detectChanges');
} }
if (this.debugMode) { this.detectChangesInternal(throwOnChange);
this._resetDebug();
try {
this.detectChangesInternal(throwOnChange);
} catch (e) {
this._rethrowWithContext(e, e.stack);
throw e;
}
} else {
this.detectChangesInternal(throwOnChange);
}
if (this.cdMode === ChangeDetectionStrategy.CheckOnce) if (this.cdMode === ChangeDetectionStrategy.CheckOnce)
this.cdMode = ChangeDetectionStrategy.Checked; this.cdMode = ChangeDetectionStrategy.Checked;
@ -355,6 +315,61 @@ export abstract class AppView<T> {
} }
} }
eventHandler(cb: Function): Function { return cb; }
throwDestroyedError(details: string): void { throw new ViewDestroyedException(details); }
}
export class DebugAppView<T> extends AppView<T> {
private _currentDebugContext: DebugContext = null;
constructor(clazz: any, componentType: RenderComponentType, type: ViewType, viewUtils: ViewUtils,
parentInjector: Injector, declarationAppElement: AppElement,
cdMode: ChangeDetectionStrategy, public staticNodeDebugInfos: StaticNodeDebugInfo[]) {
super(clazz, componentType, type, viewUtils, parentInjector, declarationAppElement, cdMode);
}
create(context: T, givenProjectableNodes: Array<any | any[]>,
rootSelectorOrNode: string | any): AppElement {
this._resetDebug();
try {
return super.create(context, givenProjectableNodes, rootSelectorOrNode);
} catch (e) {
this._rethrowWithContext(e, e.stack);
throw e;
}
}
injectorGet(token: any, nodeIndex: number, notFoundResult: any): any {
this._resetDebug();
try {
return super.injectorGet(token, nodeIndex, notFoundResult);
} catch (e) {
this._rethrowWithContext(e, e.stack);
throw e;
}
}
destroyLocal() {
this._resetDebug();
try {
super.destroyLocal();
} catch (e) {
this._rethrowWithContext(e, e.stack);
throw e;
}
}
detectChanges(throwOnChange: boolean): void {
this._resetDebug();
try {
super.detectChanges(throwOnChange);
} catch (e) {
this._rethrowWithContext(e, e.stack);
throw e;
}
}
private _resetDebug() { this._currentDebugContext = null; } private _resetDebug() { this._currentDebugContext = null; }
debug(nodeIndex: number, rowNum: number, colNum: number): DebugContext { debug(nodeIndex: number, rowNum: number, colNum: number): DebugContext {
@ -373,22 +388,17 @@ export abstract class AppView<T> {
} }
eventHandler(cb: Function): Function { eventHandler(cb: Function): Function {
if (this.debugMode) { var superHandler = super.eventHandler(cb);
return (event) => { return (event) => {
this._resetDebug(); this._resetDebug();
try { try {
return cb(event); return superHandler(event);
} catch (e) { } catch (e) {
this._rethrowWithContext(e, e.stack); this._rethrowWithContext(e, e.stack);
throw e; throw e;
} }
}; };
} else {
return cb;
}
} }
throwDestroyedError(details: string): void { throw new ViewDestroyedException(details); }
} }
function _findLastRenderNode(node: any): any { function _findLastRenderNode(node: any): any {