From dceadb99b4baccba4f20b8d0f966187caac5dda5 Mon Sep 17 00:00:00 2001 From: Chris Garrett Date: Tue, 28 Jan 2020 16:22:14 -0800 Subject: [PATCH 1/4] [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. --- .../glimmer/lib/component-managers/custom.ts | 31 +++++++++---- .../integration/components/tracked-test.js | 46 ++++++++++++++++++- .../tests/integration/helpers/get-test.js | 27 +++++++++++ packages/@ember/-internals/metal/index.ts | 4 +- .../@ember/-internals/metal/lib/chain-tags.ts | 36 --------------- packages/@ember/-internals/metal/lib/tags.ts | 13 ++++-- .../-internals/runtime/lib/mixins/-proxy.js | 11 +++-- 7 files changed, 112 insertions(+), 56 deletions(-) diff --git a/packages/@ember/-internals/glimmer/lib/component-managers/custom.ts b/packages/@ember/-internals/glimmer/lib/component-managers/custom.ts index c1179cfab03..260fbf5ba76 100644 --- a/packages/@ember/-internals/glimmer/lib/component-managers/custom.ts +++ b/packages/@ember/-internals/glimmer/lib/component-managers/custom.ts @@ -1,4 +1,4 @@ -import { ARGS_PROXY_TAGS, consume } from '@ember/-internals/metal'; +import { consume, CUSTOM_TAG_FOR } from '@ember/-internals/metal'; import { Factory } from '@ember/-internals/owner'; import { HAS_NATIVE_PROXY } from '@ember/-internals/utils'; import { OwnedTemplateMeta } from '@ember/-internals/views'; @@ -192,34 +192,41 @@ export default class CustomComponentManager ): CustomComponentState { const { delegate } = definition; const capturedArgs = args.capture(); + const namedArgs = capturedArgs.named; let value; let namedArgsProxy = {}; if (EMBER_CUSTOM_COMPONENT_ARG_PROXY) { + let getTag = (key: string) => { + return namedArgs.get(key).tag; + }; + if (HAS_NATIVE_PROXY) { let handler: ProxyHandler<{}> = { get(_target, prop) { - if (capturedArgs.named.has(prop as string)) { - let ref = capturedArgs.named.get(prop as string); + if (namedArgs.has(prop as string)) { + let ref = namedArgs.get(prop as string); consume(ref.tag); return ref.value(); + } else if (prop === CUSTOM_TAG_FOR) { + return getTag; } }, has(_target, prop) { - return capturedArgs.named.has(prop as string); + return namedArgs.has(prop as string); }, ownKeys(_target) { - return capturedArgs.named.names; + return namedArgs.names; }, getOwnPropertyDescriptor(_target, prop) { assert( '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()', - capturedArgs.named.has(prop as string) + namedArgs.has(prop as string) ); return { @@ -243,12 +250,18 @@ export default class CustomComponentManager namedArgsProxy = new Proxy(namedArgsProxy, handler); } else { - capturedArgs.named.names.forEach(name => { + Object.defineProperty(namedArgsProxy, CUSTOM_TAG_FOR, { + configurable: false, + enumerable: false, + value: getTag, + }); + + namedArgs.names.forEach(name => { Object.defineProperty(namedArgsProxy, name, { enumerable: true, configurable: true, get() { - let ref = capturedArgs.named.get(name); + let ref = namedArgs.get(name); consume(ref.tag); return ref.value(); @@ -257,8 +270,6 @@ export default class CustomComponentManager }); } - ARGS_PROXY_TAGS.set(namedArgsProxy, capturedArgs.named); - value = { named: namedArgsProxy, positional: capturedArgs.positional.value(), diff --git a/packages/@ember/-internals/glimmer/tests/integration/components/tracked-test.js b/packages/@ember/-internals/glimmer/tests/integration/components/tracked-test.js index a5890c13756..526e6441541 100644 --- a/packages/@ember/-internals/glimmer/tests/integration/components/tracked-test.js +++ b/packages/@ember/-internals/glimmer/tests/integration/components/tracked-test.js @@ -1,6 +1,6 @@ import { Object as EmberObject, A, ArrayProxy, PromiseProxyMixin } from '@ember/-internals/runtime'; import { EMBER_CUSTOM_COMPONENT_ARG_PROXY } from '@ember/canary-features'; -import { computed, tracked, nativeDescDecorator as descriptor } from '@ember/-internals/metal'; +import { computed, get, tracked, nativeDescDecorator as descriptor } from '@ember/-internals/metal'; import { Promise } from 'rsvp'; import { moduleFor, RenderingTestCase, strip, runTask } from 'internal-test-helpers'; import GlimmerishComponent from '../../utils/glimmerish-component'; @@ -568,6 +568,50 @@ if (EMBER_CUSTOM_COMPONENT_ARG_PROXY) { this.assertText('hello!'); } + '@test args can be accessed with get()'() { + class TestComponent extends GlimmerishComponent { + get text() { + return get(this, 'args.text'); + } + } + + this.registerComponent('test', { + ComponentClass: TestComponent, + template: '

{{this.text}}

', + }); + + this.render('', { + text: 'hello!', + }); + + this.assertText('hello!'); + + runTask(() => this.context.set('text', 'hello world!')); + this.assertText('hello world!'); + + runTask(() => this.context.set('text', 'hello!')); + this.assertText('hello!'); + } + + '@test args can be accessed with get() if no value is passed'() { + class TestComponent extends GlimmerishComponent { + get text() { + return get(this, 'args.text') || 'hello!'; + } + } + + this.registerComponent('test', { + ComponentClass: TestComponent, + template: '

{{this.text}}

', + }); + + this.render('', { + text: 'hello!', + }); + + this.assertText('hello!'); + } + '@test named args are enumerable'() { class TestComponent extends GlimmerishComponent { get objectKeys() { diff --git a/packages/@ember/-internals/glimmer/tests/integration/helpers/get-test.js b/packages/@ember/-internals/glimmer/tests/integration/helpers/get-test.js index 389f326db0a..9de369ebe81 100644 --- a/packages/@ember/-internals/glimmer/tests/integration/helpers/get-test.js +++ b/packages/@ember/-internals/glimmer/tests/integration/helpers/get-test.js @@ -3,6 +3,7 @@ import { RenderingTestCase, moduleFor, runTask } from 'internal-test-helpers'; import { set, get } from '@ember/-internals/metal'; import { Component } from '../../utils/helpers'; +import GlimmerishComponent from '../../utils/glimmerish-component'; moduleFor( 'Helpers test: {{get}}', @@ -616,5 +617,31 @@ moduleFor( assert.strictEqual(this.$('#get-input').val(), 'mcintosh'); } + + '@test should be able to get an object value with a path from this.args in a glimmer component'() { + class PersonComponent extends GlimmerishComponent { + options = ['first', 'last', 'age']; + } + + this.registerComponent('person-wrapper', { + ComponentClass: PersonComponent, + template: '{{#each this.options as |option|}}{{get this.args option}}{{/each}}', + }); + + this.render('', { + first: 'miguel', + last: 'andrade', + }); + + this.assertText('miguelandrade'); + + runTask(() => this.rerender()); + + this.assertText('miguelandrade'); + + runTask(() => set(this.context, 'age', 30)); + + this.assertText('miguelandrade30'); + } } ); diff --git a/packages/@ember/-internals/metal/index.ts b/packages/@ember/-internals/metal/index.ts index 3af160fd625..9bba9594d12 100644 --- a/packages/@ember/-internals/metal/index.ts +++ b/packages/@ember/-internals/metal/index.ts @@ -44,7 +44,7 @@ export { isClassicDecorator, setClassicDecorator, } from './lib/descriptor_map'; -export { getChainTagsForKey, ARGS_PROXY_TAGS } from './lib/chain-tags'; +export { getChainTagsForKey } from './lib/chain-tags'; export { default as libraries, Libraries } from './lib/libraries'; export { default as getProperties } from './lib/get_properties'; export { default as setProperties } from './lib/set_properties'; @@ -53,7 +53,7 @@ export { default as expandProperties } from './lib/expand_properties'; export { addObserver, activateObserver, removeObserver, flushAsyncObservers } from './lib/observer'; export { Mixin, aliasMethod, mixin, observer, applyMixin } from './lib/mixin'; export { default as inject, DEBUG_INJECTION_FUNCTIONS } from './lib/injected_property'; -export { tagForProperty, tagFor, markObjectAsDirty, UNKNOWN_PROPERTY_TAG } from './lib/tags'; +export { tagForProperty, createTagForProperty, tagFor, markObjectAsDirty, CUSTOM_TAG_FOR } from './lib/tags'; export { consume, Tracker, diff --git a/packages/@ember/-internals/metal/lib/chain-tags.ts b/packages/@ember/-internals/metal/lib/chain-tags.ts index eaea2d8a3de..05f5996744b 100644 --- a/packages/@ember/-internals/metal/lib/chain-tags.ts +++ b/packages/@ember/-internals/metal/lib/chain-tags.ts @@ -6,8 +6,6 @@ import { getLastRevisionFor, peekCacheFor } from './computed_cache'; import { descriptorForProperty } from './descriptor_map'; import { tagForProperty } from './tags'; -export const ARGS_PROXY_TAGS = new WeakMap(); - export function finishLazyChains(obj: any, key: string, value: any) { let meta = peekMeta(obj); let lazyTags = meta !== null ? meta.readableLazyChainsFor(key) : undefined; @@ -141,40 +139,6 @@ export function getChainTagsForKey(obj: any, path: string) { break; } - // If the segment is linking to an args proxy, we need to manually access - // the tags for the args, since they are direct references and don't have a - // tagForProperty. We then continue chaining like normal after it, since - // you could chain off an arg if it were an object, for instance. - if (segment === 'args' && ARGS_PROXY_TAGS.has(current.args)) { - assert( - `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.`, - segmentEnd !== pathLength - ); - - lastSegmentEnd = segmentEnd + 1; - segmentEnd = path.indexOf('.', lastSegmentEnd); - - if (segmentEnd === -1) { - segmentEnd = pathLength; - } - - segment = path.slice(lastSegmentEnd, segmentEnd)!; - - let namedArgs = ARGS_PROXY_TAGS.get(current.args); - let ref = namedArgs.get(segment); - - chainTags.push(ref.tag); - - // We still need to break if we're at the end of the path. - if (segmentEnd === pathLength) { - break; - } - - // Otherwise, set the current value and then continue to the next segment - current = ref.value(); - continue; - } - // TODO: Assert that current[segment] isn't an undecorated, non-MANDATORY_SETTER/dependentKeyCompat getter let propertyTag = tagForProperty(current, segment); diff --git a/packages/@ember/-internals/metal/lib/tags.ts b/packages/@ember/-internals/metal/lib/tags.ts index 9cda151358a..00a707423ef 100644 --- a/packages/@ember/-internals/metal/lib/tags.ts +++ b/packages/@ember/-internals/metal/lib/tags.ts @@ -5,19 +5,24 @@ import { DEBUG } from '@glimmer/env'; import { CONSTANT_TAG, createUpdatableTag, dirty, Tag } from '@glimmer/reference'; import { assertTagNotConsumed } from './tracked'; -export const UNKNOWN_PROPERTY_TAG = symbol('UNKNOWN_PROPERTY_TAG'); +export const CUSTOM_TAG_FOR = symbol('CUSTOM_TAG_FOR'); export function tagForProperty(object: any, propertyKey: string | symbol, _meta?: Meta): Tag { let objectType = typeof object; if (objectType !== 'function' && (objectType !== 'object' || object === null)) { return CONSTANT_TAG; } - let meta = _meta === undefined ? metaFor(object) : _meta; - if (!(propertyKey in object) && typeof object[UNKNOWN_PROPERTY_TAG] === 'function') { - return object[UNKNOWN_PROPERTY_TAG](propertyKey); + if (typeof object[CUSTOM_TAG_FOR] === 'function') { + return object[CUSTOM_TAG_FOR](propertyKey); } + return createTagForProperty(object, propertyKey); +} + +export function createTagForProperty(object: object, propertyKey: string | symbol, _meta?: Meta): Tag { + let meta = _meta === undefined ? metaFor(object) : _meta; + let tags = meta.writableTags(); let tag = tags[propertyKey]; if (tag) { diff --git a/packages/@ember/-internals/runtime/lib/mixins/-proxy.js b/packages/@ember/-internals/runtime/lib/mixins/-proxy.js index b54323468cb..95a24b8ac2f 100644 --- a/packages/@ember/-internals/runtime/lib/mixins/-proxy.js +++ b/packages/@ember/-internals/runtime/lib/mixins/-proxy.js @@ -9,8 +9,9 @@ import { defineProperty, Mixin, tagFor, + createTagForProperty, computed, - UNKNOWN_PROPERTY_TAG, + CUSTOM_TAG_FOR, getChainTagsForKey, } from '@ember/-internals/metal'; import { setProxy } from '@ember/-internals/utils'; @@ -61,8 +62,12 @@ export default Mixin.create({ return Boolean(get(this, 'content')); }), - [UNKNOWN_PROPERTY_TAG](key) { - return combine(getChainTagsForKey(this, `content.${key}`)); + [CUSTOM_TAG_FOR](key) { + if (key in this) { + return createTagForProperty(this, key); + } else { + return combine(getChainTagsForKey(this, `content.${key}`)); + } }, unknownProperty(key) { From 0aa4e5f0828a947604476bfc69553fb0e9e66a85 Mon Sep 17 00:00:00 2001 From: Chris Garrett Date: Tue, 28 Jan 2020 13:20:06 -0800 Subject: [PATCH 2/4] [BUGFIX release] Fixes tag chaining on Proxy mixins Currently, the Proxy mixin uses the private `UNKNOWN_PROPERTY_TAG` API to provide the tags for the content property that the mixin is proxying to. However, previously the chains system would do this _and_ still entangle changes to the local property. This allowed users to manually notify in subclasses of Proxy. This PR adds the tag for the property back to the returned tag, so it works the same as before. It also refactors `setupMandatorySetter`, since we now have to do that work in two places (to avoid re-entry). --- packages/@ember/-internals/metal/lib/tags.ts | 2 +- .../-internals/runtime/lib/mixins/-proxy.js | 6 ++-- .../runtime/tests/system/object_proxy_test.js | 28 +++++++++++++++++++ .../-internals/utils/lib/mandatory-setter.ts | 16 +++++++++-- 4 files changed, 47 insertions(+), 5 deletions(-) diff --git a/packages/@ember/-internals/metal/lib/tags.ts b/packages/@ember/-internals/metal/lib/tags.ts index 00a707423ef..c00aaa4c5c9 100644 --- a/packages/@ember/-internals/metal/lib/tags.ts +++ b/packages/@ember/-internals/metal/lib/tags.ts @@ -32,7 +32,7 @@ export function createTagForProperty(object: object, propertyKey: string | symbo let newTag = createUpdatableTag(); if (DEBUG) { - setupMandatorySetter!(object, propertyKey); + setupMandatorySetter!(newTag, object, propertyKey); (newTag as any)._propertyKey = propertyKey; } diff --git a/packages/@ember/-internals/runtime/lib/mixins/-proxy.js b/packages/@ember/-internals/runtime/lib/mixins/-proxy.js index 95a24b8ac2f..6a2b59a8508 100644 --- a/packages/@ember/-internals/runtime/lib/mixins/-proxy.js +++ b/packages/@ember/-internals/runtime/lib/mixins/-proxy.js @@ -63,10 +63,12 @@ export default Mixin.create({ }), [CUSTOM_TAG_FOR](key) { + let tag = createTagForProperty(this, key); + if (key in this) { - return createTagForProperty(this, key); + return tag; } else { - return combine(getChainTagsForKey(this, `content.${key}`)); + return combine([tag, ...getChainTagsForKey(this, `content.${key}`)]); } }, diff --git a/packages/@ember/-internals/runtime/tests/system/object_proxy_test.js b/packages/@ember/-internals/runtime/tests/system/object_proxy_test.js index 9a2ecda3f62..c7c2d26179a 100644 --- a/packages/@ember/-internals/runtime/tests/system/object_proxy_test.js +++ b/packages/@ember/-internals/runtime/tests/system/object_proxy_test.js @@ -338,5 +338,33 @@ moduleFor( observe: observer('foo', function() {}), }).create(); } + + async '@test custom proxies should be able to notify property changes manually'(assert) { + let proxy = ObjectProxy.extend({ + locals: { foo: 123 }, + + unknownProperty(key) { + return this.locals[key]; + }, + + setUnknownProperty(key, value) { + this.locals[key] = value; + this.notifyPropertyChange(key); + }, + }).create(); + + let count = 0; + + proxy.addObserver('foo', function() { + count++; + }); + + proxy.set('foo', 456); + await runLoopSettled(); + + assert.equal(count, 1); + assert.equal(proxy.get('foo'), 456); + assert.equal(proxy.get('locals.foo'), 456); + } } ); diff --git a/packages/@ember/-internals/utils/lib/mandatory-setter.ts b/packages/@ember/-internals/utils/lib/mandatory-setter.ts index c8dd855e49a..4b94b955b77 100644 --- a/packages/@ember/-internals/utils/lib/mandatory-setter.ts +++ b/packages/@ember/-internals/utils/lib/mandatory-setter.ts @@ -1,8 +1,12 @@ import { assert } from '@ember/debug'; +import { _WeakSet as WeakSet } from '@ember/polyfills'; import { DEBUG } from '@glimmer/env'; +import { Tag } from '@glimmer/reference'; import lookupDescriptor from './lookup-descriptor'; -export let setupMandatorySetter: ((obj: object, keyName: string | symbol) => void) | undefined; +export let setupMandatorySetter: + | ((tag: Tag, obj: object, keyName: string | symbol) => void) + | undefined; export let teardownMandatorySetter: ((obj: object, keyName: string | symbol) => void) | undefined; export let setWithMandatorySetter: | ((obj: object, keyName: string | symbol, value: any) => void) @@ -11,6 +15,8 @@ export let setWithMandatorySetter: type PropertyDescriptorWithMeta = PropertyDescriptor & { hadOwnProperty?: boolean }; if (DEBUG) { + let SEEN_TAGS = new WeakSet(); + let MANDATORY_SETTERS: WeakMap< object, // @ts-ignore @@ -21,7 +27,13 @@ if (DEBUG) { return Object.prototype.propertyIsEnumerable.call(obj, key); }; - setupMandatorySetter = function(obj: object, keyName: string | symbol) { + setupMandatorySetter = function(tag: Tag, obj: object, keyName: string | symbol) { + if (SEEN_TAGS.has(tag)) { + return; + } + + SEEN_TAGS!.add(tag); + let desc = (lookupDescriptor(obj, keyName) as PropertyDescriptorWithMeta) || {}; if (desc.get || desc.set) { From 63f9f451544aebbabf2c4873cb7b2cb5a0b66b16 Mon Sep 17 00:00:00 2001 From: Chris Garrett Date: Sat, 25 Jan 2020 09:53:45 -0800 Subject: [PATCH 3/4] [BUGFIX release] Correctly links ArrayProxy tags to `arrangedContent` Currently, if `arrangedContent` is overridden in an ArrayProxy with a computed property that depends on changes to another array/context, those changes will not propagate correctly. This is because we never link the tags of the ArrayProxy to the corresponding tags of the `arrangedContent`, instead relying on array observers to propagate changes. This works when the underlying array is being changed directly, but _doesn't_ work if the array is being replaced entirely (e.g. the computed property has invalidated and needs to recompute). This PR ensures that ArrayProxy tags are setup correctly, so that if `arrangedContent` ever changes, the proxy will also propagate those changes. This will affect anything that depends on the ArrayProxy directly, such as `{{#each}}` loops and other computed properties. One side effect of this is that ArrayProxy's no longer need to manually dirty themselves, and in fact attempting to do so can trigger the backtracking rerender assertion (specifically when the proxy first attempts to update/synchronize while rendering). Internally, a boolean flag has been added to the array change methods to allow it to opt-out of sending a notification. --- .../tests/integration/syntax/each-test.js | 21 ++++++++++++++++++- .../-internals/metal/lib/array_events.ts | 13 +++++++----- .../runtime/lib/system/array_proxy.js | 21 ++++++++++++++++++- 3 files changed, 48 insertions(+), 7 deletions(-) diff --git a/packages/@ember/-internals/glimmer/tests/integration/syntax/each-test.js b/packages/@ember/-internals/glimmer/tests/integration/syntax/each-test.js index ff1a9d214b0..5bc867f9f95 100644 --- a/packages/@ember/-internals/glimmer/tests/integration/syntax/each-test.js +++ b/packages/@ember/-internals/glimmer/tests/integration/syntax/each-test.js @@ -1,6 +1,6 @@ import { moduleFor, RenderingTestCase, applyMixins, strip, runTask } from 'internal-test-helpers'; -import { get, set, notifyPropertyChange } from '@ember/-internals/metal'; +import { get, set, notifyPropertyChange, computed } from '@ember/-internals/metal'; import { A as emberA, ArrayProxy, RSVP } from '@ember/-internals/runtime'; import { HAS_NATIVE_SYMBOL } from '@ember/-internals/utils'; @@ -1089,6 +1089,25 @@ moduleFor( } ); +moduleFor( + 'Syntax test: {{#each}} with array proxies, arrangedContent depends on external content', + class extends EachTest { + createList(items) { + let wrapped = emberA(items); + let proxy = ArrayProxy.extend({ + arrangedContent: computed('wrappedItems.[]', function() { + // Slice the items to ensure that updates must be propogated + return this.wrappedItems.slice(); + }), + }).create({ + wrappedItems: wrapped, + }); + + return { list: proxy, delegate: wrapped }; + } + } +); + moduleFor( 'Syntax test: {{#each as}} undefined path', class extends RenderingTestCase { diff --git a/packages/@ember/-internals/metal/lib/array_events.ts b/packages/@ember/-internals/metal/lib/array_events.ts index 306a7f1af0b..f898b76c9bf 100644 --- a/packages/@ember/-internals/metal/lib/array_events.ts +++ b/packages/@ember/-internals/metal/lib/array_events.ts @@ -32,7 +32,8 @@ export function arrayContentDidChange( array: T, startIdx: number, removeAmt: number, - addAmt: number + addAmt: number, + notify = true ): T { // if no args are passed assume everything changes if (startIdx === undefined) { @@ -50,11 +51,13 @@ export function arrayContentDidChange( let meta = peekMeta(array); - if (addAmt < 0 || removeAmt < 0 || addAmt - removeAmt !== 0) { - notifyPropertyChange(array, 'length', meta); - } + if (notify) { + if (addAmt < 0 || removeAmt < 0 || addAmt - removeAmt !== 0) { + notifyPropertyChange(array, 'length', meta); + } - notifyPropertyChange(array, '[]', meta); + notifyPropertyChange(array, '[]', meta); + } sendEvent(array, '@array:change', [array, startIdx, removeAmt, addAmt]); diff --git a/packages/@ember/-internals/runtime/lib/system/array_proxy.js b/packages/@ember/-internals/runtime/lib/system/array_proxy.js index a6514c4f78b..18810379d34 100644 --- a/packages/@ember/-internals/runtime/lib/system/array_proxy.js +++ b/packages/@ember/-internals/runtime/lib/system/array_proxy.js @@ -11,11 +11,14 @@ import { removeArrayObserver, replace, getChainTagsForKey, + tagForProperty, + arrayContentDidChange, + arrayContentWillChange, } from '@ember/-internals/metal'; import EmberObject from './object'; import { isArray, MutableArray } from '../mixins/array'; import { assert } from '@ember/debug'; -import { combine, validate, value } from '@glimmer/reference'; +import { combine, update, validate, value } from '@glimmer/reference'; const ARRAY_OBSERVER_MAPPING = { willChange: '_arrangedContentArrayWillChange', @@ -109,6 +112,12 @@ export default class ArrayProxy extends EmberObject { this._arrangedContentRevision = value(this._arrangedContentTag); this._addArrangedContentArrayObserver(); + + update(tagForProperty(this, '[]'), combine(getChainTagsForKey(this, 'arrangedContent.[]'))); + update( + tagForProperty(this, 'length'), + combine(getChainTagsForKey(this, 'arrangedContent.length')) + ); } willDestroy() { @@ -316,4 +325,14 @@ ArrayProxy.reopen(MutableArray, { @public */ arrangedContent: alias('content'), + + // Array proxies don't need to notify when they change since their `[]` tag is + // already dependent on the `[]` tag of `arrangedContent` + arrayContentWillChange(startIdx, removeAmt, addAmt) { + return arrayContentWillChange(this, startIdx, removeAmt, addAmt, false); + }, + + arrayContentDidChange(startIdx, removeAmt, addAmt) { + return arrayContentDidChange(this, startIdx, removeAmt, addAmt, false); + }, }); From a33a246fff91ab433fb04cf687b6899e6c99a838 Mon Sep 17 00:00:00 2001 From: Robert Jackson Date: Mon, 3 Feb 2020 11:08:26 -0500 Subject: [PATCH 4/4] Fix prettier issues. --- packages/@ember/-internals/metal/index.ts | 8 +++++++- packages/@ember/-internals/metal/lib/tags.ts | 6 +++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/@ember/-internals/metal/index.ts b/packages/@ember/-internals/metal/index.ts index 9bba9594d12..1fccb9c4754 100644 --- a/packages/@ember/-internals/metal/index.ts +++ b/packages/@ember/-internals/metal/index.ts @@ -53,7 +53,13 @@ export { default as expandProperties } from './lib/expand_properties'; export { addObserver, activateObserver, removeObserver, flushAsyncObservers } from './lib/observer'; export { Mixin, aliasMethod, mixin, observer, applyMixin } from './lib/mixin'; export { default as inject, DEBUG_INJECTION_FUNCTIONS } from './lib/injected_property'; -export { tagForProperty, createTagForProperty, tagFor, markObjectAsDirty, CUSTOM_TAG_FOR } from './lib/tags'; +export { + tagForProperty, + createTagForProperty, + tagFor, + markObjectAsDirty, + CUSTOM_TAG_FOR, +} from './lib/tags'; export { consume, Tracker, diff --git a/packages/@ember/-internals/metal/lib/tags.ts b/packages/@ember/-internals/metal/lib/tags.ts index c00aaa4c5c9..61c67da4d81 100644 --- a/packages/@ember/-internals/metal/lib/tags.ts +++ b/packages/@ember/-internals/metal/lib/tags.ts @@ -20,7 +20,11 @@ export function tagForProperty(object: any, propertyKey: string | symbol, _meta? return createTagForProperty(object, propertyKey); } -export function createTagForProperty(object: object, propertyKey: string | symbol, _meta?: Meta): Tag { +export function createTagForProperty( + object: object, + propertyKey: string | symbol, + _meta?: Meta +): Tag { let meta = _meta === undefined ? metaFor(object) : _meta; let tags = meta.writableTags();