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

Debug Render Tree (for Ember Inspector) #18372

Merged
merged 2 commits into from Sep 16, 2019
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
1 change: 1 addition & 0 deletions .eslintrc.js
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
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
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
@@ -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
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';
@@ -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
@@ -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
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,
chancancode marked this conversation as resolved.
Show resolved Hide resolved
});
}

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