Skip to content

Commit

Permalink
Merge pull request #14130 from emberjs/components-instrumentation
Browse files Browse the repository at this point in the history
[GLIMMER2] Component instrumentation support
  • Loading branch information
rwjblue committed Aug 26, 2016
2 parents f97598e + 238b57e commit ac93fc7
Show file tree
Hide file tree
Showing 13 changed files with 253 additions and 101 deletions.
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -39,7 +39,7 @@
"git-repo-info": "^1.1.4",
"git-repo-version": "^0.3.1",
"github": "^0.2.3",
"glimmer-engine": "0.10.1",
"glimmer-engine": "0.10.2",
"glob": "^5.0.13",
"htmlbars": "0.14.24",
"mocha": "^2.4.5",
Expand Down
2 changes: 0 additions & 2 deletions packages/ember-glimmer/lib/component.js
Expand Up @@ -2,7 +2,6 @@ import CoreView from 'ember-views/views/core_view';
import ClassNamesSupport from './ember-views/class-names-support';
import ChildViewsSupport from 'ember-views/mixins/child_views_support';
import ViewStateSupport from 'ember-views/mixins/view_state_support';
import InstrumentationSupport from 'ember-views/mixins/instrumentation_support';
import AriaRoleSupport from 'ember-views/mixins/aria_role_support';
import ViewMixin from 'ember-views/mixins/view_support';
import ActionSupport from 'ember-views/mixins/action_support';
Expand All @@ -29,7 +28,6 @@ const Component = CoreView.extend(
ChildViewsSupport,
ViewStateSupport,
ClassNamesSupport,
InstrumentationSupport,
AriaRoleSupport,
TargetActionSupport,
ActionSupport,
Expand Down
40 changes: 35 additions & 5 deletions packages/ember-glimmer/lib/syntax/curly-component.js
Expand Up @@ -6,6 +6,7 @@ import processArgs from '../utils/process-args';
import { privatize as P } from 'container/registry';
import assign from 'ember-metal/assign';
import get from 'ember-metal/property_get';
import { _instrumentStart } from 'ember-metal/instrumentation';
import { ComponentDefinition } from 'glimmer-runtime';
import Component from '../component';

Expand Down Expand Up @@ -116,15 +117,32 @@ export class CurlyComponentSyntax extends StatementSyntax {
}
}

function NOOP() {}

class ComponentStateBucket {
constructor(component, args) {
constructor(component, args, finalizer) {
this.component = component;
this.classRef = null;
this.args = args;
this.argsRevision = args.tag.value();
this.finalizer = finalizer;
}

finalize() {
let { finalizer } = this;
finalizer();
this.finalizer = NOOP;
}
}

function initialRenderInstrumentDetails(component) {
return component.instrumentDetails({ initialRender: true });
}

function rerenderInstrumentDetails(component) {
return component.instrumentDetails({ initialRender: false });
}

class CurlyComponentManager {
prepareArgs(definition, args) {
validatePositionalParameters(args.named, args.positional.values, definition.ComponentClass.positionalParams);
Expand Down Expand Up @@ -177,6 +195,8 @@ class CurlyComponentManager {

let component = klass.create(props);

let finalizer = _instrumentStart('render.component', initialRenderInstrumentDetails, component);

dynamicScope.view = component;
dynamicScope.targetObject = component;

Expand All @@ -187,7 +207,7 @@ class CurlyComponentManager {
component.trigger('willInsertElement');
component.trigger('willRender');

let bucket = new ComponentStateBucket(component, processedArgs);
let bucket = new ComponentStateBucket(component, processedArgs, finalizer);

if (args.named.has('class')) {
bucket.classRef = args.named.get('class');
Expand Down Expand Up @@ -258,8 +278,9 @@ class CurlyComponentManager {
component._transitionTo('hasElement');
}

didRenderLayout({ component }, bounds) {
component[BOUNDS] = bounds;
didRenderLayout(bucket, bounds) {
bucket.component[BOUNDS] = bounds;
bucket.finalize();
}

getTag({ component }) {
Expand All @@ -275,6 +296,8 @@ class CurlyComponentManager {
update(bucket, _, dynamicScope) {
let { component, args, argsRevision } = bucket;

bucket.finalizer = _instrumentStart('render.component', rerenderInstrumentDetails, component);

if (!args.tag.validate(argsRevision)) {
let { attrs, props } = args.value();

Expand All @@ -295,6 +318,10 @@ class CurlyComponentManager {
component.trigger('willRender');
}

didUpdateLayout(bucket) {
bucket.finalize();
}

didUpdate({ component }) {
component.trigger('didUpdate');
component.trigger('didRender');
Expand All @@ -310,6 +337,9 @@ const MANAGER = new CurlyComponentManager();
class TopComponentManager extends CurlyComponentManager {
create(definition, args, dynamicScope, hasBlock) {
let component = definition.ComponentClass;

let finalizer = _instrumentStart('render.component', initialRenderInstrumentDetails, component);

dynamicScope.view = component;
dynamicScope.targetObject = component;

Expand All @@ -320,7 +350,7 @@ class TopComponentManager extends CurlyComponentManager {

processComponentInitializationAssertions(component, {});

return new ComponentStateBucket(component, args);
return new ComponentStateBucket(component, args, finalizer);
}
}

Expand Down
44 changes: 36 additions & 8 deletions packages/ember-glimmer/lib/syntax/outlet.js
@@ -1,5 +1,6 @@
import { ArgsSyntax, StatementSyntax } from 'glimmer-runtime';
import { generateGuid, guidFor } from 'ember-metal/utils';
import { _instrumentStart } from 'ember-metal/instrumentation';
import { RootReference } from '../utils/references';

function outletComponentFor(vm) {
Expand Down Expand Up @@ -94,6 +95,29 @@ function revalidate(definition, lastState, newState) {
return null;
}

function instrumentationPayload({ render: { name, outlet } }) {
return { object: `${name}:${outlet}` };
}

function NOOP() {}

class StateBucket {
constructor(outletState) {
this.outletState = outletState;
this.instrument();
}

instrument() {
this.finalizer = _instrumentStart('render.outlet', instrumentationPayload, this.outletState);
}

finalize() {
let { finalizer } = this;
finalizer();
this.finalizer = NOOP;
}
}

class AbstractOutletComponentManager {
prepareArgs(definition, args) {
return args;
Expand All @@ -103,29 +127,33 @@ class AbstractOutletComponentManager {
throw new Error('Not implemented: create');
}

getSelf(state) {
return new RootReference(state.render.controller);
getSelf({ outletState }) {
return new RootReference(outletState.render.controller);
}

getTag(state) {
getTag() {
return null;
}

getDestructor(state) {
getDestructor() {
return null;
}

didRenderLayout(bucket) {
bucket.finalize();
}

didCreateElement() {}
didRenderLayout() {}
didCreate(state) {}
update(state, args, dynamicScope) {}
update(bucket) {}
didUpdateLayout(bucket) {}
didUpdate(state) {}
}

class TopLevelOutletComponentManager extends AbstractOutletComponentManager {
create(definition, args, dynamicScope) {
dynamicScope.isTopLevel = false;
return dynamicScope.outletState.value();
return new StateBucket(dynamicScope.outletState.value());
}

layoutFor(definition, bucket, env) {
Expand All @@ -140,7 +168,7 @@ class OutletComponentManager extends AbstractOutletComponentManager {
let outletStateReference = dynamicScope.outletState = dynamicScope.outletState.get(definition.outletName);
let outletState = outletStateReference.value();
dynamicScope.targetObject = outletState.render.controller;
return outletState;
return new StateBucket(outletState);
}

layoutFor(definition, bucket, env) {
Expand Down
1 change: 1 addition & 0 deletions packages/ember-glimmer/lib/syntax/render.js
Expand Up @@ -82,6 +82,7 @@ class AbstractRenderManager {
didRenderLayout() {}
didCreate(state) {}
update(state, args, dynamicScope) {}
didUpdateLayout() {}
didUpdate(state) {}
}

Expand Down
@@ -0,0 +1,137 @@
import { moduleFor, RenderingTest } from '../../utils/test-case';
import { Component } from '../../utils/helpers';
import { subscribe, reset } from 'ember-metal/instrumentation';
import { set } from 'ember-metal/property_set';

moduleFor('Components instrumentation', class extends RenderingTest {
constructor() {
super();

this.resetEvents();

subscribe('render.component', {
before: (name, timestamp, payload) => {
if (payload.view !== this.component) {
this.actual.before.push(payload);
}
},
after: (name, timestamp, payload) => {
if (payload.view !== this.component) {
this.actual.after.push(payload);
}
}
});
}

resetEvents() {
this.expected = {
before: [],
after: []
};

this.actual = {
before: [],
after: []
};
}

teardown() {
this.assert.deepEqual(this.actual.before, [], 'No unexpected events (before)');
this.assert.deepEqual(this.actual.after, [], 'No unexpected events (after)');
super.teardown();
reset();
}

['@test zomg'](assert) { assert.ok(true); }

['@test it should receive an instrumentation event for both initial render and updates'](assert) {
let testCase = this;

let BaseClass = Component.extend({
tagName: '',

willRender() {
testCase.expected.before.push(this);
testCase.expected.after.unshift(this);
}
});

this.registerComponent('x-bar', {
template: '[x-bar: {{bar}}] {{yield}}',
ComponentClass: BaseClass.extend()
});

this.registerComponent('x-baz', {
template: '[x-baz: {{baz}}]',
ComponentClass: BaseClass.extend()
});

this.registerComponent('x-bat', {
template: '[x-bat: {{bat}}]',
ComponentClass: BaseClass.extend()
});

this.render(`[-top-level: {{foo}}] {{#x-bar bar=bar}}{{x-baz baz=baz}}{{/x-bar}} {{x-bat bat=bat}}`, {
foo: 'foo', bar: 'bar', baz: 'baz', bat: 'bat'
});

this.assertText('[-top-level: foo] [x-bar: bar] [x-baz: baz] [x-bat: bat]');

this.assertEvents('after initial render', true);

this.runTask(() => this.rerender());

this.assertEvents('after no-op rerender');

this.runTask(() => set(this.context, 'foo', 'FOO'));

this.assertEvents('after updating top-level');

this.runTask(() => set(this.context, 'baz', 'BAZ'));

this.assertEvents('after updating inner-most');

this.runTask(() => {
set(this.context, 'bar', 'BAR');
set(this.context, 'bat', 'BAT');
});

this.assertEvents('after updating the rest');

this.runTask(() => {
set(this.context, 'foo', 'FOO');
set(this.context, 'bar', 'BAR');
set(this.context, 'baz', 'BAZ');
set(this.context, 'bat', 'BAT');
});

this.assertEvents('after reset');
}

assertEvents(label, initialRender = false) {
let { actual, expected } = this;

this.assert.strictEqual(actual.before.length, actual.after.length, `${label}: before and after callbacks should be balanced`);

this._assertEvents(`${label} (before):`, actual.before, expected.before, initialRender);
this._assertEvents(`${label} (after):`, actual.before, expected.before, initialRender);

this.resetEvents();
}

_assertEvents(label, actual, expected, initialRender) {
this.assert.equal(actual.length, expected.length , `${label}: expected ${expected.length} and got ${actual.length}`);

actual.forEach((payload, i) => this.assertPayload(payload, expected[i], initialRender));
}

assertPayload(payload, component, initialRender) {
this.assert.equal(payload.object, component.toString(), 'payload.object');
this.assert.equal(payload.containerKey, component._debugContainerKey, 'payload.containerKey');
this.assert.equal(payload.view, component, 'payload.view');

if (this.isGlimmer) {
this.assert.strictEqual(payload.initialRender, initialRender, 'payload.initialRender');
}
}
});
1 change: 0 additions & 1 deletion packages/ember-glimmer/tests/utils/abstract-test-case.js
Expand Up @@ -362,7 +362,6 @@ export class AbstractRenderingTest extends TestCase {

owner.register('component:-top-level', Component.extend(attrs));


this.component = owner.lookup('component:-top-level');

runAppend(this.component);
Expand Down

0 comments on commit ac93fc7

Please sign in to comment.