Skip to content

Commit

Permalink
add highlight render option to Render Performance tab (#1685)
Browse files Browse the repository at this point in the history
* add highlight render option to Render Performance tab

clean up

* fix lint error and change per comment

* Use bounds to find component instead of id

* change per comment

* change per comments

* change per comments

* change per comments

* added test

* replace timeout with later

* use async

* improve tests

* add test for glimmer component, to show the highlight is not supported.

* change per comments

Co-authored-by: Bing Dai <[email protected]>
  • Loading branch information
michaelbdai and Bing Dai authored Oct 18, 2021
1 parent 5dc4ccd commit f7e2143
Show file tree
Hide file tree
Showing 7 changed files with 767 additions and 23 deletions.
13 changes: 13 additions & 0 deletions app/components/render-tree-toolbar.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,17 @@
@value={{@searchValue}}
class="js-render-profiles-search"
/>
{{#if @isHighlightEnabled}}
<div class="toolbar__checkbox js-hightlight-render">
<label>
<Input
data-test-options-highlight-render
@type="checkbox"
@checked={{@shouldHighlightRender}}
{{on "change" @updateShouldHighlightRender}}
/>
Highlight render
</label>
</div>
{{/if}}
</div>
23 changes: 19 additions & 4 deletions app/controllers/render-tree.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@ import debounceComputed from 'ember-inspector/computed/debounce';
import { and, equal } from '@ember/object/computed';

export default Controller.extend({
port: service(),

initialEmpty: false,
modelEmpty: equal('model.length', 0),
modelEmpty: equal('model.profiles.length', 0),
showEmpty: and('initialEmpty', 'modelEmpty'),
shouldHighlightRender: false,

/**
* Storage is needed for remembering if the user closed the warning
Expand Down Expand Up @@ -56,16 +59,20 @@ export default Controller.extend({
return escapeRegExp(this.search.toLowerCase());
}),

isHighlightEnabled: computed('model.isHighlightSupported', function () {
return get(this.model, 'isHighlightSupported');
}),

filtered: computed(
'escapedSearch',
'[email protected]',
'model.profiles.@each.name',
'search',
function () {
if (isEmpty(this.escapedSearch)) {
return this.model;
return get(this.model, 'profiles');
}

return this.model.filter((item) => {
return get(this.model, 'profiles').filter((item) => {
const regExp = new RegExp(this.escapedSearch);
return recursiveMatch(item, regExp);
});
Expand All @@ -75,6 +82,14 @@ export default Controller.extend({
closeWarning: action(function () {
this.set('isWarningClosed', true);
}),

updateShouldHighlightRender: action(function () {
const value = !this.shouldHighlightRender;
this.set('shouldHighlightRender', value);
this.port.send('render:updateShouldHighlightRender', {
shouldHighlightRender: value,
});
}),
});

function recursiveMatch(item, regExp) {
Expand Down
34 changes: 24 additions & 10 deletions app/routes/render-tree.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { action, get, set } from '@ember/object';
import EmberObject, { action, get, set } from '@ember/object';
import { inject as service } from '@ember/service';
import { Promise } from 'rsvp';
import TabRoute from 'ember-inspector/routes/tab';
Expand All @@ -9,17 +9,20 @@ export default class RenderTreeRoute extends TabRoute {
model() {
const port = this.port;
return new Promise(function (resolve) {
port.one('render:profilesAdded', function (message) {
resolve(message.profiles);
});
port.one(
'render:profilesAdded',
function ({ profiles, isHighlightSupported }) {
resolve(EmberObject.create({ profiles, isHighlightSupported }));
}
);
port.send('render:watchProfiles');
});
}

setupController(controller, model) {
super.setupController(...arguments);

if (model.length === 0) {
if (get(model, 'profiles.length') === 0) {
controller.set('initialEmpty', true);
}
const port = this.port;
Expand All @@ -37,16 +40,27 @@ export default class RenderTreeRoute extends TabRoute {
}

profilesUpdated(message) {
set(this, 'controller.model', message.profiles);
set(this, 'controller.model.profiles', message.profiles);
}

profilesAdded(message) {
const model = get(this, 'controller.model');
const currentProfiles = get(this, 'controller.model.profiles');
const profiles = message.profiles;
if (
message.isHighlightSupported !== undefined &&
message.isHighlightSupported !==
get(this, 'controller.model.isHighlightSupported')
) {
set(
this,
'controller.model.isHighlightSupported',
message.isHighlightSupported
);
}

model.pushObjects(profiles);
if (model.length > 100) {
set(this, 'controller.model', model.slice(0, 100));
currentProfiles.pushObjects(profiles);
if (currentProfiles.length > 100) {
set(this, 'controller.model.profiles', currentProfiles.slice(0, 100));
}
}

Expand Down
3 changes: 3 additions & 0 deletions app/templates/render-tree.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
@refreshPage={{action send "refreshPage"}}
@searchValue={{this.searchValue}}
@showEmpty={{this.showEmpty}}
@shouldHighlightRender={{this.shouldHighlightRender}}
@updateShouldHighlightRender={{this.updateShouldHighlightRender}}
@isHighlightEnabled={{this.isHighlightEnabled}}
/>
{{/in-element}}
{{/if}}
Expand Down
137 changes: 136 additions & 1 deletion ember_debug/models/profile-manager.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,65 @@
import ProfileNode from './profile-node';
import Ember from '../utils/ember';
import { compareVersion } from 'ember-debug/utils/version';

import { later, scheduleOnce } from '../utils/ember/runloop';
import { later, scheduleOnce, cancel } from '../utils/ember/runloop';

function getEdges(first, last, closest) {
let start = null;
let end = null;
for (let i = 0; i < closest.length; i++) {
if (closest.item(i) === first.node) start = i;
else if (closest.item(i) === last.node) end = i;
}
return [start, end];
}

function getUnfilteredRoots(first, last, closest) {
if (first.node === last.node) return [first.node];

const roots = [];

const [start, end] = getEdges(first, last, closest);

if (start === null || end === null) return [];

for (let i = start; i <= end; i++) roots.push(closest.item(i));

return roots;
}

function findRoots({ first, last, parent }) {
const closest = parent.childNodes;

const roots = getUnfilteredRoots(first, last, closest);

return roots.filter((el) => el?.nodeType === 1);
}

function makeHighlight() {
const node = document.createElement('div');
node.setAttribute('role', 'presentation');
node.setAttribute('class', 'ember-inspector-render-highlight');
return node;
}
function insertHTML(node) {
document.body.appendChild(node);
}

function insertStylesheet() {
const content = `
.ember-inspector-render-highlight {
border: 2px solid rgba(255,0,0,0.2);
box-shadow: 0px 0px 1px rgba(255,0,0,0.2);
z-index: 1000000;
pointer-events: none;
}
`;
const style = document.createElement('style');
style.appendChild(document.createTextNode(content));
document.head.appendChild(style);
return style;
}

/**
* A class for keeping track of active rendering profiles as a list.
Expand All @@ -12,11 +71,20 @@ export default class ProfileManager {
this.currentSet = [];
this._profilesAddedCallbacks = [];
this.queue = [];
this.shouldHighlightRender = false;
this.stylesheet = insertStylesheet();
// keep track of all the active highlights
this.highlights = [];
this.isHighlightEnabled = compareVersion(Ember?.VERSION, '3.20.0') !== -1;
}

began(timestamp, payload, now) {
return this.wrapForErrors(this, function () {
this.current = new ProfileNode(timestamp, payload, this.current, now);
if (this.shouldHighlightRender && payload.view) {
this._highLightView(payload.view);
}
this.current.isHighlightEnabled = this.isHighlightEnabled;
return this.current;
});
}
Expand All @@ -42,6 +110,18 @@ export default class ProfileManager {
return callback.call(context);
}

_highLightView(view) {
const symbols = Object.getOwnPropertySymbols(view);
const bounds = view[symbols.find((sym) => sym.description === 'BOUNDS')];
if (!bounds) return;

const elements = findRoots(bounds);

elements.forEach((node) => {
this._renderHighlight(node);
});
}

/**
* Push a new profile into the queue
* @param info
Expand Down Expand Up @@ -77,6 +157,61 @@ export default class ProfileManager {
}
}

teardown() {
this.stylesheet?.remove();
// remove all the active highlighted components
this._removeAllHighlights();
}

_removeAllHighlights() {
const els = this.highlights.slice(0);
els.forEach((el) => {
this._removeHighlight(el);
});
}

_removeHighlight(highlight) {
this.highlights = this.highlights.filter((item) => item !== highlight);
cancel(highlight.timeout);
highlight.el.remove();
}

_addHighlight(highlight) {
insertHTML(highlight.el);
this.highlights.push(highlight);

highlight.timeout = later(() => {
this._removeHighlight(highlight);
}, 500);
}

_constructHighlight(renderedNode) {
const rect = renderedNode.getBoundingClientRect();
const highlight = makeHighlight();

const { top, left, width, height } = rect;
const { scrollX, scrollY } = window;
const { style } = highlight;
if (style) {
style.position = 'absolute';
style.top = `${top + scrollY}px`;
style.left = `${left + scrollX}px`;
style.width = `${width}px`;
style.height = `${height}px`;
}
return highlight;
}

_renderHighlight(renderedNode) {
if (!renderedNode?.getBoundingClientRect) {
return;
}

const highlight = this._constructHighlight(renderedNode);

this._addHighlight({ el: highlight });
}

_flush() {
let entry, i;
for (i = 0; i < this.queue.length; i++) {
Expand Down
11 changes: 9 additions & 2 deletions ember_debug/render-debug.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,14 @@ export default EmberObject.extend(PortMixin, {

this.profileManager.offProfilesAdded(this, this.sendAdded);
this.profileManager.offProfilesAdded(this, this._updateComponentTree);
this.profileManager.teardown();
},

sendAdded(profiles) {
this.sendMessage('profilesAdded', { profiles });
this.sendMessage('profilesAdded', {
profiles,
isHighlightSupported: this.profileManager.isHighlightEnabled,
});
},

/**
Expand All @@ -62,6 +66,10 @@ export default EmberObject.extend(PortMixin, {
});
this.profileManager.onProfilesAdded(this, this.sendAdded);
},

updateShouldHighlightRender({ shouldHighlightRender }) {
this.profileManager.shouldHighlightRender = shouldHighlightRender;
},
},
});

Expand All @@ -79,7 +87,6 @@ function _subscribeToRenderEvents() {
payload,
now: Date.now(),
};

return profileManager.addToQueue(info);
},

Expand Down
Loading

0 comments on commit f7e2143

Please sign in to comment.