Skip to content

Commit dceadb9

Browse files
Chris GarrettRobert Jackson
authored andcommitted
[BUGFIX release] Ensures the arg proxy works with get
Previously, the arg proxy used a custom system for passing along its `capturedArgs` directly to `getChainTags`, in order to interoperate with computed properties. As it turns out, there are a number of other places where the correct tag needs to be gotten and setup. This PR replaces the `UNKNOWN_PROPERTY_TAG` system with a more general `CUSTOM_TAG_FOR` system. In this system, if we detect that an object has this method, we defer to it to get the tag, even if the property was defined on the object. There are two users of this system: - ProxyMixin - The arg proxy Given that it's private, and we have relatively few use cases, I believe this is the cleanest solution at the moment. The alternative would be to keep `UNKNOWN_PROPERTY_TAG` and also add `CUSTOM_TAG_FOR`, but that seems unnecessary given low usage.
1 parent 7ffd647 commit dceadb9

File tree

7 files changed

+112
-56
lines changed

7 files changed

+112
-56
lines changed

packages/@ember/-internals/glimmer/lib/component-managers/custom.ts

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ARGS_PROXY_TAGS, consume } from '@ember/-internals/metal';
1+
import { consume, CUSTOM_TAG_FOR } from '@ember/-internals/metal';
22
import { Factory } from '@ember/-internals/owner';
33
import { HAS_NATIVE_PROXY } from '@ember/-internals/utils';
44
import { OwnedTemplateMeta } from '@ember/-internals/views';
@@ -192,34 +192,41 @@ export default class CustomComponentManager<ComponentInstance>
192192
): CustomComponentState<ComponentInstance> {
193193
const { delegate } = definition;
194194
const capturedArgs = args.capture();
195+
const namedArgs = capturedArgs.named;
195196

196197
let value;
197198
let namedArgsProxy = {};
198199

199200
if (EMBER_CUSTOM_COMPONENT_ARG_PROXY) {
201+
let getTag = (key: string) => {
202+
return namedArgs.get(key).tag;
203+
};
204+
200205
if (HAS_NATIVE_PROXY) {
201206
let handler: ProxyHandler<{}> = {
202207
get(_target, prop) {
203-
if (capturedArgs.named.has(prop as string)) {
204-
let ref = capturedArgs.named.get(prop as string);
208+
if (namedArgs.has(prop as string)) {
209+
let ref = namedArgs.get(prop as string);
205210
consume(ref.tag);
206211

207212
return ref.value();
213+
} else if (prop === CUSTOM_TAG_FOR) {
214+
return getTag;
208215
}
209216
},
210217

211218
has(_target, prop) {
212-
return capturedArgs.named.has(prop as string);
219+
return namedArgs.has(prop as string);
213220
},
214221

215222
ownKeys(_target) {
216-
return capturedArgs.named.names;
223+
return namedArgs.names;
217224
},
218225

219226
getOwnPropertyDescriptor(_target, prop) {
220227
assert(
221228
'args proxies do not have real property descriptors, so you should never need to call getOwnPropertyDescriptor yourself. This code exists for enumerability, such as in for-in loops and Object.keys()',
222-
capturedArgs.named.has(prop as string)
229+
namedArgs.has(prop as string)
223230
);
224231

225232
return {
@@ -243,12 +250,18 @@ export default class CustomComponentManager<ComponentInstance>
243250

244251
namedArgsProxy = new Proxy(namedArgsProxy, handler);
245252
} else {
246-
capturedArgs.named.names.forEach(name => {
253+
Object.defineProperty(namedArgsProxy, CUSTOM_TAG_FOR, {
254+
configurable: false,
255+
enumerable: false,
256+
value: getTag,
257+
});
258+
259+
namedArgs.names.forEach(name => {
247260
Object.defineProperty(namedArgsProxy, name, {
248261
enumerable: true,
249262
configurable: true,
250263
get() {
251-
let ref = capturedArgs.named.get(name);
264+
let ref = namedArgs.get(name);
252265
consume(ref.tag);
253266

254267
return ref.value();
@@ -257,8 +270,6 @@ export default class CustomComponentManager<ComponentInstance>
257270
});
258271
}
259272

260-
ARGS_PROXY_TAGS.set(namedArgsProxy, capturedArgs.named);
261-
262273
value = {
263274
named: namedArgsProxy,
264275
positional: capturedArgs.positional.value(),

packages/@ember/-internals/glimmer/tests/integration/components/tracked-test.js

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Object as EmberObject, A, ArrayProxy, PromiseProxyMixin } from '@ember/-internals/runtime';
22
import { EMBER_CUSTOM_COMPONENT_ARG_PROXY } from '@ember/canary-features';
3-
import { computed, tracked, nativeDescDecorator as descriptor } from '@ember/-internals/metal';
3+
import { computed, get, tracked, nativeDescDecorator as descriptor } from '@ember/-internals/metal';
44
import { Promise } from 'rsvp';
55
import { moduleFor, RenderingTestCase, strip, runTask } from 'internal-test-helpers';
66
import GlimmerishComponent from '../../utils/glimmerish-component';
@@ -568,6 +568,50 @@ if (EMBER_CUSTOM_COMPONENT_ARG_PROXY) {
568568
this.assertText('hello!');
569569
}
570570

571+
'@test args can be accessed with get()'() {
572+
class TestComponent extends GlimmerishComponent {
573+
get text() {
574+
return get(this, 'args.text');
575+
}
576+
}
577+
578+
this.registerComponent('test', {
579+
ComponentClass: TestComponent,
580+
template: '<p>{{this.text}}</p>',
581+
});
582+
583+
this.render('<Test @text={{this.text}}/>', {
584+
text: 'hello!',
585+
});
586+
587+
this.assertText('hello!');
588+
589+
runTask(() => this.context.set('text', 'hello world!'));
590+
this.assertText('hello world!');
591+
592+
runTask(() => this.context.set('text', 'hello!'));
593+
this.assertText('hello!');
594+
}
595+
596+
'@test args can be accessed with get() if no value is passed'() {
597+
class TestComponent extends GlimmerishComponent {
598+
get text() {
599+
return get(this, 'args.text') || 'hello!';
600+
}
601+
}
602+
603+
this.registerComponent('test', {
604+
ComponentClass: TestComponent,
605+
template: '<p>{{this.text}}</p>',
606+
});
607+
608+
this.render('<Test/>', {
609+
text: 'hello!',
610+
});
611+
612+
this.assertText('hello!');
613+
}
614+
571615
'@test named args are enumerable'() {
572616
class TestComponent extends GlimmerishComponent {
573617
get objectKeys() {

packages/@ember/-internals/glimmer/tests/integration/helpers/get-test.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { RenderingTestCase, moduleFor, runTask } from 'internal-test-helpers';
33
import { set, get } from '@ember/-internals/metal';
44

55
import { Component } from '../../utils/helpers';
6+
import GlimmerishComponent from '../../utils/glimmerish-component';
67

78
moduleFor(
89
'Helpers test: {{get}}',
@@ -616,5 +617,31 @@ moduleFor(
616617

617618
assert.strictEqual(this.$('#get-input').val(), 'mcintosh');
618619
}
620+
621+
'@test should be able to get an object value with a path from this.args in a glimmer component'() {
622+
class PersonComponent extends GlimmerishComponent {
623+
options = ['first', 'last', 'age'];
624+
}
625+
626+
this.registerComponent('person-wrapper', {
627+
ComponentClass: PersonComponent,
628+
template: '{{#each this.options as |option|}}{{get this.args option}}{{/each}}',
629+
});
630+
631+
this.render('<PersonWrapper @first={{first}} @last={{last}} @age={{age}}/>', {
632+
first: 'miguel',
633+
last: 'andrade',
634+
});
635+
636+
this.assertText('miguelandrade');
637+
638+
runTask(() => this.rerender());
639+
640+
this.assertText('miguelandrade');
641+
642+
runTask(() => set(this.context, 'age', 30));
643+
644+
this.assertText('miguelandrade30');
645+
}
619646
}
620647
);

packages/@ember/-internals/metal/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export {
4444
isClassicDecorator,
4545
setClassicDecorator,
4646
} from './lib/descriptor_map';
47-
export { getChainTagsForKey, ARGS_PROXY_TAGS } from './lib/chain-tags';
47+
export { getChainTagsForKey } from './lib/chain-tags';
4848
export { default as libraries, Libraries } from './lib/libraries';
4949
export { default as getProperties } from './lib/get_properties';
5050
export { default as setProperties } from './lib/set_properties';
@@ -53,7 +53,7 @@ export { default as expandProperties } from './lib/expand_properties';
5353
export { addObserver, activateObserver, removeObserver, flushAsyncObservers } from './lib/observer';
5454
export { Mixin, aliasMethod, mixin, observer, applyMixin } from './lib/mixin';
5555
export { default as inject, DEBUG_INJECTION_FUNCTIONS } from './lib/injected_property';
56-
export { tagForProperty, tagFor, markObjectAsDirty, UNKNOWN_PROPERTY_TAG } from './lib/tags';
56+
export { tagForProperty, createTagForProperty, tagFor, markObjectAsDirty, CUSTOM_TAG_FOR } from './lib/tags';
5757
export {
5858
consume,
5959
Tracker,

packages/@ember/-internals/metal/lib/chain-tags.ts

Lines changed: 0 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ import { getLastRevisionFor, peekCacheFor } from './computed_cache';
66
import { descriptorForProperty } from './descriptor_map';
77
import { tagForProperty } from './tags';
88

9-
export const ARGS_PROXY_TAGS = new WeakMap();
10-
119
export function finishLazyChains(obj: any, key: string, value: any) {
1210
let meta = peekMeta(obj);
1311
let lazyTags = meta !== null ? meta.readableLazyChainsFor(key) : undefined;
@@ -141,40 +139,6 @@ export function getChainTagsForKey(obj: any, path: string) {
141139
break;
142140
}
143141

144-
// If the segment is linking to an args proxy, we need to manually access
145-
// the tags for the args, since they are direct references and don't have a
146-
// tagForProperty. We then continue chaining like normal after it, since
147-
// you could chain off an arg if it were an object, for instance.
148-
if (segment === 'args' && ARGS_PROXY_TAGS.has(current.args)) {
149-
assert(
150-
`When watching the 'args' on a GlimmerComponent, you must watch a value on the args. You cannot watch the object itself, as it never changes.`,
151-
segmentEnd !== pathLength
152-
);
153-
154-
lastSegmentEnd = segmentEnd + 1;
155-
segmentEnd = path.indexOf('.', lastSegmentEnd);
156-
157-
if (segmentEnd === -1) {
158-
segmentEnd = pathLength;
159-
}
160-
161-
segment = path.slice(lastSegmentEnd, segmentEnd)!;
162-
163-
let namedArgs = ARGS_PROXY_TAGS.get(current.args);
164-
let ref = namedArgs.get(segment);
165-
166-
chainTags.push(ref.tag);
167-
168-
// We still need to break if we're at the end of the path.
169-
if (segmentEnd === pathLength) {
170-
break;
171-
}
172-
173-
// Otherwise, set the current value and then continue to the next segment
174-
current = ref.value();
175-
continue;
176-
}
177-
178142
// TODO: Assert that current[segment] isn't an undecorated, non-MANDATORY_SETTER/dependentKeyCompat getter
179143

180144
let propertyTag = tagForProperty(current, segment);

packages/@ember/-internals/metal/lib/tags.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,24 @@ import { DEBUG } from '@glimmer/env';
55
import { CONSTANT_TAG, createUpdatableTag, dirty, Tag } from '@glimmer/reference';
66
import { assertTagNotConsumed } from './tracked';
77

8-
export const UNKNOWN_PROPERTY_TAG = symbol('UNKNOWN_PROPERTY_TAG');
8+
export const CUSTOM_TAG_FOR = symbol('CUSTOM_TAG_FOR');
99

1010
export function tagForProperty(object: any, propertyKey: string | symbol, _meta?: Meta): Tag {
1111
let objectType = typeof object;
1212
if (objectType !== 'function' && (objectType !== 'object' || object === null)) {
1313
return CONSTANT_TAG;
1414
}
15-
let meta = _meta === undefined ? metaFor(object) : _meta;
1615

17-
if (!(propertyKey in object) && typeof object[UNKNOWN_PROPERTY_TAG] === 'function') {
18-
return object[UNKNOWN_PROPERTY_TAG](propertyKey);
16+
if (typeof object[CUSTOM_TAG_FOR] === 'function') {
17+
return object[CUSTOM_TAG_FOR](propertyKey);
1918
}
2019

20+
return createTagForProperty(object, propertyKey);
21+
}
22+
23+
export function createTagForProperty(object: object, propertyKey: string | symbol, _meta?: Meta): Tag {
24+
let meta = _meta === undefined ? metaFor(object) : _meta;
25+
2126
let tags = meta.writableTags();
2227
let tag = tags[propertyKey];
2328
if (tag) {

packages/@ember/-internals/runtime/lib/mixins/-proxy.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ import {
99
defineProperty,
1010
Mixin,
1111
tagFor,
12+
createTagForProperty,
1213
computed,
13-
UNKNOWN_PROPERTY_TAG,
14+
CUSTOM_TAG_FOR,
1415
getChainTagsForKey,
1516
} from '@ember/-internals/metal';
1617
import { setProxy } from '@ember/-internals/utils';
@@ -61,8 +62,12 @@ export default Mixin.create({
6162
return Boolean(get(this, 'content'));
6263
}),
6364

64-
[UNKNOWN_PROPERTY_TAG](key) {
65-
return combine(getChainTagsForKey(this, `content.${key}`));
65+
[CUSTOM_TAG_FOR](key) {
66+
if (key in this) {
67+
return createTagForProperty(this, key);
68+
} else {
69+
return combine(getChainTagsForKey(this, `content.${key}`));
70+
}
6671
},
6772

6873
unknownProperty(key) {

0 commit comments

Comments
 (0)