diff --git a/packages/@glimmer-workspace/integration-tests/test/debug-render-tree-test.ts b/packages/@glimmer-workspace/integration-tests/test/debug-render-tree-test.ts index 0a1b3c8885..f1acdc5d49 100644 --- a/packages/@glimmer-workspace/integration-tests/test/debug-render-tree-test.ts +++ b/packages/@glimmer-workspace/integration-tests/test/debug-render-tree-test.ts @@ -283,6 +283,54 @@ class DebugRenderTreeTest extends RenderTest { ]); } + @test 'in-element in tree'() { + this.registerComponent('Glimmer', 'HiWorld', 'Hi World'); + this.registerComponent( + 'Glimmer', + 'HelloWorld', + '{{#in-element this.destinationElement}}{{/in-element}}', + class extends GlimmerishComponent { + get destinationElement() { + return document.getElementById('target'); + } + } + ); + + this.render(`
`); + + this.assertRenderTree([ + { + type: 'component', + name: 'HelloWorld', + args: { positional: [], named: { arg: 'first' } }, + instance: (instance: GlimmerishComponent) => instance.args['arg'] === 'first', + template: '(unknown template module)', + bounds: this.nodeBounds(this.element.firstChild!.nextSibling), + children: [ + { + type: 'keyword', + name: 'in-element', + args: { positional: [this.element.firstChild], named: {} }, + instance: (instance: GlimmerishComponent) => instance === null, + template: null, + bounds: this.elementBounds(this.element.firstChild! as unknown as Element), + children: [ + { + type: 'component', + name: 'HiWorld', + args: { positional: [], named: {} }, + instance: (instance: GlimmerishComponent) => instance, + template: '(unknown template module)', + bounds: this.nodeBounds(this.element.firstChild!.firstChild), + children: [], + }, + ], + }, + ], + }, + ]); + } + @test 'getDebugCustomRenderTree works'() { let bucket1 = {}; let instance1 = {}; @@ -532,7 +580,7 @@ class DebugRenderTreeTest extends RenderTest { this.assertRenderNode(actualNode, expected, `${actualNode.type}:${actualNode.name}`); }); } else { - this.assert.deepEqual(actual, [], path); + this.assert.deepEqual(actual, expectedNodes, path); } } diff --git a/packages/@glimmer/interfaces/lib/dom/attributes.d.ts b/packages/@glimmer/interfaces/lib/dom/attributes.d.ts index 370671a937..82adb3bcb6 100644 --- a/packages/@glimmer/interfaces/lib/dom/attributes.d.ts +++ b/packages/@glimmer/interfaces/lib/dom/attributes.d.ts @@ -37,8 +37,8 @@ export interface DOMStack { element: SimpleElement, guid: string, insertBefore: Maybe - ): Nullable; - popRemoteElement(): void; + ): RemoteLiveBlock; + popRemoteElement(): RemoteLiveBlock; popElement(): void; openElement(tag: string, _operations?: ElementOperations): SimpleElement; flushElement(modifiers: Nullable): void; diff --git a/packages/@glimmer/interfaces/lib/runtime/debug-render-tree.d.ts b/packages/@glimmer/interfaces/lib/runtime/debug-render-tree.d.ts index f9f8ae5943..a2edcedce5 100644 --- a/packages/@glimmer/interfaces/lib/runtime/debug-render-tree.d.ts +++ b/packages/@glimmer/interfaces/lib/runtime/debug-render-tree.d.ts @@ -3,7 +3,7 @@ import type { SimpleElement, SimpleNode } from '@simple-dom/interface'; import type { Bounds } from '../dom/bounds.js'; import type { Arguments, CapturedArguments } from './arguments.js'; -export type RenderNodeType = 'outlet' | 'engine' | 'route-template' | 'component'; +export type RenderNodeType = 'outlet' | 'engine' | 'route-template' | 'component' | 'keyword'; export interface RenderNode { type: RenderNodeType; diff --git a/packages/@glimmer/node/lib/serialize-builder.ts b/packages/@glimmer/node/lib/serialize-builder.ts index e161956c36..baa8c1dacc 100644 --- a/packages/@glimmer/node/lib/serialize-builder.ts +++ b/packages/@glimmer/node/lib/serialize-builder.ts @@ -129,7 +129,7 @@ class SerializeBuilder extends NewElementBuilder implements ElementBuilder { element: SimpleElement, cursorId: string, insertBefore: Maybe = null - ): Nullable { + ): RemoteLiveBlock { let { dom } = this; let script = dom.createElement('script'); script.setAttribute('glmr', cursorId); diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts index d6c2a377dc..94542550d0 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts @@ -19,7 +19,7 @@ import { CheckOption, CheckString, } from '@glimmer/debug'; -import { associateDestroyableChild, destroy } from '@glimmer/destroyable'; +import { associateDestroyableChild, destroy, registerDestructor } from '@glimmer/destroyable'; import { getInternalModifierManager } from '@glimmer/manager'; import { createComputeRef, isConstRef, valueForRef } from '@glimmer/reference'; import { debugToString, expect, isObject } from '@glimmer/util'; @@ -32,6 +32,7 @@ import type { DynamicAttribute } from '../../vm/attributes/dynamic'; import { isCurriedType, resolveCurriedValue } from '../../curried-value'; import { APPEND_OPCODES } from '../../opcodes'; import { CONSTANTS } from '../../symbols'; +import { createCapturedArgs } from '../../vm/arguments'; import { CheckArguments, CheckOperations, CheckReference } from './-debug-strip'; import { Assert } from './vm'; @@ -71,10 +72,36 @@ APPEND_OPCODES.add(Op.PushRemoteElement, (vm) => { let block = vm.elements().pushRemoteElement(element, guid, insertBefore); if (block) vm.associateDestroyable(block); + + if (vm.env.debugRenderTree !== undefined) { + // Note that there is nothing to update – when the args for an + // {{#in-element}} changes it gets torn down and a new one is + // re-created/rendered in its place (see the `Assert`s above) + let args = createCapturedArgs( + insertBefore === undefined ? {} : { insertBefore: insertBeforeRef }, + [elementRef] + ); + + vm.env.debugRenderTree.create(block, { + type: 'keyword', + name: 'in-element', + args, + instance: null, + }); + + registerDestructor(block, () => { + vm.env.debugRenderTree?.willDestroy(block); + }); + } }); APPEND_OPCODES.add(Op.PopRemoteElement, (vm) => { - vm.elements().popRemoteElement(); + let bounds = vm.elements().popRemoteElement(); + + if (vm.env.debugRenderTree !== undefined) { + // The RemoteLiveBlock is also its bounds + vm.env.debugRenderTree.didRender(bounds, bounds); + } }); APPEND_OPCODES.add(Op.FlushElement, (vm) => { diff --git a/packages/@glimmer/runtime/lib/vm/element-builder.ts b/packages/@glimmer/runtime/lib/vm/element-builder.ts index e4e6d30012..f79273694f 100644 --- a/packages/@glimmer/runtime/lib/vm/element-builder.ts +++ b/packages/@glimmer/runtime/lib/vm/element-builder.ts @@ -100,7 +100,6 @@ export class NewElementBuilder implements ElementBuilder { constructor(env: Environment, parentNode: SimpleElement, nextSibling: Nullable) { this.pushElement(parentNode, nextSibling); - this.env = env; this.dom = env.getAppendOperations(); this.updateOperations = env.getDOM(); @@ -214,7 +213,7 @@ export class NewElementBuilder implements ElementBuilder { element: SimpleElement, guid: string, insertBefore: Maybe - ): Nullable { + ): RemoteLiveBlock { return this.__pushRemoteElement(element, guid, insertBefore); } @@ -222,7 +221,7 @@ export class NewElementBuilder implements ElementBuilder { element: SimpleElement, _guid: string, insertBefore: Maybe - ): Nullable { + ): RemoteLiveBlock { this.pushElement(element, insertBefore); if (insertBefore === undefined) { @@ -236,12 +235,14 @@ export class NewElementBuilder implements ElementBuilder { return this.pushLiveBlock(block, true); } - popRemoteElement() { - this.popBlock(); + popRemoteElement(): RemoteLiveBlock { + const block = this.popBlock(); + assert(block instanceof RemoteLiveBlock, '[BUG] expecting a RemoteLiveBlock'); this.popElement(); + return block; } - protected pushElement(element: SimpleElement, nextSibling: Maybe = null) { + protected pushElement(element: SimpleElement, nextSibling: Maybe = null): void { this[CURSOR_STACK].push(new CursorImpl(element, nextSibling)); } @@ -268,7 +269,7 @@ export class NewElementBuilder implements ElementBuilder { return element; } - willCloseElement() { + willCloseElement(): void { this.block().closeElement(); } diff --git a/packages/@glimmer/runtime/lib/vm/rehydrate-builder.ts b/packages/@glimmer/runtime/lib/vm/rehydrate-builder.ts index dbb91bd300..66501924e4 100644 --- a/packages/@glimmer/runtime/lib/vm/rehydrate-builder.ts +++ b/packages/@glimmer/runtime/lib/vm/rehydrate-builder.ts @@ -455,7 +455,7 @@ export class RehydrateBuilder extends NewElementBuilder implements ElementBuilde element: SimpleElement, cursorId: string, insertBefore: Maybe - ): Nullable { + ): RemoteLiveBlock { const marker = this.getMarker(castToBrowser(element, 'HTML'), cursorId); assert(