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 430a6d5b39..b5a962d0a7 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
@@ -253,6 +253,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.nodeBounds(this.element.firstChild!.firstChild, this.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 = {};
@@ -441,12 +489,12 @@ class DebugRenderTreeTest extends RenderTest {
assert.deepEqual(this.delegate.getCapturedRenderTree(), [], 'there was no output');
}
- nodeBounds(_node: SimpleNode | null): CapturedBounds {
+ nodeBounds(_node: SimpleNode | null, parent?: SimpleNode): CapturedBounds {
let node = expect(_node, 'BUG: Expected node');
return {
parentElement: expect(
- node.parentNode,
+ parent || node.parentNode,
'BUG: detached node'
) as unknown as SimpleNode as SimpleElement,
firstNode: node as unknown as SimpleNode,
@@ -502,7 +550,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/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/vm/element-builder.ts b/packages/@glimmer/runtime/lib/vm/element-builder.ts
index e4e6d30012..6fb49b907a 100644
--- a/packages/@glimmer/runtime/lib/vm/element-builder.ts
+++ b/packages/@glimmer/runtime/lib/vm/element-builder.ts
@@ -1,6 +1,7 @@
import type {
AttrNamespace,
Bounds,
+ CapturedArguments,
Cursor,
CursorStackSymbol,
ElementBuilder,
@@ -12,6 +13,7 @@ import type {
Maybe,
ModifierInstance,
Nullable,
+ Reference,
SimpleComment,
SimpleDocumentFragment,
SimpleElement,
@@ -20,6 +22,7 @@ import type {
UpdatableBlock,
} from '@glimmer/interfaces';
import { destroy, registerDestructor } from '@glimmer/destroyable';
+import { createConstRef } from '@glimmer/reference';
import { assert, expect, Stack } from '@glimmer/util';
import type { DynamicAttribute } from './attributes/dynamic';
@@ -100,7 +103,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,15 +216,31 @@ export class NewElementBuilder implements ElementBuilder {
element: SimpleElement,
guid: string,
insertBefore: Maybe
- ): Nullable {
- return this.__pushRemoteElement(element, guid, insertBefore);
+ ): RemoteLiveBlock {
+ const block = this.__pushRemoteElement(element, guid, insertBefore);
+ if (this.env.debugRenderTree) {
+ const namedArgs: Record = {};
+ if (insertBefore) {
+ namedArgs['insertBefore'] = createConstRef(insertBefore, false);
+ }
+ this.env.debugRenderTree.create(block, {
+ type: 'keyword',
+ name: 'in-element',
+ args: {
+ named: namedArgs,
+ positional: [createConstRef(element, false)],
+ } as CapturedArguments,
+ instance: null,
+ });
+ }
+ return block;
}
__pushRemoteElement(
element: SimpleElement,
_guid: string,
insertBefore: Maybe
- ): Nullable {
+ ): RemoteLiveBlock {
this.pushElement(element, insertBefore);
if (insertBefore === undefined) {
@@ -236,12 +254,20 @@ export class NewElementBuilder implements ElementBuilder {
return this.pushLiveBlock(block, true);
}
- popRemoteElement() {
- this.popBlock();
+ popRemoteElement(): void {
+ const block = this.popBlock();
this.popElement();
+ const parentElement = this.element;
+ if (this.env.debugRenderTree) {
+ this.env.debugRenderTree?.didRender(block, {
+ parentElement: () => parentElement,
+ firstNode: () => block.firstNode(),
+ lastNode: () => block.lastNode(),
+ });
+ }
}
- 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 +294,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(