From 4384558a3250bcda2f12d319bf1fb0152706334f Mon Sep 17 00:00:00 2001 From: Robbie Wagner Date: Tue, 21 Jan 2025 09:21:37 -0500 Subject: [PATCH 1/2] Remove pushObjects from RenderTree --- app/computed/{debounce.js => debounce.ts} | 7 +- .../{render-tree.js => render-tree.ts} | 94 ++++++++++--------- app/routes/render-tree.js | 66 ------------- app/routes/render-tree.ts | 92 ++++++++++++++++++ app/routes/tab.js | 14 --- app/routes/tab.ts | 21 +++++ app/services/port.ts | 1 + app/utils/escape-reg-exp.ts | 4 +- 8 files changed, 172 insertions(+), 127 deletions(-) rename app/computed/{debounce.js => debounce.ts} (75%) rename app/controllers/{render-tree.js => render-tree.ts} (58%) delete mode 100644 app/routes/render-tree.js create mode 100644 app/routes/render-tree.ts delete mode 100644 app/routes/tab.js create mode 100644 app/routes/tab.ts diff --git a/app/computed/debounce.js b/app/computed/debounce.ts similarity index 75% rename from app/computed/debounce.js rename to app/computed/debounce.ts index c89b93a909..46489a889e 100644 --- a/app/computed/debounce.js +++ b/app/computed/debounce.ts @@ -1,14 +1,15 @@ import { debounce } from '@ember/runloop'; import { computed } from '@ember/object'; +import type { AnyFn } from 'ember/-private/type-utils'; // Use this if you want a property to debounce // another property with a certain delay. // This means that every time this prop changes, // the other prop will change to the same val after [delay] -export default function (prop, delay, callback) { - let value; +export default function (prop: string, delay: number, callback: AnyFn) { + let value: unknown; - let updateVal = function () { + let updateVal = function (this: any) { this.set(prop, value); if (callback) { callback.call(this); diff --git a/app/controllers/render-tree.js b/app/controllers/render-tree.ts similarity index 58% rename from app/controllers/render-tree.js rename to app/controllers/render-tree.ts index ed8eb78d3c..c272d637fd 100644 --- a/app/controllers/render-tree.js +++ b/app/controllers/render-tree.ts @@ -3,63 +3,68 @@ import { tracked } from '@glimmer/tracking'; import { isEmpty } from '@ember/utils'; import Controller from '@ember/controller'; import { inject as service } from '@ember/service'; -import escapeRegExp from 'ember-inspector/utils/escape-reg-exp'; -import debounceComputed from 'ember-inspector/computed/debounce'; -import { and, equal } from '@ember/object/computed'; + +import escapeRegExp from '../utils/escape-reg-exp'; +// @ts-expect-error TODO: not yet typed +import debounceComputed from '../computed/debounce'; +import type WebExtension from '../services/adapters/web-extension'; +import type PortService from '../services/port'; +import type StorageService from '../services/storage'; +import type { RenderTreeModel } from '../routes/render-tree'; +import { isNullish } from '../utils/nullish'; export default class RenderTreeController extends Controller { - @service adapter; - @service port; + @service declare adapter: WebExtension; + @service declare port: PortService; /** * Storage is needed for remembering if the user closed the warning - * - * @property storage - * @type {Service} */ - @service storage; + @service declare storage: StorageService; + + declare model: RenderTreeModel; - initialEmpty = false; + @tracked initialEmpty = false; @tracked shouldHighlightRender = false; @tracked search = ''; - @equal('model.profiles.length', 0) - modelEmpty; + get escapedSearch() { + return escapeRegExp(this.search?.toLowerCase()); + } - @and('initialEmpty', 'modelEmpty') - showEmpty; + /** + * Indicate the table's header's height in pixels. + * + * @property headerHeight + * @type {Number} + */ + get headerHeight() { + return this.isWarningClosed ? 31 : 56; + } /** * Checks if the user previously closed the warning by referencing localStorage - * - * @property isWarningClosed - * @type {Boolean} */ get isWarningClosed() { return !!this.storage.getItem('is-render-tree-warning-closed'); } set isWarningClosed(value) { + // @ts-expect-error Ignore this boolean/string mismatch for now. this.storage.setItem('is-render-tree-warning-closed', value); } - /** - * Indicate the table's header's height in pixels. - * - * @property headerHeight - * @type {Number} - */ - get headerHeight() { - return this.isWarningClosed ? 31 : 56; + get modelEmpty() { + return this.model.profiles.length === 0; + } + + get showEmpty() { + return this.initialEmpty && this.modelEmpty; } // bound to the input field, updates the `search` property // 300ms after changing @debounceComputed('search', 300) - searchValue; - - get escapedSearch() { - return escapeRegExp(this.search?.toLowerCase()); - } + searchValue: any; @computed('model.isHighlightSupported') get isHighlightEnabled() { @@ -68,14 +73,16 @@ export default class RenderTreeController extends Controller { @computed('escapedSearch', 'model.profiles.@each.name', 'search') get filtered() { - if (isEmpty(this.escapedSearch)) { + if (isNullish(this.escapedSearch)) { return this.model.profiles; } - return this.model.profiles.filter((item) => { - const regExp = new RegExp(this.escapedSearch); - return recursiveMatch(item, regExp); - }); + return this.model.profiles.filter( + (item: RenderTreeModel['profiles'][number]) => { + const regExp = new RegExp(this.escapedSearch as string); + return recursiveMatch(item, regExp); + }, + ); } @action @@ -85,7 +92,7 @@ export default class RenderTreeController extends Controller { @action closeWarning() { - this.set('isWarningClosed', true); + this.isWarningClosed = true; } @action @@ -98,18 +105,19 @@ export default class RenderTreeController extends Controller { } } -function recursiveMatch(item, regExp) { - let children, child; - let name = item.name; - if (name.toLowerCase().match(regExp)) { +function recursiveMatch( + item: RenderTreeModel['profiles'][number], + regExp: string | RegExp, +) { + if (item.name.toLowerCase().match(regExp)) { return true; } - children = item.children; - for (let i = 0; i < children.length; i++) { - child = children[i]; + + for (const child of item.children) { if (recursiveMatch(child, regExp)) { return true; } } + return false; } diff --git a/app/routes/render-tree.js b/app/routes/render-tree.js deleted file mode 100644 index 721b2567a8..0000000000 --- a/app/routes/render-tree.js +++ /dev/null @@ -1,66 +0,0 @@ -import EmberObject, { get, set } from '@ember/object'; -import { inject as service } from '@ember/service'; -import { Promise } from 'rsvp'; -import TabRoute from 'ember-inspector/routes/tab'; - -export default class RenderTreeRoute extends TabRoute { - @service port; - - model() { - const port = this.port; - return new Promise(function (resolve) { - port.one( - 'render:profilesAdded', - function ({ profiles, isHighlightSupported }) { - resolve(EmberObject.create({ profiles, isHighlightSupported })); - }, - ); - port.send('render:watchProfiles'); - }); - } - - setupController(controller, model) { - super.setupController(...arguments); - - if (get(model, 'profiles.length') === 0) { - controller.set('initialEmpty', true); - } - const port = this.port; - port.on('render:profilesUpdated', this, this.profilesUpdated); - port.on('render:profilesAdded', this, this.profilesAdded); - } - - deactivate() { - super.deactivate(...arguments); - - const port = this.port; - port.off('render:profilesUpdated', this, this.profilesUpdated); - port.off('render:profilesAdded', this, this.profilesAdded); - port.send('render:releaseProfiles'); - } - - profilesUpdated(message) { - set(this, 'controller.model.profiles', message.profiles); - } - - profilesAdded(message) { - 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, - ); - } - - currentProfiles.pushObjects(profiles); - if (currentProfiles.length > 100) { - set(this, 'controller.model.profiles', currentProfiles.slice(0, 100)); - } - } -} diff --git a/app/routes/render-tree.ts b/app/routes/render-tree.ts new file mode 100644 index 0000000000..60a6dd8a88 --- /dev/null +++ b/app/routes/render-tree.ts @@ -0,0 +1,92 @@ +import EmberObject, { get, set } from '@ember/object'; +import { inject as service } from '@ember/service'; +import { Promise } from 'rsvp'; +import type Transition from '@ember/routing/transition'; + +import { TrackedArray } from 'tracked-built-ins'; + +import type PortService from '../services/port'; +import type RenderTreeController from '../controllers/render-tree'; +import TabRoute from './tab'; + +export interface Profile { + children: Array; + name: string; +} + +export interface RenderTreeModel { + isHighlightSupported: boolean; + profiles: Array; +} + +export default class RenderTreeRoute extends TabRoute { + @service declare port: PortService; + + declare controller: RenderTreeController; + + model() { + return new Promise((resolve) => { + this.port.one( + 'render:profilesAdded', + function ({ profiles, isHighlightSupported }) { + resolve( + EmberObject.create({ + profiles: new TrackedArray(profiles), + isHighlightSupported, + }), + ); + }, + ); + this.port.send('render:watchProfiles'); + }); + } + + setupController( + controller: RenderTreeController, + model: RenderTreeModel, + transition: Transition, + ) { + super.setupController(controller, model, transition); + + if (model.profiles.length === 0) { + controller.initialEmpty = true; + } + + this.port.on('render:profilesUpdated', this, this.profilesUpdated); + this.port.on('render:profilesAdded', this, this.profilesAdded); + } + + deactivate(transition: Transition) { + super.deactivate(transition); + + this.port.off('render:profilesUpdated', this, this.profilesUpdated); + this.port.off('render:profilesAdded', this, this.profilesAdded); + this.port.send('render:releaseProfiles'); + } + + profilesUpdated(message: RenderTreeModel) { + set(this.controller.model, 'profiles', message.profiles); + } + + profilesAdded(message: RenderTreeModel) { + 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, + ); + } + + // @ts-expect-error TODO: fix this type error + currentProfiles.push(profiles); + if (currentProfiles.length > 100) { + set(this.controller.model, 'profiles', currentProfiles.slice(0, 100)); + } + } +} diff --git a/app/routes/tab.js b/app/routes/tab.js deleted file mode 100644 index 8efec837ca..0000000000 --- a/app/routes/tab.js +++ /dev/null @@ -1,14 +0,0 @@ -import Route from '@ember/routing/route'; -import { scheduleOnce } from '@ember/runloop'; - -export default class TabRoute extends Route { - setupController(controller) { - super.setupController(...arguments); - - function setToolbarContainer() { - controller.set('toolbarContainer', document.querySelector('#toolbar')); - } - - scheduleOnce('afterRender', this, setToolbarContainer); - } -} diff --git a/app/routes/tab.ts b/app/routes/tab.ts new file mode 100644 index 0000000000..1447db8d45 --- /dev/null +++ b/app/routes/tab.ts @@ -0,0 +1,21 @@ +import type Controller from '@ember/controller'; +import Route from '@ember/routing/route'; +import { scheduleOnce } from '@ember/runloop'; +import type Transition from '@ember/routing/transition'; + +export default class TabRoute extends Route { + setupController( + controller: Controller, + model: unknown, + transition: Transition, + ) { + super.setupController(controller, model, transition); + + function setToolbarContainer() { + // @ts-expect-error The controller could be different types. + controller.set('toolbarContainer', document.querySelector('#toolbar')); + } + + scheduleOnce('afterRender', this, setToolbarContainer); + } +} diff --git a/app/services/port.ts b/app/services/port.ts index 9d5f047859..7f1e6c5dcb 100644 --- a/app/services/port.ts +++ b/app/services/port.ts @@ -14,6 +14,7 @@ export interface Message { frameId?: any; from: string; name?: string; + shouldHighlightRender?: boolean; tabId?: number; type: string; unloading?: boolean; diff --git a/app/utils/escape-reg-exp.ts b/app/utils/escape-reg-exp.ts index 34dfd68c30..2f26b299c0 100644 --- a/app/utils/escape-reg-exp.ts +++ b/app/utils/escape-reg-exp.ts @@ -1,6 +1,8 @@ /* eslint-disable no-useless-escape */ -export default function (str: string) { +export default function (str?: string) { if (typeof str === 'string') { return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'); } + + return undefined; } From 86fdb7f853fee37c39eced20c697cbe8552e48dc Mon Sep 17 00:00:00 2001 From: Robbie Wagner Date: Tue, 21 Jan 2025 10:21:26 -0500 Subject: [PATCH 2/2] Fix types --- app/computed/debounce.ts | 2 +- app/controllers/render-tree.ts | 4 +--- app/routes/promise-tree.ts | 13 +++++++++---- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/app/computed/debounce.ts b/app/computed/debounce.ts index 46489a889e..1e04a6184e 100644 --- a/app/computed/debounce.ts +++ b/app/computed/debounce.ts @@ -6,7 +6,7 @@ import type { AnyFn } from 'ember/-private/type-utils'; // another property with a certain delay. // This means that every time this prop changes, // the other prop will change to the same val after [delay] -export default function (prop: string, delay: number, callback: AnyFn) { +export default function (prop: string, delay: number, callback?: AnyFn) { let value: unknown; let updateVal = function (this: any) { diff --git a/app/controllers/render-tree.ts b/app/controllers/render-tree.ts index c272d637fd..cd05a008ef 100644 --- a/app/controllers/render-tree.ts +++ b/app/controllers/render-tree.ts @@ -1,11 +1,9 @@ import { action, computed } from '@ember/object'; import { tracked } from '@glimmer/tracking'; -import { isEmpty } from '@ember/utils'; import Controller from '@ember/controller'; import { inject as service } from '@ember/service'; import escapeRegExp from '../utils/escape-reg-exp'; -// @ts-expect-error TODO: not yet typed import debounceComputed from '../computed/debounce'; import type WebExtension from '../services/adapters/web-extension'; import type PortService from '../services/port'; @@ -64,7 +62,7 @@ export default class RenderTreeController extends Controller { // bound to the input field, updates the `search` property // 300ms after changing @debounceComputed('search', 300) - searchValue: any; + declare searchValue: string; @computed('model.isHighlightSupported') get isHighlightEnabled() { diff --git a/app/routes/promise-tree.ts b/app/routes/promise-tree.ts index fd9c0ea19d..9d94844aca 100644 --- a/app/routes/promise-tree.ts +++ b/app/routes/promise-tree.ts @@ -1,11 +1,12 @@ +import type Controller from '@ember/controller'; import { set } from '@ember/object'; import { inject as service } from '@ember/service'; import { Promise } from 'rsvp'; -// @ts-expect-error TODO: not yet typed -import TabRoute from 'ember-inspector/routes/tab'; +import type Transition from '@ember/routing/transition'; import PromiseAssembler from '../libs/promise-assembler'; import type PortService from '../services/port'; +import TabRoute from '../routes/tab'; export default class PromiseTreeRoute extends TabRoute { @service declare port: PortService; @@ -31,8 +32,12 @@ export default class PromiseTreeRoute extends TabRoute { }); } - setupController() { - super.setupController(...arguments); + setupController( + controller: Controller, + model: unknown, + transition: Transition, + ) { + super.setupController(controller, model, transition); this.port.on( 'promise:instrumentWithStack',