From 252beff571d93004da58c670cd7a38998383f118 Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Thu, 21 Nov 2019 16:54:06 -0800 Subject: [PATCH] Refactor render tree to use Octane idioms --- app/controllers/application.js | 30 +- app/controllers/component-tree.js | 549 +++++++++--------- app/helpers/component-indentation.js | 12 - app/helpers/component-parent-selected.js | 24 - app/routes/application.js | 14 +- app/routes/component-tree.js | 72 +-- app/templates/component-tree-toolbar.hbs | 26 +- app/templates/component-tree.hbs | 19 +- .../components/component-tree-item.hbs | 26 +- ember_debug/libs/render-tree.js | 10 +- ember_debug/view-debug.js | 46 +- 11 files changed, 377 insertions(+), 451 deletions(-) delete mode 100644 app/helpers/component-indentation.js delete mode 100644 app/helpers/component-parent-selected.js diff --git a/app/controllers/application.js b/app/controllers/application.js index cb06d5da85..47c376205d 100644 --- a/app/controllers/application.js +++ b/app/controllers/application.js @@ -66,12 +66,32 @@ export default Controller.extend({ this.port.send('objectInspector:releaseObject', { objectId: item.objectId }); }), + showInspector: action(function() { + if (this.inspectorExpanded === false) { + this.set('inspectorExpanded', true); + // Broadcast that tables have been resized (used by `x-list`). + schedule('afterRender', () => { + this.layoutService.trigger('resize', { source: 'object-inspector' }); + }); + } + }), + + hideInspector: action(function() { + if (this.inspectorExpanded === true) { + this.set('inspectorExpanded', false); + // Broadcast that tables have been resized (used by `x-list`). + schedule('afterRender', () => { + this.layoutService.trigger('resize', { source: 'object-inspector' }); + }); + } + }), + toggleInspector: action(function() { - this.toggleProperty('inspectorExpanded'); - // Broadcast that tables have been resized (used by `x-list`). - schedule('afterRender', () => { - this.layoutService.trigger('resize', { source: 'object-inspector' }); - }); + if (this.inspectorExpanded) { + this.hideInspector(); + } else { + this.showInspector(); + } }), setActive: action(function(bool) { diff --git a/app/controllers/component-tree.js b/app/controllers/component-tree.js index e9ff00c769..7306fc41eb 100644 --- a/app/controllers/component-tree.js +++ b/app/controllers/component-tree.js @@ -1,307 +1,300 @@ -import { - action, - computed -} from '@ember/object'; -import Controller, { - inject as controller -} from '@ember/controller'; +import Controller, { inject as controller } from '@ember/controller'; +import { action } from '@ember/object'; +import { inject as service } from '@ember/service'; +import { htmlSafe } from '@ember/string'; +import { tracked } from '@glimmer/tracking'; + import searchMatch from 'ember-inspector/utils/search-match'; -import { - notEmpty -} from '@ember/object/computed'; -import { - isEmpty -} from '@ember/utils'; - -import { - schedule -} from '@ember/runloop'; - -import ComponentViewItem from 'ember-inspector/models/component-view-item'; - -const flattenSearchTreeNodes = ( - searchValue, - treeNodes, - parent, - parentCount, - parentMatched, - list -) => { - let flattened = []; - - for (let treeNode of treeNodes) { - flattened.push(...flattenSearchTreeNode( - searchValue, - treeNode, - parent, - parentCount, - parentMatched, - list - )); - } - - return flattened; -}; - -/** - * Takes the `viewTree` from `view-debug`'s `sendTree()` method, and recursively - * flattens it into an array of `ComponentViewItem` objects - * @param {string} searchValue The value of the search box - * @param {*} treeNode The node in the viewTree - * @param {ComponentViewItem} parent The parent `ComponentViewItem` - * @param {number} parentCount The component hierarchy depth - * @param {boolean} parentMatched Whether the parent node is initially set to display - * @param {Array} list The accumulator, gets mutated in each call - */ -const flattenSearchTreeNode = ( - searchValue, - treeNode, - parent, - parentCount, - parentMatched, - list -) => { - let activeSearch = !isEmpty(searchValue); - let searchMatched = activeSearch ? - searchMatch(treeNode.name, searchValue) : - true; - - let viewItem = ComponentViewItem.create({ - renderNode: treeNode, - parent, - parentCount, - searchMatched, - activeSearch, - expanded: !activeSearch, - hasChildren: treeNode.children.length > 0, - children: treeNode.children - }); - - // remember if there is no active search, searchMatched will be true - let shouldAddItem = searchMatched || parentMatched; - if (shouldAddItem) { - list.push(viewItem); - } - - let newParentCount = shouldAddItem ? parentCount + 1 : 0; - - treeNode.children.forEach(child => { - flattenSearchTreeNode( - searchValue, - child, - viewItem, - newParentCount, - shouldAddItem, - list - ); - }); - return list; -}; - -export default Controller.extend({ - application: controller(), - queryParams: ['pinned'], - - /** - * The entry in the component tree corresponding to the id - * will be selected - */ - - pinned: null, - isInspecting: false, - - /** - * Bound to the search field to filter the component list. - * - * @property searchValue - * @type {String} - * @default '' - */ - searchValue: '', - - activeSearch: notEmpty('searchValue'), - - /** - * The final list that the `{{vertical-collection}}` renders - * is created in three stages: - * 1. The `viewArray` is recalculated When the controller's `viewTree` is set by the route, or when - * a user updates the search box. - * 2. The `filteredArray` CP takes the `viewArray` and caches the expanded state for each item. - * This keeps the tree from suddenly re-expanding if the `viewTree` updates after users have - * collapsed some nodes. - * 3. Once the list is rendered, when users expand/collapse a node that action directly - * toggles the `expanded` property on each item, which makes `visible` recompute. - * - * This could probably happen in one big function, but for the time being its split in the - * interest of clarity rather than performance (even if the extra CPs might avoid doing some extra - * work when users expand/contract tree nodes) - */ - displayedList: computed('filteredArray.@each.visible', function() { - return this.filteredArray.filterBy('visible'); - }), - - filteredArray: computed('viewArray.[]', function() { - let viewArray = this.viewArray; - let expandedStateCache = this.expandedStateCache; - viewArray.forEach(viewItem => { - let cachedExpansion = expandedStateCache[viewItem.id]; - if (cachedExpansion !== undefined) { - viewItem.set('expanded', cachedExpansion); + +export default class ComponentTreeController extends Controller { + queryParams = ['pinned', 'query']; + + // Estimated height for each row + itemHeight = 22; + + @controller application; + @service port; + + @tracked query; + @tracked isInspecting = false; + @tracked renderItems = []; + + _store = Object.create(null); + + set renderTree(renderTree) { + let { _store } = this; + + let store = Object.create(null); + let renderItems = []; + + let flatten = (parent, renderNode) => { + if (isInternalRenderNode(renderNode)) { + renderNode.children.forEach(node => flatten(parent, node)); } else { - expandedStateCache[viewItem.id] = viewItem.expanded; + let item = _store[renderNode.id]; + + if (item === undefined) { + item = new RenderItem(this, parent, renderNode); + } else { + item.renderNode = renderNode; + } + + store[renderNode.id] = item; + + renderItems.push(item); + + renderNode.children.forEach(node => flatten(item, node)); } - }); + }; - return viewArray; - }), + renderTree.forEach(node => flatten(null, node)); - viewArray: computed('viewTree', 'searchValue', function() { - let tree = this.viewTree; - if (!tree) { - return []; - } - return flattenSearchTreeNodes(this.searchValue, tree, null, 0, false, []); - }), - - expandedStateCache: null, //set on init - - init() { - this._super(...arguments); - this.set('expandedStateCache', {}); - }, - - /** - * Expands the component tree so that entry for the given render node will - * be shown. Recursively expands the entry's parents up to the root. - * @param {*} id The id of the render node to show - */ - expandToNode(id) { - let node = this.filteredArray.find(item => item.id === id); - if (node) { - node.expandParents(); + this._store = store; + + this.renderItems = renderItems; + } + + findItem(id) { + return this._store[id]; + } + + get pinnedItem() { + return this.findItem(this.pinned); + } + + get matchingItems() { + let { renderItems, query } = this; + + if (query) { + let match = item => searchMatch(item.name, query) || item.childItems.some(match); + renderItems = renderItems.filter(match); } - }, - - /** - * This method is basically a trick to get the `{{vertical-collection}}` in the vicinity - * of the item that's been selected. We can't directly scroll to the element but we - * can guess at how far down the list the item is. Then we can manually set the scrollTop - * of the virtual scroll. - */ - scrollTreeToItem(id) { - let selectedItemIndex = this.displayedList.findIndex(item => item.id === id); - - if (selectedItemIndex === -1) { + + return renderItems; + } + + get visibleItems() { + return this.matchingItems.filter(item => item.isVisible); + } + + @tracked _pinned = undefined; + + get pinned() { + return this._pinned; + } + + set pinned(id) { + if (this.pinned === id) { return; } - const averageItemHeight = 22; - const targetScrollTop = averageItemHeight * selectedItemIndex; - const componentTreeEl = document.querySelector('.js-component-tree'); - const height = componentTreeEl.offsetHeight; + let item = this.findItem(id); + + if (item) { + this._pinned = id; + + item.show(); - // Only scroll to item if not already in view - if (targetScrollTop < componentTreeEl.scrollTop || targetScrollTop > componentTreeEl.scrollTop + height) { - schedule('afterRender', () => { - componentTreeEl.scrollTop = targetScrollTop - (height / 2); - }); + if (item.hasInstance) { + this.port.send('objectInspector:inspectById', { objectId: item.instance }); + } else { + this.application.hideInspector(); + } + } else { + this._pinnded = undefined; + this.application.hideInspector(); } - }, - - /** - * @param {array} objects Array of render node ids - * @param {boolean} state expanded state for objects - */ - setExpandedStateForObjects(ids, state) { - this.filteredArray - .filter(item => ids.indexOf(item.id) > -1) - .forEach(item => { - item.set('expanded', state); - this.expandedStateCache[item.id] = state; - }); - }, - - /** - * Builds array of objectids and the expanded state they should be set to - * @param {ComponentViewItem} item - */ - toggleWithChildren(item) { - let newState = !item.expanded; - let ids = []; - - let collectIds = item => { - ids.push(item.id); - item.children.forEach(collectIds); - }; + } - collectIds(item); + @action toggleInspect() { + this.port.send('view:inspectViews', { inspect: !this.isInspecting }); + } - this.setExpandedStateForObjects(ids, newState); - }, + @action expandAll() { + this.renderItems.forEach(item => item.expand()); + } - /** - * Scrolls the main page to put the selected element into view - */ - scrollIntoView: action(function(id, event) { - event.stopPropagation(); - this.port.send('view:scrollIntoView', { id }); - }), + @action collapseAll() { + this.renderItems.forEach(item => item.collapse()); + } +} - showPreview: action(function(id) { - this.port.send('view:showPreview', { id }); - }), +function isInternalRenderNode(renderNode) { + return renderNode.type === 'outlet' && renderNode.name === 'main' || + renderNode.type === 'route-template' && renderNode.name === '-top-level'; +} - hidePreview: action(function() { - this.port.send('view:hidePreview'); - }), +class RenderItem { + @tracked isExpanded = true; - toggleExpanded: action(function(item, event) { - event.stopPropagation(); + constructor(controller, parentItem, renderNode) { + this.controller = controller; + this.parentItem = parentItem; + this.renderNode = renderNode; + } + + get id() { + return this.renderNode.id; + } + + get isOutlet() { + return this.renderNode.type === 'outlet'; + } + + get isEngine() { + return this.renderNode.type === 'engine'; + } + + get isRouteTemplate() { + return this.renderNode.type === 'route-template'; + } + + get isComponent() { + return this.renderNode.type === 'component'; + } + + get name() { + return this.renderNode.name; + } + + get hasInstance() { + let { instance } = this.renderNode; + return typeof instance === 'object' && instance !== null; + } + + get instance() { + if (this.hasInstance) { + return this.renderNode.instance.id; + } else { + return null; + } + } + + get hasBounds() { + return this.renderNode.bounds !== null; + } + + get isRoot() { + return this.parentItem === null; + } - if (event.altKey) { - this.toggleWithChildren(item); + get level() { + if (this.isRoot) { + return 0; } else { - item.toggleProperty('expanded'); - this.expandedStateCache[item.id] = item.get('expanded'); + return this.parentItem.level + 1; } - }), + } - viewInElementsPanel: action(function(id, event) { - event.stopPropagation(); - this.port.send('view:inspect', { id }); - }), - - /** - * Expand or collapse all component nodes - * @param {Boolean} expanded if true, expanded, if false, collapsed - */ - expandOrCollapseAll: action(function(expanded) { - this.expandedStateCache = {}; - this.filteredArray.forEach((item) => { - item.set('expanded', expanded); - this.expandedStateCache[item.id] = expanded; - }); - }), + get hasChildren() { + return this.renderNode.children.length > 0; + } - toggleViewInspection: action(function() { - this.port.send('view:inspectViews', { - inspect: !this.isInspecting + get childItems() { + return this.renderNode.children.map(child => { + return this.controller.findItem(child.id); }); - }), + } - inspect: action(function({ id, instance }) { - if (id === this.pinned) { - return; + get isVisible() { + if (this.isRoot) { + return true; + } else { + return this.parentItem.isVisible && this.parentItem.isExpanded; + } + } + + get isSelected() { + return this.id === this.controller.pinned; + } + + get isHighlighted() { + if (this.isRoot) { + return false; + } else { + return this.parentItem.isPinned || this.parentItem.isHighlighted; + } + } + + get style() { + let indentation = 25; + + indentation += this.level * 20; + + if (this.hasChildren) { + // folding triangle + indentation -= 12; + } + + return htmlSafe(`padding-left: ${indentation}px`); + } + + @action showPreview() { + this.send('view:showPreview', { id: this.id }); + } + + @action hidePreview() { + let { pinnedItem } = this.controller; + + if (pinnedItem) { + this.send('view:showPreview', { id: pinnedItem.id }); + } else { + this.send('view:hidePreview'); } + } - this.set('pinned', id); - this.expandToNode(id); - this.scrollTreeToItem(id); + @action inspect() { + this.controller.pinned = this.id; + } + + @action toggle(event) { + event.stopPropagation(); - if (typeof instance === 'object' && instance !== null) { - this.port.send('objectInspector:inspectById', { objectId: instance.id }); + if (this.isExpanded) { + this.collapse(event.altKey); } else { - this.application.set('inspectorExpanded', false); + this.expand(event.altKey); + } + } + + @action scrollIntoView() { + event.stopPropagation(); + + this.send('view:scrollIntoView', { id: this.id }); + } + + @action inspectElement() { + event.stopPropagation(); + + this.send('view:inspectElement', { id: this.id }); + } + + show() { + let item = this.parent; + + while (item) { + item.expand(); + item = item.parent; + } + } + + expand(deep = false) { + this.isExpanded = true; + + if (deep === true) { + this.childItems.forEach(child => child.expand(true)); } - }) -}); + } + + collapse(deep = false) { + this.isExpanded = false; + + if (deep === true) { + this.childItems.forEach(child => child.collapse(true)); + } + } + + send(message, payload) { + this.controller.port.send(message, payload); + } +} diff --git a/app/helpers/component-indentation.js b/app/helpers/component-indentation.js deleted file mode 100644 index d1c6915cd1..0000000000 --- a/app/helpers/component-indentation.js +++ /dev/null @@ -1,12 +0,0 @@ -import Helper from '@ember/component/helper'; -import { htmlSafe } from '@ember/string'; - -export default Helper.extend({ - compute(params) { - const [hasChildren, parentCount] = params; - const triangleOffset = hasChildren ? 12 : 0; - const padding = parentCount * 20 - triangleOffset + 25; - - return htmlSafe(`padding-left: ${padding}px;`); - } -}); diff --git a/app/helpers/component-parent-selected.js b/app/helpers/component-parent-selected.js deleted file mode 100644 index 1b27bd628a..0000000000 --- a/app/helpers/component-parent-selected.js +++ /dev/null @@ -1,24 +0,0 @@ -import Helper from '@ember/component/helper'; - -const getParentIds = function(item, arr = []) { - if (!item.parent || !item.parent.id) { - return arr; - } - - arr.push(item.parent.id); - return getParentIds(item.parent, arr); -}; - -export default Helper.extend({ - compute(params) { - const [item, selectedID] = params; - - if (!selectedID) { - return false; - } - - return !!getParentIds(item).find(function(parentID) { - return parentID === selectedID; - }); - } -}); diff --git a/app/routes/application.js b/app/routes/application.js index 76456fd4c4..ebb40889c9 100644 --- a/app/routes/application.js +++ b/app/routes/application.js @@ -1,7 +1,6 @@ import { set, get } from '@ember/object'; import Route from '@ember/routing/route'; import { inject as service } from '@ember/service'; -import { schedule } from '@ember/runloop'; import Ember from "ember"; const { @@ -31,10 +30,10 @@ export default Route.extend({ port.off('view:inspectComponent', this, this.inspectComponent); }, - inspectComponent({ viewId }) { + inspectComponent({ id }) { this.transitionTo('component-tree', { queryParams: { - pinned: viewId + pinned: id } }); }, @@ -57,7 +56,7 @@ export default Route.extend({ controller.activateMixinDetails(name, objectId, details, errors); } - this.send('expandInspector'); + this.controller.showInspector(); }, setDeprecationCount(message) { @@ -99,13 +98,6 @@ export default Route.extend({ layoutService: service('layout'), actions: { - expandInspector() { - this.set("controller.inspectorExpanded", true); - // Broadcast that tables have been resized (used by `list`). - schedule('afterRender', () => { - this.layoutService.trigger('resize', { source: 'object-inspector' }); - }); - }, inspectObject(objectId) { if (objectId) { this.port.send('objectInspector:inspectById', { objectId }); diff --git a/app/routes/component-tree.js b/app/routes/component-tree.js index 6d76b7b8d6..4f8a734a71 100644 --- a/app/routes/component-tree.js +++ b/app/routes/component-tree.js @@ -1,73 +1,53 @@ import { Promise } from 'rsvp'; import TabRoute from 'ember-inspector/routes/tab'; -export default TabRoute.extend({ - queryParams: { - pinned: { - replace: true - } - }, +export default class ComponentTreeRoute extends TabRoute { + queryParams = { + pinned: { replace: true }, + query: { replace: true }, + }; model() { return new Promise(resolve => { - this.port.one('view:viewTree', resolve); + this.port.one('view:renderTree', resolve); this.port.send('view:getTree'); }); - }, + } setupController(controller, message) { - this._super(...arguments); - this.setViewTree(message); - }, + super.setupController(...arguments); + this.setRenderTree(message); + } activate() { - this._super(...arguments); - this.port.on('view:viewTree', this, this.setViewTree); - this.port.on('view:stopInspecting', this, this.stopInspecting); + super.activate(...arguments); + this.port.on('view:renderTree', this, this.setRenderTree); this.port.on('view:startInspecting', this, this.startInspecting); + this.port.on('view:stopInspecting', this, this.stopInspecting); this.port.on('view:inspectDOMNode', this, this.inspectDOMNode); - }, + } deactivate() { - this._super(...arguments); - this.port.off('view:viewTree', this, this.setViewTree); - this.port.off('view:stopInspecting', this, this.stopInspecting); + super.deactivate(...arguments); + this.port.off('view:renderTree', this, this.setViewTree); this.port.off('view:startInspecting', this, this.startInspecting); + this.port.off('view:stopInspecting', this, this.stopInspecting); this.port.off('view:inspectDOMNode', this, this.inspectDOMNode); - }, - - setViewTree(options) { - this.set('controller.viewTree', options.tree); - - // If we're waiting for view tree to inspect a component - const componentToInspect = this.controller.pinned; - if (componentToInspect) { - this.inspectComponent(componentToInspect); - } - }, + } - inspectComponent(viewId) { - this.controller.inspect(viewId); - }, + setRenderTree({ tree }) { + this.controller.renderTree = tree; + } startInspecting() { - this.set('controller.isInspecting', true); - }, + this.controller.isInspecting = true; + } stopInspecting() { - this.set('controller.isInspecting', false); - }, + this.controller.isInspecting = false; + } inspectDOMNode({ name }) { this.port.adapter.inspectDOMNode(name); - }, - - actions: { - queryParamsDidChange(params) { - const { pinned } = params; - if (pinned) { - this.inspectComponent(pinned); - } - } } -}); +} diff --git a/app/templates/component-tree-toolbar.hbs b/app/templates/component-tree-toolbar.hbs index 7ce1d6dc76..d90694d7c5 100644 --- a/app/templates/component-tree-toolbar.hbs +++ b/app/templates/component-tree-toolbar.hbs @@ -1,13 +1,9 @@
@@ -15,31 +11,23 @@
diff --git a/app/templates/component-tree.hbs b/app/templates/component-tree.hbs index 0470c00667..241b6f3c72 100644 --- a/app/templates/component-tree.hbs +++ b/app/templates/component-tree.hbs @@ -1,20 +1,5 @@
- {{#vertical-collection - displayedList - estimateHeight=22 - key="id" - as |item| - }} - + {{#vertical-collection this.visibleItems estimateHeight=this.itemHeight key="id" as |item|}} + {{/vertical-collection}}
diff --git a/app/templates/components/component-tree-item.hbs b/app/templates/components/component-tree-item.hbs index f484db3405..e4c5432979 100644 --- a/app/templates/components/component-tree-item.hbs +++ b/app/templates/components/component-tree-item.hbs @@ -1,22 +1,22 @@
{{#if @item.hasChildren}} {{/if}} @@ -38,19 +38,15 @@ diff --git a/ember_debug/libs/render-tree.js b/ember_debug/libs/render-tree.js index fdf8a0c7f6..2a641d8a58 100644 --- a/ember_debug/libs/render-tree.js +++ b/ember_debug/libs/render-tree.js @@ -201,7 +201,11 @@ export default class RenderTree { let element = this._findNode(node.bounds, [Node.ELEMENT_NODE]); if (element) { - element.scrollIntoView(); + element.scrollIntoView({ + behavior: "smooth", + block: "center", + inline: "nearest" + }); } } @@ -209,10 +213,10 @@ export default class RenderTree { * Inspect the bounds for the given render node id in the "Elements" panel (if the render node * is found and has valid `bounds`). * - * @method inspect + * @method inspectElement * @param {string} id A render node id. */ - inspect(id) { + inspectElement(id) { let node = this.nodes[id]; if (!node || node.bounds === null) { diff --git a/ember_debug/view-debug.js b/ember_debug/view-debug.js index eaa682677e..59704d7171 100644 --- a/ember_debug/view-debug.js +++ b/ember_debug/view-debug.js @@ -10,7 +10,7 @@ const { run, Object: EmberObject, } = Ember; -const { throttle } = run; +const { backburner } = run; const { readOnly } = computed; export default EmberObject.extend(PortMixin, { @@ -35,8 +35,8 @@ export default EmberObject.extend(PortMixin, { this.viewInspection.hide(); }, - inspectViews(message) { - if (message.inspect) { + inspectViews({ inspect }) { + if (inspect) { this.startInspecting(); } else { this.stopInspecting(); @@ -47,8 +47,8 @@ export default EmberObject.extend(PortMixin, { this.renderTree.scrollIntoView(id); }, - inspect({ id }) { - this.renderTree.inspect(id); + inspectElement({ id }) { + this.renderTree.inspectElement(id); }, contextMenu() { @@ -85,6 +85,10 @@ export default EmberObject.extend(PortMixin, { this.onResize = this.onResize.bind(this); window.addEventListener('resize', this.resizeHandler); + + this.scheduledSendTree = null; + this.sendTree = this.sendTree.bind(this); + backburner.on('end', this.sendTree); }, cleanupListeners() { @@ -92,6 +96,12 @@ export default EmberObject.extend(PortMixin, { window.removeEventListener('mousedown', this.onRightClick); window.removeEventListener('resize', this.onResize); + + backburner.off('end', this.sendTree); + + if (this.scheduledSendTree) { + window.clearTimeout(this.scheduledSendTree); + } }, onRightClick(event) { @@ -105,7 +115,11 @@ export default EmberObject.extend(PortMixin, { }, inspectNearest(node) { - if (!this.viewInspection.inspectNearest(node)) { + let renderNode = this.viewInspection.inspectNearest(node); + + if (renderNode) { + this.sendMessage('inspectComponent', { id: renderNode.id }); + } else { this.adapter.log('No Ember component found.'); } }, @@ -145,13 +159,11 @@ export default EmberObject.extend(PortMixin, { }, sendTree() { - run.scheduleOnce('afterRender', this, this.scheduledSendTree); - }, + if (this.scheduledSendTree) { + return; + } - scheduledSendTree() { - // needs to trigger on the trailing edge of the wait interval, otherwise it might happen - // that we do not pick up fast route switching or 2 or more short rerenders - throttle(this, this.send, 250, false); + window.setTimeout(() => this.send(), 250); }, send() { @@ -159,21 +171,13 @@ export default EmberObject.extend(PortMixin, { return; } - this.sendMessage('viewTree', { + this.sendMessage('renderTree', { tree: this.getTree() }); }, getTree() { this.releaseCurrentObjects(); - - // TODO: why is this needed? - let emberApp = this.getOwner(); - - if (!emberApp) { - return []; - } - return this.renderTree.build(); },