Skip to content

Commit

Permalink
Merge pull request #18372 from emberjs/render-tree
Browse files Browse the repository at this point in the history
Debug Render Tree (for Ember Inspector)
  • Loading branch information
rwjblue authored Sep 16, 2019
2 parents 6fd89f7 + d7d942e commit ee8a894
Show file tree
Hide file tree
Showing 34 changed files with 2,463 additions and 132 deletions.
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ module.exports = {
rules: {
// the TypeScript compiler already takes care of this and
// leaving it enabled results in false positives for interface imports
'no-dupe-class-members': 'off',
'no-unused-vars': 'off',
'no-undef': 'off',

Expand Down
6 changes: 6 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ jobs:
- yarn ember build -prod
- yarn test

- name: Production (All Tests + Canary Features + Debug Render Tree)
env: DEBUG_RENDER_TREE=true
script:
- yarn ember build -prod
- yarn test

- name: Old Jquery and Extend Prototypes
env: TEST_SUITE=old-jquery-and-extend-prototypes
script:
Expand Down
4 changes: 4 additions & 0 deletions bin/run-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ function getBrowserRunner() {
}

function run(queryString) {
if (process.env.DEBUG_RENDER_TREE) {
queryString = `${queryString}&debugrendertree`;
}

let url = 'http://localhost:' + PORT + '/tests/?' + queryString;
return runInBrowser(url, 3);
}
Expand Down
38 changes: 38 additions & 0 deletions packages/@ember/-internals/environment/lib/env.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { FUNCTION_PROTOTYPE_EXTENSIONS } from '@ember/deprecated-features';
import { DEBUG } from '@glimmer/env';
import global from './global';

/**
Expand Down Expand Up @@ -98,6 +99,39 @@ export const ENV = {
*/
_TEMPLATE_ONLY_GLIMMER_COMPONENTS: false,

/**
Whether to perform extra bookkeeping needed to make the `captureRenderTree`
API work.
This has to be set before the ember JavaScript code is evaluated. This is
usually done by setting `window.EmberENV = { _DEBUG_RENDER_TREE: true };`
or `window.ENV = { _DEBUG_RENDER_TREE: true };` before the "vendor"
`<script>` tag in `index.html`.
Setting the flag after Ember is already loaded will not work correctly. It
may appear to work somewhat, but fundamentally broken.
This is not intended to be set directly. Ember Inspector will enable the
flag on behalf of the user as needed.
This flag is always on in development mode.
The flag is off by default in production mode, due to the cost associated
with the the bookkeeping work.
The expected flow is that Ember Inspector will ask the user to refresh the
page after enabling the feature. It could also offer a feature where the
user add some domains to the "always on" list. In either case, Ember
Inspector will inject the code on the page to set the flag if needed.
@property _DEBUG_RENDER_TREE
@for EmberENV
@type Boolean
@default false
@private
*/
_DEBUG_RENDER_TREE: DEBUG,

/**
Whether the app is using jQuery. See RFC #294.
Expand Down Expand Up @@ -203,6 +237,10 @@ export const ENV = {
ENV.FEATURES[feature] = FEATURES[feature] === true;
}
}

if (DEBUG) {
ENV._DEBUG_RENDER_TREE = true;
}
})(global.EmberENV || global.ENV);

export function getENV() {
Expand Down
3 changes: 2 additions & 1 deletion packages/@ember/-internals/glimmer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -388,11 +388,12 @@ export { default as AbstractComponentManager } from './lib/component-managers/ab
// it supports for example
export { UpdatableReference, INVOKE } from './lib/utils/references';
export { default as iterableFor } from './lib/utils/iterable';
export { default as DebugStack } from './lib/utils/debug-stack';
export { default as getDebugStack, DebugStack } from './lib/utils/debug-stack';
export { default as OutletView } from './lib/views/outlet';
export { capabilities } from './lib/component-managers/custom';
export { setComponentManager, getComponentManager } from './lib/utils/custom-component-manager';
export { setModifierManager, getModifierManager } from './lib/utils/custom-modifier-manager';
export { capabilities as modifierCapabilities } from './lib/modifiers/custom';
export { isSerializationFirstNode } from './lib/utils/serialization-first-node-helpers';
export { setComponentTemplate, getComponentTemplate } from './lib/utils/component-template';
export { CapturedRenderNode } from './lib/utils/debug-render-tree';
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { DEBUG } from '@glimmer/env';
import { ComponentCapabilities, Simple } from '@glimmer/interfaces';
import { Tag, VersionedPathReference } from '@glimmer/reference';
import {
Expand All @@ -11,20 +10,14 @@ import {
PreparedArguments,
} from '@glimmer/runtime';
import { Destroyable, Opaque, Option } from '@glimmer/util';
import DebugStack from '../utils/debug-stack';
import { DebugStack } from '../utils/debug-stack';

// implements the ComponentManager interface as defined in glimmer:
// tslint:disable-next-line:max-line-length
// https://github.com/glimmerjs/glimmer-vm/blob/v0.24.0-beta.4/packages/%40glimmer/runtime/lib/component/interfaces.ts#L21

export default abstract class AbstractManager<T, U> implements ComponentManager<T, U> {
public debugStack: typeof DebugStack;
public _pushToDebugStack!: (name: string, environment: any) => void;
public _pushEngineToDebugStack!: (name: string, environment: any) => void;

constructor() {
this.debugStack = undefined;
}
public debugStack: DebugStack | undefined = undefined;

prepareArgs(_state: U, _args: Arguments): Option<PreparedArguments> {
return null;
Expand Down Expand Up @@ -83,15 +76,3 @@ export default abstract class AbstractManager<T, U> implements ComponentManager<

abstract getDestructor(bucket: T): Option<Destroyable>;
}

if (DEBUG) {
AbstractManager.prototype._pushToDebugStack = function(name: string, environment) {
this.debugStack = environment.debugStack;
this.debugStack.push(name);
};

AbstractManager.prototype._pushEngineToDebugStack = function(name: string, environment) {
this.debugStack = environment.debugStack;
this.debugStack.pushEngine(name);
};
}
51 changes: 44 additions & 7 deletions packages/@ember/-internals/glimmer/lib/component-managers/curly.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { privatize as P } from '@ember/-internals/container';
import { ENV } from '@ember/-internals/environment';
import { getOwner } from '@ember/-internals/owner';
import { guidFor } from '@ember/-internals/utils';
import {
Expand Down Expand Up @@ -239,7 +240,7 @@ export default class CurlyComponentManager
hasBlock: boolean
): ComponentStateBucket {
if (DEBUG) {
this._pushToDebugStack(`component:${state.name}`, environment);
environment.debugStack.push(`component:${state.name}`);
}

// Get the nearest concrete component instance from the scope. "Virtual"
Expand Down Expand Up @@ -275,6 +276,12 @@ export default class CurlyComponentManager
props.layout = state.template;
}

// caller:
// <FaIcon @name="bug" />
//
// callee:
// <i class="fa-{{@name}}"></i>

// Now that we've built up all of the properties to set on the component instance,
// actually create it.
let component = factory.create(props);
Expand Down Expand Up @@ -330,6 +337,15 @@ export default class CurlyComponentManager
component.trigger('willRender');
}

if (ENV._DEBUG_RENDER_TREE) {
environment.debugRenderTree.create(bucket, {
type: 'component',
name: state.name,
args: args.capture(),
instance: component,
});
}

return bucket;
}

Expand Down Expand Up @@ -388,8 +404,12 @@ export default class CurlyComponentManager
bucket.component[BOUNDS] = bounds;
bucket.finalize();

if (ENV._DEBUG_RENDER_TREE) {
bucket.environment.debugRenderTree.didRender(bucket, bounds);
}

if (DEBUG) {
this.debugStack.pop();
bucket.environment.debugStack.pop();
}
}

Expand All @@ -408,8 +428,12 @@ export default class CurlyComponentManager
update(bucket: ComponentStateBucket): void {
let { component, args, argsRevision, environment } = bucket;

if (ENV._DEBUG_RENDER_TREE) {
environment.debugRenderTree.update(bucket);
}

if (DEBUG) {
this._pushToDebugStack(component._debugContainerKey, environment);
environment.debugStack.push(component._debugContainerKey);
}

bucket.finalizer = _instrumentStart('render.component', rerenderInstrumentDetails, component);
Expand All @@ -433,11 +457,15 @@ export default class CurlyComponentManager
}
}

didUpdateLayout(bucket: ComponentStateBucket): void {
didUpdateLayout(bucket: ComponentStateBucket, bounds: Bounds): void {
bucket.finalize();

if (ENV._DEBUG_RENDER_TREE) {
bucket.environment.debugRenderTree.didRender(bucket, bounds);
}

if (DEBUG) {
this.debugStack.pop();
bucket.environment.debugStack.pop();
}
}

Expand All @@ -448,8 +476,17 @@ export default class CurlyComponentManager
}
}

getDestructor(stateBucket: ComponentStateBucket): Option<Destroyable> {
return stateBucket;
getDestructor(bucket: ComponentStateBucket): Option<Destroyable> {
if (ENV._DEBUG_RENDER_TREE) {
return {
destroy() {
bucket.environment.debugRenderTree.willDestroy(bucket);
bucket.destroy();
},
};
} else {
return bucket;
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ import {
import { createTag, isConst, PathReference, Tag } from '@glimmer/reference';
import {
Arguments,
Bounds,
CapturedArguments,
ComponentDefinition,
Invocation,
WithStaticLayout,
} from '@glimmer/runtime';
import { Destroyable } from '@glimmer/util';

import { ENV } from '@ember/-internals/environment';
import Environment from '../environment';
import RuntimeResolver from '../resolver';
import { OwnedTemplate } from '../template';
Expand Down Expand Up @@ -184,7 +186,7 @@ export default class CustomComponentManager<ComponentInstance>
RuntimeResolver
> {
create(
_env: Environment,
env: Environment,
definition: CustomComponentDefinitionState<ComponentInstance>,
args: Arguments
): CustomComponentState<ComponentInstance> {
Expand Down Expand Up @@ -267,10 +269,27 @@ export default class CustomComponentManager<ComponentInstance>

const component = delegate.createComponent(definition.ComponentClass.class, value);

return new CustomComponentState(delegate, component, capturedArgs, namedArgsProxy);
let bucket = new CustomComponentState(delegate, component, capturedArgs, env, namedArgsProxy);

if (ENV._DEBUG_RENDER_TREE) {
env.debugRenderTree.create(bucket, {
type: 'component',
name: definition.name,
args: args.capture(),
instance: component,
});
}

return bucket;
}

update({ delegate, component, args, namedArgsProxy }: CustomComponentState<ComponentInstance>) {
update(bucket: CustomComponentState<ComponentInstance>) {
if (ENV._DEBUG_RENDER_TREE) {
bucket.env.debugRenderTree.update(bucket);
}

let { delegate, component, args, namedArgsProxy } = bucket;

let value;

if (EMBER_CUSTOM_COMPONENT_ARG_PROXY) {
Expand Down Expand Up @@ -308,18 +327,34 @@ export default class CustomComponentManager<ComponentInstance>
}

getDestructor(state: CustomComponentState<ComponentInstance>): Option<Destroyable> {
let destructor: Option<Destroyable> = null;

if (hasDestructors(state.delegate)) {
return state;
} else {
return null;
destructor = state;
}

if (ENV._DEBUG_RENDER_TREE) {
let inner = destructor;

destructor = {
destroy() {
state.env.debugRenderTree.willDestroy(state);

if (inner) {
inner.destroy();
}
},
};
}

return destructor;
}

getCapabilities({
delegate,
}: CustomComponentDefinitionState<ComponentInstance>): ComponentCapabilities {
return Object.assign({}, CAPABILITIES, {
updateHook: delegate.capabilities.updateHook,
updateHook: ENV._DEBUG_RENDER_TREE || delegate.capabilities.updateHook,
});
}

Expand All @@ -332,7 +367,17 @@ export default class CustomComponentManager<ComponentInstance>
}
}

didRenderLayout() {}
didRenderLayout(bucket: CustomComponentState<ComponentInstance>, bounds: Bounds) {
if (ENV._DEBUG_RENDER_TREE) {
bucket.env.debugRenderTree.didRender(bucket, bounds);
}
}

didUpdateLayout(bucket: CustomComponentState<ComponentInstance>, bounds: Bounds) {
if (ENV._DEBUG_RENDER_TREE) {
bucket.env.debugRenderTree.didRender(bucket, bounds);
}
}

getLayout(state: DefinitionState<ComponentInstance>): Invocation {
return {
Expand All @@ -351,6 +396,7 @@ export class CustomComponentState<ComponentInstance> {
public delegate: ManagerDelegate<ComponentInstance>,
public component: ComponentInstance,
public args: CapturedArguments,
public env: Environment,
public namedArgsProxy?: {}
) {}

Expand Down
Loading

0 comments on commit ee8a894

Please sign in to comment.