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

add highlight render option to Render Performance tab #1685

Merged
merged 13 commits into from
Oct 18, 2021
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),
michaelbdai marked this conversation as resolved.
Show resolved Hide resolved
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');
michaelbdai marked this conversation as resolved.
Show resolved Hide resolved
}

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(
michaelbdai marked this conversation as resolved.
Show resolved Hide resolved
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')];
michaelbdai marked this conversation as resolved.
Show resolved Hide resolved
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);

michaelbdai marked this conversation as resolved.
Show resolved Hide resolved
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