Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ import {
} from '@glimmer/validator/lib/tracking';
import { validateTag, valueForTag } from '@glimmer/validator/lib/validators';
import type Component from '../component';
import type { DynamicScope } from '../renderer';
import type { View } from '../renderer';
import { provideView, readParentView } from '../utils/render-scope';
import type RuntimeResolver from '../resolver';
import { isTemplateFactory } from '../template';
import {
Expand Down Expand Up @@ -262,13 +263,14 @@ export default class CurlyComponentManager
owner: Owner,
ComponentClass: ComponentFactory,
args: VMArguments,
{ isInteractive }: Environment,
dynamicScope: DynamicScope,
env: Environment,
callerSelfRef: Reference
): ComponentStateBucket {
let { isInteractive } = env;

// Get the nearest concrete component instance from the scope. "Virtual"
// components will be skipped.
let parentView = dynamicScope.view;
let parentView = (readParentView(env.renderScope.current) ?? null) as View | null;

// Capture the arguments, which tells Glimmer to give us our own, stable
// copy of the Arguments object that is safe to hold on to between renders.
Expand Down Expand Up @@ -309,8 +311,8 @@ export default class CurlyComponentManager
let finalizer = _instrumentStart('render.component', initialRenderInstrumentDetails, component);

// We become the new parentView for downstream components, so save our
// component off on the dynamic scope.
dynamicScope.view = component;
// component off on the render scope.
provideView(env, component);

// Unless we're the root component, we need to add ourselves to our parent
// component's childViews array.
Expand Down Expand Up @@ -554,7 +556,7 @@ const CURLY_CAPABILITIES: InternalComponentCapabilities = {
attributeHook: true,
elementHook: true,
createCaller: true,
dynamicScope: true,
renderScope: true,
updateHook: true,
createInstance: true,
wrapped: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const CAPABILITIES = {
attributeHook: false,
elementHook: false,
createCaller: true,
dynamicScope: true,
renderScope: true,
updateHook: true,
createInstance: true,
wrapped: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ import { UNDEFINED_REFERENCE, valueForRef } from '@glimmer/reference/lib/referen
import { EMPTY_ARGS } from '@glimmer/runtime/lib/vm/arguments';
import { unwrapTemplate } from './unwrap-template';

import type { DynamicScope } from '../renderer';
import type { OutletState } from '../utils/outlet';
import { provideOutletState, readOutletState } from '../utils/render-scope';
import type OutletView from '../views/outlet';

function instrumentationPayload(def: OutletDefinitionState) {
Expand Down Expand Up @@ -53,7 +53,7 @@ const CAPABILITIES: InternalComponentCapabilities = {
attributeHook: false,
elementHook: false,
createCaller: false,
dynamicScope: true,
renderScope: true,
updateHook: false,
createInstance: true,
wrapped: false,
Expand All @@ -72,17 +72,16 @@ class OutletComponentManager
_owner: InternalOwner,
definition: OutletDefinitionState,
_args: VMArguments,
env: Environment,
dynamicScope: DynamicScope
env: Environment
): OutletInstanceState {
let parentStateRef = dynamicScope.get('outletState');
let parentStateRef = readOutletState(env.renderScope.current);
let currentStateRef = definition.ref;

// This is the actual primary responsibility of the outlet component –
// it represents the switching from one route component/template into
// the next. The rest only exists to support the debug render tree and
// the old-school (and unreliable) instrumentation.
dynamicScope.set('outletState', currentStateRef);
provideOutletState(env, currentStateRef);

let state: OutletInstanceState = {
finalize: _instrumentStart('render.outlet', instrumentationPayload, definition),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ import { capabilityFlagsFrom } from '@glimmer/manager/lib/util/capabilities';
import { CONSTANT_TAG } from '@glimmer/validator/lib/validators';
import { consumeTag } from '@glimmer/validator/lib/tracking';
import type Component from '../component';
import type { DynamicScope } from '../renderer';
import ComponentStateBucket from '../utils/curly-component-state-bucket';
import CurlyComponentManager, {
DIRTY_TAG,
initialRenderInstrumentDetails,
processComponentInitializationAssertions,
} from './curly';
import { provideView } from '../utils/render-scope';

class RootComponentManager extends CurlyComponentManager {
component: Component;
Expand All @@ -34,14 +34,14 @@ class RootComponentManager extends CurlyComponentManager {
_owner: Owner,
_state: unknown,
_args: Nullable<VMArguments>,
{ isInteractive }: Environment,
dynamicScope: DynamicScope
env: Environment
) {
let { isInteractive } = env;
let component = this.component;

let finalizer = _instrumentStart('render.component', initialRenderInstrumentDetails, component);

dynamicScope.view = component;
provideView(env, component);

let hasWrappedElement = component.tagName !== '';

Expand Down Expand Up @@ -87,7 +87,7 @@ const ROOT_CAPABILITIES: InternalComponentCapabilities = {
attributeHook: true,
elementHook: true,
createCaller: true,
dynamicScope: true,
renderScope: true,
updateHook: true,
createInstance: true,
wrapped: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ const CAPABILITIES: InternalComponentCapabilities = {
attributeHook: false,
elementHook: false,
createCaller: false,
dynamicScope: false,
renderScope: false,
updateHook: false,
createInstance: true,
wrapped: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { assert } from '@ember/debug';
import type {
CapturedArguments,
Destroyable,
DynamicScope,
Environment,
InternalComponentCapabilities,
InternalComponentManager,
Expand Down Expand Up @@ -164,7 +163,7 @@ const CAPABILITIES: InternalComponentCapabilities = {
attributeHook: false,
elementHook: false,
createCaller: true,
dynamicScope: false,
renderScope: false,
updateHook: false,
createInstance: true,
wrapped: false,
Expand All @@ -186,7 +185,6 @@ class InternalManager
definition: OpaqueInternalComponentConstructor,
args: VMArguments,
_env: Environment,
_dynamicScope: DynamicScope,
caller: Reference
): InternalComponent {
assert('caller must be const', isConstRef(caller));
Expand Down
36 changes: 1 addition & 35 deletions packages/@ember/-internals/glimmer/lib/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import type {
Bounds,
Environment,
RenderResult as GlimmerRenderResult,
DynamicScope as GlimmerDynamicScope,
Template,
TemplateFactory,
EvaluationContext,
Expand All @@ -39,7 +38,6 @@ import { BOUNDS } from './component-managers/curly';
import { createRootOutlet } from './component-managers/outlet';
import { RootComponentDefinition } from './component-managers/root';
import RouterResolver from './router-resolver';
import type { OutletState } from './utils/outlet';
import OutletView from './views/outlet';
import { makeRouteTemplate } from './component-managers/route-template';
import type { IBuilder, RendererRoot } from './base-renderer';
Expand Down Expand Up @@ -67,34 +65,6 @@ export interface View {
[BOUNDS]: Bounds | null;
}

export class DynamicScope implements GlimmerDynamicScope {
constructor(
public view: View | null,
public outletState: Reference<OutletState | undefined>
) {}

child() {
return new DynamicScope(this.view, this.outletState);
}

get(key: 'outletState'): Reference<OutletState | undefined> {
assert(
`Using \`-get-dynamic-scope\` is only supported for \`outletState\` (you used \`${key}\`).`,
key === 'outletState'
);
return this.outletState;
}

set(key: 'outletState', value: Reference<OutletState | undefined>) {
assert(
`Using \`-with-dynamic-scope\` is only supported for \`outletState\` (you used \`${key}\`).`,
key === 'outletState'
);
this.outletState = value;
return value;
}
}

class ClassicRootState implements RendererRoot {
readonly type = 'classic';
public id: string;
Expand All @@ -110,7 +80,6 @@ class ClassicRootState implements RendererRoot {
template: Template,
self: Reference<unknown>,
parentElement: SimpleElement,
dynamicScope: DynamicScope,
builder: IBuilder
) {
assert(
Expand All @@ -131,8 +100,7 @@ class ClassicRootState implements RendererRoot {
owner,
self,
builder(context.env, { element: parentElement, nextSibling: null }),
layout,
dynamicScope
layout
);

let result = (this.result = iterator.sync());
Expand Down Expand Up @@ -268,15 +236,13 @@ export class Renderer extends BaseRenderer {
target: SimpleElement
): void {
let self = createConstRef(definition, 'this');
let dynamicScope = new DynamicScope(null, UNDEFINED_REFERENCE);
let rootState = new ClassicRootState(
root,
this.state.context,
this.state.owner,
this._rootTemplate,
self,
target,
dynamicScope,
this.state.builder
);
this.state.renderRoot(rootState, this);
Expand Down
8 changes: 5 additions & 3 deletions packages/@ember/-internals/glimmer/lib/syntax/outlet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { DEBUG } from '@glimmer/env';
import type {
CapturedArguments,
CurriedComponent,
DynamicScope,
RenderScopeNode,
Template,
} from '@glimmer/interfaces';
import type { Reference } from '@glimmer/reference/lib/reference';
Expand All @@ -24,6 +24,7 @@ import { OutletComponent, type OutletDefinitionState } from '../component-manage
import { makeRouteTemplate } from '../component-managers/route-template';
import { internalHelper } from '../helpers/internal-helper';
import type { OutletState } from '../utils/outlet';
import { readOutletState } from '../utils/render-scope';

/**
The `{{outlet}}` helper lets you specify where a child route will render in
Expand Down Expand Up @@ -57,15 +58,16 @@ import type { OutletState } from '../utils/outlet';
@public
*/
export const outletHelper = /*@__PURE__*/ internalHelper(
(_args: CapturedArguments, owner?: InternalOwner, scope?: DynamicScope) => {
(_args: CapturedArguments, owner?: InternalOwner, scope?: RenderScopeNode) => {
assert('Expected owner to be present, {{outlet}} requires an owner', owner);
assert(
'Expected dynamic scope to be present. You may have attempted to use the {{outlet}} keyword dynamically. This keyword cannot be used dynamically.',
scope
);

let outletStateRef = readOutletState(scope);
let outletRef = createComputeRef(() => {
let state = valueForRef(scope.get('outletState') as Reference<OutletState | undefined>);
let state = valueForRef(outletStateRef);
return state?.outlets?.main;
});

Expand Down
36 changes: 36 additions & 0 deletions packages/@ember/-internals/glimmer/lib/utils/render-scope.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { Environment, Nullable, RenderScopeNode } from '@glimmer/interfaces';
import type { Reference } from '@glimmer/reference/lib/reference';
import { UNDEFINED_REFERENCE } from '@glimmer/reference/lib/reference';
import { provideRenderScopeValue, readRenderScopeValue } from '@glimmer/runtime/lib/render-scope';
import type { OutletState } from './outlet';

// Ember's two ambient render-scope values. `OUTLET_STATE` is how each
// `{{outlet}}` hands the current route's outlet state to descendant outlets;
// `VIEW` is the classic component `parentView` chain.
const OUTLET_STATE = Symbol('OUTLET_STATE');
const VIEW = Symbol('VIEW');

export function provideOutletState(
env: Environment,
state: Reference<OutletState | undefined>
): void {
provideRenderScopeValue(env.renderScope, OUTLET_STATE, state);
}

export function readOutletState(
scope: Nullable<RenderScopeNode>
): Reference<OutletState | undefined> {
return (
(readRenderScopeValue(scope, OUTLET_STATE) as
| Reference<OutletState | undefined>
| undefined) ?? UNDEFINED_REFERENCE
);
}

export function provideView(env: Environment, view: object): void {
provideRenderScopeValue(env.renderScope, VIEW, view);
}

export function readParentView(scope: Nullable<RenderScopeNode>): object | undefined {
return readRenderScopeValue(scope, VIEW) as object | undefined;
}
Original file line number Diff line number Diff line change
Expand Up @@ -117,47 +117,5 @@ moduleFor(

this.assertText('HIBYE');
}

['@test outletState can pass through user code (liquid-fire initimate API) ']() {
this.registerTemplate(
'outer',
'A{{#-with-dynamic-vars outletState=(identity (-get-dynamic-var "outletState"))}}B{{outlet}}D{{/-with-dynamic-vars}}E'
);
this.registerTemplate('inner', 'C');

// This looks like it doesn't do anything, but its presence
// guarantees that the outletState gets converted from a reference
// to a value and then back to a reference. That is what we're
// testing here.
this.registerHelper('identity', ([a]) => a);

let outletState = {
render: {
owner: this.owner,
name: 'outer',
controller: {},
template: this.owner.lookup('template:outer')(this.owner),
},
outlets: {
main: {
render: {
owner: this.owner,
name: 'inner',
controller: {},
template: this.owner.lookup('template:inner')(this.owner),
},
outlets: Object.create(null),
},
},
};

runTask(() => this.component.setOutletState(outletState));

runAppend(this.component);

this.assertText('ABCDE');

this.assertStableRerender();
}
}
);
Loading
Loading