From 6fd39d49623c0b5fc85969b58b9947e80a4f71dd Mon Sep 17 00:00:00 2001 From: Edward Faulkner Date: Sat, 7 Sep 2024 17:19:34 -0400 Subject: [PATCH 1/2] Update test matrix This switching our tests from "lts_5_8" to "release" and locks the release into 5.11 plus my bugfix backport PR https://github.com/emberjs/ember.js/pull/20743. I'm doing this because the circularity problems continue to be obnoxious, and some of the things I need to test are 5.x behaviors that are removed at 6.x. --- package.json | 3 +- patches/ember-source@5.11.0.patch | 186746 +++++++++++++++++++++++++++ pnpm-lock.yaml | 11 +- tests/scenarios/package.json | 1 + tests/scenarios/scenarios.ts | 4 +- 5 files changed, 186760 insertions(+), 5 deletions(-) create mode 100644 patches/ember-source@5.11.0.patch diff --git a/package.json b/package.json index 4375eac84..fbfeee629 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,8 @@ "babel-plugin-module-resolver@5.0.1": "5.0.0" }, "patchedDependencies": { - "ember-source@5.8.0": "patches/ember-source@5.8.0.patch" + "ember-source@5.8.0": "patches/ember-source@5.8.0.patch", + "ember-source@5.11.0": "patches/ember-source@5.11.0.patch" } }, "devDependencies": { diff --git a/patches/ember-source@5.11.0.patch b/patches/ember-source@5.11.0.patch new file mode 100644 index 000000000..a14b5d636 --- /dev/null +++ b/patches/ember-source@5.11.0.patch @@ -0,0 +1,186746 @@ +diff --git a/dist/ember-template-compiler.js b/dist/ember-template-compiler.js +index a49bddf8f5da1aa6126263a5d3e62f2f7653cd8f..f31006d80bc3fc031350ca313e38d713e5bc6333 100644 +--- a/dist/ember-template-compiler.js ++++ b/dist/ember-template-compiler.js +@@ -5,7 +5,7 @@ + * Portions Copyright 2008-2011 Apple Inc. All rights reserved. + * @license Licensed under MIT license + * See https://raw.github.com/emberjs/ember.js/master/LICENSE +- * @version 5.11.0 ++ * @version 5.11.0-backport-circularity-fixes+86872874 + */ + /* eslint-disable no-var */ + /* globals global globalThis self */ +@@ -854,6 +854,50 @@ var define, require; + } + } + ++ let assert = () => {}; ++ function setAssert(implementation) { ++ assert = implementation; ++ return implementation; ++ } ++ { ++ /** ++ Verify that a certain expectation is met, or throw a exception otherwise. ++ This is useful for communicating assumptions in the code to other human ++ readers as well as catching bugs that accidentally violates these ++ expectations. ++ Assertions are removed from production builds, so they can be freely added ++ for documentation and debugging purposes without worries of incuring any ++ performance penalty. However, because of that, they should not be used for ++ checks that could reasonably fail during normal usage. Furthermore, care ++ should be taken to avoid accidentally relying on side-effects produced from ++ evaluating the condition itself, since the code will not run in production. ++ ```javascript ++ import { assert } from '@ember/debug'; ++ // Test for truthiness ++ assert('Must pass a string', typeof str === 'string'); ++ // Fail unconditionally ++ assert('This code path should never be run'); ++ ``` ++ @method assert ++ @static ++ @for @ember/debug ++ @param {String} description Describes the expectation. This will become the ++ text of the Error thrown if the assertion fails. ++ @param {any} condition Must be truthy for the assertion to pass. If ++ falsy, an exception will be thrown. ++ @public ++ @since 1.0.0 ++ */ ++ ++ // eslint-disable-next-line no-inner-declarations ++ function assert(desc, test) { ++ if (!test) { ++ throw new Error(`Assertion Failed: ${desc}`); ++ } ++ } ++ setAssert(assert); ++ } ++ + let HANDLERS = {}; + let registerHandler$2 = function registerHandler(_type, _callback) {}; + let invoke = () => {}; +@@ -1289,7 +1333,7 @@ var define, require; + break; + } + let key = keys[i]; +- (!(key) && assert('has key', key)); // Looping over array ++ assert('has key', key); // Looping over array + s += `${inspectKey(String(key))}: ${inspectValue(obj[key], depth, seen)}`; + } + s += ' }'; +@@ -1994,7 +2038,6 @@ var define, require; + + // SAFETY: these casts are just straight-up lies, but the point is that they do + // not do anything in production builds. +- let assert = noop; + let info = noop; + let warn = noop; + let debug = noop; +@@ -2011,7 +2054,7 @@ var define, require; + setDebugFunction = function (type, callback) { + switch (type) { + case 'assert': +- return assert = callback; ++ return setAssert(callback); + case 'info': + return info = callback; + case 'warn': +@@ -2059,43 +2102,6 @@ var define, require; + */ + + { +- /** +- Verify that a certain expectation is met, or throw a exception otherwise. +- This is useful for communicating assumptions in the code to other human +- readers as well as catching bugs that accidentally violates these +- expectations. +- Assertions are removed from production builds, so they can be freely added +- for documentation and debugging purposes without worries of incuring any +- performance penalty. However, because of that, they should not be used for +- checks that could reasonably fail during normal usage. Furthermore, care +- should be taken to avoid accidentally relying on side-effects produced from +- evaluating the condition itself, since the code will not run in production. +- ```javascript +- import { assert } from '@ember/debug'; +- // Test for truthiness +- assert('Must pass a string', typeof str === 'string'); +- // Fail unconditionally +- assert('This code path should never be run'); +- ``` +- @method assert +- @static +- @for @ember/debug +- @param {String} description Describes the expectation. This will become the +- text of the Error thrown if the assertion fails. +- @param {any} condition Must be truthy for the assertion to pass. If +- falsy, an exception will be thrown. +- @public +- @since 1.0.0 +- */ +- +- // eslint-disable-next-line no-inner-declarations +- function assert(desc, test) { +- if (!test) { +- throw new Error(`Assertion Failed: ${desc}`); +- } +- } +- setDebugFunction('assert', assert); +- + /** + Display a debug notice. + Calls to this function are not invoked in production builds. +diff --git a/dist/ember-testing.js b/dist/ember-testing.js +index 62a254f0fad1751dbe5d92f2262bf879dd45c397..637a083fdf89d0eb5767a04548b2f0ca581bfa8d 100644 +--- a/dist/ember-testing.js ++++ b/dist/ember-testing.js +@@ -5,7 +5,7 @@ + * Portions Copyright 2008-2011 Apple Inc. All rights reserved. + * @license Licensed under MIT license + * See https://raw.github.com/emberjs/ember.js/master/LICENSE +- * @version 5.11.0 ++ * @version 5.11.0-backport-circularity-fixes+86872874 + */ + /* eslint-disable no-var */ + /* globals global globalThis self */ +diff --git a/dist/ember.debug.js b/dist/ember.debug.js +index 49133d055b53769881ad1134ed8b1c2b9ffb86f3..037b79b1b18f9c3bed665452a55493b2537842ed 100644 +--- a/dist/ember.debug.js ++++ b/dist/ember.debug.js +@@ -5,7 +5,7 @@ + * Portions Copyright 2008-2011 Apple Inc. All rights reserved. + * @license Licensed under MIT license + * See https://raw.github.com/emberjs/ember.js/master/LICENSE +- * @version 5.11.0 ++ * @version 5.11.0-backport-circularity-fixes+86872874 + */ + /* eslint-disable no-var */ + /* globals global globalThis self */ +@@ -854,6 +854,50 @@ var define, require; + setLookup + }, Symbol.toStringTag, { value: 'Module' }); + ++ let assert$1 = () => {}; ++ function setAssert(implementation) { ++ assert$1 = implementation; ++ return implementation; ++ } ++ { ++ /** ++ Verify that a certain expectation is met, or throw a exception otherwise. ++ This is useful for communicating assumptions in the code to other human ++ readers as well as catching bugs that accidentally violates these ++ expectations. ++ Assertions are removed from production builds, so they can be freely added ++ for documentation and debugging purposes without worries of incuring any ++ performance penalty. However, because of that, they should not be used for ++ checks that could reasonably fail during normal usage. Furthermore, care ++ should be taken to avoid accidentally relying on side-effects produced from ++ evaluating the condition itself, since the code will not run in production. ++ ```javascript ++ import { assert } from '@ember/debug'; ++ // Test for truthiness ++ assert('Must pass a string', typeof str === 'string'); ++ // Fail unconditionally ++ assert('This code path should never be run'); ++ ``` ++ @method assert ++ @static ++ @for @ember/debug ++ @param {String} description Describes the expectation. This will become the ++ text of the Error thrown if the assertion fails. ++ @param {any} condition Must be truthy for the assertion to pass. If ++ falsy, an exception will be thrown. ++ @public ++ @since 1.0.0 ++ */ ++ ++ // eslint-disable-next-line no-inner-declarations ++ function assert(desc, test) { ++ if (!test) { ++ throw new Error(`Assertion Failed: ${desc}`); ++ } ++ } ++ setAssert(assert); ++ } ++ + let HANDLERS = {}; + let registerHandler$2 = function registerHandler(_type, _callback) {}; + let invoke = () => {}; +@@ -1289,7 +1333,7 @@ var define, require; + break; + } + let key = keys[i]; +- (!(key) && assert$1('has key', key)); // Looping over array ++ assert$1('has key', key); // Looping over array + s += `${inspectKey(String(key))}: ${inspectValue(obj[key], depth, seen)}`; + } + s += ' }'; +@@ -1994,7 +2038,6 @@ var define, require; + + // SAFETY: these casts are just straight-up lies, but the point is that they do + // not do anything in production builds. +- let assert$1 = noop$3; + let info = noop$3; + let warn = noop$3; + let debug$2 = noop$3; +@@ -2011,7 +2054,7 @@ var define, require; + setDebugFunction = function (type, callback) { + switch (type) { + case 'assert': +- return assert$1 = callback; ++ return setAssert(callback); + case 'info': + return info = callback; + case 'warn': +@@ -2059,43 +2102,6 @@ var define, require; + */ + + { +- /** +- Verify that a certain expectation is met, or throw a exception otherwise. +- This is useful for communicating assumptions in the code to other human +- readers as well as catching bugs that accidentally violates these +- expectations. +- Assertions are removed from production builds, so they can be freely added +- for documentation and debugging purposes without worries of incuring any +- performance penalty. However, because of that, they should not be used for +- checks that could reasonably fail during normal usage. Furthermore, care +- should be taken to avoid accidentally relying on side-effects produced from +- evaluating the condition itself, since the code will not run in production. +- ```javascript +- import { assert } from '@ember/debug'; +- // Test for truthiness +- assert('Must pass a string', typeof str === 'string'); +- // Fail unconditionally +- assert('This code path should never be run'); +- ``` +- @method assert +- @static +- @for @ember/debug +- @param {String} description Describes the expectation. This will become the +- text of the Error thrown if the assertion fails. +- @param {any} condition Must be truthy for the assertion to pass. If +- falsy, an exception will be thrown. +- @public +- @since 1.0.0 +- */ +- +- // eslint-disable-next-line no-inner-declarations +- function assert(desc, test) { +- if (!test) { +- throw new Error(`Assertion Failed: ${desc}`); +- } +- } +- setDebugFunction('assert', assert); +- + /** + Display a debug notice. + Calls to this function are not invoked in production builds. +@@ -10557,6 +10563,385 @@ var define, require; + setMeta + }, Symbol.toStringTag, { value: 'Module' }); + ++ function objectAt(array, index) { ++ if (Array.isArray(array)) { ++ return array[index]; ++ } else { ++ return array.objectAt(index); ++ } ++ } ++ ++ ///////// ++ ++ // This is exported for `@tracked`, but should otherwise be avoided. Use `tagForObject`. ++ const SELF_TAG = symbol('SELF_TAG'); ++ function tagForProperty(obj, propertyKey, addMandatorySetter = false, meta) { ++ let customTagFor = getCustomTagFor(obj); ++ if (customTagFor !== undefined) { ++ return customTagFor(obj, propertyKey, addMandatorySetter); ++ } ++ let tag = tagFor(obj, propertyKey, meta); ++ if (addMandatorySetter) { ++ setupMandatorySetter(tag, obj, propertyKey); ++ } ++ return tag; ++ } ++ function tagForObject(obj) { ++ if (isObject$1(obj)) { ++ { ++ (!(!isDestroyed(obj)) && assert$1(isDestroyed(obj) ? `Cannot create a new tag for \`${toString$1(obj)}\` after it has been destroyed.` : '', !isDestroyed(obj))); ++ } ++ return tagFor(obj, SELF_TAG); ++ } ++ return CONSTANT_TAG; ++ } ++ function markObjectAsDirty(obj, propertyKey) { ++ dirtyTagFor(obj, propertyKey); ++ dirtyTagFor(obj, SELF_TAG); ++ } ++ ++ const CHAIN_PASS_THROUGH = new WeakSet(); ++ function finishLazyChains(meta, key, value) { ++ let lazyTags = meta.readableLazyChainsFor(key); ++ if (lazyTags === undefined) { ++ return; ++ } ++ if (isObject$1(value)) { ++ for (let [tag, deps] of lazyTags) { ++ UPDATE_TAG(tag, getChainTagsForKey(value, deps, tagMetaFor(value), peekMeta(value))); ++ } ++ } ++ lazyTags.length = 0; ++ } ++ function getChainTagsForKeys(obj, keys, tagMeta, meta) { ++ let tags = []; ++ for (let key of keys) { ++ getChainTags(tags, obj, key, tagMeta, meta); ++ } ++ return combine(tags); ++ } ++ function getChainTagsForKey(obj, key, tagMeta, meta) { ++ return combine(getChainTags([], obj, key, tagMeta, meta)); ++ } ++ function getChainTags(chainTags, obj, path, tagMeta, meta$1) { ++ let current = obj; ++ let currentTagMeta = tagMeta; ++ let currentMeta = meta$1; ++ let pathLength = path.length; ++ let segmentEnd = -1; ++ // prevent closures ++ let segment, descriptor; ++ ++ // eslint-disable-next-line no-constant-condition ++ while (true) { ++ let lastSegmentEnd = segmentEnd + 1; ++ segmentEnd = path.indexOf('.', lastSegmentEnd); ++ if (segmentEnd === -1) { ++ segmentEnd = pathLength; ++ } ++ segment = path.slice(lastSegmentEnd, segmentEnd); ++ ++ // If the segment is an @each, we can process it and then break ++ if (segment === '@each' && segmentEnd !== pathLength) { ++ lastSegmentEnd = segmentEnd + 1; ++ segmentEnd = path.indexOf('.', lastSegmentEnd); ++ let arrLength = current.length; ++ if (typeof arrLength !== 'number' || ++ // TODO: should the second test be `isEmberArray` instead? ++ !(Array.isArray(current) || 'objectAt' in current)) { ++ // If the current object isn't an array, there's nothing else to do, ++ // we don't watch individual properties. Break out of the loop. ++ break; ++ } else if (arrLength === 0) { ++ // Fast path for empty arrays ++ chainTags.push(tagForProperty(current, '[]')); ++ break; ++ } ++ if (segmentEnd === -1) { ++ segment = path.slice(lastSegmentEnd); ++ } else { ++ // Deprecated, remove once we turn the deprecation into an assertion ++ segment = path.slice(lastSegmentEnd, segmentEnd); ++ } ++ ++ // Push the tags for each item's property ++ for (let i = 0; i < arrLength; i++) { ++ let item = objectAt(current, i); ++ if (item) { ++ (!(typeof item === 'object') && assert$1(`When using @each to observe the array \`${current.toString()}\`, the items in the array must be objects`, typeof item === 'object')); ++ chainTags.push(tagForProperty(item, segment, true)); ++ currentMeta = peekMeta(item); ++ descriptor = currentMeta !== null ? currentMeta.peekDescriptors(segment) : undefined; ++ ++ // If the key is an alias, we need to bootstrap it ++ if (descriptor !== undefined && typeof descriptor.altKey === 'string') { ++ item[segment]; ++ } ++ } ++ } ++ ++ // Push the tag for the array length itself ++ chainTags.push(tagForProperty(current, '[]', true, currentTagMeta)); ++ break; ++ } ++ let propertyTag = tagForProperty(current, segment, true, currentTagMeta); ++ descriptor = currentMeta !== null ? currentMeta.peekDescriptors(segment) : undefined; ++ chainTags.push(propertyTag); ++ ++ // If we're at the end of the path, processing the last segment, and it's ++ // not an alias, we should _not_ get the last value, since we already have ++ // its tag. There's no reason to access it and do more work. ++ if (segmentEnd === pathLength) { ++ // If the key was an alias, we should always get the next value in order to ++ // bootstrap the alias. This is because aliases, unlike other CPs, should ++ // always be in sync with the aliased value. ++ if (CHAIN_PASS_THROUGH.has(descriptor)) { ++ current[segment]; ++ } ++ break; ++ } ++ if (descriptor === undefined) { ++ // If the descriptor is undefined, then its a normal property, so we should ++ // lookup the value to chain off of like normal. ++ ++ if (!(segment in current) && typeof current.unknownProperty === 'function') { ++ current = current.unknownProperty(segment); ++ } else { ++ current = current[segment]; ++ } ++ } else if (CHAIN_PASS_THROUGH.has(descriptor)) { ++ current = current[segment]; ++ } else { ++ // If the descriptor is defined, then its a normal CP (not an alias, which ++ // would have been handled earlier). We get the last revision to check if ++ // the CP is still valid, and if so we use the cached value. If not, then ++ // we create a lazy chain lookup, and the next time the CP is calculated, ++ // it will update that lazy chain. ++ let instanceMeta = currentMeta.source === current ? currentMeta : meta(current); ++ let lastRevision = instanceMeta.revisionFor(segment); ++ if (lastRevision !== undefined && validateTag(propertyTag, lastRevision)) { ++ current = instanceMeta.valueFor(segment); ++ } else { ++ // use metaFor here to ensure we have the meta for the instance ++ let lazyChains = instanceMeta.writableLazyChainsFor(segment); ++ let rest = path.substring(segmentEnd + 1); ++ let placeholderTag = createUpdatableTag(); ++ lazyChains.push([placeholderTag, rest]); ++ chainTags.push(placeholderTag); ++ break; ++ } ++ } ++ if (!isObject$1(current)) { ++ // we've hit the end of the chain for now, break out ++ break; ++ } ++ currentTagMeta = tagMetaFor(current); ++ currentMeta = peekMeta(current); ++ } ++ return chainTags; ++ } ++ ++ // Same as built-in MethodDecorator but with more arguments ++ ++ function isElementDescriptor(args) { ++ let [maybeTarget, maybeKey, maybeDesc] = args; ++ return ( ++ // Ensure we have the right number of args ++ args.length === 3 && ( ++ // Make sure the target is a class or object (prototype) ++ typeof maybeTarget === 'function' || typeof maybeTarget === 'object' && maybeTarget !== null) && ++ // Make sure the key is a string ++ typeof maybeKey === 'string' && ( ++ // Make sure the descriptor is the right shape ++ typeof maybeDesc === 'object' && maybeDesc !== null || maybeDesc === undefined) ++ ); ++ } ++ function nativeDescDecorator(propertyDesc) { ++ let decorator = function () { ++ return propertyDesc; ++ }; ++ setClassicDecorator(decorator); ++ return decorator; ++ } ++ ++ /** ++ Objects of this type can implement an interface to respond to requests to ++ get and set. The default implementation handles simple properties. ++ ++ @class Descriptor ++ @private ++ */ ++ class ComputedDescriptor { ++ enumerable = true; ++ configurable = true; ++ _dependentKeys = undefined; ++ _meta = undefined; ++ setup(_obj, keyName, _propertyDesc, meta) { ++ meta.writeDescriptors(keyName, this); ++ } ++ teardown(_obj, keyName, meta) { ++ meta.removeDescriptors(keyName); ++ } ++ } ++ let COMPUTED_GETTERS; ++ { ++ COMPUTED_GETTERS = new WeakSet(); ++ } ++ function DESCRIPTOR_GETTER_FUNCTION(name, descriptor) { ++ function getter() { ++ return descriptor.get(this, name); ++ } ++ { ++ COMPUTED_GETTERS.add(getter); ++ } ++ return getter; ++ } ++ function DESCRIPTOR_SETTER_FUNCTION(name, descriptor) { ++ let set = function CPSETTER_FUNCTION(value) { ++ return descriptor.set(this, name, value); ++ }; ++ COMPUTED_SETTERS.add(set); ++ return set; ++ } ++ const COMPUTED_SETTERS = new WeakSet(); ++ function makeComputedDecorator(desc, DecoratorClass) { ++ let decorator = function COMPUTED_DECORATOR(target, key, propertyDesc, maybeMeta, isClassicDecorator) { ++ (!(isClassicDecorator || !propertyDesc || !propertyDesc.get || !COMPUTED_GETTERS.has(propertyDesc.get)) && assert$1(`Only one computed property decorator can be applied to a class field or accessor, but '${key}' was decorated twice. You may have added the decorator to both a getter and setter, which is unnecessary.`, isClassicDecorator || !propertyDesc || !propertyDesc.get || !COMPUTED_GETTERS.has(propertyDesc.get))); ++ let meta$1 = arguments.length === 3 ? meta(target) : maybeMeta; ++ desc.setup(target, key, propertyDesc, meta$1); ++ let computedDesc = { ++ enumerable: desc.enumerable, ++ configurable: desc.configurable, ++ get: DESCRIPTOR_GETTER_FUNCTION(key, desc), ++ set: DESCRIPTOR_SETTER_FUNCTION(key, desc) ++ }; ++ return computedDesc; ++ }; ++ setClassicDecorator(decorator, desc); ++ Object.setPrototypeOf(decorator, DecoratorClass.prototype); ++ return decorator; ++ } ++ ++ ///////////// ++ ++ const DECORATOR_DESCRIPTOR_MAP = new WeakMap(); ++ ++ /** ++ Returns the CP descriptor associated with `obj` and `keyName`, if any. ++ ++ @method descriptorForProperty ++ @param {Object} obj the object to check ++ @param {String} keyName the key to check ++ @return {Descriptor} ++ @private ++ */ ++ function descriptorForProperty(obj, keyName, _meta) { ++ (!(obj !== null) && assert$1('Cannot call `descriptorForProperty` on null', obj !== null)); ++ (!(obj !== undefined) && assert$1('Cannot call `descriptorForProperty` on undefined', obj !== undefined)); ++ (!(typeof obj === 'object' || typeof obj === 'function') && assert$1(`Cannot call \`descriptorForProperty\` on ${typeof obj}`, typeof obj === 'object' || typeof obj === 'function')); ++ let meta = _meta === undefined ? peekMeta(obj) : _meta; ++ if (meta !== null) { ++ return meta.peekDescriptors(keyName); ++ } ++ } ++ function descriptorForDecorator(dec) { ++ return DECORATOR_DESCRIPTOR_MAP.get(dec); ++ } ++ ++ /** ++ Check whether a value is a decorator ++ ++ @method isClassicDecorator ++ @param {any} possibleDesc the value to check ++ @return {boolean} ++ @private ++ */ ++ function isClassicDecorator(dec) { ++ return typeof dec === 'function' && DECORATOR_DESCRIPTOR_MAP.has(dec); ++ } ++ ++ /** ++ Set a value as a decorator ++ ++ @method setClassicDecorator ++ @param {function} decorator the value to mark as a decorator ++ @private ++ */ ++ function setClassicDecorator(dec, value = true) { ++ DECORATOR_DESCRIPTOR_MAP.set(dec, value); ++ } ++ ++ /** ++ @module @ember/object ++ */ ++ ++ const END_WITH_EACH_REGEX = /\.@each$/; ++ ++ /** ++ Expands `pattern`, invoking `callback` for each expansion. ++ ++ The only pattern supported is brace-expansion, anything else will be passed ++ once to `callback` directly. ++ ++ Example ++ ++ ```js ++ import { expandProperties } from '@ember/object/computed'; ++ ++ function echo(arg){ console.log(arg); } ++ ++ expandProperties('foo.bar', echo); //=> 'foo.bar' ++ expandProperties('{foo,bar}', echo); //=> 'foo', 'bar' ++ expandProperties('foo.{bar,baz}', echo); //=> 'foo.bar', 'foo.baz' ++ expandProperties('{foo,bar}.baz', echo); //=> 'foo.baz', 'bar.baz' ++ expandProperties('foo.{bar,baz}.[]', echo) //=> 'foo.bar.[]', 'foo.baz.[]' ++ expandProperties('{foo,bar}.{spam,eggs}', echo) //=> 'foo.spam', 'foo.eggs', 'bar.spam', 'bar.eggs' ++ expandProperties('{foo}.bar.{baz}') //=> 'foo.bar.baz' ++ ``` ++ ++ @method expandProperties ++ @static ++ @for @ember/object/computed ++ @public ++ @param {String} pattern The property pattern to expand. ++ @param {Function} callback The callback to invoke. It is invoked once per ++ expansion, and is passed the expansion. ++ */ ++ function expandProperties(pattern, callback) { ++ (!(typeof pattern === 'string') && assert$1(`A computed property key must be a string, you passed ${typeof pattern} ${pattern}`, typeof pattern === 'string')); ++ (!(pattern.indexOf(' ') === -1) && assert$1('Brace expanded properties cannot contain spaces, e.g. "user.{firstName, lastName}" should be "user.{firstName,lastName}"', pattern.indexOf(' ') === -1)); // regex to look for double open, double close, or unclosed braces ++ (!(pattern.match(/\{[^}{]*\{|\}[^}{]*\}|\{[^}]*$/g) === null) && assert$1(`Brace expanded properties have to be balanced and cannot be nested, pattern: ${pattern}`, pattern.match(/\{[^}{]*\{|\}[^}{]*\}|\{[^}]*$/g) === null)); ++ let start = pattern.indexOf('{'); ++ if (start < 0) { ++ callback(pattern.replace(END_WITH_EACH_REGEX, '.[]')); ++ } else { ++ dive('', pattern, start, callback); ++ } ++ } ++ function dive(prefix, pattern, start, callback) { ++ let end = pattern.indexOf('}'), ++ i = 0, ++ newStart, ++ arrayLength; ++ let tempArr = pattern.substring(start + 1, end).split(','); ++ let after = pattern.substring(end + 1); ++ prefix = prefix + pattern.substring(0, start); ++ arrayLength = tempArr.length; ++ while (i < arrayLength) { ++ newStart = after.indexOf('{'); ++ if (newStart < 0) { ++ callback((prefix + tempArr[i++] + after).replace(END_WITH_EACH_REGEX, '.[]')); ++ } else { ++ dive(prefix + tempArr[i++], after, newStart, callback); ++ } ++ } ++ } ++ ++ const AFTER_OBSERVERS = ':change'; ++ function changeEvent(keyName) { ++ return keyName + AFTER_OBSERVERS; ++ } ++ + /** + @module @ember/object + */ +@@ -10731,5146 +11116,4768 @@ var define, require; + return func; + } + +- const SET_TIMEOUT = setTimeout; +- const NOOP$4 = () => {}; +- function buildNext(flush) { +- // Using "promises first" here to: +- // +- // 1) Ensure more consistent experience on browsers that +- // have differently queued microtasks (separate queues for +- // MutationObserver vs Promises). +- // 2) Ensure better debugging experiences (it shows up in Chrome +- // call stack as "Promise.then (async)") which is more consistent +- // with user expectations +- // +- // When Promise is unavailable use MutationObserver (mostly so that we +- // still get microtasks on IE11), and when neither MutationObserver and +- // Promise are present use a plain old setTimeout. +- if (typeof Promise === 'function') { +- const autorunPromise = Promise.resolve(); +- return () => autorunPromise.then(flush); +- } else if (typeof MutationObserver === 'function') { +- let iterations = 0; +- let observer = new MutationObserver(flush); +- let node = document.createTextNode(''); +- observer.observe(node, { +- characterData: true +- }); +- return () => { +- iterations = ++iterations % 2; +- node.data = '' + iterations; +- return iterations; +- }; +- } else { +- return () => SET_TIMEOUT(flush, 0); ++ const SYNC_DEFAULT = !ENV._DEFAULT_ASYNC_OBSERVERS; ++ const SYNC_OBSERVERS = new Map(); ++ const ASYNC_OBSERVERS = new Map(); ++ ++ /** ++ @module @ember/object ++ */ ++ ++ /** ++ @method addObserver ++ @static ++ @for @ember/object/observers ++ @param obj ++ @param {String} path ++ @param {Object|Function} target ++ @param {Function|String} [method] ++ @public ++ */ ++ function addObserver(obj, path, target, method, sync = SYNC_DEFAULT) { ++ let eventName = changeEvent(path); ++ addListener(obj, eventName, target, method, false, sync); ++ let meta = peekMeta(obj); ++ if (meta === null || !(meta.isPrototypeMeta(obj) || meta.isInitializing())) { ++ activateObserver(obj, eventName, sync); + } + } +- function buildPlatform(flush) { +- let clearNext = NOOP$4; +- return { +- setTimeout(fn, ms) { +- return setTimeout(fn, ms); +- }, +- clearTimeout(timerId) { +- return clearTimeout(timerId); +- }, +- now() { +- return Date.now(); +- }, +- next: buildNext(flush), +- clearNext +- }; +- } +- const NUMBER = /\d+/; +- const TIMERS_OFFSET = 6; +- function isCoercableNumber(suspect) { +- let type = typeof suspect; +- return type === 'number' && suspect === suspect || type === 'string' && NUMBER.test(suspect); ++ ++ /** ++ @method removeObserver ++ @static ++ @for @ember/object/observers ++ @param obj ++ @param {String} path ++ @param {Object|Function} target ++ @param {Function|String} [method] ++ @public ++ */ ++ function removeObserver(obj, path, target, method, sync = SYNC_DEFAULT) { ++ let eventName = changeEvent(path); ++ let meta = peekMeta(obj); ++ if (meta === null || !(meta.isPrototypeMeta(obj) || meta.isInitializing())) { ++ deactivateObserver(obj, eventName, sync); ++ } ++ removeListener(obj, eventName, target, method); + } +- function getOnError(options) { +- return options.onError || options.onErrorTarget && options.onErrorTarget[options.onErrorMethod]; ++ function getOrCreateActiveObserversFor(target, sync) { ++ let observerMap = sync === true ? SYNC_OBSERVERS : ASYNC_OBSERVERS; ++ if (!observerMap.has(target)) { ++ observerMap.set(target, new Map()); ++ registerDestructor$1(target, () => destroyObservers(target), true); ++ } ++ return observerMap.get(target); + } +- function findItem(target, method, collection) { +- let index = -1; +- for (let i = 0, l = collection.length; i < l; i += 4) { +- if (collection[i] === target && collection[i + 1] === method) { +- index = i; +- break; +- } ++ function activateObserver(target, eventName, sync = false) { ++ let activeObservers = getOrCreateActiveObserversFor(target, sync); ++ if (activeObservers.has(eventName)) { ++ activeObservers.get(eventName).count++; ++ } else { ++ let path = eventName.substring(0, eventName.lastIndexOf(':')); ++ let tag = getChainTagsForKey(target, path, tagMetaFor(target), peekMeta(target)); ++ activeObservers.set(eventName, { ++ count: 1, ++ path, ++ tag, ++ lastRevision: valueForTag(tag), ++ suspended: false ++ }); + } +- return index; + } +- function findTimerItem(target, method, collection) { +- let index = -1; +- for (let i = 2, l = collection.length; i < l; i += 6) { +- if (collection[i] === target && collection[i + 1] === method) { +- index = i - 2; +- break; ++ let DEACTIVATE_SUSPENDED = false; ++ let SCHEDULED_DEACTIVATE = []; ++ function deactivateObserver(target, eventName, sync = false) { ++ if (DEACTIVATE_SUSPENDED === true) { ++ SCHEDULED_DEACTIVATE.push([target, eventName, sync]); ++ return; ++ } ++ let observerMap = sync === true ? SYNC_OBSERVERS : ASYNC_OBSERVERS; ++ let activeObservers = observerMap.get(target); ++ if (activeObservers !== undefined) { ++ let observer = activeObservers.get(eventName); ++ observer.count--; ++ if (observer.count === 0) { ++ activeObservers.delete(eventName); ++ if (activeObservers.size === 0) { ++ observerMap.delete(target); ++ } + } + } +- return index; + } +- function getQueueItems(items, queueItemLength, queueItemPositionOffset = 0) { +- let queueItems = []; +- for (let i = 0; i < items.length; i += queueItemLength) { +- let maybeError = items[i + 3 /* stack */ + queueItemPositionOffset]; +- let queueItem = { +- target: items[i + 0 /* target */ + queueItemPositionOffset], +- method: items[i + 1 /* method */ + queueItemPositionOffset], +- args: items[i + 2 /* args */ + queueItemPositionOffset], +- stack: maybeError !== undefined && 'stack' in maybeError ? maybeError.stack : '' +- }; +- queueItems.push(queueItem); +- } +- return queueItems; ++ function suspendedObserverDeactivation() { ++ DEACTIVATE_SUSPENDED = true; + } +- function binarySearch(time, timers) { +- let start = 0; +- let end = timers.length - TIMERS_OFFSET; +- let middle; +- let l; +- while (start < end) { +- // since timers is an array of pairs 'l' will always +- // be an integer +- l = (end - start) / TIMERS_OFFSET; +- // compensate for the index in case even number +- // of pairs inside timers +- middle = start + l - l % TIMERS_OFFSET; +- if (time >= timers[middle]) { +- start = middle + TIMERS_OFFSET; +- } else { +- end = middle; +- } ++ function resumeObserverDeactivation() { ++ DEACTIVATE_SUSPENDED = false; ++ for (let [target, eventName, sync] of SCHEDULED_DEACTIVATE) { ++ deactivateObserver(target, eventName, sync); + } +- return time >= timers[start] ? start + TIMERS_OFFSET : start; ++ SCHEDULED_DEACTIVATE = []; + } +- const QUEUE_ITEM_LENGTH = 4; +- class Queue { +- constructor(name, options = {}, globalOptions = {}) { +- this._queueBeingFlushed = []; +- this.targetQueues = new Map(); +- this.index = 0; +- this._queue = []; +- this.name = name; +- this.options = options; +- this.globalOptions = globalOptions; ++ ++ /** ++ * Primarily used for cases where we are redefining a class, e.g. mixins/reopen ++ * being applied later. Revalidates all the observers, resetting their tags. ++ * ++ * @private ++ * @param target ++ */ ++ function revalidateObservers(target) { ++ if (ASYNC_OBSERVERS.has(target)) { ++ ASYNC_OBSERVERS.get(target).forEach(observer => { ++ observer.tag = getChainTagsForKey(target, observer.path, tagMetaFor(target), peekMeta(target)); ++ observer.lastRevision = valueForTag(observer.tag); ++ }); + } +- stackFor(index) { +- if (index < this._queue.length) { +- let entry = this._queue[index * 3 + QUEUE_ITEM_LENGTH]; +- if (entry) { +- return entry.stack; +- } else { +- return null; +- } +- } ++ if (SYNC_OBSERVERS.has(target)) { ++ SYNC_OBSERVERS.get(target).forEach(observer => { ++ observer.tag = getChainTagsForKey(target, observer.path, tagMetaFor(target), peekMeta(target)); ++ observer.lastRevision = valueForTag(observer.tag); ++ }); + } +- flush(sync) { +- let { +- before, +- after +- } = this.options; +- let target; +- let method; +- let args; +- let errorRecordedForStack; +- this.targetQueues.clear(); +- if (this._queueBeingFlushed.length === 0) { +- this._queueBeingFlushed = this._queue; +- this._queue = []; +- } +- if (before !== undefined) { +- before(); +- } +- let invoke; +- let queueItems = this._queueBeingFlushed; +- if (queueItems.length > 0) { +- let onError = getOnError(this.globalOptions); +- invoke = onError ? this.invokeWithOnError : this.invoke; +- for (let i = this.index; i < queueItems.length; i += QUEUE_ITEM_LENGTH) { +- this.index += QUEUE_ITEM_LENGTH; +- method = queueItems[i + 1]; +- // method could have been nullified / canceled during flush +- if (method !== null) { +- // +- // ** Attention intrepid developer ** +- // +- // To find out the stack of this task when it was scheduled onto +- // the run loop, add the following to your app.js: +- // +- // Ember.run.backburner.DEBUG = true; // NOTE: This slows your app, don't leave it on in production. +- // +- // Once that is in place, when you are at a breakpoint and navigate +- // here in the stack explorer, you can look at `errorRecordedForStack.stack`, +- // which will be the captured stack when this job was scheduled. +- // +- // One possible long-term solution is the following Chrome issue: +- // https://bugs.chromium.org/p/chromium/issues/detail?id=332624 +- // +- target = queueItems[i]; +- args = queueItems[i + 2]; +- errorRecordedForStack = queueItems[i + 3]; // Debugging assistance +- invoke(target, method, args, onError, errorRecordedForStack); ++ } ++ let lastKnownRevision = 0; ++ function flushAsyncObservers(_schedule) { ++ let currentRevision = valueForTag(CURRENT_TAG); ++ if (lastKnownRevision === currentRevision) { ++ return; ++ } ++ lastKnownRevision = currentRevision; ++ ASYNC_OBSERVERS.forEach((activeObservers, target) => { ++ let meta = peekMeta(target); ++ activeObservers.forEach((observer, eventName) => { ++ if (!validateTag(observer.tag, observer.lastRevision)) { ++ let sendObserver = () => { ++ try { ++ sendEvent(target, eventName, [target, observer.path], undefined, meta); ++ } finally { ++ observer.tag = getChainTagsForKey(target, observer.path, tagMetaFor(target), peekMeta(target)); ++ observer.lastRevision = valueForTag(observer.tag); ++ } ++ }; ++ if (_schedule) { ++ _schedule('actions', sendObserver); ++ } else { ++ sendObserver(); + } +- if (this.index !== this._queueBeingFlushed.length && this.globalOptions.mustYield && this.globalOptions.mustYield()) { +- return 1 /* Pause */; ++ } ++ }); ++ }); ++ } ++ function flushSyncObservers() { ++ // When flushing synchronous observers, we know that something has changed (we ++ // only do this during a notifyPropertyChange), so there's no reason to check ++ // a global revision. ++ ++ SYNC_OBSERVERS.forEach((activeObservers, target) => { ++ let meta = peekMeta(target); ++ activeObservers.forEach((observer, eventName) => { ++ if (!observer.suspended && !validateTag(observer.tag, observer.lastRevision)) { ++ try { ++ observer.suspended = true; ++ sendEvent(target, eventName, [target, observer.path], undefined, meta); ++ } finally { ++ observer.tag = getChainTagsForKey(target, observer.path, tagMetaFor(target), peekMeta(target)); ++ observer.lastRevision = valueForTag(observer.tag); ++ observer.suspended = false; + } + } +- } +- if (after !== undefined) { +- after(); +- } +- this._queueBeingFlushed.length = 0; +- this.index = 0; +- if (sync !== false && this._queue.length > 0) { +- // check if new items have been added +- this.flush(true); +- } +- } +- hasWork() { +- return this._queueBeingFlushed.length > 0 || this._queue.length > 0; +- } +- cancel({ +- target, +- method +- }) { +- let queue = this._queue; +- let targetQueueMap = this.targetQueues.get(target); +- if (targetQueueMap !== undefined) { +- targetQueueMap.delete(method); +- } +- let index = findItem(target, method, queue); +- if (index > -1) { +- queue[index + 1] = null; +- return true; +- } +- // if not found in current queue +- // could be in the queue that is being flushed +- queue = this._queueBeingFlushed; +- index = findItem(target, method, queue); +- if (index > -1) { +- queue[index + 1] = null; +- return true; +- } +- return false; ++ }); ++ }); ++ } ++ function setObserverSuspended(target, property, suspended) { ++ let activeObservers = SYNC_OBSERVERS.get(target); ++ if (!activeObservers) { ++ return; + } +- push(target, method, args, stack) { +- this._queue.push(target, method, args, stack); +- return { +- queue: this, +- target, +- method +- }; ++ let observer = activeObservers.get(changeEvent(property)); ++ if (observer) { ++ observer.suspended = suspended; + } +- pushUnique(target, method, args, stack) { +- let localQueueMap = this.targetQueues.get(target); +- if (localQueueMap === undefined) { +- localQueueMap = new Map(); +- this.targetQueues.set(target, localQueueMap); +- } +- let index = localQueueMap.get(method); +- if (index === undefined) { +- let queueIndex = this._queue.push(target, method, args, stack) - QUEUE_ITEM_LENGTH; +- localQueueMap.set(method, queueIndex); +- } else { +- let queue = this._queue; +- queue[index + 2] = args; // replace args +- queue[index + 3] = stack; // replace stack +- } +- return { +- queue: this, +- target, +- method +- }; ++ } ++ function destroyObservers(target) { ++ if (SYNC_OBSERVERS.size > 0) SYNC_OBSERVERS.delete(target); ++ if (ASYNC_OBSERVERS.size > 0) ASYNC_OBSERVERS.delete(target); ++ } ++ ++ /** ++ @module ember ++ @private ++ */ ++ ++ const PROPERTY_DID_CHANGE = Symbol('PROPERTY_DID_CHANGE'); ++ function hasPropertyDidChange(obj) { ++ return obj != null && typeof obj === 'object' && typeof obj[PROPERTY_DID_CHANGE] === 'function'; ++ } ++ let deferred$1 = 0; ++ ++ /** ++ This function is called just after an object property has changed. ++ It will notify any observers and clear caches among other things. ++ ++ Normally you will not need to call this method directly but if for some ++ reason you can't directly watch a property you can invoke this method ++ manually. ++ ++ @method notifyPropertyChange ++ @for @ember/object ++ @param {Object} obj The object with the property that will change ++ @param {String} keyName The property key (or path) that will change. ++ @param {Meta} [_meta] The objects meta. ++ @param {unknown} [value] The new value to set for the property ++ @return {void} ++ @since 3.1.0 ++ @public ++ */ ++ function notifyPropertyChange(obj, keyName, _meta, value) { ++ let meta = _meta === undefined ? peekMeta(obj) : _meta; ++ if (meta !== null && (meta.isInitializing() || meta.isPrototypeMeta(obj))) { ++ return; + } +- _getDebugInfo(debugEnabled) { +- if (debugEnabled) { +- let debugInfo = getQueueItems(this._queue, QUEUE_ITEM_LENGTH); +- return debugInfo; +- } +- return undefined; ++ markObjectAsDirty(obj, keyName); ++ if (deferred$1 <= 0) { ++ flushSyncObservers(); + } +- invoke(target, method, args /*, onError, errorRecordedForStack */) { +- if (args === undefined) { +- method.call(target); ++ if (PROPERTY_DID_CHANGE in obj) { ++ // It's redundant to do this here, but we don't want to check above so we can avoid an extra function call in prod. ++ (!(hasPropertyDidChange(obj)) && assert$1('property did change hook is invalid', hasPropertyDidChange(obj))); // we need to check the arguments length here; there's a check in Component's `PROPERTY_DID_CHANGE` ++ // that checks its arguments length, so we have to explicitly not call this with `value` ++ // if it is not passed to `notifyPropertyChange` ++ if (arguments.length === 4) { ++ obj[PROPERTY_DID_CHANGE](keyName, value); + } else { +- method.apply(target, args); ++ obj[PROPERTY_DID_CHANGE](keyName); + } + } +- invokeWithOnError(target, method, args, onError, errorRecordedForStack) { +- try { +- if (args === undefined) { +- method.call(target); +- } else { +- method.apply(target, args); +- } +- } catch (error) { +- onError(error, errorRecordedForStack); +- } ++ } ++ ++ /** ++ @method beginPropertyChanges ++ @chainable ++ @private ++ */ ++ function beginPropertyChanges() { ++ deferred$1++; ++ suspendedObserverDeactivation(); ++ } ++ ++ /** ++ @method endPropertyChanges ++ @private ++ */ ++ function endPropertyChanges() { ++ deferred$1--; ++ if (deferred$1 <= 0) { ++ flushSyncObservers(); ++ resumeObserverDeactivation(); + } + } +- class DeferredActionQueues { +- constructor(queueNames = [], options) { +- this.queues = {}; +- this.queueNameIndex = 0; +- this.queueNames = queueNames; +- queueNames.reduce(function (queues, queueName) { +- queues[queueName] = new Queue(queueName, options[queueName], options); +- return queues; +- }, this.queues); ++ ++ /** ++ Make a series of property changes together in an ++ exception-safe way. ++ ++ ```javascript ++ Ember.changeProperties(function() { ++ obj1.set('foo', mayBlowUpWhenSet); ++ obj2.set('bar', baz); ++ }); ++ ``` ++ ++ @method changeProperties ++ @param {Function} callback ++ @private ++ */ ++ function changeProperties(callback) { ++ beginPropertyChanges(); ++ try { ++ callback(); ++ } finally { ++ endPropertyChanges(); + } +- /** +- * @method schedule +- * @param {String} queueName +- * @param {Any} target +- * @param {Any} method +- * @param {Any} args +- * @param {Boolean} onceFlag +- * @param {Any} stack +- * @return queue +- */ +- schedule(queueName, target, method, args, onceFlag, stack) { +- let queues = this.queues; +- let queue = queues[queueName]; +- if (queue === undefined) { +- throw new Error(`You attempted to schedule an action in a queue (${queueName}) that doesn\'t exist`); ++ } ++ ++ /** ++ @module @ember/object ++ */ ++ ++ const DEEP_EACH_REGEX = /\.@each\.[^.]+\./; ++ function noop$2() {} ++ /** ++ `@computed` is a decorator that turns a JavaScript getter and setter into a ++ computed property, which is a _cached, trackable value_. By default the getter ++ will only be called once and the result will be cached. You can specify ++ various properties that your computed property depends on. This will force the ++ cached result to be cleared if the dependencies are modified, and lazily recomputed the next time something asks for it. ++ ++ In the following example we decorate a getter - `fullName` - by calling ++ `computed` with the property dependencies (`firstName` and `lastName`) as ++ arguments. The `fullName` getter will be called once (regardless of how many ++ times it is accessed) as long as its dependencies do not change. Once ++ `firstName` or `lastName` are updated any future calls to `fullName` will ++ incorporate the new values, and any watchers of the value such as templates ++ will be updated: ++ ++ ```javascript ++ import { computed, set } from '@ember/object'; ++ ++ class Person { ++ constructor(firstName, lastName) { ++ set(this, 'firstName', firstName); ++ set(this, 'lastName', lastName); + } +- if (method === undefined || method === null) { +- throw new Error(`You attempted to schedule an action in a queue (${queueName}) for a method that doesn\'t exist`); ++ ++ @computed('firstName', 'lastName') ++ get fullName() { ++ return `${this.firstName} ${this.lastName}`; + } +- this.queueNameIndex = 0; +- if (onceFlag) { +- return queue.pushUnique(target, method, args, stack); +- } else { +- return queue.push(target, method, args, stack); ++ }); ++ ++ let tom = new Person('Tom', 'Dale'); ++ ++ tom.fullName; // 'Tom Dale' ++ ``` ++ ++ You can also provide a setter, which will be used when updating the computed ++ property. Ember's `set` function must be used to update the property ++ since it will also notify observers of the property: ++ ++ ```javascript ++ import { computed, set } from '@ember/object'; ++ ++ class Person { ++ constructor(firstName, lastName) { ++ set(this, 'firstName', firstName); ++ set(this, 'lastName', lastName); + } +- } +- /** +- * DeferredActionQueues.flush() calls Queue.flush() +- * +- * @method flush +- * @param {Boolean} fromAutorun +- */ +- flush(fromAutorun = false) { +- let queue; +- let queueName; +- let numberOfQueues = this.queueNames.length; +- while (this.queueNameIndex < numberOfQueues) { +- queueName = this.queueNames[this.queueNameIndex]; +- queue = this.queues[queueName]; +- if (queue.hasWork() === false) { +- this.queueNameIndex++; +- if (fromAutorun && this.queueNameIndex < numberOfQueues) { +- return 1 /* Pause */; +- } +- } else { +- if (queue.flush(false /* async */) === 1 /* Pause */) { +- return 1 /* Pause */; +- } +- } ++ ++ @computed('firstName', 'lastName') ++ get fullName() { ++ return `${this.firstName} ${this.lastName}`; + } +- } +- /** +- * Returns debug information for the current queues. +- * +- * @method _getDebugInfo +- * @param {Boolean} debugEnabled +- * @returns {IDebugInfo | undefined} +- */ +- _getDebugInfo(debugEnabled) { +- if (debugEnabled) { +- let debugInfo = {}; +- let queue; +- let queueName; +- let numberOfQueues = this.queueNames.length; +- let i = 0; +- while (i < numberOfQueues) { +- queueName = this.queueNames[i]; +- queue = this.queues[queueName]; +- debugInfo[queueName] = queue._getDebugInfo(debugEnabled); +- i++; +- } +- return debugInfo; ++ ++ set fullName(value) { ++ let [firstName, lastName] = value.split(' '); ++ ++ set(this, 'firstName', firstName); ++ set(this, 'lastName', lastName); + } +- return; ++ }); ++ ++ let person = new Person(); ++ ++ set(person, 'fullName', 'Peter Wagenet'); ++ person.firstName; // 'Peter' ++ person.lastName; // 'Wagenet' ++ ``` ++ ++ You can also pass a getter function or object with `get` and `set` functions ++ as the last argument to the computed decorator. This allows you to define ++ computed property _macros_: ++ ++ ```js ++ import { computed } from '@ember/object'; ++ ++ function join(...keys) { ++ return computed(...keys, function() { ++ return keys.map(key => this[key]).join(' '); ++ }); + } +- } +- function iteratorDrain(fn) { +- let iterator = fn(); +- let result = iterator.next(); +- while (result.done === false) { +- result.value(); +- result = iterator.next(); ++ ++ class Person { ++ @join('firstName', 'lastName') ++ fullName; + } +- } +- const noop$2 = function () {}; +- const DISABLE_SCHEDULE = Object.freeze([]); +- function parseArgs() { +- let length = arguments.length; +- let args; +- let method; +- let target; +- if (length === 0) ;else if (length === 1) { +- target = null; +- method = arguments[0]; +- } else { +- let argsIndex = 2; +- let methodOrTarget = arguments[0]; +- let methodOrArgs = arguments[1]; +- let type = typeof methodOrArgs; +- if (type === 'function') { +- target = methodOrTarget; +- method = methodOrArgs; +- } else if (methodOrTarget !== null && type === 'string' && methodOrArgs in methodOrTarget) { +- target = methodOrTarget; +- method = target[methodOrArgs]; +- } else if (typeof methodOrTarget === 'function') { +- argsIndex = 1; +- target = null; +- method = methodOrTarget; +- } +- if (length > argsIndex) { +- let len = length - argsIndex; +- args = new Array(len); +- for (let i = 0; i < len; i++) { +- args[i] = arguments[i + argsIndex]; ++ ``` ++ ++ Note that when defined this way, getters and setters receive the _key_ of the ++ property they are decorating as the first argument. Setters receive the value ++ they are setting to as the second argument instead. Additionally, setters must ++ _return_ the value that should be cached: ++ ++ ```javascript ++ import { computed, set } from '@ember/object'; ++ ++ function fullNameMacro(firstNameKey, lastNameKey) { ++ return computed(firstNameKey, lastNameKey, { ++ get() { ++ return `${this[firstNameKey]} ${this[lastNameKey]}`; + } +- } ++ ++ set(key, value) { ++ let [firstName, lastName] = value.split(' '); ++ ++ set(this, firstNameKey, firstName); ++ set(this, lastNameKey, lastName); ++ ++ return value; ++ } ++ }); + } +- return [target, method, args]; +- } +- function parseTimerArgs() { +- let [target, method, args] = parseArgs(...arguments); +- let wait = 0; +- let length = args !== undefined ? args.length : 0; +- if (length > 0) { +- let last = args[length - 1]; +- if (isCoercableNumber(last)) { +- wait = parseInt(args.pop(), 10); ++ ++ class Person { ++ constructor(firstName, lastName) { ++ set(this, 'firstName', firstName); ++ set(this, 'lastName', lastName); + } +- } +- return [target, method, args, wait]; +- } +- function parseDebounceArgs() { +- let target; +- let method; +- let isImmediate; +- let args; +- let wait; +- if (arguments.length === 2) { +- method = arguments[0]; +- wait = arguments[1]; +- target = null; +- } else { +- [target, method, args] = parseArgs(...arguments); +- if (args === undefined) { +- wait = 0; +- } else { +- wait = args.pop(); +- if (!isCoercableNumber(wait)) { +- isImmediate = wait === true; +- wait = args.pop(); ++ ++ @fullNameMacro('firstName', 'lastName') fullName; ++ }); ++ ++ let person = new Person(); ++ ++ set(person, 'fullName', 'Peter Wagenet'); ++ person.firstName; // 'Peter' ++ person.lastName; // 'Wagenet' ++ ``` ++ ++ Computed properties can also be used in classic classes. To do this, we ++ provide the getter and setter as the last argument like we would for a macro, ++ and we assign it to a property on the class definition. This is an _anonymous_ ++ computed macro: ++ ++ ```javascript ++ import EmberObject, { computed, set } from '@ember/object'; ++ ++ let Person = EmberObject.extend({ ++ // these will be supplied by `create` ++ firstName: null, ++ lastName: null, ++ ++ fullName: computed('firstName', 'lastName', { ++ get() { ++ return `${this.firstName} ${this.lastName}`; ++ } ++ ++ set(key, value) { ++ let [firstName, lastName] = value.split(' '); ++ ++ set(this, 'firstName', firstName); ++ set(this, 'lastName', lastName); ++ ++ return value; + } ++ }) ++ }); ++ ++ let tom = Person.create({ ++ firstName: 'Tom', ++ lastName: 'Dale' ++ }); ++ ++ tom.get('fullName') // 'Tom Dale' ++ ``` ++ ++ You can overwrite computed property without setters with a normal property (no ++ longer computed) that won't change if dependencies change. You can also mark ++ computed property as `.readOnly()` and block all attempts to set it. ++ ++ ```javascript ++ import { computed, set } from '@ember/object'; ++ ++ class Person { ++ constructor(firstName, lastName) { ++ set(this, 'firstName', firstName); ++ set(this, 'lastName', lastName); + } +- } +- wait = parseInt(wait, 10); +- return [target, method, args, wait, isImmediate]; +- } +- let UUID = 0; +- let beginCount = 0; +- let endCount = 0; +- let beginEventCount = 0; +- let endEventCount = 0; +- let runCount = 0; +- let joinCount = 0; +- let deferCount = 0; +- let scheduleCount = 0; +- let scheduleIterableCount = 0; +- let deferOnceCount = 0; +- let scheduleOnceCount = 0; +- let setTimeoutCount = 0; +- let laterCount = 0; +- let throttleCount = 0; +- let debounceCount = 0; +- let cancelTimersCount = 0; +- let cancelCount = 0; +- let autorunsCreatedCount = 0; +- let autorunsCompletedCount = 0; +- let deferredActionQueuesCreatedCount = 0; +- let nestedDeferredActionQueuesCreated = 0; +- class Backburner { +- constructor(queueNames, options) { +- this.DEBUG = false; +- this.currentInstance = null; +- this.instanceStack = []; +- this._eventCallbacks = { +- end: [], +- begin: [] +- }; +- this._timerTimeoutId = null; +- this._timers = []; +- this._autorun = false; +- this._autorunStack = null; +- this.queueNames = queueNames; +- this.options = options || {}; +- if (typeof this.options.defaultQueue === 'string') { +- this._defaultQueue = this.options.defaultQueue; +- } else { +- this._defaultQueue = this.queueNames[0]; ++ ++ @computed('firstName', 'lastName').readOnly() ++ get fullName() { ++ return `${this.firstName} ${this.lastName}`; + } +- this._onBegin = this.options.onBegin || noop$2; +- this._onEnd = this.options.onEnd || noop$2; +- this._boundRunExpiredTimers = this._runExpiredTimers.bind(this); +- this._boundAutorunEnd = () => { +- autorunsCompletedCount++; +- // if the autorun was already flushed, do nothing +- if (this._autorun === false) { +- return; ++ }); ++ ++ let person = new Person(); ++ person.set('fullName', 'Peter Wagenet'); // Uncaught Error: Cannot set read-only property "fullName" on object: <(...):emberXXX> ++ ``` ++ ++ Additional resources: ++ - [Decorators RFC](https://github.com/emberjs/rfcs/blob/master/text/0408-decorators.md) ++ - [New CP syntax RFC](https://github.com/emberjs/rfcs/blob/master/text/0011-improved-cp-syntax.md) ++ - [New computed syntax explained in "Ember 1.12 released" ](https://emberjs.com/blog/2015/05/13/ember-1-12-released.html#toc_new-computed-syntax) ++ ++ @class ComputedProperty ++ @public ++ */ ++ class ComputedProperty extends ComputedDescriptor { ++ _readOnly = false; ++ _hasConfig = false; ++ _getter = undefined; ++ _setter = undefined; ++ constructor(args) { ++ super(); ++ let maybeConfig = args[args.length - 1]; ++ if (typeof maybeConfig === 'function' || maybeConfig !== null && typeof maybeConfig === 'object') { ++ this._hasConfig = true; ++ let config = args.pop(); ++ if (typeof config === 'function') { ++ (!(!isClassicDecorator(config)) && assert$1(`You attempted to pass a computed property instance to computed(). Computed property instances are decorator functions, and cannot be passed to computed() because they cannot be turned into decorators twice`, !isClassicDecorator(config))); ++ this._getter = config; ++ } else { ++ const objectConfig = config; ++ (!(typeof objectConfig === 'object' && !Array.isArray(objectConfig)) && assert$1('computed expects a function or an object as last argument.', typeof objectConfig === 'object' && !Array.isArray(objectConfig))); ++ (!(Object.keys(objectConfig).every(key => key === 'get' || key === 'set')) && assert$1('Config object passed to computed can only contain `get` and `set` keys.', Object.keys(objectConfig).every(key => key === 'get' || key === 'set'))); ++ (!(Boolean(objectConfig.get) || Boolean(objectConfig.set)) && assert$1('Computed properties must receive a getter or a setter, you passed none.', Boolean(objectConfig.get) || Boolean(objectConfig.set))); ++ this._getter = objectConfig.get || noop$2; ++ this._setter = objectConfig.set; + } +- this._autorun = false; +- this._autorunStack = null; +- this._end(true /* fromAutorun */); +- }; +- let builder = this.options._buildPlatform || buildPlatform; +- this._platform = builder(this._boundAutorunEnd); ++ } ++ if (args.length > 0) { ++ this._property(...args); ++ } + } +- get counters() { +- return { +- begin: beginCount, +- end: endCount, +- events: { +- begin: beginEventCount, +- end: endEventCount +- }, +- autoruns: { +- created: autorunsCreatedCount, +- completed: autorunsCompletedCount +- }, +- run: runCount, +- join: joinCount, +- defer: deferCount, +- schedule: scheduleCount, +- scheduleIterable: scheduleIterableCount, +- deferOnce: deferOnceCount, +- scheduleOnce: scheduleOnceCount, +- setTimeout: setTimeoutCount, +- later: laterCount, +- throttle: throttleCount, +- debounce: debounceCount, +- cancelTimers: cancelTimersCount, +- cancel: cancelCount, +- loops: { +- total: deferredActionQueuesCreatedCount, +- nested: nestedDeferredActionQueuesCreated ++ setup(obj, keyName, propertyDesc, meta) { ++ super.setup(obj, keyName, propertyDesc, meta); ++ (!(!(propertyDesc && typeof propertyDesc.value === 'function')) && assert$1(`@computed can only be used on accessors or fields, attempted to use it with ${keyName} but that was a method. Try converting it to a getter (e.g. \`get ${keyName}() {}\`)`, !(propertyDesc && typeof propertyDesc.value === 'function'))); ++ (!(!propertyDesc || !propertyDesc.initializer) && assert$1(`@computed can only be used on empty fields. ${keyName} has an initial value (e.g. \`${keyName} = someValue\`)`, !propertyDesc || !propertyDesc.initializer)); ++ (!(!(this._hasConfig && propertyDesc && (typeof propertyDesc.get === 'function' || typeof propertyDesc.set === 'function'))) && assert$1(`Attempted to apply a computed property that already has a getter/setter to a ${keyName}, but it is a method or an accessor. If you passed @computed a function or getter/setter (e.g. \`@computed({ get() { ... } })\`), then it must be applied to a field`, !(this._hasConfig && propertyDesc && (typeof propertyDesc.get === 'function' || typeof propertyDesc.set === 'function')))); ++ if (this._hasConfig === false) { ++ (!(propertyDesc && (typeof propertyDesc.get === 'function' || typeof propertyDesc.set === 'function')) && assert$1(`Attempted to use @computed on ${keyName}, but it did not have a getter or a setter. You must either pass a get a function or getter/setter to @computed directly (e.g. \`@computed({ get() { ... } })\`) or apply @computed directly to a getter/setter`, propertyDesc && (typeof propertyDesc.get === 'function' || typeof propertyDesc.set === 'function'))); ++ let { ++ get, ++ set ++ } = propertyDesc; ++ if (get !== undefined) { ++ this._getter = get; + } +- }; +- } +- get defaultQueue() { +- return this._defaultQueue; +- } +- /* +- @method begin +- @return instantiated class DeferredActionQueues +- */ +- begin() { +- beginCount++; +- let options = this.options; +- let previousInstance = this.currentInstance; +- let current; +- if (this._autorun !== false) { +- current = previousInstance; +- this._cancelAutorun(); +- } else { +- if (previousInstance !== null) { +- nestedDeferredActionQueuesCreated++; +- this.instanceStack.push(previousInstance); ++ if (set !== undefined) { ++ this._setter = function setterWrapper(_key, value) { ++ let ret = set.call(this, value); ++ if (get !== undefined) { ++ return typeof ret === 'undefined' ? get.call(this) : ret; ++ } ++ return ret; ++ }; + } +- deferredActionQueuesCreatedCount++; +- current = this.currentInstance = new DeferredActionQueues(this.queueNames, options); +- beginEventCount++; +- this._trigger('begin', current, previousInstance); + } +- this._onBegin(current, previousInstance); +- return current; +- } +- end() { +- endCount++; +- this._end(false); + } +- on(eventName, callback) { +- if (typeof callback !== 'function') { +- throw new TypeError(`Callback must be a function`); ++ _property(...passedArgs) { ++ let args = []; ++ function addArg(property) { ++ (!(DEEP_EACH_REGEX.test(property) === false) && assert$1(`Dependent keys containing @each only work one level deep. ` + `You used the key "${property}" which is invalid. ` + `Please create an intermediary computed property or ` + `switch to using tracked properties.`, DEEP_EACH_REGEX.test(property) === false)); ++ args.push(property); + } +- let callbacks = this._eventCallbacks[eventName]; +- if (callbacks !== undefined) { +- callbacks.push(callback); +- } else { +- throw new TypeError(`Cannot on() event ${eventName} because it does not exist`); ++ for (let arg of passedArgs) { ++ expandProperties(arg, addArg); + } ++ this._dependentKeys = args; + } +- off(eventName, callback) { +- let callbacks = this._eventCallbacks[eventName]; +- if (!eventName || callbacks === undefined) { +- throw new TypeError(`Cannot off() event ${eventName} because it does not exist`); +- } +- let callbackFound = false; +- if (callback) { +- for (let i = 0; i < callbacks.length; i++) { +- if (callbacks[i] === callback) { +- callbackFound = true; +- callbacks.splice(i, 1); +- i--; +- } +- } +- } +- if (!callbackFound) { +- throw new TypeError(`Cannot off() callback that does not exist`); +- } +- } +- run() { +- runCount++; +- let [target, method, args] = parseArgs(...arguments); +- return this._run(target, method, args); +- } +- join() { +- joinCount++; +- let [target, method, args] = parseArgs(...arguments); +- return this._join(target, method, args); +- } +- /** +- * @deprecated please use schedule instead. +- */ +- defer(queueName, target, method, ...args) { +- deferCount++; +- return this.schedule(queueName, target, method, ...args); +- } +- schedule(queueName, ..._args) { +- scheduleCount++; +- let [target, method, args] = parseArgs(..._args); +- let stack = this.DEBUG ? new Error() : undefined; +- return this._ensureInstance().schedule(queueName, target, method, args, false, stack); +- } +- /* +- Defer the passed iterable of functions to run inside the specified queue. +- @method scheduleIterable +- @param {String} queueName +- @param {Iterable} an iterable of functions to execute +- @return method result +- */ +- scheduleIterable(queueName, iterable) { +- scheduleIterableCount++; +- let stack = this.DEBUG ? new Error() : undefined; +- return this._ensureInstance().schedule(queueName, null, iteratorDrain, [iterable], false, stack); +- } +- /** +- * @deprecated please use scheduleOnce instead. +- */ +- deferOnce(queueName, target, method, ...args) { +- deferOnceCount++; +- return this.scheduleOnce(queueName, target, method, ...args); +- } +- scheduleOnce(queueName, ..._args) { +- scheduleOnceCount++; +- let [target, method, args] = parseArgs(..._args); +- let stack = this.DEBUG ? new Error() : undefined; +- return this._ensureInstance().schedule(queueName, target, method, args, true, stack); +- } +- setTimeout() { +- setTimeoutCount++; +- return this.later(...arguments); +- } +- later() { +- laterCount++; +- let [target, method, args, wait] = parseTimerArgs(...arguments); +- return this._later(target, method, args, wait); +- } +- throttle() { +- throttleCount++; +- let [target, method, args, wait, isImmediate = true] = parseDebounceArgs(...arguments); +- let index = findTimerItem(target, method, this._timers); +- let timerId; +- if (index === -1) { +- timerId = this._later(target, method, isImmediate ? DISABLE_SCHEDULE : args, wait); +- if (isImmediate) { +- this._join(target, method, args); +- } +- } else { +- timerId = this._timers[index + 1]; +- let argIndex = index + 4; +- if (this._timers[argIndex] !== DISABLE_SCHEDULE) { +- this._timers[argIndex] = args; +- } +- } +- return timerId; +- } +- debounce() { +- debounceCount++; +- let [target, method, args, wait, isImmediate = false] = parseDebounceArgs(...arguments); +- let _timers = this._timers; +- let index = findTimerItem(target, method, _timers); +- let timerId; +- if (index === -1) { +- timerId = this._later(target, method, isImmediate ? DISABLE_SCHEDULE : args, wait); +- if (isImmediate) { +- this._join(target, method, args); +- } ++ get(obj, keyName) { ++ let meta$1 = meta(obj); ++ let tagMeta = tagMetaFor(obj); ++ let propertyTag = tagFor(obj, keyName, tagMeta); ++ let ret; ++ let revision = meta$1.revisionFor(keyName); ++ if (revision !== undefined && validateTag(propertyTag, revision)) { ++ ret = meta$1.valueFor(keyName); + } else { +- let executeAt = this._platform.now() + wait; +- let argIndex = index + 4; +- if (_timers[argIndex] === DISABLE_SCHEDULE) { +- args = DISABLE_SCHEDULE; +- } +- timerId = _timers[index + 1]; +- let i = binarySearch(executeAt, _timers); +- if (index + TIMERS_OFFSET === i) { +- _timers[index] = executeAt; +- _timers[argIndex] = args; +- } else { +- let stack = this._timers[index + 5]; +- this._timers.splice(i, 0, executeAt, timerId, target, method, args, stack); +- this._timers.splice(index, TIMERS_OFFSET); +- } +- if (index === 0) { +- this._reinstallTimerTimeout(); ++ // For backwards compatibility, we only throw if the CP has any dependencies. CPs without dependencies ++ // should be allowed, even after the object has been destroyed, which is why we check _dependentKeys. ++ (!(this._dependentKeys === undefined || !isDestroyed(obj)) && assert$1(`Attempted to access the computed ${obj}.${keyName} on a destroyed object, which is not allowed`, this._dependentKeys === undefined || !isDestroyed(obj))); ++ let { ++ _getter, ++ _dependentKeys ++ } = this; ++ ++ // Create a tracker that absorbs any trackable actions inside the CP ++ untrack(() => { ++ ret = _getter.call(obj, keyName); ++ }); ++ if (_dependentKeys !== undefined) { ++ UPDATE_TAG(propertyTag, getChainTagsForKeys(obj, _dependentKeys, tagMeta, meta$1)); ++ { ++ ALLOW_CYCLES.set(propertyTag, true); ++ } + } ++ meta$1.setValueFor(keyName, ret); ++ meta$1.setRevisionFor(keyName, valueForTag(propertyTag)); ++ finishLazyChains(meta$1, keyName, ret); + } +- return timerId; +- } +- cancelTimers() { +- cancelTimersCount++; +- this._clearTimerTimeout(); +- this._timers = []; +- this._cancelAutorun(); +- } +- hasTimers() { +- return this._timers.length > 0 || this._autorun; +- } +- cancel(timer) { +- cancelCount++; +- if (timer === null || timer === undefined) { +- return false; +- } +- let timerType = typeof timer; +- if (timerType === 'number') { +- // we're cancelling a setTimeout or throttle or debounce +- return this._cancelLaterTimer(timer); +- } else if (timerType === 'object' && timer.queue && timer.method) { +- // we're cancelling a deferOnce +- return timer.queue.cancel(timer); ++ consumeTag(propertyTag); ++ ++ // Add the tag of the returned value if it is an array, since arrays ++ // should always cause updates if they are consumed and then changed ++ if (Array.isArray(ret)) { ++ consumeTag(tagFor(ret, '[]')); + } +- return false; +- } +- ensureInstance() { +- this._ensureInstance(); ++ return ret; + } +- /** +- * Returns debug information related to the current instance of Backburner +- * +- * @method getDebugInfo +- * @returns {Object | undefined} Will return and Object containing debug information if +- * the DEBUG flag is set to true on the current instance of Backburner, else undefined. +- */ +- getDebugInfo() { +- if (this.DEBUG) { +- return { +- autorun: this._autorunStack, +- counters: this.counters, +- timers: getQueueItems(this._timers, TIMERS_OFFSET, 2), +- instanceStack: [this.currentInstance, ...this.instanceStack].map(deferredActionQueue => deferredActionQueue && deferredActionQueue._getDebugInfo(this.DEBUG)) +- }; ++ set(obj, keyName, value) { ++ if (this._readOnly) { ++ this._throwReadOnlyError(obj, keyName); + } +- return undefined; +- } +- _end(fromAutorun) { +- let currentInstance = this.currentInstance; +- let nextInstance = null; +- if (currentInstance === null) { +- throw new Error(`end called without begin`); ++ (!(this._setter !== undefined) && assert$1(`Cannot override the computed property \`${keyName}\` on ${toString$1(obj)}.`, this._setter !== undefined)); ++ let meta$1 = meta(obj); ++ ++ // ensure two way binding works when the component has defined a computed ++ // property with both a setter and dependent keys, in that scenario without ++ // the sync observer added below the caller's value will never be updated ++ // ++ // See GH#18147 / GH#19028 for details. ++ if ( ++ // ensure that we only run this once, while the component is being instantiated ++ meta$1.isInitializing() && this._dependentKeys !== undefined && this._dependentKeys.length > 0 && typeof obj[PROPERTY_DID_CHANGE] === 'function' && obj.isComponent) { ++ // It's redundant to do this here, but we don't want to check above so we can avoid an extra function call in prod. ++ (!(hasPropertyDidChange(obj)) && assert$1('property did change hook is invalid', hasPropertyDidChange(obj))); ++ addObserver(obj, keyName, () => { ++ obj[PROPERTY_DID_CHANGE](keyName); ++ }, undefined, true); + } +- // Prevent double-finally bug in Safari 6.0.2 and iOS 6 +- // This bug appears to be resolved in Safari 6.0.5 and iOS 7 +- let finallyAlreadyCalled = false; +- let result; ++ let ret; + try { +- result = currentInstance.flush(fromAutorun); +- } finally { +- if (!finallyAlreadyCalled) { +- finallyAlreadyCalled = true; +- if (result === 1 /* Pause */) { +- const plannedNextQueue = this.queueNames[currentInstance.queueNameIndex]; +- this._scheduleAutorun(plannedNextQueue); +- } else { +- this.currentInstance = null; +- if (this.instanceStack.length > 0) { +- nextInstance = this.instanceStack.pop(); +- this.currentInstance = nextInstance; +- } +- this._trigger('end', currentInstance, nextInstance); +- this._onEnd(currentInstance, nextInstance); ++ beginPropertyChanges(); ++ ret = this._set(obj, keyName, value, meta$1); ++ finishLazyChains(meta$1, keyName, ret); ++ let tagMeta = tagMetaFor(obj); ++ let propertyTag = tagFor(obj, keyName, tagMeta); ++ let { ++ _dependentKeys ++ } = this; ++ if (_dependentKeys !== undefined) { ++ UPDATE_TAG(propertyTag, getChainTagsForKeys(obj, _dependentKeys, tagMeta, meta$1)); ++ if (true /* DEBUG */) { ++ ALLOW_CYCLES.set(propertyTag, true); + } + } ++ meta$1.setRevisionFor(keyName, valueForTag(propertyTag)); ++ } finally { ++ endPropertyChanges(); + } ++ return ret; + } +- _join(target, method, args) { +- if (this.currentInstance === null) { +- return this._run(target, method, args); +- } +- if (target === undefined && args === undefined) { +- return method(); +- } else { +- return method.apply(target, args); +- } ++ _throwReadOnlyError(obj, keyName) { ++ throw new Error(`Cannot set read-only property "${keyName}" on object: ${inspect(obj)}`); + } +- _run(target, method, args) { +- let onError = getOnError(this.options); +- this.begin(); +- if (onError) { +- try { +- return method.apply(target, args); +- } catch (error) { +- onError(error); +- } finally { +- this.end(); +- } +- } else { +- try { +- return method.apply(target, args); +- } finally { +- this.end(); +- } ++ _set(obj, keyName, value, meta) { ++ let hadCachedValue = meta.revisionFor(keyName) !== undefined; ++ let cachedValue = meta.valueFor(keyName); ++ let ret; ++ let { ++ _setter ++ } = this; ++ setObserverSuspended(obj, keyName, true); ++ try { ++ ret = _setter.call(obj, keyName, value, cachedValue); ++ } finally { ++ setObserverSuspended(obj, keyName, false); + } ++ ++ // allows setter to return the same value that is cached already ++ if (hadCachedValue && cachedValue === ret) { ++ return ret; ++ } ++ meta.setValueFor(keyName, ret); ++ notifyPropertyChange(obj, keyName, meta, value); ++ return ret; + } +- _cancelAutorun() { +- if (this._autorun) { +- this._platform.clearNext(); +- this._autorun = false; +- this._autorunStack = null; ++ ++ /* called before property is overridden */ ++ teardown(obj, keyName, meta) { ++ if (meta.revisionFor(keyName) !== undefined) { ++ meta.setRevisionFor(keyName, undefined); ++ meta.setValueFor(keyName, undefined); + } ++ super.teardown(obj, keyName, meta); + } +- _later(target, method, args, wait) { +- let stack = this.DEBUG ? new Error() : undefined; +- let executeAt = this._platform.now() + wait; +- let id = UUID++; +- if (this._timers.length === 0) { +- this._timers.push(executeAt, id, target, method, args, stack); +- this._installTimerTimeout(); ++ } ++ class AutoComputedProperty extends ComputedProperty { ++ get(obj, keyName) { ++ let meta$1 = meta(obj); ++ let tagMeta = tagMetaFor(obj); ++ let propertyTag = tagFor(obj, keyName, tagMeta); ++ let ret; ++ let revision = meta$1.revisionFor(keyName); ++ if (revision !== undefined && validateTag(propertyTag, revision)) { ++ ret = meta$1.valueFor(keyName); + } else { +- // find position to insert +- let i = binarySearch(executeAt, this._timers); +- this._timers.splice(i, 0, executeAt, id, target, method, args, stack); +- // always reinstall since it could be out of sync +- this._reinstallTimerTimeout(); ++ (!(!isDestroyed(obj)) && assert$1(`Attempted to access the computed ${obj}.${keyName} on a destroyed object, which is not allowed`, !isDestroyed(obj))); ++ let { ++ _getter ++ } = this; ++ ++ // Create a tracker that absorbs any trackable actions inside the CP ++ let tag = track(() => { ++ ret = _getter.call(obj, keyName); ++ }); ++ UPDATE_TAG(propertyTag, tag); ++ meta$1.setValueFor(keyName, ret); ++ meta$1.setRevisionFor(keyName, valueForTag(propertyTag)); ++ finishLazyChains(meta$1, keyName, ret); + } +- return id; ++ consumeTag(propertyTag); ++ ++ // Add the tag of the returned value if it is an array, since arrays ++ // should always cause updates if they are consumed and then changed ++ if (Array.isArray(ret)) { ++ consumeTag(tagFor(ret, '[]', tagMeta)); ++ } ++ return ret; + } +- _cancelLaterTimer(timer) { +- for (let i = 1; i < this._timers.length; i += TIMERS_OFFSET) { +- if (this._timers[i] === timer) { +- this._timers.splice(i - 1, TIMERS_OFFSET); +- if (i === 1) { +- this._reinstallTimerTimeout(); +- } +- return true; ++ } ++ // TODO: This class can be svelted once `meta` has been deprecated ++ class ComputedDecoratorImpl extends Function { ++ /** ++ Call on a computed property to set it into read-only mode. When in this ++ mode the computed property will throw an error when set. ++ Example: ++ ```javascript ++ import { computed, set } from '@ember/object'; ++ class Person { ++ @computed().readOnly() ++ get guid() { ++ return 'guid-guid-guid'; + } + } +- return false; ++ let person = new Person(); ++ set(person, 'guid', 'new-guid'); // will throw an exception ++ ``` ++ Classic Class Example: ++ ```javascript ++ import EmberObject, { computed } from '@ember/object'; ++ let Person = EmberObject.extend({ ++ guid: computed(function() { ++ return 'guid-guid-guid'; ++ }).readOnly() ++ }); ++ let person = Person.create(); ++ person.set('guid', 'new-guid'); // will throw an exception ++ ``` ++ @method readOnly ++ @return {ComputedProperty} this ++ @chainable ++ @public ++ */ ++ readOnly() { ++ let desc = descriptorForDecorator(this); ++ (!(!(desc._setter && desc._setter !== desc._getter)) && assert$1('Computed properties that define a setter using the new syntax cannot be read-only', !(desc._setter && desc._setter !== desc._getter))); ++ desc._readOnly = true; ++ return this; + } ++ + /** +- Trigger an event. Supports up to two arguments. Designed around +- triggering transition events from one run loop instance to the +- next, which requires an argument for the instance and then +- an argument for the next instance. +- @private +- @method _trigger +- @param {String} eventName +- @param {any} arg1 +- @param {any} arg2 +- */ +- _trigger(eventName, arg1, arg2) { +- let callbacks = this._eventCallbacks[eventName]; +- if (callbacks !== undefined) { +- for (let i = 0; i < callbacks.length; i++) { +- callbacks[i](arg1, arg2); ++ In some cases, you may want to annotate computed properties with additional ++ metadata about how they function or what values they operate on. For example, ++ computed property functions may close over variables that are then no longer ++ available for introspection. You can pass a hash of these values to a ++ computed property. ++ Example: ++ ```javascript ++ import { computed } from '@ember/object'; ++ import Person from 'my-app/utils/person'; ++ class Store { ++ @computed().meta({ type: Person }) ++ get person() { ++ let personId = this.personId; ++ return Person.create({ id: personId }); + } + } +- } +- _runExpiredTimers() { +- this._timerTimeoutId = null; +- if (this._timers.length > 0) { +- this.begin(); +- this._scheduleExpiredTimers(); +- this.end(); ++ ``` ++ Classic Class Example: ++ ```javascript ++ import { computed } from '@ember/object'; ++ import Person from 'my-app/utils/person'; ++ const Store = EmberObject.extend({ ++ person: computed(function() { ++ let personId = this.get('personId'); ++ return Person.create({ id: personId }); ++ }).meta({ type: Person }) ++ }); ++ ``` ++ The hash that you pass to the `meta()` function will be saved on the ++ computed property descriptor under the `_meta` key. Ember runtime ++ exposes a public API for retrieving these values from classes, ++ via the `metaForProperty()` function. ++ @method meta ++ @param {Object} meta ++ @chainable ++ @public ++ */ ++ ++ meta(meta) { ++ let prop = descriptorForDecorator(this); ++ if (arguments.length === 0) { ++ return prop._meta || {}; ++ } else { ++ prop._meta = meta; ++ return this; + } + } +- _scheduleExpiredTimers() { +- let timers = this._timers; +- let i = 0; +- let l = timers.length; +- let defaultQueue = this._defaultQueue; +- let n = this._platform.now(); +- for (; i < l; i += TIMERS_OFFSET) { +- let executeAt = timers[i]; +- if (executeAt > n) { +- break; +- } +- let args = timers[i + 4]; +- if (args !== DISABLE_SCHEDULE) { +- let target = timers[i + 2]; +- let method = timers[i + 3]; +- let stack = timers[i + 5]; +- this.currentInstance.schedule(defaultQueue, target, method, args, false, stack); +- } +- } +- timers.splice(0, i); +- this._installTimerTimeout(); ++ ++ // TODO: Remove this when we can provide alternatives in the ecosystem to ++ // addons such as ember-macro-helpers that use it. ++ /** @internal */ ++ get _getter() { ++ return descriptorForDecorator(this)._getter; + } +- _reinstallTimerTimeout() { +- this._clearTimerTimeout(); +- this._installTimerTimeout(); +- } +- _clearTimerTimeout() { +- if (this._timerTimeoutId === null) { +- return; +- } +- this._platform.clearTimeout(this._timerTimeoutId); +- this._timerTimeoutId = null; +- } +- _installTimerTimeout() { +- if (this._timers.length === 0) { +- return; +- } +- let minExpiresAt = this._timers[0]; +- let n = this._platform.now(); +- let wait = Math.max(0, minExpiresAt - n); +- this._timerTimeoutId = this._platform.setTimeout(this._boundRunExpiredTimers, wait); +- } +- _ensureInstance() { +- let currentInstance = this.currentInstance; +- if (currentInstance === null) { +- this._autorunStack = this.DEBUG ? new Error() : undefined; +- currentInstance = this.begin(); +- this._scheduleAutorun(this.queueNames[0]); +- } +- return currentInstance; +- } +- _scheduleAutorun(plannedNextQueue) { +- autorunsCreatedCount++; +- const next = this._platform.next; +- const flush = this.options.flush; +- if (flush) { +- flush(plannedNextQueue, next); +- } else { +- next(); +- } +- this._autorun = true; ++ ++ // TODO: Refactor this, this is an internal API only ++ /** @internal */ ++ set enumerable(value) { ++ descriptorForDecorator(this).enumerable = value; + } + } +- Backburner.Queue = Queue; +- Backburner.buildPlatform = buildPlatform; +- Backburner.buildNext = buildNext; + +- const backburnerjs = /*#__PURE__*/Object.defineProperty({ +- __proto__: null, +- buildPlatform, +- default: Backburner +- }, Symbol.toStringTag, { value: 'Module' }); ++ /** ++ This helper returns a new property descriptor that wraps the passed ++ computed property function. You can use this helper to define properties with ++ native decorator syntax, mixins, or via `defineProperty()`. + +- // Partial types from https://medium.com/codex/currying-in-typescript-ca5226c85b85 ++ Example: + +- let currentRunLoop = null; +- function _getCurrentRunLoop() { +- return currentRunLoop; +- } +- function onBegin(current) { +- currentRunLoop = current; +- } +- function onEnd(_current, next) { +- currentRunLoop = next; +- flushAsyncObservers(); +- } +- function flush$1(queueName, next) { +- if (queueName === 'render' || queueName === _rsvpErrorQueue) { +- flushAsyncObservers(); ++ ```js ++ import { computed, set } from '@ember/object'; ++ ++ class Person { ++ constructor() { ++ this.firstName = 'Betty'; ++ this.lastName = 'Jones'; ++ }, ++ ++ @computed('firstName', 'lastName') ++ get fullName() { ++ return `${this.firstName} ${this.lastName}`; ++ } + } +- next(); +- } +- const _rsvpErrorQueue = `${Math.random()}${Date.now()}`.replace('.', ''); + +- /** +- Array of named queues. This array determines the order in which queues +- are flushed at the end of the RunLoop. You can define your own queues by +- simply adding the queue name to this array. Normally you should not need +- to inspect or modify this property. ++ let client = new Person(); + +- @property queues +- @type Array +- @default ['actions', 'destroy'] +- @private +- */ +- const _queues = ['actions', +- // used in router transitions to prevent unnecessary loading state entry +- // if all context promises resolve on the 'actions' queue first +- 'routerTransitions', 'render', 'afterRender', 'destroy', +- // used to re-throw unhandled RSVP rejection errors specifically in this +- // position to avoid breaking anything rendered in the other sections +- _rsvpErrorQueue]; ++ client.fullName; // 'Betty Jones' + +- /** +- * @internal +- * @private +- */ +- const _backburner = new Backburner(_queues, { +- defaultQueue: 'actions', +- onBegin, +- onEnd, +- onErrorTarget, +- onErrorMethod: 'onerror', +- flush: flush$1 +- }); ++ set(client, 'lastName', 'Fuller'); ++ client.fullName; // 'Betty Fuller' ++ ``` + +- /** +- @module @ember/runloop +- */ +- // .......................................................... +- // run - this is ideally the only public API the dev sees +- // ++ Classic Class Example: + +- /** +- Runs the passed target and method inside of a RunLoop, ensuring any +- deferred actions including bindings and views updates are flushed at the +- end. ++ ```js ++ import EmberObject, { computed } from '@ember/object'; + +- Normally you should not need to invoke this method yourself. However if +- you are implementing raw event handlers when interfacing with other +- libraries or plugins, you should probably wrap all of your code inside this +- call. ++ let Person = EmberObject.extend({ ++ init() { ++ this._super(...arguments); + +- ```javascript +- import { run } from '@ember/runloop'; ++ this.firstName = 'Betty'; ++ this.lastName = 'Jones'; ++ }, + +- run(function() { +- // code to be executed within a RunLoop ++ fullName: computed('firstName', 'lastName', function() { ++ return `${this.get('firstName')} ${this.get('lastName')}`; ++ }) + }); +- ``` +- @method run +- @for @ember/runloop +- @static +- @param {Object} [target] target of method to call +- @param {Function|String} method Method to invoke. +- May be a function or a string. If you pass a string +- then it will be looked up on the passed target. +- @param {Object} [args*] Any additional arguments you wish to pass to the method. +- @return {Object} return value from invoking the passed function. +- @public +- */ +- +- function run$1(...args) { +- // @ts-expect-error TS doesn't like our spread args +- return _backburner.run(...args); +- } + +- /** +- If no run-loop is present, it creates a new one. If a run loop is +- present it will queue itself to run on the existing run-loops action +- queue. ++ let client = Person.create(); + +- Please note: This is not for normal usage, and should be used sparingly. ++ client.get('fullName'); // 'Betty Jones' + +- If invoked when not within a run loop: ++ client.set('lastName', 'Fuller'); ++ client.get('fullName'); // 'Betty Fuller' ++ ``` + +- ```javascript +- import { join } from '@ember/runloop'; ++ You can also provide a setter, either directly on the class using native class ++ syntax, or by passing a hash with `get` and `set` functions. + +- join(function() { +- // creates a new run-loop +- }); +- ``` ++ Example: + +- Alternatively, if called within an existing run loop: ++ ```js ++ import { computed, set } from '@ember/object'; + +- ```javascript +- import { run, join } from '@ember/runloop'; ++ class Person { ++ constructor() { ++ this.firstName = 'Betty'; ++ this.lastName = 'Jones'; ++ }, + +- run(function() { +- // creates a new run-loop ++ @computed('firstName', 'lastName') ++ get fullName() { ++ return `${this.firstName} ${this.lastName}`; ++ } + +- join(function() { +- // joins with the existing run-loop, and queues for invocation on +- // the existing run-loops action queue. +- }); +- }); +- ``` ++ set fullName(value) { ++ let [firstName, lastName] = value.split(/\s+/); + +- @method join +- @static +- @for @ember/runloop +- @param {Object} [target] target of method to call +- @param {Function|String} method Method to invoke. +- May be a function or a string. If you pass a string +- then it will be looked up on the passed target. +- @param {Object} [args*] Any additional arguments you wish to pass to the method. +- @return {Object} Return value from invoking the passed function. Please note, +- when called within an existing loop, no return value is possible. +- @public +- */ ++ set(this, 'firstName', firstName); ++ set(this, 'lastName', lastName); + +- function join(methodOrTarget, methodOrArg, ...additionalArgs) { +- return _backburner.join(methodOrTarget, methodOrArg, ...additionalArgs); +- } ++ return value; ++ } ++ } + +- /** +- Allows you to specify which context to call the specified function in while +- adding the execution of that function to the Ember run loop. This ability +- makes this method a great way to asynchronously integrate third-party libraries +- into your Ember application. ++ let client = new Person(); + +- `bind` takes two main arguments, the desired context and the function to +- invoke in that context. Any additional arguments will be supplied as arguments +- to the function that is passed in. ++ client.fullName; // 'Betty Jones' + +- Let's use the creation of a TinyMCE component as an example. Currently, +- TinyMCE provides a setup configuration option we can use to do some processing +- after the TinyMCE instance is initialized but before it is actually rendered. +- We can use that setup option to do some additional setup for our component. +- The component itself could look something like the following: ++ set(client, 'lastName', 'Fuller'); ++ client.fullName; // 'Betty Fuller' ++ ``` + +- ```app/components/rich-text-editor.js +- import Component from '@ember/component'; +- import { on } from '@ember/object/evented'; +- import { bind } from '@ember/runloop'; ++ Classic Class Example: + +- export default Component.extend({ +- initializeTinyMCE: on('didInsertElement', function() { +- tinymce.init({ +- selector: '#' + this.$().prop('id'), +- setup: bind(this, this.setupEditor) +- }); +- }), ++ ```js ++ import EmberObject, { computed } from '@ember/object'; + +- didInsertElement() { +- tinymce.init({ +- selector: '#' + this.$().prop('id'), +- setup: bind(this, this.setupEditor) +- }); +- } ++ let Person = EmberObject.extend({ ++ init() { ++ this._super(...arguments); + +- setupEditor(editor) { +- this.set('editor', editor); ++ this.firstName = 'Betty'; ++ this.lastName = 'Jones'; ++ }, + +- editor.on('change', function() { +- console.log('content changed!'); +- }); +- } ++ fullName: computed('firstName', 'lastName', { ++ get(key) { ++ return `${this.get('firstName')} ${this.get('lastName')}`; ++ }, ++ set(key, value) { ++ let [firstName, lastName] = value.split(/\s+/); ++ this.setProperties({ firstName, lastName }); ++ return value; ++ } ++ }) + }); ++ ++ let client = Person.create(); ++ client.get('firstName'); // 'Betty' ++ ++ client.set('fullName', 'Carroll Fuller'); ++ client.get('firstName'); // 'Carroll' + ``` + +- In this example, we use `bind` to bind the setupEditor method to the +- context of the RichTextEditor component and to have the invocation of that +- method be safely handled and executed by the Ember run loop. ++ When passed as an argument, the `set` function should accept two parameters, ++ `key` and `value`. The value returned from `set` will be the new value of the ++ property. + +- @method bind ++ _Note: This is the preferred way to define computed properties when writing third-party ++ libraries that depend on or use Ember, since there is no guarantee that the user ++ will have [prototype Extensions](https://guides.emberjs.com/release/configuring-ember/disabling-prototype-extensions/) enabled._ ++ ++ @method computed ++ @for @ember/object + @static +- @for @ember/runloop +- @param {Object} [target] target of method to call +- @param {Function|String} method Method to invoke. +- May be a function or a string. If you pass a string +- then it will be looked up on the passed target. +- @param {Object} [args*] Any additional arguments you wish to pass to the method. +- @return {Function} returns a new function that will always have a particular context +- @since 1.4.0 ++ @param {String} [dependentKeys*] Optional dependent keys that trigger this computed property. ++ @param {Function} func The computed property function. ++ @return {ComputedDecorator} property decorator instance + @public + */ ++ // @computed without parens or computed with descriptor args + +- // This final fallback is the equivalent of the (quite unsafe!) type for `bind` +- // from TS' defs for `Function.prototype.bind`. In general, it means we have a +- // loss of safety if we do not ++ // @computed with keys only + +- function bind(...curried) { +- (!(function (methodOrTarget, methodOrArg) { +- // Applies the same logic as backburner parseArgs for detecting if a method +- // is actually being passed. +- let length = arguments.length; +- if (length === 0) { +- return false; +- } else if (length === 1) { +- return typeof methodOrTarget === 'function'; +- } else { +- return typeof methodOrArg === 'function' || +- // second argument is a function +- methodOrTarget !== null && typeof methodOrArg === 'string' && methodOrArg in methodOrTarget || +- // second argument is the name of a method in first argument +- typeof methodOrTarget === 'function' //first argument is a function +- ; +- } +- // @ts-expect-error TS doesn't like our spread args +- }(...curried)) && assert$1('could not find a suitable method to bind', function (methodOrTarget, methodOrArg) { +- let length = arguments.length; +- if (length === 0) { +- return false; +- } else if (length === 1) { +- return typeof methodOrTarget === 'function'; +- } else { +- return typeof methodOrArg === 'function' || methodOrTarget !== null && typeof methodOrArg === 'string' && methodOrArg in methodOrTarget || typeof methodOrTarget === 'function'; +- } +- }(...curried))); // @ts-expect-error TS doesn't like our spread args +- return (...args) => join(...curried.concat(args)); ++ // @computed with keys and config ++ ++ // @computed with config only ++ ++ function computed(...args) { ++ (!(!(isElementDescriptor(args.slice(0, 3)) && args.length === 5 && args[4] === true)) && assert$1(`@computed can only be used directly as a native decorator. If you're using tracked in classic classes, add parenthesis to call it like a function: computed()`, !(isElementDescriptor(args.slice(0, 3)) && args.length === 5 && args[4] === true))); ++ if (isElementDescriptor(args)) { ++ // SAFETY: We passed in the impl for this class ++ let decorator = makeComputedDecorator(new ComputedProperty([]), ComputedDecoratorImpl); ++ return decorator(args[0], args[1], args[2]); ++ } ++ ++ // SAFETY: We passed in the impl for this class ++ return makeComputedDecorator(new ComputedProperty(args), ComputedDecoratorImpl); ++ } ++ function autoComputed(...config) { ++ // SAFETY: We passed in the impl for this class ++ return makeComputedDecorator(new AutoComputedProperty(config), ComputedDecoratorImpl); + } + + /** +- Begins a new RunLoop. Any deferred actions invoked after the begin will +- be buffered until you invoke a matching call to `end()`. This is +- a lower-level way to use a RunLoop instead of using `run()`. +- +- ```javascript +- import { begin, end } from '@ember/runloop'; ++ Allows checking if a given property on an object is a computed property. For the most part, ++ this doesn't matter (you would normally just access the property directly and use its value), ++ but for some tooling specific scenarios (e.g. the ember-inspector) it is important to ++ differentiate if a property is a computed property or a "normal" property. + +- begin(); +- // code to be executed within a RunLoop +- end(); +- ``` ++ This will work on either a class's prototype or an instance itself. + +- @method begin + @static +- @for @ember/runloop +- @return {void} +- @public +- */ +- function begin() { +- _backburner.begin(); ++ @method isComputed ++ @for @ember/debug ++ @private ++ */ ++ function isComputed(obj, key) { ++ return Boolean(descriptorForProperty(obj, key)); ++ } ++ ++ function getCachedValueFor(obj, key) { ++ let meta = peekMeta(obj); ++ if (meta) { ++ return meta.valueFor(key); ++ } else { ++ return undefined; ++ } + } + + /** +- Ends a RunLoop. This must be called sometime after you call +- `begin()` to flush any deferred actions. This is a lower-level way +- to use a RunLoop instead of using `run()`. ++ @module @ember/object ++ */ + +- ```javascript +- import { begin, end } from '@ember/runloop'; + +- begin(); +- // code to be executed within a RunLoop +- end(); +- ``` ++ /** ++ NOTE: This is a low-level method used by other parts of the API. You almost ++ never want to call this method directly. Instead you should use ++ `mixin()` to define new properties. + +- @method end +- @static +- @for @ember/runloop +- @return {void} +- @public +- */ +- function end() { +- _backburner.end(); +- } ++ Defines a property on an object. This method works much like the ES5 ++ `Object.defineProperty()` method except that it can also accept computed ++ properties and other special descriptors. + +- /** +- Adds the passed target/method and any optional arguments to the named +- queue to be executed at the end of the RunLoop. If you have not already +- started a RunLoop when calling this method one will be started for you +- automatically. ++ Normally this method takes only three parameters. However if you pass an ++ instance of `Descriptor` as the third param then you can pass an ++ optional value as the fourth parameter. This is often more efficient than ++ creating new descriptor hashes for each property. + +- At the end of a RunLoop, any methods scheduled in this way will be invoked. +- Methods will be invoked in an order matching the named queues defined in +- the `queues` property. ++ ## Examples + + ```javascript +- import { schedule } from '@ember/runloop'; ++ import { defineProperty, computed } from '@ember/object'; + +- schedule('afterRender', this, function() { +- // this will be executed in the 'afterRender' queue +- console.log('scheduled on afterRender queue'); ++ // ES5 compatible mode ++ defineProperty(contact, 'firstName', { ++ writable: true, ++ configurable: false, ++ enumerable: true, ++ value: 'Charles' + }); + +- schedule('actions', this, function() { +- // this will be executed in the 'actions' queue +- console.log('scheduled on actions queue'); +- }); ++ // define a simple property ++ defineProperty(contact, 'lastName', undefined, 'Jolley'); + +- // Note the functions will be run in order based on the run queues order. +- // Output would be: +- // scheduled on actions queue +- // scheduled on afterRender queue ++ // define a computed property ++ defineProperty(contact, 'fullName', computed('firstName', 'lastName', function() { ++ return this.firstName+' '+this.lastName; ++ })); + ``` + +- @method schedule +- @static +- @for @ember/runloop +- @param {String} queue The name of the queue to schedule against. Default queues is 'actions' +- @param {Object} [target] target object to use as the context when invoking a method. +- @param {String|Function} method The method to invoke. If you pass a string it +- will be resolved on the target object at the time the scheduled item is +- invoked allowing you to change the target function. +- @param {Object} [arguments*] Optional arguments to be passed to the queued method. +- @return {*} Timer information for use in canceling, see `cancel`. + @public ++ @method defineProperty ++ @static ++ @for @ember/object ++ @param {Object} obj the object to define this property on. This may be a prototype. ++ @param {String} keyName the name of the property ++ @param {Descriptor} [desc] an instance of `Descriptor` (typically a ++ computed property) or an ES5 descriptor. ++ You must provide this or `data` but not both. ++ @param {*} [data] something other than a descriptor, that will ++ become the explicit value of this property. + */ ++ function defineProperty(obj, keyName, desc, data, _meta) { ++ let meta$1 = _meta === undefined ? meta(obj) : _meta; ++ let previousDesc = descriptorForProperty(obj, keyName, meta$1); ++ let wasDescriptor = previousDesc !== undefined; ++ if (wasDescriptor) { ++ previousDesc.teardown(obj, keyName, meta$1); ++ } ++ if (isClassicDecorator(desc)) { ++ defineDecorator(obj, keyName, desc, meta$1); ++ } else if (desc === null || desc === undefined) { ++ defineValue(obj, keyName, data, wasDescriptor, true); ++ } else { ++ // fallback to ES5 ++ Object.defineProperty(obj, keyName, desc); ++ } + +- function schedule(...args) { +- // @ts-expect-error TS doesn't like the rest args here +- return _backburner.schedule(...args); ++ // if key is being watched, override chains that ++ // were initialized with the prototype ++ if (!meta$1.isPrototypeMeta(obj)) { ++ revalidateObservers(obj); ++ } + } ++ function defineDecorator(obj, keyName, desc, meta) { ++ let propertyDesc; ++ { ++ propertyDesc = desc(obj, keyName, undefined, meta, true); ++ } ++ Object.defineProperty(obj, keyName, propertyDesc); + +- // Used by global test teardown +- function _hasScheduledTimers() { +- return _backburner.hasTimers(); ++ // pass the decorator function forward for backwards compat ++ return desc; + } +- +- // Used by global test teardown +- function _cancelTimers() { +- _backburner.cancelTimers(); ++ function defineValue(obj, keyName, value, wasDescriptor, enumerable = true) { ++ if (wasDescriptor === true || enumerable === false) { ++ Object.defineProperty(obj, keyName, { ++ configurable: true, ++ enumerable, ++ writable: true, ++ value ++ }); ++ } else { ++ { ++ setWithMandatorySetter(obj, keyName, value); ++ } ++ } ++ return value; + } + +- /** +- Invokes the passed target/method and optional arguments after a specified +- period of time. The last parameter of this method must always be a number +- of milliseconds. +- +- You should use this method whenever you need to run some action after a +- period of time instead of using `setTimeout()`. This method will ensure that +- items that expire during the same script execution cycle all execute +- together, which is often more efficient than using a real setTimeout. +- +- ```javascript +- import { later } from '@ember/runloop'; +- +- later(myContext, function() { +- // code here will execute within a RunLoop in about 500ms with this == myContext +- }, 500); +- ``` ++ const EMBER_ARRAYS = new WeakSet(); ++ function setEmberArray(obj) { ++ EMBER_ARRAYS.add(obj); ++ } ++ function isEmberArray(obj) { ++ return EMBER_ARRAYS.has(obj); ++ } + +- @method later +- @static +- @for @ember/runloop +- @param {Object} [target] target of method to invoke +- @param {Function|String} method The method to invoke. +- If you pass a string it will be resolved on the +- target at the time the method is invoked. +- @param {Object} [args*] Optional arguments to pass to the timeout. +- @param {Number} wait Number of milliseconds to wait. +- @return {*} Timer information for use in canceling, see `cancel`. +- @public +- */ ++ const emberArrayinternals = /*#__PURE__*/Object.defineProperty({ ++ __proto__: null, ++ isEmberArray, ++ setEmberArray ++ }, Symbol.toStringTag, { value: 'Module' }); + +- function later(...args) { +- return _backburner.later(...args); ++ const firstDotIndexCache = new Cache(1000, key => key.indexOf('.')); ++ function isPath(path) { ++ return typeof path === 'string' && firstDotIndexCache.get(path) !== -1; + } + + /** +- Schedule a function to run one time during the current RunLoop. This is equivalent +- to calling `scheduleOnce` with the "actions" queue. +- +- @method once +- @static +- @for @ember/runloop +- @param {Object} [target] The target of the method to invoke. +- @param {Function|String} method The method to invoke. +- If you pass a string it will be resolved on the +- target at the time the method is invoked. +- @param {Object} [args*] Optional arguments to pass to the timeout. +- @return {Object} Timer information for use in canceling, see `cancel`. +- @public ++ @module @ember/object + */ + +- function once(...args) { +- // @ts-expect-error TS doesn't like the rest args here +- return _backburner.scheduleOnce('actions', ...args); ++ const PROXY_CONTENT = symbol('PROXY_CONTENT'); ++ let getPossibleMandatoryProxyValue; ++ { ++ getPossibleMandatoryProxyValue = function getPossibleMandatoryProxyValue(obj, keyName) { ++ let content = obj[PROXY_CONTENT]; ++ if (content === undefined) { ++ return obj[keyName]; ++ } else { ++ /* global Reflect */ ++ return Reflect.get(content, keyName, obj); ++ } ++ }; ++ } ++ function hasUnknownProperty(val) { ++ return typeof val === 'object' && val !== null && typeof val.unknownProperty === 'function'; + } + +- /** +- Schedules a function to run one time in a given queue of the current RunLoop. +- Calling this method with the same queue/target/method combination will have +- no effect (past the initial call). ++ // .......................................................... ++ // GET AND SET ++ // ++ // If we are on a platform that supports accessors we can use those. ++ // Otherwise simulate accessors by looking up the property directly on the ++ // object. + +- Note that although you can pass optional arguments these will not be +- considered when looking for duplicates. New arguments will replace previous +- calls. ++ /** ++ Gets the value of a property on an object. If the property is computed, ++ the function will be invoked. If the property is not defined but the ++ object implements the `unknownProperty` method then that will be invoked. + + ```javascript +- import { run, scheduleOnce } from '@ember/runloop'; ++ import { get } from '@ember/object'; ++ get(obj, "name"); ++ ``` + +- function sayHi() { +- console.log('hi'); +- } ++ If you plan to run on IE8 and older browsers then you should use this ++ method anytime you want to retrieve a property on an object that you don't ++ know for sure is private. (Properties beginning with an underscore '_' ++ are considered private.) + +- run(function() { +- scheduleOnce('afterRender', myContext, sayHi); +- scheduleOnce('afterRender', myContext, sayHi); +- // sayHi will only be executed once, in the afterRender queue of the RunLoop +- }); +- ``` ++ On all newer browsers, you only need to use this method to retrieve ++ properties if the property might not be defined on the object and you want ++ to respect the `unknownProperty` handler. Otherwise you can ignore this ++ method. + +- Also note that for `scheduleOnce` to prevent additional calls, you need to +- pass the same function instance. The following case works as expected: ++ Note that if the object itself is `undefined`, this method will throw ++ an error. + +- ```javascript +- function log() { +- console.log('Logging only once'); +- } ++ @method get ++ @for @ember/object ++ @static ++ @param {Object} obj The object to retrieve from. ++ @param {String} keyName The property key to retrieve ++ @return {Object} the property value or `null`. ++ @public ++ */ + +- function scheduleIt() { +- scheduleOnce('actions', myContext, log); ++ function get$2(obj, keyName) { ++ (!(arguments.length === 2) && assert$1(`Get must be called with two arguments; an object and a property key`, arguments.length === 2)); ++ (!(obj !== undefined && obj !== null) && assert$1(`Cannot call get with '${keyName}' on an undefined object.`, obj !== undefined && obj !== null)); ++ (!(typeof keyName === 'string' || typeof keyName === 'number' && !isNaN(keyName)) && assert$1(`The key provided to get must be a string or number, you passed ${keyName}`, typeof keyName === 'string' || typeof keyName === 'number' && !isNaN(keyName))); ++ (!(typeof keyName !== 'string' || keyName.lastIndexOf('this.', 0) !== 0) && assert$1(`'this' in paths is not supported`, typeof keyName !== 'string' || keyName.lastIndexOf('this.', 0) !== 0)); ++ return isPath(keyName) ? _getPath(obj, keyName) : _getProp(obj, keyName); ++ } ++ function _getProp(obj, keyName) { ++ if (obj == null) { ++ return; ++ } ++ let value; ++ if (typeof obj === 'object' || typeof obj === 'function') { ++ { ++ value = getPossibleMandatoryProxyValue(obj, keyName); ++ } ++ if (value === undefined && typeof obj === 'object' && !(keyName in obj) && hasUnknownProperty(obj)) { ++ value = obj.unknownProperty(keyName); ++ } ++ if (isTracking()) { ++ consumeTag(tagFor(obj, keyName)); ++ if (Array.isArray(value) || isEmberArray(value)) { ++ // Add the tag of the returned value if it is an array, since arrays ++ // should always cause updates if they are consumed and then changed ++ consumeTag(tagFor(value, '[]')); ++ } ++ } ++ } else { ++ // SAFETY: It should be ok to access properties on any non-nullish value ++ value = obj[keyName]; ++ } ++ return value; ++ } ++ function _getPath(obj, path, forSet) { ++ let parts = typeof path === 'string' ? path.split('.') : path; ++ for (let part of parts) { ++ if (obj === undefined || obj === null || obj.isDestroyed) { ++ return undefined; ++ } ++ if (forSet && (part === '__proto__' || part === 'constructor')) { ++ return; ++ } ++ obj = _getProp(obj, part); + } ++ return obj; ++ } + +- scheduleIt(); +- scheduleIt(); +- ``` ++ // Warm it up ++ _getProp('foo', 'a'); ++ _getProp('foo', 1); ++ _getProp({}, 'a'); ++ _getProp({}, 1); ++ _getProp({ ++ unknownProperty() {} ++ }, 'a'); ++ _getProp({ ++ unknownProperty() {} ++ }, 1); ++ get$2({}, 'foo'); ++ get$2({}, 'foo.bar'); ++ let fakeProxy = {}; ++ setProxy(fakeProxy); ++ track(() => _getProp({}, 'a')); ++ track(() => _getProp({}, 1)); ++ track(() => _getProp({ ++ a: [] ++ }, 'a')); ++ track(() => _getProp({ ++ a: fakeProxy ++ }, 'a')); + +- But this other case will schedule the function multiple times: ++ /** ++ @module @ember/object ++ */ ++ /** ++ Sets the value of a property on an object, respecting computed properties ++ and notifying observers and other listeners of the change. ++ If the specified property is not defined on the object and the object ++ implements the `setUnknownProperty` method, then instead of setting the ++ value of the property on the object, its `setUnknownProperty` handler ++ will be invoked with the two parameters `keyName` and `value`. + + ```javascript +- import { scheduleOnce } from '@ember/runloop'; +- +- function scheduleIt() { +- scheduleOnce('actions', myContext, function() { +- console.log('Closure'); +- }); +- } +- +- scheduleIt(); +- scheduleIt(); +- +- // "Closure" will print twice, even though we're using `scheduleOnce`, +- // because the function we pass to it won't match the +- // previously scheduled operation. ++ import { set } from '@ember/object'; ++ set(obj, "name", value); + ``` + +- Available queues, and their order, can be found at `queues` +- +- @method scheduleOnce ++ @method set + @static +- @for @ember/runloop +- @param {String} [queue] The name of the queue to schedule against. Default queues is 'actions'. +- @param {Object} [target] The target of the method to invoke. +- @param {Function|String} method The method to invoke. +- If you pass a string it will be resolved on the +- target at the time the method is invoked. +- @param {Object} [args*] Optional arguments to pass to the timeout. +- @return {Object} Timer information for use in canceling, see `cancel`. ++ @for @ember/object ++ @param {Object} obj The object to modify. ++ @param {String} keyName The property key to set ++ @param {Object} value The value to set ++ @return {Object} the passed value. + @public + */ +- +- function scheduleOnce(...args) { +- // @ts-expect-error TS doesn't like the rest args here +- return _backburner.scheduleOnce(...args); ++ function set(obj, keyName, value, tolerant) { ++ (!(arguments.length === 3 || arguments.length === 4) && assert$1(`Set must be called with three or four arguments; an object, a property key, a value and tolerant true/false`, arguments.length === 3 || arguments.length === 4)); ++ (!(obj && typeof obj === 'object' || typeof obj === 'function') && assert$1(`Cannot call set with '${keyName}' on an undefined object.`, obj && typeof obj === 'object' || typeof obj === 'function')); ++ (!(typeof keyName === 'string' || typeof keyName === 'number' && !isNaN(keyName)) && assert$1(`The key provided to set must be a string or number, you passed ${keyName}`, typeof keyName === 'string' || typeof keyName === 'number' && !isNaN(keyName))); ++ (!(typeof keyName !== 'string' || keyName.lastIndexOf('this.', 0) !== 0) && assert$1(`'this' in paths is not supported`, typeof keyName !== 'string' || keyName.lastIndexOf('this.', 0) !== 0)); ++ if (obj.isDestroyed) { ++ (!(tolerant) && assert$1(`calling set on destroyed object: ${toString$1(obj)}.${keyName} = ${toString$1(value)}`, tolerant)); ++ return value; ++ } ++ return isPath(keyName) ? _setPath(obj, keyName, value, tolerant) : _setProp(obj, keyName, value); ++ } ++ function _setProp(obj, keyName, value) { ++ let descriptor = lookupDescriptor(obj, keyName); ++ if (descriptor !== null && COMPUTED_SETTERS.has(descriptor.set)) { ++ obj[keyName] = value; ++ return value; ++ } ++ let currentValue; ++ { ++ currentValue = getPossibleMandatoryProxyValue(obj, keyName); ++ } ++ if (currentValue === undefined && 'object' === typeof obj && !(keyName in obj) && typeof obj.setUnknownProperty === 'function') { ++ /* unknown property */ ++ obj.setUnknownProperty(keyName, value); ++ } else { ++ { ++ setWithMandatorySetter(obj, keyName, value); ++ } ++ if (currentValue !== value) { ++ notifyPropertyChange(obj, keyName); ++ } ++ } ++ return value; ++ } ++ function _setPath(root, path, value, tolerant) { ++ let parts = path.split('.'); ++ let keyName = parts.pop(); ++ (!(keyName.trim().length > 0) && assert$1('Property set failed: You passed an empty path', keyName.trim().length > 0)); ++ let newRoot = _getPath(root, parts, true); ++ if (newRoot !== null && newRoot !== undefined) { ++ return set(newRoot, keyName, value); ++ } else if (!tolerant) { ++ throw new Error(`Property set failed: object in path "${parts.join('.')}" could not be found.`); ++ } + } + + /** +- Schedules an item to run from within a separate run loop, after +- control has been returned to the system. This is equivalent to calling +- `later` with a wait time of 1ms. ++ Error-tolerant form of `set`. Will not blow up if any part of the ++ chain is `undefined`, `null`, or destroyed. ++ ++ This is primarily used when syncing bindings, which may try to update after ++ an object has been destroyed. + + ```javascript +- import { next } from '@ember/runloop'; ++ import { trySet } from '@ember/object'; + +- next(myContext, function() { +- // code to be executed in the next run loop, +- // which will be scheduled after the current one +- }); ++ let obj = { name: "Zoey" }; ++ trySet(obj, "contacts.twitter", "@emberjs"); + ``` + +- Multiple operations scheduled with `next` will coalesce +- into the same later run loop, along with any other operations +- scheduled by `later` that expire right around the same +- time that `next` operations will fire. +- +- Note that there are often alternatives to using `next`. +- For instance, if you'd like to schedule an operation to happen +- after all DOM element operations have completed within the current +- run loop, you can make use of the `afterRender` run loop queue (added +- by the `ember-views` package, along with the preceding `render` queue +- where all the DOM element operations happen). +- +- Example: +- +- ```app/components/my-component.js +- import Component from '@ember/component'; +- import { scheduleOnce } from '@ember/runloop'; ++ @method trySet ++ @static ++ @for @ember/object ++ @param {Object} root The object to modify. ++ @param {String} path The property path to set ++ @param {Object} value The value to set ++ @public ++ */ ++ function trySet(root, path, value) { ++ return set(root, path, value, true); ++ } + +- export Component.extend({ +- didInsertElement() { +- this._super(...arguments); +- scheduleOnce('afterRender', this, 'processChildElements'); +- }, ++ function alias(altKey) { ++ (!(!isElementDescriptor(Array.prototype.slice.call(arguments))) && assert$1('You attempted to use @alias as a decorator directly, but it requires a `altKey` parameter', !isElementDescriptor(Array.prototype.slice.call(arguments)))); // SAFETY: We passed in the impl for this class ++ return makeComputedDecorator(new AliasedProperty(altKey), AliasDecoratorImpl); ++ } + +- processChildElements() { +- // ... do something with component's child component +- // elements after they've finished rendering, which +- // can't be done within this component's +- // `didInsertElement` hook because that gets run +- // before the child elements have been added to the DOM. ++ // TODO: This class can be svelted once `meta` has been deprecated ++ class AliasDecoratorImpl extends Function { ++ readOnly() { ++ descriptorForDecorator(this).readOnly(); ++ return this; ++ } ++ oneWay() { ++ descriptorForDecorator(this).oneWay(); ++ return this; ++ } ++ meta(meta) { ++ let prop = descriptorForDecorator(this); ++ if (arguments.length === 0) { ++ return prop._meta || {}; ++ } else { ++ prop._meta = meta; + } +- }); +- ``` +- +- One benefit of the above approach compared to using `next` is +- that you will be able to perform DOM/CSS operations before unprocessed +- elements are rendered to the screen, which may prevent flickering or +- other artifacts caused by delaying processing until after rendering. ++ } ++ } ++ class AliasedProperty extends ComputedDescriptor { ++ altKey; ++ constructor(altKey) { ++ super(); ++ this.altKey = altKey; ++ } ++ setup(obj, keyName, propertyDesc, meta) { ++ (!(this.altKey !== keyName) && assert$1(`Setting alias '${keyName}' on self`, this.altKey !== keyName)); ++ super.setup(obj, keyName, propertyDesc, meta); ++ CHAIN_PASS_THROUGH.add(this); ++ } ++ get(obj, keyName) { ++ let ret; ++ let meta$1 = meta(obj); ++ let tagMeta = tagMetaFor(obj); ++ let propertyTag = tagFor(obj, keyName, tagMeta); + +- The other major benefit to the above approach is that `next` +- introduces an element of non-determinism, which can make things much +- harder to test, due to its reliance on `setTimeout`; it's much harder +- to guarantee the order of scheduled operations when they are scheduled +- outside of the current run loop, i.e. with `next`. ++ // We don't use the tag since CPs are not automatic, we just want to avoid ++ // anything tracking while we get the altKey ++ untrack(() => { ++ ret = get$2(obj, this.altKey); ++ }); ++ let lastRevision = meta$1.revisionFor(keyName); ++ if (lastRevision === undefined || !validateTag(propertyTag, lastRevision)) { ++ UPDATE_TAG(propertyTag, getChainTagsForKey(obj, this.altKey, tagMeta, meta$1)); ++ meta$1.setRevisionFor(keyName, valueForTag(propertyTag)); ++ finishLazyChains(meta$1, keyName, ret); ++ } ++ consumeTag(propertyTag); ++ return ret; ++ } ++ set(obj, _keyName, value) { ++ return set(obj, this.altKey, value); ++ } ++ readOnly() { ++ this.set = AliasedProperty_readOnlySet; ++ } ++ oneWay() { ++ this.set = AliasedProperty_oneWaySet; ++ } ++ } ++ function AliasedProperty_readOnlySet(obj, keyName) { ++ throw new Error(`Cannot set read-only property '${keyName}' on object: ${inspect(obj)}`); ++ } ++ function AliasedProperty_oneWaySet(obj, keyName, value) { ++ defineProperty(obj, keyName, null); ++ return set(obj, keyName, value); ++ } + +- @method next +- @static +- @for @ember/runloop +- @param {Object} [target] target of method to invoke +- @param {Function|String} method The method to invoke. +- If you pass a string it will be resolved on the +- target at the time the method is invoked. +- @param {Object} [args*] Optional arguments to pass to the timeout. +- @return {Object} Timer information for use in canceling, see `cancel`. +- @public ++ /** ++ @module ember + */ + +- function next(...args) { +- return _backburner.later(...args, 1); +- } + + /** +- Cancels a scheduled item. Must be a value returned by `later()`, +- `once()`, `scheduleOnce()`, `next()`, `debounce()`, or +- `throttle()`. ++ Used internally to allow changing properties in a backwards compatible way, and print a helpful ++ deprecation warning. + +- ```javascript +- import { +- next, +- cancel, +- later, +- scheduleOnce, +- once, +- throttle, +- debounce +- } from '@ember/runloop'; ++ @method deprecateProperty ++ @param {Object} object The object to add the deprecated property to. ++ @param {String} deprecatedKey The property to add (and print deprecation warnings upon accessing). ++ @param {String} newKey The property that will be aliased. ++ @private ++ @since 1.7.0 ++ */ + +- let runNext = next(myContext, function() { +- // will not be executed ++ function deprecateProperty(object, deprecatedKey, newKey, options) { ++ function _deprecate() { ++ (deprecate$1(`Usage of \`${deprecatedKey}\` is deprecated, use \`${newKey}\` instead.`, false, options)); ++ } ++ Object.defineProperty(object, deprecatedKey, { ++ configurable: true, ++ enumerable: false, ++ set(value) { ++ _deprecate(); ++ set(this, newKey, value); ++ }, ++ get() { ++ _deprecate(); ++ return get$2(this, newKey); ++ } + }); ++ } + +- cancel(runNext); +- +- let runLater = later(myContext, function() { +- // will not be executed +- }, 500); +- +- cancel(runLater); +- +- let runScheduleOnce = scheduleOnce('afterRender', myContext, function() { +- // will not be executed +- }); +- +- cancel(runScheduleOnce); +- +- let runOnce = once(myContext, function() { +- // will not be executed +- }); ++ function arrayContentWillChange(array, startIdx, removeAmt, addAmt) { ++ // if no args are passed assume everything changes ++ if (startIdx === undefined) { ++ startIdx = 0; ++ removeAmt = addAmt = -1; ++ } else { ++ if (removeAmt === undefined) { ++ removeAmt = -1; ++ } ++ if (addAmt === undefined) { ++ addAmt = -1; ++ } ++ } ++ sendEvent(array, '@array:before', [array, startIdx, removeAmt, addAmt]); ++ return array; ++ } ++ function arrayContentDidChange(array, startIdx, removeAmt, addAmt, notify = true) { ++ // if no args are passed assume everything changes ++ if (startIdx === undefined) { ++ startIdx = 0; ++ removeAmt = addAmt = -1; ++ } else { ++ if (removeAmt === undefined) { ++ removeAmt = -1; ++ } ++ if (addAmt === undefined) { ++ addAmt = -1; ++ } ++ } ++ let meta = peekMeta(array); ++ if (notify) { ++ if (addAmt < 0 || removeAmt < 0 || addAmt - removeAmt !== 0) { ++ notifyPropertyChange(array, 'length', meta); ++ } ++ notifyPropertyChange(array, '[]', meta); ++ } ++ sendEvent(array, '@array:change', [array, startIdx, removeAmt, addAmt]); ++ if (meta !== null) { ++ let length = array.length; ++ let addedAmount = addAmt === -1 ? 0 : addAmt; ++ let removedAmount = removeAmt === -1 ? 0 : removeAmt; ++ let delta = addedAmount - removedAmount; ++ let previousLength = length - delta; ++ let normalStartIdx = startIdx < 0 ? previousLength + startIdx : startIdx; ++ if (meta.revisionFor('firstObject') !== undefined && normalStartIdx === 0) { ++ notifyPropertyChange(array, 'firstObject', meta); ++ } ++ if (meta.revisionFor('lastObject') !== undefined) { ++ let previousLastIndex = previousLength - 1; ++ let lastAffectedIndex = normalStartIdx + removedAmount; ++ if (previousLastIndex < lastAffectedIndex) { ++ notifyPropertyChange(array, 'lastObject', meta); ++ } ++ } ++ } ++ return array; ++ } + +- cancel(runOnce); ++ const EMPTY_ARRAY$3 = Object.freeze([]); + +- let throttle = throttle(myContext, function() { +- // will not be executed +- }, 1, false); ++ // Ideally, we'd use MutableArray.detect but for unknown reasons this causes ++ // the node tests to fail strangely. ++ function isMutableArray(obj) { ++ return obj != null && typeof obj.replace === 'function'; ++ } ++ function replace(array, start, deleteCount, items = EMPTY_ARRAY$3) { ++ if (isMutableArray(array)) { ++ array.replace(start, deleteCount, items); ++ } else { ++ (!(Array.isArray(array)) && assert$1('Can only replace content of a native array or MutableArray', Array.isArray(array))); ++ replaceInNativeArray(array, start, deleteCount, items); ++ } ++ } ++ const CHUNK_SIZE = 60000; + +- cancel(throttle); ++ // To avoid overflowing the stack, we splice up to CHUNK_SIZE items at a time. ++ // See https://code.google.com/p/chromium/issues/detail?id=56588 for more details. ++ function replaceInNativeArray(array, start, deleteCount, items) { ++ arrayContentWillChange(array, start, deleteCount, items.length); ++ if (items.length <= CHUNK_SIZE) { ++ array.splice(start, deleteCount, ...items); ++ } else { ++ array.splice(start, deleteCount); ++ for (let i = 0; i < items.length; i += CHUNK_SIZE) { ++ let chunk = items.slice(i, i + CHUNK_SIZE); ++ array.splice(start + i, 0, ...chunk); ++ } ++ } ++ arrayContentDidChange(array, start, deleteCount, items.length); ++ } ++ function arrayObserversHelper(obj, target, opts, operation) { ++ let { ++ willChange, ++ didChange ++ } = opts; ++ operation(obj, '@array:before', target, willChange); ++ operation(obj, '@array:change', target, didChange); + +- let debounce = debounce(myContext, function() { +- // will not be executed +- }, 1); ++ /* ++ * Array proxies have a `_revalidate` method which must be called to set ++ * up their internal array observation systems. ++ */ ++ obj._revalidate?.(); ++ return obj; ++ } ++ function addArrayObserver(array, target, opts) { ++ return arrayObserversHelper(array, target, opts, addListener); ++ } ++ function removeArrayObserver(array, target, opts) { ++ return arrayObserversHelper(array, target, opts, removeListener); ++ } + +- cancel(debounce); ++ const EACH_PROXIES = new WeakMap(); ++ function eachProxyArrayWillChange(array, idx, removedCnt, addedCnt) { ++ let eachProxy = EACH_PROXIES.get(array); ++ if (eachProxy !== undefined) { ++ eachProxy.arrayWillChange(array, idx, removedCnt, addedCnt); ++ } ++ } ++ function eachProxyArrayDidChange(array, idx, removedCnt, addedCnt) { ++ let eachProxy = EACH_PROXIES.get(array); ++ if (eachProxy !== undefined) { ++ eachProxy.arrayDidChange(array, idx, removedCnt, addedCnt); ++ } ++ } + +- let debounceImmediate = debounce(myContext, function() { +- // will be executed since we passed in true (immediate) +- }, 100, true); ++ /** ++ @module ember ++ */ ++ /** ++ Helper class that allows you to register your library with Ember. + +- // the 100ms delay until this method can be called again will be canceled +- cancel(debounceImmediate); +- ``` ++ Singleton created at `Ember.libraries`. + +- @method cancel +- @static +- @for @ember/runloop +- @param {Object} [timer] Timer object to cancel +- @return {Boolean} true if canceled or false/undefined if it wasn't found +- @public ++ @class Libraries ++ @constructor ++ @private + */ +- function cancel(timer) { +- return _backburner.cancel(timer); ++ class Libraries { ++ _registry; ++ _coreLibIndex; ++ constructor() { ++ this._registry = []; ++ this._coreLibIndex = 0; ++ } ++ _getLibraryByName(name) { ++ let libs = this._registry; ++ for (let lib of libs) { ++ if (lib.name === name) { ++ return lib; ++ } ++ } ++ return undefined; ++ } ++ register(name, version, isCoreLibrary) { ++ let index = this._registry.length; ++ if (!this._getLibraryByName(name)) { ++ if (isCoreLibrary) { ++ index = this._coreLibIndex++; ++ } ++ this._registry.splice(index, 0, { ++ name, ++ version ++ }); ++ } else { ++ (warn(`Library "${name}" is already registered with Ember.`, false, { ++ id: 'ember-metal.libraries-register' ++ })); ++ } ++ } ++ registerCoreLibrary(name, version) { ++ this.register(name, version, true); ++ } ++ deRegister(name) { ++ let lib = this._getLibraryByName(name); ++ let index; ++ if (lib) { ++ index = this._registry.indexOf(lib); ++ this._registry.splice(index, 1); ++ } ++ } ++ isRegistered; ++ logVersions; ++ } ++ { ++ Libraries.prototype.logVersions = function () { ++ let libs = this._registry; ++ let nameLengths = libs.map(item => get$2(item, 'name.length')); ++ (!(nameLengths instanceof Array && nameLengths.every(n => typeof n === 'number')) && assert$1('nameLengths is number array', nameLengths instanceof Array && nameLengths.every(n => typeof n === 'number'))); ++ let maxNameLength = Math.max.apply(null, nameLengths); ++ debug$2('-------------------------------'); ++ for (let lib of libs) { ++ let spaces = new Array(maxNameLength - lib.name.length + 1).join(' '); ++ debug$2([lib.name, spaces, ' : ', lib.version].join('')); ++ } ++ debug$2('-------------------------------'); ++ }; + } ++ const LIBRARIES = new Libraries(); ++ LIBRARIES.registerCoreLibrary('Ember', Version); + + /** +- Delay calling the target method until the debounce period has elapsed +- with no additional debounce calls. If `debounce` is called again before +- the specified time has elapsed, the timer is reset and the entire period +- must pass again before the target method is called. ++ @module @ember/object ++ */ + +- This method should be used when an event may be called multiple times +- but the action should only be called once when the event is done firing. +- A common example is for scroll events where you only want updates to +- happen once scrolling has ceased. ++ /** ++ To get multiple properties at once, call `getProperties` ++ with an object followed by a list of strings or an array: + + ```javascript +- import { debounce } from '@ember/runloop'; +- +- function whoRan() { +- console.log(this.name + ' ran.'); +- } +- +- let myContext = { name: 'debounce' }; +- +- debounce(myContext, whoRan, 150); +- +- // less than 150ms passes +- debounce(myContext, whoRan, 150); ++ import { getProperties } from '@ember/object'; + +- // 150ms passes +- // whoRan is invoked with context myContext +- // console logs 'debounce ran.' one time. ++ getProperties(record, 'firstName', 'lastName', 'zipCode'); ++ // { firstName: 'John', lastName: 'Doe', zipCode: '10011' } + ``` + +- Immediate allows you to run the function immediately, but debounce +- other calls for this function until the wait time has elapsed. If +- `debounce` is called again before the specified time has elapsed, +- the timer is reset and the entire period must pass again before +- the method can be called again. ++ is equivalent to: + + ```javascript +- import { debounce } from '@ember/runloop'; +- +- function whoRan() { +- console.log(this.name + ' ran.'); +- } +- +- let myContext = { name: 'debounce' }; +- +- debounce(myContext, whoRan, 150, true); +- +- // console logs 'debounce ran.' one time immediately. +- // 100ms passes +- debounce(myContext, whoRan, 150, true); +- +- // 150ms passes and nothing else is logged to the console and +- // the debouncee is no longer being watched +- debounce(myContext, whoRan, 150, true); ++ import { getProperties } from '@ember/object'; + +- // console logs 'debounce ran.' one time immediately. +- // 150ms passes and nothing else is logged to the console and +- // the debouncee is no longer being watched ++ getProperties(record, ['firstName', 'lastName', 'zipCode']); ++ // { firstName: 'John', lastName: 'Doe', zipCode: '10011' } + ``` + +- @method debounce ++ @method getProperties + @static +- @for @ember/runloop +- @param {Object} [target] target of method to invoke +- @param {Function|String} method The method to invoke. +- May be a function or a string. If you pass a string +- then it will be looked up on the passed target. +- @param {Object} [args*] Optional arguments to pass to the timeout. +- @param {Number} wait Number of milliseconds to wait. +- @param {Boolean} immediate Trigger the function on the leading instead +- of the trailing edge of the wait interval. Defaults to false. +- @return {Array} Timer information for use in canceling, see `cancel`. ++ @for @ember/object ++ @param {Object} obj ++ @param {String...|Array} list of keys to get ++ @return {Object} + @public + */ + +- function debounce(...args) { +- // @ts-expect-error TS doesn't like the rest args here +- return _backburner.debounce(...args); ++ function getProperties(obj, keys) { ++ let ret = {}; ++ let propertyNames; ++ let i = 1; ++ if (arguments.length === 2 && Array.isArray(keys)) { ++ i = 0; ++ propertyNames = arguments[1]; ++ } else { ++ propertyNames = Array.from(arguments); ++ } ++ for (; i < propertyNames.length; i++) { ++ // SAFETY: we are just walking the list of property names, so we know the ++ // index access never produces `undefined`. ++ let name = propertyNames[i]; ++ ret[name] = get$2(obj, name); ++ } ++ return ret; + } + + /** +- Ensure that the target method is never called more frequently than +- the specified spacing period. The target method is called immediately. ++ @module @ember/object ++ */ ++ /** ++ Set a list of properties on an object. These properties are set inside ++ a single `beginPropertyChanges` and `endPropertyChanges` batch, so ++ observers will be buffered. + + ```javascript +- import { throttle } from '@ember/runloop'; +- +- function whoRan() { +- console.log(this.name + ' ran.'); +- } +- +- let myContext = { name: 'throttle' }; +- +- throttle(myContext, whoRan, 150); +- // whoRan is invoked with context myContext +- // console logs 'throttle ran.' +- +- // 50ms passes +- throttle(myContext, whoRan, 150); +- +- // 50ms passes +- throttle(myContext, whoRan, 150); ++ import EmberObject from '@ember/object'; ++ let anObject = EmberObject.create(); + +- // 150ms passes +- throttle(myContext, whoRan, 150); +- // whoRan is invoked with context myContext +- // console logs 'throttle ran.' ++ anObject.setProperties({ ++ firstName: 'Stanley', ++ lastName: 'Stuart', ++ age: 21 ++ }); + ``` + +- @method throttle ++ @method setProperties + @static +- @for @ember/runloop +- @param {Object} [target] target of method to invoke +- @param {Function|String} method The method to invoke. +- May be a function or a string. If you pass a string +- then it will be looked up on the passed target. +- @param {Object} [args*] Optional arguments to pass to the timeout. +- @param {Number} spacing Number of milliseconds to space out requests. +- @param {Boolean} immediate Trigger the function on the leading instead +- of the trailing edge of the wait interval. Defaults to true. +- @return {Array} Timer information for use in canceling, see `cancel`. ++ @for @ember/object ++ @param obj ++ @param {Object} properties ++ @return properties + @public + */ + +- function throttle(...args) { +- // @ts-expect-error TS doesn't like the rest args here +- return _backburner.throttle(...args); ++ function setProperties(obj, properties) { ++ if (properties === null || typeof properties !== 'object') { ++ return properties; ++ } ++ changeProperties(() => { ++ let props = Object.keys(properties); ++ for (let propertyName of props) { ++ // SAFETY: casting `properties` this way is safe because any object in JS ++ // can be indexed this way, and the result will be `unknown`, making it ++ // safe for callers. ++ set(obj, propertyName, properties[propertyName]); ++ } ++ }); ++ return properties; + } + +- const emberRunloopIndex = /*#__PURE__*/Object.defineProperty({ +- __proto__: null, +- _backburner, +- _cancelTimers, +- _getCurrentRunLoop, +- _hasScheduledTimers, +- _queues, +- _rsvpErrorQueue, +- begin, +- bind, +- cancel, +- debounce, +- end, +- join, +- later, +- next, +- once, +- run: run$1, +- schedule, +- scheduleOnce, +- throttle +- }, Symbol.toStringTag, { value: 'Module' }); +- +- const AFTER_OBSERVERS = ':change'; +- function changeEvent(keyName) { +- return keyName + AFTER_OBSERVERS; ++ let DEBUG_INJECTION_FUNCTIONS; ++ { ++ DEBUG_INJECTION_FUNCTIONS = new WeakMap(); + } + +- const SYNC_DEFAULT = !ENV._DEFAULT_ASYNC_OBSERVERS; +- const SYNC_OBSERVERS = new Map(); +- const ASYNC_OBSERVERS = new Map(); +- + /** +- @module @ember/object +- */ ++ @module ember ++ @private ++ */ + + /** +- @method addObserver +- @static +- @for @ember/object/observers +- @param obj +- @param {String} path +- @param {Object|Function} target +- @param {Function|String} [method] +- @public ++ Read-only property that returns the result of a container lookup. ++ ++ @class InjectedProperty ++ @namespace Ember ++ @constructor ++ @param {String} type The container type the property will lookup ++ @param {String} nameOrDesc (optional) The name the property will lookup, defaults ++ to the property's name ++ @private + */ +- function addObserver(obj, path, target, method, sync = SYNC_DEFAULT) { +- let eventName = changeEvent(path); +- addListener(obj, eventName, target, method, false, sync); +- let meta = peekMeta(obj); +- if (meta === null || !(meta.isPrototypeMeta(obj) || meta.isInitializing())) { +- activateObserver(obj, eventName, sync); ++ // Decorator factory (with args) ++ // (Also matches non-decorator form, types may be incorrect for this.) ++ ++ // Non-decorator ++ ++ // Decorator (without args) ++ ++ // Catch-all for service and controller injections ++ ++ function inject$2(type, ...args) { ++ (!(typeof type === 'string') && assert$1('a string type must be provided to inject', typeof type === 'string')); ++ let elementDescriptor; ++ let name; ++ if (isElementDescriptor(args)) { ++ elementDescriptor = args; ++ } else if (typeof args[0] === 'string') { ++ name = args[0]; ++ } ++ let getInjection = function (propertyName) { ++ let owner = getOwner$2(this) || this.container; // fallback to `container` for backwards compat ++ (!(Boolean(owner)) && assert$1(`Attempting to lookup an injected property on an object without a container, ensure that the object was instantiated via a container.`, Boolean(owner))); ++ return owner.lookup(`${type}:${name || propertyName}`); ++ }; ++ { ++ DEBUG_INJECTION_FUNCTIONS.set(getInjection, { ++ type, ++ name ++ }); ++ } ++ let decorator = computed({ ++ get: getInjection, ++ set(keyName, value) { ++ defineProperty(this, keyName, null, value); ++ } ++ }); ++ if (elementDescriptor) { ++ return decorator(elementDescriptor[0], elementDescriptor[1], elementDescriptor[2]); ++ } else { ++ return decorator; + } + } + + /** +- @method removeObserver +- @static +- @for @ember/object/observers +- @param obj +- @param {String} path +- @param {Object|Function} target +- @param {Function|String} [method] +- @public +- */ +- function removeObserver(obj, path, target, method, sync = SYNC_DEFAULT) { +- let eventName = changeEvent(path); +- let meta = peekMeta(obj); +- if (meta === null || !(meta.isPrototypeMeta(obj) || meta.isInitializing())) { +- deactivateObserver(obj, eventName, sync); +- } +- removeListener(obj, eventName, target, method); +- } +- function getOrCreateActiveObserversFor(target, sync) { +- let observerMap = sync === true ? SYNC_OBSERVERS : ASYNC_OBSERVERS; +- if (!observerMap.has(target)) { +- observerMap.set(target, new Map()); +- registerDestructor$1(target, () => destroyObservers(target), true); +- } +- return observerMap.get(target); +- } +- function activateObserver(target, eventName, sync = false) { +- let activeObservers = getOrCreateActiveObserversFor(target, sync); +- if (activeObservers.has(eventName)) { +- activeObservers.get(eventName).count++; +- } else { +- let path = eventName.substring(0, eventName.lastIndexOf(':')); +- let tag = getChainTagsForKey(target, path, tagMetaFor(target), peekMeta(target)); +- activeObservers.set(eventName, { +- count: 1, +- path, +- tag, +- lastRevision: valueForTag(tag), +- suspended: false +- }); ++ @decorator ++ @private ++ ++ Marks a property as tracked. ++ ++ By default, a component's properties are expected to be static, ++ meaning you are not able to update them and have the template update accordingly. ++ Marking a property as tracked means that when that property changes, ++ a rerender of the component is scheduled so the template is kept up to date. ++ ++ There are two usages for the `@tracked` decorator, shown below. ++ ++ @example No dependencies ++ ++ If you don't pass an argument to `@tracked`, only changes to that property ++ will be tracked: ++ ++ ```typescript ++ import Component from '@glimmer/component'; ++ import { tracked } from '@glimmer/tracking'; ++ ++ export default class MyComponent extends Component { ++ @tracked ++ remainingApples = 10 + } +- } +- let DEACTIVATE_SUSPENDED = false; +- let SCHEDULED_DEACTIVATE = []; +- function deactivateObserver(target, eventName, sync = false) { +- if (DEACTIVATE_SUSPENDED === true) { +- SCHEDULED_DEACTIVATE.push([target, eventName, sync]); +- return; ++ ``` ++ ++ When something changes the component's `remainingApples` property, the rerender ++ will be scheduled. ++ ++ @example Dependents ++ ++ In the case that you have a computed property that depends other ++ properties, you want to track both so that when one of the ++ dependents change, a rerender is scheduled. ++ ++ In the following example we have two properties, ++ `eatenApples`, and `remainingApples`. ++ ++ ```typescript ++ import Component from '@glimmer/component'; ++ import { tracked } from '@glimmer/tracking'; ++ ++ const totalApples = 100; ++ ++ export default class MyComponent extends Component { ++ @tracked ++ eatenApples = 0 ++ ++ get remainingApples() { ++ return totalApples - this.eatenApples; ++ } ++ ++ increment() { ++ this.eatenApples = this.eatenApples + 1; ++ } + } +- let observerMap = sync === true ? SYNC_OBSERVERS : ASYNC_OBSERVERS; +- let activeObservers = observerMap.get(target); +- if (activeObservers !== undefined) { +- let observer = activeObservers.get(eventName); +- observer.count--; +- if (observer.count === 0) { +- activeObservers.delete(eventName); +- if (activeObservers.size === 0) { +- observerMap.delete(target); +- } ++ ``` ++ ++ @param dependencies Optional dependents to be tracked. ++ */ ++ ++ function tracked(...args) { ++ (!(!(isElementDescriptor(args.slice(0, 3)) && args.length === 5 && args[4] === true)) && assert$1(`@tracked can only be used directly as a native decorator. If you're using tracked in classic classes, add parenthesis to call it like a function: tracked()`, !(isElementDescriptor(args.slice(0, 3)) && args.length === 5 && args[4] === true))); ++ if (!isElementDescriptor(args)) { ++ let propertyDesc = args[0]; ++ (!(args.length === 0 || typeof propertyDesc === 'object' && propertyDesc !== null) && assert$1(`tracked() may only receive an options object containing 'value' or 'initializer', received ${propertyDesc}`, args.length === 0 || typeof propertyDesc === 'object' && propertyDesc !== null)); ++ if (propertyDesc) { ++ let keys = Object.keys(propertyDesc); ++ (!(keys.length <= 1 && (keys[0] === undefined || keys[0] === 'value' || keys[0] === 'initializer')) && assert$1(`The options object passed to tracked() may only contain a 'value' or 'initializer' property, not both. Received: [${keys}]`, keys.length <= 1 && (keys[0] === undefined || keys[0] === 'value' || keys[0] === 'initializer'))); ++ (!(!('initializer' in propertyDesc) || typeof propertyDesc.initializer === 'function') && assert$1(`The initializer passed to tracked must be a function. Received ${propertyDesc.initializer}`, !('initializer' in propertyDesc) || typeof propertyDesc.initializer === 'function')); + } ++ let initializer = propertyDesc ? propertyDesc.initializer : undefined; ++ let value = propertyDesc ? propertyDesc.value : undefined; ++ let decorator = function (target, key, _desc, _meta, isClassicDecorator) { ++ (!(isClassicDecorator) && assert$1(`You attempted to set a default value for ${key} with the @tracked({ value: 'default' }) syntax. You can only use this syntax with classic classes. For native classes, you can use class initializers: @tracked field = 'default';`, isClassicDecorator)); ++ let fieldDesc = { ++ initializer: initializer || (() => value) ++ }; ++ return descriptorForField([target, key, fieldDesc]); ++ }; ++ setClassicDecorator(decorator); ++ return decorator; + } ++ return descriptorForField(args); + } +- function suspendedObserverDeactivation() { +- DEACTIVATE_SUSPENDED = true; +- } +- function resumeObserverDeactivation() { +- DEACTIVATE_SUSPENDED = false; +- for (let [target, eventName, sync] of SCHEDULED_DEACTIVATE) { +- deactivateObserver(target, eventName, sync); +- } +- SCHEDULED_DEACTIVATE = []; ++ { ++ // Normally this isn't a classic decorator, but we want to throw a helpful ++ // error in development so we need it to treat it like one ++ setClassicDecorator(tracked); + } ++ function descriptorForField([target, key, desc]) { ++ (!(!desc || !desc.value && !desc.get && !desc.set) && assert$1(`You attempted to use @tracked on ${key}, but that element is not a class field. @tracked is only usable on class fields. Native getters and setters will autotrack add any tracked fields they encounter, so there is no need mark getters and setters with @tracked.`, !desc || !desc.value && !desc.get && !desc.set)); ++ let { ++ getter, ++ setter ++ } = trackedData(key, desc ? desc.initializer : undefined); ++ function get() { ++ let value = getter(this); + +- /** +- * Primarily used for cases where we are redefining a class, e.g. mixins/reopen +- * being applied later. Revalidates all the observers, resetting their tags. +- * +- * @private +- * @param target +- */ +- function revalidateObservers(target) { +- if (ASYNC_OBSERVERS.has(target)) { +- ASYNC_OBSERVERS.get(target).forEach(observer => { +- observer.tag = getChainTagsForKey(target, observer.path, tagMetaFor(target), peekMeta(target)); +- observer.lastRevision = valueForTag(observer.tag); +- }); ++ // Add the tag of the returned value if it is an array, since arrays ++ // should always cause updates if they are consumed and then changed ++ if (Array.isArray(value) || isEmberArray(value)) { ++ consumeTag(tagFor(value, '[]')); ++ } ++ return value; + } +- if (SYNC_OBSERVERS.has(target)) { +- SYNC_OBSERVERS.get(target).forEach(observer => { +- observer.tag = getChainTagsForKey(target, observer.path, tagMetaFor(target), peekMeta(target)); +- observer.lastRevision = valueForTag(observer.tag); +- }); ++ function set(newValue) { ++ setter(this, newValue); ++ dirtyTagFor(this, SELF_TAG); + } ++ let newDesc = { ++ enumerable: true, ++ configurable: true, ++ isTracked: true, ++ get, ++ set ++ }; ++ COMPUTED_SETTERS.add(set); ++ meta(target).writeDescriptors(key, new TrackedDescriptor(get, set)); ++ return newDesc; + } +- let lastKnownRevision = 0; +- function flushAsyncObservers(shouldSchedule = true) { +- let currentRevision = valueForTag(CURRENT_TAG); +- if (lastKnownRevision === currentRevision) { +- return; ++ class TrackedDescriptor { ++ constructor(_get, _set) { ++ this._get = _get; ++ this._set = _set; ++ CHAIN_PASS_THROUGH.add(this); + } +- lastKnownRevision = currentRevision; +- ASYNC_OBSERVERS.forEach((activeObservers, target) => { +- let meta = peekMeta(target); +- activeObservers.forEach((observer, eventName) => { +- if (!validateTag(observer.tag, observer.lastRevision)) { +- let sendObserver = () => { +- try { +- sendEvent(target, eventName, [target, observer.path], undefined, meta); +- } finally { +- observer.tag = getChainTagsForKey(target, observer.path, tagMetaFor(target), peekMeta(target)); +- observer.lastRevision = valueForTag(observer.tag); +- } +- }; +- if (shouldSchedule) { +- schedule('actions', sendObserver); +- } else { +- sendObserver(); +- } +- } +- }); +- }); +- } +- function flushSyncObservers() { +- // When flushing synchronous observers, we know that something has changed (we +- // only do this during a notifyPropertyChange), so there's no reason to check +- // a global revision. +- +- SYNC_OBSERVERS.forEach((activeObservers, target) => { +- let meta = peekMeta(target); +- activeObservers.forEach((observer, eventName) => { +- if (!observer.suspended && !validateTag(observer.tag, observer.lastRevision)) { +- try { +- observer.suspended = true; +- sendEvent(target, eventName, [target, observer.path], undefined, meta); +- } finally { +- observer.tag = getChainTagsForKey(target, observer.path, tagMetaFor(target), peekMeta(target)); +- observer.lastRevision = valueForTag(observer.tag); +- observer.suspended = false; +- } +- } +- }); +- }); +- } +- function setObserverSuspended(target, property, suspended) { +- let activeObservers = SYNC_OBSERVERS.get(target); +- if (!activeObservers) { +- return; ++ get(obj) { ++ return this._get.call(obj); + } +- let observer = activeObservers.get(changeEvent(property)); +- if (observer) { +- observer.suspended = suspended; ++ set(obj, _key, value) { ++ this._set.call(obj, value); + } + } +- function destroyObservers(target) { +- if (SYNC_OBSERVERS.size > 0) SYNC_OBSERVERS.delete(target); +- if (ASYNC_OBSERVERS.size > 0) ASYNC_OBSERVERS.delete(target); +- } + +- ///////// ++ // NOTE: copied from: https://github.com/glimmerjs/glimmer.js/pull/358 ++ // Both glimmerjs/glimmer.js and emberjs/ember.js have the exact same implementation ++ // of @cached, so any changes made to one should also be made to the other + +- // This is exported for `@tracked`, but should otherwise be avoided. Use `tagForObject`. +- const SELF_TAG = symbol('SELF_TAG'); +- function tagForProperty(obj, propertyKey, addMandatorySetter = false, meta) { +- let customTagFor = getCustomTagFor(obj); +- if (customTagFor !== undefined) { +- return customTagFor(obj, propertyKey, addMandatorySetter); +- } +- let tag = tagFor(obj, propertyKey, meta); +- if (addMandatorySetter) { +- setupMandatorySetter(tag, obj, propertyKey); +- } +- return tag; +- } +- function tagForObject(obj) { +- if (isObject$1(obj)) { +- { +- (!(!isDestroyed(obj)) && assert$1(isDestroyed(obj) ? `Cannot create a new tag for \`${toString$1(obj)}\` after it has been destroyed.` : '', !isDestroyed(obj))); +- } +- return tagFor(obj, SELF_TAG); +- } +- return CONSTANT_TAG; +- } +- function markObjectAsDirty(obj, propertyKey) { +- dirtyTagFor(obj, propertyKey); +- dirtyTagFor(obj, SELF_TAG); +- } + + /** +- @module ember +- @private +- */ ++ * @decorator ++ * ++ Gives the getter a caching behavior. The return value of the getter ++ will be cached until any of the properties it is entangled with ++ are invalidated. This is useful when a getter is expensive and ++ used very often. + +- const PROPERTY_DID_CHANGE = Symbol('PROPERTY_DID_CHANGE'); +- function hasPropertyDidChange(obj) { +- return obj != null && typeof obj === 'object' && typeof obj[PROPERTY_DID_CHANGE] === 'function'; +- } +- let deferred$1 = 0; ++ For instance, in this `GuestList` class, we have the `sortedGuests` ++ getter that sorts the guests alphabetically: + +- /** +- This function is called just after an object property has changed. +- It will notify any observers and clear caches among other things. ++ ```javascript ++ import { tracked } from '@glimmer/tracking'; + +- Normally you will not need to call this method directly but if for some +- reason you can't directly watch a property you can invoke this method +- manually. ++ class GuestList { ++ @tracked guests = ['Zoey', 'Tomster']; + +- @method notifyPropertyChange +- @for @ember/object +- @param {Object} obj The object with the property that will change +- @param {String} keyName The property key (or path) that will change. +- @param {Meta} [_meta] The objects meta. +- @param {unknown} [value] The new value to set for the property +- @return {void} +- @since 3.1.0 +- @public +- */ +- function notifyPropertyChange(obj, keyName, _meta, value) { +- let meta = _meta === undefined ? peekMeta(obj) : _meta; +- if (meta !== null && (meta.isInitializing() || meta.isPrototypeMeta(obj))) { +- return; +- } +- markObjectAsDirty(obj, keyName); +- if (deferred$1 <= 0) { +- flushSyncObservers(); +- } +- if (PROPERTY_DID_CHANGE in obj) { +- // It's redundant to do this here, but we don't want to check above so we can avoid an extra function call in prod. +- (!(hasPropertyDidChange(obj)) && assert$1('property did change hook is invalid', hasPropertyDidChange(obj))); // we need to check the arguments length here; there's a check in Component's `PROPERTY_DID_CHANGE` +- // that checks its arguments length, so we have to explicitly not call this with `value` +- // if it is not passed to `notifyPropertyChange` +- if (arguments.length === 4) { +- obj[PROPERTY_DID_CHANGE](keyName, value); +- } else { +- obj[PROPERTY_DID_CHANGE](keyName); ++ get sortedGuests() { ++ return this.guests.slice().sort() ++ } + } +- } +- } ++ ``` + +- /** +- @method beginPropertyChanges +- @chainable +- @private +- */ +- function beginPropertyChanges() { +- deferred$1++; +- suspendedObserverDeactivation(); +- } ++ Every time `sortedGuests` is accessed, a new array will be created and sorted, ++ because JavaScript getters do not cache by default. When the guest list ++ is small, like the one in the example, this is not a problem. However, if ++ the guest list were to grow very large, it would mean that we would be doing ++ a large amount of work each time we accessed `sortedGuests`. With `@cached`, ++ we can cache the value instead: + +- /** +- @method endPropertyChanges +- @private +- */ +- function endPropertyChanges() { +- deferred$1--; +- if (deferred$1 <= 0) { +- flushSyncObservers(); +- resumeObserverDeactivation(); +- } +- } ++ ```javascript ++ import { tracked, cached } from '@glimmer/tracking'; + +- /** +- Make a series of property changes together in an +- exception-safe way. ++ class GuestList { ++ @tracked guests = ['Zoey', 'Tomster']; ++ ++ @cached ++ get sortedGuests() { ++ return this.guests.slice().sort() ++ } ++ } ++ ``` ++ ++ Now the `sortedGuests` getter will be cached based on autotracking. ++ It will only rerun and create a new sorted array when the guests tracked ++ property is updated. ++ ++ ++ ### Tradeoffs ++ ++ Overuse is discouraged. ++ ++ In general, you should avoid using `@cached` unless you have confirmed that ++ the getter you are decorating is computationally expensive, since `@cached` ++ adds a small amount of overhead to the getter. ++ While the individual costs are small, a systematic use of the `@cached` ++ decorator can add up to a large impact overall in your app. ++ Many getters and tracked properties are only accessed once during rendering, ++ and then never rerendered, so adding `@cached` when unnecessary can ++ negatively impact performance. ++ ++ Also, `@cached` may rerun even if the values themselves have not changed, ++ since tracked properties will always invalidate. ++ For example updating an integer value from `5` to an other `5` will trigger ++ a rerun of the cached properties building from this integer. ++ ++ Avoiding a cache invalidation in this case is not something that can ++ be achieved on the `@cached` decorator itself, but rather when updating ++ the underlying tracked values, by applying some diff checking mechanisms: + + ```javascript +- Ember.changeProperties(function() { +- obj1.set('foo', mayBlowUpWhenSet); +- obj2.set('bar', baz); +- }); ++ if (nextValue !== this.trackedProp) { ++ this.trackedProp = nextValue; ++ } + ``` + +- @method changeProperties +- @param {Function} callback +- @private +- */ +- function changeProperties(callback) { +- beginPropertyChanges(); +- try { +- callback(); +- } finally { +- endPropertyChanges(); ++ Here equal values won't update the property, therefore not triggering ++ the subsequent cache invalidations of the `@cached` properties who were ++ using this `trackedProp`. ++ ++ Remember that setting tracked data should only be done during initialization, ++ or as the result of a user action. Setting tracked data during render ++ (such as in a getter), is not supported. ++ ++ @method cached ++ @static ++ @for @glimmer/tracking ++ @public ++ */ ++ const cached = (...args) => { ++ const [target, key, descriptor] = args; ++ ++ // Error on `@cached()`, `@cached(...args)`, and `@cached propName = value;` ++ if (target === undefined) throwCachedExtraneousParens(); ++ if ((typeof target !== 'object' || typeof key !== 'string' || typeof descriptor !== 'object' || args.length !== 3)) { ++ throwCachedInvalidArgsError(args); ++ } ++ if ((!('get' in descriptor) || typeof descriptor.get !== 'function')) { ++ throwCachedGetterOnlyError(key); + } ++ const caches = new WeakMap(); ++ const getter = descriptor.get; ++ descriptor.get = function () { ++ if (!caches.has(this)) { ++ caches.set(this, createCache(getter.bind(this))); ++ } ++ return getValue(caches.get(this)); ++ }; ++ }; ++ function throwCachedExtraneousParens() { ++ throw new Error('You attempted to use @cached(), which is not necessary nor supported. Remove the parentheses and you will be good to go!'); ++ } ++ function throwCachedGetterOnlyError(key) { ++ throw new Error(`The @cached decorator must be applied to getters. '${key}' is not a getter.`); ++ } ++ function throwCachedInvalidArgsError(args = []) { ++ throw new Error(`You attempted to use @cached on with ${args.length > 1 ? 'arguments' : 'an argument'} ( @cached(${args.map(d => `'${d}'`).join(', ')}), which is not supported. Dependencies are automatically tracked, so you can just use ${'`@cached`'}`); + } + +- function arrayContentWillChange(array, startIdx, removeAmt, addAmt) { +- // if no args are passed assume everything changes +- if (startIdx === undefined) { +- startIdx = 0; +- removeAmt = addAmt = -1; +- } else { +- if (removeAmt === undefined) { +- removeAmt = -1; ++ const hasOwnProperty$2 = Object.prototype.hasOwnProperty; ++ let searchDisabled = false; ++ const flags = { ++ _set: 0, ++ _unprocessedNamespaces: false, ++ get unprocessedNamespaces() { ++ return this._unprocessedNamespaces; ++ }, ++ set unprocessedNamespaces(v) { ++ this._set++; ++ this._unprocessedNamespaces = v; ++ } ++ }; ++ let unprocessedMixins = false; ++ const NAMESPACES = []; ++ const NAMESPACES_BY_ID = Object.create(null); ++ function addNamespace(namespace) { ++ flags.unprocessedNamespaces = true; ++ NAMESPACES.push(namespace); ++ } ++ function removeNamespace(namespace) { ++ let name = getName(namespace); ++ delete NAMESPACES_BY_ID[name]; ++ NAMESPACES.splice(NAMESPACES.indexOf(namespace), 1); ++ if (name in context$1.lookup && namespace === context$1.lookup[name]) { ++ context$1.lookup[name] = undefined; ++ } ++ } ++ function findNamespaces() { ++ if (!flags.unprocessedNamespaces) { ++ return; ++ } ++ let lookup = context$1.lookup; ++ let keys = Object.keys(lookup); ++ for (let key of keys) { ++ // Only process entities that start with uppercase A-Z ++ if (!isUppercase(key.charCodeAt(0))) { ++ continue; + } +- if (addAmt === undefined) { +- addAmt = -1; ++ let obj = tryIsNamespace(lookup, key); ++ if (obj) { ++ setName(obj, key); + } + } +- sendEvent(array, '@array:before', [array, startIdx, removeAmt, addAmt]); +- return array; + } +- function arrayContentDidChange(array, startIdx, removeAmt, addAmt, notify = true) { +- // if no args are passed assume everything changes +- if (startIdx === undefined) { +- startIdx = 0; +- removeAmt = addAmt = -1; +- } else { +- if (removeAmt === undefined) { +- removeAmt = -1; +- } +- if (addAmt === undefined) { +- addAmt = -1; +- } ++ function findNamespace(name) { ++ if (!searchDisabled) { ++ processAllNamespaces(); + } +- let meta = peekMeta(array); +- if (notify) { +- if (addAmt < 0 || removeAmt < 0 || addAmt - removeAmt !== 0) { +- notifyPropertyChange(array, 'length', meta); +- } +- notifyPropertyChange(array, '[]', meta); ++ return NAMESPACES_BY_ID[name]; ++ } ++ function processNamespace(namespace) { ++ _processNamespace([namespace.toString()], namespace, new Set()); ++ } ++ function processAllNamespaces() { ++ let unprocessedNamespaces = flags.unprocessedNamespaces; ++ if (unprocessedNamespaces) { ++ findNamespaces(); ++ flags.unprocessedNamespaces = false; + } +- sendEvent(array, '@array:change', [array, startIdx, removeAmt, addAmt]); +- if (meta !== null) { +- let length = array.length; +- let addedAmount = addAmt === -1 ? 0 : addAmt; +- let removedAmount = removeAmt === -1 ? 0 : removeAmt; +- let delta = addedAmount - removedAmount; +- let previousLength = length - delta; +- let normalStartIdx = startIdx < 0 ? previousLength + startIdx : startIdx; +- if (meta.revisionFor('firstObject') !== undefined && normalStartIdx === 0) { +- notifyPropertyChange(array, 'firstObject', meta); +- } +- if (meta.revisionFor('lastObject') !== undefined) { +- let previousLastIndex = previousLength - 1; +- let lastAffectedIndex = normalStartIdx + removedAmount; +- if (previousLastIndex < lastAffectedIndex) { +- notifyPropertyChange(array, 'lastObject', meta); +- } ++ if (unprocessedNamespaces || unprocessedMixins) { ++ let namespaces = NAMESPACES; ++ for (let namespace of namespaces) { ++ processNamespace(namespace); + } ++ unprocessedMixins = false; + } +- return array; + } +- +- const EMPTY_ARRAY$3 = Object.freeze([]); +- function objectAt(array, index) { +- if (Array.isArray(array)) { +- return array[index]; +- } else { +- return array.objectAt(index); +- } ++ function isSearchDisabled() { ++ return searchDisabled; + } +- +- // Ideally, we'd use MutableArray.detect but for unknown reasons this causes +- // the node tests to fail strangely. +- function isMutableArray(obj) { +- return obj != null && typeof obj.replace === 'function'; ++ function setSearchDisabled(flag) { ++ searchDisabled = Boolean(flag); + } +- function replace(array, start, deleteCount, items = EMPTY_ARRAY$3) { +- if (isMutableArray(array)) { +- array.replace(start, deleteCount, items); +- } else { +- (!(Array.isArray(array)) && assert$1('Can only replace content of a native array or MutableArray', Array.isArray(array))); +- replaceInNativeArray(array, start, deleteCount, items); +- } ++ function setUnprocessedMixins() { ++ unprocessedMixins = true; + } +- const CHUNK_SIZE = 60000; ++ function _processNamespace(paths, root, seen) { ++ let idx = paths.length; ++ let id = paths.join('.'); ++ NAMESPACES_BY_ID[id] = root; ++ setName(root, id); + +- // To avoid overflowing the stack, we splice up to CHUNK_SIZE items at a time. +- // See https://code.google.com/p/chromium/issues/detail?id=56588 for more details. +- function replaceInNativeArray(array, start, deleteCount, items) { +- arrayContentWillChange(array, start, deleteCount, items.length); +- if (items.length <= CHUNK_SIZE) { +- array.splice(start, deleteCount, ...items); +- } else { +- array.splice(start, deleteCount); +- for (let i = 0; i < items.length; i += CHUNK_SIZE) { +- let chunk = items.slice(i, i + CHUNK_SIZE); +- array.splice(start + i, 0, ...chunk); ++ // Loop over all of the keys in the namespace, looking for classes ++ for (let key in root) { ++ if (!hasOwnProperty$2.call(root, key)) { ++ continue; + } +- } +- arrayContentDidChange(array, start, deleteCount, items.length); +- } +- function arrayObserversHelper(obj, target, opts, operation) { +- let { +- willChange, +- didChange +- } = opts; +- operation(obj, '@array:before', target, willChange); +- operation(obj, '@array:change', target, didChange); ++ let obj = root[key]; + +- /* +- * Array proxies have a `_revalidate` method which must be called to set +- * up their internal array observation systems. +- */ +- obj._revalidate?.(); +- return obj; +- } +- function addArrayObserver(array, target, opts) { +- return arrayObserversHelper(array, target, opts, addListener); +- } +- function removeArrayObserver(array, target, opts) { +- return arrayObserversHelper(array, target, opts, removeListener); +- } ++ // If we are processing the `Ember` namespace, for example, the ++ // `paths` will start with `["Ember"]`. Every iteration through ++ // the loop will update the **second** element of this list with ++ // the key, so processing `Ember.View` will make the Array ++ // `['Ember', 'View']`. ++ paths[idx] = key; + +- const CHAIN_PASS_THROUGH = new WeakSet(); +- function finishLazyChains(meta, key, value) { +- let lazyTags = meta.readableLazyChainsFor(key); +- if (lazyTags === undefined) { +- return; +- } +- if (isObject$1(value)) { +- for (let [tag, deps] of lazyTags) { +- UPDATE_TAG(tag, getChainTagsForKey(value, deps, tagMetaFor(value), peekMeta(value))); ++ // If we have found an unprocessed class ++ if (obj && getName(obj) === void 0) { ++ // Replace the class' `toString` with the dot-separated path ++ setName(obj, paths.join('.')); ++ // Support nested namespaces ++ } else if (obj && isNamespace(obj)) { ++ // Skip aliased namespaces ++ if (seen.has(obj)) { ++ continue; ++ } ++ seen.add(obj); ++ // Process the child namespace ++ _processNamespace(paths, obj, seen); + } + } +- lazyTags.length = 0; ++ paths.length = idx; // cut out last item + } +- function getChainTagsForKeys(obj, keys, tagMeta, meta) { +- let tags = []; +- for (let key of keys) { +- getChainTags(tags, obj, key, tagMeta, meta); +- } +- return combine(tags); ++ function isNamespace(obj) { ++ return obj != null && typeof obj === 'object' && obj.isNamespace; + } +- function getChainTagsForKey(obj, key, tagMeta, meta) { +- return combine(getChainTags([], obj, key, tagMeta, meta)); ++ function isUppercase(code) { ++ return code >= 65 && code <= 90 // A ++ ; // Z ++ } ++ function tryIsNamespace(lookup, prop) { ++ try { ++ let obj = lookup[prop]; ++ return (obj !== null && typeof obj === 'object' || typeof obj === 'function') && obj.isNamespace && obj; ++ } catch (_e) { ++ // continue ++ } + } +- function getChainTags(chainTags, obj, path, tagMeta, meta$1) { +- let current = obj; +- let currentTagMeta = tagMeta; +- let currentMeta = meta$1; +- let pathLength = path.length; +- let segmentEnd = -1; +- // prevent closures +- let segment, descriptor; +- +- // eslint-disable-next-line no-constant-condition +- while (true) { +- let lastSegmentEnd = segmentEnd + 1; +- segmentEnd = path.indexOf('.', lastSegmentEnd); +- if (segmentEnd === -1) { +- segmentEnd = pathLength; +- } +- segment = path.slice(lastSegmentEnd, segmentEnd); +- +- // If the segment is an @each, we can process it and then break +- if (segment === '@each' && segmentEnd !== pathLength) { +- lastSegmentEnd = segmentEnd + 1; +- segmentEnd = path.indexOf('.', lastSegmentEnd); +- let arrLength = current.length; +- if (typeof arrLength !== 'number' || +- // TODO: should the second test be `isEmberArray` instead? +- !(Array.isArray(current) || 'objectAt' in current)) { +- // If the current object isn't an array, there's nothing else to do, +- // we don't watch individual properties. Break out of the loop. +- break; +- } else if (arrLength === 0) { +- // Fast path for empty arrays +- chainTags.push(tagForProperty(current, '[]')); +- break; +- } +- if (segmentEnd === -1) { +- segment = path.slice(lastSegmentEnd); +- } else { +- // Deprecated, remove once we turn the deprecation into an assertion +- segment = path.slice(lastSegmentEnd, segmentEnd); +- } +- +- // Push the tags for each item's property +- for (let i = 0; i < arrLength; i++) { +- let item = objectAt(current, i); +- if (item) { +- (!(typeof item === 'object') && assert$1(`When using @each to observe the array \`${current.toString()}\`, the items in the array must be objects`, typeof item === 'object')); +- chainTags.push(tagForProperty(item, segment, true)); +- currentMeta = peekMeta(item); +- descriptor = currentMeta !== null ? currentMeta.peekDescriptors(segment) : undefined; + +- // If the key is an alias, we need to bootstrap it +- if (descriptor !== undefined && typeof descriptor.altKey === 'string') { +- item[segment]; +- } +- } +- } ++ const emberinternalsMetalIndex = /*#__PURE__*/Object.defineProperty({ ++ __proto__: null, ++ ASYNC_OBSERVERS, ++ ComputedDescriptor, ++ ComputedProperty, ++ get DEBUG_INJECTION_FUNCTIONS () { return DEBUG_INJECTION_FUNCTIONS; }, ++ Libraries, ++ NAMESPACES, ++ NAMESPACES_BY_ID, ++ PROPERTY_DID_CHANGE, ++ PROXY_CONTENT, ++ SYNC_OBSERVERS, ++ TrackedDescriptor, ++ _getPath, ++ _getProp, ++ _setProp, ++ activateObserver, ++ addArrayObserver, ++ addListener, ++ addNamespace, ++ addObserver, ++ alias, ++ arrayContentDidChange, ++ arrayContentWillChange, ++ autoComputed, ++ beginPropertyChanges, ++ cached, ++ changeProperties, ++ computed, ++ createCache, ++ defineDecorator, ++ defineProperty, ++ defineValue, ++ deprecateProperty, ++ descriptorForDecorator, ++ descriptorForProperty, ++ eachProxyArrayDidChange, ++ eachProxyArrayWillChange, ++ endPropertyChanges, ++ expandProperties, ++ findNamespace, ++ findNamespaces, ++ flushAsyncObservers, ++ get: get$2, ++ getCachedValueFor, ++ getProperties, ++ getValue, ++ hasListeners, ++ hasUnknownProperty, ++ inject: inject$2, ++ isClassicDecorator, ++ isComputed, ++ isConst, ++ isElementDescriptor, ++ isNamespaceSearchDisabled: isSearchDisabled, ++ libraries: LIBRARIES, ++ makeComputedDecorator, ++ markObjectAsDirty, ++ nativeDescDecorator, ++ notifyPropertyChange, ++ objectAt, ++ on: on$3, ++ processAllNamespaces, ++ processNamespace, ++ removeArrayObserver, ++ removeListener, ++ removeNamespace, ++ removeObserver, ++ replace, ++ replaceInNativeArray, ++ revalidateObservers, ++ sendEvent, ++ set, ++ setClassicDecorator, ++ setNamespaceSearchDisabled: setSearchDisabled, ++ setProperties, ++ setUnprocessedMixins, ++ tagForObject, ++ tagForProperty, ++ tracked, ++ trySet ++ }, Symbol.toStringTag, { value: 'Module' }); + +- // Push the tag for the array length itself +- chainTags.push(tagForProperty(current, '[]', true, currentTagMeta)); +- break; +- } +- let propertyTag = tagForProperty(current, segment, true, currentTagMeta); +- descriptor = currentMeta !== null ? currentMeta.peekDescriptors(segment) : undefined; +- chainTags.push(propertyTag); ++ const emberObjectEvents = /*#__PURE__*/Object.defineProperty({ ++ __proto__: null, ++ addListener, ++ removeListener, ++ sendEvent ++ }, Symbol.toStringTag, { value: 'Module' }); + +- // If we're at the end of the path, processing the last segment, and it's +- // not an alias, we should _not_ get the last value, since we already have +- // its tag. There's no reason to access it and do more work. +- if (segmentEnd === pathLength) { +- // If the key was an alias, we should always get the next value in order to +- // bootstrap the alias. This is because aliases, unlike other CPs, should +- // always be in sync with the aliased value. +- if (CHAIN_PASS_THROUGH.has(descriptor)) { +- current[segment]; ++ /** ++ @module @ember/object/mixin ++ */ ++ const a_concat = Array.prototype.concat; ++ const { ++ isArray: isArray$4 ++ } = Array; ++ function extractAccessors(properties) { ++ if (properties !== undefined) { ++ for (let key of Object.keys(properties)) { ++ let desc = Object.getOwnPropertyDescriptor(properties, key); ++ if (desc.get !== undefined || desc.set !== undefined) { ++ Object.defineProperty(properties, key, { ++ value: nativeDescDecorator(desc) ++ }); + } +- break; + } +- if (descriptor === undefined) { +- // If the descriptor is undefined, then its a normal property, so we should +- // lookup the value to chain off of like normal. ++ } ++ return properties; ++ } ++ function concatenatedMixinProperties(concatProp, props, values, base) { ++ // reset before adding each new mixin to pickup concats from previous ++ let concats = values[concatProp] || base[concatProp]; ++ if (props[concatProp]) { ++ concats = concats ? a_concat.call(concats, props[concatProp]) : props[concatProp]; ++ } ++ return concats; ++ } ++ function giveDecoratorSuper(key, decorator, property, descs) { ++ if (property === true) { ++ return decorator; ++ } ++ let originalGetter = property._getter; ++ if (originalGetter === undefined) { ++ return decorator; ++ } ++ let superDesc = descs[key]; + +- if (!(segment in current) && typeof current.unknownProperty === 'function') { +- current = current.unknownProperty(segment); +- } else { +- current = current[segment]; +- } +- } else if (CHAIN_PASS_THROUGH.has(descriptor)) { +- current = current[segment]; ++ // Check to see if the super property is a decorator first, if so load its descriptor ++ let superProperty = typeof superDesc === 'function' ? descriptorForDecorator(superDesc) : superDesc; ++ if (superProperty === undefined || superProperty === true) { ++ return decorator; ++ } ++ let superGetter = superProperty._getter; ++ if (superGetter === undefined) { ++ return decorator; ++ } ++ let get = wrap$1(originalGetter, superGetter); ++ let set; ++ let originalSetter = property._setter; ++ let superSetter = superProperty._setter; ++ if (superSetter !== undefined) { ++ if (originalSetter !== undefined) { ++ set = wrap$1(originalSetter, superSetter); + } else { +- // If the descriptor is defined, then its a normal CP (not an alias, which +- // would have been handled earlier). We get the last revision to check if +- // the CP is still valid, and if so we use the cached value. If not, then +- // we create a lazy chain lookup, and the next time the CP is calculated, +- // it will update that lazy chain. +- let instanceMeta = currentMeta.source === current ? currentMeta : meta(current); +- let lastRevision = instanceMeta.revisionFor(segment); +- if (lastRevision !== undefined && validateTag(propertyTag, lastRevision)) { +- current = instanceMeta.valueFor(segment); +- } else { +- // use metaFor here to ensure we have the meta for the instance +- let lazyChains = instanceMeta.writableLazyChainsFor(segment); +- let rest = path.substring(segmentEnd + 1); +- let placeholderTag = createUpdatableTag(); +- lazyChains.push([placeholderTag, rest]); +- chainTags.push(placeholderTag); +- break; +- } +- } +- if (!isObject$1(current)) { +- // we've hit the end of the chain for now, break out +- break; ++ // If the super property has a setter, we default to using it no matter what. ++ // This is clearly very broken and weird, but it's what was here so we have ++ // to keep it until the next major at least. ++ // ++ // TODO: Add a deprecation here. ++ set = superSetter; + } +- currentTagMeta = tagMetaFor(current); +- currentMeta = peekMeta(current); ++ } else { ++ set = originalSetter; + } +- return chainTags; +- } + +- // Same as built-in MethodDecorator but with more arguments ++ // only create a new CP if we must ++ if (get !== originalGetter || set !== originalSetter) { ++ // Since multiple mixins may inherit from the same parent, we need ++ // to clone the computed property so that other mixins do not receive ++ // the wrapped version. ++ let dependentKeys = property._dependentKeys || []; ++ let newProperty = new ComputedProperty([...dependentKeys, { ++ get, ++ set ++ }]); ++ newProperty._readOnly = property._readOnly; ++ newProperty._meta = property._meta; ++ newProperty.enumerable = property.enumerable; + +- function isElementDescriptor(args) { +- let [maybeTarget, maybeKey, maybeDesc] = args; +- return ( +- // Ensure we have the right number of args +- args.length === 3 && ( +- // Make sure the target is a class or object (prototype) +- typeof maybeTarget === 'function' || typeof maybeTarget === 'object' && maybeTarget !== null) && +- // Make sure the key is a string +- typeof maybeKey === 'string' && ( +- // Make sure the descriptor is the right shape +- typeof maybeDesc === 'object' && maybeDesc !== null || maybeDesc === undefined) +- ); +- } +- function nativeDescDecorator(propertyDesc) { +- let decorator = function () { +- return propertyDesc; +- }; +- setClassicDecorator(decorator); ++ // SAFETY: We passed in the impl for this class ++ return makeComputedDecorator(newProperty, ComputedProperty); ++ } + return decorator; + } ++ function giveMethodSuper(key, method, values, descs) { ++ // Methods overwrite computed properties, and do not call super to them. ++ if (descs[key] !== undefined) { ++ return method; ++ } + +- /** +- Objects of this type can implement an interface to respond to requests to +- get and set. The default implementation handles simple properties. ++ // Find the original method in a parent mixin ++ let superMethod = values[key]; + +- @class Descriptor +- @private +- */ +- class ComputedDescriptor { +- enumerable = true; +- configurable = true; +- _dependentKeys = undefined; +- _meta = undefined; +- setup(_obj, keyName, _propertyDesc, meta) { +- meta.writeDescriptors(keyName, this); +- } +- teardown(_obj, keyName, meta) { +- meta.removeDescriptors(keyName); ++ // Only wrap the new method if the original method was a function ++ if (typeof superMethod === 'function') { ++ return wrap$1(method, superMethod); + } ++ return method; + } +- let COMPUTED_GETTERS; +- { +- COMPUTED_GETTERS = new WeakSet(); +- } +- function DESCRIPTOR_GETTER_FUNCTION(name, descriptor) { +- function getter() { +- return descriptor.get(this, name); +- } +- { +- COMPUTED_GETTERS.add(getter); ++ function simpleMakeArray(value) { ++ if (!value) { ++ return []; ++ } else if (!Array.isArray(value)) { ++ return [value]; ++ } else { ++ return value; + } +- return getter; + } +- function DESCRIPTOR_SETTER_FUNCTION(name, descriptor) { +- let set = function CPSETTER_FUNCTION(value) { +- return descriptor.set(this, name, value); +- }; +- COMPUTED_SETTERS.add(set); +- return set; +- } +- const COMPUTED_SETTERS = new WeakSet(); +- function makeComputedDecorator(desc, DecoratorClass) { +- let decorator = function COMPUTED_DECORATOR(target, key, propertyDesc, maybeMeta, isClassicDecorator) { +- (!(isClassicDecorator || !propertyDesc || !propertyDesc.get || !COMPUTED_GETTERS.has(propertyDesc.get)) && assert$1(`Only one computed property decorator can be applied to a class field or accessor, but '${key}' was decorated twice. You may have added the decorator to both a getter and setter, which is unnecessary.`, isClassicDecorator || !propertyDesc || !propertyDesc.get || !COMPUTED_GETTERS.has(propertyDesc.get))); +- let meta$1 = arguments.length === 3 ? meta(target) : maybeMeta; +- desc.setup(target, key, propertyDesc, meta$1); +- let computedDesc = { +- enumerable: desc.enumerable, +- configurable: desc.configurable, +- get: DESCRIPTOR_GETTER_FUNCTION(key, desc), +- set: DESCRIPTOR_SETTER_FUNCTION(key, desc) +- }; +- return computedDesc; +- }; +- setClassicDecorator(decorator, desc); +- Object.setPrototypeOf(decorator, DecoratorClass.prototype); +- return decorator; +- } +- +- ///////////// +- +- const DECORATOR_DESCRIPTOR_MAP = new WeakMap(); +- +- /** +- Returns the CP descriptor associated with `obj` and `keyName`, if any. +- +- @method descriptorForProperty +- @param {Object} obj the object to check +- @param {String} keyName the key to check +- @return {Descriptor} +- @private +- */ +- function descriptorForProperty(obj, keyName, _meta) { +- (!(obj !== null) && assert$1('Cannot call `descriptorForProperty` on null', obj !== null)); +- (!(obj !== undefined) && assert$1('Cannot call `descriptorForProperty` on undefined', obj !== undefined)); +- (!(typeof obj === 'object' || typeof obj === 'function') && assert$1(`Cannot call \`descriptorForProperty\` on ${typeof obj}`, typeof obj === 'object' || typeof obj === 'function')); +- let meta = _meta === undefined ? peekMeta(obj) : _meta; +- if (meta !== null) { +- return meta.peekDescriptors(keyName); ++ function applyConcatenatedProperties(key, value, values) { ++ let baseValue = values[key]; ++ let ret = simpleMakeArray(baseValue).concat(simpleMakeArray(value)); ++ { ++ // it is possible to use concatenatedProperties with strings (which cannot be frozen) ++ // only freeze objects... ++ if (typeof ret === 'object' && ret !== null) { ++ // prevent mutating `concatenatedProperties` array after it is applied ++ Object.freeze(ret); ++ } + } ++ return ret; + } +- function descriptorForDecorator(dec) { +- return DECORATOR_DESCRIPTOR_MAP.get(dec); ++ function applyMergedProperties(key, value, values) { ++ let baseValue = values[key]; ++ (!(!isArray$4(value)) && assert$1(`You passed in \`${JSON.stringify(value)}\` as the value for \`${key}\` but \`${key}\` cannot be an Array`, !isArray$4(value))); ++ if (!baseValue) { ++ return value; ++ } ++ let newBase = Object.assign({}, baseValue); ++ let hasFunction = false; ++ let props = Object.keys(value); ++ for (let prop of props) { ++ let propValue = value[prop]; ++ if (typeof propValue === 'function') { ++ hasFunction = true; ++ newBase[prop] = giveMethodSuper(prop, propValue, baseValue, {}); ++ } else { ++ newBase[prop] = propValue; ++ } ++ } ++ if (hasFunction) { ++ newBase._super = ROOT; ++ } ++ return newBase; + } +- +- /** +- Check whether a value is a decorator +- +- @method isClassicDecorator +- @param {any} possibleDesc the value to check +- @return {boolean} +- @private +- */ +- function isClassicDecorator(dec) { +- return typeof dec === 'function' && DECORATOR_DESCRIPTOR_MAP.has(dec); ++ function mergeMixins(mixins, meta, descs, values, base, keys, keysWithSuper) { ++ let currentMixin; ++ for (let i = 0; i < mixins.length; i++) { ++ currentMixin = mixins[i]; ++ (!(typeof currentMixin === 'object' && currentMixin !== null && Object.prototype.toString.call(currentMixin) !== '[object Array]') && assert$1(`Expected hash or Mixin instance, got ${Object.prototype.toString.call(currentMixin)}`, typeof currentMixin === 'object' && currentMixin !== null && Object.prototype.toString.call(currentMixin) !== '[object Array]')); ++ if (MIXINS.has(currentMixin)) { ++ if (meta.hasMixin(currentMixin)) { ++ continue; ++ } ++ meta.addMixin(currentMixin); ++ let { ++ properties, ++ mixins ++ } = currentMixin; ++ if (properties !== undefined) { ++ mergeProps(meta, properties, descs, values, base, keys, keysWithSuper); ++ } else if (mixins !== undefined) { ++ mergeMixins(mixins, meta, descs, values, base, keys, keysWithSuper); ++ if (currentMixin instanceof Mixin && currentMixin._without !== undefined) { ++ currentMixin._without.forEach(keyName => { ++ // deleting the key means we won't process the value ++ let index = keys.indexOf(keyName); ++ if (index !== -1) { ++ keys.splice(index, 1); ++ } ++ }); ++ } ++ } ++ } else { ++ mergeProps(meta, currentMixin, descs, values, base, keys, keysWithSuper); ++ } ++ } + } ++ function mergeProps(meta, props, descs, values, base, keys, keysWithSuper) { ++ let concats = concatenatedMixinProperties('concatenatedProperties', props, values, base); ++ let mergings = concatenatedMixinProperties('mergedProperties', props, values, base); ++ let propKeys = Object.keys(props); ++ for (let key of propKeys) { ++ let value = props[key]; ++ if (value === undefined) continue; ++ if (keys.indexOf(key) === -1) { ++ keys.push(key); ++ let desc = meta.peekDescriptors(key); ++ if (desc === undefined) { ++ // If the value is a classic decorator, we don't want to actually ++ // access it, because that will execute the decorator while we're ++ // building the class. ++ if (!isClassicDecorator(value)) { ++ // The superclass did not have a CP, which means it may have ++ // observers or listeners on that property. ++ let prev = values[key] = base[key]; ++ if (typeof prev === 'function') { ++ updateObserversAndListeners(base, key, prev, false); ++ } ++ } ++ } else { ++ descs[key] = desc; + +- /** +- Set a value as a decorator +- +- @method setClassicDecorator +- @param {function} decorator the value to mark as a decorator +- @private +- */ +- function setClassicDecorator(dec, value = true) { +- DECORATOR_DESCRIPTOR_MAP.set(dec, value); ++ // The super desc will be overwritten on descs, so save off the fact that ++ // there was a super so we know to Object.defineProperty when writing ++ // the value ++ keysWithSuper.push(key); ++ desc.teardown(base, key, meta); ++ } ++ } ++ let isFunction = typeof value === 'function'; ++ if (isFunction) { ++ let desc = descriptorForDecorator(value); ++ if (desc !== undefined) { ++ // Wrap descriptor function to implement _super() if needed ++ descs[key] = giveDecoratorSuper(key, value, desc, descs); ++ values[key] = undefined; ++ continue; ++ } ++ } ++ if (concats && concats.indexOf(key) >= 0 || key === 'concatenatedProperties' || key === 'mergedProperties') { ++ value = applyConcatenatedProperties(key, value, values); ++ } else if (mergings && mergings.indexOf(key) > -1) { ++ value = applyMergedProperties(key, value, values); ++ } else if (isFunction) { ++ value = giveMethodSuper(key, value, values, descs); ++ } ++ values[key] = value; ++ descs[key] = undefined; ++ } + } +- +- /** +- @module @ember/object +- */ +- +- const END_WITH_EACH_REGEX = /\.@each$/; +- +- /** +- Expands `pattern`, invoking `callback` for each expansion. +- +- The only pattern supported is brace-expansion, anything else will be passed +- once to `callback` directly. +- +- Example +- +- ```js +- import { expandProperties } from '@ember/object/computed'; +- +- function echo(arg){ console.log(arg); } +- +- expandProperties('foo.bar', echo); //=> 'foo.bar' +- expandProperties('{foo,bar}', echo); //=> 'foo', 'bar' +- expandProperties('foo.{bar,baz}', echo); //=> 'foo.bar', 'foo.baz' +- expandProperties('{foo,bar}.baz', echo); //=> 'foo.baz', 'bar.baz' +- expandProperties('foo.{bar,baz}.[]', echo) //=> 'foo.bar.[]', 'foo.baz.[]' +- expandProperties('{foo,bar}.{spam,eggs}', echo) //=> 'foo.spam', 'foo.eggs', 'bar.spam', 'bar.eggs' +- expandProperties('{foo}.bar.{baz}') //=> 'foo.bar.baz' +- ``` +- +- @method expandProperties +- @static +- @for @ember/object/computed +- @public +- @param {String} pattern The property pattern to expand. +- @param {Function} callback The callback to invoke. It is invoked once per +- expansion, and is passed the expansion. +- */ +- function expandProperties(pattern, callback) { +- (!(typeof pattern === 'string') && assert$1(`A computed property key must be a string, you passed ${typeof pattern} ${pattern}`, typeof pattern === 'string')); +- (!(pattern.indexOf(' ') === -1) && assert$1('Brace expanded properties cannot contain spaces, e.g. "user.{firstName, lastName}" should be "user.{firstName,lastName}"', pattern.indexOf(' ') === -1)); // regex to look for double open, double close, or unclosed braces +- (!(pattern.match(/\{[^}{]*\{|\}[^}{]*\}|\{[^}]*$/g) === null) && assert$1(`Brace expanded properties have to be balanced and cannot be nested, pattern: ${pattern}`, pattern.match(/\{[^}{]*\{|\}[^}{]*\}|\{[^}]*$/g) === null)); +- let start = pattern.indexOf('{'); +- if (start < 0) { +- callback(pattern.replace(END_WITH_EACH_REGEX, '.[]')); +- } else { +- dive('', pattern, start, callback); ++ function updateObserversAndListeners(obj, key, fn, add) { ++ let meta = observerListenerMetaFor(fn); ++ if (meta === undefined) return; ++ let { ++ observers, ++ listeners ++ } = meta; ++ if (observers !== undefined) { ++ let updateObserver = add ? addObserver : removeObserver; ++ for (let path of observers.paths) { ++ updateObserver(obj, path, null, key, observers.sync); ++ } ++ } ++ if (listeners !== undefined) { ++ let updateListener = add ? addListener : removeListener; ++ for (let listener of listeners) { ++ updateListener(obj, listener, null, key); ++ } + } + } +- function dive(prefix, pattern, start, callback) { +- let end = pattern.indexOf('}'), +- i = 0, +- newStart, +- arrayLength; +- let tempArr = pattern.substring(start + 1, end).split(','); +- let after = pattern.substring(end + 1); +- prefix = prefix + pattern.substring(0, start); +- arrayLength = tempArr.length; +- while (i < arrayLength) { +- newStart = after.indexOf('{'); +- if (newStart < 0) { +- callback((prefix + tempArr[i++] + after).replace(END_WITH_EACH_REGEX, '.[]')); +- } else { +- dive(prefix + tempArr[i++], after, newStart, callback); ++ function applyMixin(obj, mixins, _hideKeys = false) { ++ let descs = Object.create(null); ++ let values = Object.create(null); ++ let meta$1 = meta(obj); ++ let keys = []; ++ let keysWithSuper = []; ++ obj._super = ROOT; ++ ++ // Go through all mixins and hashes passed in, and: ++ // ++ // * Handle concatenated properties ++ // * Handle merged properties ++ // * Set up _super wrapping if necessary ++ // * Set up computed property descriptors ++ // * Copying `toString` in broken browsers ++ mergeMixins(mixins, meta$1, descs, values, obj, keys, keysWithSuper); ++ for (let key of keys) { ++ let value = values[key]; ++ let desc = descs[key]; ++ if (value !== undefined) { ++ if (typeof value === 'function') { ++ updateObserversAndListeners(obj, key, value, true); ++ } ++ defineValue(obj, key, value, keysWithSuper.indexOf(key) !== -1, !_hideKeys); ++ } else if (desc !== undefined) { ++ defineDecorator(obj, key, desc, meta$1); + } + } ++ if (!meta$1.isPrototypeMeta(obj)) { ++ revalidateObservers(obj); ++ } ++ return obj; + } + + /** +- @module @ember/object ++ @method mixin ++ @param obj ++ @param mixins* ++ @return obj ++ @private + */ ++ function mixin(obj, ...args) { ++ applyMixin(obj, args); ++ return obj; ++ } ++ const MIXINS = new WeakSet(); + +- const DEEP_EACH_REGEX = /\.@each\.[^.]+\./; +- function noop$1() {} + /** +- `@computed` is a decorator that turns a JavaScript getter and setter into a +- computed property, which is a _cached, trackable value_. By default the getter +- will only be called once and the result will be cached. You can specify +- various properties that your computed property depends on. This will force the +- cached result to be cleared if the dependencies are modified, and lazily recomputed the next time something asks for it. +- +- In the following example we decorate a getter - `fullName` - by calling +- `computed` with the property dependencies (`firstName` and `lastName`) as +- arguments. The `fullName` getter will be called once (regardless of how many +- times it is accessed) as long as its dependencies do not change. Once +- `firstName` or `lastName` are updated any future calls to `fullName` will +- incorporate the new values, and any watchers of the value such as templates +- will be updated: ++ The `Mixin` class allows you to create mixins, whose properties can be ++ added to other classes. For instance, + + ```javascript +- import { computed, set } from '@ember/object'; +- +- class Person { +- constructor(firstName, lastName) { +- set(this, 'firstName', firstName); +- set(this, 'lastName', lastName); +- } ++ import Mixin from '@ember/object/mixin'; + +- @computed('firstName', 'lastName') +- get fullName() { +- return `${this.firstName} ${this.lastName}`; +- } ++ const EditableMixin = Mixin.create({ ++ edit() { ++ console.log('starting to edit'); ++ this.set('isEditing', true); ++ }, ++ isEditing: false + }); +- +- let tom = new Person('Tom', 'Dale'); +- +- tom.fullName; // 'Tom Dale' + ``` + +- You can also provide a setter, which will be used when updating the computed +- property. Ember's `set` function must be used to update the property +- since it will also notify observers of the property: +- + ```javascript +- import { computed, set } from '@ember/object'; +- +- class Person { +- constructor(firstName, lastName) { +- set(this, 'firstName', firstName); +- set(this, 'lastName', lastName); +- } +- +- @computed('firstName', 'lastName') +- get fullName() { +- return `${this.firstName} ${this.lastName}`; +- } +- +- set fullName(value) { +- let [firstName, lastName] = value.split(' '); ++ import EmberObject from '@ember/object'; ++ import EditableMixin from '../mixins/editable'; + +- set(this, 'firstName', firstName); +- set(this, 'lastName', lastName); +- } ++ // Mix mixins into classes by passing them as the first arguments to ++ // `.extend.` ++ const Comment = EmberObject.extend(EditableMixin, { ++ post: null + }); + +- let person = new Person(); ++ let comment = Comment.create({ ++ post: somePost ++ }); + +- set(person, 'fullName', 'Peter Wagenet'); +- person.firstName; // 'Peter' +- person.lastName; // 'Wagenet' ++ comment.edit(); // outputs 'starting to edit' + ``` + +- You can also pass a getter function or object with `get` and `set` functions +- as the last argument to the computed decorator. This allows you to define +- computed property _macros_: +- +- ```js +- import { computed } from '@ember/object'; +- +- function join(...keys) { +- return computed(...keys, function() { +- return keys.map(key => this[key]).join(' '); +- }); +- } +- +- class Person { +- @join('firstName', 'lastName') +- fullName; +- } +- ``` ++ Note that Mixins are created with `Mixin.create`, not ++ `Mixin.extend`. + +- Note that when defined this way, getters and setters receive the _key_ of the +- property they are decorating as the first argument. Setters receive the value +- they are setting to as the second argument instead. Additionally, setters must +- _return_ the value that should be cached: ++ Note that mixins extend a constructor's prototype so arrays and object literals ++ defined as properties will be shared amongst objects that implement the mixin. ++ If you want to define a property in a mixin that is not shared, you can define ++ it either as a computed property or have it be created on initialization of the object. + + ```javascript +- import { computed, set } from '@ember/object'; +- +- function fullNameMacro(firstNameKey, lastNameKey) { +- return computed(firstNameKey, lastNameKey, { +- get() { +- return `${this[firstNameKey]} ${this[lastNameKey]}`; +- } +- +- set(key, value) { +- let [firstName, lastName] = value.split(' '); +- +- set(this, firstNameKey, firstName); +- set(this, lastNameKey, lastName); +- +- return value; +- } +- }); +- } +- +- class Person { +- constructor(firstName, lastName) { +- set(this, 'firstName', firstName); +- set(this, 'lastName', lastName); +- } ++ // filters array will be shared amongst any object implementing mixin ++ import Mixin from '@ember/object/mixin'; ++ import { A } from '@ember/array'; + +- @fullNameMacro('firstName', 'lastName') fullName; ++ const FilterableMixin = Mixin.create({ ++ filters: A() + }); +- +- let person = new Person(); +- +- set(person, 'fullName', 'Peter Wagenet'); +- person.firstName; // 'Peter' +- person.lastName; // 'Wagenet' + ``` + +- Computed properties can also be used in classic classes. To do this, we +- provide the getter and setter as the last argument like we would for a macro, +- and we assign it to a property on the class definition. This is an _anonymous_ +- computed macro: +- + ```javascript +- import EmberObject, { computed, set } from '@ember/object'; +- +- let Person = EmberObject.extend({ +- // these will be supplied by `create` +- firstName: null, +- lastName: null, +- +- fullName: computed('firstName', 'lastName', { +- get() { +- return `${this.firstName} ${this.lastName}`; +- } +- +- set(key, value) { +- let [firstName, lastName] = value.split(' '); +- +- set(this, 'firstName', firstName); +- set(this, 'lastName', lastName); ++ import Mixin from '@ember/object/mixin'; ++ import { A } from '@ember/array'; ++ import { computed } from '@ember/object'; + +- return value; +- } ++ // filters will be a separate array for every object implementing the mixin ++ const FilterableMixin = Mixin.create({ ++ filters: computed(function() { ++ return A(); + }) + }); +- +- let tom = Person.create({ +- firstName: 'Tom', +- lastName: 'Dale' +- }); +- +- tom.get('fullName') // 'Tom Dale' + ``` + +- You can overwrite computed property without setters with a normal property (no +- longer computed) that won't change if dependencies change. You can also mark +- computed property as `.readOnly()` and block all attempts to set it. +- + ```javascript +- import { computed, set } from '@ember/object'; ++ import Mixin from '@ember/object/mixin'; ++ import { A } from '@ember/array'; + +- class Person { +- constructor(firstName, lastName) { +- set(this, 'firstName', firstName); +- set(this, 'lastName', lastName); +- } ++ // filters will be created as a separate array during the object's initialization ++ const Filterable = Mixin.create({ ++ filters: null, + +- @computed('firstName', 'lastName').readOnly() +- get fullName() { +- return `${this.firstName} ${this.lastName}`; ++ init() { ++ this._super(...arguments); ++ this.set("filters", A()); + } + }); +- +- let person = new Person(); +- person.set('fullName', 'Peter Wagenet'); // Uncaught Error: Cannot set read-only property "fullName" on object: <(...):emberXXX> + ``` + +- Additional resources: +- - [Decorators RFC](https://github.com/emberjs/rfcs/blob/master/text/0408-decorators.md) +- - [New CP syntax RFC](https://github.com/emberjs/rfcs/blob/master/text/0011-improved-cp-syntax.md) +- - [New computed syntax explained in "Ember 1.12 released" ](https://emberjs.com/blog/2015/05/13/ember-1-12-released.html#toc_new-computed-syntax) +- +- @class ComputedProperty ++ @class Mixin + @public + */ +- class ComputedProperty extends ComputedDescriptor { +- _readOnly = false; +- _hasConfig = false; +- _getter = undefined; +- _setter = undefined; +- constructor(args) { +- super(); +- let maybeConfig = args[args.length - 1]; +- if (typeof maybeConfig === 'function' || maybeConfig !== null && typeof maybeConfig === 'object') { +- this._hasConfig = true; +- let config = args.pop(); +- if (typeof config === 'function') { +- (!(!isClassicDecorator(config)) && assert$1(`You attempted to pass a computed property instance to computed(). Computed property instances are decorator functions, and cannot be passed to computed() because they cannot be turned into decorators twice`, !isClassicDecorator(config))); +- this._getter = config; +- } else { +- const objectConfig = config; +- (!(typeof objectConfig === 'object' && !Array.isArray(objectConfig)) && assert$1('computed expects a function or an object as last argument.', typeof objectConfig === 'object' && !Array.isArray(objectConfig))); +- (!(Object.keys(objectConfig).every(key => key === 'get' || key === 'set')) && assert$1('Config object passed to computed can only contain `get` and `set` keys.', Object.keys(objectConfig).every(key => key === 'get' || key === 'set'))); +- (!(Boolean(objectConfig.get) || Boolean(objectConfig.set)) && assert$1('Computed properties must receive a getter or a setter, you passed none.', Boolean(objectConfig.get) || Boolean(objectConfig.set))); +- this._getter = objectConfig.get || noop$1; +- this._setter = objectConfig.set; +- } +- } +- if (args.length > 0) { +- this._property(...args); +- } +- } +- setup(obj, keyName, propertyDesc, meta) { +- super.setup(obj, keyName, propertyDesc, meta); +- (!(!(propertyDesc && typeof propertyDesc.value === 'function')) && assert$1(`@computed can only be used on accessors or fields, attempted to use it with ${keyName} but that was a method. Try converting it to a getter (e.g. \`get ${keyName}() {}\`)`, !(propertyDesc && typeof propertyDesc.value === 'function'))); +- (!(!propertyDesc || !propertyDesc.initializer) && assert$1(`@computed can only be used on empty fields. ${keyName} has an initial value (e.g. \`${keyName} = someValue\`)`, !propertyDesc || !propertyDesc.initializer)); +- (!(!(this._hasConfig && propertyDesc && (typeof propertyDesc.get === 'function' || typeof propertyDesc.set === 'function'))) && assert$1(`Attempted to apply a computed property that already has a getter/setter to a ${keyName}, but it is a method or an accessor. If you passed @computed a function or getter/setter (e.g. \`@computed({ get() { ... } })\`), then it must be applied to a field`, !(this._hasConfig && propertyDesc && (typeof propertyDesc.get === 'function' || typeof propertyDesc.set === 'function')))); +- if (this._hasConfig === false) { +- (!(propertyDesc && (typeof propertyDesc.get === 'function' || typeof propertyDesc.set === 'function')) && assert$1(`Attempted to use @computed on ${keyName}, but it did not have a getter or a setter. You must either pass a get a function or getter/setter to @computed directly (e.g. \`@computed({ get() { ... } })\`) or apply @computed directly to a getter/setter`, propertyDesc && (typeof propertyDesc.get === 'function' || typeof propertyDesc.set === 'function'))); +- let { +- get, +- set +- } = propertyDesc; +- if (get !== undefined) { +- this._getter = get; +- } +- if (set !== undefined) { +- this._setter = function setterWrapper(_key, value) { +- let ret = set.call(this, value); +- if (get !== undefined) { +- return typeof ret === 'undefined' ? get.call(this) : ret; +- } +- return ret; +- }; +- } +- } +- } +- _property(...passedArgs) { +- let args = []; +- function addArg(property) { +- (!(DEEP_EACH_REGEX.test(property) === false) && assert$1(`Dependent keys containing @each only work one level deep. ` + `You used the key "${property}" which is invalid. ` + `Please create an intermediary computed property or ` + `switch to using tracked properties.`, DEEP_EACH_REGEX.test(property) === false)); +- args.push(property); +- } +- for (let arg of passedArgs) { +- expandProperties(arg, addArg); +- } +- this._dependentKeys = args; +- } +- get(obj, keyName) { +- let meta$1 = meta(obj); +- let tagMeta = tagMetaFor(obj); +- let propertyTag = tagFor(obj, keyName, tagMeta); +- let ret; +- let revision = meta$1.revisionFor(keyName); +- if (revision !== undefined && validateTag(propertyTag, revision)) { +- ret = meta$1.valueFor(keyName); +- } else { +- // For backwards compatibility, we only throw if the CP has any dependencies. CPs without dependencies +- // should be allowed, even after the object has been destroyed, which is why we check _dependentKeys. +- (!(this._dependentKeys === undefined || !isDestroyed(obj)) && assert$1(`Attempted to access the computed ${obj}.${keyName} on a destroyed object, which is not allowed`, this._dependentKeys === undefined || !isDestroyed(obj))); +- let { +- _getter, +- _dependentKeys +- } = this; ++ class Mixin { ++ /** @internal */ + +- // Create a tracker that absorbs any trackable actions inside the CP +- untrack(() => { +- ret = _getter.call(obj, keyName); +- }); +- if (_dependentKeys !== undefined) { +- UPDATE_TAG(propertyTag, getChainTagsForKeys(obj, _dependentKeys, tagMeta, meta$1)); +- { +- ALLOW_CYCLES.set(propertyTag, true); +- } +- } +- meta$1.setValueFor(keyName, ret); +- meta$1.setRevisionFor(keyName, valueForTag(propertyTag)); +- finishLazyChains(meta$1, keyName, ret); +- } +- consumeTag(propertyTag); ++ /** @internal */ ++ mixins; + +- // Add the tag of the returned value if it is an array, since arrays +- // should always cause updates if they are consumed and then changed +- if (Array.isArray(ret)) { +- consumeTag(tagFor(ret, '[]')); +- } +- return ret; +- } +- set(obj, keyName, value) { +- if (this._readOnly) { +- this._throwReadOnlyError(obj, keyName); +- } +- (!(this._setter !== undefined) && assert$1(`Cannot override the computed property \`${keyName}\` on ${toString$1(obj)}.`, this._setter !== undefined)); +- let meta$1 = meta(obj); ++ /** @internal */ ++ properties; + +- // ensure two way binding works when the component has defined a computed +- // property with both a setter and dependent keys, in that scenario without +- // the sync observer added below the caller's value will never be updated +- // +- // See GH#18147 / GH#19028 for details. +- if ( +- // ensure that we only run this once, while the component is being instantiated +- meta$1.isInitializing() && this._dependentKeys !== undefined && this._dependentKeys.length > 0 && typeof obj[PROPERTY_DID_CHANGE] === 'function' && obj.isComponent) { +- // It's redundant to do this here, but we don't want to check above so we can avoid an extra function call in prod. +- (!(hasPropertyDidChange(obj)) && assert$1('property did change hook is invalid', hasPropertyDidChange(obj))); +- addObserver(obj, keyName, () => { +- obj[PROPERTY_DID_CHANGE](keyName); +- }, undefined, true); +- } +- let ret; +- try { +- beginPropertyChanges(); +- ret = this._set(obj, keyName, value, meta$1); +- finishLazyChains(meta$1, keyName, ret); +- let tagMeta = tagMetaFor(obj); +- let propertyTag = tagFor(obj, keyName, tagMeta); +- let { +- _dependentKeys +- } = this; +- if (_dependentKeys !== undefined) { +- UPDATE_TAG(propertyTag, getChainTagsForKeys(obj, _dependentKeys, tagMeta, meta$1)); +- if (true /* DEBUG */) { +- ALLOW_CYCLES.set(propertyTag, true); +- } ++ /** @internal */ ++ ownerConstructor; ++ ++ /** @internal */ ++ _without; ++ /** @internal */ ++ constructor(mixins, properties) { ++ MIXINS.add(this); ++ this.properties = extractAccessors(properties); ++ this.mixins = buildMixinsArray(mixins); ++ this.ownerConstructor = undefined; ++ this._without = undefined; ++ { ++ // Eagerly add INIT_FACTORY to avoid issues in DEBUG as a result of Object.seal(mixin) ++ this[INIT_FACTORY] = null; ++ /* ++ In debug builds, we seal mixins to help avoid performance pitfalls. ++ In IE11 there is a quirk that prevents sealed objects from being added ++ to a WeakMap. Unfortunately, the mixin system currently relies on ++ weak maps in `guidFor`, so we need to prime the guid cache weak map. ++ */ ++ guidFor(this); ++ if (Mixin._disableDebugSeal !== true) { ++ Object.seal(this); + } +- meta$1.setRevisionFor(keyName, valueForTag(propertyTag)); +- } finally { +- endPropertyChanges(); + } +- return ret; + } +- _throwReadOnlyError(obj, keyName) { +- throw new Error(`Cannot set read-only property "${keyName}" on object: ${inspect(obj)}`); ++ ++ /** ++ @method create ++ @for @ember/object/mixin ++ @static ++ @param arguments* ++ @public ++ */ ++ static create(...args) { ++ setUnprocessedMixins(); ++ let M = this; ++ return new M(args, undefined); + } +- _set(obj, keyName, value, meta) { +- let hadCachedValue = meta.revisionFor(keyName) !== undefined; +- let cachedValue = meta.valueFor(keyName); +- let ret; +- let { +- _setter +- } = this; +- setObserverSuspended(obj, keyName, true); +- try { +- ret = _setter.call(obj, keyName, value, cachedValue); +- } finally { +- setObserverSuspended(obj, keyName, false); +- } + +- // allows setter to return the same value that is cached already +- if (hadCachedValue && cachedValue === ret) { ++ // returns the mixins currently applied to the specified object ++ // TODO: Make `mixin` ++ /** @internal */ ++ static mixins(obj) { ++ let meta = peekMeta(obj); ++ let ret = []; ++ if (meta === null) { + return ret; + } +- meta.setValueFor(keyName, ret); +- notifyPropertyChange(obj, keyName, meta, value); ++ meta.forEachMixins(currentMixin => { ++ // skip primitive mixins since these are always anonymous ++ if (!currentMixin.properties) { ++ ret.push(currentMixin); ++ } ++ }); + return ret; + } + +- /* called before property is overridden */ +- teardown(obj, keyName, meta) { +- if (meta.revisionFor(keyName) !== undefined) { +- meta.setRevisionFor(keyName, undefined); +- meta.setValueFor(keyName, undefined); +- } +- super.teardown(obj, keyName, meta); +- } +- } +- class AutoComputedProperty extends ComputedProperty { +- get(obj, keyName) { +- let meta$1 = meta(obj); +- let tagMeta = tagMetaFor(obj); +- let propertyTag = tagFor(obj, keyName, tagMeta); +- let ret; +- let revision = meta$1.revisionFor(keyName); +- if (revision !== undefined && validateTag(propertyTag, revision)) { +- ret = meta$1.valueFor(keyName); +- } else { +- (!(!isDestroyed(obj)) && assert$1(`Attempted to access the computed ${obj}.${keyName} on a destroyed object, which is not allowed`, !isDestroyed(obj))); +- let { +- _getter +- } = this; +- +- // Create a tracker that absorbs any trackable actions inside the CP +- let tag = track(() => { +- ret = _getter.call(obj, keyName); +- }); +- UPDATE_TAG(propertyTag, tag); +- meta$1.setValueFor(keyName, ret); +- meta$1.setRevisionFor(keyName, valueForTag(propertyTag)); +- finishLazyChains(meta$1, keyName, ret); ++ /** ++ @method reopen ++ @param arguments* ++ @private ++ @internal ++ */ ++ reopen(...args) { ++ if (args.length === 0) { ++ return this; + } +- consumeTag(propertyTag); +- +- // Add the tag of the returned value if it is an array, since arrays +- // should always cause updates if they are consumed and then changed +- if (Array.isArray(ret)) { +- consumeTag(tagFor(ret, '[]', tagMeta)); ++ if (this.properties) { ++ let currentMixin = new Mixin(undefined, this.properties); ++ this.properties = undefined; ++ this.mixins = [currentMixin]; ++ } else if (!this.mixins) { ++ this.mixins = []; + } +- return ret; ++ this.mixins = this.mixins.concat(buildMixinsArray(args)); ++ return this; + } +- } +- // TODO: This class can be svelted once `meta` has been deprecated +- class ComputedDecoratorImpl extends Function { ++ + /** +- Call on a computed property to set it into read-only mode. When in this +- mode the computed property will throw an error when set. +- Example: +- ```javascript +- import { computed, set } from '@ember/object'; +- class Person { +- @computed().readOnly() +- get guid() { +- return 'guid-guid-guid'; +- } +- } +- let person = new Person(); +- set(person, 'guid', 'new-guid'); // will throw an exception +- ``` +- Classic Class Example: +- ```javascript +- import EmberObject, { computed } from '@ember/object'; +- let Person = EmberObject.extend({ +- guid: computed(function() { +- return 'guid-guid-guid'; +- }).readOnly() +- }); +- let person = Person.create(); +- person.set('guid', 'new-guid'); // will throw an exception +- ``` +- @method readOnly +- @return {ComputedProperty} this +- @chainable +- @public ++ @method apply ++ @param obj ++ @return applied object ++ @private ++ @internal + */ +- readOnly() { +- let desc = descriptorForDecorator(this); +- (!(!(desc._setter && desc._setter !== desc._getter)) && assert$1('Computed properties that define a setter using the new syntax cannot be read-only', !(desc._setter && desc._setter !== desc._getter))); +- desc._readOnly = true; +- return this; ++ apply(obj, _hideKeys = false) { ++ // Ember.NativeArray is a normal Ember.Mixin that we mix into `Array.prototype` when prototype extensions are enabled ++ // mutating a native object prototype like this should _not_ result in enumerable properties being added (or we have significant ++ // issues with things like deep equality checks from test frameworks, or things like jQuery.extend(true, [], [])). ++ // ++ // _hideKeys disables enumerablity when applying the mixin. This is a hack, and we should stop mutating the array prototype by default 😫 ++ return applyMixin(obj, [this], _hideKeys); ++ } ++ ++ /** @internal */ ++ applyPartial(obj) { ++ return applyMixin(obj, [this]); + } + + /** +- In some cases, you may want to annotate computed properties with additional +- metadata about how they function or what values they operate on. For example, +- computed property functions may close over variables that are then no longer +- available for introspection. You can pass a hash of these values to a +- computed property. +- Example: +- ```javascript +- import { computed } from '@ember/object'; +- import Person from 'my-app/utils/person'; +- class Store { +- @computed().meta({ type: Person }) +- get person() { +- let personId = this.personId; +- return Person.create({ id: personId }); +- } +- } +- ``` +- Classic Class Example: +- ```javascript +- import { computed } from '@ember/object'; +- import Person from 'my-app/utils/person'; +- const Store = EmberObject.extend({ +- person: computed(function() { +- let personId = this.get('personId'); +- return Person.create({ id: personId }); +- }).meta({ type: Person }) +- }); +- ``` +- The hash that you pass to the `meta()` function will be saved on the +- computed property descriptor under the `_meta` key. Ember runtime +- exposes a public API for retrieving these values from classes, +- via the `metaForProperty()` function. +- @method meta +- @param {Object} meta +- @chainable +- @public ++ @method detect ++ @param obj ++ @return {Boolean} ++ @private ++ @internal + */ +- +- meta(meta) { +- let prop = descriptorForDecorator(this); +- if (arguments.length === 0) { +- return prop._meta || {}; +- } else { +- prop._meta = meta; +- return this; ++ detect(obj) { ++ if (typeof obj !== 'object' || obj === null) { ++ return false; ++ } ++ if (MIXINS.has(obj)) { ++ return _detect(obj, this); ++ } ++ let meta = peekMeta(obj); ++ if (meta === null) { ++ return false; + } ++ return meta.hasMixin(this); + } + +- // TODO: Remove this when we can provide alternatives in the ecosystem to +- // addons such as ember-macro-helpers that use it. + /** @internal */ +- get _getter() { +- return descriptorForDecorator(this)._getter; ++ without(...args) { ++ let ret = new Mixin([this]); ++ ret._without = args; ++ return ret; + } + +- // TODO: Refactor this, this is an internal API only + /** @internal */ +- set enumerable(value) { +- descriptorForDecorator(this).enumerable = value; ++ keys() { ++ let keys = _keys(this); ++ (!(keys) && assert$1('[BUG] Missing keys for mixin!', keys)); ++ return keys; + } +- } +- +- /** +- This helper returns a new property descriptor that wraps the passed +- computed property function. You can use this helper to define properties with +- native decorator syntax, mixins, or via `defineProperty()`. +- +- Example: +- +- ```js +- import { computed, set } from '@ember/object'; +- +- class Person { +- constructor() { +- this.firstName = 'Betty'; +- this.lastName = 'Jones'; +- }, + +- @computed('firstName', 'lastName') +- get fullName() { +- return `${this.firstName} ${this.lastName}`; ++ /** @internal */ ++ toString() { ++ return '(unknown mixin)'; ++ } ++ } ++ { ++ Object.defineProperty(Mixin, '_disableDebugSeal', { ++ configurable: true, ++ enumerable: false, ++ writable: true, ++ value: false ++ }); ++ } ++ function buildMixinsArray(mixins) { ++ let length = mixins && mixins.length || 0; ++ let m = undefined; ++ if (length > 0) { ++ m = new Array(length); ++ for (let i = 0; i < length; i++) { ++ let x = mixins[i]; ++ (!(typeof x === 'object' && x !== null && Object.prototype.toString.call(x) !== '[object Array]') && assert$1(`Expected hash or Mixin instance, got ${Object.prototype.toString.call(x)}`, typeof x === 'object' && x !== null && Object.prototype.toString.call(x) !== '[object Array]')); ++ if (MIXINS.has(x)) { ++ m[i] = x; ++ } else { ++ m[i] = new Mixin(undefined, x); ++ } + } + } ++ return m; ++ } ++ { ++ Object.seal(Mixin.prototype); ++ } ++ function _detect(curMixin, targetMixin, seen = new Set()) { ++ if (seen.has(curMixin)) { ++ return false; ++ } ++ seen.add(curMixin); ++ if (curMixin === targetMixin) { ++ return true; ++ } ++ let mixins = curMixin.mixins; ++ if (mixins) { ++ return mixins.some(mixin => _detect(mixin, targetMixin, seen)); ++ } ++ return false; ++ } ++ function _keys(mixin, ret = new Set(), seen = new Set()) { ++ if (seen.has(mixin)) { ++ return; ++ } ++ seen.add(mixin); ++ if (mixin.properties) { ++ let props = Object.keys(mixin.properties); ++ for (let prop of props) { ++ ret.add(prop); ++ } ++ } else if (mixin.mixins) { ++ mixin.mixins.forEach(x => _keys(x, ret, seen)); ++ } ++ return ret; ++ } + +- let client = new Person(); +- +- client.fullName; // 'Betty Jones' +- +- set(client, 'lastName', 'Fuller'); +- client.fullName; // 'Betty Fuller' +- ``` +- +- Classic Class Example: +- +- ```js +- import EmberObject, { computed } from '@ember/object'; ++ const emberObjectMixin = /*#__PURE__*/Object.defineProperty({ ++ __proto__: null, ++ applyMixin, ++ default: Mixin, ++ mixin ++ }, Symbol.toStringTag, { value: 'Module' }); + +- let Person = EmberObject.extend({ +- init() { +- this._super(...arguments); ++ /** ++ @module ember ++ */ + +- this.firstName = 'Betty'; +- this.lastName = 'Jones'; +- }, + +- fullName: computed('firstName', 'lastName', function() { +- return `${this.get('firstName')} ${this.get('lastName')}`; +- }) +- }); ++ /** ++ RegistryProxyMixin is used to provide public access to specific ++ registry functionality. + +- let client = Person.create(); ++ @class RegistryProxyMixin ++ @extends RegistryProxy ++ @private ++ */ + +- client.get('fullName'); // 'Betty Jones' +- +- client.set('lastName', 'Fuller'); +- client.get('fullName'); // 'Betty Fuller' +- ``` +- +- You can also provide a setter, either directly on the class using native class +- syntax, or by passing a hash with `get` and `set` functions. +- +- Example: +- +- ```js +- import { computed, set } from '@ember/object'; +- +- class Person { +- constructor() { +- this.firstName = 'Betty'; +- this.lastName = 'Jones'; +- }, +- +- @computed('firstName', 'lastName') +- get fullName() { +- return `${this.firstName} ${this.lastName}`; +- } +- +- set fullName(value) { +- let [firstName, lastName] = value.split(/\s+/); +- +- set(this, 'firstName', firstName); +- set(this, 'lastName', lastName); +- +- return value; +- } +- } +- +- let client = new Person(); +- +- client.fullName; // 'Betty Jones' +- +- set(client, 'lastName', 'Fuller'); +- client.fullName; // 'Betty Fuller' +- ``` +- +- Classic Class Example: +- +- ```js +- import EmberObject, { computed } from '@ember/object'; +- +- let Person = EmberObject.extend({ +- init() { +- this._super(...arguments); +- +- this.firstName = 'Betty'; +- this.lastName = 'Jones'; +- }, +- +- fullName: computed('firstName', 'lastName', { +- get(key) { +- return `${this.get('firstName')} ${this.get('lastName')}`; +- }, +- set(key, value) { +- let [firstName, lastName] = value.split(/\s+/); +- this.setProperties({ firstName, lastName }); +- return value; +- } +- }) +- }); +- +- let client = Person.create(); +- client.get('firstName'); // 'Betty' +- +- client.set('fullName', 'Carroll Fuller'); +- client.get('firstName'); // 'Carroll' +- ``` +- +- When passed as an argument, the `set` function should accept two parameters, +- `key` and `value`. The value returned from `set` will be the new value of the +- property. +- +- _Note: This is the preferred way to define computed properties when writing third-party +- libraries that depend on or use Ember, since there is no guarantee that the user +- will have [prototype Extensions](https://guides.emberjs.com/release/configuring-ember/disabling-prototype-extensions/) enabled._ +- +- @method computed +- @for @ember/object +- @static +- @param {String} [dependentKeys*] Optional dependent keys that trigger this computed property. +- @param {Function} func The computed property function. +- @return {ComputedDecorator} property decorator instance +- @public +- */ +- // @computed without parens or computed with descriptor args +- +- // @computed with keys only +- +- // @computed with keys and config +- +- // @computed with config only +- +- function computed(...args) { +- (!(!(isElementDescriptor(args.slice(0, 3)) && args.length === 5 && args[4] === true)) && assert$1(`@computed can only be used directly as a native decorator. If you're using tracked in classic classes, add parenthesis to call it like a function: computed()`, !(isElementDescriptor(args.slice(0, 3)) && args.length === 5 && args[4] === true))); +- if (isElementDescriptor(args)) { +- // SAFETY: We passed in the impl for this class +- let decorator = makeComputedDecorator(new ComputedProperty([]), ComputedDecoratorImpl); +- return decorator(args[0], args[1], args[2]); +- } +- +- // SAFETY: We passed in the impl for this class +- return makeComputedDecorator(new ComputedProperty(args), ComputedDecoratorImpl); +- } +- function autoComputed(...config) { +- // SAFETY: We passed in the impl for this class +- return makeComputedDecorator(new AutoComputedProperty(config), ComputedDecoratorImpl); ++ const RegistryProxyMixin = Mixin.create({ ++ __registry__: null, ++ resolveRegistration(fullName) { ++ (!(this.__registry__.isValidFullName(fullName)) && assert$1('fullName must be a proper full name', this.__registry__.isValidFullName(fullName))); ++ return this.__registry__.resolve(fullName); ++ }, ++ register: registryAlias('register'), ++ unregister: registryAlias('unregister'), ++ hasRegistration: registryAlias('has'), ++ registeredOption: registryAlias('getOption'), ++ registerOptions: registryAlias('options'), ++ registeredOptions: registryAlias('getOptions'), ++ registerOptionsForType: registryAlias('optionsForType'), ++ registeredOptionsForType: registryAlias('getOptionsForType') ++ }); ++ function registryAlias(name) { ++ return function (...args) { ++ // We need this cast because `Parameters` is deferred so that it is not ++ // possible for TS to see it will always produce the right type. However, ++ // since `AnyFn` has a rest type, it is allowed. See discussion on [this ++ // issue](https://github.com/microsoft/TypeScript/issues/47615). ++ return this.__registry__[name](...args); ++ }; + } + +- /** +- Allows checking if a given property on an object is a computed property. For the most part, +- this doesn't matter (you would normally just access the property directly and use its value), +- but for some tooling specific scenarios (e.g. the ember-inspector) it is important to +- differentiate if a property is a computed property or a "normal" property. +- +- This will work on either a class's prototype or an instance itself. +- +- @static +- @method isComputed +- @for @ember/debug +- @private +- */ +- function isComputed(obj, key) { +- return Boolean(descriptorForProperty(obj, key)); +- } ++ const emberinternalsRuntimeLibMixinsRegistryProxy = /*#__PURE__*/Object.defineProperty({ ++ __proto__: null, ++ default: RegistryProxyMixin ++ }, Symbol.toStringTag, { value: 'Module' }); + +- function getCachedValueFor(obj, key) { +- let meta = peekMeta(obj); +- if (meta) { +- return meta.valueFor(key); ++ const SET_TIMEOUT = setTimeout; ++ const NOOP$4 = () => {}; ++ function buildNext(flush) { ++ // Using "promises first" here to: ++ // ++ // 1) Ensure more consistent experience on browsers that ++ // have differently queued microtasks (separate queues for ++ // MutationObserver vs Promises). ++ // 2) Ensure better debugging experiences (it shows up in Chrome ++ // call stack as "Promise.then (async)") which is more consistent ++ // with user expectations ++ // ++ // When Promise is unavailable use MutationObserver (mostly so that we ++ // still get microtasks on IE11), and when neither MutationObserver and ++ // Promise are present use a plain old setTimeout. ++ if (typeof Promise === 'function') { ++ const autorunPromise = Promise.resolve(); ++ return () => autorunPromise.then(flush); ++ } else if (typeof MutationObserver === 'function') { ++ let iterations = 0; ++ let observer = new MutationObserver(flush); ++ let node = document.createTextNode(''); ++ observer.observe(node, { ++ characterData: true ++ }); ++ return () => { ++ iterations = ++iterations % 2; ++ node.data = '' + iterations; ++ return iterations; ++ }; + } else { +- return undefined; ++ return () => SET_TIMEOUT(flush, 0); + } + } +- +- /** +- @module @ember/object +- */ +- +- +- /** +- NOTE: This is a low-level method used by other parts of the API. You almost +- never want to call this method directly. Instead you should use +- `mixin()` to define new properties. +- +- Defines a property on an object. This method works much like the ES5 +- `Object.defineProperty()` method except that it can also accept computed +- properties and other special descriptors. +- +- Normally this method takes only three parameters. However if you pass an +- instance of `Descriptor` as the third param then you can pass an +- optional value as the fourth parameter. This is often more efficient than +- creating new descriptor hashes for each property. +- +- ## Examples +- +- ```javascript +- import { defineProperty, computed } from '@ember/object'; +- +- // ES5 compatible mode +- defineProperty(contact, 'firstName', { +- writable: true, +- configurable: false, +- enumerable: true, +- value: 'Charles' +- }); +- +- // define a simple property +- defineProperty(contact, 'lastName', undefined, 'Jolley'); +- +- // define a computed property +- defineProperty(contact, 'fullName', computed('firstName', 'lastName', function() { +- return this.firstName+' '+this.lastName; +- })); +- ``` +- +- @public +- @method defineProperty +- @static +- @for @ember/object +- @param {Object} obj the object to define this property on. This may be a prototype. +- @param {String} keyName the name of the property +- @param {Descriptor} [desc] an instance of `Descriptor` (typically a +- computed property) or an ES5 descriptor. +- You must provide this or `data` but not both. +- @param {*} [data] something other than a descriptor, that will +- become the explicit value of this property. +- */ +- function defineProperty(obj, keyName, desc, data, _meta) { +- let meta$1 = _meta === undefined ? meta(obj) : _meta; +- let previousDesc = descriptorForProperty(obj, keyName, meta$1); +- let wasDescriptor = previousDesc !== undefined; +- if (wasDescriptor) { +- previousDesc.teardown(obj, keyName, meta$1); +- } +- if (isClassicDecorator(desc)) { +- defineDecorator(obj, keyName, desc, meta$1); +- } else if (desc === null || desc === undefined) { +- defineValue(obj, keyName, data, wasDescriptor, true); +- } else { +- // fallback to ES5 +- Object.defineProperty(obj, keyName, desc); +- } +- +- // if key is being watched, override chains that +- // were initialized with the prototype +- if (!meta$1.isPrototypeMeta(obj)) { +- revalidateObservers(obj); +- } ++ function buildPlatform(flush) { ++ let clearNext = NOOP$4; ++ return { ++ setTimeout(fn, ms) { ++ return setTimeout(fn, ms); ++ }, ++ clearTimeout(timerId) { ++ return clearTimeout(timerId); ++ }, ++ now() { ++ return Date.now(); ++ }, ++ next: buildNext(flush), ++ clearNext ++ }; + } +- function defineDecorator(obj, keyName, desc, meta) { +- let propertyDesc; +- { +- propertyDesc = desc(obj, keyName, undefined, meta, true); +- } +- Object.defineProperty(obj, keyName, propertyDesc); +- +- // pass the decorator function forward for backwards compat +- return desc; ++ const NUMBER = /\d+/; ++ const TIMERS_OFFSET = 6; ++ function isCoercableNumber(suspect) { ++ let type = typeof suspect; ++ return type === 'number' && suspect === suspect || type === 'string' && NUMBER.test(suspect); + } +- function defineValue(obj, keyName, value, wasDescriptor, enumerable = true) { +- if (wasDescriptor === true || enumerable === false) { +- Object.defineProperty(obj, keyName, { +- configurable: true, +- enumerable, +- writable: true, +- value +- }); +- } else { +- { +- setWithMandatorySetter(obj, keyName, value); ++ function getOnError(options) { ++ return options.onError || options.onErrorTarget && options.onErrorTarget[options.onErrorMethod]; ++ } ++ function findItem(target, method, collection) { ++ let index = -1; ++ for (let i = 0, l = collection.length; i < l; i += 4) { ++ if (collection[i] === target && collection[i + 1] === method) { ++ index = i; ++ break; + } + } +- return value; +- } +- +- const EMBER_ARRAYS = new WeakSet(); +- function setEmberArray(obj) { +- EMBER_ARRAYS.add(obj); ++ return index; + } +- function isEmberArray(obj) { +- return EMBER_ARRAYS.has(obj); ++ function findTimerItem(target, method, collection) { ++ let index = -1; ++ for (let i = 2, l = collection.length; i < l; i += 6) { ++ if (collection[i] === target && collection[i + 1] === method) { ++ index = i - 2; ++ break; ++ } ++ } ++ return index; + } +- +- const emberArrayinternals = /*#__PURE__*/Object.defineProperty({ +- __proto__: null, +- isEmberArray, +- setEmberArray +- }, Symbol.toStringTag, { value: 'Module' }); +- +- const firstDotIndexCache = new Cache(1000, key => key.indexOf('.')); +- function isPath(path) { +- return typeof path === 'string' && firstDotIndexCache.get(path) !== -1; ++ function getQueueItems(items, queueItemLength, queueItemPositionOffset = 0) { ++ let queueItems = []; ++ for (let i = 0; i < items.length; i += queueItemLength) { ++ let maybeError = items[i + 3 /* stack */ + queueItemPositionOffset]; ++ let queueItem = { ++ target: items[i + 0 /* target */ + queueItemPositionOffset], ++ method: items[i + 1 /* method */ + queueItemPositionOffset], ++ args: items[i + 2 /* args */ + queueItemPositionOffset], ++ stack: maybeError !== undefined && 'stack' in maybeError ? maybeError.stack : '' ++ }; ++ queueItems.push(queueItem); ++ } ++ return queueItems; + } +- +- /** +- @module @ember/object +- */ +- +- const PROXY_CONTENT = symbol('PROXY_CONTENT'); +- let getPossibleMandatoryProxyValue; +- { +- getPossibleMandatoryProxyValue = function getPossibleMandatoryProxyValue(obj, keyName) { +- let content = obj[PROXY_CONTENT]; +- if (content === undefined) { +- return obj[keyName]; ++ function binarySearch(time, timers) { ++ let start = 0; ++ let end = timers.length - TIMERS_OFFSET; ++ let middle; ++ let l; ++ while (start < end) { ++ // since timers is an array of pairs 'l' will always ++ // be an integer ++ l = (end - start) / TIMERS_OFFSET; ++ // compensate for the index in case even number ++ // of pairs inside timers ++ middle = start + l - l % TIMERS_OFFSET; ++ if (time >= timers[middle]) { ++ start = middle + TIMERS_OFFSET; + } else { +- /* global Reflect */ +- return Reflect.get(content, keyName, obj); ++ end = middle; + } +- }; +- } +- function hasUnknownProperty(val) { +- return typeof val === 'object' && val !== null && typeof val.unknownProperty === 'function'; +- } +- +- // .......................................................... +- // GET AND SET +- // +- // If we are on a platform that supports accessors we can use those. +- // Otherwise simulate accessors by looking up the property directly on the +- // object. +- +- /** +- Gets the value of a property on an object. If the property is computed, +- the function will be invoked. If the property is not defined but the +- object implements the `unknownProperty` method then that will be invoked. +- +- ```javascript +- import { get } from '@ember/object'; +- get(obj, "name"); +- ``` +- +- If you plan to run on IE8 and older browsers then you should use this +- method anytime you want to retrieve a property on an object that you don't +- know for sure is private. (Properties beginning with an underscore '_' +- are considered private.) +- +- On all newer browsers, you only need to use this method to retrieve +- properties if the property might not be defined on the object and you want +- to respect the `unknownProperty` handler. Otherwise you can ignore this +- method. +- +- Note that if the object itself is `undefined`, this method will throw +- an error. +- +- @method get +- @for @ember/object +- @static +- @param {Object} obj The object to retrieve from. +- @param {String} keyName The property key to retrieve +- @return {Object} the property value or `null`. +- @public +- */ +- +- function get$2(obj, keyName) { +- (!(arguments.length === 2) && assert$1(`Get must be called with two arguments; an object and a property key`, arguments.length === 2)); +- (!(obj !== undefined && obj !== null) && assert$1(`Cannot call get with '${keyName}' on an undefined object.`, obj !== undefined && obj !== null)); +- (!(typeof keyName === 'string' || typeof keyName === 'number' && !isNaN(keyName)) && assert$1(`The key provided to get must be a string or number, you passed ${keyName}`, typeof keyName === 'string' || typeof keyName === 'number' && !isNaN(keyName))); +- (!(typeof keyName !== 'string' || keyName.lastIndexOf('this.', 0) !== 0) && assert$1(`'this' in paths is not supported`, typeof keyName !== 'string' || keyName.lastIndexOf('this.', 0) !== 0)); +- return isPath(keyName) ? _getPath(obj, keyName) : _getProp(obj, keyName); ++ } ++ return time >= timers[start] ? start + TIMERS_OFFSET : start; + } +- function _getProp(obj, keyName) { +- if (obj == null) { +- return; ++ const QUEUE_ITEM_LENGTH = 4; ++ class Queue { ++ constructor(name, options = {}, globalOptions = {}) { ++ this._queueBeingFlushed = []; ++ this.targetQueues = new Map(); ++ this.index = 0; ++ this._queue = []; ++ this.name = name; ++ this.options = options; ++ this.globalOptions = globalOptions; + } +- let value; +- if (typeof obj === 'object' || typeof obj === 'function') { +- { +- value = getPossibleMandatoryProxyValue(obj, keyName); +- } +- if (value === undefined && typeof obj === 'object' && !(keyName in obj) && hasUnknownProperty(obj)) { +- value = obj.unknownProperty(keyName); +- } +- if (isTracking()) { +- consumeTag(tagFor(obj, keyName)); +- if (Array.isArray(value) || isEmberArray(value)) { +- // Add the tag of the returned value if it is an array, since arrays +- // should always cause updates if they are consumed and then changed +- consumeTag(tagFor(value, '[]')); ++ stackFor(index) { ++ if (index < this._queue.length) { ++ let entry = this._queue[index * 3 + QUEUE_ITEM_LENGTH]; ++ if (entry) { ++ return entry.stack; ++ } else { ++ return null; + } + } +- } else { +- // SAFETY: It should be ok to access properties on any non-nullish value +- value = obj[keyName]; + } +- return value; +- } +- function _getPath(obj, path, forSet) { +- let parts = typeof path === 'string' ? path.split('.') : path; +- for (let part of parts) { +- if (obj === undefined || obj === null || obj.isDestroyed) { +- return undefined; ++ flush(sync) { ++ let { ++ before, ++ after ++ } = this.options; ++ let target; ++ let method; ++ let args; ++ let errorRecordedForStack; ++ this.targetQueues.clear(); ++ if (this._queueBeingFlushed.length === 0) { ++ this._queueBeingFlushed = this._queue; ++ this._queue = []; + } +- if (forSet && (part === '__proto__' || part === 'constructor')) { +- return; ++ if (before !== undefined) { ++ before(); + } +- obj = _getProp(obj, part); +- } +- return obj; +- } +- +- // Warm it up +- _getProp('foo', 'a'); +- _getProp('foo', 1); +- _getProp({}, 'a'); +- _getProp({}, 1); +- _getProp({ +- unknownProperty() {} +- }, 'a'); +- _getProp({ +- unknownProperty() {} +- }, 1); +- get$2({}, 'foo'); +- get$2({}, 'foo.bar'); +- let fakeProxy = {}; +- setProxy(fakeProxy); +- track(() => _getProp({}, 'a')); +- track(() => _getProp({}, 1)); +- track(() => _getProp({ +- a: [] +- }, 'a')); +- track(() => _getProp({ +- a: fakeProxy +- }, 'a')); +- +- /** +- @module @ember/object +- */ +- /** +- Sets the value of a property on an object, respecting computed properties +- and notifying observers and other listeners of the change. +- If the specified property is not defined on the object and the object +- implements the `setUnknownProperty` method, then instead of setting the +- value of the property on the object, its `setUnknownProperty` handler +- will be invoked with the two parameters `keyName` and `value`. +- +- ```javascript +- import { set } from '@ember/object'; +- set(obj, "name", value); +- ``` +- +- @method set +- @static +- @for @ember/object +- @param {Object} obj The object to modify. +- @param {String} keyName The property key to set +- @param {Object} value The value to set +- @return {Object} the passed value. +- @public +- */ +- function set(obj, keyName, value, tolerant) { +- (!(arguments.length === 3 || arguments.length === 4) && assert$1(`Set must be called with three or four arguments; an object, a property key, a value and tolerant true/false`, arguments.length === 3 || arguments.length === 4)); +- (!(obj && typeof obj === 'object' || typeof obj === 'function') && assert$1(`Cannot call set with '${keyName}' on an undefined object.`, obj && typeof obj === 'object' || typeof obj === 'function')); +- (!(typeof keyName === 'string' || typeof keyName === 'number' && !isNaN(keyName)) && assert$1(`The key provided to set must be a string or number, you passed ${keyName}`, typeof keyName === 'string' || typeof keyName === 'number' && !isNaN(keyName))); +- (!(typeof keyName !== 'string' || keyName.lastIndexOf('this.', 0) !== 0) && assert$1(`'this' in paths is not supported`, typeof keyName !== 'string' || keyName.lastIndexOf('this.', 0) !== 0)); +- if (obj.isDestroyed) { +- (!(tolerant) && assert$1(`calling set on destroyed object: ${toString$1(obj)}.${keyName} = ${toString$1(value)}`, tolerant)); +- return value; +- } +- return isPath(keyName) ? _setPath(obj, keyName, value, tolerant) : _setProp(obj, keyName, value); +- } +- function _setProp(obj, keyName, value) { +- let descriptor = lookupDescriptor(obj, keyName); +- if (descriptor !== null && COMPUTED_SETTERS.has(descriptor.set)) { +- obj[keyName] = value; +- return value; +- } +- let currentValue; +- { +- currentValue = getPossibleMandatoryProxyValue(obj, keyName); +- } +- if (currentValue === undefined && 'object' === typeof obj && !(keyName in obj) && typeof obj.setUnknownProperty === 'function') { +- /* unknown property */ +- obj.setUnknownProperty(keyName, value); +- } else { +- { +- setWithMandatorySetter(obj, keyName, value); ++ let invoke; ++ let queueItems = this._queueBeingFlushed; ++ if (queueItems.length > 0) { ++ let onError = getOnError(this.globalOptions); ++ invoke = onError ? this.invokeWithOnError : this.invoke; ++ for (let i = this.index; i < queueItems.length; i += QUEUE_ITEM_LENGTH) { ++ this.index += QUEUE_ITEM_LENGTH; ++ method = queueItems[i + 1]; ++ // method could have been nullified / canceled during flush ++ if (method !== null) { ++ // ++ // ** Attention intrepid developer ** ++ // ++ // To find out the stack of this task when it was scheduled onto ++ // the run loop, add the following to your app.js: ++ // ++ // Ember.run.backburner.DEBUG = true; // NOTE: This slows your app, don't leave it on in production. ++ // ++ // Once that is in place, when you are at a breakpoint and navigate ++ // here in the stack explorer, you can look at `errorRecordedForStack.stack`, ++ // which will be the captured stack when this job was scheduled. ++ // ++ // One possible long-term solution is the following Chrome issue: ++ // https://bugs.chromium.org/p/chromium/issues/detail?id=332624 ++ // ++ target = queueItems[i]; ++ args = queueItems[i + 2]; ++ errorRecordedForStack = queueItems[i + 3]; // Debugging assistance ++ invoke(target, method, args, onError, errorRecordedForStack); ++ } ++ if (this.index !== this._queueBeingFlushed.length && this.globalOptions.mustYield && this.globalOptions.mustYield()) { ++ return 1 /* Pause */; ++ } ++ } + } +- if (currentValue !== value) { +- notifyPropertyChange(obj, keyName); ++ if (after !== undefined) { ++ after(); ++ } ++ this._queueBeingFlushed.length = 0; ++ this.index = 0; ++ if (sync !== false && this._queue.length > 0) { ++ // check if new items have been added ++ this.flush(true); + } + } +- return value; +- } +- function _setPath(root, path, value, tolerant) { +- let parts = path.split('.'); +- let keyName = parts.pop(); +- (!(keyName.trim().length > 0) && assert$1('Property set failed: You passed an empty path', keyName.trim().length > 0)); +- let newRoot = _getPath(root, parts, true); +- if (newRoot !== null && newRoot !== undefined) { +- return set(newRoot, keyName, value); +- } else if (!tolerant) { +- throw new Error(`Property set failed: object in path "${parts.join('.')}" could not be found.`); ++ hasWork() { ++ return this._queueBeingFlushed.length > 0 || this._queue.length > 0; + } +- } +- +- /** +- Error-tolerant form of `set`. Will not blow up if any part of the +- chain is `undefined`, `null`, or destroyed. +- +- This is primarily used when syncing bindings, which may try to update after +- an object has been destroyed. +- +- ```javascript +- import { trySet } from '@ember/object'; +- +- let obj = { name: "Zoey" }; +- trySet(obj, "contacts.twitter", "@emberjs"); +- ``` +- +- @method trySet +- @static +- @for @ember/object +- @param {Object} root The object to modify. +- @param {String} path The property path to set +- @param {Object} value The value to set +- @public +- */ +- function trySet(root, path, value) { +- return set(root, path, value, true); +- } +- +- function alias(altKey) { +- (!(!isElementDescriptor(Array.prototype.slice.call(arguments))) && assert$1('You attempted to use @alias as a decorator directly, but it requires a `altKey` parameter', !isElementDescriptor(Array.prototype.slice.call(arguments)))); // SAFETY: We passed in the impl for this class +- return makeComputedDecorator(new AliasedProperty(altKey), AliasDecoratorImpl); +- } +- +- // TODO: This class can be svelted once `meta` has been deprecated +- class AliasDecoratorImpl extends Function { +- readOnly() { +- descriptorForDecorator(this).readOnly(); +- return this; ++ cancel({ ++ target, ++ method ++ }) { ++ let queue = this._queue; ++ let targetQueueMap = this.targetQueues.get(target); ++ if (targetQueueMap !== undefined) { ++ targetQueueMap.delete(method); ++ } ++ let index = findItem(target, method, queue); ++ if (index > -1) { ++ queue[index + 1] = null; ++ return true; ++ } ++ // if not found in current queue ++ // could be in the queue that is being flushed ++ queue = this._queueBeingFlushed; ++ index = findItem(target, method, queue); ++ if (index > -1) { ++ queue[index + 1] = null; ++ return true; ++ } ++ return false; + } +- oneWay() { +- descriptorForDecorator(this).oneWay(); +- return this; ++ push(target, method, args, stack) { ++ this._queue.push(target, method, args, stack); ++ return { ++ queue: this, ++ target, ++ method ++ }; + } +- meta(meta) { +- let prop = descriptorForDecorator(this); +- if (arguments.length === 0) { +- return prop._meta || {}; ++ pushUnique(target, method, args, stack) { ++ let localQueueMap = this.targetQueues.get(target); ++ if (localQueueMap === undefined) { ++ localQueueMap = new Map(); ++ this.targetQueues.set(target, localQueueMap); ++ } ++ let index = localQueueMap.get(method); ++ if (index === undefined) { ++ let queueIndex = this._queue.push(target, method, args, stack) - QUEUE_ITEM_LENGTH; ++ localQueueMap.set(method, queueIndex); + } else { +- prop._meta = meta; ++ let queue = this._queue; ++ queue[index + 2] = args; // replace args ++ queue[index + 3] = stack; // replace stack + } ++ return { ++ queue: this, ++ target, ++ method ++ }; + } +- } +- class AliasedProperty extends ComputedDescriptor { +- altKey; +- constructor(altKey) { +- super(); +- this.altKey = altKey; +- } +- setup(obj, keyName, propertyDesc, meta) { +- (!(this.altKey !== keyName) && assert$1(`Setting alias '${keyName}' on self`, this.altKey !== keyName)); +- super.setup(obj, keyName, propertyDesc, meta); +- CHAIN_PASS_THROUGH.add(this); ++ _getDebugInfo(debugEnabled) { ++ if (debugEnabled) { ++ let debugInfo = getQueueItems(this._queue, QUEUE_ITEM_LENGTH); ++ return debugInfo; ++ } ++ return undefined; + } +- get(obj, keyName) { +- let ret; +- let meta$1 = meta(obj); +- let tagMeta = tagMetaFor(obj); +- let propertyTag = tagFor(obj, keyName, tagMeta); +- +- // We don't use the tag since CPs are not automatic, we just want to avoid +- // anything tracking while we get the altKey +- untrack(() => { +- ret = get$2(obj, this.altKey); +- }); +- let lastRevision = meta$1.revisionFor(keyName); +- if (lastRevision === undefined || !validateTag(propertyTag, lastRevision)) { +- UPDATE_TAG(propertyTag, getChainTagsForKey(obj, this.altKey, tagMeta, meta$1)); +- meta$1.setRevisionFor(keyName, valueForTag(propertyTag)); +- finishLazyChains(meta$1, keyName, ret); ++ invoke(target, method, args /*, onError, errorRecordedForStack */) { ++ if (args === undefined) { ++ method.call(target); ++ } else { ++ method.apply(target, args); + } +- consumeTag(propertyTag); +- return ret; + } +- set(obj, _keyName, value) { +- return set(obj, this.altKey, value); ++ invokeWithOnError(target, method, args, onError, errorRecordedForStack) { ++ try { ++ if (args === undefined) { ++ method.call(target); ++ } else { ++ method.apply(target, args); ++ } ++ } catch (error) { ++ onError(error, errorRecordedForStack); ++ } + } +- readOnly() { +- this.set = AliasedProperty_readOnlySet; ++ } ++ class DeferredActionQueues { ++ constructor(queueNames = [], options) { ++ this.queues = {}; ++ this.queueNameIndex = 0; ++ this.queueNames = queueNames; ++ queueNames.reduce(function (queues, queueName) { ++ queues[queueName] = new Queue(queueName, options[queueName], options); ++ return queues; ++ }, this.queues); + } +- oneWay() { +- this.set = AliasedProperty_oneWaySet; ++ /** ++ * @method schedule ++ * @param {String} queueName ++ * @param {Any} target ++ * @param {Any} method ++ * @param {Any} args ++ * @param {Boolean} onceFlag ++ * @param {Any} stack ++ * @return queue ++ */ ++ schedule(queueName, target, method, args, onceFlag, stack) { ++ let queues = this.queues; ++ let queue = queues[queueName]; ++ if (queue === undefined) { ++ throw new Error(`You attempted to schedule an action in a queue (${queueName}) that doesn\'t exist`); ++ } ++ if (method === undefined || method === null) { ++ throw new Error(`You attempted to schedule an action in a queue (${queueName}) for a method that doesn\'t exist`); ++ } ++ this.queueNameIndex = 0; ++ if (onceFlag) { ++ return queue.pushUnique(target, method, args, stack); ++ } else { ++ return queue.push(target, method, args, stack); ++ } + } +- } +- function AliasedProperty_readOnlySet(obj, keyName) { +- throw new Error(`Cannot set read-only property '${keyName}' on object: ${inspect(obj)}`); +- } +- function AliasedProperty_oneWaySet(obj, keyName, value) { +- defineProperty(obj, keyName, null); +- return set(obj, keyName, value); +- } +- +- /** +- @module ember +- */ +- +- +- /** +- Used internally to allow changing properties in a backwards compatible way, and print a helpful +- deprecation warning. +- +- @method deprecateProperty +- @param {Object} object The object to add the deprecated property to. +- @param {String} deprecatedKey The property to add (and print deprecation warnings upon accessing). +- @param {String} newKey The property that will be aliased. +- @private +- @since 1.7.0 +- */ +- +- function deprecateProperty(object, deprecatedKey, newKey, options) { +- function _deprecate() { +- (deprecate$1(`Usage of \`${deprecatedKey}\` is deprecated, use \`${newKey}\` instead.`, false, options)); ++ /** ++ * DeferredActionQueues.flush() calls Queue.flush() ++ * ++ * @method flush ++ * @param {Boolean} fromAutorun ++ */ ++ flush(fromAutorun = false) { ++ let queue; ++ let queueName; ++ let numberOfQueues = this.queueNames.length; ++ while (this.queueNameIndex < numberOfQueues) { ++ queueName = this.queueNames[this.queueNameIndex]; ++ queue = this.queues[queueName]; ++ if (queue.hasWork() === false) { ++ this.queueNameIndex++; ++ if (fromAutorun && this.queueNameIndex < numberOfQueues) { ++ return 1 /* Pause */; ++ } ++ } else { ++ if (queue.flush(false /* async */) === 1 /* Pause */) { ++ return 1 /* Pause */; ++ } ++ } ++ } + } +- Object.defineProperty(object, deprecatedKey, { +- configurable: true, +- enumerable: false, +- set(value) { +- _deprecate(); +- set(this, newKey, value); +- }, +- get() { +- _deprecate(); +- return get$2(this, newKey); ++ /** ++ * Returns debug information for the current queues. ++ * ++ * @method _getDebugInfo ++ * @param {Boolean} debugEnabled ++ * @returns {IDebugInfo | undefined} ++ */ ++ _getDebugInfo(debugEnabled) { ++ if (debugEnabled) { ++ let debugInfo = {}; ++ let queue; ++ let queueName; ++ let numberOfQueues = this.queueNames.length; ++ let i = 0; ++ while (i < numberOfQueues) { ++ queueName = this.queueNames[i]; ++ queue = this.queues[queueName]; ++ debugInfo[queueName] = queue._getDebugInfo(debugEnabled); ++ i++; ++ } ++ return debugInfo; + } +- }); +- } +- +- const EACH_PROXIES = new WeakMap(); +- function eachProxyArrayWillChange(array, idx, removedCnt, addedCnt) { +- let eachProxy = EACH_PROXIES.get(array); +- if (eachProxy !== undefined) { +- eachProxy.arrayWillChange(array, idx, removedCnt, addedCnt); ++ return; + } + } +- function eachProxyArrayDidChange(array, idx, removedCnt, addedCnt) { +- let eachProxy = EACH_PROXIES.get(array); +- if (eachProxy !== undefined) { +- eachProxy.arrayDidChange(array, idx, removedCnt, addedCnt); ++ function iteratorDrain(fn) { ++ let iterator = fn(); ++ let result = iterator.next(); ++ while (result.done === false) { ++ result.value(); ++ result = iterator.next(); + } + } +- +- /** +- @module ember +- */ +- /** +- Helper class that allows you to register your library with Ember. +- +- Singleton created at `Ember.libraries`. +- +- @class Libraries +- @constructor +- @private +- */ +- class Libraries { +- _registry; +- _coreLibIndex; +- constructor() { +- this._registry = []; +- this._coreLibIndex = 0; +- } +- _getLibraryByName(name) { +- let libs = this._registry; +- for (let lib of libs) { +- if (lib.name === name) { +- return lib; +- } ++ const noop$1 = function () {}; ++ const DISABLE_SCHEDULE = Object.freeze([]); ++ function parseArgs() { ++ let length = arguments.length; ++ let args; ++ let method; ++ let target; ++ if (length === 0) ;else if (length === 1) { ++ target = null; ++ method = arguments[0]; ++ } else { ++ let argsIndex = 2; ++ let methodOrTarget = arguments[0]; ++ let methodOrArgs = arguments[1]; ++ let type = typeof methodOrArgs; ++ if (type === 'function') { ++ target = methodOrTarget; ++ method = methodOrArgs; ++ } else if (methodOrTarget !== null && type === 'string' && methodOrArgs in methodOrTarget) { ++ target = methodOrTarget; ++ method = target[methodOrArgs]; ++ } else if (typeof methodOrTarget === 'function') { ++ argsIndex = 1; ++ target = null; ++ method = methodOrTarget; + } +- return undefined; +- } +- register(name, version, isCoreLibrary) { +- let index = this._registry.length; +- if (!this._getLibraryByName(name)) { +- if (isCoreLibrary) { +- index = this._coreLibIndex++; ++ if (length > argsIndex) { ++ let len = length - argsIndex; ++ args = new Array(len); ++ for (let i = 0; i < len; i++) { ++ args[i] = arguments[i + argsIndex]; + } +- this._registry.splice(index, 0, { +- name, +- version +- }); +- } else { +- (warn(`Library "${name}" is already registered with Ember.`, false, { +- id: 'ember-metal.libraries-register' +- })); + } + } +- registerCoreLibrary(name, version) { +- this.register(name, version, true); +- } +- deRegister(name) { +- let lib = this._getLibraryByName(name); +- let index; +- if (lib) { +- index = this._registry.indexOf(lib); +- this._registry.splice(index, 1); ++ return [target, method, args]; ++ } ++ function parseTimerArgs() { ++ let [target, method, args] = parseArgs(...arguments); ++ let wait = 0; ++ let length = args !== undefined ? args.length : 0; ++ if (length > 0) { ++ let last = args[length - 1]; ++ if (isCoercableNumber(last)) { ++ wait = parseInt(args.pop(), 10); + } + } +- isRegistered; +- logVersions; ++ return [target, method, args, wait]; + } +- { +- Libraries.prototype.logVersions = function () { +- let libs = this._registry; +- let nameLengths = libs.map(item => get$2(item, 'name.length')); +- (!(nameLengths instanceof Array && nameLengths.every(n => typeof n === 'number')) && assert$1('nameLengths is number array', nameLengths instanceof Array && nameLengths.every(n => typeof n === 'number'))); +- let maxNameLength = Math.max.apply(null, nameLengths); +- debug$2('-------------------------------'); +- for (let lib of libs) { +- let spaces = new Array(maxNameLength - lib.name.length + 1).join(' '); +- debug$2([lib.name, spaces, ' : ', lib.version].join('')); +- } +- debug$2('-------------------------------'); +- }; +- } +- const LIBRARIES = new Libraries(); +- LIBRARIES.registerCoreLibrary('Ember', Version); +- +- /** +- @module @ember/object +- */ +- +- /** +- To get multiple properties at once, call `getProperties` +- with an object followed by a list of strings or an array: +- +- ```javascript +- import { getProperties } from '@ember/object'; +- +- getProperties(record, 'firstName', 'lastName', 'zipCode'); +- // { firstName: 'John', lastName: 'Doe', zipCode: '10011' } +- ``` +- +- is equivalent to: +- +- ```javascript +- import { getProperties } from '@ember/object'; +- +- getProperties(record, ['firstName', 'lastName', 'zipCode']); +- // { firstName: 'John', lastName: 'Doe', zipCode: '10011' } +- ``` +- +- @method getProperties +- @static +- @for @ember/object +- @param {Object} obj +- @param {String...|Array} list of keys to get +- @return {Object} +- @public +- */ +- +- function getProperties(obj, keys) { +- let ret = {}; +- let propertyNames; +- let i = 1; +- if (arguments.length === 2 && Array.isArray(keys)) { +- i = 0; +- propertyNames = arguments[1]; ++ function parseDebounceArgs() { ++ let target; ++ let method; ++ let isImmediate; ++ let args; ++ let wait; ++ if (arguments.length === 2) { ++ method = arguments[0]; ++ wait = arguments[1]; ++ target = null; + } else { +- propertyNames = Array.from(arguments); +- } +- for (; i < propertyNames.length; i++) { +- // SAFETY: we are just walking the list of property names, so we know the +- // index access never produces `undefined`. +- let name = propertyNames[i]; +- ret[name] = get$2(obj, name); ++ [target, method, args] = parseArgs(...arguments); ++ if (args === undefined) { ++ wait = 0; ++ } else { ++ wait = args.pop(); ++ if (!isCoercableNumber(wait)) { ++ isImmediate = wait === true; ++ wait = args.pop(); ++ } ++ } + } +- return ret; ++ wait = parseInt(wait, 10); ++ return [target, method, args, wait, isImmediate]; + } +- +- /** +- @module @ember/object +- */ +- /** +- Set a list of properties on an object. These properties are set inside +- a single `beginPropertyChanges` and `endPropertyChanges` batch, so +- observers will be buffered. +- +- ```javascript +- import EmberObject from '@ember/object'; +- let anObject = EmberObject.create(); +- +- anObject.setProperties({ +- firstName: 'Stanley', +- lastName: 'Stuart', +- age: 21 +- }); +- ``` +- +- @method setProperties +- @static +- @for @ember/object +- @param obj +- @param {Object} properties +- @return properties +- @public +- */ +- +- function setProperties(obj, properties) { +- if (properties === null || typeof properties !== 'object') { +- return properties; +- } +- changeProperties(() => { +- let props = Object.keys(properties); +- for (let propertyName of props) { +- // SAFETY: casting `properties` this way is safe because any object in JS +- // can be indexed this way, and the result will be `unknown`, making it +- // safe for callers. +- set(obj, propertyName, properties[propertyName]); ++ let UUID = 0; ++ let beginCount = 0; ++ let endCount = 0; ++ let beginEventCount = 0; ++ let endEventCount = 0; ++ let runCount = 0; ++ let joinCount = 0; ++ let deferCount = 0; ++ let scheduleCount = 0; ++ let scheduleIterableCount = 0; ++ let deferOnceCount = 0; ++ let scheduleOnceCount = 0; ++ let setTimeoutCount = 0; ++ let laterCount = 0; ++ let throttleCount = 0; ++ let debounceCount = 0; ++ let cancelTimersCount = 0; ++ let cancelCount = 0; ++ let autorunsCreatedCount = 0; ++ let autorunsCompletedCount = 0; ++ let deferredActionQueuesCreatedCount = 0; ++ let nestedDeferredActionQueuesCreated = 0; ++ class Backburner { ++ constructor(queueNames, options) { ++ this.DEBUG = false; ++ this.currentInstance = null; ++ this.instanceStack = []; ++ this._eventCallbacks = { ++ end: [], ++ begin: [] ++ }; ++ this._timerTimeoutId = null; ++ this._timers = []; ++ this._autorun = false; ++ this._autorunStack = null; ++ this.queueNames = queueNames; ++ this.options = options || {}; ++ if (typeof this.options.defaultQueue === 'string') { ++ this._defaultQueue = this.options.defaultQueue; ++ } else { ++ this._defaultQueue = this.queueNames[0]; + } +- }); +- return properties; +- } +- +- let DEBUG_INJECTION_FUNCTIONS; +- { +- DEBUG_INJECTION_FUNCTIONS = new WeakMap(); +- } +- +- /** +- @module ember +- @private +- */ +- +- /** +- Read-only property that returns the result of a container lookup. +- +- @class InjectedProperty +- @namespace Ember +- @constructor +- @param {String} type The container type the property will lookup +- @param {String} nameOrDesc (optional) The name the property will lookup, defaults +- to the property's name +- @private +- */ +- // Decorator factory (with args) +- // (Also matches non-decorator form, types may be incorrect for this.) +- +- // Non-decorator +- +- // Decorator (without args) +- +- // Catch-all for service and controller injections +- +- function inject$2(type, ...args) { +- (!(typeof type === 'string') && assert$1('a string type must be provided to inject', typeof type === 'string')); +- let elementDescriptor; +- let name; +- if (isElementDescriptor(args)) { +- elementDescriptor = args; +- } else if (typeof args[0] === 'string') { +- name = args[0]; ++ this._onBegin = this.options.onBegin || noop$1; ++ this._onEnd = this.options.onEnd || noop$1; ++ this._boundRunExpiredTimers = this._runExpiredTimers.bind(this); ++ this._boundAutorunEnd = () => { ++ autorunsCompletedCount++; ++ // if the autorun was already flushed, do nothing ++ if (this._autorun === false) { ++ return; ++ } ++ this._autorun = false; ++ this._autorunStack = null; ++ this._end(true /* fromAutorun */); ++ }; ++ let builder = this.options._buildPlatform || buildPlatform; ++ this._platform = builder(this._boundAutorunEnd); + } +- let getInjection = function (propertyName) { +- let owner = getOwner$2(this) || this.container; // fallback to `container` for backwards compat +- (!(Boolean(owner)) && assert$1(`Attempting to lookup an injected property on an object without a container, ensure that the object was instantiated via a container.`, Boolean(owner))); +- return owner.lookup(`${type}:${name || propertyName}`); +- }; +- { +- DEBUG_INJECTION_FUNCTIONS.set(getInjection, { +- type, +- name +- }); ++ get counters() { ++ return { ++ begin: beginCount, ++ end: endCount, ++ events: { ++ begin: beginEventCount, ++ end: endEventCount ++ }, ++ autoruns: { ++ created: autorunsCreatedCount, ++ completed: autorunsCompletedCount ++ }, ++ run: runCount, ++ join: joinCount, ++ defer: deferCount, ++ schedule: scheduleCount, ++ scheduleIterable: scheduleIterableCount, ++ deferOnce: deferOnceCount, ++ scheduleOnce: scheduleOnceCount, ++ setTimeout: setTimeoutCount, ++ later: laterCount, ++ throttle: throttleCount, ++ debounce: debounceCount, ++ cancelTimers: cancelTimersCount, ++ cancel: cancelCount, ++ loops: { ++ total: deferredActionQueuesCreatedCount, ++ nested: nestedDeferredActionQueuesCreated ++ } ++ }; + } +- let decorator = computed({ +- get: getInjection, +- set(keyName, value) { +- defineProperty(this, keyName, null, value); ++ get defaultQueue() { ++ return this._defaultQueue; ++ } ++ /* ++ @method begin ++ @return instantiated class DeferredActionQueues ++ */ ++ begin() { ++ beginCount++; ++ let options = this.options; ++ let previousInstance = this.currentInstance; ++ let current; ++ if (this._autorun !== false) { ++ current = previousInstance; ++ this._cancelAutorun(); ++ } else { ++ if (previousInstance !== null) { ++ nestedDeferredActionQueuesCreated++; ++ this.instanceStack.push(previousInstance); ++ } ++ deferredActionQueuesCreatedCount++; ++ current = this.currentInstance = new DeferredActionQueues(this.queueNames, options); ++ beginEventCount++; ++ this._trigger('begin', current, previousInstance); + } +- }); +- if (elementDescriptor) { +- return decorator(elementDescriptor[0], elementDescriptor[1], elementDescriptor[2]); +- } else { +- return decorator; ++ this._onBegin(current, previousInstance); ++ return current; + } +- } +- +- /** +- @decorator +- @private +- +- Marks a property as tracked. +- +- By default, a component's properties are expected to be static, +- meaning you are not able to update them and have the template update accordingly. +- Marking a property as tracked means that when that property changes, +- a rerender of the component is scheduled so the template is kept up to date. +- +- There are two usages for the `@tracked` decorator, shown below. +- +- @example No dependencies +- +- If you don't pass an argument to `@tracked`, only changes to that property +- will be tracked: +- +- ```typescript +- import Component from '@glimmer/component'; +- import { tracked } from '@glimmer/tracking'; +- +- export default class MyComponent extends Component { +- @tracked +- remainingApples = 10 ++ end() { ++ endCount++; ++ this._end(false); + } +- ``` +- +- When something changes the component's `remainingApples` property, the rerender +- will be scheduled. +- +- @example Dependents +- +- In the case that you have a computed property that depends other +- properties, you want to track both so that when one of the +- dependents change, a rerender is scheduled. +- +- In the following example we have two properties, +- `eatenApples`, and `remainingApples`. +- +- ```typescript +- import Component from '@glimmer/component'; +- import { tracked } from '@glimmer/tracking'; +- +- const totalApples = 100; +- +- export default class MyComponent extends Component { +- @tracked +- eatenApples = 0 +- +- get remainingApples() { +- return totalApples - this.eatenApples; ++ on(eventName, callback) { ++ if (typeof callback !== 'function') { ++ throw new TypeError(`Callback must be a function`); + } +- +- increment() { +- this.eatenApples = this.eatenApples + 1; ++ let callbacks = this._eventCallbacks[eventName]; ++ if (callbacks !== undefined) { ++ callbacks.push(callback); ++ } else { ++ throw new TypeError(`Cannot on() event ${eventName} because it does not exist`); + } + } +- ``` +- +- @param dependencies Optional dependents to be tracked. +- */ +- +- function tracked(...args) { +- (!(!(isElementDescriptor(args.slice(0, 3)) && args.length === 5 && args[4] === true)) && assert$1(`@tracked can only be used directly as a native decorator. If you're using tracked in classic classes, add parenthesis to call it like a function: tracked()`, !(isElementDescriptor(args.slice(0, 3)) && args.length === 5 && args[4] === true))); +- if (!isElementDescriptor(args)) { +- let propertyDesc = args[0]; +- (!(args.length === 0 || typeof propertyDesc === 'object' && propertyDesc !== null) && assert$1(`tracked() may only receive an options object containing 'value' or 'initializer', received ${propertyDesc}`, args.length === 0 || typeof propertyDesc === 'object' && propertyDesc !== null)); +- if (propertyDesc) { +- let keys = Object.keys(propertyDesc); +- (!(keys.length <= 1 && (keys[0] === undefined || keys[0] === 'value' || keys[0] === 'initializer')) && assert$1(`The options object passed to tracked() may only contain a 'value' or 'initializer' property, not both. Received: [${keys}]`, keys.length <= 1 && (keys[0] === undefined || keys[0] === 'value' || keys[0] === 'initializer'))); +- (!(!('initializer' in propertyDesc) || typeof propertyDesc.initializer === 'function') && assert$1(`The initializer passed to tracked must be a function. Received ${propertyDesc.initializer}`, !('initializer' in propertyDesc) || typeof propertyDesc.initializer === 'function')); ++ off(eventName, callback) { ++ let callbacks = this._eventCallbacks[eventName]; ++ if (!eventName || callbacks === undefined) { ++ throw new TypeError(`Cannot off() event ${eventName} because it does not exist`); + } +- let initializer = propertyDesc ? propertyDesc.initializer : undefined; +- let value = propertyDesc ? propertyDesc.value : undefined; +- let decorator = function (target, key, _desc, _meta, isClassicDecorator) { +- (!(isClassicDecorator) && assert$1(`You attempted to set a default value for ${key} with the @tracked({ value: 'default' }) syntax. You can only use this syntax with classic classes. For native classes, you can use class initializers: @tracked field = 'default';`, isClassicDecorator)); +- let fieldDesc = { +- initializer: initializer || (() => value) +- }; +- return descriptorForField([target, key, fieldDesc]); +- }; +- setClassicDecorator(decorator); +- return decorator; +- } +- return descriptorForField(args); +- } +- { +- // Normally this isn't a classic decorator, but we want to throw a helpful +- // error in development so we need it to treat it like one +- setClassicDecorator(tracked); +- } +- function descriptorForField([target, key, desc]) { +- (!(!desc || !desc.value && !desc.get && !desc.set) && assert$1(`You attempted to use @tracked on ${key}, but that element is not a class field. @tracked is only usable on class fields. Native getters and setters will autotrack add any tracked fields they encounter, so there is no need mark getters and setters with @tracked.`, !desc || !desc.value && !desc.get && !desc.set)); +- let { +- getter, +- setter +- } = trackedData(key, desc ? desc.initializer : undefined); +- function get() { +- let value = getter(this); +- +- // Add the tag of the returned value if it is an array, since arrays +- // should always cause updates if they are consumed and then changed +- if (Array.isArray(value) || isEmberArray(value)) { +- consumeTag(tagFor(value, '[]')); ++ let callbackFound = false; ++ if (callback) { ++ for (let i = 0; i < callbacks.length; i++) { ++ if (callbacks[i] === callback) { ++ callbackFound = true; ++ callbacks.splice(i, 1); ++ i--; ++ } ++ } ++ } ++ if (!callbackFound) { ++ throw new TypeError(`Cannot off() callback that does not exist`); + } +- return value; + } +- function set(newValue) { +- setter(this, newValue); +- dirtyTagFor(this, SELF_TAG); ++ run() { ++ runCount++; ++ let [target, method, args] = parseArgs(...arguments); ++ return this._run(target, method, args); + } +- let newDesc = { +- enumerable: true, +- configurable: true, +- isTracked: true, +- get, +- set +- }; +- COMPUTED_SETTERS.add(set); +- meta(target).writeDescriptors(key, new TrackedDescriptor(get, set)); +- return newDesc; +- } +- class TrackedDescriptor { +- constructor(_get, _set) { +- this._get = _get; +- this._set = _set; +- CHAIN_PASS_THROUGH.add(this); ++ join() { ++ joinCount++; ++ let [target, method, args] = parseArgs(...arguments); ++ return this._join(target, method, args); + } +- get(obj) { +- return this._get.call(obj); ++ /** ++ * @deprecated please use schedule instead. ++ */ ++ defer(queueName, target, method, ...args) { ++ deferCount++; ++ return this.schedule(queueName, target, method, ...args); + } +- set(obj, _key, value) { +- this._set.call(obj, value); ++ schedule(queueName, ..._args) { ++ scheduleCount++; ++ let [target, method, args] = parseArgs(..._args); ++ let stack = this.DEBUG ? new Error() : undefined; ++ return this._ensureInstance().schedule(queueName, target, method, args, false, stack); + } +- } +- +- // NOTE: copied from: https://github.com/glimmerjs/glimmer.js/pull/358 +- // Both glimmerjs/glimmer.js and emberjs/ember.js have the exact same implementation +- // of @cached, so any changes made to one should also be made to the other +- +- +- /** +- * @decorator +- * +- Gives the getter a caching behavior. The return value of the getter +- will be cached until any of the properties it is entangled with +- are invalidated. This is useful when a getter is expensive and +- used very often. +- +- For instance, in this `GuestList` class, we have the `sortedGuests` +- getter that sorts the guests alphabetically: +- +- ```javascript +- import { tracked } from '@glimmer/tracking'; +- +- class GuestList { +- @tracked guests = ['Zoey', 'Tomster']; +- +- get sortedGuests() { +- return this.guests.slice().sort() +- } +- } +- ``` +- +- Every time `sortedGuests` is accessed, a new array will be created and sorted, +- because JavaScript getters do not cache by default. When the guest list +- is small, like the one in the example, this is not a problem. However, if +- the guest list were to grow very large, it would mean that we would be doing +- a large amount of work each time we accessed `sortedGuests`. With `@cached`, +- we can cache the value instead: +- +- ```javascript +- import { tracked, cached } from '@glimmer/tracking'; +- +- class GuestList { +- @tracked guests = ['Zoey', 'Tomster']; +- +- @cached +- get sortedGuests() { +- return this.guests.slice().sort() +- } +- } +- ``` +- +- Now the `sortedGuests` getter will be cached based on autotracking. +- It will only rerun and create a new sorted array when the guests tracked +- property is updated. +- +- +- ### Tradeoffs +- +- Overuse is discouraged. +- +- In general, you should avoid using `@cached` unless you have confirmed that +- the getter you are decorating is computationally expensive, since `@cached` +- adds a small amount of overhead to the getter. +- While the individual costs are small, a systematic use of the `@cached` +- decorator can add up to a large impact overall in your app. +- Many getters and tracked properties are only accessed once during rendering, +- and then never rerendered, so adding `@cached` when unnecessary can +- negatively impact performance. +- +- Also, `@cached` may rerun even if the values themselves have not changed, +- since tracked properties will always invalidate. +- For example updating an integer value from `5` to an other `5` will trigger +- a rerun of the cached properties building from this integer. +- +- Avoiding a cache invalidation in this case is not something that can +- be achieved on the `@cached` decorator itself, but rather when updating +- the underlying tracked values, by applying some diff checking mechanisms: +- +- ```javascript +- if (nextValue !== this.trackedProp) { +- this.trackedProp = nextValue; ++ /* ++ Defer the passed iterable of functions to run inside the specified queue. ++ @method scheduleIterable ++ @param {String} queueName ++ @param {Iterable} an iterable of functions to execute ++ @return method result ++ */ ++ scheduleIterable(queueName, iterable) { ++ scheduleIterableCount++; ++ let stack = this.DEBUG ? new Error() : undefined; ++ return this._ensureInstance().schedule(queueName, null, iteratorDrain, [iterable], false, stack); + } +- ``` +- +- Here equal values won't update the property, therefore not triggering +- the subsequent cache invalidations of the `@cached` properties who were +- using this `trackedProp`. +- +- Remember that setting tracked data should only be done during initialization, +- or as the result of a user action. Setting tracked data during render +- (such as in a getter), is not supported. +- +- @method cached +- @static +- @for @glimmer/tracking +- @public +- */ +- const cached = (...args) => { +- const [target, key, descriptor] = args; +- +- // Error on `@cached()`, `@cached(...args)`, and `@cached propName = value;` +- if (target === undefined) throwCachedExtraneousParens(); +- if ((typeof target !== 'object' || typeof key !== 'string' || typeof descriptor !== 'object' || args.length !== 3)) { +- throwCachedInvalidArgsError(args); ++ /** ++ * @deprecated please use scheduleOnce instead. ++ */ ++ deferOnce(queueName, target, method, ...args) { ++ deferOnceCount++; ++ return this.scheduleOnce(queueName, target, method, ...args); + } +- if ((!('get' in descriptor) || typeof descriptor.get !== 'function')) { +- throwCachedGetterOnlyError(key); ++ scheduleOnce(queueName, ..._args) { ++ scheduleOnceCount++; ++ let [target, method, args] = parseArgs(..._args); ++ let stack = this.DEBUG ? new Error() : undefined; ++ return this._ensureInstance().schedule(queueName, target, method, args, true, stack); + } +- const caches = new WeakMap(); +- const getter = descriptor.get; +- descriptor.get = function () { +- if (!caches.has(this)) { +- caches.set(this, createCache(getter.bind(this))); ++ setTimeout() { ++ setTimeoutCount++; ++ return this.later(...arguments); ++ } ++ later() { ++ laterCount++; ++ let [target, method, args, wait] = parseTimerArgs(...arguments); ++ return this._later(target, method, args, wait); ++ } ++ throttle() { ++ throttleCount++; ++ let [target, method, args, wait, isImmediate = true] = parseDebounceArgs(...arguments); ++ let index = findTimerItem(target, method, this._timers); ++ let timerId; ++ if (index === -1) { ++ timerId = this._later(target, method, isImmediate ? DISABLE_SCHEDULE : args, wait); ++ if (isImmediate) { ++ this._join(target, method, args); ++ } ++ } else { ++ timerId = this._timers[index + 1]; ++ let argIndex = index + 4; ++ if (this._timers[argIndex] !== DISABLE_SCHEDULE) { ++ this._timers[argIndex] = args; ++ } + } +- return getValue(caches.get(this)); +- }; +- }; +- function throwCachedExtraneousParens() { +- throw new Error('You attempted to use @cached(), which is not necessary nor supported. Remove the parentheses and you will be good to go!'); +- } +- function throwCachedGetterOnlyError(key) { +- throw new Error(`The @cached decorator must be applied to getters. '${key}' is not a getter.`); +- } +- function throwCachedInvalidArgsError(args = []) { +- throw new Error(`You attempted to use @cached on with ${args.length > 1 ? 'arguments' : 'an argument'} ( @cached(${args.map(d => `'${d}'`).join(', ')}), which is not supported. Dependencies are automatically tracked, so you can just use ${'`@cached`'}`); +- } +- +- const hasOwnProperty$2 = Object.prototype.hasOwnProperty; +- let searchDisabled = false; +- const flags = { +- _set: 0, +- _unprocessedNamespaces: false, +- get unprocessedNamespaces() { +- return this._unprocessedNamespaces; +- }, +- set unprocessedNamespaces(v) { +- this._set++; +- this._unprocessedNamespaces = v; ++ return timerId; + } +- }; +- let unprocessedMixins = false; +- const NAMESPACES = []; +- const NAMESPACES_BY_ID = Object.create(null); +- function addNamespace(namespace) { +- flags.unprocessedNamespaces = true; +- NAMESPACES.push(namespace); +- } +- function removeNamespace(namespace) { +- let name = getName(namespace); +- delete NAMESPACES_BY_ID[name]; +- NAMESPACES.splice(NAMESPACES.indexOf(namespace), 1); +- if (name in context$1.lookup && namespace === context$1.lookup[name]) { +- context$1.lookup[name] = undefined; ++ debounce() { ++ debounceCount++; ++ let [target, method, args, wait, isImmediate = false] = parseDebounceArgs(...arguments); ++ let _timers = this._timers; ++ let index = findTimerItem(target, method, _timers); ++ let timerId; ++ if (index === -1) { ++ timerId = this._later(target, method, isImmediate ? DISABLE_SCHEDULE : args, wait); ++ if (isImmediate) { ++ this._join(target, method, args); ++ } ++ } else { ++ let executeAt = this._platform.now() + wait; ++ let argIndex = index + 4; ++ if (_timers[argIndex] === DISABLE_SCHEDULE) { ++ args = DISABLE_SCHEDULE; ++ } ++ timerId = _timers[index + 1]; ++ let i = binarySearch(executeAt, _timers); ++ if (index + TIMERS_OFFSET === i) { ++ _timers[index] = executeAt; ++ _timers[argIndex] = args; ++ } else { ++ let stack = this._timers[index + 5]; ++ this._timers.splice(i, 0, executeAt, timerId, target, method, args, stack); ++ this._timers.splice(index, TIMERS_OFFSET); ++ } ++ if (index === 0) { ++ this._reinstallTimerTimeout(); ++ } ++ } ++ return timerId; + } +- } +- function findNamespaces() { +- if (!flags.unprocessedNamespaces) { +- return; ++ cancelTimers() { ++ cancelTimersCount++; ++ this._clearTimerTimeout(); ++ this._timers = []; ++ this._cancelAutorun(); + } +- let lookup = context$1.lookup; +- let keys = Object.keys(lookup); +- for (let key of keys) { +- // Only process entities that start with uppercase A-Z +- if (!isUppercase(key.charCodeAt(0))) { +- continue; ++ hasTimers() { ++ return this._timers.length > 0 || this._autorun; ++ } ++ cancel(timer) { ++ cancelCount++; ++ if (timer === null || timer === undefined) { ++ return false; + } +- let obj = tryIsNamespace(lookup, key); +- if (obj) { +- setName(obj, key); ++ let timerType = typeof timer; ++ if (timerType === 'number') { ++ // we're cancelling a setTimeout or throttle or debounce ++ return this._cancelLaterTimer(timer); ++ } else if (timerType === 'object' && timer.queue && timer.method) { ++ // we're cancelling a deferOnce ++ return timer.queue.cancel(timer); + } ++ return false; + } +- } +- function findNamespace(name) { +- if (!searchDisabled) { +- processAllNamespaces(); +- } +- return NAMESPACES_BY_ID[name]; +- } +- function processNamespace(namespace) { +- _processNamespace([namespace.toString()], namespace, new Set()); +- } +- function processAllNamespaces() { +- let unprocessedNamespaces = flags.unprocessedNamespaces; +- if (unprocessedNamespaces) { +- findNamespaces(); +- flags.unprocessedNamespaces = false; ++ ensureInstance() { ++ this._ensureInstance(); + } +- if (unprocessedNamespaces || unprocessedMixins) { +- let namespaces = NAMESPACES; +- for (let namespace of namespaces) { +- processNamespace(namespace); ++ /** ++ * Returns debug information related to the current instance of Backburner ++ * ++ * @method getDebugInfo ++ * @returns {Object | undefined} Will return and Object containing debug information if ++ * the DEBUG flag is set to true on the current instance of Backburner, else undefined. ++ */ ++ getDebugInfo() { ++ if (this.DEBUG) { ++ return { ++ autorun: this._autorunStack, ++ counters: this.counters, ++ timers: getQueueItems(this._timers, TIMERS_OFFSET, 2), ++ instanceStack: [this.currentInstance, ...this.instanceStack].map(deferredActionQueue => deferredActionQueue && deferredActionQueue._getDebugInfo(this.DEBUG)) ++ }; + } +- unprocessedMixins = false; ++ return undefined; + } +- } +- function isSearchDisabled() { +- return searchDisabled; +- } +- function setSearchDisabled(flag) { +- searchDisabled = Boolean(flag); +- } +- function setUnprocessedMixins() { +- unprocessedMixins = true; +- } +- function _processNamespace(paths, root, seen) { +- let idx = paths.length; +- let id = paths.join('.'); +- NAMESPACES_BY_ID[id] = root; +- setName(root, id); +- +- // Loop over all of the keys in the namespace, looking for classes +- for (let key in root) { +- if (!hasOwnProperty$2.call(root, key)) { +- continue; ++ _end(fromAutorun) { ++ let currentInstance = this.currentInstance; ++ let nextInstance = null; ++ if (currentInstance === null) { ++ throw new Error(`end called without begin`); + } +- let obj = root[key]; +- +- // If we are processing the `Ember` namespace, for example, the +- // `paths` will start with `["Ember"]`. Every iteration through +- // the loop will update the **second** element of this list with +- // the key, so processing `Ember.View` will make the Array +- // `['Ember', 'View']`. +- paths[idx] = key; +- +- // If we have found an unprocessed class +- if (obj && getName(obj) === void 0) { +- // Replace the class' `toString` with the dot-separated path +- setName(obj, paths.join('.')); +- // Support nested namespaces +- } else if (obj && isNamespace(obj)) { +- // Skip aliased namespaces +- if (seen.has(obj)) { +- continue; ++ // Prevent double-finally bug in Safari 6.0.2 and iOS 6 ++ // This bug appears to be resolved in Safari 6.0.5 and iOS 7 ++ let finallyAlreadyCalled = false; ++ let result; ++ try { ++ result = currentInstance.flush(fromAutorun); ++ } finally { ++ if (!finallyAlreadyCalled) { ++ finallyAlreadyCalled = true; ++ if (result === 1 /* Pause */) { ++ const plannedNextQueue = this.queueNames[currentInstance.queueNameIndex]; ++ this._scheduleAutorun(plannedNextQueue); ++ } else { ++ this.currentInstance = null; ++ if (this.instanceStack.length > 0) { ++ nextInstance = this.instanceStack.pop(); ++ this.currentInstance = nextInstance; ++ } ++ this._trigger('end', currentInstance, nextInstance); ++ this._onEnd(currentInstance, nextInstance); ++ } + } +- seen.add(obj); +- // Process the child namespace +- _processNamespace(paths, obj, seen); + } + } +- paths.length = idx; // cut out last item +- } +- function isNamespace(obj) { +- return obj != null && typeof obj === 'object' && obj.isNamespace; +- } +- function isUppercase(code) { +- return code >= 65 && code <= 90 // A +- ; // Z +- } +- function tryIsNamespace(lookup, prop) { +- try { +- let obj = lookup[prop]; +- return (obj !== null && typeof obj === 'object' || typeof obj === 'function') && obj.isNamespace && obj; +- } catch (_e) { +- // continue ++ _join(target, method, args) { ++ if (this.currentInstance === null) { ++ return this._run(target, method, args); ++ } ++ if (target === undefined && args === undefined) { ++ return method(); ++ } else { ++ return method.apply(target, args); ++ } + } +- } +- +- const emberinternalsMetalIndex = /*#__PURE__*/Object.defineProperty({ +- __proto__: null, +- ASYNC_OBSERVERS, +- ComputedDescriptor, +- ComputedProperty, +- get DEBUG_INJECTION_FUNCTIONS () { return DEBUG_INJECTION_FUNCTIONS; }, +- Libraries, +- NAMESPACES, +- NAMESPACES_BY_ID, +- PROPERTY_DID_CHANGE, +- PROXY_CONTENT, +- SYNC_OBSERVERS, +- TrackedDescriptor, +- _getPath, +- _getProp, +- _setProp, +- activateObserver, +- addArrayObserver, +- addListener, +- addNamespace, +- addObserver, +- alias, +- arrayContentDidChange, +- arrayContentWillChange, +- autoComputed, +- beginPropertyChanges, +- cached, +- changeProperties, +- computed, +- createCache, +- defineDecorator, +- defineProperty, +- defineValue, +- deprecateProperty, +- descriptorForDecorator, +- descriptorForProperty, +- eachProxyArrayDidChange, +- eachProxyArrayWillChange, +- endPropertyChanges, +- expandProperties, +- findNamespace, +- findNamespaces, +- flushAsyncObservers, +- get: get$2, +- getCachedValueFor, +- getProperties, +- getValue, +- hasListeners, +- hasUnknownProperty, +- inject: inject$2, +- isClassicDecorator, +- isComputed, +- isConst, +- isElementDescriptor, +- isNamespaceSearchDisabled: isSearchDisabled, +- libraries: LIBRARIES, +- makeComputedDecorator, +- markObjectAsDirty, +- nativeDescDecorator, +- notifyPropertyChange, +- objectAt, +- on: on$3, +- processAllNamespaces, +- processNamespace, +- removeArrayObserver, +- removeListener, +- removeNamespace, +- removeObserver, +- replace, +- replaceInNativeArray, +- revalidateObservers, +- sendEvent, +- set, +- setClassicDecorator, +- setNamespaceSearchDisabled: setSearchDisabled, +- setProperties, +- setUnprocessedMixins, +- tagForObject, +- tagForProperty, +- tracked, +- trySet +- }, Symbol.toStringTag, { value: 'Module' }); +- +- const emberObjectEvents = /*#__PURE__*/Object.defineProperty({ +- __proto__: null, +- addListener, +- removeListener, +- sendEvent +- }, Symbol.toStringTag, { value: 'Module' }); +- +- /** +- @module @ember/object/mixin +- */ +- const a_concat = Array.prototype.concat; +- const { +- isArray: isArray$4 +- } = Array; +- function extractAccessors(properties) { +- if (properties !== undefined) { +- for (let key of Object.keys(properties)) { +- let desc = Object.getOwnPropertyDescriptor(properties, key); +- if (desc.get !== undefined || desc.set !== undefined) { +- Object.defineProperty(properties, key, { +- value: nativeDescDecorator(desc) +- }); ++ _run(target, method, args) { ++ let onError = getOnError(this.options); ++ this.begin(); ++ if (onError) { ++ try { ++ return method.apply(target, args); ++ } catch (error) { ++ onError(error); ++ } finally { ++ this.end(); ++ } ++ } else { ++ try { ++ return method.apply(target, args); ++ } finally { ++ this.end(); + } + } + } +- return properties; +- } +- function concatenatedMixinProperties(concatProp, props, values, base) { +- // reset before adding each new mixin to pickup concats from previous +- let concats = values[concatProp] || base[concatProp]; +- if (props[concatProp]) { +- concats = concats ? a_concat.call(concats, props[concatProp]) : props[concatProp]; ++ _cancelAutorun() { ++ if (this._autorun) { ++ this._platform.clearNext(); ++ this._autorun = false; ++ this._autorunStack = null; ++ } + } +- return concats; +- } +- function giveDecoratorSuper(key, decorator, property, descs) { +- if (property === true) { +- return decorator; ++ _later(target, method, args, wait) { ++ let stack = this.DEBUG ? new Error() : undefined; ++ let executeAt = this._platform.now() + wait; ++ let id = UUID++; ++ if (this._timers.length === 0) { ++ this._timers.push(executeAt, id, target, method, args, stack); ++ this._installTimerTimeout(); ++ } else { ++ // find position to insert ++ let i = binarySearch(executeAt, this._timers); ++ this._timers.splice(i, 0, executeAt, id, target, method, args, stack); ++ // always reinstall since it could be out of sync ++ this._reinstallTimerTimeout(); ++ } ++ return id; + } +- let originalGetter = property._getter; +- if (originalGetter === undefined) { +- return decorator; ++ _cancelLaterTimer(timer) { ++ for (let i = 1; i < this._timers.length; i += TIMERS_OFFSET) { ++ if (this._timers[i] === timer) { ++ this._timers.splice(i - 1, TIMERS_OFFSET); ++ if (i === 1) { ++ this._reinstallTimerTimeout(); ++ } ++ return true; ++ } ++ } ++ return false; + } +- let superDesc = descs[key]; +- +- // Check to see if the super property is a decorator first, if so load its descriptor +- let superProperty = typeof superDesc === 'function' ? descriptorForDecorator(superDesc) : superDesc; +- if (superProperty === undefined || superProperty === true) { +- return decorator; +- } +- let superGetter = superProperty._getter; +- if (superGetter === undefined) { +- return decorator; +- } +- let get = wrap$1(originalGetter, superGetter); +- let set; +- let originalSetter = property._setter; +- let superSetter = superProperty._setter; +- if (superSetter !== undefined) { +- if (originalSetter !== undefined) { +- set = wrap$1(originalSetter, superSetter); +- } else { +- // If the super property has a setter, we default to using it no matter what. +- // This is clearly very broken and weird, but it's what was here so we have +- // to keep it until the next major at least. +- // +- // TODO: Add a deprecation here. +- set = superSetter; +- } +- } else { +- set = originalSetter; +- } +- +- // only create a new CP if we must +- if (get !== originalGetter || set !== originalSetter) { +- // Since multiple mixins may inherit from the same parent, we need +- // to clone the computed property so that other mixins do not receive +- // the wrapped version. +- let dependentKeys = property._dependentKeys || []; +- let newProperty = new ComputedProperty([...dependentKeys, { +- get, +- set +- }]); +- newProperty._readOnly = property._readOnly; +- newProperty._meta = property._meta; +- newProperty.enumerable = property.enumerable; +- +- // SAFETY: We passed in the impl for this class +- return makeComputedDecorator(newProperty, ComputedProperty); +- } +- return decorator; +- } +- function giveMethodSuper(key, method, values, descs) { +- // Methods overwrite computed properties, and do not call super to them. +- if (descs[key] !== undefined) { +- return method; +- } +- +- // Find the original method in a parent mixin +- let superMethod = values[key]; +- +- // Only wrap the new method if the original method was a function +- if (typeof superMethod === 'function') { +- return wrap$1(method, superMethod); +- } +- return method; +- } +- function simpleMakeArray(value) { +- if (!value) { +- return []; +- } else if (!Array.isArray(value)) { +- return [value]; +- } else { +- return value; +- } +- } +- function applyConcatenatedProperties(key, value, values) { +- let baseValue = values[key]; +- let ret = simpleMakeArray(baseValue).concat(simpleMakeArray(value)); +- { +- // it is possible to use concatenatedProperties with strings (which cannot be frozen) +- // only freeze objects... +- if (typeof ret === 'object' && ret !== null) { +- // prevent mutating `concatenatedProperties` array after it is applied +- Object.freeze(ret); ++ /** ++ Trigger an event. Supports up to two arguments. Designed around ++ triggering transition events from one run loop instance to the ++ next, which requires an argument for the instance and then ++ an argument for the next instance. ++ @private ++ @method _trigger ++ @param {String} eventName ++ @param {any} arg1 ++ @param {any} arg2 ++ */ ++ _trigger(eventName, arg1, arg2) { ++ let callbacks = this._eventCallbacks[eventName]; ++ if (callbacks !== undefined) { ++ for (let i = 0; i < callbacks.length; i++) { ++ callbacks[i](arg1, arg2); ++ } + } + } +- return ret; +- } +- function applyMergedProperties(key, value, values) { +- let baseValue = values[key]; +- (!(!isArray$4(value)) && assert$1(`You passed in \`${JSON.stringify(value)}\` as the value for \`${key}\` but \`${key}\` cannot be an Array`, !isArray$4(value))); +- if (!baseValue) { +- return value; +- } +- let newBase = Object.assign({}, baseValue); +- let hasFunction = false; +- let props = Object.keys(value); +- for (let prop of props) { +- let propValue = value[prop]; +- if (typeof propValue === 'function') { +- hasFunction = true; +- newBase[prop] = giveMethodSuper(prop, propValue, baseValue, {}); +- } else { +- newBase[prop] = propValue; ++ _runExpiredTimers() { ++ this._timerTimeoutId = null; ++ if (this._timers.length > 0) { ++ this.begin(); ++ this._scheduleExpiredTimers(); ++ this.end(); + } + } +- if (hasFunction) { +- newBase._super = ROOT; +- } +- return newBase; +- } +- function mergeMixins(mixins, meta, descs, values, base, keys, keysWithSuper) { +- let currentMixin; +- for (let i = 0; i < mixins.length; i++) { +- currentMixin = mixins[i]; +- (!(typeof currentMixin === 'object' && currentMixin !== null && Object.prototype.toString.call(currentMixin) !== '[object Array]') && assert$1(`Expected hash or Mixin instance, got ${Object.prototype.toString.call(currentMixin)}`, typeof currentMixin === 'object' && currentMixin !== null && Object.prototype.toString.call(currentMixin) !== '[object Array]')); +- if (MIXINS.has(currentMixin)) { +- if (meta.hasMixin(currentMixin)) { +- continue; ++ _scheduleExpiredTimers() { ++ let timers = this._timers; ++ let i = 0; ++ let l = timers.length; ++ let defaultQueue = this._defaultQueue; ++ let n = this._platform.now(); ++ for (; i < l; i += TIMERS_OFFSET) { ++ let executeAt = timers[i]; ++ if (executeAt > n) { ++ break; + } +- meta.addMixin(currentMixin); +- let { +- properties, +- mixins +- } = currentMixin; +- if (properties !== undefined) { +- mergeProps(meta, properties, descs, values, base, keys, keysWithSuper); +- } else if (mixins !== undefined) { +- mergeMixins(mixins, meta, descs, values, base, keys, keysWithSuper); +- if (currentMixin instanceof Mixin && currentMixin._without !== undefined) { +- currentMixin._without.forEach(keyName => { +- // deleting the key means we won't process the value +- let index = keys.indexOf(keyName); +- if (index !== -1) { +- keys.splice(index, 1); +- } +- }); +- } ++ let args = timers[i + 4]; ++ if (args !== DISABLE_SCHEDULE) { ++ let target = timers[i + 2]; ++ let method = timers[i + 3]; ++ let stack = timers[i + 5]; ++ this.currentInstance.schedule(defaultQueue, target, method, args, false, stack); + } +- } else { +- mergeProps(meta, currentMixin, descs, values, base, keys, keysWithSuper); + } ++ timers.splice(0, i); ++ this._installTimerTimeout(); + } +- } +- function mergeProps(meta, props, descs, values, base, keys, keysWithSuper) { +- let concats = concatenatedMixinProperties('concatenatedProperties', props, values, base); +- let mergings = concatenatedMixinProperties('mergedProperties', props, values, base); +- let propKeys = Object.keys(props); +- for (let key of propKeys) { +- let value = props[key]; +- if (value === undefined) continue; +- if (keys.indexOf(key) === -1) { +- keys.push(key); +- let desc = meta.peekDescriptors(key); +- if (desc === undefined) { +- // If the value is a classic decorator, we don't want to actually +- // access it, because that will execute the decorator while we're +- // building the class. +- if (!isClassicDecorator(value)) { +- // The superclass did not have a CP, which means it may have +- // observers or listeners on that property. +- let prev = values[key] = base[key]; +- if (typeof prev === 'function') { +- updateObserversAndListeners(base, key, prev, false); +- } +- } +- } else { +- descs[key] = desc; +- +- // The super desc will be overwritten on descs, so save off the fact that +- // there was a super so we know to Object.defineProperty when writing +- // the value +- keysWithSuper.push(key); +- desc.teardown(base, key, meta); +- } +- } +- let isFunction = typeof value === 'function'; +- if (isFunction) { +- let desc = descriptorForDecorator(value); +- if (desc !== undefined) { +- // Wrap descriptor function to implement _super() if needed +- descs[key] = giveDecoratorSuper(key, value, desc, descs); +- values[key] = undefined; +- continue; +- } ++ _reinstallTimerTimeout() { ++ this._clearTimerTimeout(); ++ this._installTimerTimeout(); ++ } ++ _clearTimerTimeout() { ++ if (this._timerTimeoutId === null) { ++ return; + } +- if (concats && concats.indexOf(key) >= 0 || key === 'concatenatedProperties' || key === 'mergedProperties') { +- value = applyConcatenatedProperties(key, value, values); +- } else if (mergings && mergings.indexOf(key) > -1) { +- value = applyMergedProperties(key, value, values); +- } else if (isFunction) { +- value = giveMethodSuper(key, value, values, descs); ++ this._platform.clearTimeout(this._timerTimeoutId); ++ this._timerTimeoutId = null; ++ } ++ _installTimerTimeout() { ++ if (this._timers.length === 0) { ++ return; + } +- values[key] = value; +- descs[key] = undefined; ++ let minExpiresAt = this._timers[0]; ++ let n = this._platform.now(); ++ let wait = Math.max(0, minExpiresAt - n); ++ this._timerTimeoutId = this._platform.setTimeout(this._boundRunExpiredTimers, wait); + } +- } +- function updateObserversAndListeners(obj, key, fn, add) { +- let meta = observerListenerMetaFor(fn); +- if (meta === undefined) return; +- let { +- observers, +- listeners +- } = meta; +- if (observers !== undefined) { +- let updateObserver = add ? addObserver : removeObserver; +- for (let path of observers.paths) { +- updateObserver(obj, path, null, key, observers.sync); ++ _ensureInstance() { ++ let currentInstance = this.currentInstance; ++ if (currentInstance === null) { ++ this._autorunStack = this.DEBUG ? new Error() : undefined; ++ currentInstance = this.begin(); ++ this._scheduleAutorun(this.queueNames[0]); + } ++ return currentInstance; + } +- if (listeners !== undefined) { +- let updateListener = add ? addListener : removeListener; +- for (let listener of listeners) { +- updateListener(obj, listener, null, key); ++ _scheduleAutorun(plannedNextQueue) { ++ autorunsCreatedCount++; ++ const next = this._platform.next; ++ const flush = this.options.flush; ++ if (flush) { ++ flush(plannedNextQueue, next); ++ } else { ++ next(); + } ++ this._autorun = true; + } + } +- function applyMixin(obj, mixins, _hideKeys = false) { +- let descs = Object.create(null); +- let values = Object.create(null); +- let meta$1 = meta(obj); +- let keys = []; +- let keysWithSuper = []; +- obj._super = ROOT; ++ Backburner.Queue = Queue; ++ Backburner.buildPlatform = buildPlatform; ++ Backburner.buildNext = buildNext; + +- // Go through all mixins and hashes passed in, and: +- // +- // * Handle concatenated properties +- // * Handle merged properties +- // * Set up _super wrapping if necessary +- // * Set up computed property descriptors +- // * Copying `toString` in broken browsers +- mergeMixins(mixins, meta$1, descs, values, obj, keys, keysWithSuper); +- for (let key of keys) { +- let value = values[key]; +- let desc = descs[key]; +- if (value !== undefined) { +- if (typeof value === 'function') { +- updateObserversAndListeners(obj, key, value, true); +- } +- defineValue(obj, key, value, keysWithSuper.indexOf(key) !== -1, !_hideKeys); +- } else if (desc !== undefined) { +- defineDecorator(obj, key, desc, meta$1); +- } +- } +- if (!meta$1.isPrototypeMeta(obj)) { +- revalidateObservers(obj); ++ const backburnerjs = /*#__PURE__*/Object.defineProperty({ ++ __proto__: null, ++ buildPlatform, ++ default: Backburner ++ }, Symbol.toStringTag, { value: 'Module' }); ++ ++ // Partial types from https://medium.com/codex/currying-in-typescript-ca5226c85b85 ++ ++ let currentRunLoop = null; ++ function _getCurrentRunLoop() { ++ return currentRunLoop; ++ } ++ function onBegin(current) { ++ currentRunLoop = current; ++ } ++ function onEnd(_current, next) { ++ currentRunLoop = next; ++ flushAsyncObservers(schedule); ++ } ++ function flush$1(queueName, next) { ++ if (queueName === 'render' || queueName === _rsvpErrorQueue) { ++ flushAsyncObservers(schedule); + } +- return obj; ++ next(); + } ++ const _rsvpErrorQueue = `${Math.random()}${Date.now()}`.replace('.', ''); + + /** +- @method mixin +- @param obj +- @param mixins* +- @return obj ++ Array of named queues. This array determines the order in which queues ++ are flushed at the end of the RunLoop. You can define your own queues by ++ simply adding the queue name to this array. Normally you should not need ++ to inspect or modify this property. ++ ++ @property queues ++ @type Array ++ @default ['actions', 'destroy'] + @private + */ +- function mixin(obj, ...args) { +- applyMixin(obj, args); +- return obj; +- } +- const MIXINS = new WeakSet(); ++ const _queues = ['actions', ++ // used in router transitions to prevent unnecessary loading state entry ++ // if all context promises resolve on the 'actions' queue first ++ 'routerTransitions', 'render', 'afterRender', 'destroy', ++ // used to re-throw unhandled RSVP rejection errors specifically in this ++ // position to avoid breaking anything rendered in the other sections ++ _rsvpErrorQueue]; + + /** +- The `Mixin` class allows you to create mixins, whose properties can be +- added to other classes. For instance, ++ * @internal ++ * @private ++ */ ++ const _backburner = new Backburner(_queues, { ++ defaultQueue: 'actions', ++ onBegin, ++ onEnd, ++ onErrorTarget, ++ onErrorMethod: 'onerror', ++ flush: flush$1 ++ }); + +- ```javascript +- import Mixin from '@ember/object/mixin'; ++ /** ++ @module @ember/runloop ++ */ ++ // .......................................................... ++ // run - this is ideally the only public API the dev sees ++ // + +- const EditableMixin = Mixin.create({ +- edit() { +- console.log('starting to edit'); +- this.set('isEditing', true); +- }, +- isEditing: false +- }); +- ``` ++ /** ++ Runs the passed target and method inside of a RunLoop, ensuring any ++ deferred actions including bindings and views updates are flushed at the ++ end. + +- ```javascript +- import EmberObject from '@ember/object'; +- import EditableMixin from '../mixins/editable'; ++ Normally you should not need to invoke this method yourself. However if ++ you are implementing raw event handlers when interfacing with other ++ libraries or plugins, you should probably wrap all of your code inside this ++ call. + +- // Mix mixins into classes by passing them as the first arguments to +- // `.extend.` +- const Comment = EmberObject.extend(EditableMixin, { +- post: null +- }); ++ ```javascript ++ import { run } from '@ember/runloop'; + +- let comment = Comment.create({ +- post: somePost ++ run(function() { ++ // code to be executed within a RunLoop + }); +- +- comment.edit(); // outputs 'starting to edit' + ``` ++ @method run ++ @for @ember/runloop ++ @static ++ @param {Object} [target] target of method to call ++ @param {Function|String} method Method to invoke. ++ May be a function or a string. If you pass a string ++ then it will be looked up on the passed target. ++ @param {Object} [args*] Any additional arguments you wish to pass to the method. ++ @return {Object} return value from invoking the passed function. ++ @public ++ */ + +- Note that Mixins are created with `Mixin.create`, not +- `Mixin.extend`. ++ function run$1(...args) { ++ // @ts-expect-error TS doesn't like our spread args ++ return _backburner.run(...args); ++ } + +- Note that mixins extend a constructor's prototype so arrays and object literals +- defined as properties will be shared amongst objects that implement the mixin. +- If you want to define a property in a mixin that is not shared, you can define +- it either as a computed property or have it be created on initialization of the object. ++ /** ++ If no run-loop is present, it creates a new one. If a run loop is ++ present it will queue itself to run on the existing run-loops action ++ queue. + +- ```javascript +- // filters array will be shared amongst any object implementing mixin +- import Mixin from '@ember/object/mixin'; +- import { A } from '@ember/array'; ++ Please note: This is not for normal usage, and should be used sparingly. + +- const FilterableMixin = Mixin.create({ +- filters: A() +- }); +- ``` ++ If invoked when not within a run loop: + + ```javascript +- import Mixin from '@ember/object/mixin'; +- import { A } from '@ember/array'; +- import { computed } from '@ember/object'; ++ import { join } from '@ember/runloop'; + +- // filters will be a separate array for every object implementing the mixin +- const FilterableMixin = Mixin.create({ +- filters: computed(function() { +- return A(); +- }) ++ join(function() { ++ // creates a new run-loop + }); + ``` + ++ Alternatively, if called within an existing run loop: ++ + ```javascript +- import Mixin from '@ember/object/mixin'; +- import { A } from '@ember/array'; ++ import { run, join } from '@ember/runloop'; + +- // filters will be created as a separate array during the object's initialization +- const Filterable = Mixin.create({ +- filters: null, ++ run(function() { ++ // creates a new run-loop + +- init() { +- this._super(...arguments); +- this.set("filters", A()); +- } ++ join(function() { ++ // joins with the existing run-loop, and queues for invocation on ++ // the existing run-loops action queue. ++ }); + }); + ``` + +- @class Mixin ++ @method join ++ @static ++ @for @ember/runloop ++ @param {Object} [target] target of method to call ++ @param {Function|String} method Method to invoke. ++ May be a function or a string. If you pass a string ++ then it will be looked up on the passed target. ++ @param {Object} [args*] Any additional arguments you wish to pass to the method. ++ @return {Object} Return value from invoking the passed function. Please note, ++ when called within an existing loop, no return value is possible. + @public + */ +- class Mixin { +- /** @internal */ + +- /** @internal */ +- mixins; ++ function join(methodOrTarget, methodOrArg, ...additionalArgs) { ++ return _backburner.join(methodOrTarget, methodOrArg, ...additionalArgs); ++ } + +- /** @internal */ +- properties; ++ /** ++ Allows you to specify which context to call the specified function in while ++ adding the execution of that function to the Ember run loop. This ability ++ makes this method a great way to asynchronously integrate third-party libraries ++ into your Ember application. + +- /** @internal */ +- ownerConstructor; ++ `bind` takes two main arguments, the desired context and the function to ++ invoke in that context. Any additional arguments will be supplied as arguments ++ to the function that is passed in. + +- /** @internal */ +- _without; +- /** @internal */ +- constructor(mixins, properties) { +- MIXINS.add(this); +- this.properties = extractAccessors(properties); +- this.mixins = buildMixinsArray(mixins); +- this.ownerConstructor = undefined; +- this._without = undefined; +- { +- // Eagerly add INIT_FACTORY to avoid issues in DEBUG as a result of Object.seal(mixin) +- this[INIT_FACTORY] = null; +- /* +- In debug builds, we seal mixins to help avoid performance pitfalls. +- In IE11 there is a quirk that prevents sealed objects from being added +- to a WeakMap. Unfortunately, the mixin system currently relies on +- weak maps in `guidFor`, so we need to prime the guid cache weak map. +- */ +- guidFor(this); +- if (Mixin._disableDebugSeal !== true) { +- Object.seal(this); +- } +- } +- } ++ Let's use the creation of a TinyMCE component as an example. Currently, ++ TinyMCE provides a setup configuration option we can use to do some processing ++ after the TinyMCE instance is initialized but before it is actually rendered. ++ We can use that setup option to do some additional setup for our component. ++ The component itself could look something like the following: + +- /** +- @method create +- @for @ember/object/mixin +- @static +- @param arguments* +- @public +- */ +- static create(...args) { +- setUnprocessedMixins(); +- let M = this; +- return new M(args, undefined); +- } ++ ```app/components/rich-text-editor.js ++ import Component from '@ember/component'; ++ import { on } from '@ember/object/evented'; ++ import { bind } from '@ember/runloop'; + +- // returns the mixins currently applied to the specified object +- // TODO: Make `mixin` +- /** @internal */ +- static mixins(obj) { +- let meta = peekMeta(obj); +- let ret = []; +- if (meta === null) { +- return ret; +- } +- meta.forEachMixins(currentMixin => { +- // skip primitive mixins since these are always anonymous +- if (!currentMixin.properties) { +- ret.push(currentMixin); +- } +- }); +- return ret; +- } ++ export default Component.extend({ ++ initializeTinyMCE: on('didInsertElement', function() { ++ tinymce.init({ ++ selector: '#' + this.$().prop('id'), ++ setup: bind(this, this.setupEditor) ++ }); ++ }), + +- /** +- @method reopen +- @param arguments* +- @private +- @internal +- */ +- reopen(...args) { +- if (args.length === 0) { +- return this; ++ didInsertElement() { ++ tinymce.init({ ++ selector: '#' + this.$().prop('id'), ++ setup: bind(this, this.setupEditor) ++ }); + } +- if (this.properties) { +- let currentMixin = new Mixin(undefined, this.properties); +- this.properties = undefined; +- this.mixins = [currentMixin]; +- } else if (!this.mixins) { +- this.mixins = []; ++ ++ setupEditor(editor) { ++ this.set('editor', editor); ++ ++ editor.on('change', function() { ++ console.log('content changed!'); ++ }); + } +- this.mixins = this.mixins.concat(buildMixinsArray(args)); +- return this; +- } ++ }); ++ ``` + +- /** +- @method apply +- @param obj +- @return applied object +- @private +- @internal +- */ +- apply(obj, _hideKeys = false) { +- // Ember.NativeArray is a normal Ember.Mixin that we mix into `Array.prototype` when prototype extensions are enabled +- // mutating a native object prototype like this should _not_ result in enumerable properties being added (or we have significant +- // issues with things like deep equality checks from test frameworks, or things like jQuery.extend(true, [], [])). +- // +- // _hideKeys disables enumerablity when applying the mixin. This is a hack, and we should stop mutating the array prototype by default 😫 +- return applyMixin(obj, [this], _hideKeys); +- } ++ In this example, we use `bind` to bind the setupEditor method to the ++ context of the RichTextEditor component and to have the invocation of that ++ method be safely handled and executed by the Ember run loop. + +- /** @internal */ +- applyPartial(obj) { +- return applyMixin(obj, [this]); +- } ++ @method bind ++ @static ++ @for @ember/runloop ++ @param {Object} [target] target of method to call ++ @param {Function|String} method Method to invoke. ++ May be a function or a string. If you pass a string ++ then it will be looked up on the passed target. ++ @param {Object} [args*] Any additional arguments you wish to pass to the method. ++ @return {Function} returns a new function that will always have a particular context ++ @since 1.4.0 ++ @public ++ */ + +- /** +- @method detect +- @param obj +- @return {Boolean} +- @private +- @internal +- */ +- detect(obj) { +- if (typeof obj !== 'object' || obj === null) { ++ // This final fallback is the equivalent of the (quite unsafe!) type for `bind` ++ // from TS' defs for `Function.prototype.bind`. In general, it means we have a ++ // loss of safety if we do not ++ ++ function bind(...curried) { ++ (!(function (methodOrTarget, methodOrArg) { ++ // Applies the same logic as backburner parseArgs for detecting if a method ++ // is actually being passed. ++ let length = arguments.length; ++ if (length === 0) { + return false; ++ } else if (length === 1) { ++ return typeof methodOrTarget === 'function'; ++ } else { ++ return typeof methodOrArg === 'function' || ++ // second argument is a function ++ methodOrTarget !== null && typeof methodOrArg === 'string' && methodOrArg in methodOrTarget || ++ // second argument is the name of a method in first argument ++ typeof methodOrTarget === 'function' //first argument is a function ++ ; + } +- if (MIXINS.has(obj)) { +- return _detect(obj, this); +- } +- let meta = peekMeta(obj); +- if (meta === null) { ++ // @ts-expect-error TS doesn't like our spread args ++ }(...curried)) && assert$1('could not find a suitable method to bind', function (methodOrTarget, methodOrArg) { ++ let length = arguments.length; ++ if (length === 0) { + return false; ++ } else if (length === 1) { ++ return typeof methodOrTarget === 'function'; ++ } else { ++ return typeof methodOrArg === 'function' || methodOrTarget !== null && typeof methodOrArg === 'string' && methodOrArg in methodOrTarget || typeof methodOrTarget === 'function'; + } +- return meta.hasMixin(this); +- } ++ }(...curried))); // @ts-expect-error TS doesn't like our spread args ++ return (...args) => join(...curried.concat(args)); ++ } + +- /** @internal */ +- without(...args) { +- let ret = new Mixin([this]); +- ret._without = args; +- return ret; +- } ++ /** ++ Begins a new RunLoop. Any deferred actions invoked after the begin will ++ be buffered until you invoke a matching call to `end()`. This is ++ a lower-level way to use a RunLoop instead of using `run()`. + +- /** @internal */ +- keys() { +- let keys = _keys(this); +- (!(keys) && assert$1('[BUG] Missing keys for mixin!', keys)); +- return keys; +- } ++ ```javascript ++ import { begin, end } from '@ember/runloop'; + +- /** @internal */ +- toString() { +- return '(unknown mixin)'; +- } +- } +- { +- Object.defineProperty(Mixin, '_disableDebugSeal', { +- configurable: true, +- enumerable: false, +- writable: true, +- value: false +- }); +- } +- function buildMixinsArray(mixins) { +- let length = mixins && mixins.length || 0; +- let m = undefined; +- if (length > 0) { +- m = new Array(length); +- for (let i = 0; i < length; i++) { +- let x = mixins[i]; +- (!(typeof x === 'object' && x !== null && Object.prototype.toString.call(x) !== '[object Array]') && assert$1(`Expected hash or Mixin instance, got ${Object.prototype.toString.call(x)}`, typeof x === 'object' && x !== null && Object.prototype.toString.call(x) !== '[object Array]')); +- if (MIXINS.has(x)) { +- m[i] = x; +- } else { +- m[i] = new Mixin(undefined, x); +- } +- } +- } +- return m; +- } +- { +- Object.seal(Mixin.prototype); +- } +- function _detect(curMixin, targetMixin, seen = new Set()) { +- if (seen.has(curMixin)) { +- return false; +- } +- seen.add(curMixin); +- if (curMixin === targetMixin) { +- return true; +- } +- let mixins = curMixin.mixins; +- if (mixins) { +- return mixins.some(mixin => _detect(mixin, targetMixin, seen)); +- } +- return false; +- } +- function _keys(mixin, ret = new Set(), seen = new Set()) { +- if (seen.has(mixin)) { +- return; +- } +- seen.add(mixin); +- if (mixin.properties) { +- let props = Object.keys(mixin.properties); +- for (let prop of props) { +- ret.add(prop); +- } +- } else if (mixin.mixins) { +- mixin.mixins.forEach(x => _keys(x, ret, seen)); +- } +- return ret; +- } ++ begin(); ++ // code to be executed within a RunLoop ++ end(); ++ ``` + +- const emberObjectMixin = /*#__PURE__*/Object.defineProperty({ +- __proto__: null, +- applyMixin, +- default: Mixin, +- mixin +- }, Symbol.toStringTag, { value: 'Module' }); ++ @method begin ++ @static ++ @for @ember/runloop ++ @return {void} ++ @public ++ */ ++ function begin() { ++ _backburner.begin(); ++ } + + /** +- @module ember +- */ ++ Ends a RunLoop. This must be called sometime after you call ++ `begin()` to flush any deferred actions. This is a lower-level way ++ to use a RunLoop instead of using `run()`. + ++ ```javascript ++ import { begin, end } from '@ember/runloop'; + +- /** +- RegistryProxyMixin is used to provide public access to specific +- registry functionality. ++ begin(); ++ // code to be executed within a RunLoop ++ end(); ++ ``` + +- @class RegistryProxyMixin +- @extends RegistryProxy +- @private ++ @method end ++ @static ++ @for @ember/runloop ++ @return {void} ++ @public + */ +- +- const RegistryProxyMixin = Mixin.create({ +- __registry__: null, +- resolveRegistration(fullName) { +- (!(this.__registry__.isValidFullName(fullName)) && assert$1('fullName must be a proper full name', this.__registry__.isValidFullName(fullName))); +- return this.__registry__.resolve(fullName); +- }, +- register: registryAlias('register'), +- unregister: registryAlias('unregister'), +- hasRegistration: registryAlias('has'), +- registeredOption: registryAlias('getOption'), +- registerOptions: registryAlias('options'), +- registeredOptions: registryAlias('getOptions'), +- registerOptionsForType: registryAlias('optionsForType'), +- registeredOptionsForType: registryAlias('getOptionsForType') +- }); +- function registryAlias(name) { +- return function (...args) { +- // We need this cast because `Parameters` is deferred so that it is not +- // possible for TS to see it will always produce the right type. However, +- // since `AnyFn` has a rest type, it is allowed. See discussion on [this +- // issue](https://github.com/microsoft/TypeScript/issues/47615). +- return this.__registry__[name](...args); +- }; ++ function end() { ++ _backburner.end(); + } + +- const emberinternalsRuntimeLibMixinsRegistryProxy = /*#__PURE__*/Object.defineProperty({ +- __proto__: null, +- default: RegistryProxyMixin +- }, Symbol.toStringTag, { value: 'Module' }); ++ /** ++ Adds the passed target/method and any optional arguments to the named ++ queue to be executed at the end of the RunLoop. If you have not already ++ started a RunLoop when calling this method one will be started for you ++ automatically. + +- // This is defined as a separate interface so that it can be used in the definition of +- // `Owner` without also including the `__container__` property. ++ At the end of a RunLoop, any methods scheduled in this way will be invoked. ++ Methods will be invoked in an order matching the named queues defined in ++ the `queues` property. + +- /** +- ContainerProxyMixin is used to provide public access to specific +- container functionality. ++ ```javascript ++ import { schedule } from '@ember/runloop'; + +- @class ContainerProxyMixin +- @extends ContainerProxy +- @private +- */ ++ schedule('afterRender', this, function() { ++ // this will be executed in the 'afterRender' queue ++ console.log('scheduled on afterRender queue'); ++ }); + +- const ContainerProxyMixin = Mixin.create({ +- /** +- The container stores state. +- @private +- @property {Ember.Container} __container__ +- */ +- __container__: null, +- ownerInjection() { +- return this.__container__.ownerInjection(); +- }, +- lookup(fullName, options) { +- return this.__container__.lookup(fullName, options); +- }, +- destroy() { +- let container = this.__container__; +- if (container) { +- join(() => { +- container.destroy(); +- schedule('destroy', container, 'finalizeDestroy'); +- }); +- } +- this._super(); +- }, +- factoryFor(fullName) { +- return this.__container__.factoryFor(fullName); +- } +- }); ++ schedule('actions', this, function() { ++ // this will be executed in the 'actions' queue ++ console.log('scheduled on actions queue'); ++ }); + +- const emberinternalsRuntimeLibMixinsContainerProxy = /*#__PURE__*/Object.defineProperty({ +- __proto__: null, +- default: ContainerProxyMixin +- }, Symbol.toStringTag, { value: 'Module' }); ++ // Note the functions will be run in order based on the run queues order. ++ // Output would be: ++ // scheduled on actions queue ++ // scheduled on afterRender queue ++ ``` + +- /** +- @module ember ++ @method schedule ++ @static ++ @for @ember/runloop ++ @param {String} queue The name of the queue to schedule against. Default queues is 'actions' ++ @param {Object} [target] target object to use as the context when invoking a method. ++ @param {String|Function} method The method to invoke. If you pass a string it ++ will be resolved on the target object at the time the scheduled item is ++ invoked allowing you to change the target function. ++ @param {Object} [arguments*] Optional arguments to be passed to the queued method. ++ @return {*} Timer information for use in canceling, see `cancel`. ++ @public + */ + ++ function schedule(...args) { ++ // @ts-expect-error TS doesn't like the rest args here ++ return _backburner.schedule(...args); ++ } ++ ++ // Used by global test teardown ++ function _hasScheduledTimers() { ++ return _backburner.hasTimers(); ++ } ++ ++ // Used by global test teardown ++ function _cancelTimers() { ++ _backburner.cancelTimers(); ++ } ++ + /** +- Implements some standard methods for comparing objects. Add this mixin to +- any class you create that can compare its instances. ++ Invokes the passed target/method and optional arguments after a specified ++ period of time. The last parameter of this method must always be a number ++ of milliseconds. + +- You should implement the `compare()` method. ++ You should use this method whenever you need to run some action after a ++ period of time instead of using `setTimeout()`. This method will ensure that ++ items that expire during the same script execution cycle all execute ++ together, which is often more efficient than using a real setTimeout. + +- @class Comparable +- @namespace Ember +- @since Ember 0.9 +- @private +- */ ++ ```javascript ++ import { later } from '@ember/runloop'; + +- const Comparable = Mixin.create({ +- /** +- __Required.__ You must implement this method to apply this mixin. +- Override to return the result of the comparison of the two parameters. The +- compare method should return: +- - `-1` if `a < b` +- - `0` if `a == b` +- - `1` if `a > b` +- Default implementation raises an exception. +- @method compare +- @param a {Object} the first object to compare +- @param b {Object} the second object to compare +- @return {Number} the result of the comparison +- @private +- */ +- compare: null +- }); ++ later(myContext, function() { ++ // code here will execute within a RunLoop in about 500ms with this == myContext ++ }, 500); ++ ``` + +- const emberinternalsRuntimeLibMixinsComparable = /*#__PURE__*/Object.defineProperty({ +- __proto__: null, +- default: Comparable +- }, Symbol.toStringTag, { value: 'Module' }); ++ @method later ++ @static ++ @for @ember/runloop ++ @param {Object} [target] target of method to invoke ++ @param {Function|String} method The method to invoke. ++ If you pass a string it will be resolved on the ++ target at the time the method is invoked. ++ @param {Object} [args*] Optional arguments to pass to the timeout. ++ @param {Number} wait Number of milliseconds to wait. ++ @return {*} Timer information for use in canceling, see `cancel`. ++ @public ++ */ ++ ++ function later(...args) { ++ return _backburner.later(...args); ++ } + + /** +- @module ember ++ Schedule a function to run one time during the current RunLoop. This is equivalent ++ to calling `scheduleOnce` with the "actions" queue. ++ ++ @method once ++ @static ++ @for @ember/runloop ++ @param {Object} [target] The target of the method to invoke. ++ @param {Function|String} method The method to invoke. ++ If you pass a string it will be resolved on the ++ target at the time the method is invoked. ++ @param {Object} [args*] Optional arguments to pass to the timeout. ++ @return {Object} Timer information for use in canceling, see `cancel`. ++ @public + */ + ++ function once(...args) { ++ // @ts-expect-error TS doesn't like the rest args here ++ return _backburner.scheduleOnce('actions', ...args); ++ } + + /** +- `Ember.ActionHandler` is available on some familiar classes including +- `Route`, `Component`, and `Controller`. +- (Internally the mixin is used by `Ember.CoreView`, `Ember.ControllerMixin`, +- and `Route` and available to the above classes through +- inheritance.) ++ Schedules a function to run one time in a given queue of the current RunLoop. ++ Calling this method with the same queue/target/method combination will have ++ no effect (past the initial call). + +- @class ActionHandler +- @namespace Ember +- @private +- */ ++ Note that although you can pass optional arguments these will not be ++ considered when looking for duplicates. New arguments will replace previous ++ calls. + +- const ActionHandler = Mixin.create({ +- mergedProperties: ['actions'], +- /** +- The collection of functions, keyed by name, available on this +- `ActionHandler` as action targets. +- These functions will be invoked when a matching `{{action}}` is triggered +- from within a template and the application's current route is this route. +- Actions can also be invoked from other parts of your application +- via `ActionHandler#send`. +- The `actions` hash will inherit action handlers from +- the `actions` hash defined on extended parent classes +- or mixins rather than just replace the entire hash, e.g.: ++ ```javascript ++ import { run, scheduleOnce } from '@ember/runloop'; ++ ++ function sayHi() { ++ console.log('hi'); ++ } ++ ++ run(function() { ++ scheduleOnce('afterRender', myContext, sayHi); ++ scheduleOnce('afterRender', myContext, sayHi); ++ // sayHi will only be executed once, in the afterRender queue of the RunLoop ++ }); ++ ``` ++ ++ Also note that for `scheduleOnce` to prevent additional calls, you need to ++ pass the same function instance. The following case works as expected: ++ ++ ```javascript ++ function log() { ++ console.log('Logging only once'); ++ } ++ ++ function scheduleIt() { ++ scheduleOnce('actions', myContext, log); ++ } ++ ++ scheduleIt(); ++ scheduleIt(); ++ ``` ++ ++ But this other case will schedule the function multiple times: ++ ++ ```javascript ++ import { scheduleOnce } from '@ember/runloop'; ++ ++ function scheduleIt() { ++ scheduleOnce('actions', myContext, function() { ++ console.log('Closure'); ++ }); ++ } ++ ++ scheduleIt(); ++ scheduleIt(); ++ ++ // "Closure" will print twice, even though we're using `scheduleOnce`, ++ // because the function we pass to it won't match the ++ // previously scheduled operation. ++ ``` ++ ++ Available queues, and their order, can be found at `queues` ++ ++ @method scheduleOnce ++ @static ++ @for @ember/runloop ++ @param {String} [queue] The name of the queue to schedule against. Default queues is 'actions'. ++ @param {Object} [target] The target of the method to invoke. ++ @param {Function|String} method The method to invoke. ++ If you pass a string it will be resolved on the ++ target at the time the method is invoked. ++ @param {Object} [args*] Optional arguments to pass to the timeout. ++ @return {Object} Timer information for use in canceling, see `cancel`. ++ @public ++ */ ++ ++ function scheduleOnce(...args) { ++ // @ts-expect-error TS doesn't like the rest args here ++ return _backburner.scheduleOnce(...args); ++ } ++ ++ /** ++ Schedules an item to run from within a separate run loop, after ++ control has been returned to the system. This is equivalent to calling ++ `later` with a wait time of 1ms. ++ ++ ```javascript ++ import { next } from '@ember/runloop'; ++ ++ next(myContext, function() { ++ // code to be executed in the next run loop, ++ // which will be scheduled after the current one ++ }); ++ ``` ++ ++ Multiple operations scheduled with `next` will coalesce ++ into the same later run loop, along with any other operations ++ scheduled by `later` that expire right around the same ++ time that `next` operations will fire. ++ ++ Note that there are often alternatives to using `next`. ++ For instance, if you'd like to schedule an operation to happen ++ after all DOM element operations have completed within the current ++ run loop, you can make use of the `afterRender` run loop queue (added ++ by the `ember-views` package, along with the preceding `render` queue ++ where all the DOM element operations happen). ++ ++ Example: ++ ++ ```app/components/my-component.js ++ import Component from '@ember/component'; ++ import { scheduleOnce } from '@ember/runloop'; ++ ++ export Component.extend({ ++ didInsertElement() { ++ this._super(...arguments); ++ scheduleOnce('afterRender', this, 'processChildElements'); ++ }, ++ ++ processChildElements() { ++ // ... do something with component's child component ++ // elements after they've finished rendering, which ++ // can't be done within this component's ++ // `didInsertElement` hook because that gets run ++ // before the child elements have been added to the DOM. ++ } ++ }); ++ ``` ++ ++ One benefit of the above approach compared to using `next` is ++ that you will be able to perform DOM/CSS operations before unprocessed ++ elements are rendered to the screen, which may prevent flickering or ++ other artifacts caused by delaying processing until after rendering. ++ ++ The other major benefit to the above approach is that `next` ++ introduces an element of non-determinism, which can make things much ++ harder to test, due to its reliance on `setTimeout`; it's much harder ++ to guarantee the order of scheduled operations when they are scheduled ++ outside of the current run loop, i.e. with `next`. ++ ++ @method next ++ @static ++ @for @ember/runloop ++ @param {Object} [target] target of method to invoke ++ @param {Function|String} method The method to invoke. ++ If you pass a string it will be resolved on the ++ target at the time the method is invoked. ++ @param {Object} [args*] Optional arguments to pass to the timeout. ++ @return {Object} Timer information for use in canceling, see `cancel`. ++ @public ++ */ ++ ++ function next(...args) { ++ return _backburner.later(...args, 1); ++ } ++ ++ /** ++ Cancels a scheduled item. Must be a value returned by `later()`, ++ `once()`, `scheduleOnce()`, `next()`, `debounce()`, or ++ `throttle()`. ++ ++ ```javascript ++ import { ++ next, ++ cancel, ++ later, ++ scheduleOnce, ++ once, ++ throttle, ++ debounce ++ } from '@ember/runloop'; ++ ++ let runNext = next(myContext, function() { ++ // will not be executed ++ }); ++ ++ cancel(runNext); ++ ++ let runLater = later(myContext, function() { ++ // will not be executed ++ }, 500); ++ ++ cancel(runLater); ++ ++ let runScheduleOnce = scheduleOnce('afterRender', myContext, function() { ++ // will not be executed ++ }); ++ ++ cancel(runScheduleOnce); ++ ++ let runOnce = once(myContext, function() { ++ // will not be executed ++ }); ++ ++ cancel(runOnce); ++ ++ let throttle = throttle(myContext, function() { ++ // will not be executed ++ }, 1, false); ++ ++ cancel(throttle); ++ ++ let debounce = debounce(myContext, function() { ++ // will not be executed ++ }, 1); ++ ++ cancel(debounce); ++ ++ let debounceImmediate = debounce(myContext, function() { ++ // will be executed since we passed in true (immediate) ++ }, 100, true); ++ ++ // the 100ms delay until this method can be called again will be canceled ++ cancel(debounceImmediate); ++ ``` ++ ++ @method cancel ++ @static ++ @for @ember/runloop ++ @param {Object} [timer] Timer object to cancel ++ @return {Boolean} true if canceled or false/undefined if it wasn't found ++ @public ++ */ ++ function cancel(timer) { ++ return _backburner.cancel(timer); ++ } ++ ++ /** ++ Delay calling the target method until the debounce period has elapsed ++ with no additional debounce calls. If `debounce` is called again before ++ the specified time has elapsed, the timer is reset and the entire period ++ must pass again before the target method is called. ++ ++ This method should be used when an event may be called multiple times ++ but the action should only be called once when the event is done firing. ++ A common example is for scroll events where you only want updates to ++ happen once scrolling has ceased. ++ ++ ```javascript ++ import { debounce } from '@ember/runloop'; ++ ++ function whoRan() { ++ console.log(this.name + ' ran.'); ++ } ++ ++ let myContext = { name: 'debounce' }; ++ ++ debounce(myContext, whoRan, 150); ++ ++ // less than 150ms passes ++ debounce(myContext, whoRan, 150); ++ ++ // 150ms passes ++ // whoRan is invoked with context myContext ++ // console logs 'debounce ran.' one time. ++ ``` ++ ++ Immediate allows you to run the function immediately, but debounce ++ other calls for this function until the wait time has elapsed. If ++ `debounce` is called again before the specified time has elapsed, ++ the timer is reset and the entire period must pass again before ++ the method can be called again. ++ ++ ```javascript ++ import { debounce } from '@ember/runloop'; ++ ++ function whoRan() { ++ console.log(this.name + ' ran.'); ++ } ++ ++ let myContext = { name: 'debounce' }; ++ ++ debounce(myContext, whoRan, 150, true); ++ ++ // console logs 'debounce ran.' one time immediately. ++ // 100ms passes ++ debounce(myContext, whoRan, 150, true); ++ ++ // 150ms passes and nothing else is logged to the console and ++ // the debouncee is no longer being watched ++ debounce(myContext, whoRan, 150, true); ++ ++ // console logs 'debounce ran.' one time immediately. ++ // 150ms passes and nothing else is logged to the console and ++ // the debouncee is no longer being watched ++ ``` ++ ++ @method debounce ++ @static ++ @for @ember/runloop ++ @param {Object} [target] target of method to invoke ++ @param {Function|String} method The method to invoke. ++ May be a function or a string. If you pass a string ++ then it will be looked up on the passed target. ++ @param {Object} [args*] Optional arguments to pass to the timeout. ++ @param {Number} wait Number of milliseconds to wait. ++ @param {Boolean} immediate Trigger the function on the leading instead ++ of the trailing edge of the wait interval. Defaults to false. ++ @return {Array} Timer information for use in canceling, see `cancel`. ++ @public ++ */ ++ ++ function debounce(...args) { ++ // @ts-expect-error TS doesn't like the rest args here ++ return _backburner.debounce(...args); ++ } ++ ++ /** ++ Ensure that the target method is never called more frequently than ++ the specified spacing period. The target method is called immediately. ++ ++ ```javascript ++ import { throttle } from '@ember/runloop'; ++ ++ function whoRan() { ++ console.log(this.name + ' ran.'); ++ } ++ ++ let myContext = { name: 'throttle' }; ++ ++ throttle(myContext, whoRan, 150); ++ // whoRan is invoked with context myContext ++ // console logs 'throttle ran.' ++ ++ // 50ms passes ++ throttle(myContext, whoRan, 150); ++ ++ // 50ms passes ++ throttle(myContext, whoRan, 150); ++ ++ // 150ms passes ++ throttle(myContext, whoRan, 150); ++ // whoRan is invoked with context myContext ++ // console logs 'throttle ran.' ++ ``` ++ ++ @method throttle ++ @static ++ @for @ember/runloop ++ @param {Object} [target] target of method to invoke ++ @param {Function|String} method The method to invoke. ++ May be a function or a string. If you pass a string ++ then it will be looked up on the passed target. ++ @param {Object} [args*] Optional arguments to pass to the timeout. ++ @param {Number} spacing Number of milliseconds to space out requests. ++ @param {Boolean} immediate Trigger the function on the leading instead ++ of the trailing edge of the wait interval. Defaults to true. ++ @return {Array} Timer information for use in canceling, see `cancel`. ++ @public ++ */ ++ ++ function throttle(...args) { ++ // @ts-expect-error TS doesn't like the rest args here ++ return _backburner.throttle(...args); ++ } ++ ++ const emberRunloopIndex = /*#__PURE__*/Object.defineProperty({ ++ __proto__: null, ++ _backburner, ++ _cancelTimers, ++ _getCurrentRunLoop, ++ _hasScheduledTimers, ++ _queues, ++ _rsvpErrorQueue, ++ begin, ++ bind, ++ cancel, ++ debounce, ++ end, ++ join, ++ later, ++ next, ++ once, ++ run: run$1, ++ schedule, ++ scheduleOnce, ++ throttle ++ }, Symbol.toStringTag, { value: 'Module' }); ++ ++ // This is defined as a separate interface so that it can be used in the definition of ++ // `Owner` without also including the `__container__` property. ++ ++ /** ++ ContainerProxyMixin is used to provide public access to specific ++ container functionality. ++ ++ @class ContainerProxyMixin ++ @extends ContainerProxy ++ @private ++ */ ++ ++ const ContainerProxyMixin = Mixin.create({ ++ /** ++ The container stores state. ++ @private ++ @property {Ember.Container} __container__ ++ */ ++ __container__: null, ++ ownerInjection() { ++ return this.__container__.ownerInjection(); ++ }, ++ lookup(fullName, options) { ++ return this.__container__.lookup(fullName, options); ++ }, ++ destroy() { ++ let container = this.__container__; ++ if (container) { ++ join(() => { ++ container.destroy(); ++ schedule('destroy', container, 'finalizeDestroy'); ++ }); ++ } ++ this._super(); ++ }, ++ factoryFor(fullName) { ++ return this.__container__.factoryFor(fullName); ++ } ++ }); ++ ++ const emberinternalsRuntimeLibMixinsContainerProxy = /*#__PURE__*/Object.defineProperty({ ++ __proto__: null, ++ default: ContainerProxyMixin ++ }, Symbol.toStringTag, { value: 'Module' }); ++ ++ /** ++ @module ember ++ */ ++ ++ /** ++ Implements some standard methods for comparing objects. Add this mixin to ++ any class you create that can compare its instances. ++ ++ You should implement the `compare()` method. ++ ++ @class Comparable ++ @namespace Ember ++ @since Ember 0.9 ++ @private ++ */ ++ ++ const Comparable = Mixin.create({ ++ /** ++ __Required.__ You must implement this method to apply this mixin. ++ Override to return the result of the comparison of the two parameters. The ++ compare method should return: ++ - `-1` if `a < b` ++ - `0` if `a == b` ++ - `1` if `a > b` ++ Default implementation raises an exception. ++ @method compare ++ @param a {Object} the first object to compare ++ @param b {Object} the second object to compare ++ @return {Number} the result of the comparison ++ @private ++ */ ++ compare: null ++ }); ++ ++ const emberinternalsRuntimeLibMixinsComparable = /*#__PURE__*/Object.defineProperty({ ++ __proto__: null, ++ default: Comparable ++ }, Symbol.toStringTag, { value: 'Module' }); ++ ++ /** ++ @module ember ++ */ ++ ++ ++ /** ++ `Ember.ActionHandler` is available on some familiar classes including ++ `Route`, `Component`, and `Controller`. ++ (Internally the mixin is used by `Ember.CoreView`, `Ember.ControllerMixin`, ++ and `Route` and available to the above classes through ++ inheritance.) ++ ++ @class ActionHandler ++ @namespace Ember ++ @private ++ */ ++ ++ const ActionHandler = Mixin.create({ ++ mergedProperties: ['actions'], ++ /** ++ The collection of functions, keyed by name, available on this ++ `ActionHandler` as action targets. ++ These functions will be invoked when a matching `{{action}}` is triggered ++ from within a template and the application's current route is this route. ++ Actions can also be invoked from other parts of your application ++ via `ActionHandler#send`. ++ The `actions` hash will inherit action handlers from ++ the `actions` hash defined on extended parent classes ++ or mixins rather than just replace the entire hash, e.g.: + ```app/mixins/can-display-banner.js + import Mixin from '@ember/object/mixin'; + export default Mixin.create({ +@@ -18678,541 +18685,920 @@ var define, require; + onerrorDefault + }, Symbol.toStringTag, { value: 'Module' }); + ++ const { ++ isArray: isArray$3 ++ } = Array; + /** +- @module @ember/utils ++ @module @ember/array + */ + /** +- Returns true if the passed value is null or undefined. This avoids errors +- from JSLint complaining about use of ==, which can be technically +- confusing. +- +- ```javascript +- isNone(null); // true +- isNone(undefined); // true +- isNone(''); // false +- isNone([]); // false +- isNone(function() {}); // false +- ``` +- +- @method isNone +- @static +- @for @ember/utils +- @param {Object} obj Value to test +- @return {Boolean} +- @public +- */ +- function isNone(obj) { +- return obj === null || obj === undefined; +- } ++ Forces the passed object to be part of an array. If the object is already ++ an array, it will return the object. Otherwise, it will add the object to ++ an array. If object is `null` or `undefined`, it will return an empty array. + +- const emberUtilsLibIsNone = /*#__PURE__*/Object.defineProperty({ +- __proto__: null, +- default: isNone +- }, Symbol.toStringTag, { value: 'Module' }); ++ ```javascript ++ import { makeArray } from '@ember/array'; ++ import ArrayProxy from '@ember/array/proxy'; + +- /** +- @module @ember/utils +- */ +- /** +- Verifies that a value is `null` or `undefined`, an empty string, or an empty +- array. ++ makeArray(); // [] ++ makeArray(null); // [] ++ makeArray(undefined); // [] ++ makeArray('lindsay'); // ['lindsay'] ++ makeArray([1, 2, 42]); // [1, 2, 42] + +- Constrains the rules on `isNone` by returning true for empty strings and +- empty arrays. ++ let proxy = ArrayProxy.create({ content: [] }); + +- If the value is an object with a `size` property of type number, it is used +- to check emptiness. ++ makeArray(proxy) === proxy; // false ++ ``` + +- ```javascript +- isEmpty(null); // true +- isEmpty(undefined); // true +- isEmpty(''); // true +- isEmpty([]); // true +- isEmpty({ size: 0}); // true +- isEmpty({}); // false +- isEmpty('Adam Hawkins'); // false +- isEmpty([0,1,2]); // false +- isEmpty('\n\t'); // false +- isEmpty(' '); // false +- isEmpty({ size: 1 }) // false +- isEmpty({ size: () => 0 }) // false +- ``` ++ @method makeArray ++ @static ++ @for @ember/array ++ @param {Object} obj the object ++ @return {Array} ++ @private ++ */ + +- @method isEmpty +- @static +- @for @ember/utils +- @param {Object} obj Value to test +- @return {Boolean} +- @public +- */ +- function isEmpty$3(obj) { ++ function makeArray(obj) { + if (obj === null || obj === undefined) { +- return true; +- } +- if (!hasUnknownProperty(obj) && typeof obj.size === 'number') { +- return !obj.size; +- } +- if (typeof obj === 'object') { +- let size = get$2(obj, 'size'); +- if (typeof size === 'number') { +- return !size; +- } +- let length = get$2(obj, 'length'); +- if (typeof length === 'number') { +- return !length; +- } +- } +- if (typeof obj.length === 'number' && typeof obj !== 'function') { +- return !obj.length; ++ return []; + } +- return false; ++ return isArray$3(obj) ? obj : [obj]; + } + +- const emberUtilsLibIsEmpty = /*#__PURE__*/Object.defineProperty({ ++ const emberArrayLibMakeArray = /*#__PURE__*/Object.defineProperty({ + __proto__: null, +- default: isEmpty$3 ++ default: makeArray + }, Symbol.toStringTag, { value: 'Module' }); + + /** +- @module @ember/utils ++ @module @ember/object/core + */ +- /** +- A value is blank if it is empty or a whitespace string. + +- ```javascript +- import { isBlank } from '@ember/utils'; + +- isBlank(null); // true +- isBlank(undefined); // true +- isBlank(''); // true +- isBlank([]); // true +- isBlank('\n\t'); // true +- isBlank(' '); // true +- isBlank({}); // false +- isBlank('\n\t Hello'); // false +- isBlank('Hello world'); // false +- isBlank([1,2,3]); // false +- ``` ++ // TODO: Is this correct? + +- @method isBlank +- @static +- @for @ember/utils +- @param {Object} obj Value to test +- @return {Boolean} +- @since 1.5.0 +- @public +- */ +- function isBlank(obj) { +- return isEmpty$3(obj) || typeof obj === 'string' && /\S/.test(obj) === false; ++ function hasSetUnknownProperty(val) { ++ return typeof val === 'object' && val !== null && typeof val.setUnknownProperty === 'function'; + } ++ function hasToStringExtension(val) { ++ return typeof val === 'object' && val !== null && typeof val.toStringExtension === 'function'; ++ } ++ const reopen = Mixin.prototype.reopen; ++ const wasApplied = new WeakSet(); ++ const prototypeMixinMap = new WeakMap(); ++ const initCalled = new WeakSet() ; // only used in debug builds to enable the proxy trap + +- const emberUtilsLibIsBlank = /*#__PURE__*/Object.defineProperty({ +- __proto__: null, +- default: isBlank +- }, Symbol.toStringTag, { value: 'Module' }); +- +- /** +- @module @ember/utils +- */ +- /** +- A value is present if it not `isBlank`. +- +- ```javascript +- isPresent(null); // false +- isPresent(undefined); // false +- isPresent(''); // false +- isPresent(' '); // false +- isPresent('\n\t'); // false +- isPresent([]); // false +- isPresent({ length: 0 }); // false +- isPresent(false); // true +- isPresent(true); // true +- isPresent('string'); // true +- isPresent(0); // true +- isPresent(function() {}); // true +- isPresent({}); // true +- isPresent('\n\t Hello'); // true +- isPresent([1, 2, 3]); // true +- ``` +- +- @method isPresent +- @static +- @for @ember/utils +- @param {Object} obj Value to test +- @return {Boolean} +- @since 1.8.0 +- @public +- */ +- function isPresent$1(obj) { +- return !isBlank(obj); ++ const destroyCalled = new Set(); ++ function ensureDestroyCalled(instance) { ++ if (!destroyCalled.has(instance)) { ++ instance.destroy(); ++ } + } ++ function initialize(obj, properties) { ++ let m = meta(obj); ++ if (properties !== undefined) { ++ (!(typeof properties === 'object' && properties !== null) && assert$1('EmberObject.create only accepts objects.', typeof properties === 'object' && properties !== null)); ++ (!(!(properties instanceof Mixin)) && assert$1('EmberObject.create no longer supports mixing in other ' + 'definitions, use .extend & .create separately instead.', !(properties instanceof Mixin))); ++ let concatenatedProperties = obj.concatenatedProperties; ++ let mergedProperties = obj.mergedProperties; ++ let keyNames = Object.keys(properties); ++ for (let keyName of keyNames) { ++ // SAFETY: this cast as a Record is safe because all object types can be ++ // indexed in JS, and we explicitly type it as returning `unknown`, so the ++ // result *must* be checked below. ++ let value = properties[keyName]; ++ (!(!isClassicDecorator(value)) && assert$1('EmberObject.create no longer supports defining computed ' + 'properties. Define computed properties using extend() or reopen() ' + 'before calling create().', !isClassicDecorator(value))); ++ (!(!(typeof value === 'function' && value.toString().indexOf('._super') !== -1)) && assert$1('EmberObject.create no longer supports defining methods that call _super.', !(typeof value === 'function' && value.toString().indexOf('._super') !== -1))); ++ (!(!(keyName === 'actions' && ActionHandler.detect(obj))) && assert$1('`actions` must be provided at extend time, not at create time, ' + 'when Ember.ActionHandler is used (i.e. views, controllers & routes).', !(keyName === 'actions' && ActionHandler.detect(obj)))); ++ let possibleDesc = descriptorForProperty(obj, keyName, m); ++ let isDescriptor = possibleDesc !== undefined; ++ if (!isDescriptor) { ++ if (concatenatedProperties !== undefined && concatenatedProperties.length > 0 && concatenatedProperties.includes(keyName)) { ++ let baseValue = obj[keyName]; ++ if (baseValue) { ++ value = makeArray(baseValue).concat(value); ++ } else { ++ value = makeArray(value); ++ } ++ } ++ if (mergedProperties !== undefined && mergedProperties.length > 0 && mergedProperties.includes(keyName)) { ++ let baseValue = obj[keyName]; ++ value = Object.assign({}, baseValue, value); ++ } ++ } ++ if (isDescriptor) { ++ possibleDesc.set(obj, keyName, value); ++ } else if (hasSetUnknownProperty(obj) && !(keyName in obj)) { ++ obj.setUnknownProperty(keyName, value); ++ } else { ++ { ++ defineProperty(obj, keyName, null, value, m); // setup mandatory setter ++ } ++ } ++ } ++ } + +- const emberUtilsLibIsPresent = /*#__PURE__*/Object.defineProperty({ +- __proto__: null, +- default: isPresent$1 +- }, Symbol.toStringTag, { value: 'Module' }); ++ // using DEBUG here to avoid the extraneous variable when not needed ++ { ++ initCalled.add(obj); ++ } ++ obj.init(properties); ++ m.unsetInitializing(); ++ let observerEvents = m.observerEvents(); ++ if (observerEvents !== undefined) { ++ for (let i = 0; i < observerEvents.length; i++) { ++ activateObserver(obj, observerEvents[i].event, observerEvents[i].sync); ++ } ++ } ++ sendEvent(obj, 'init', undefined, undefined, m); ++ } + + /** +- @module @ember/utils +- */ +- /** +- Compares two objects, returning true if they are equal. +- +- ```javascript +- import { isEqual } from '@ember/utils'; +- +- isEqual('hello', 'hello'); // true +- isEqual(1, 2); // false +- ``` ++ `CoreObject` is the base class for all Ember constructs. It establishes a ++ class system based on Ember's Mixin system, and provides the basis for the ++ Ember Object Model. `CoreObject` should generally not be used directly, ++ instead you should use `EmberObject`. + +- `isEqual` is a more specific comparison than a triple equal comparison. +- It will call the `isEqual` instance method on the objects being +- compared, allowing finer control over when objects should be considered +- equal to each other. ++ ## Usage + +- ```javascript +- import { isEqual } from '@ember/utils'; +- import EmberObject from '@ember/object'; ++ You can define a class by extending from `CoreObject` using the `extend` ++ method: + +- let Person = EmberObject.extend({ +- isEqual(other) { return this.ssn == other.ssn; } ++ ```js ++ const Person = CoreObject.extend({ ++ name: 'Tomster', + }); +- +- let personA = Person.create({name: 'Muhammad Ali', ssn: '123-45-6789'}); +- let personB = Person.create({name: 'Cassius Clay', ssn: '123-45-6789'}); +- +- isEqual(personA, personB); // true + ``` + +- Due to the expense of array comparisons, collections will never be equal to +- each other even if each of their items are equal to each other. ++ For detailed usage, see the [Object Model](https://guides.emberjs.com/release/object-model/) ++ section of the guides. + +- ```javascript +- import { isEqual } from '@ember/utils'; ++ ## Usage with Native Classes + +- isEqual([4, 2], [4, 2]); // false +- ``` ++ Native JavaScript `class` syntax can be used to extend from any `CoreObject` ++ based class: + +- @method isEqual +- @for @ember/utils +- @static +- @param {Object} a first object to compare +- @param {Object} b second object to compare +- @return {Boolean} +- @public +- */ +- function isEqual(a, b) { +- if (a && typeof a.isEqual === 'function') { +- return a.isEqual(b); +- } +- if (a instanceof Date && b instanceof Date) { +- return a.getTime() === b.getTime(); ++ ```js ++ class Person extends CoreObject { ++ init() { ++ super.init(...arguments); ++ this.name = 'Tomster'; ++ } + } +- return a === b; +- } ++ ``` + +- const emberUtilsLibIsEqual = /*#__PURE__*/Object.defineProperty({ +- __proto__: null, +- default: isEqual +- }, Symbol.toStringTag, { value: 'Module' }); ++ Some notes about `class` usage: + +- // ........................................ +- // TYPING & ARRAY MESSAGING +- // +- const TYPE_MAP = { +- '[object Boolean]': 'boolean', +- '[object Number]': 'number', +- '[object String]': 'string', +- '[object Function]': 'function', +- '[object AsyncFunction]': 'function', +- '[object Array]': 'array', +- '[object Date]': 'date', +- '[object RegExp]': 'regexp', +- '[object Object]': 'object', +- '[object FileList]': 'filelist' +- }; +- const { +- toString +- } = Object.prototype; ++ * `new` syntax is not currently supported with classes that extend from ++ `EmberObject` or `CoreObject`. You must continue to use the `create` method ++ when making new instances of classes, even if they are defined using native ++ class syntax. If you want to use `new` syntax, consider creating classes ++ which do _not_ extend from `EmberObject` or `CoreObject`. Ember features, ++ such as computed properties and decorators, will still work with base-less ++ classes. ++ * Instead of using `this._super()`, you must use standard `super` syntax in ++ native classes. See the [MDN docs on classes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes#Super_class_calls_with_super) ++ for more details. ++ * Native classes support using [constructors](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes#Constructor) ++ to set up newly-created instances. Ember uses these to, among other things, ++ support features that need to retrieve other entities by name, like Service ++ injection and `getOwner`. To ensure your custom instance setup logic takes ++ place after this important work is done, avoid using the `constructor` in ++ favor of `init`. ++ * Properties passed to `create` will be available on the instance by the time ++ `init` runs, so any code that requires these values should work at that ++ time. ++ * Using native classes, and switching back to the old Ember Object model is ++ fully supported. + +- /** +- @module @ember/utils ++ @class CoreObject ++ @public + */ +- /** +- Returns a consistent type for the passed object. +- +- Use this instead of the built-in `typeof` to get the type of an item. +- It will return the same result across all browsers and includes a bit +- more detail. Here is what will be returned: +- +- | Return Value | Meaning | +- |---------------|------------------------------------------------------| +- | 'string' | String primitive or String object. | +- | 'number' | Number primitive or Number object. | +- | 'boolean' | Boolean primitive or Boolean object. | +- | 'null' | Null value | +- | 'undefined' | Undefined value | +- | 'function' | A function | +- | 'array' | An instance of Array | +- | 'regexp' | An instance of RegExp | +- | 'date' | An instance of Date | +- | 'filelist' | An instance of FileList | +- | 'class' | An Ember class (created using EmberObject.extend()) | +- | 'instance' | An Ember object instance | +- | 'error' | An instance of the Error object | +- | 'object' | A JavaScript object not inheriting from EmberObject | + +- Examples: ++ class CoreObject { ++ /** @internal */ ++ [OWNER$1]; ++ constructor(owner) { ++ this[OWNER$1] = owner; + +- ```javascript +- import { A } from '@ember/array'; +- import { typeOf } from '@ember/utils'; +- import EmberObject from '@ember/object'; ++ // prepare prototype... ++ this.constructor.proto(); ++ let self; ++ if (hasUnknownProperty(this)) { ++ let messageFor = (obj, property) => { ++ return `You attempted to access the \`${String(property)}\` property (of ${obj}).\n` + `Since Ember 3.1, this is usually fine as you no longer need to use \`.get()\`\n` + `to access computed properties. However, in this case, the object in question\n` + `is a special kind of Ember object (a proxy). Therefore, it is still necessary\n` + `to use \`.get('${String(property)}')\` in this case.\n\n` + `If you encountered this error because of third-party code that you don't control,\n` + `there is more information at https://github.com/emberjs/ember.js/issues/16148, and\n` + `you can help us improve this error message by telling us more about what happened in\n` + `this situation.`; ++ }; + +- typeOf(); // 'undefined' +- typeOf(null); // 'null' +- typeOf(undefined); // 'undefined' +- typeOf('michael'); // 'string' +- typeOf(new String('michael')); // 'string' +- typeOf(101); // 'number' +- typeOf(new Number(101)); // 'number' +- typeOf(true); // 'boolean' +- typeOf(new Boolean(true)); // 'boolean' +- typeOf(A); // 'function' +- typeOf(A()); // 'array' +- typeOf([1, 2, 90]); // 'array' +- typeOf(/abc/); // 'regexp' +- typeOf(new Date()); // 'date' +- typeOf(event.target.files); // 'filelist' +- typeOf(EmberObject.extend()); // 'class' +- typeOf(EmberObject.create()); // 'instance' +- typeOf(new Error('teamocil')); // 'error' ++ /* globals Proxy Reflect */ ++ self = new Proxy(this, { ++ get(target, property, receiver) { ++ if (property === PROXY_CONTENT) { ++ return target; ++ } else if ( ++ // init called will be set on the proxy, not the target, so get with the receiver ++ !initCalled.has(receiver) || typeof property === 'symbol' || isInternalSymbol(property) || property === 'toJSON' || property === 'toString' || property === 'toStringExtension' || property === 'didDefineProperty' || property === 'willWatchProperty' || property === 'didUnwatchProperty' || property === 'didAddListener' || property === 'didRemoveListener' || property === 'isDescriptor' || property === '_onLookup' || property in target) { ++ return Reflect.get(target, property, receiver); ++ } ++ let value = target.unknownProperty.call(receiver, property); ++ if (typeof value !== 'function') { ++ (!(value === undefined || value === null) && assert$1(messageFor(receiver, property), value === undefined || value === null)); ++ } ++ } ++ }); ++ } else { ++ self = this; ++ } ++ const destroyable = self; ++ registerDestructor$1(self, ensureDestroyCalled, true); ++ registerDestructor$1(self, () => destroyable.willDestroy()); + +- // 'normal' JavaScript object +- typeOf({ a: 'b' }); // 'object' +- ``` ++ // disable chains ++ let m = meta(self); ++ m.setInitializing(); + +- @method typeOf +- @for @ember/utils +- @param item the item to check +- @return {String} the type +- @public +- @static +- */ +- function typeOf(item) { +- if (item === null) { +- return 'null'; +- } +- if (item === undefined) { +- return 'undefined'; +- } +- let ret = TYPE_MAP[toString.call(item)] || 'object'; +- if (ret === 'function') { +- if (EmberCoreObject.detect(item)) { +- ret = 'class'; +- } +- } else if (ret === 'object') { +- if (item instanceof Error) { +- ret = 'error'; +- } else if (item instanceof EmberCoreObject) { +- ret = 'instance'; +- } else if (item instanceof Date) { +- ret = 'date'; ++ // only return when in debug builds and `self` is the proxy created above ++ if (self !== this) { ++ return self; + } + } +- return ret; +- } ++ reopen(...args) { ++ applyMixin(this, args); ++ return this; ++ } + +- const emberUtilsLibTypeOf = /*#__PURE__*/Object.defineProperty({ +- __proto__: null, +- default: typeOf +- }, Symbol.toStringTag, { value: 'Module' }); ++ /** ++ An overridable method called when objects are instantiated. By default, ++ does nothing unless it is overridden during class definition. ++ Example: ++ ```javascript ++ import EmberObject from '@ember/object'; ++ const Person = EmberObject.extend({ ++ init() { ++ alert(`Name is ${this.get('name')}`); ++ } ++ }); ++ let steve = Person.create({ ++ name: 'Steve' ++ }); ++ // alerts 'Name is Steve'. ++ ``` ++ NOTE: If you do override `init` for a framework class like `Component` ++ from `@ember/component`, be sure to call `this._super(...arguments)` ++ in your `init` declaration! ++ If you don't, Ember may not have an opportunity to ++ do important setup work, and you'll see strange behavior in your ++ application. ++ @method init ++ @public ++ */ ++ init(_properties) {} + +- const TYPE_ORDER = { +- undefined: 0, +- null: 1, +- boolean: 2, +- number: 3, +- string: 4, +- array: 5, +- object: 6, +- instance: 7, +- function: 8, +- class: 9, +- date: 10, +- regexp: 11, +- filelist: 12, +- error: 13 +- }; +- // +- // the spaceship operator +- // +- // `. ___ +- // __,' __`. _..----....____ +- // __...--.'``;. ,. ;``--..__ .' ,-._ _.-' +- // _..-''-------' `' `' `' O ``-''._ (,;') _,' +- // ,'________________ \`-._`-',' +- // `._ ```````````------...___ '-.._'-: +- // ```--.._ ,. ````--...__\-. +- // `.--. `-` "INFINITY IS LESS ____ | |` +- // `. `. THAN BEYOND" ,'`````. ; ;` +- // `._`. __________ `. \'__/` +- // `-:._____/______/___/____`. \ ` +- // | `._ `. \ +- // `._________`-. `. `.___ +- // SSt `------'` +- function spaceship(a, b) { +- // SAFETY: `Math.sign` always returns `-1` for negative, `0` for zero, and `1` +- // for positive numbers. (The extra precision is useful for the way we use +- // this in the context of `compare`.) +- return Math.sign(a - b); +- } +- +- /** +- @module @ember/utils +- */ +- +- /** +- Compares two javascript values and returns: +- +- - -1 if the first is smaller than the second, +- - 0 if both are equal, +- - 1 if the first is greater than the second. +- +- ```javascript +- import { compare } from '@ember/utils'; +- +- compare('hello', 'hello'); // 0 +- compare('abc', 'dfg'); // -1 +- compare(2, 1); // 1 +- ``` +- +- If the types of the two objects are different precedence occurs in the +- following order, with types earlier in the list considered `<` types +- later in the list: +- +- - undefined +- - null +- - boolean +- - number +- - string +- - array +- - object +- - instance +- - function +- - class +- - date +- +- ```javascript +- import { compare } from '@ember/utils'; ++ /** ++ Defines the properties that will be concatenated from the superclass ++ (instead of overridden). ++ By default, when you extend an Ember class a property defined in ++ the subclass overrides a property with the same name that is defined ++ in the superclass. However, there are some cases where it is preferable ++ to build up a property's value by combining the superclass' property ++ value with the subclass' value. An example of this in use within Ember ++ is the `classNames` property of `Component` from `@ember/component`. ++ Here is some sample code showing the difference between a concatenated ++ property and a normal one: ++ ```javascript ++ import EmberObject from '@ember/object'; ++ const Bar = EmberObject.extend({ ++ // Configure which properties to concatenate ++ concatenatedProperties: ['concatenatedProperty'], ++ someNonConcatenatedProperty: ['bar'], ++ concatenatedProperty: ['bar'] ++ }); ++ const FooBar = Bar.extend({ ++ someNonConcatenatedProperty: ['foo'], ++ concatenatedProperty: ['foo'] ++ }); ++ let fooBar = FooBar.create(); ++ fooBar.get('someNonConcatenatedProperty'); // ['foo'] ++ fooBar.get('concatenatedProperty'); // ['bar', 'foo'] ++ ``` ++ This behavior extends to object creation as well. Continuing the ++ above example: ++ ```javascript ++ let fooBar = FooBar.create({ ++ someNonConcatenatedProperty: ['baz'], ++ concatenatedProperty: ['baz'] ++ }) ++ fooBar.get('someNonConcatenatedProperty'); // ['baz'] ++ fooBar.get('concatenatedProperty'); // ['bar', 'foo', 'baz'] ++ ``` ++ Adding a single property that is not an array will just add it in the array: ++ ```javascript ++ let fooBar = FooBar.create({ ++ concatenatedProperty: 'baz' ++ }) ++ view.get('concatenatedProperty'); // ['bar', 'foo', 'baz'] ++ ``` ++ Using the `concatenatedProperties` property, we can tell Ember to mix the ++ content of the properties. ++ In `Component` the `classNames`, `classNameBindings` and ++ `attributeBindings` properties are concatenated. ++ This feature is available for you to use throughout the Ember object model, ++ although typical app developers are likely to use it infrequently. Since ++ it changes expectations about behavior of properties, you should properly ++ document its usage in each individual concatenated property (to not ++ mislead your users to think they can override the property in a subclass). ++ @property concatenatedProperties ++ @type Array ++ @default null ++ @public ++ */ + +- compare('hello', 50); // 1 +- compare(50, 'hello'); // -1 +- ``` ++ /** ++ Defines the properties that will be merged from the superclass ++ (instead of overridden). ++ By default, when you extend an Ember class a property defined in ++ the subclass overrides a property with the same name that is defined ++ in the superclass. However, there are some cases where it is preferable ++ to build up a property's value by merging the superclass property value ++ with the subclass property's value. An example of this in use within Ember ++ is the `queryParams` property of routes. ++ Here is some sample code showing the difference between a merged ++ property and a normal one: ++ ```javascript ++ import EmberObject from '@ember/object'; ++ const Bar = EmberObject.extend({ ++ // Configure which properties are to be merged ++ mergedProperties: ['mergedProperty'], ++ someNonMergedProperty: { ++ nonMerged: 'superclass value of nonMerged' ++ }, ++ mergedProperty: { ++ page: { replace: false }, ++ limit: { replace: true } ++ } ++ }); ++ const FooBar = Bar.extend({ ++ someNonMergedProperty: { ++ completelyNonMerged: 'subclass value of nonMerged' ++ }, ++ mergedProperty: { ++ limit: { replace: false } ++ } ++ }); ++ let fooBar = FooBar.create(); ++ fooBar.get('someNonMergedProperty'); ++ // => { completelyNonMerged: 'subclass value of nonMerged' } ++ // ++ // Note the entire object, including the nonMerged property of ++ // the superclass object, has been replaced ++ fooBar.get('mergedProperty'); ++ // => { ++ // page: {replace: false}, ++ // limit: {replace: false} ++ // } ++ // ++ // Note the page remains from the superclass, and the ++ // `limit` property's value of `false` has been merged from ++ // the subclass. ++ ``` ++ This behavior is not available during object `create` calls. It is only ++ available at `extend` time. ++ In `Route` the `queryParams` property is merged. ++ This feature is available for you to use throughout the Ember object model, ++ although typical app developers are likely to use it infrequently. Since ++ it changes expectations about behavior of properties, you should properly ++ document its usage in each individual merged property (to not ++ mislead your users to think they can override the property in a subclass). ++ @property mergedProperties ++ @type Array ++ @default null ++ @public ++ */ + +- @method compare +- @for @ember/utils +- @static +- @param {Object} v First value to compare +- @param {Object} w Second value to compare +- @return {Number} -1 if v < w, 0 if v = w and 1 if v > w. +- @public +- */ +- function compare(v, w) { +- if (v === w) { +- return 0; ++ /** ++ Destroyed object property flag. ++ if this property is `true` the observers and bindings were already ++ removed by the effect of calling the `destroy()` method. ++ @property isDestroyed ++ @default false ++ @public ++ */ ++ get isDestroyed() { ++ return isDestroyed(this); + } +- let type1 = typeOf(v); +- let type2 = typeOf(w); +- if (type1 === 'instance' && isComparable(v) && v.constructor.compare) { +- return v.constructor.compare(v, w); ++ set isDestroyed(_value) { ++ (assert$1(`You cannot set \`${this}.isDestroyed\` directly, please use \`.destroy()\`.`, false)); + } +- if (type2 === 'instance' && isComparable(w) && w.constructor.compare) { +- // SAFETY: Multiplying by a negative just changes the sign +- return w.constructor.compare(w, v) * -1; ++ ++ /** ++ Destruction scheduled flag. The `destroy()` method has been called. ++ The object stays intact until the end of the run loop at which point ++ the `isDestroyed` flag is set. ++ @property isDestroying ++ @default false ++ @public ++ */ ++ get isDestroying() { ++ return isDestroying(this); + } +- let res = spaceship(TYPE_ORDER[type1], TYPE_ORDER[type2]); +- if (res !== 0) { +- return res; ++ set isDestroying(_value) { ++ (assert$1(`You cannot set \`${this}.isDestroying\` directly, please use \`.destroy()\`.`, false)); + } + +- // types are equal - so we have to check values now +- switch (type1) { +- case 'boolean': +- (!(typeof v === 'boolean' && typeof w === 'boolean') && assert$1('both are boolean', typeof v === 'boolean' && typeof w === 'boolean')); +- return spaceship(Number(v), Number(w)); +- case 'number': +- (!(typeof v === 'number' && typeof w === 'number') && assert$1('both are numbers', typeof v === 'number' && typeof w === 'number')); +- return spaceship(v, w); +- case 'string': +- (!(typeof v === 'string' && typeof w === 'string') && assert$1('both are strings', typeof v === 'string' && typeof w === 'string')); +- return spaceship(v.localeCompare(w), 0); +- case 'array': +- { +- (!(Array.isArray(v) && Array.isArray(w)) && assert$1('both are arrays', Array.isArray(v) && Array.isArray(w))); +- let vLen = v.length; +- let wLen = w.length; +- let len = Math.min(vLen, wLen); +- for (let i = 0; i < len; i++) { +- let r = compare(v[i], w[i]); +- if (r !== 0) { +- return r; +- } +- } +- +- // all elements are equal now +- // shorter array should be ordered first +- return spaceship(vLen, wLen); +- } +- case 'instance': +- if (isComparable(v) && v.compare) { +- return v.compare(v, w); +- } +- return 0; +- case 'date': +- (!(v instanceof Date && w instanceof Date) && assert$1('both are dates', v instanceof Date && w instanceof Date)); +- return spaceship(v.getTime(), w.getTime()); +- default: +- return 0; ++ /** ++ Destroys an object by setting the `isDestroyed` flag and removing its ++ metadata, which effectively destroys observers and bindings. ++ If you try to set a property on a destroyed object, an exception will be ++ raised. ++ Note that destruction is scheduled for the end of the run loop and does not ++ happen immediately. It will set an isDestroying flag immediately. ++ @method destroy ++ @return {EmberObject} receiver ++ @public ++ */ ++ destroy() { ++ // Used to ensure that manually calling `.destroy()` does not immediately call destroy again ++ destroyCalled.add(this); ++ try { ++ destroy(this); ++ } finally { ++ destroyCalled.delete(this); ++ } ++ return this; + } +- } +- function isComparable(value) { +- return Comparable.detect(value); +- } + +- const emberUtilsLibCompare = /*#__PURE__*/Object.defineProperty({ +- __proto__: null, +- default: compare +- }, Symbol.toStringTag, { value: 'Module' }); ++ /** ++ Override to implement teardown. ++ @method willDestroy ++ @public ++ */ ++ willDestroy() {} + +- const emberUtilsIndex = /*#__PURE__*/Object.defineProperty({ +- __proto__: null, +- compare, +- isBlank, +- isEmpty: isEmpty$3, +- isEqual, +- isNone, +- isPresent: isPresent$1, +- typeOf +- }, Symbol.toStringTag, { value: 'Module' }); ++ /** ++ Returns a string representation which attempts to provide more information ++ than Javascript's `toString` typically does, in a generic way for all Ember ++ objects. ++ ```javascript ++ import EmberObject from '@ember/object'; ++ const Person = EmberObject.extend(); ++ person = Person.create(); ++ person.toString(); //=> "" ++ ``` ++ If the object's class is not defined on an Ember namespace, it will ++ indicate it is a subclass of the registered superclass: ++ ```javascript ++ const Student = Person.extend(); ++ let student = Student.create(); ++ student.toString(); //=> "<(subclass of Person):ember1025>" ++ ``` ++ If the method `toStringExtension` is defined, its return value will be ++ included in the output. ++ ```javascript ++ const Teacher = Person.extend({ ++ toStringExtension() { ++ return this.get('fullName'); ++ } ++ }); ++ teacher = Teacher.create(); ++ teacher.toString(); //=> "" ++ ``` ++ @method toString ++ @return {String} string representation ++ @public ++ */ ++ toString() { ++ let extension = hasToStringExtension(this) ? `:${this.toStringExtension()}` : ''; ++ return `<${getFactoryFor(this) || '(unknown)'}:${guidFor(this)}${extension}>`; ++ } + +- /** +- @module @ember/object/observable +- */ ++ /** ++ Creates a new subclass. ++ ```javascript ++ import EmberObject from '@ember/object'; ++ const Person = EmberObject.extend({ ++ say(thing) { ++ alert(thing); ++ } ++ }); ++ ``` ++ This defines a new subclass of EmberObject: `Person`. It contains one method: `say()`. ++ You can also create a subclass from any existing class by calling its `extend()` method. ++ For example, you might want to create a subclass of Ember's built-in `Component` class: ++ ```javascript ++ import Component from '@ember/component'; ++ const PersonComponent = Component.extend({ ++ tagName: 'li', ++ classNameBindings: ['isAdministrator'] ++ }); ++ ``` ++ When defining a subclass, you can override methods but still access the ++ implementation of your parent class by calling the special `_super()` method: ++ ```javascript ++ import EmberObject from '@ember/object'; ++ const Person = EmberObject.extend({ ++ say(thing) { ++ let name = this.get('name'); ++ alert(`${name} says: ${thing}`); ++ } ++ }); ++ const Soldier = Person.extend({ ++ say(thing) { ++ this._super(`${thing}, sir!`); ++ }, ++ march(numberOfHours) { ++ alert(`${this.get('name')} marches for ${numberOfHours} hours.`); ++ } ++ }); ++ let yehuda = Soldier.create({ ++ name: 'Yehuda Katz' ++ }); ++ yehuda.say('Yes'); // alerts "Yehuda Katz says: Yes, sir!" ++ ``` ++ The `create()` on line #17 creates an *instance* of the `Soldier` class. ++ The `extend()` on line #8 creates a *subclass* of `Person`. Any instance ++ of the `Person` class will *not* have the `march()` method. ++ You can also pass `Mixin` classes to add additional properties to the subclass. ++ ```javascript ++ import EmberObject from '@ember/object'; ++ import Mixin from '@ember/object/mixin'; ++ const Person = EmberObject.extend({ ++ say(thing) { ++ alert(`${this.get('name')} says: ${thing}`); ++ } ++ }); ++ const SingingMixin = Mixin.create({ ++ sing(thing) { ++ alert(`${this.get('name')} sings: la la la ${thing}`); ++ } ++ }); ++ const BroadwayStar = Person.extend(SingingMixin, { ++ dance() { ++ alert(`${this.get('name')} dances: tap tap tap tap `); ++ } ++ }); ++ ``` ++ The `BroadwayStar` class contains three methods: `say()`, `sing()`, and `dance()`. ++ @method extend ++ @static ++ @for @ember/object ++ @param {Mixin} [mixins]* One or more Mixin classes ++ @param {Object} [arguments]* Object containing values to use within the new class ++ @public ++ */ + ++ static extend(...mixins) { ++ let Class = class extends this {}; ++ reopen.apply(Class.PrototypeMixin, mixins); ++ return Class; ++ } + +- /** +- ## Overview ++ /** ++ Creates an instance of a class. Accepts either no arguments, or an object ++ containing values to initialize the newly instantiated object with. ++ ```javascript ++ import EmberObject from '@ember/object'; ++ const Person = EmberObject.extend({ ++ helloWorld() { ++ alert(`Hi, my name is ${this.get('name')}`); ++ } ++ }); ++ let tom = Person.create({ ++ name: 'Tom Dale' ++ }); ++ tom.helloWorld(); // alerts "Hi, my name is Tom Dale". ++ ``` ++ `create` will call the `init` function if defined during ++ `AnyObject.extend` ++ If no arguments are passed to `create`, it will not set values to the new ++ instance during initialization: ++ ```javascript ++ let noName = Person.create(); ++ noName.helloWorld(); // alerts undefined ++ ``` ++ NOTE: For performance reasons, you cannot declare methods or computed ++ properties during `create`. You should instead declare methods and computed ++ properties when using `extend`. ++ @method create ++ @for @ember/object ++ @static ++ @param [arguments]* ++ @public ++ */ + +- This mixin provides properties and property observing functionality, core +- features of the Ember object model. ++ static create(...args) { ++ let props = args[0]; ++ let instance; ++ if (props !== undefined) { ++ instance = new this(getOwner$2(props)); ++ // TODO(SAFETY): at present, we cannot actually rely on this being set, ++ // because a number of acceptance tests are (incorrectly? Unclear!) ++ // relying on the ability to run through this path with `factory` being ++ // `undefined`. It's *possible* that actually means that the type for ++ // `setFactoryFor()` should allow `undefined`, but we typed it the other ++ // way for good reason! Accordingly, this *casts* `factory`, and the ++ // commented-out `assert()` is here in the hope that we can enable it ++ // after addressing tests *or* updating the call signature here. ++ let factory = getFactoryFor(props); ++ // assert(`missing factory when creating object ${instance}`, factory !== undefined); ++ setFactoryFor(instance, factory); ++ } else { ++ instance = new this(); ++ } ++ if (args.length <= 1) { ++ initialize(instance, props); ++ } else { ++ initialize(instance, flattenProps.apply(this, args)); ++ } + +- Properties and observers allow one object to observe changes to a +- property on another object. This is one of the fundamental ways that +- models, controllers and views communicate with each other in an Ember +- application. ++ // SAFETY: The `initialize` call is responsible to merge the prototype chain ++ // so that this holds. ++ return instance; ++ } + +- Any object that has this mixin applied can be used in observer ++ /** ++ Augments a constructor's prototype with additional ++ properties and functions: ++ ```javascript ++ import EmberObject from '@ember/object'; ++ const MyObject = EmberObject.extend({ ++ name: 'an object' ++ }); ++ o = MyObject.create(); ++ o.get('name'); // 'an object' ++ MyObject.reopen({ ++ say(msg) { ++ console.log(msg); ++ } ++ }); ++ o2 = MyObject.create(); ++ o2.say('hello'); // logs "hello" ++ o.say('goodbye'); // logs "goodbye" ++ ``` ++ To add functions and properties to the constructor itself, ++ see `reopenClass` ++ @method reopen ++ @for @ember/object ++ @static ++ @public ++ */ ++ static reopen(...args) { ++ this.willReopen(); ++ reopen.apply(this.PrototypeMixin, args); ++ return this; ++ } ++ static willReopen() { ++ let p = this.prototype; ++ if (wasApplied.has(p)) { ++ wasApplied.delete(p); ++ ++ // If the base mixin already exists and was applied, create a new mixin to ++ // make sure that it gets properly applied. Reusing the same mixin after ++ // the first `proto` call will cause it to get skipped. ++ if (prototypeMixinMap.has(this)) { ++ prototypeMixinMap.set(this, Mixin.create(this.PrototypeMixin)); ++ } ++ } ++ } ++ ++ /** ++ Augments a constructor's own properties and functions: ++ ```javascript ++ import EmberObject from '@ember/object'; ++ const MyObject = EmberObject.extend({ ++ name: 'an object' ++ }); ++ MyObject.reopenClass({ ++ canBuild: false ++ }); ++ MyObject.canBuild; // false ++ o = MyObject.create(); ++ ``` ++ In other words, this creates static properties and functions for the class. ++ These are only available on the class and not on any instance of that class. ++ ```javascript ++ import EmberObject from '@ember/object'; ++ const Person = EmberObject.extend({ ++ name: '', ++ sayHello() { ++ alert(`Hello. My name is ${this.get('name')}`); ++ } ++ }); ++ Person.reopenClass({ ++ species: 'Homo sapiens', ++ createPerson(name) { ++ return Person.create({ name }); ++ } ++ }); ++ let tom = Person.create({ ++ name: 'Tom Dale' ++ }); ++ let yehuda = Person.createPerson('Yehuda Katz'); ++ tom.sayHello(); // "Hello. My name is Tom Dale" ++ yehuda.sayHello(); // "Hello. My name is Yehuda Katz" ++ alert(Person.species); // "Homo sapiens" ++ ``` ++ Note that `species` and `createPerson` are *not* valid on the `tom` and `yehuda` ++ variables. They are only valid on `Person`. ++ To add functions and properties to instances of ++ a constructor by extending the constructor's prototype ++ see `reopen` ++ @method reopenClass ++ @for @ember/object ++ @static ++ @public ++ */ ++ static reopenClass(...mixins) { ++ applyMixin(this, mixins); ++ return this; ++ } ++ static detect(obj) { ++ if ('function' !== typeof obj) { ++ return false; ++ } ++ while (obj) { ++ if (obj === this) { ++ return true; ++ } ++ obj = obj.superclass; ++ } ++ return false; ++ } ++ static detectInstance(obj) { ++ return obj instanceof this; ++ } ++ ++ /** ++ In some cases, you may want to annotate computed properties with additional ++ metadata about how they function or what values they operate on. For ++ example, computed property functions may close over variables that are then ++ no longer available for introspection. ++ You can pass a hash of these values to a computed property like this: ++ ```javascript ++ import { computed } from '@ember/object'; ++ person: computed(function() { ++ let personId = this.get('personId'); ++ return Person.create({ id: personId }); ++ }).meta({ type: Person }) ++ ``` ++ Once you've done this, you can retrieve the values saved to the computed ++ property from your class like this: ++ ```javascript ++ MyClass.metaForProperty('person'); ++ ``` ++ This will return the original hash that was passed to `meta()`. ++ @static ++ @method metaForProperty ++ @param key {String} property name ++ @private ++ */ ++ static metaForProperty(key) { ++ let proto = this.proto(); // ensure prototype is initialized ++ let possibleDesc = descriptorForProperty(proto, key); ++ (!(possibleDesc !== undefined) && assert$1(`metaForProperty() could not find a computed property with key '${key}'.`, possibleDesc !== undefined)); ++ return possibleDesc._meta || {}; ++ } ++ ++ /** ++ Iterate over each computed property for the class, passing its name ++ and any associated metadata (see `metaForProperty`) to the callback. ++ @static ++ @method eachComputedProperty ++ @param {Function} callback ++ @param {Object} binding ++ @private ++ */ ++ static eachComputedProperty(callback, binding = this) { ++ this.proto(); // ensure prototype is initialized ++ let empty = {}; ++ meta(this.prototype).forEachDescriptors((name, descriptor) => { ++ if (descriptor.enumerable) { ++ let meta = descriptor._meta || empty; ++ callback.call(binding, name, meta); ++ } ++ }); ++ } ++ static get PrototypeMixin() { ++ let prototypeMixin = prototypeMixinMap.get(this); ++ if (prototypeMixin === undefined) { ++ prototypeMixin = Mixin.create(); ++ prototypeMixin.ownerConstructor = this; ++ prototypeMixinMap.set(this, prototypeMixin); ++ } ++ return prototypeMixin; ++ } ++ static get superclass() { ++ let c = Object.getPrototypeOf(this); ++ return c !== Function.prototype ? c : undefined; ++ } ++ static proto() { ++ let p = this.prototype; ++ if (!wasApplied.has(p)) { ++ wasApplied.add(p); ++ let parent = this.superclass; ++ if (parent) { ++ parent.proto(); ++ } ++ ++ // If the prototype mixin exists, apply it. In the case of native classes, ++ // it will not exist (unless the class has been reopened). ++ if (prototypeMixinMap.has(this)) { ++ this.PrototypeMixin.apply(p); ++ } ++ } ++ return p; ++ } ++ static toString() { ++ return `<${getFactoryFor(this) || '(unknown)'}:constructor>`; ++ } ++ static isClass = true; ++ static isMethod = false; ++ static _onLookup; ++ static _lazyInjections; ++ } ++ function flattenProps(...props) { ++ let initProperties = {}; ++ for (let properties of props) { ++ (!(!(properties instanceof Mixin)) && assert$1('EmberObject.create no longer supports mixing in other ' + 'definitions, use .extend & .create separately instead.', !(properties instanceof Mixin))); ++ let keyNames = Object.keys(properties); ++ for (let j = 0, k = keyNames.length; j < k; j++) { ++ let keyName = keyNames[j]; ++ let value = properties[keyName]; ++ initProperties[keyName] = value; ++ } ++ } ++ return initProperties; ++ } ++ { ++ /** ++ Provides lookup-time type validation for injected properties. ++ @private ++ @method _onLookup ++ */ ++ CoreObject._onLookup = function injectedPropertyAssertion(debugContainerKey) { ++ let [type] = debugContainerKey.split(':'); ++ let proto = this.proto(); ++ for (let key in proto) { ++ let desc = descriptorForProperty(proto, key); ++ if (desc && DEBUG_INJECTION_FUNCTIONS.has(desc._getter)) { ++ (!(type === 'controller' || DEBUG_INJECTION_FUNCTIONS.get(desc._getter).type !== 'controller') && assert$1(`Defining \`${key}\` as an injected controller property on a non-controller (\`${debugContainerKey}\`) is not allowed.`, type === 'controller' || DEBUG_INJECTION_FUNCTIONS.get(desc._getter).type !== 'controller')); ++ } ++ } ++ }; ++ ++ /** ++ Returns a hash of property names and container names that injected ++ properties will lookup on the container lazily. ++ @method _lazyInjections ++ @return {Object} Hash of all lazy injected property keys to container names ++ @private ++ */ ++ CoreObject._lazyInjections = function () { ++ let injections = {}; ++ let proto = this.proto(); ++ let key; ++ let desc; ++ for (key in proto) { ++ desc = descriptorForProperty(proto, key); ++ if (desc && DEBUG_INJECTION_FUNCTIONS.has(desc._getter)) { ++ let { ++ namespace, ++ source, ++ type, ++ name ++ } = DEBUG_INJECTION_FUNCTIONS.get(desc._getter); ++ injections[key] = { ++ namespace, ++ source, ++ specifier: `${type}:${name || key}` ++ }; ++ } ++ } ++ return injections; ++ }; ++ } ++ ++ const emberObjectCore = /*#__PURE__*/Object.defineProperty({ ++ __proto__: null, ++ default: CoreObject ++ }, Symbol.toStringTag, { value: 'Module' }); ++ ++ /** ++ @module @ember/object/observable ++ */ ++ ++ ++ /** ++ ## Overview ++ ++ This mixin provides properties and property observing functionality, core ++ features of the Ember object model. ++ ++ Properties and observers allow one object to observe changes to a ++ property on another object. This is one of the fundamental ways that ++ models, controllers and views communicate with each other in an Ember ++ application. ++ ++ Any object that has this mixin applied can be used in observer + operations. That includes `EmberObject` and most objects you will + interact with as you write your Ember application. + +@@ -19365,3996 +19751,2487 @@ var define, require; + default: Observable + }, Symbol.toStringTag, { value: 'Module' }); + +- const { +- isArray: isArray$3 +- } = Array; + /** +- @module @ember/array ++ @module @ember/object + */ +- /** +- Forces the passed object to be part of an array. If the object is already +- an array, it will return the object. Otherwise, it will add the object to +- an array. If object is `null` or `undefined`, it will return an empty array. +- +- ```javascript +- import { makeArray } from '@ember/array'; +- import ArrayProxy from '@ember/array/proxy'; +- +- makeArray(); // [] +- makeArray(null); // [] +- makeArray(undefined); // [] +- makeArray('lindsay'); // ['lindsay'] +- makeArray([1, 2, 42]); // [1, 2, 42] +- +- let proxy = ArrayProxy.create({ content: [] }); + +- makeArray(proxy) === proxy; // false +- ``` ++ /** ++ `EmberObject` is the main base class for all Ember objects. It is a subclass ++ of `CoreObject` with the `Observable` mixin applied. For details, ++ see the documentation for each of these. + +- @method makeArray +- @static +- @for @ember/array +- @param {Object} obj the object +- @return {Array} +- @private +- */ ++ @class EmberObject ++ @extends CoreObject ++ @uses Observable ++ @public ++ */ ++ // eslint-disable-next-line @typescript-eslint/no-empty-interface + +- function makeArray(obj) { +- if (obj === null || obj === undefined) { +- return []; ++ class EmberObject extends CoreObject.extend(Observable) { ++ get _debugContainerKey() { ++ let factory = getFactoryFor(this); ++ return factory !== undefined && factory.fullName; + } +- return isArray$3(obj) ? obj : [obj]; + } + +- const emberArrayLibMakeArray = /*#__PURE__*/Object.defineProperty({ +- __proto__: null, +- default: makeArray +- }, Symbol.toStringTag, { value: 'Module' }); +- + /** +- @module @ember/array +- */ ++ Decorator that turns the target function into an Action which can be accessed ++ directly by reference. + +- const EMPTY_ARRAY$2 = Object.freeze([]); +- const identityFunction = item => item; +- function uniqBy$1(array, keyOrFunc = identityFunction) { +- (!(isArray$2(array)) && assert$1(`first argument passed to \`uniqBy\` should be array`, isArray$2(array))); +- let ret = A(); +- let seen = new Set(); +- let getter = typeof keyOrFunc === 'function' ? keyOrFunc : item => get$2(item, keyOrFunc); +- array.forEach(item => { +- let val = getter(item); +- if (!seen.has(val)) { +- seen.add(val); +- ret.push(item); +- } +- }); +- return ret; +- } +- function iter(...args) { +- let valueProvided = args.length === 2; +- let [key, value] = args; +- return valueProvided ? item => value === get$2(item, key) : item => Boolean(get$2(item, key)); +- } +- function findIndex(array, predicate, startAt) { +- let len = array.length; +- for (let index = startAt; index < len; index++) { +- // SAFETY: Because we're checking the index this value should always be set. +- let item = objectAt(array, index); +- if (predicate(item, index, array)) { +- return index; ++ ```js ++ import Component from '@ember/component'; ++ import { action, set } from '@ember/object'; ++ ++ export default class Tooltip extends Component { ++ @action ++ toggleShowing() { ++ set(this, 'isShowing', !this.isShowing); + } + } +- return -1; +- } +- function find(array, callback, target = null) { +- let predicate = callback.bind(target); +- let index = findIndex(array, predicate, 0); +- return index === -1 ? undefined : objectAt(array, index); +- } +- function any(array, callback, target = null) { +- let predicate = callback.bind(target); +- return findIndex(array, predicate, 0) !== -1; +- } +- function every(array, callback, target = null) { +- let cb = callback.bind(target); +- let predicate = (item, index, array) => !cb(item, index, array); +- return findIndex(array, predicate, 0) === -1; +- } +- function indexOf$1(array, val, startAt = 0, withNaNCheck) { +- let len = array.length; +- if (startAt < 0) { +- startAt += len; +- } ++ ``` ++ ```hbs ++ ++ + +- // SameValueZero comparison (NaN !== NaN) +- let predicate = withNaNCheck && val !== val ? item => item !== item : item => item === val; +- return findIndex(array, predicate, startAt); +- } +- function removeAt(array, index, len) { +- (!(index > -1 && index < array.length) && assert$1(`\`removeAt\` index provided is out of range`, index > -1 && index < array.length)); +- replace(array, index, len ?? 1, EMPTY_ARRAY$2); +- return array; +- } +- function insertAt(array, index, item) { +- (!(index > -1 && index <= array.length) && assert$1(`\`insertAt\` index provided is out of range`, index > -1 && index <= array.length)); +- replace(array, index, 0, [item]); +- return item; +- } ++ {{#if isShowing}} ++
++ I'm a tooltip! ++
++ {{/if}} ++ ``` + +- /** +- Returns true if the passed object is an array or Array-like. ++ Decorated actions also interop with the string style template actions: + +- Objects are considered Array-like if any of the following are true: ++ ```hbs ++ ++ + +- - the object is a native Array +- - the object has an objectAt property +- - the object is an Object, and has a length property ++ {{#if isShowing}} ++
++ I'm a tooltip! ++
++ {{/if}} ++ ``` + +- Unlike `typeOf` this method returns true even if the passed object is +- not formally an array but appears to be array-like (i.e. implements `Array`) ++ It also binds the function directly to the instance, so it can be used in any ++ context and will correctly refer to the class it came from: + +- ```javascript +- import { isArray } from '@ember/array'; +- import ArrayProxy from '@ember/array/proxy'; ++ ```hbs ++ ++ + +- isArray(); // false +- isArray([]); // true +- isArray(ArrayProxy.create({ content: [] })); // true ++ {{#if isShowing}} ++
++ I'm a tooltip! ++
++ {{/if}} + ``` + +- @method isArray +- @static +- @for @ember/array +- @param {Object} obj The object to test +- @return {Boolean} true if the passed object is an array or Array-like +- @public +- */ +- function isArray$2(obj) { +- if (typeof obj === 'object' && obj !== null) { +- // SAFETY: Property read checks are safe if it's an object +- let possibleProxyContent = obj[PROXY_CONTENT]; +- if (possibleProxyContent !== undefined) { +- obj = possibleProxyContent; ++ This can also be used in JavaScript code directly: ++ ++ ```js ++ import Component from '@ember/component'; ++ import { action, set } from '@ember/object'; ++ ++ export default class Tooltip extends Component { ++ constructor() { ++ super(...arguments); ++ ++ // this.toggleShowing is still bound correctly when added to ++ // the event listener ++ document.addEventListener('click', this.toggleShowing); + } +- } + +- // SAFETY: Property read checks are safe if it's an object +- if (!obj || obj.setInterval) { +- return false; +- } +- if (Array.isArray(obj) || EmberArray.detect(obj)) { +- return true; +- } +- let type = typeOf(obj); +- if ('array' === type) { +- return true; ++ @action ++ toggleShowing() { ++ set(this, 'isShowing', !this.isShowing); ++ } + } ++ ``` + +- // SAFETY: Property read checks are safe if it's an object +- let length = obj.length; +- if (typeof length === 'number' && length === length && 'object' === type) { +- return true; +- } +- return false; +- } ++ This is considered best practice, since it means that methods will be bound ++ correctly no matter where they are used. By contrast, the `{{action}}` helper ++ and modifier can also be used to bind context, but it will be required for ++ every usage of the method: + +- /* +- This allows us to define computed properties that are not enumerable. +- The primary reason this is important is that when `NativeArray` is +- applied to `Array.prototype` we need to ensure that we do not add _any_ +- new enumerable properties. +- */ +- function nonEnumerableComputed(callback) { +- let property = computed(callback); +- property.enumerable = false; +- return property; +- } +- function mapBy$1(key) { +- return this.map(next => get$2(next, key)); +- } +- +- // .......................................................... +- // ARRAY +- // +- /** +- This mixin implements Observer-friendly Array-like behavior. It is not a +- concrete implementation, but it can be used up by other classes that want +- to appear like arrays. +- +- For example, ArrayProxy is a concrete class that can be instantiated to +- implement array-like behavior. This class uses the Array Mixin by way of +- the MutableArray mixin, which allows observable changes to be made to the +- underlying array. +- +- This mixin defines methods specifically for collections that provide +- index-ordered access to their contents. When you are designing code that +- needs to accept any kind of Array-like object, you should use these methods +- instead of Array primitives because these will properly notify observers of +- changes to the array. +- +- Although these methods are efficient, they do add a layer of indirection to +- your application so it is a good idea to use them only when you need the +- flexibility of using both true JavaScript arrays and "virtual" arrays such +- as controllers and collections. ++ ```hbs ++ ++ + +- You can use the methods defined in this module to access and modify array +- contents in an observable-friendly way. You can also be notified whenever +- the membership of an array changes by using `.observes('myArray.[]')`. ++ {{#if isShowing}} ++
++ I'm a tooltip! ++
++ {{/if}} ++ ``` + +- To support `EmberArray` in your own class, you must override two +- primitives to use it: `length()` and `objectAt()`. ++ They also do not have equivalents in JavaScript directly, so they cannot be ++ used for other situations where binding would be useful. + +- @class EmberArray +- @uses Enumerable +- @since Ember 0.9.0 + @public ++ @method action ++ @for @ember/object ++ @static ++ @param {Function|undefined} callback The function to turn into an action, ++ when used in classic classes ++ @return {PropertyDecorator} property decorator instance + */ + +- const EmberArray = Mixin.create(Enumerable, { +- init() { +- this._super(...arguments); +- setEmberArray(this); +- }, +- objectsAt(indexes) { +- return indexes.map(idx => objectAt(this, idx)); +- }, +- '[]': nonEnumerableComputed({ ++ const BINDINGS_MAP = new WeakMap(); ++ function hasProto(obj) { ++ return obj != null && obj.constructor !== undefined && typeof obj.constructor.proto === 'function'; ++ } ++ function setupAction(target, key, actionFn) { ++ if (hasProto(target)) { ++ target.constructor.proto(); ++ } ++ if (!Object.prototype.hasOwnProperty.call(target, 'actions')) { ++ let parentActions = target.actions; ++ // we need to assign because of the way mixins copy actions down when inheriting ++ target.actions = parentActions ? Object.assign({}, parentActions) : {}; ++ } ++ (!(target.actions != null) && assert$1("[BUG] Somehow the target doesn't have actions!", target.actions != null)); ++ target.actions[key] = actionFn; ++ return { + get() { +- return this; +- }, +- set(_key, value) { +- this.replace(0, this.length, value); +- return this; +- } +- }), +- firstObject: nonEnumerableComputed(function () { +- return objectAt(this, 0); +- }).readOnly(), +- lastObject: nonEnumerableComputed(function () { +- return objectAt(this, this.length - 1); +- }).readOnly(), +- // Add any extra methods to EmberArray that are native to the built-in Array. +- slice(beginIndex = 0, endIndex) { +- let ret = A(); +- let length = this.length; +- if (beginIndex < 0) { +- beginIndex = length + beginIndex; +- } +- let validatedEndIndex; +- if (endIndex === undefined || endIndex > length) { +- validatedEndIndex = length; +- } else if (endIndex < 0) { +- validatedEndIndex = length + endIndex; +- } else { +- validatedEndIndex = endIndex; +- } +- while (beginIndex < validatedEndIndex) { +- ret[ret.length] = objectAt(this, beginIndex++); +- } +- return ret; +- }, +- indexOf(object, startAt) { +- return indexOf$1(this, object, startAt, false); +- }, +- lastIndexOf(object, startAt) { +- let len = this.length; +- if (startAt === undefined || startAt >= len) { +- startAt = len - 1; +- } +- if (startAt < 0) { +- startAt += len; +- } +- for (let idx = startAt; idx >= 0; idx--) { +- if (objectAt(this, idx) === object) { +- return idx; +- } +- } +- return -1; +- }, +- forEach(callback, target = null) { +- (!(typeof callback === 'function') && assert$1('`forEach` expects a function as first argument.', typeof callback === 'function')); +- let length = this.length; +- for (let index = 0; index < length; index++) { +- let item = this.objectAt(index); +- callback.call(target, item, index, this); +- } +- return this; +- }, +- getEach: mapBy$1, +- setEach(key, value) { +- return this.forEach(item => set(item, key, value)); +- }, +- map(callback, target = null) { +- (!(typeof callback === 'function') && assert$1('`map` expects a function as first argument.', typeof callback === 'function')); +- let ret = A(); +- this.forEach((x, idx, i) => ret[idx] = callback.call(target, x, idx, i)); +- return ret; +- }, +- mapBy: mapBy$1, +- filter(callback, target = null) { +- (!(typeof callback === 'function') && assert$1('`filter` expects a function as first argument.', typeof callback === 'function')); +- let ret = A(); +- this.forEach((x, idx, i) => { +- if (callback.call(target, x, idx, i)) { +- ret.push(x); ++ let bindings = BINDINGS_MAP.get(this); ++ if (bindings === undefined) { ++ bindings = new Map(); ++ BINDINGS_MAP.set(this, bindings); + } +- }); +- return ret; +- }, +- reject(callback, target = null) { +- (!(typeof callback === 'function') && assert$1('`reject` expects a function as first argument.', typeof callback === 'function')); +- return this.filter(function () { +- // @ts-expect-error TS doesn't like us using arguments like this +- return !callback.apply(target, arguments); +- }); +- }, +- filterBy() { +- // @ts-expect-error TS doesn't like the ...arguments spread here. +- return this.filter(iter(...arguments)); +- }, +- rejectBy() { +- // @ts-expect-error TS doesn't like the ...arguments spread here. +- return this.reject(iter(...arguments)); +- }, +- find(callback, target = null) { +- (!(typeof callback === 'function') && assert$1('`find` expects a function as first argument.', typeof callback === 'function')); +- return find(this, callback, target); +- }, +- findBy() { +- // @ts-expect-error TS doesn't like the ...arguments spread here. +- let callback = iter(...arguments); +- return find(this, callback); +- }, +- every(callback, target = null) { +- (!(typeof callback === 'function') && assert$1('`every` expects a function as first argument.', typeof callback === 'function')); +- return every(this, callback, target); +- }, +- isEvery() { +- // @ts-expect-error TS doesn't like the ...arguments spread here. +- let callback = iter(...arguments); +- return every(this, callback); +- }, +- any(callback, target = null) { +- (!(typeof callback === 'function') && assert$1('`any` expects a function as first argument.', typeof callback === 'function')); +- return any(this, callback, target); +- }, +- isAny() { +- // @ts-expect-error TS doesn't like us using arguments like this +- let callback = iter(...arguments); +- return any(this, callback); +- }, +- // FIXME: When called without initialValue, behavior does not match native behavior +- reduce(callback, initialValue) { +- (!(typeof callback === 'function') && assert$1('`reduce` expects a function as first argument.', typeof callback === 'function')); +- let ret = initialValue; +- this.forEach(function (item, i) { +- ret = callback(ret, item, i, this); +- }, this); +- return ret; +- }, +- invoke(methodName, ...args) { +- let ret = A(); +- +- // SAFETY: This is not entirely safe and the code will not work with Ember proxies +- this.forEach(item => ret.push(item[methodName]?.(...args))); +- return ret; +- }, +- toArray() { +- return this.map(item => item); +- }, +- compact() { +- return this.filter(value => value != null); +- }, +- includes(object, startAt) { +- return indexOf$1(this, object, startAt, true) !== -1; +- }, +- sortBy() { +- let sortKeys = arguments; +- return this.toArray().sort((a, b) => { +- for (let i = 0; i < sortKeys.length; i++) { +- let key = sortKeys[i]; +- let propA = get$2(a, key); +- let propB = get$2(b, key); +- // return 1 or -1 else continue to the next sortKey +- let compareValue = compare(propA, propB); +- if (compareValue) { +- return compareValue; +- } ++ let fn = bindings.get(actionFn); ++ if (fn === undefined) { ++ fn = actionFn.bind(this); ++ bindings.set(actionFn, fn); + } +- return 0; +- }); +- }, +- uniq() { +- return uniqBy$1(this); +- }, +- uniqBy(key) { +- return uniqBy$1(this, key); +- }, +- without(value) { +- if (!this.includes(value)) { +- return this; // nothing to do ++ return fn; + } +- +- // SameValueZero comparison (NaN !== NaN) +- let predicate = value === value ? item => item !== value : item => item === item; +- return this.filter(predicate); ++ }; ++ } ++ function action$1(...args) { ++ let actionFn; ++ if (!isElementDescriptor(args)) { ++ actionFn = args[0]; ++ let decorator = function (target, key, _desc, _meta, isClassicDecorator) { ++ (!(isClassicDecorator) && assert$1('The @action decorator may only be passed a method when used in classic classes. You should decorate methods directly in native classes', isClassicDecorator)); ++ (!(typeof actionFn === 'function') && assert$1('The action() decorator must be passed a method when used in classic classes', typeof actionFn === 'function')); ++ return setupAction(target, key, actionFn); ++ }; ++ setClassicDecorator(decorator); ++ return decorator; + } +- }); +- +- /** +- This mixin defines the API for modifying array-like objects. These methods +- can be applied only to a collection that keeps its items in an ordered set. +- It builds upon the Array mixin and adds methods to modify the array. +- One concrete implementations of this class include ArrayProxy. +- +- It is important to use the methods in this class to modify arrays so that +- changes are observable. This allows the binding system in Ember to function +- correctly. +- +- +- Note that an Array can change even if it does not implement this mixin. +- For example, one might implement a SparseArray that cannot be directly +- modified, but if its underlying enumerable changes, it will change also. ++ let [target, key, desc] = args; ++ actionFn = desc?.value; ++ (!(typeof actionFn === 'function') && assert$1('The @action decorator must be applied to methods when used in native classes', typeof actionFn === 'function')); // SAFETY: TS types are weird with decorators. This should work. ++ return setupAction(target, key, actionFn); ++ } + +- @class MutableArray +- @uses EmberArray +- @uses MutableEnumerable +- @public +- */ ++ // SAFETY: TS types are weird with decorators. This should work. ++ setClassicDecorator(action$1); + +- const MutableArray = Mixin.create(EmberArray, MutableEnumerable, { +- clear() { +- let len = this.length; +- if (len === 0) { +- return this; +- } +- this.replace(0, len, EMPTY_ARRAY$2); +- return this; +- }, +- insertAt(idx, object) { +- insertAt(this, idx, object); +- return this; +- }, +- removeAt(start, len) { +- return removeAt(this, start, len); +- }, +- pushObject(obj) { +- return insertAt(this, this.length, obj); +- }, +- pushObjects(objects) { +- this.replace(this.length, 0, objects); +- return this; +- }, +- popObject() { +- let len = this.length; +- if (len === 0) { +- return null; +- } +- let ret = objectAt(this, len - 1); +- this.removeAt(len - 1, 1); +- return ret; +- }, +- shiftObject() { +- if (this.length === 0) { +- return null; +- } +- let ret = objectAt(this, 0); +- this.removeAt(0); +- return ret; +- }, +- unshiftObject(obj) { +- return insertAt(this, 0, obj); +- }, +- unshiftObjects(objects) { +- this.replace(0, 0, objects); +- return this; +- }, +- reverseObjects() { +- let len = this.length; +- if (len === 0) { +- return this; +- } +- let objects = this.toArray().reverse(); +- this.replace(0, len, objects); +- return this; +- }, +- setObjects(objects) { +- if (objects.length === 0) { +- return this.clear(); +- } +- let len = this.length; +- this.replace(0, len, objects); +- return this; +- }, +- removeObject(obj) { +- let loc = this.length || 0; +- while (--loc >= 0) { +- let curObject = objectAt(this, loc); +- if (curObject === obj) { +- this.removeAt(loc); +- } +- } +- return this; +- }, +- removeObjects(objects) { +- beginPropertyChanges(); +- for (let i = objects.length - 1; i >= 0; i--) { +- // SAFETY: Due to the loop structure we know this will always exist. +- this.removeObject(objects[i]); +- } +- endPropertyChanges(); +- return this; +- }, +- addObject(obj) { +- let included = this.includes(obj); +- if (!included) { +- this.pushObject(obj); +- } +- return this; +- }, +- addObjects(objects) { +- beginPropertyChanges(); +- objects.forEach(obj => this.addObject(obj)); +- endPropertyChanges(); +- return this; +- } +- }); ++ // .......................................................... ++ // OBSERVER HELPER ++ // + + /** +- Creates an `Ember.NativeArray` from an Array-like object. +- Does not modify the original object's contents. `A()` is not needed if +- `EmberENV.EXTEND_PROTOTYPES` is `true` (the default value). However, +- it is recommended that you use `A()` when creating addons for +- ember or when you can not guarantee that `EmberENV.EXTEND_PROTOTYPES` +- will be `true`. +- +- Example +- +- ```app/components/my-component.js +- import Component from '@ember/component'; +- import { A } from '@ember/array'; +- +- export default Component.extend({ +- tagName: 'ul', +- classNames: ['pagination'], ++ Specify a method that observes property changes. + +- init() { +- this._super(...arguments); ++ ```javascript ++ import EmberObject from '@ember/object'; ++ import { observer } from '@ember/object'; + +- if (!this.get('content')) { +- this.set('content', A()); +- this.set('otherContent', A([1,2,3])); +- } +- } ++ export default EmberObject.extend({ ++ valueObserver: observer('value', function() { ++ // Executes whenever the "value" property changes ++ }) + }); + ``` + +- @method A +- @static +- @for @ember/array +- @return {Ember.NativeArray} ++ Also available as `Function.prototype.observes` if prototype extensions are ++ enabled. ++ ++ @method observer ++ @for @ember/object ++ @param {String} propertyNames* ++ @param {Function} func ++ @return func + @public ++ @static + */ ++ function observer(...args) { ++ let funcOrDef = args.pop(); ++ (!(typeof funcOrDef === 'function' || typeof funcOrDef === 'object' && funcOrDef !== null) && assert$1('observer must be provided a function or an observer definition', typeof funcOrDef === 'function' || typeof funcOrDef === 'object' && funcOrDef !== null)); ++ let func; ++ let dependentKeys; ++ let sync; ++ if (typeof funcOrDef === 'function') { ++ func = funcOrDef; ++ dependentKeys = args; ++ sync = !ENV._DEFAULT_ASYNC_OBSERVERS; ++ } else { ++ func = funcOrDef.fn; ++ dependentKeys = funcOrDef.dependentKeys; ++ sync = funcOrDef.sync; ++ } ++ (!(typeof func === 'function') && assert$1('observer called without a function', typeof func === 'function')); ++ (!(Array.isArray(dependentKeys) && dependentKeys.length > 0 && dependentKeys.every(p => typeof p === 'string' && Boolean(p.length))) && assert$1('observer called without valid path', Array.isArray(dependentKeys) && dependentKeys.length > 0 && dependentKeys.every(p => typeof p === 'string' && Boolean(p.length)))); ++ (!(typeof sync === 'boolean') && assert$1('observer called without sync', typeof sync === 'boolean')); ++ let paths = []; ++ for (let dependentKey of dependentKeys) { ++ expandProperties(dependentKey, path => paths.push(path)); ++ } ++ setObservers(func, { ++ paths, ++ sync ++ }); ++ return func; ++ } + +- // Add Ember.Array to Array.prototype. Remove methods with native +- // implementations and supply some more optimized versions of generic methods +- // because they are so common. +- /** +- @module ember +- */ ++ const emberObjectIndex = /*#__PURE__*/Object.defineProperty({ ++ __proto__: null, ++ action: action$1, ++ computed, ++ default: EmberObject, ++ defineProperty, ++ get: get$2, ++ getProperties, ++ notifyPropertyChange, ++ observer, ++ set, ++ setProperties, ++ trySet ++ }, Symbol.toStringTag, { value: 'Module' }); + + /** +- * The final definition of NativeArray removes all native methods. This is the list of removed methods +- * when run in Chrome 106. ++ * Default component template, which is a plain yield + */ ++ const DEFAULT_TEMPLATE_BLOCK = [[[opcodes.Yield, 1, null]], ['&default'], false, []]; ++ const DEFAULT_TEMPLATE = { ++ // random uuid ++ id: '1b32f5c2-7623-43d6-a0ad-9672898920a1', ++ moduleName: '__default__.hbs', ++ block: JSON.stringify(DEFAULT_TEMPLATE_BLOCK), ++ scope: null, ++ isStrictMode: true ++ }; ++ const WELL_KNOWN_EMPTY_ARRAY = Object.freeze([]); ++ const STARTER_CONSTANTS = constants(WELL_KNOWN_EMPTY_ARRAY); ++ const WELL_KNOWN_EMPTY_ARRAY_POSITION = STARTER_CONSTANTS.indexOf(WELL_KNOWN_EMPTY_ARRAY); ++ class CompileTimeConstantImpl { ++ // `0` means NULL + +- /** +- * These additional items must be redefined since `Omit` causes methods that return `this` to return the +- * type at the time of the Omit. +- */ +- +- // This is the same as MutableArray, but removes the actual native methods that exist on Array.prototype. +- +- /** +- The NativeArray mixin contains the properties needed to make the native +- Array support MutableArray and all of its dependent APIs. Unless you +- have `EmberENV.EXTEND_PROTOTYPES` or `EmberENV.EXTEND_PROTOTYPES.Array` set to +- false, this will be applied automatically. Otherwise you can apply the mixin +- at anytime by calling `Ember.NativeArray.apply(Array.prototype)`. +- +- @class Ember.NativeArray +- @uses MutableArray +- @uses Observable +- @public +- */ +- +- let NativeArray = Mixin.create(MutableArray, Observable, { +- objectAt(idx) { +- return this[idx]; +- }, +- // primitive for array support. +- replace(start, deleteCount, items = EMPTY_ARRAY$2) { +- (!(Array.isArray(items)) && assert$1('The third argument to replace needs to be an array.', Array.isArray(items))); +- replaceInNativeArray(this, start, deleteCount, items); +- return this; +- } +- }); +- +- // Remove any methods implemented natively so we don't override them +- const ignore = ['length']; +- NativeArray.keys().forEach(methodName => { +- // SAFETY: It's safe to read unknown properties from an object +- if (Array.prototype[methodName]) { +- ignore.push(methodName); ++ values = STARTER_CONSTANTS.slice(); ++ indexMap = new Map(this.values.map((value, index) => [value, index])); ++ value(value) { ++ let indexMap = this.indexMap; ++ let index = indexMap.get(value); ++ if (index === undefined) { ++ index = this.values.push(value) - 1; ++ indexMap.set(value, index); ++ } ++ return index; + } +- }); +- NativeArray = NativeArray.without(...ignore); +- let A; +- if (ENV.EXTEND_PROTOTYPES.Array) { +- NativeArray.apply(Array.prototype, true); +- A = function (arr) { +- (!(!(this instanceof A)) && assert$1('You cannot create an Ember Array with `new A()`, please update to calling A as a function: `A()`', !(this instanceof A))); // SAFTEY: Since we are extending prototypes all true native arrays are Ember NativeArrays +- return arr || []; +- }; +- } else { +- A = function (arr) { +- (!(!(this instanceof A)) && assert$1('You cannot create an Ember Array with `new A()`, please update to calling A as a function: `A()`', !(this instanceof A))); +- if (isEmberArray(arr)) { +- // SAFETY: If it's a true native array and it is also an EmberArray then it should be an Ember NativeArray +- return arr; +- } else { +- // SAFETY: This will return an NativeArray but TS can't infer that. +- return NativeArray.apply(arr ?? []); ++ array(values) { ++ if (values.length === 0) { ++ return WELL_KNOWN_EMPTY_ARRAY_POSITION; + } +- }; +- } +- +- const emberArrayIndex = /*#__PURE__*/Object.defineProperty({ +- __proto__: null, +- get A () { return A; }, +- MutableArray, +- get NativeArray () { return NativeArray; }, +- default: EmberArray, +- isArray: isArray$2, +- makeArray, +- removeAt, +- uniqBy: uniqBy$1 +- }, Symbol.toStringTag, { value: 'Module' }); +- +- /** +- @module @ember/object/core +- */ +- +- +- // TODO: Is this correct? +- +- function hasSetUnknownProperty(val) { +- return typeof val === 'object' && val !== null && typeof val.setUnknownProperty === 'function'; ++ let handles = new Array(values.length); ++ for (let i = 0; i < values.length; i++) { ++ handles[i] = this.value(values[i]); ++ } ++ return this.value(handles); ++ } ++ toPool() { ++ return this.values; ++ } + } +- function hasToStringExtension(val) { +- return typeof val === 'object' && val !== null && typeof val.toStringExtension === 'function'; ++ class RuntimeConstantsImpl { ++ values; ++ constructor(pool) { ++ this.values = pool; ++ } ++ getValue(handle) { ++ return this.values[handle]; ++ } ++ getArray(value) { ++ let handles = this.getValue(value); ++ let reified = new Array(handles.length); ++ for (const [i, n] of enumerate(handles)) { ++ reified[i] = this.getValue(n); ++ } ++ return reified; ++ } + } +- const reopen = Mixin.prototype.reopen; +- const wasApplied = new WeakSet(); +- const prototypeMixinMap = new WeakMap(); +- const initCalled = new WeakSet() ; // only used in debug builds to enable the proxy trap ++ class ConstantsImpl extends CompileTimeConstantImpl { ++ reifiedArrs = { ++ [WELL_KNOWN_EMPTY_ARRAY_POSITION]: WELL_KNOWN_EMPTY_ARRAY ++ }; ++ defaultTemplate = templateFactory(DEFAULT_TEMPLATE)(); + +- const destroyCalled = new Set(); +- function ensureDestroyCalled(instance) { +- if (!destroyCalled.has(instance)) { +- instance.destroy(); ++ // Used for tests and debugging purposes, and to be able to analyze large apps ++ // This is why it's enabled even in production ++ helperDefinitionCount = 0; ++ modifierDefinitionCount = 0; ++ componentDefinitionCount = 0; ++ helperDefinitionCache = new WeakMap(); ++ modifierDefinitionCache = new WeakMap(); ++ componentDefinitionCache = new WeakMap(); ++ helper(definitionState, ++ // TODO: Add a way to expose resolved name for debugging ++ _resolvedName = null, isOptional) { ++ let handle = this.helperDefinitionCache.get(definitionState); ++ if (handle === undefined) { ++ let managerOrHelper = getInternalHelperManager(definitionState, isOptional); ++ if (managerOrHelper === null) { ++ this.helperDefinitionCache.set(definitionState, null); ++ return null; ++ } ++ debugAssert(managerOrHelper, 'BUG: expected manager or helper'); ++ let helper = typeof managerOrHelper === 'function' ? managerOrHelper : managerOrHelper.getHelper(definitionState); ++ handle = this.value(helper); ++ this.helperDefinitionCache.set(definitionState, handle); ++ this.helperDefinitionCount++; ++ } ++ return handle; + } +- } +- function initialize(obj, properties) { +- let m = meta(obj); +- if (properties !== undefined) { +- (!(typeof properties === 'object' && properties !== null) && assert$1('EmberObject.create only accepts objects.', typeof properties === 'object' && properties !== null)); +- (!(!(properties instanceof Mixin)) && assert$1('EmberObject.create no longer supports mixing in other ' + 'definitions, use .extend & .create separately instead.', !(properties instanceof Mixin))); +- let concatenatedProperties = obj.concatenatedProperties; +- let mergedProperties = obj.mergedProperties; +- let keyNames = Object.keys(properties); +- for (let keyName of keyNames) { +- // SAFETY: this cast as a Record is safe because all object types can be +- // indexed in JS, and we explicitly type it as returning `unknown`, so the +- // result *must* be checked below. +- let value = properties[keyName]; +- (!(!isClassicDecorator(value)) && assert$1('EmberObject.create no longer supports defining computed ' + 'properties. Define computed properties using extend() or reopen() ' + 'before calling create().', !isClassicDecorator(value))); +- (!(!(typeof value === 'function' && value.toString().indexOf('._super') !== -1)) && assert$1('EmberObject.create no longer supports defining methods that call _super.', !(typeof value === 'function' && value.toString().indexOf('._super') !== -1))); +- (!(!(keyName === 'actions' && ActionHandler.detect(obj))) && assert$1('`actions` must be provided at extend time, not at create time, ' + 'when Ember.ActionHandler is used (i.e. views, controllers & routes).', !(keyName === 'actions' && ActionHandler.detect(obj)))); +- let possibleDesc = descriptorForProperty(obj, keyName, m); +- let isDescriptor = possibleDesc !== undefined; +- if (!isDescriptor) { +- if (concatenatedProperties !== undefined && concatenatedProperties.length > 0 && concatenatedProperties.includes(keyName)) { +- let baseValue = obj[keyName]; +- if (baseValue) { +- value = makeArray(baseValue).concat(value); +- } else { +- value = makeArray(value); +- } +- } +- if (mergedProperties !== undefined && mergedProperties.length > 0 && mergedProperties.includes(keyName)) { +- let baseValue = obj[keyName]; +- value = Object.assign({}, baseValue, value); +- } ++ modifier(definitionState, resolvedName = null, isOptional) { ++ let handle = this.modifierDefinitionCache.get(definitionState); ++ if (handle === undefined) { ++ let manager = getInternalModifierManager(definitionState, isOptional); ++ if (manager === null) { ++ this.modifierDefinitionCache.set(definitionState, null); ++ return null; + } +- if (isDescriptor) { +- possibleDesc.set(obj, keyName, value); +- } else if (hasSetUnknownProperty(obj) && !(keyName in obj)) { +- obj.setUnknownProperty(keyName, value); ++ let definition = { ++ resolvedName, ++ manager, ++ state: definitionState ++ }; ++ handle = this.value(definition); ++ this.modifierDefinitionCache.set(definitionState, handle); ++ this.modifierDefinitionCount++; ++ } ++ return handle; ++ } ++ component(definitionState, owner, isOptional) { ++ let definition = this.componentDefinitionCache.get(definitionState); ++ if (definition === undefined) { ++ let manager = getInternalComponentManager(definitionState, isOptional); ++ if (manager === null) { ++ this.componentDefinitionCache.set(definitionState, null); ++ return null; ++ } ++ debugAssert(manager, 'BUG: expected manager'); ++ let capabilities = capabilityFlagsFrom(manager.getCapabilities(definitionState)); ++ let templateFactory = getComponentTemplate(definitionState); ++ let compilable = null; ++ let template; ++ if (!managerHasCapability(manager, capabilities, InternalComponentCapabilities.dynamicLayout)) { ++ template = templateFactory?.(owner) ?? this.defaultTemplate; + } else { +- { +- defineProperty(obj, keyName, null, value, m); // setup mandatory setter +- } ++ template = templateFactory?.(owner); ++ } ++ if (template !== undefined) { ++ template = unwrapTemplate(template); ++ compilable = managerHasCapability(manager, capabilities, InternalComponentCapabilities.wrapped) ? template.asWrappedLayout() : template.asLayout(); ++ } ++ definition = { ++ resolvedName: null, ++ handle: -1, ++ // replaced momentarily ++ manager, ++ capabilities, ++ state: definitionState, ++ compilable ++ }; ++ definition.handle = this.value(definition); ++ this.componentDefinitionCache.set(definitionState, definition); ++ this.componentDefinitionCount++; ++ } ++ return definition; ++ } ++ resolvedComponent(resolvedDefinition, resolvedName) { ++ let definition = this.componentDefinitionCache.get(resolvedDefinition); ++ if (definition === undefined) { ++ let { ++ manager, ++ state, ++ template ++ } = resolvedDefinition; ++ let capabilities = capabilityFlagsFrom(manager.getCapabilities(resolvedDefinition)); ++ let compilable = null; ++ if (!managerHasCapability(manager, capabilities, InternalComponentCapabilities.dynamicLayout)) { ++ template = template ?? this.defaultTemplate; ++ } ++ if (template !== null) { ++ template = unwrapTemplate(template); ++ compilable = managerHasCapability(manager, capabilities, InternalComponentCapabilities.wrapped) ? template.asWrappedLayout() : template.asLayout(); + } ++ definition = { ++ resolvedName, ++ handle: -1, ++ // replaced momentarily ++ manager, ++ capabilities, ++ state, ++ compilable ++ }; ++ definition.handle = this.value(definition); ++ this.componentDefinitionCache.set(resolvedDefinition, definition); ++ this.componentDefinitionCount++; + } ++ return expect(definition, 'BUG: resolved component definitions cannot be null'); + } +- +- // using DEBUG here to avoid the extraneous variable when not needed +- { +- initCalled.add(obj); ++ getValue(index) { ++ debugAssert(index >= 0, `cannot get value for handle: ${index}`); ++ return this.values[index]; + } +- obj.init(properties); +- m.unsetInitializing(); +- let observerEvents = m.observerEvents(); +- if (observerEvents !== undefined) { +- for (let i = 0; i < observerEvents.length; i++) { +- activateObserver(obj, observerEvents[i].event, observerEvents[i].sync); ++ getArray(index) { ++ let reifiedArrs = this.reifiedArrs; ++ let reified = reifiedArrs[index]; ++ if (reified === undefined) { ++ let names = this.getValue(index); ++ reified = new Array(names.length); ++ for (const [i, name] of enumerate(names)) { ++ reified[i] = this.getValue(name); ++ } ++ reifiedArrs[index] = reified; + } ++ return reified; + } +- sendEvent(obj, 'init', undefined, undefined, m); + } ++ class RuntimeOpImpl { ++ offset = 0; ++ constructor(heap) { ++ this.heap = heap; ++ } ++ get size() { ++ let rawType = this.heap.getbyaddr(this.offset); ++ return ((rawType & OPERAND_LEN_MASK) >> ARG_SHIFT) + 1; ++ } ++ get isMachine() { ++ let rawType = this.heap.getbyaddr(this.offset); ++ return rawType & MACHINE_MASK ? 1 : 0; ++ } ++ get type() { ++ return this.heap.getbyaddr(this.offset) & TYPE_MASK; ++ } ++ get op1() { ++ return this.heap.getbyaddr(this.offset + 1); ++ } ++ get op2() { ++ return this.heap.getbyaddr(this.offset + 2); ++ } ++ get op3() { ++ return this.heap.getbyaddr(this.offset + 3); ++ } ++ } ++ var TableSlotState = /*#__PURE__*/function (TableSlotState) { ++ TableSlotState[TableSlotState["Allocated"] = 0] = "Allocated"; ++ TableSlotState[TableSlotState["Freed"] = 1] = "Freed"; ++ TableSlotState[TableSlotState["Purged"] = 2] = "Purged"; ++ TableSlotState[TableSlotState["Pointer"] = 3] = "Pointer"; ++ return TableSlotState; ++ }(TableSlotState || {}); ++ const PAGE_SIZE = 0x100000; ++ class RuntimeHeapImpl { ++ heap; ++ table; ++ constructor(serializedHeap) { ++ let { ++ buffer, ++ table ++ } = serializedHeap; ++ this.heap = new Int32Array(buffer); ++ this.table = table; ++ } + +- /** +- `CoreObject` is the base class for all Ember constructs. It establishes a +- class system based on Ember's Mixin system, and provides the basis for the +- Ember Object Model. `CoreObject` should generally not be used directly, +- instead you should use `EmberObject`. +- +- ## Usage +- +- You can define a class by extending from `CoreObject` using the `extend` +- method: +- +- ```js +- const Person = CoreObject.extend({ +- name: 'Tomster', +- }); +- ``` +- +- For detailed usage, see the [Object Model](https://guides.emberjs.com/release/object-model/) +- section of the guides. +- +- ## Usage with Native Classes +- +- Native JavaScript `class` syntax can be used to extend from any `CoreObject` +- based class: ++ // It is illegal to close over this address, as compaction ++ // may move it. However, it is legal to use this address ++ // multiple times between compactions. ++ getaddr(handle) { ++ return unwrap$1(this.table[handle]); ++ } ++ getbyaddr(address) { ++ return expect(this.heap[address], 'Access memory out of bounds of the heap'); ++ } ++ sizeof(handle) { ++ return sizeof(this.table); ++ } ++ } ++ function hydrateHeap(serializedHeap) { ++ return new RuntimeHeapImpl(serializedHeap); ++ } + +- ```js +- class Person extends CoreObject { +- init() { +- super.init(...arguments); +- this.name = 'Tomster'; ++ /** ++ * The Heap is responsible for dynamically allocating ++ * memory in which we read/write the VM's instructions ++ * from/to. When we malloc we pass out a VMHandle, which ++ * is used as an indirect way of accessing the memory during ++ * execution of the VM. Internally we track the different ++ * regions of the memory in an int array known as the table. ++ * ++ * The table 32-bit aligned and has the following layout: ++ * ++ * | ... | hp (u32) | info (u32) | size (u32) | ++ * | ... | Handle | Scope Size | State | Size | ++ * | ... | 32bits | 30bits | 2bits | 32bit | ++ * ++ * With this information we effectively have the ability to ++ * control when we want to free memory. That being said you ++ * can not free during execution as raw address are only ++ * valid during the execution. This means you cannot close ++ * over them as you will have a bad memory access exception. ++ */ ++ class HeapImpl { ++ offset = 0; ++ heap; ++ handleTable; ++ handleState; ++ handle = 0; ++ constructor() { ++ this.heap = new Int32Array(PAGE_SIZE); ++ this.handleTable = []; ++ this.handleState = []; ++ } ++ pushRaw(value) { ++ this.sizeCheck(); ++ this.heap[this.offset++] = value; ++ } ++ pushOp(item) { ++ this.pushRaw(item); ++ } ++ pushMachine(item) { ++ this.pushRaw(item | MACHINE_MASK); ++ } ++ sizeCheck() { ++ let { ++ heap ++ } = this; ++ if (this.offset === this.heap.length) { ++ let newHeap = new Int32Array(heap.length + PAGE_SIZE); ++ newHeap.set(heap, 0); ++ this.heap = newHeap; + } + } +- ``` +- +- Some notes about `class` usage: +- +- * `new` syntax is not currently supported with classes that extend from +- `EmberObject` or `CoreObject`. You must continue to use the `create` method +- when making new instances of classes, even if they are defined using native +- class syntax. If you want to use `new` syntax, consider creating classes +- which do _not_ extend from `EmberObject` or `CoreObject`. Ember features, +- such as computed properties and decorators, will still work with base-less +- classes. +- * Instead of using `this._super()`, you must use standard `super` syntax in +- native classes. See the [MDN docs on classes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes#Super_class_calls_with_super) +- for more details. +- * Native classes support using [constructors](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes#Constructor) +- to set up newly-created instances. Ember uses these to, among other things, +- support features that need to retrieve other entities by name, like Service +- injection and `getOwner`. To ensure your custom instance setup logic takes +- place after this important work is done, avoid using the `constructor` in +- favor of `init`. +- * Properties passed to `create` will be available on the instance by the time +- `init` runs, so any code that requires these values should work at that +- time. +- * Using native classes, and switching back to the old Ember Object model is +- fully supported. ++ getbyaddr(address) { ++ return unwrap$1(this.heap[address]); ++ } ++ setbyaddr(address, value) { ++ this.heap[address] = value; ++ } ++ malloc() { ++ // push offset, info, size ++ this.handleTable.push(this.offset); ++ return this.handleTable.length - 1; ++ } ++ finishMalloc(handle) {} ++ size() { ++ return this.offset; ++ } + +- @class CoreObject +- @public +- */ ++ // It is illegal to close over this address, as compaction ++ // may move it. However, it is legal to use this address ++ // multiple times between compactions. ++ getaddr(handle) { ++ return unwrap$1(this.handleTable[handle]); ++ } ++ sizeof(handle) { ++ return sizeof(this.handleTable); ++ } ++ free(handle) { ++ this.handleState[handle] = TableSlotState.Freed; ++ } + +- class CoreObject { +- /** @internal */ +- [OWNER$1]; +- constructor(owner) { +- this[OWNER$1] = owner; +- +- // prepare prototype... +- this.constructor.proto(); +- let self; +- if (hasUnknownProperty(this)) { +- let messageFor = (obj, property) => { +- return `You attempted to access the \`${String(property)}\` property (of ${obj}).\n` + `Since Ember 3.1, this is usually fine as you no longer need to use \`.get()\`\n` + `to access computed properties. However, in this case, the object in question\n` + `is a special kind of Ember object (a proxy). Therefore, it is still necessary\n` + `to use \`.get('${String(property)}')\` in this case.\n\n` + `If you encountered this error because of third-party code that you don't control,\n` + `there is more information at https://github.com/emberjs/ember.js/issues/16148, and\n` + `you can help us improve this error message by telling us more about what happened in\n` + `this situation.`; +- }; +- +- /* globals Proxy Reflect */ +- self = new Proxy(this, { +- get(target, property, receiver) { +- if (property === PROXY_CONTENT) { +- return target; +- } else if ( +- // init called will be set on the proxy, not the target, so get with the receiver +- !initCalled.has(receiver) || typeof property === 'symbol' || isInternalSymbol(property) || property === 'toJSON' || property === 'toString' || property === 'toStringExtension' || property === 'didDefineProperty' || property === 'willWatchProperty' || property === 'didUnwatchProperty' || property === 'didAddListener' || property === 'didRemoveListener' || property === 'isDescriptor' || property === '_onLookup' || property in target) { +- return Reflect.get(target, property, receiver); +- } +- let value = target.unknownProperty.call(receiver, property); +- if (typeof value !== 'function') { +- (!(value === undefined || value === null) && assert$1(messageFor(receiver, property), value === undefined || value === null)); +- } ++ /** ++ * The heap uses the [Mark-Compact Algorithm](https://en.wikipedia.org/wiki/Mark-compact_algorithm) to shift ++ * reachable memory to the bottom of the heap and freeable ++ * memory to the top of the heap. When we have shifted all ++ * the reachable memory to the top of the heap, we move the ++ * offset to the next free position. ++ */ ++ compact() { ++ let compactedSize = 0; ++ let { ++ handleTable, ++ handleState, ++ heap ++ } = this; ++ for (let i = 0; i < length; i++) { ++ let offset = unwrap$1(handleTable[i]); ++ let size = unwrap$1(handleTable[i + 1]) - unwrap$1(offset); ++ let state = handleState[i]; ++ if (state === TableSlotState.Purged) { ++ continue; ++ } else if (state === TableSlotState.Freed) { ++ // transition to "already freed" aka "purged" ++ // a good improvement would be to reuse ++ // these slots ++ handleState[i] = TableSlotState.Purged; ++ compactedSize += size; ++ } else if (state === TableSlotState.Allocated) { ++ for (let j = offset; j <= i + size; j++) { ++ heap[j - compactedSize] = unwrap$1(heap[j]); + } +- }); +- } else { +- self = this; ++ handleTable[i] = offset - compactedSize; ++ } else if (state === TableSlotState.Pointer) { ++ handleTable[i] = offset - compactedSize; ++ } + } +- const destroyable = self; +- registerDestructor$1(self, ensureDestroyCalled, true); +- registerDestructor$1(self, () => destroyable.willDestroy()); ++ this.offset = this.offset - compactedSize; ++ } ++ capture(offset = this.offset) { ++ // Only called in eager mode ++ let buffer = slice$2(this.heap, 0, offset).buffer; ++ return { ++ handle: this.handle, ++ table: this.handleTable, ++ buffer: buffer ++ }; ++ } ++ } ++ class RuntimeProgramImpl { ++ _opcode; ++ constructor(constants, heap) { ++ this.constants = constants; ++ this.heap = heap; ++ this._opcode = new RuntimeOpImpl(this.heap); ++ } ++ opcode(offset) { ++ this._opcode.offset = offset; ++ return this._opcode; ++ } ++ } ++ function slice$2(arr, start, end) { ++ if (arr.slice !== undefined) { ++ return arr.slice(start, end); ++ } ++ let ret = new Int32Array(end); ++ for (; start < end; start++) { ++ ret[start] = unwrap$1(arr[start]); ++ } ++ return ret; ++ } ++ function sizeof(table, handle) { ++ { ++ return -1; ++ } ++ } ++ function artifacts() { ++ return { ++ constants: new ConstantsImpl(), ++ heap: new HeapImpl() ++ }; ++ } + +- // disable chains +- let m = meta(self); +- m.setInitializing(); ++ const glimmerProgram = /*#__PURE__*/Object.defineProperty({ ++ __proto__: null, ++ CompileTimeConstantImpl, ++ ConstantsImpl, ++ HeapImpl, ++ RuntimeConstantsImpl, ++ RuntimeHeapImpl, ++ RuntimeOpImpl, ++ RuntimeProgramImpl, ++ artifacts, ++ hydrateHeap ++ }, Symbol.toStringTag, { value: 'Module' }); + +- // only return when in debug builds and `self` is the proxy created above +- if (self !== this) { +- return self; ++ class DynamicScopeImpl { ++ bucket; ++ constructor(bucket) { ++ if (bucket) { ++ this.bucket = assign({}, bucket); ++ } else { ++ this.bucket = {}; + } + } +- reopen(...args) { +- applyMixin(this, args); +- return this; ++ get(key) { ++ return unwrap$1(this.bucket[key]); + } +- +- /** +- An overridable method called when objects are instantiated. By default, +- does nothing unless it is overridden during class definition. +- Example: +- ```javascript +- import EmberObject from '@ember/object'; +- const Person = EmberObject.extend({ +- init() { +- alert(`Name is ${this.get('name')}`); +- } +- }); +- let steve = Person.create({ +- name: 'Steve' +- }); +- // alerts 'Name is Steve'. +- ``` +- NOTE: If you do override `init` for a framework class like `Component` +- from `@ember/component`, be sure to call `this._super(...arguments)` +- in your `init` declaration! +- If you don't, Ember may not have an opportunity to +- do important setup work, and you'll see strange behavior in your +- application. +- @method init +- @public +- */ +- init(_properties) {} +- +- /** +- Defines the properties that will be concatenated from the superclass +- (instead of overridden). +- By default, when you extend an Ember class a property defined in +- the subclass overrides a property with the same name that is defined +- in the superclass. However, there are some cases where it is preferable +- to build up a property's value by combining the superclass' property +- value with the subclass' value. An example of this in use within Ember +- is the `classNames` property of `Component` from `@ember/component`. +- Here is some sample code showing the difference between a concatenated +- property and a normal one: +- ```javascript +- import EmberObject from '@ember/object'; +- const Bar = EmberObject.extend({ +- // Configure which properties to concatenate +- concatenatedProperties: ['concatenatedProperty'], +- someNonConcatenatedProperty: ['bar'], +- concatenatedProperty: ['bar'] +- }); +- const FooBar = Bar.extend({ +- someNonConcatenatedProperty: ['foo'], +- concatenatedProperty: ['foo'] ++ set(key, reference) { ++ return this.bucket[key] = reference; ++ } ++ child() { ++ return new DynamicScopeImpl(this.bucket); ++ } ++ } ++ class PartialScopeImpl { ++ static root(self, size = 0, owner) { ++ let refs = new Array(size + 1).fill(UNDEFINED_REFERENCE); ++ return new PartialScopeImpl(refs, owner, null, null, null).init({ ++ self + }); +- let fooBar = FooBar.create(); +- fooBar.get('someNonConcatenatedProperty'); // ['foo'] +- fooBar.get('concatenatedProperty'); // ['bar', 'foo'] +- ``` +- This behavior extends to object creation as well. Continuing the +- above example: +- ```javascript +- let fooBar = FooBar.create({ +- someNonConcatenatedProperty: ['baz'], +- concatenatedProperty: ['baz'] +- }) +- fooBar.get('someNonConcatenatedProperty'); // ['baz'] +- fooBar.get('concatenatedProperty'); // ['bar', 'foo', 'baz'] +- ``` +- Adding a single property that is not an array will just add it in the array: +- ```javascript +- let fooBar = FooBar.create({ +- concatenatedProperty: 'baz' +- }) +- view.get('concatenatedProperty'); // ['bar', 'foo', 'baz'] +- ``` +- Using the `concatenatedProperties` property, we can tell Ember to mix the +- content of the properties. +- In `Component` the `classNames`, `classNameBindings` and +- `attributeBindings` properties are concatenated. +- This feature is available for you to use throughout the Ember object model, +- although typical app developers are likely to use it infrequently. Since +- it changes expectations about behavior of properties, you should properly +- document its usage in each individual concatenated property (to not +- mislead your users to think they can override the property in a subclass). +- @property concatenatedProperties +- @type Array +- @default null +- @public +- */ ++ } ++ static sized(size = 0, owner) { ++ let refs = new Array(size + 1).fill(UNDEFINED_REFERENCE); ++ return new PartialScopeImpl(refs, owner, null, null, null); ++ } ++ constructor( ++ // the 0th slot is `self` ++ slots, owner, callerScope, ++ // named arguments and blocks passed to a layout that uses eval ++ evalScope, ++ // locals in scope when the partial was invoked ++ partialMap) { ++ this.slots = slots; ++ this.owner = owner; ++ this.callerScope = callerScope; ++ this.evalScope = evalScope; ++ this.partialMap = partialMap; ++ } ++ init({ ++ self ++ }) { ++ this.slots[0] = self; ++ return this; ++ } ++ getSelf() { ++ return this.get(0); ++ } ++ getSymbol(symbol) { ++ return this.get(symbol); ++ } ++ getBlock(symbol) { ++ let block = this.get(symbol); ++ return block === UNDEFINED_REFERENCE ? null : block; ++ } ++ getEvalScope() { ++ return this.evalScope; ++ } ++ getPartialMap() { ++ return this.partialMap; ++ } ++ bind(symbol, value) { ++ this.set(symbol, value); ++ } ++ bindSelf(self) { ++ this.set(0, self); ++ } ++ bindSymbol(symbol, value) { ++ this.set(symbol, value); ++ } ++ bindBlock(symbol, value) { ++ this.set(symbol, value); ++ } ++ bindEvalScope(map) { ++ this.evalScope = map; ++ } ++ bindPartialMap(map) { ++ this.partialMap = map; ++ } ++ bindCallerScope(scope) { ++ this.callerScope = scope; ++ } ++ getCallerScope() { ++ return this.callerScope; ++ } ++ child() { ++ return new PartialScopeImpl(this.slots.slice(), this.owner, this.callerScope, this.evalScope, this.partialMap); ++ } ++ get(index) { ++ if (index >= this.slots.length) { ++ throw new RangeError(`BUG: cannot get $${index} from scope; length=${this.slots.length}`); ++ } ++ return this.slots[index]; ++ } ++ set(index, value) { ++ if (index >= this.slots.length) { ++ throw new RangeError(`BUG: cannot get $${index} from scope; length=${this.slots.length}`); ++ } ++ this.slots[index] = value; ++ } ++ } + +- /** +- Defines the properties that will be merged from the superclass +- (instead of overridden). +- By default, when you extend an Ember class a property defined in +- the subclass overrides a property with the same name that is defined +- in the superclass. However, there are some cases where it is preferable +- to build up a property's value by merging the superclass property value +- with the subclass property's value. An example of this in use within Ember +- is the `queryParams` property of routes. +- Here is some sample code showing the difference between a merged +- property and a normal one: +- ```javascript +- import EmberObject from '@ember/object'; +- const Bar = EmberObject.extend({ +- // Configure which properties are to be merged +- mergedProperties: ['mergedProperty'], +- someNonMergedProperty: { +- nonMerged: 'superclass value of nonMerged' +- }, +- mergedProperty: { +- page: { replace: false }, +- limit: { replace: true } +- } +- }); +- const FooBar = Bar.extend({ +- someNonMergedProperty: { +- completelyNonMerged: 'subclass value of nonMerged' +- }, +- mergedProperty: { +- limit: { replace: false } +- } +- }); +- let fooBar = FooBar.create(); +- fooBar.get('someNonMergedProperty'); +- // => { completelyNonMerged: 'subclass value of nonMerged' } +- // +- // Note the entire object, including the nonMerged property of +- // the superclass object, has been replaced +- fooBar.get('mergedProperty'); +- // => { +- // page: {replace: false}, +- // limit: {replace: false} +- // } +- // +- // Note the page remains from the superclass, and the +- // `limit` property's value of `false` has been merged from +- // the subclass. +- ``` +- This behavior is not available during object `create` calls. It is only +- available at `extend` time. +- In `Route` the `queryParams` property is merged. +- This feature is available for you to use throughout the Ember object model, +- although typical app developers are likely to use it infrequently. Since +- it changes expectations about behavior of properties, you should properly +- document its usage in each individual merged property (to not +- mislead your users to think they can override the property in a subclass). +- @property mergedProperties +- @type Array +- @default null +- @public +- */ ++ // These symbols represent "friend" properties that are used inside of ++ // the VM in other classes, but are not intended to be a part of ++ // Glimmer's API. + +- /** +- Destroyed object property flag. +- if this property is `true` the observers and bindings were already +- removed by the effect of calling the `destroy()` method. +- @property isDestroyed +- @default false +- @public +- */ +- get isDestroyed() { +- return isDestroyed(this); ++ const INNER_VM = Symbol('INNER_VM'); ++ const DESTROYABLE_STACK = Symbol('DESTROYABLE_STACK'); ++ const STACKS = Symbol('STACKS'); ++ const REGISTERS = Symbol('REGISTERS'); ++ const HEAP = Symbol('HEAP'); ++ const CONSTANTS = Symbol('CONSTANTS'); ++ const ARGS$1 = Symbol('ARGS'); ++ class CursorImpl { ++ constructor(element, nextSibling) { ++ this.element = element; ++ this.nextSibling = nextSibling; + } +- set isDestroyed(_value) { +- (assert$1(`You cannot set \`${this}.isDestroyed\` directly, please use \`.destroy()\`.`, false)); ++ } ++ class ConcreteBounds { ++ constructor(parentNode, first, last) { ++ this.parentNode = parentNode; ++ this.first = first; ++ this.last = last; + } +- +- /** +- Destruction scheduled flag. The `destroy()` method has been called. +- The object stays intact until the end of the run loop at which point +- the `isDestroyed` flag is set. +- @property isDestroying +- @default false +- @public +- */ +- get isDestroying() { +- return isDestroying(this); ++ parentElement() { ++ return this.parentNode; + } +- set isDestroying(_value) { +- (assert$1(`You cannot set \`${this}.isDestroying\` directly, please use \`.destroy()\`.`, false)); ++ firstNode() { ++ return this.first; + } ++ lastNode() { ++ return this.last; ++ } ++ } ++ function move(bounds, reference) { ++ let parent = bounds.parentElement(); ++ let first = bounds.firstNode(); ++ let last = bounds.lastNode(); ++ let current = first; + +- /** +- Destroys an object by setting the `isDestroyed` flag and removing its +- metadata, which effectively destroys observers and bindings. +- If you try to set a property on a destroyed object, an exception will be +- raised. +- Note that destruction is scheduled for the end of the run loop and does not +- happen immediately. It will set an isDestroying flag immediately. +- @method destroy +- @return {EmberObject} receiver +- @public +- */ +- destroy() { +- // Used to ensure that manually calling `.destroy()` does not immediately call destroy again +- destroyCalled.add(this); +- try { +- destroy(this); +- } finally { +- destroyCalled.delete(this); ++ // eslint-disable-next-line no-constant-condition ++ while (true) { ++ let next = current.nextSibling; ++ parent.insertBefore(current, reference); ++ if (current === last) { ++ return next; + } +- return this; ++ current = expect(next, 'invalid bounds'); + } ++ } ++ function clear(bounds) { ++ let parent = bounds.parentElement(); ++ let first = bounds.firstNode(); ++ let last = bounds.lastNode(); ++ let current = first; + +- /** +- Override to implement teardown. +- @method willDestroy +- @public +- */ +- willDestroy() {} ++ // eslint-disable-next-line no-constant-condition ++ while (true) { ++ let next = current.nextSibling; ++ parent.removeChild(current); ++ if (current === last) { ++ return next; ++ } ++ current = expect(next, 'invalid bounds'); ++ } ++ } ++ function normalizeStringValue(value) { ++ if (isEmpty$2(value)) { ++ return ''; ++ } ++ return String(value); ++ } ++ function shouldCoerce(value) { ++ return isString(value) || isEmpty$2(value) || typeof value === 'boolean' || typeof value === 'number'; ++ } ++ function isEmpty$2(value) { ++ return value === null || value === undefined || typeof value.toString !== 'function'; ++ } ++ function isSafeString(value) { ++ return typeof value === 'object' && value !== null && typeof value.toHTML === 'function'; ++ } ++ function isNode(value) { ++ return typeof value === 'object' && value !== null && typeof value.nodeType === 'number'; ++ } ++ function isFragment(value) { ++ return isNode(value) && value.nodeType === 11; ++ } ++ function isString(value) { ++ return typeof value === 'string'; ++ } + +- /** +- Returns a string representation which attempts to provide more information +- than Javascript's `toString` typically does, in a generic way for all Ember +- objects. +- ```javascript +- import EmberObject from '@ember/object'; +- const Person = EmberObject.extend(); +- person = Person.create(); +- person.toString(); //=> "" +- ``` +- If the object's class is not defined on an Ember namespace, it will +- indicate it is a subclass of the registered superclass: +- ```javascript +- const Student = Person.extend(); +- let student = Student.create(); +- student.toString(); //=> "<(subclass of Person):ember1025>" +- ``` +- If the method `toStringExtension` is defined, its return value will be +- included in the output. +- ```javascript +- const Teacher = Person.extend({ +- toStringExtension() { +- return this.get('fullName'); +- } +- }); +- teacher = Teacher.create(); +- teacher.toString(); //=> "" +- ``` +- @method toString +- @return {String} string representation +- @public +- */ +- toString() { +- let extension = hasToStringExtension(this) ? `:${this.toStringExtension()}` : ''; +- return `<${getFactoryFor(this) || '(unknown)'}:${guidFor(this)}${extension}>`; +- } +- +- /** +- Creates a new subclass. +- ```javascript +- import EmberObject from '@ember/object'; +- const Person = EmberObject.extend({ +- say(thing) { +- alert(thing); +- } +- }); +- ``` +- This defines a new subclass of EmberObject: `Person`. It contains one method: `say()`. +- You can also create a subclass from any existing class by calling its `extend()` method. +- For example, you might want to create a subclass of Ember's built-in `Component` class: +- ```javascript +- import Component from '@ember/component'; +- const PersonComponent = Component.extend({ +- tagName: 'li', +- classNameBindings: ['isAdministrator'] +- }); +- ``` +- When defining a subclass, you can override methods but still access the +- implementation of your parent class by calling the special `_super()` method: +- ```javascript +- import EmberObject from '@ember/object'; +- const Person = EmberObject.extend({ +- say(thing) { +- let name = this.get('name'); +- alert(`${name} says: ${thing}`); +- } +- }); +- const Soldier = Person.extend({ +- say(thing) { +- this._super(`${thing}, sir!`); +- }, +- march(numberOfHours) { +- alert(`${this.get('name')} marches for ${numberOfHours} hours.`); +- } +- }); +- let yehuda = Soldier.create({ +- name: 'Yehuda Katz' +- }); +- yehuda.say('Yes'); // alerts "Yehuda Katz says: Yes, sir!" +- ``` +- The `create()` on line #17 creates an *instance* of the `Soldier` class. +- The `extend()` on line #8 creates a *subclass* of `Person`. Any instance +- of the `Person` class will *not* have the `march()` method. +- You can also pass `Mixin` classes to add additional properties to the subclass. +- ```javascript +- import EmberObject from '@ember/object'; +- import Mixin from '@ember/object/mixin'; +- const Person = EmberObject.extend({ +- say(thing) { +- alert(`${this.get('name')} says: ${thing}`); +- } +- }); +- const SingingMixin = Mixin.create({ +- sing(thing) { +- alert(`${this.get('name')} sings: la la la ${thing}`); +- } +- }); +- const BroadwayStar = Person.extend(SingingMixin, { +- dance() { +- alert(`${this.get('name')} dances: tap tap tap tap `); +- } +- }); +- ``` +- The `BroadwayStar` class contains three methods: `say()`, `sing()`, and `dance()`. +- @method extend +- @static +- @for @ember/object +- @param {Mixin} [mixins]* One or more Mixin classes +- @param {Object} [arguments]* Object containing values to use within the new class +- @public +- */ +- +- static extend(...mixins) { +- let Class = class extends this {}; +- reopen.apply(Class.PrototypeMixin, mixins); +- return Class; +- } +- +- /** +- Creates an instance of a class. Accepts either no arguments, or an object +- containing values to initialize the newly instantiated object with. +- ```javascript +- import EmberObject from '@ember/object'; +- const Person = EmberObject.extend({ +- helloWorld() { +- alert(`Hi, my name is ${this.get('name')}`); +- } +- }); +- let tom = Person.create({ +- name: 'Tom Dale' +- }); +- tom.helloWorld(); // alerts "Hi, my name is Tom Dale". +- ``` +- `create` will call the `init` function if defined during +- `AnyObject.extend` +- If no arguments are passed to `create`, it will not set values to the new +- instance during initialization: +- ```javascript +- let noName = Person.create(); +- noName.helloWorld(); // alerts undefined +- ``` +- NOTE: For performance reasons, you cannot declare methods or computed +- properties during `create`. You should instead declare methods and computed +- properties when using `extend`. +- @method create +- @for @ember/object +- @static +- @param [arguments]* +- @public +- */ +- +- static create(...args) { +- let props = args[0]; +- let instance; +- if (props !== undefined) { +- instance = new this(getOwner$2(props)); +- // TODO(SAFETY): at present, we cannot actually rely on this being set, +- // because a number of acceptance tests are (incorrectly? Unclear!) +- // relying on the ability to run through this path with `factory` being +- // `undefined`. It's *possible* that actually means that the type for +- // `setFactoryFor()` should allow `undefined`, but we typed it the other +- // way for good reason! Accordingly, this *casts* `factory`, and the +- // commented-out `assert()` is here in the hope that we can enable it +- // after addressing tests *or* updating the call signature here. +- let factory = getFactoryFor(props); +- // assert(`missing factory when creating object ${instance}`, factory !== undefined); +- setFactoryFor(instance, factory); +- } else { +- instance = new this(); +- } +- if (args.length <= 1) { +- initialize(instance, props); ++ /* ++ * @method normalizeProperty ++ * @param element {HTMLElement} ++ * @param slotName {String} ++ * @returns {Object} { name, type } ++ */ ++ function normalizeProperty(element, slotName) { ++ let type, normalized; ++ if (slotName in element) { ++ normalized = slotName; ++ type = 'prop'; ++ } else { ++ let lower = slotName.toLowerCase(); ++ if (lower in element) { ++ type = 'prop'; ++ normalized = lower; + } else { +- initialize(instance, flattenProps.apply(this, args)); ++ type = 'attr'; ++ normalized = slotName; + } +- +- // SAFETY: The `initialize` call is responsible to merge the prototype chain +- // so that this holds. +- return instance; + } +- +- /** +- Augments a constructor's prototype with additional +- properties and functions: +- ```javascript +- import EmberObject from '@ember/object'; +- const MyObject = EmberObject.extend({ +- name: 'an object' +- }); +- o = MyObject.create(); +- o.get('name'); // 'an object' +- MyObject.reopen({ +- say(msg) { +- console.log(msg); +- } +- }); +- o2 = MyObject.create(); +- o2.say('hello'); // logs "hello" +- o.say('goodbye'); // logs "goodbye" +- ``` +- To add functions and properties to the constructor itself, +- see `reopenClass` +- @method reopen +- @for @ember/object +- @static +- @public +- */ +- static reopen(...args) { +- this.willReopen(); +- reopen.apply(this.PrototypeMixin, args); +- return this; ++ if (type === 'prop' && (normalized.toLowerCase() === 'style' || preferAttr(element.tagName, normalized))) { ++ type = 'attr'; + } +- static willReopen() { +- let p = this.prototype; +- if (wasApplied.has(p)) { +- wasApplied.delete(p); ++ return { ++ normalized, ++ type ++ }; ++ } + +- // If the base mixin already exists and was applied, create a new mixin to +- // make sure that it gets properly applied. Reusing the same mixin after +- // the first `proto` call will cause it to get skipped. +- if (prototypeMixinMap.has(this)) { +- prototypeMixinMap.set(this, Mixin.create(this.PrototypeMixin)); +- } +- } ++ // properties that MUST be set as attributes, due to: ++ // * browser bug ++ // * strange spec outlier ++ const ATTR_OVERRIDES = { ++ INPUT: { ++ form: true, ++ // Chrome 46.0.2464.0: 'autocorrect' in document.createElement('input') === false ++ // Safari 8.0.7: 'autocorrect' in document.createElement('input') === false ++ // Mobile Safari (iOS 8.4 simulator): 'autocorrect' in document.createElement('input') === true ++ autocorrect: true, ++ // Chrome 54.0.2840.98: 'list' in document.createElement('input') === true ++ // Safari 9.1.3: 'list' in document.createElement('input') === false ++ list: true ++ }, ++ // element.form is actually a legitimate readOnly property, that is to be ++ // mutated, but must be mutated by setAttribute... ++ SELECT: { ++ form: true ++ }, ++ OPTION: { ++ form: true ++ }, ++ TEXTAREA: { ++ form: true ++ }, ++ LABEL: { ++ form: true ++ }, ++ FIELDSET: { ++ form: true ++ }, ++ LEGEND: { ++ form: true ++ }, ++ OBJECT: { ++ form: true ++ }, ++ OUTPUT: { ++ form: true ++ }, ++ BUTTON: { ++ form: true + } +- +- /** +- Augments a constructor's own properties and functions: +- ```javascript +- import EmberObject from '@ember/object'; +- const MyObject = EmberObject.extend({ +- name: 'an object' +- }); +- MyObject.reopenClass({ +- canBuild: false +- }); +- MyObject.canBuild; // false +- o = MyObject.create(); +- ``` +- In other words, this creates static properties and functions for the class. +- These are only available on the class and not on any instance of that class. +- ```javascript +- import EmberObject from '@ember/object'; +- const Person = EmberObject.extend({ +- name: '', +- sayHello() { +- alert(`Hello. My name is ${this.get('name')}`); +- } +- }); +- Person.reopenClass({ +- species: 'Homo sapiens', +- createPerson(name) { +- return Person.create({ name }); ++ }; ++ function preferAttr(tagName, propName) { ++ let tag = ATTR_OVERRIDES[tagName.toUpperCase()]; ++ return tag && tag[propName.toLowerCase()] || false; ++ } ++ const badProtocols = ['javascript:', 'vbscript:']; ++ const badTags = ['A', 'BODY', 'LINK', 'IMG', 'IFRAME', 'BASE', 'FORM']; ++ const badTagsForDataURI = ['EMBED']; ++ const badAttributes = ['href', 'src', 'background', 'action']; ++ const badAttributesForDataURI = ['src']; ++ function has(array, item) { ++ return array.indexOf(item) !== -1; ++ } ++ function checkURI(tagName, attribute) { ++ return (tagName === null || has(badTags, tagName)) && has(badAttributes, attribute); ++ } ++ function checkDataURI(tagName, attribute) { ++ if (tagName === null) return false; ++ return has(badTagsForDataURI, tagName) && has(badAttributesForDataURI, attribute); ++ } ++ function requiresSanitization(tagName, attribute) { ++ return checkURI(tagName, attribute) || checkDataURI(tagName, attribute); ++ } ++ function findProtocolForURL() { ++ if (typeof URL === 'object' && URL !== null && ++ // this is super annoying, TS thinks that URL **must** be a function so `URL.parse` check ++ // thinks it is `never` without this `as unknown as any` ++ typeof URL.parse === 'function') { ++ // In Ember-land the `fastboot` package sets the `URL` global to `require('url')` ++ // ultimately, this should be changed (so that we can either rely on the natural `URL` global ++ // that exists) but for now we have to detect the specific `FastBoot` case first ++ // ++ // a future version of `fastboot` will detect if this legacy URL setup is required (by ++ // inspecting Ember version) and if new enough, it will avoid shadowing the `URL` global ++ // constructor with `require('url')`. ++ let nodeURL = URL; ++ return url => { ++ let protocol = null; ++ if (typeof url === 'string') { ++ protocol = nodeURL.parse(url).protocol; + } +- }); +- let tom = Person.create({ +- name: 'Tom Dale' +- }); +- let yehuda = Person.createPerson('Yehuda Katz'); +- tom.sayHello(); // "Hello. My name is Tom Dale" +- yehuda.sayHello(); // "Hello. My name is Yehuda Katz" +- alert(Person.species); // "Homo sapiens" +- ``` +- Note that `species` and `createPerson` are *not* valid on the `tom` and `yehuda` +- variables. They are only valid on `Person`. +- To add functions and properties to instances of +- a constructor by extending the constructor's prototype +- see `reopen` +- @method reopenClass +- @for @ember/object +- @static +- @public +- */ +- static reopenClass(...mixins) { +- applyMixin(this, mixins); +- return this; +- } +- static detect(obj) { +- if ('function' !== typeof obj) { +- return false; +- } +- while (obj) { +- if (obj === this) { +- return true; ++ return protocol === null ? ':' : protocol; ++ }; ++ } else if (typeof URL === 'function') { ++ return _url => { ++ try { ++ let url = new URL(_url); ++ return url.protocol; ++ } catch (error) { ++ // any non-fully qualified url string will trigger an error (because there is no ++ // baseURI that we can provide; in that case we **know** that the protocol is ++ // "safe" because it isn't specifically one of the `badProtocols` listed above ++ // (and those protocols can never be the default baseURI) ++ return ':'; + } +- obj = obj.superclass; +- } +- return false; +- } +- static detectInstance(obj) { +- return obj instanceof this; ++ }; ++ } else { ++ throw new Error(`@glimmer/runtime needs a valid "globalThis.URL"`); + } +- +- /** +- In some cases, you may want to annotate computed properties with additional +- metadata about how they function or what values they operate on. For +- example, computed property functions may close over variables that are then +- no longer available for introspection. +- You can pass a hash of these values to a computed property like this: +- ```javascript +- import { computed } from '@ember/object'; +- person: computed(function() { +- let personId = this.get('personId'); +- return Person.create({ id: personId }); +- }).meta({ type: Person }) +- ``` +- Once you've done this, you can retrieve the values saved to the computed +- property from your class like this: +- ```javascript +- MyClass.metaForProperty('person'); +- ``` +- This will return the original hash that was passed to `meta()`. +- @static +- @method metaForProperty +- @param key {String} property name +- @private +- */ +- static metaForProperty(key) { +- let proto = this.proto(); // ensure prototype is initialized +- let possibleDesc = descriptorForProperty(proto, key); +- (!(possibleDesc !== undefined) && assert$1(`metaForProperty() could not find a computed property with key '${key}'.`, possibleDesc !== undefined)); +- return possibleDesc._meta || {}; ++ } ++ let _protocolForUrlImplementation; ++ function protocolForUrl(url) { ++ if (!_protocolForUrlImplementation) { ++ _protocolForUrlImplementation = findProtocolForURL(); + } +- +- /** +- Iterate over each computed property for the class, passing its name +- and any associated metadata (see `metaForProperty`) to the callback. +- @static +- @method eachComputedProperty +- @param {Function} callback +- @param {Object} binding +- @private +- */ +- static eachComputedProperty(callback, binding = this) { +- this.proto(); // ensure prototype is initialized +- let empty = {}; +- meta(this.prototype).forEachDescriptors((name, descriptor) => { +- if (descriptor.enumerable) { +- let meta = descriptor._meta || empty; +- callback.call(binding, name, meta); +- } +- }); ++ return _protocolForUrlImplementation(url); ++ } ++ function sanitizeAttributeValue(element, attribute, value) { ++ let tagName = null; ++ if (value === null || value === undefined) { ++ return value; + } +- static get PrototypeMixin() { +- let prototypeMixin = prototypeMixinMap.get(this); +- if (prototypeMixin === undefined) { +- prototypeMixin = Mixin.create(); +- prototypeMixin.ownerConstructor = this; +- prototypeMixinMap.set(this, prototypeMixin); +- } +- return prototypeMixin; ++ if (isSafeString(value)) { ++ return value.toHTML(); + } +- static get superclass() { +- let c = Object.getPrototypeOf(this); +- return c !== Function.prototype ? c : undefined; ++ if (!element) { ++ tagName = null; ++ } else { ++ tagName = element.tagName.toUpperCase(); + } +- static proto() { +- let p = this.prototype; +- if (!wasApplied.has(p)) { +- wasApplied.add(p); +- let parent = this.superclass; +- if (parent) { +- parent.proto(); +- } +- +- // If the prototype mixin exists, apply it. In the case of native classes, +- // it will not exist (unless the class has been reopened). +- if (prototypeMixinMap.has(this)) { +- this.PrototypeMixin.apply(p); +- } ++ let str = normalizeStringValue(value); ++ if (checkURI(tagName, attribute)) { ++ let protocol = protocolForUrl(str); ++ if (has(badProtocols, protocol)) { ++ return `unsafe:${str}`; + } +- return p; + } +- static toString() { +- return `<${getFactoryFor(this) || '(unknown)'}:constructor>`; ++ if (checkDataURI(tagName, attribute)) { ++ return `unsafe:${str}`; + } +- static isClass = true; +- static isMethod = false; +- static _onLookup; +- static _lazyInjections; ++ return str; + } +- function flattenProps(...props) { +- let initProperties = {}; +- for (let properties of props) { +- (!(!(properties instanceof Mixin)) && assert$1('EmberObject.create no longer supports mixing in other ' + 'definitions, use .extend & .create separately instead.', !(properties instanceof Mixin))); +- let keyNames = Object.keys(properties); +- for (let j = 0, k = keyNames.length; j < k; j++) { +- let keyName = keyNames[j]; +- let value = properties[keyName]; +- initProperties[keyName] = value; +- } ++ function dynamicAttribute(element, attr, namespace, isTrusting = false) { ++ const { ++ tagName, ++ namespaceURI ++ } = element; ++ const attribute = { ++ element, ++ name: attr, ++ namespace ++ }; ++ if (attr === 'style' && !isTrusting) { ++ return new DebugStyleAttributeManager(attribute); ++ } ++ if (namespaceURI === NS_SVG) { ++ return buildDynamicAttribute(tagName, attr, attribute); ++ } ++ const { ++ type, ++ normalized ++ } = normalizeProperty(element, attr); ++ if (type === 'attr') { ++ return buildDynamicAttribute(tagName, normalized, attribute); ++ } else { ++ return buildDynamicProperty(tagName, normalized, attribute); + } +- return initProperties; + } +- { +- /** +- Provides lookup-time type validation for injected properties. +- @private +- @method _onLookup +- */ +- CoreObject._onLookup = function injectedPropertyAssertion(debugContainerKey) { +- let [type] = debugContainerKey.split(':'); +- let proto = this.proto(); +- for (let key in proto) { +- let desc = descriptorForProperty(proto, key); +- if (desc && DEBUG_INJECTION_FUNCTIONS.has(desc._getter)) { +- (!(type === 'controller' || DEBUG_INJECTION_FUNCTIONS.get(desc._getter).type !== 'controller') && assert$1(`Defining \`${key}\` as an injected controller property on a non-controller (\`${debugContainerKey}\`) is not allowed.`, type === 'controller' || DEBUG_INJECTION_FUNCTIONS.get(desc._getter).type !== 'controller')); +- } +- } +- }; +- +- /** +- Returns a hash of property names and container names that injected +- properties will lookup on the container lazily. +- @method _lazyInjections +- @return {Object} Hash of all lazy injected property keys to container names +- @private +- */ +- CoreObject._lazyInjections = function () { +- let injections = {}; +- let proto = this.proto(); +- let key; +- let desc; +- for (key in proto) { +- desc = descriptorForProperty(proto, key); +- if (desc && DEBUG_INJECTION_FUNCTIONS.has(desc._getter)) { +- let { +- namespace, +- source, +- type, +- name +- } = DEBUG_INJECTION_FUNCTIONS.get(desc._getter); +- injections[key] = { +- namespace, +- source, +- specifier: `${type}:${name || key}` +- }; +- } +- } +- return injections; +- }; ++ function buildDynamicAttribute(tagName, name, attribute) { ++ if (requiresSanitization(tagName, name)) { ++ return new SafeDynamicAttribute(attribute); ++ } else { ++ return new SimpleDynamicAttribute(attribute); ++ } + } +- const EmberCoreObject = CoreObject; +- +- const emberObjectCore = /*#__PURE__*/Object.defineProperty({ +- __proto__: null, +- default: EmberCoreObject +- }, Symbol.toStringTag, { value: 'Module' }); +- +- /** +- @module @ember/object +- */ +- +- /** +- `EmberObject` is the main base class for all Ember objects. It is a subclass +- of `CoreObject` with the `Observable` mixin applied. For details, +- see the documentation for each of these. +- +- @class EmberObject +- @extends CoreObject +- @uses Observable +- @public +- */ +- // eslint-disable-next-line @typescript-eslint/no-empty-interface +- +- class EmberObject extends EmberCoreObject.extend(Observable) { +- get _debugContainerKey() { +- let factory = getFactoryFor(this); +- return factory !== undefined && factory.fullName; ++ function buildDynamicProperty(tagName, name, attribute) { ++ if (requiresSanitization(tagName, name)) { ++ return new SafeDynamicProperty(name, attribute); + } ++ if (isUserInputValue(tagName, name)) { ++ return new InputValueDynamicAttribute(name, attribute); ++ } ++ if (isOptionSelected(tagName, name)) { ++ return new OptionSelectedDynamicAttribute(name, attribute); ++ } ++ return new DefaultDynamicProperty(name, attribute); + } +- +- /** +- Decorator that turns the target function into an Action which can be accessed +- directly by reference. +- +- ```js +- import Component from '@ember/component'; +- import { action, set } from '@ember/object'; +- +- export default class Tooltip extends Component { +- @action +- toggleShowing() { +- set(this, 'isShowing', !this.isShowing); +- } ++ class DynamicAttribute { ++ constructor(attribute) { ++ this.attribute = attribute; + } +- ``` +- ```hbs +- +- +- +- {{#if isShowing}} +-
+- I'm a tooltip! +-
+- {{/if}} +- ``` +- +- Decorated actions also interop with the string style template actions: +- +- ```hbs +- +- +- +- {{#if isShowing}} +-
+- I'm a tooltip! +-
+- {{/if}} +- ``` +- +- It also binds the function directly to the instance, so it can be used in any +- context and will correctly refer to the class it came from: +- +- ```hbs +- +- +- +- {{#if isShowing}} +-
+- I'm a tooltip! +-
+- {{/if}} +- ``` +- +- This can also be used in JavaScript code directly: +- +- ```js +- import Component from '@ember/component'; +- import { action, set } from '@ember/object'; +- +- export default class Tooltip extends Component { +- constructor() { +- super(...arguments); +- +- // this.toggleShowing is still bound correctly when added to +- // the event listener +- document.addEventListener('click', this.toggleShowing); ++ } ++ class SimpleDynamicAttribute extends DynamicAttribute { ++ set(dom, value, _env) { ++ const normalizedValue = normalizeValue(value); ++ if (normalizedValue !== null) { ++ const { ++ name, ++ namespace ++ } = this.attribute; ++ dom.__setAttribute(name, normalizedValue, namespace); + } +- +- @action +- toggleShowing() { +- set(this, 'isShowing', !this.isShowing); ++ } ++ update(value, _env) { ++ const normalizedValue = normalizeValue(value); ++ const { ++ element, ++ name ++ } = this.attribute; ++ if (normalizedValue === null) { ++ element.removeAttribute(name); ++ } else { ++ element.setAttribute(name, normalizedValue); + } + } +- ``` +- +- This is considered best practice, since it means that methods will be bound +- correctly no matter where they are used. By contrast, the `{{action}}` helper +- and modifier can also be used to bind context, but it will be required for +- every usage of the method: +- +- ```hbs +- +- +- +- {{#if isShowing}} +-
+- I'm a tooltip! +-
+- {{/if}} +- ``` +- +- They also do not have equivalents in JavaScript directly, so they cannot be +- used for other situations where binding would be useful. +- +- @public +- @method action +- @for @ember/object +- @static +- @param {Function|undefined} callback The function to turn into an action, +- when used in classic classes +- @return {PropertyDecorator} property decorator instance +- */ +- +- const BINDINGS_MAP = new WeakMap(); +- function hasProto(obj) { +- return obj != null && obj.constructor !== undefined && typeof obj.constructor.proto === 'function'; + } +- function setupAction(target, key, actionFn) { +- if (hasProto(target)) { +- target.constructor.proto(); ++ class DefaultDynamicProperty extends DynamicAttribute { ++ constructor(normalizedName, attribute) { ++ super(attribute); ++ this.normalizedName = normalizedName; + } +- if (!Object.prototype.hasOwnProperty.call(target, 'actions')) { +- let parentActions = target.actions; +- // we need to assign because of the way mixins copy actions down when inheriting +- target.actions = parentActions ? Object.assign({}, parentActions) : {}; ++ value; ++ set(dom, value, _env) { ++ if (value !== null && value !== undefined) { ++ this.value = value; ++ dom.__setProperty(this.normalizedName, value); ++ } + } +- (!(target.actions != null) && assert$1("[BUG] Somehow the target doesn't have actions!", target.actions != null)); +- target.actions[key] = actionFn; +- return { +- get() { +- let bindings = BINDINGS_MAP.get(this); +- if (bindings === undefined) { +- bindings = new Map(); +- BINDINGS_MAP.set(this, bindings); +- } +- let fn = bindings.get(actionFn); +- if (fn === undefined) { +- fn = actionFn.bind(this); +- bindings.set(actionFn, fn); ++ update(value, _env) { ++ const { ++ element ++ } = this.attribute; ++ if (this.value !== value) { ++ element[this.normalizedName] = this.value = value; ++ if (value === null || value === undefined) { ++ this.removeAttribute(); + } +- return fn; + } +- }; ++ } ++ removeAttribute() { ++ // TODO this sucks but to preserve properties first and to meet current ++ // semantics we must do this. ++ const { ++ element, ++ namespace ++ } = this.attribute; ++ if (namespace) { ++ element.removeAttributeNS(namespace, this.normalizedName); ++ } else { ++ element.removeAttribute(this.normalizedName); ++ } ++ } + } +- function action$1(...args) { +- let actionFn; +- if (!isElementDescriptor(args)) { +- actionFn = args[0]; +- let decorator = function (target, key, _desc, _meta, isClassicDecorator) { +- (!(isClassicDecorator) && assert$1('The @action decorator may only be passed a method when used in classic classes. You should decorate methods directly in native classes', isClassicDecorator)); +- (!(typeof actionFn === 'function') && assert$1('The action() decorator must be passed a method when used in classic classes', typeof actionFn === 'function')); +- return setupAction(target, key, actionFn); +- }; +- setClassicDecorator(decorator); +- return decorator; ++ class SafeDynamicProperty extends DefaultDynamicProperty { ++ set(dom, value, env) { ++ const { ++ element, ++ name ++ } = this.attribute; ++ const sanitized = sanitizeAttributeValue(element, name, value); ++ super.set(dom, sanitized, env); ++ } ++ update(value, env) { ++ const { ++ element, ++ name ++ } = this.attribute; ++ const sanitized = sanitizeAttributeValue(element, name, value); ++ super.update(sanitized, env); + } +- let [target, key, desc] = args; +- actionFn = desc?.value; +- (!(typeof actionFn === 'function') && assert$1('The @action decorator must be applied to methods when used in native classes', typeof actionFn === 'function')); // SAFETY: TS types are weird with decorators. This should work. +- return setupAction(target, key, actionFn); + } +- +- // SAFETY: TS types are weird with decorators. This should work. +- setClassicDecorator(action$1); +- +- // .......................................................... +- // OBSERVER HELPER +- // +- +- /** +- Specify a method that observes property changes. +- +- ```javascript +- import EmberObject from '@ember/object'; +- import { observer } from '@ember/object'; +- +- export default EmberObject.extend({ +- valueObserver: observer('value', function() { +- // Executes whenever the "value" property changes +- }) +- }); +- ``` +- +- Also available as `Function.prototype.observes` if prototype extensions are +- enabled. +- +- @method observer +- @for @ember/object +- @param {String} propertyNames* +- @param {Function} func +- @return func +- @public +- @static +- */ +- function observer(...args) { +- let funcOrDef = args.pop(); +- (!(typeof funcOrDef === 'function' || typeof funcOrDef === 'object' && funcOrDef !== null) && assert$1('observer must be provided a function or an observer definition', typeof funcOrDef === 'function' || typeof funcOrDef === 'object' && funcOrDef !== null)); +- let func; +- let dependentKeys; +- let sync; +- if (typeof funcOrDef === 'function') { +- func = funcOrDef; +- dependentKeys = args; +- sync = !ENV._DEFAULT_ASYNC_OBSERVERS; +- } else { +- func = funcOrDef.fn; +- dependentKeys = funcOrDef.dependentKeys; +- sync = funcOrDef.sync; ++ class SafeDynamicAttribute extends SimpleDynamicAttribute { ++ set(dom, value, env) { ++ const { ++ element, ++ name ++ } = this.attribute; ++ const sanitized = sanitizeAttributeValue(element, name, value); ++ super.set(dom, sanitized, env); + } +- (!(typeof func === 'function') && assert$1('observer called without a function', typeof func === 'function')); +- (!(Array.isArray(dependentKeys) && dependentKeys.length > 0 && dependentKeys.every(p => typeof p === 'string' && Boolean(p.length))) && assert$1('observer called without valid path', Array.isArray(dependentKeys) && dependentKeys.length > 0 && dependentKeys.every(p => typeof p === 'string' && Boolean(p.length)))); +- (!(typeof sync === 'boolean') && assert$1('observer called without sync', typeof sync === 'boolean')); +- let paths = []; +- for (let dependentKey of dependentKeys) { +- expandProperties(dependentKey, path => paths.push(path)); ++ update(value, env) { ++ const { ++ element, ++ name ++ } = this.attribute; ++ const sanitized = sanitizeAttributeValue(element, name, value); ++ super.update(sanitized, env); + } +- setObservers(func, { +- paths, +- sync +- }); +- return func; + } +- +- const emberObjectIndex = /*#__PURE__*/Object.defineProperty({ +- __proto__: null, +- action: action$1, +- computed, +- default: EmberObject, +- defineProperty, +- get: get$2, +- getProperties, +- notifyPropertyChange, +- observer, +- set, +- setProperties, +- trySet +- }, Symbol.toStringTag, { value: 'Module' }); +- +- /** +- * Default component template, which is a plain yield +- */ +- const DEFAULT_TEMPLATE_BLOCK = [[[opcodes.Yield, 1, null]], ['&default'], false, []]; +- const DEFAULT_TEMPLATE = { +- // random uuid +- id: '1b32f5c2-7623-43d6-a0ad-9672898920a1', +- moduleName: '__default__.hbs', +- block: JSON.stringify(DEFAULT_TEMPLATE_BLOCK), +- scope: null, +- isStrictMode: true +- }; +- const WELL_KNOWN_EMPTY_ARRAY = Object.freeze([]); +- const STARTER_CONSTANTS = constants(WELL_KNOWN_EMPTY_ARRAY); +- const WELL_KNOWN_EMPTY_ARRAY_POSITION = STARTER_CONSTANTS.indexOf(WELL_KNOWN_EMPTY_ARRAY); +- class CompileTimeConstantImpl { +- // `0` means NULL +- +- values = STARTER_CONSTANTS.slice(); +- indexMap = new Map(this.values.map((value, index) => [value, index])); +- value(value) { +- let indexMap = this.indexMap; +- let index = indexMap.get(value); +- if (index === undefined) { +- index = this.values.push(value) - 1; +- indexMap.set(value, index); +- } +- return index; ++ class InputValueDynamicAttribute extends DefaultDynamicProperty { ++ set(dom, value) { ++ dom.__setProperty('value', normalizeStringValue(value)); + } +- array(values) { +- if (values.length === 0) { +- return WELL_KNOWN_EMPTY_ARRAY_POSITION; +- } +- let handles = new Array(values.length); +- for (let i = 0; i < values.length; i++) { +- handles[i] = this.value(values[i]); ++ update(value) { ++ const input = castToBrowser(this.attribute.element, ['input', 'textarea']); ++ const currentValue = input.value; ++ const normalizedValue = normalizeStringValue(value); ++ if (currentValue !== normalizedValue) { ++ input.value = normalizedValue; + } +- return this.value(handles); +- } +- toPool() { +- return this.values; + } + } +- class RuntimeConstantsImpl { +- values; +- constructor(pool) { +- this.values = pool; +- } +- getValue(handle) { +- return this.values[handle]; ++ class OptionSelectedDynamicAttribute extends DefaultDynamicProperty { ++ set(dom, value) { ++ if (value !== null && value !== undefined && value !== false) { ++ dom.__setProperty('selected', true); ++ } + } +- getArray(value) { +- let handles = this.getValue(value); +- let reified = new Array(handles.length); +- for (const [i, n] of enumerate(handles)) { +- reified[i] = this.getValue(n); ++ update(value) { ++ const option = castToBrowser(this.attribute.element, 'option'); ++ if (value) { ++ option.selected = true; ++ } else { ++ option.selected = false; + } +- return reified; + } + } +- class ConstantsImpl extends CompileTimeConstantImpl { +- reifiedArrs = { +- [WELL_KNOWN_EMPTY_ARRAY_POSITION]: WELL_KNOWN_EMPTY_ARRAY +- }; +- defaultTemplate = templateFactory(DEFAULT_TEMPLATE)(); +- +- // Used for tests and debugging purposes, and to be able to analyze large apps +- // This is why it's enabled even in production +- helperDefinitionCount = 0; +- modifierDefinitionCount = 0; +- componentDefinitionCount = 0; +- helperDefinitionCache = new WeakMap(); +- modifierDefinitionCache = new WeakMap(); +- componentDefinitionCache = new WeakMap(); +- helper(definitionState, +- // TODO: Add a way to expose resolved name for debugging +- _resolvedName = null, isOptional) { +- let handle = this.helperDefinitionCache.get(definitionState); +- if (handle === undefined) { +- let managerOrHelper = getInternalHelperManager(definitionState, isOptional); +- if (managerOrHelper === null) { +- this.helperDefinitionCache.set(definitionState, null); +- return null; +- } +- debugAssert(managerOrHelper, 'BUG: expected manager or helper'); +- let helper = typeof managerOrHelper === 'function' ? managerOrHelper : managerOrHelper.getHelper(definitionState); +- handle = this.value(helper); +- this.helperDefinitionCache.set(definitionState, handle); +- this.helperDefinitionCount++; +- } +- return handle; ++ function isOptionSelected(tagName, attribute) { ++ return tagName === 'OPTION' && attribute === 'selected'; ++ } ++ function isUserInputValue(tagName, attribute) { ++ return (tagName === 'INPUT' || tagName === 'TEXTAREA') && attribute === 'value'; ++ } ++ function normalizeValue(value) { ++ if (value === false || value === undefined || value === null || typeof value.toString === 'undefined') { ++ return null; + } +- modifier(definitionState, resolvedName = null, isOptional) { +- let handle = this.modifierDefinitionCache.get(definitionState); +- if (handle === undefined) { +- let manager = getInternalModifierManager(definitionState, isOptional); +- if (manager === null) { +- this.modifierDefinitionCache.set(definitionState, null); +- return null; +- } +- let definition = { +- resolvedName, +- manager, +- state: definitionState +- }; +- handle = this.value(definition); +- this.modifierDefinitionCache.set(definitionState, handle); +- this.modifierDefinitionCount++; +- } +- return handle; ++ if (value === true) { ++ return ''; + } +- component(definitionState, owner, isOptional) { +- let definition = this.componentDefinitionCache.get(definitionState); +- if (definition === undefined) { +- let manager = getInternalComponentManager(definitionState, isOptional); +- if (manager === null) { +- this.componentDefinitionCache.set(definitionState, null); +- return null; +- } +- debugAssert(manager, 'BUG: expected manager'); +- let capabilities = capabilityFlagsFrom(manager.getCapabilities(definitionState)); +- let templateFactory = getComponentTemplate(definitionState); +- let compilable = null; +- let template; +- if (!managerHasCapability(manager, capabilities, InternalComponentCapabilities.dynamicLayout)) { +- template = templateFactory?.(owner) ?? this.defaultTemplate; +- } else { +- template = templateFactory?.(owner); +- } +- if (template !== undefined) { +- template = unwrapTemplate(template); +- compilable = managerHasCapability(manager, capabilities, InternalComponentCapabilities.wrapped) ? template.asWrappedLayout() : template.asLayout(); +- } +- definition = { +- resolvedName: null, +- handle: -1, +- // replaced momentarily +- manager, +- capabilities, +- state: definitionState, +- compilable +- }; +- definition.handle = this.value(definition); +- this.componentDefinitionCache.set(definitionState, definition); +- this.componentDefinitionCount++; +- } +- return definition; ++ // onclick function etc in SSR ++ if (typeof value === 'function') { ++ return null; + } +- resolvedComponent(resolvedDefinition, resolvedName) { +- let definition = this.componentDefinitionCache.get(resolvedDefinition); +- if (definition === undefined) { +- let { +- manager, +- state, +- template +- } = resolvedDefinition; +- let capabilities = capabilityFlagsFrom(manager.getCapabilities(resolvedDefinition)); +- let compilable = null; +- if (!managerHasCapability(manager, capabilities, InternalComponentCapabilities.dynamicLayout)) { +- template = template ?? this.defaultTemplate; +- } +- if (template !== null) { +- template = unwrapTemplate(template); +- compilable = managerHasCapability(manager, capabilities, InternalComponentCapabilities.wrapped) ? template.asWrappedLayout() : template.asLayout(); +- } +- definition = { +- resolvedName, +- handle: -1, +- // replaced momentarily +- manager, +- capabilities, +- state, +- compilable +- }; +- definition.handle = this.value(definition); +- this.componentDefinitionCache.set(resolvedDefinition, definition); +- this.componentDefinitionCount++; ++ return String(value); ++ } ++ let DebugStyleAttributeManager; ++ { ++ DebugStyleAttributeManager = class extends SimpleDynamicAttribute { ++ set(dom, value, env) { ++ warnIfStyleNotTrusted(value); ++ super.set(dom, value, env); + } +- return expect(definition, 'BUG: resolved component definitions cannot be null'); +- } +- getValue(index) { +- debugAssert(index >= 0, `cannot get value for handle: ${index}`); +- return this.values[index]; +- } +- getArray(index) { +- let reifiedArrs = this.reifiedArrs; +- let reified = reifiedArrs[index]; +- if (reified === undefined) { +- let names = this.getValue(index); +- reified = new Array(names.length); +- for (const [i, name] of enumerate(names)) { +- reified[i] = this.getValue(name); +- } +- reifiedArrs[index] = reified; ++ update(value, env) { ++ warnIfStyleNotTrusted(value); ++ super.update(value, env); + } +- return reified; ++ }; ++ } ++ class First { ++ constructor(node) { ++ this.node = node; ++ } ++ firstNode() { ++ return this.node; + } + } +- class RuntimeOpImpl { +- offset = 0; +- constructor(heap) { +- this.heap = heap; ++ class Last { ++ constructor(node) { ++ this.node = node; + } +- get size() { +- let rawType = this.heap.getbyaddr(this.offset); +- return ((rawType & OPERAND_LEN_MASK) >> ARG_SHIFT) + 1; ++ lastNode() { ++ return this.node; + } +- get isMachine() { +- let rawType = this.heap.getbyaddr(this.offset); +- return rawType & MACHINE_MASK ? 1 : 0; ++ } ++ const CURSOR_STACK = Symbol('CURSOR_STACK'); ++ class NewElementBuilder { ++ dom; ++ updateOperations; ++ constructing = null; ++ operations = null; ++ env; ++ [CURSOR_STACK] = new StackImpl(); ++ modifierStack = new StackImpl(); ++ blockStack = new StackImpl(); ++ static forInitialRender(env, cursor) { ++ return new this(env, cursor.element, cursor.nextSibling).initialize(); + } +- get type() { +- return this.heap.getbyaddr(this.offset) & TYPE_MASK; ++ static resume(env, block) { ++ let parentNode = block.parentElement(); ++ let nextSibling = block.reset(env); ++ let stack = new this(env, parentNode, nextSibling).initialize(); ++ stack.pushLiveBlock(block); ++ return stack; + } +- get op1() { +- return this.heap.getbyaddr(this.offset + 1); ++ constructor(env, parentNode, nextSibling) { ++ this.pushElement(parentNode, nextSibling); ++ this.env = env; ++ this.dom = env.getAppendOperations(); ++ this.updateOperations = env.getDOM(); + } +- get op2() { +- return this.heap.getbyaddr(this.offset + 2); ++ initialize() { ++ this.pushSimpleBlock(); ++ return this; + } +- get op3() { +- return this.heap.getbyaddr(this.offset + 3); ++ debugBlocks() { ++ return this.blockStack.toArray(); + } +- } +- var TableSlotState = /*#__PURE__*/function (TableSlotState) { +- TableSlotState[TableSlotState["Allocated"] = 0] = "Allocated"; +- TableSlotState[TableSlotState["Freed"] = 1] = "Freed"; +- TableSlotState[TableSlotState["Purged"] = 2] = "Purged"; +- TableSlotState[TableSlotState["Pointer"] = 3] = "Pointer"; +- return TableSlotState; +- }(TableSlotState || {}); +- const PAGE_SIZE = 0x100000; +- class RuntimeHeapImpl { +- heap; +- table; +- constructor(serializedHeap) { +- let { +- buffer, +- table +- } = serializedHeap; +- this.heap = new Int32Array(buffer); +- this.table = table; ++ get element() { ++ return this[CURSOR_STACK].current.element; + } +- +- // It is illegal to close over this address, as compaction +- // may move it. However, it is legal to use this address +- // multiple times between compactions. +- getaddr(handle) { +- return unwrap$1(this.table[handle]); ++ get nextSibling() { ++ return this[CURSOR_STACK].current.nextSibling; + } +- getbyaddr(address) { +- return expect(this.heap[address], 'Access memory out of bounds of the heap'); ++ get hasBlocks() { ++ return this.blockStack.size > 0; + } +- sizeof(handle) { +- return sizeof(this.table); ++ block() { ++ return expect(this.blockStack.current, 'Expected a current live block'); + } +- } +- function hydrateHeap(serializedHeap) { +- return new RuntimeHeapImpl(serializedHeap); +- } +- +- /** +- * The Heap is responsible for dynamically allocating +- * memory in which we read/write the VM's instructions +- * from/to. When we malloc we pass out a VMHandle, which +- * is used as an indirect way of accessing the memory during +- * execution of the VM. Internally we track the different +- * regions of the memory in an int array known as the table. +- * +- * The table 32-bit aligned and has the following layout: +- * +- * | ... | hp (u32) | info (u32) | size (u32) | +- * | ... | Handle | Scope Size | State | Size | +- * | ... | 32bits | 30bits | 2bits | 32bit | +- * +- * With this information we effectively have the ability to +- * control when we want to free memory. That being said you +- * can not free during execution as raw address are only +- * valid during the execution. This means you cannot close +- * over them as you will have a bad memory access exception. +- */ +- class HeapImpl { +- offset = 0; +- heap; +- handleTable; +- handleState; +- handle = 0; +- constructor() { +- this.heap = new Int32Array(PAGE_SIZE); +- this.handleTable = []; +- this.handleState = []; ++ popElement() { ++ this[CURSOR_STACK].pop(); ++ expect(this[CURSOR_STACK].current, "can't pop past the last element"); + } +- pushRaw(value) { +- this.sizeCheck(); +- this.heap[this.offset++] = value; ++ pushSimpleBlock() { ++ return this.pushLiveBlock(new SimpleLiveBlock(this.element)); + } +- pushOp(item) { +- this.pushRaw(item); ++ pushUpdatableBlock() { ++ return this.pushLiveBlock(new UpdatableBlockImpl(this.element)); + } +- pushMachine(item) { +- this.pushRaw(item | MACHINE_MASK); ++ pushBlockList(list) { ++ return this.pushLiveBlock(new LiveBlockList(this.element, list)); + } +- sizeCheck() { +- let { +- heap +- } = this; +- if (this.offset === this.heap.length) { +- let newHeap = new Int32Array(heap.length + PAGE_SIZE); +- newHeap.set(heap, 0); +- this.heap = newHeap; ++ pushLiveBlock(block, isRemote = false) { ++ let current = this.blockStack.current; ++ if (current !== null) { ++ if (!isRemote) { ++ current.didAppendBounds(block); ++ } + } ++ this.__openBlock(); ++ this.blockStack.push(block); ++ return block; + } +- getbyaddr(address) { +- return unwrap$1(this.heap[address]); ++ popBlock() { ++ this.block().finalize(this); ++ this.__closeBlock(); ++ return expect(this.blockStack.pop(), 'Expected popBlock to return a block'); + } +- setbyaddr(address, value) { +- this.heap[address] = value; ++ __openBlock() {} ++ __closeBlock() {} ++ ++ // todo return seems unused ++ openElement(tag) { ++ let element = this.__openElement(tag); ++ this.constructing = element; ++ return element; + } +- malloc() { +- // push offset, info, size +- this.handleTable.push(this.offset); +- return this.handleTable.length - 1; ++ __openElement(tag) { ++ return this.dom.createElement(tag, this.element); + } +- finishMalloc(handle) {} +- size() { +- return this.offset; ++ flushElement(modifiers) { ++ let parent = this.element; ++ let element = expect(this.constructing, `flushElement should only be called when constructing an element`); ++ this.__flushElement(parent, element); ++ this.constructing = null; ++ this.operations = null; ++ this.pushModifiers(modifiers); ++ this.pushElement(element, null); ++ this.didOpenElement(element); + } +- +- // It is illegal to close over this address, as compaction +- // may move it. However, it is legal to use this address +- // multiple times between compactions. +- getaddr(handle) { +- return unwrap$1(this.handleTable[handle]); ++ __flushElement(parent, constructing) { ++ this.dom.insertBefore(parent, constructing, this.nextSibling); + } +- sizeof(handle) { +- return sizeof(this.handleTable); ++ closeElement() { ++ this.willCloseElement(); ++ this.popElement(); ++ return this.popModifiers(); + } +- free(handle) { +- this.handleState[handle] = TableSlotState.Freed; ++ pushRemoteElement(element, guid, insertBefore) { ++ return this.__pushRemoteElement(element, guid, insertBefore); + } +- +- /** +- * The heap uses the [Mark-Compact Algorithm](https://en.wikipedia.org/wiki/Mark-compact_algorithm) to shift +- * reachable memory to the bottom of the heap and freeable +- * memory to the top of the heap. When we have shifted all +- * the reachable memory to the top of the heap, we move the +- * offset to the next free position. +- */ +- compact() { +- let compactedSize = 0; +- let { +- handleTable, +- handleState, +- heap +- } = this; +- for (let i = 0; i < length; i++) { +- let offset = unwrap$1(handleTable[i]); +- let size = unwrap$1(handleTable[i + 1]) - unwrap$1(offset); +- let state = handleState[i]; +- if (state === TableSlotState.Purged) { +- continue; +- } else if (state === TableSlotState.Freed) { +- // transition to "already freed" aka "purged" +- // a good improvement would be to reuse +- // these slots +- handleState[i] = TableSlotState.Purged; +- compactedSize += size; +- } else if (state === TableSlotState.Allocated) { +- for (let j = offset; j <= i + size; j++) { +- heap[j - compactedSize] = unwrap$1(heap[j]); +- } +- handleTable[i] = offset - compactedSize; +- } else if (state === TableSlotState.Pointer) { +- handleTable[i] = offset - compactedSize; ++ __pushRemoteElement(element, _guid, insertBefore) { ++ this.pushElement(element, insertBefore); ++ if (insertBefore === undefined) { ++ while (element.lastChild) { ++ element.removeChild(element.lastChild); + } + } +- this.offset = this.offset - compactedSize; +- } +- capture(offset = this.offset) { +- // Only called in eager mode +- let buffer = slice$2(this.heap, 0, offset).buffer; +- return { +- handle: this.handle, +- table: this.handleTable, +- buffer: buffer +- }; ++ let block = new RemoteLiveBlock(element); ++ return this.pushLiveBlock(block, true); + } +- } +- class RuntimeProgramImpl { +- _opcode; +- constructor(constants, heap) { +- this.constants = constants; +- this.heap = heap; +- this._opcode = new RuntimeOpImpl(this.heap); ++ popRemoteElement() { ++ const block = this.popBlock(); ++ debugAssert(block instanceof RemoteLiveBlock, '[BUG] expecting a RemoteLiveBlock'); ++ this.popElement(); ++ return block; + } +- opcode(offset) { +- this._opcode.offset = offset; +- return this._opcode; ++ pushElement(element, nextSibling = null) { ++ this[CURSOR_STACK].push(new CursorImpl(element, nextSibling)); + } +- } +- function slice$2(arr, start, end) { +- if (arr.slice !== undefined) { +- return arr.slice(start, end); ++ pushModifiers(modifiers) { ++ this.modifierStack.push(modifiers); + } +- let ret = new Int32Array(end); +- for (; start < end; start++) { +- ret[start] = unwrap$1(arr[start]); ++ popModifiers() { ++ return this.modifierStack.pop(); + } +- return ret; +- } +- function sizeof(table, handle) { +- { +- return -1; ++ didAppendBounds(bounds) { ++ this.block().didAppendBounds(bounds); ++ return bounds; + } +- } +- function artifacts() { +- return { +- constants: new ConstantsImpl(), +- heap: new HeapImpl() +- }; +- } +- +- const glimmerProgram = /*#__PURE__*/Object.defineProperty({ +- __proto__: null, +- CompileTimeConstantImpl, +- ConstantsImpl, +- HeapImpl, +- RuntimeConstantsImpl, +- RuntimeHeapImpl, +- RuntimeOpImpl, +- RuntimeProgramImpl, +- artifacts, +- hydrateHeap +- }, Symbol.toStringTag, { value: 'Module' }); +- +- class DynamicScopeImpl { +- bucket; +- constructor(bucket) { +- if (bucket) { +- this.bucket = assign({}, bucket); +- } else { +- this.bucket = {}; +- } ++ didAppendNode(node) { ++ this.block().didAppendNode(node); ++ return node; + } +- get(key) { +- return unwrap$1(this.bucket[key]); ++ didOpenElement(element) { ++ this.block().openElement(element); ++ return element; + } +- set(key, reference) { +- return this.bucket[key] = reference; ++ willCloseElement() { ++ this.block().closeElement(); + } +- child() { +- return new DynamicScopeImpl(this.bucket); ++ appendText(string) { ++ return this.didAppendNode(this.__appendText(string)); + } +- } +- class PartialScopeImpl { +- static root(self, size = 0, owner) { +- let refs = new Array(size + 1).fill(UNDEFINED_REFERENCE); +- return new PartialScopeImpl(refs, owner, null, null, null).init({ +- self +- }); ++ __appendText(text) { ++ let { ++ dom, ++ element, ++ nextSibling ++ } = this; ++ let node = dom.createTextNode(text); ++ dom.insertBefore(element, node, nextSibling); ++ return node; + } +- static sized(size = 0, owner) { +- let refs = new Array(size + 1).fill(UNDEFINED_REFERENCE); +- return new PartialScopeImpl(refs, owner, null, null, null); ++ __appendNode(node) { ++ this.dom.insertBefore(this.element, node, this.nextSibling); ++ return node; + } +- constructor( +- // the 0th slot is `self` +- slots, owner, callerScope, +- // named arguments and blocks passed to a layout that uses eval +- evalScope, +- // locals in scope when the partial was invoked +- partialMap) { +- this.slots = slots; +- this.owner = owner; +- this.callerScope = callerScope; +- this.evalScope = evalScope; +- this.partialMap = partialMap; ++ __appendFragment(fragment) { ++ let first = fragment.firstChild; ++ if (first) { ++ let ret = new ConcreteBounds(this.element, first, fragment.lastChild); ++ this.dom.insertBefore(this.element, fragment, this.nextSibling); ++ return ret; ++ } else { ++ const comment = this.__appendComment(''); ++ return new ConcreteBounds(this.element, comment, comment); ++ } + } +- init({ +- self +- }) { +- this.slots[0] = self; +- return this; ++ __appendHTML(html) { ++ return this.dom.insertHTMLBefore(this.element, this.nextSibling, html); + } +- getSelf() { +- return this.get(0); ++ appendDynamicHTML(value) { ++ let bounds = this.trustedContent(value); ++ this.didAppendBounds(bounds); + } +- getSymbol(symbol) { +- return this.get(symbol); ++ appendDynamicText(value) { ++ let node = this.untrustedContent(value); ++ this.didAppendNode(node); ++ return node; + } +- getBlock(symbol) { +- let block = this.get(symbol); +- return block === UNDEFINED_REFERENCE ? null : block; ++ appendDynamicFragment(value) { ++ let bounds = this.__appendFragment(value); ++ this.didAppendBounds(bounds); + } +- getEvalScope() { +- return this.evalScope; ++ appendDynamicNode(value) { ++ let node = this.__appendNode(value); ++ let bounds = new ConcreteBounds(this.element, node, node); ++ this.didAppendBounds(bounds); + } +- getPartialMap() { +- return this.partialMap; ++ trustedContent(value) { ++ return this.__appendHTML(value); + } +- bind(symbol, value) { +- this.set(symbol, value); ++ untrustedContent(value) { ++ return this.__appendText(value); + } +- bindSelf(self) { +- this.set(0, self); ++ appendComment(string) { ++ return this.didAppendNode(this.__appendComment(string)); + } +- bindSymbol(symbol, value) { +- this.set(symbol, value); ++ __appendComment(string) { ++ let { ++ dom, ++ element, ++ nextSibling ++ } = this; ++ let node = dom.createComment(string); ++ dom.insertBefore(element, node, nextSibling); ++ return node; + } +- bindBlock(symbol, value) { +- this.set(symbol, value); ++ __setAttribute(name, value, namespace) { ++ this.dom.setAttribute(this.constructing, name, value, namespace); + } +- bindEvalScope(map) { +- this.evalScope = map; ++ __setProperty(name, value) { ++ this.constructing[name] = value; + } +- bindPartialMap(map) { +- this.partialMap = map; ++ setStaticAttribute(name, value, namespace) { ++ this.__setAttribute(name, value, namespace); + } +- bindCallerScope(scope) { +- this.callerScope = scope; ++ setDynamicAttribute(name, value, trusting, namespace) { ++ let element = this.constructing; ++ let attribute = dynamicAttribute(element, name, namespace, trusting); ++ attribute.set(this, value, this.env); ++ return attribute; + } +- getCallerScope() { +- return this.callerScope; ++ } ++ class SimpleLiveBlock { ++ first = null; ++ last = null; ++ nesting = 0; ++ constructor(parent) { ++ this.parent = parent; + } +- child() { +- return new PartialScopeImpl(this.slots.slice(), this.owner, this.callerScope, this.evalScope, this.partialMap); ++ parentElement() { ++ return this.parent; + } +- get(index) { +- if (index >= this.slots.length) { +- throw new RangeError(`BUG: cannot get $${index} from scope; length=${this.slots.length}`); ++ firstNode() { ++ let first = expect(this.first, 'cannot call `firstNode()` while `SimpleLiveBlock` is still initializing'); ++ return first.firstNode(); ++ } ++ lastNode() { ++ let last = expect(this.last, 'cannot call `lastNode()` while `SimpleLiveBlock` is still initializing'); ++ return last.lastNode(); ++ } ++ openElement(element) { ++ this.didAppendNode(element); ++ this.nesting++; ++ } ++ closeElement() { ++ this.nesting--; ++ } ++ didAppendNode(node) { ++ if (this.nesting !== 0) return; ++ if (!this.first) { ++ this.first = new First(node); + } +- return this.slots[index]; ++ this.last = new Last(node); + } +- set(index, value) { +- if (index >= this.slots.length) { +- throw new RangeError(`BUG: cannot get $${index} from scope; length=${this.slots.length}`); ++ didAppendBounds(bounds) { ++ if (this.nesting !== 0) return; ++ if (!this.first) { ++ this.first = bounds; ++ } ++ this.last = bounds; ++ } ++ finalize(stack) { ++ if (this.first === null) { ++ stack.appendComment(''); + } +- this.slots[index] = value; + } + } +- +- // These symbols represent "friend" properties that are used inside of +- // the VM in other classes, but are not intended to be a part of +- // Glimmer's API. +- +- const INNER_VM = Symbol('INNER_VM'); +- const DESTROYABLE_STACK = Symbol('DESTROYABLE_STACK'); +- const STACKS = Symbol('STACKS'); +- const REGISTERS = Symbol('REGISTERS'); +- const HEAP = Symbol('HEAP'); +- const CONSTANTS = Symbol('CONSTANTS'); +- const ARGS$1 = Symbol('ARGS'); +- class CursorImpl { +- constructor(element, nextSibling) { +- this.element = element; +- this.nextSibling = nextSibling; ++ class RemoteLiveBlock extends SimpleLiveBlock { ++ constructor(parent) { ++ super(parent); ++ registerDestructor$1(this, () => { ++ // In general, you only need to clear the root of a hierarchy, and should never ++ // need to clear any child nodes. This is an important constraint that gives us ++ // a strong guarantee that clearing a subtree is a single DOM operation. ++ // ++ // Because remote blocks are not normally physically nested inside of the tree ++ // that they are logically nested inside, we manually clear remote blocks when ++ // a logical parent is cleared. ++ // ++ // HOWEVER, it is currently possible for a remote block to be physically nested ++ // inside of the block it is logically contained inside of. This happens when ++ // the remote block is appended to the end of the application's entire element. ++ // ++ // The problem with that scenario is that Glimmer believes that it owns more of ++ // the DOM than it actually does. The code is attempting to write past the end ++ // of the Glimmer-managed root, but Glimmer isn't aware of that. ++ // ++ // The correct solution to that problem is for Glimmer to be aware of the end ++ // of the bounds that it owns, and once we make that change, this check could ++ // be removed. ++ // ++ // For now, a more targeted fix is to check whether the node was already removed ++ // and avoid clearing the node if it was. In most cases this shouldn't happen, ++ // so this might hide bugs where the code clears nested nodes unnecessarily, ++ // so we should eventually try to do the correct fix. ++ if (this.parentElement() === this.firstNode().parentNode) { ++ clear(this); ++ } ++ }); + } + } +- class ConcreteBounds { +- constructor(parentNode, first, last) { +- this.parentNode = parentNode; +- this.first = first; +- this.last = last; ++ class UpdatableBlockImpl extends SimpleLiveBlock { ++ reset() { ++ destroy(this); ++ let nextSibling = clear(this); ++ this.first = null; ++ this.last = null; ++ this.nesting = 0; ++ return nextSibling; ++ } ++ } ++ ++ // FIXME: All the noops in here indicate a modelling problem ++ class LiveBlockList { ++ constructor(parent, boundList) { ++ this.parent = parent; ++ this.boundList = boundList; ++ this.parent = parent; ++ this.boundList = boundList; + } + parentElement() { +- return this.parentNode; ++ return this.parent; + } + firstNode() { +- return this.first; ++ let head = expect(this.boundList[0], 'cannot call `firstNode()` while `LiveBlockList` is still initializing'); ++ return head.firstNode(); + } + lastNode() { +- return this.last; ++ let boundList = this.boundList; ++ let tail = expect(boundList[boundList.length - 1], 'cannot call `lastNode()` while `LiveBlockList` is still initializing'); ++ return tail.lastNode(); ++ } ++ openElement(_element) { ++ debugAssert(false, 'Cannot openElement directly inside a block list'); ++ } ++ closeElement() { ++ debugAssert(false, 'Cannot closeElement directly inside a block list'); ++ } ++ didAppendNode(_node) { ++ debugAssert(false, 'Cannot create a new node directly inside a block list'); ++ } ++ didAppendBounds(_bounds) {} ++ finalize(_stack) { ++ debugAssert(this.boundList.length > 0, 'boundsList cannot be empty'); + } + } +- function move(bounds, reference) { +- let parent = bounds.parentElement(); +- let first = bounds.firstNode(); +- let last = bounds.lastNode(); +- let current = first; +- +- // eslint-disable-next-line no-constant-condition +- while (true) { +- let next = current.nextSibling; +- parent.insertBefore(current, reference); +- if (current === last) { +- return next; ++ function clientBuilder(env, cursor) { ++ return NewElementBuilder.forInitialRender(env, cursor); ++ } ++ class AppendOpcodes { ++ evaluateOpcode = new Array(Op.Size).fill(null); ++ add(name, evaluate, kind = 'syscall') { ++ this.evaluateOpcode[name] = { ++ syscall: kind !== 'machine', ++ evaluate ++ }; ++ } ++ debugBefore(vm, opcode) { ++ let params = undefined; ++ let opName = undefined; ++ let sp; ++ recordStackSize(vm.fetchValue($sp)); ++ return { ++ sp: sp, ++ pc: vm.fetchValue($pc), ++ name: opName, ++ params, ++ type: opcode.type, ++ isMachine: opcode.isMachine, ++ size: opcode.size, ++ state: undefined ++ }; ++ } ++ debugAfter(vm, pre) {} ++ evaluate(vm, opcode, type) { ++ let operation = unwrap$1(this.evaluateOpcode[type]); ++ if (operation.syscall) { ++ debugAssert(!opcode.isMachine, `BUG: Mismatch between operation.syscall (${operation.syscall}) and opcode.isMachine (${opcode.isMachine}) for ${opcode.type}`); ++ operation.evaluate(vm, opcode); ++ } else { ++ debugAssert(opcode.isMachine, `BUG: Mismatch between operation.syscall (${operation.syscall}) and opcode.isMachine (${opcode.isMachine}) for ${opcode.type}`); ++ operation.evaluate(vm[INNER_VM], opcode); + } +- current = expect(next, 'invalid bounds'); + } + } +- function clear(bounds) { +- let parent = bounds.parentElement(); +- let first = bounds.firstNode(); +- let last = bounds.lastNode(); +- let current = first; ++ const APPEND_OPCODES = new AppendOpcodes(); ++ const TYPE = Symbol('TYPE'); ++ const INNER = Symbol('INNER'); ++ const OWNER = Symbol('OWNER'); ++ const ARGS$2 = Symbol('ARGS'); ++ const RESOLVED = Symbol('RESOLVED'); ++ const CURRIED_VALUES = new WeakSet(); ++ function isCurriedValue(value) { ++ return CURRIED_VALUES.has(value); ++ } ++ function isCurriedType(value, type) { ++ return isCurriedValue(value) && value[TYPE] === type; ++ } ++ class CurriedValue { ++ [TYPE]; ++ [INNER]; ++ [OWNER]; ++ [ARGS$2]; ++ [RESOLVED]; ++ ++ /** @internal */ ++ constructor(type, inner, owner, args, resolved = false) { ++ CURRIED_VALUES.add(this); ++ this[TYPE] = type; ++ this[INNER] = inner; ++ this[OWNER] = owner; ++ this[ARGS$2] = args; ++ this[RESOLVED] = resolved; ++ } ++ } ++ function resolveCurriedValue(curriedValue) { ++ let currentWrapper = curriedValue; ++ let positional; ++ let named; ++ let definition, owner, resolved; + + // eslint-disable-next-line no-constant-condition + while (true) { +- let next = current.nextSibling; +- parent.removeChild(current); +- if (current === last) { +- return next; ++ let { ++ [ARGS$2]: curriedArgs, ++ [INNER]: inner ++ } = currentWrapper; ++ if (curriedArgs !== null) { ++ let { ++ named: curriedNamed, ++ positional: curriedPositional ++ } = curriedArgs; ++ if (curriedPositional.length > 0) { ++ positional = positional === undefined ? curriedPositional : curriedPositional.concat(positional); ++ } ++ if (named === undefined) { ++ named = []; ++ } ++ named.unshift(curriedNamed); + } +- current = expect(next, 'invalid bounds'); +- } +- } +- function normalizeStringValue(value) { +- if (isEmpty$2(value)) { +- return ''; ++ if (!isCurriedValue(inner)) { ++ // Save off the owner that this helper was curried with. Later on, ++ // we'll fetch the value of this register and set it as the owner on the ++ // new root scope. ++ definition = inner; ++ owner = currentWrapper[OWNER]; ++ resolved = currentWrapper[RESOLVED]; ++ break; ++ } ++ currentWrapper = inner; + } +- return String(value); ++ return { ++ definition, ++ owner, ++ resolved, ++ positional, ++ named ++ }; + } +- function shouldCoerce(value) { +- return isString(value) || isEmpty$2(value) || typeof value === 'boolean' || typeof value === 'number'; ++ function curry(type, spec, owner, args, resolved = false) { ++ return new CurriedValue(type, spec, owner, args, resolved); + } +- function isEmpty$2(value) { +- return value === null || value === undefined || typeof value.toString !== 'function'; ++ function createCurryRef(type, inner, owner, args, resolver, isStrict) { ++ let lastValue, curriedDefinition; ++ return createComputeRef(() => { ++ let value = valueForRef(inner); ++ if (value === lastValue) { ++ return curriedDefinition; ++ } ++ if (isCurriedType(value, type)) { ++ curriedDefinition = args ? curry(type, value, owner, args) : args; ++ } else if (type === CurriedTypes.Component && typeof value === 'string' && value) { ++ // Only components should enter this path, as helpers and modifiers do not ++ // support string based resolution ++ ++ { ++ if (isStrict) { ++ throw new Error(`Attempted to resolve a dynamic component with a string definition, \`${value}\` in a strict mode template. In strict mode, using strings to resolve component definitions is prohibited. You can instead import the component definition and use it directly.`); ++ } ++ let resolvedDefinition = expect(resolver, 'BUG: expected resolver for curried component definitions').lookupComponent(value, owner); ++ if (!resolvedDefinition) { ++ throw new Error(`Attempted to resolve \`${value}\`, which was expected to be a component, but nothing was found.`); ++ } ++ } ++ curriedDefinition = curry(type, value, owner, args); ++ } else if (isObject(value)) { ++ curriedDefinition = curry(type, value, owner, args); ++ } else { ++ curriedDefinition = null; ++ } ++ lastValue = value; ++ return curriedDefinition; ++ }); + } +- function isSafeString(value) { +- return typeof value === 'object' && value !== null && typeof value.toHTML === 'function'; ++ ++ /** @internal */ ++ function hasCustomDebugRenderTreeLifecycle(manager) { ++ return 'getDebugCustomRenderTree' in manager; + } +- function isNode(value) { +- return typeof value === 'object' && value !== null && typeof value.nodeType === 'number'; ++ function resolveComponent(resolver, constants, name, owner) { ++ let definition = resolver.lookupComponent(name, expect(owner, 'BUG: expected owner when looking up component')); ++ if (!definition) { ++ throw new Error(`Attempted to resolve \`${name}\`, which was expected to be a component, but nothing was found.`); ++ } ++ return constants.resolvedComponent(definition, name); + } +- function isFragment(value) { +- return isNode(value) && value.nodeType === 11; ++ function createClassListRef(list) { ++ return createComputeRef(() => { ++ let ret = []; ++ for (const ref of list) { ++ let value = normalizeStringValue(typeof ref === 'string' ? ref : valueForRef(ref)); ++ if (value) ret.push(value); ++ } ++ return ret.length === 0 ? null : ret.join(' '); ++ }); + } +- function isString(value) { +- return typeof value === 'string'; ++ function stackAssert(name, top) { ++ return `Expected top of stack to be ${name}, was ${String(top)}`; + } +- +- /* +- * @method normalizeProperty +- * @param element {HTMLElement} +- * @param slotName {String} +- * @returns {Object} { name, type } +- */ +- function normalizeProperty(element, slotName) { +- let type, normalized; +- if (slotName in element) { +- normalized = slotName; +- type = 'prop'; ++ APPEND_OPCODES.add(Op.ChildScope, vm => vm.pushChildScope()); ++ APPEND_OPCODES.add(Op.PopScope, vm => vm.popScope()); ++ APPEND_OPCODES.add(Op.PushDynamicScope, vm => vm.pushDynamicScope()); ++ APPEND_OPCODES.add(Op.PopDynamicScope, vm => vm.popDynamicScope()); ++ APPEND_OPCODES.add(Op.Constant, (vm, { ++ op1: other ++ }) => { ++ vm.stack.push(vm[CONSTANTS].getValue(decodeHandle(other))); ++ }); ++ APPEND_OPCODES.add(Op.ConstantReference, (vm, { ++ op1: other ++ }) => { ++ vm.stack.push(createConstRef(vm[CONSTANTS].getValue(decodeHandle(other)), false)); ++ }); ++ APPEND_OPCODES.add(Op.Primitive, (vm, { ++ op1: primitive ++ }) => { ++ let stack = vm.stack; ++ if (isHandle(primitive)) { ++ // it is a handle which does not already exist on the stack ++ let value = vm[CONSTANTS].getValue(decodeHandle(primitive)); ++ stack.push(value); + } else { +- let lower = slotName.toLowerCase(); +- if (lower in element) { +- type = 'prop'; +- normalized = lower; +- } else { +- type = 'attr'; +- normalized = slotName; +- } ++ // is already an encoded immediate or primitive handle ++ stack.push(decodeImmediate(primitive)); + } +- if (type === 'prop' && (normalized.toLowerCase() === 'style' || preferAttr(element.tagName, normalized))) { +- type = 'attr'; ++ }); ++ APPEND_OPCODES.add(Op.PrimitiveReference, vm => { ++ let stack = vm.stack; ++ let value = check(stack.pop(), CheckPrimitive); ++ let ref; ++ if (value === undefined) { ++ ref = UNDEFINED_REFERENCE; ++ } else if (value === null) { ++ ref = NULL_REFERENCE; ++ } else if (value === true) { ++ ref = TRUE_REFERENCE; ++ } else if (value === false) { ++ ref = FALSE_REFERENCE; ++ } else { ++ ref = createPrimitiveRef(value); + } +- return { +- normalized, +- type +- }; +- } +- +- // properties that MUST be set as attributes, due to: +- // * browser bug +- // * strange spec outlier +- const ATTR_OVERRIDES = { +- INPUT: { +- form: true, +- // Chrome 46.0.2464.0: 'autocorrect' in document.createElement('input') === false +- // Safari 8.0.7: 'autocorrect' in document.createElement('input') === false +- // Mobile Safari (iOS 8.4 simulator): 'autocorrect' in document.createElement('input') === true +- autocorrect: true, +- // Chrome 54.0.2840.98: 'list' in document.createElement('input') === true +- // Safari 9.1.3: 'list' in document.createElement('input') === false +- list: true +- }, +- // element.form is actually a legitimate readOnly property, that is to be +- // mutated, but must be mutated by setAttribute... +- SELECT: { +- form: true +- }, +- OPTION: { +- form: true +- }, +- TEXTAREA: { +- form: true +- }, +- LABEL: { +- form: true +- }, +- FIELDSET: { +- form: true +- }, +- LEGEND: { +- form: true +- }, +- OBJECT: { +- form: true +- }, +- OUTPUT: { +- form: true +- }, +- BUTTON: { +- form: true +- } +- }; +- function preferAttr(tagName, propName) { +- let tag = ATTR_OVERRIDES[tagName.toUpperCase()]; +- return tag && tag[propName.toLowerCase()] || false; +- } +- const badProtocols = ['javascript:', 'vbscript:']; +- const badTags = ['A', 'BODY', 'LINK', 'IMG', 'IFRAME', 'BASE', 'FORM']; +- const badTagsForDataURI = ['EMBED']; +- const badAttributes = ['href', 'src', 'background', 'action']; +- const badAttributesForDataURI = ['src']; +- function has(array, item) { +- return array.indexOf(item) !== -1; +- } +- function checkURI(tagName, attribute) { +- return (tagName === null || has(badTags, tagName)) && has(badAttributes, attribute); +- } +- function checkDataURI(tagName, attribute) { +- if (tagName === null) return false; +- return has(badTagsForDataURI, tagName) && has(badAttributesForDataURI, attribute); +- } +- function requiresSanitization(tagName, attribute) { +- return checkURI(tagName, attribute) || checkDataURI(tagName, attribute); +- } +- function findProtocolForURL() { +- if (typeof URL === 'object' && URL !== null && +- // this is super annoying, TS thinks that URL **must** be a function so `URL.parse` check +- // thinks it is `never` without this `as unknown as any` +- typeof URL.parse === 'function') { +- // In Ember-land the `fastboot` package sets the `URL` global to `require('url')` +- // ultimately, this should be changed (so that we can either rely on the natural `URL` global +- // that exists) but for now we have to detect the specific `FastBoot` case first +- // +- // a future version of `fastboot` will detect if this legacy URL setup is required (by +- // inspecting Ember version) and if new enough, it will avoid shadowing the `URL` global +- // constructor with `require('url')`. +- let nodeURL = URL; +- return url => { +- let protocol = null; +- if (typeof url === 'string') { +- protocol = nodeURL.parse(url).protocol; +- } +- return protocol === null ? ':' : protocol; +- }; +- } else if (typeof URL === 'function') { +- return _url => { +- try { +- let url = new URL(_url); +- return url.protocol; +- } catch (error) { +- // any non-fully qualified url string will trigger an error (because there is no +- // baseURI that we can provide; in that case we **know** that the protocol is +- // "safe" because it isn't specifically one of the `badProtocols` listed above +- // (and those protocols can never be the default baseURI) +- return ':'; +- } +- }; ++ stack.push(ref); ++ }); ++ APPEND_OPCODES.add(Op.Dup, (vm, { ++ op1: register, ++ op2: offset ++ }) => { ++ let position = check(vm.fetchValue(register), CheckNumber) - offset; ++ vm.stack.dup(position); ++ }); ++ APPEND_OPCODES.add(Op.Pop, (vm, { ++ op1: count ++ }) => { ++ vm.stack.pop(count); ++ }); ++ APPEND_OPCODES.add(Op.Load, (vm, { ++ op1: register ++ }) => { ++ vm.load(register); ++ }); ++ APPEND_OPCODES.add(Op.Fetch, (vm, { ++ op1: register ++ }) => { ++ vm.fetch(register); ++ }); ++ APPEND_OPCODES.add(Op.BindDynamicScope, (vm, { ++ op1: _names ++ }) => { ++ let names = vm[CONSTANTS].getArray(_names); ++ vm.bindDynamicScope(names); ++ }); ++ APPEND_OPCODES.add(Op.Enter, (vm, { ++ op1: args ++ }) => { ++ vm.enter(args); ++ }); ++ APPEND_OPCODES.add(Op.Exit, vm => { ++ vm.exit(); ++ }); ++ APPEND_OPCODES.add(Op.PushSymbolTable, (vm, { ++ op1: _table ++ }) => { ++ let stack = vm.stack; ++ stack.push(vm[CONSTANTS].getValue(_table)); ++ }); ++ APPEND_OPCODES.add(Op.PushBlockScope, vm => { ++ let stack = vm.stack; ++ stack.push(vm.scope()); ++ }); ++ APPEND_OPCODES.add(Op.CompileBlock, vm => { ++ let stack = vm.stack; ++ let block = stack.pop(); ++ if (block) { ++ stack.push(vm.compile(block)); + } else { +- throw new Error(`@glimmer/runtime needs a valid "globalThis.URL"`); +- } +- } +- let _protocolForUrlImplementation; +- function protocolForUrl(url) { +- if (!_protocolForUrlImplementation) { +- _protocolForUrlImplementation = findProtocolForURL(); +- } +- return _protocolForUrlImplementation(url); +- } +- function sanitizeAttributeValue(element, attribute, value) { +- let tagName = null; +- if (value === null || value === undefined) { +- return value; +- } +- if (isSafeString(value)) { +- return value.toHTML(); ++ stack.push(null); + } +- if (!element) { +- tagName = null; +- } else { +- tagName = element.tagName.toUpperCase(); ++ }); ++ APPEND_OPCODES.add(Op.InvokeYield, vm => { ++ let { ++ stack ++ } = vm; ++ let handle = check(stack.pop(), CheckOption(CheckHandle)); ++ let scope = check(stack.pop(), CheckOption(CheckScope)); ++ let table = check(stack.pop(), CheckOption(CheckBlockSymbolTable)); ++ debugAssert(table === null || table && typeof table === 'object' && Array.isArray(table.parameters), stackAssert('Option', table)); ++ let args = check(stack.pop(), CheckInstanceof(VMArgumentsImpl)); ++ if (table === null) { ++ // To balance the pop{Frame,Scope} ++ vm.pushFrame(); ++ vm.pushScope(scope ?? vm.scope()); ++ return; + } +- let str = normalizeStringValue(value); +- if (checkURI(tagName, attribute)) { +- let protocol = protocolForUrl(str); +- if (has(badProtocols, protocol)) { +- return `unsafe:${str}`; ++ let invokingScope = expect(scope, 'BUG: expected scope'); ++ ++ // If necessary, create a child scope ++ { ++ let locals = table.parameters; ++ let localsCount = locals.length; ++ if (localsCount > 0) { ++ invokingScope = invokingScope.child(); ++ for (let i = 0; i < localsCount; i++) { ++ invokingScope.bindSymbol(unwrap$1(locals[i]), args.at(i)); ++ } + } + } +- if (checkDataURI(tagName, attribute)) { +- return `unsafe:${str}`; +- } +- return str; +- } +- function dynamicAttribute(element, attr, namespace, isTrusting = false) { +- const { +- tagName, +- namespaceURI +- } = element; +- const attribute = { +- element, +- name: attr, +- namespace +- }; +- if (attr === 'style' && !isTrusting) { +- return new DebugStyleAttributeManager(attribute); +- } +- if (namespaceURI === NS_SVG) { +- return buildDynamicAttribute(tagName, attr, attribute); +- } +- const { +- type, +- normalized +- } = normalizeProperty(element, attr); +- if (type === 'attr') { +- return buildDynamicAttribute(tagName, normalized, attribute); ++ vm.pushFrame(); ++ vm.pushScope(invokingScope); ++ vm.call(handle); ++ }); ++ APPEND_OPCODES.add(Op.JumpIf, (vm, { ++ op1: target ++ }) => { ++ let reference = check(vm.stack.pop(), CheckReference); ++ let value = Boolean(valueForRef(reference)); ++ if (isConstRef(reference)) { ++ if (value === true) { ++ vm.goto(target); ++ } + } else { +- return buildDynamicProperty(tagName, normalized, attribute); ++ if (value === true) { ++ vm.goto(target); ++ } ++ vm.updateWith(new Assert(reference)); + } +- } +- function buildDynamicAttribute(tagName, name, attribute) { +- if (requiresSanitization(tagName, name)) { +- return new SafeDynamicAttribute(attribute); ++ }); ++ APPEND_OPCODES.add(Op.JumpUnless, (vm, { ++ op1: target ++ }) => { ++ let reference = check(vm.stack.pop(), CheckReference); ++ let value = Boolean(valueForRef(reference)); ++ if (isConstRef(reference)) { ++ if (value === false) { ++ vm.goto(target); ++ } + } else { +- return new SimpleDynamicAttribute(attribute); ++ if (value === false) { ++ vm.goto(target); ++ } ++ vm.updateWith(new Assert(reference)); + } +- } +- function buildDynamicProperty(tagName, name, attribute) { +- if (requiresSanitization(tagName, name)) { +- return new SafeDynamicProperty(name, attribute); ++ }); ++ APPEND_OPCODES.add(Op.JumpEq, (vm, { ++ op1: target, ++ op2: comparison ++ }) => { ++ let other = check(vm.stack.peek(), CheckNumber); ++ if (other === comparison) { ++ vm.goto(target); + } +- if (isUserInputValue(tagName, name)) { +- return new InputValueDynamicAttribute(name, attribute); ++ }); ++ APPEND_OPCODES.add(Op.AssertSame, vm => { ++ let reference = check(vm.stack.peek(), CheckReference); ++ if (isConstRef(reference) === false) { ++ vm.updateWith(new Assert(reference)); + } +- if (isOptionSelected(tagName, name)) { +- return new OptionSelectedDynamicAttribute(name, attribute); ++ }); ++ APPEND_OPCODES.add(Op.ToBoolean, vm => { ++ let { ++ stack ++ } = vm; ++ let valueRef = check(stack.pop(), CheckReference); ++ stack.push(createComputeRef(() => toBool$1(valueForRef(valueRef)))); ++ }); ++ class Assert { ++ last; ++ constructor(ref) { ++ this.ref = ref; ++ this.last = valueForRef(ref); + } +- return new DefaultDynamicProperty(name, attribute); +- } +- class DynamicAttribute { +- constructor(attribute) { +- this.attribute = attribute; ++ evaluate(vm) { ++ let { ++ last, ++ ref ++ } = this; ++ let current = valueForRef(ref); ++ if (last !== current) { ++ vm.throw(); ++ } + } + } +- class SimpleDynamicAttribute extends DynamicAttribute { +- set(dom, value, _env) { +- const normalizedValue = normalizeValue(value); +- if (normalizedValue !== null) { +- const { +- name, +- namespace +- } = this.attribute; +- dom.__setAttribute(name, normalizedValue, namespace); +- } ++ class AssertFilter { ++ last; ++ constructor(ref, filter) { ++ this.ref = ref; ++ this.filter = filter; ++ this.last = filter(valueForRef(ref)); + } +- update(value, _env) { +- const normalizedValue = normalizeValue(value); +- const { +- element, +- name +- } = this.attribute; +- if (normalizedValue === null) { +- element.removeAttribute(name); +- } else { +- element.setAttribute(name, normalizedValue); ++ evaluate(vm) { ++ let { ++ last, ++ ref, ++ filter ++ } = this; ++ let current = filter(valueForRef(ref)); ++ if (last !== current) { ++ vm.throw(); + } + } + } +- class DefaultDynamicProperty extends DynamicAttribute { +- constructor(normalizedName, attribute) { +- super(attribute); +- this.normalizedName = normalizedName; +- } +- value; +- set(dom, value, _env) { +- if (value !== null && value !== undefined) { +- this.value = value; +- dom.__setProperty(this.normalizedName, value); +- } ++ class JumpIfNotModifiedOpcode { ++ tag = CONSTANT_TAG; ++ lastRevision = INITIAL; ++ target; ++ finalize(tag, target) { ++ this.target = target; ++ this.didModify(tag); + } +- update(value, _env) { +- const { +- element +- } = this.attribute; +- if (this.value !== value) { +- element[this.normalizedName] = this.value = value; +- if (value === null || value === undefined) { +- this.removeAttribute(); +- } ++ evaluate(vm) { ++ let { ++ tag, ++ target, ++ lastRevision ++ } = this; ++ if (!vm.alwaysRevalidate && validateTag(tag, lastRevision)) { ++ consumeTag(tag); ++ vm.goto(expect(target, 'VM BUG: Target must be set before attempting to jump')); + } + } +- removeAttribute() { +- // TODO this sucks but to preserve properties first and to meet current +- // semantics we must do this. +- const { +- element, +- namespace +- } = this.attribute; +- if (namespace) { +- element.removeAttributeNS(namespace, this.normalizedName); +- } else { +- element.removeAttribute(this.normalizedName); +- } ++ didModify(tag) { ++ this.tag = tag; ++ this.lastRevision = valueForTag(this.tag); ++ consumeTag(tag); + } + } +- class SafeDynamicProperty extends DefaultDynamicProperty { +- set(dom, value, env) { +- const { +- element, +- name +- } = this.attribute; +- const sanitized = sanitizeAttributeValue(element, name, value); +- super.set(dom, sanitized, env); ++ class BeginTrackFrameOpcode { ++ constructor(debugLabel) { ++ this.debugLabel = debugLabel; + } +- update(value, env) { +- const { +- element, +- name +- } = this.attribute; +- const sanitized = sanitizeAttributeValue(element, name, value); +- super.update(sanitized, env); ++ evaluate() { ++ beginTrackFrame(this.debugLabel); + } + } +- class SafeDynamicAttribute extends SimpleDynamicAttribute { +- set(dom, value, env) { +- const { +- element, +- name +- } = this.attribute; +- const sanitized = sanitizeAttributeValue(element, name, value); +- super.set(dom, sanitized, env); ++ class EndTrackFrameOpcode { ++ constructor(target) { ++ this.target = target; + } +- update(value, env) { +- const { +- element, +- name +- } = this.attribute; +- const sanitized = sanitizeAttributeValue(element, name, value); +- super.update(sanitized, env); ++ evaluate() { ++ let tag = endTrackFrame(); ++ this.target.didModify(tag); + } + } +- class InputValueDynamicAttribute extends DefaultDynamicProperty { +- set(dom, value) { +- dom.__setProperty('value', normalizeStringValue(value)); ++ APPEND_OPCODES.add(Op.Text, (vm, { ++ op1: text ++ }) => { ++ vm.elements().appendText(vm[CONSTANTS].getValue(text)); ++ }); ++ APPEND_OPCODES.add(Op.Comment, (vm, { ++ op1: text ++ }) => { ++ vm.elements().appendComment(vm[CONSTANTS].getValue(text)); ++ }); ++ APPEND_OPCODES.add(Op.OpenElement, (vm, { ++ op1: tag ++ }) => { ++ vm.elements().openElement(vm[CONSTANTS].getValue(tag)); ++ }); ++ APPEND_OPCODES.add(Op.OpenDynamicElement, vm => { ++ let tagName = check(valueForRef(check(vm.stack.pop(), CheckReference)), CheckString); ++ vm.elements().openElement(tagName); ++ }); ++ APPEND_OPCODES.add(Op.PushRemoteElement, vm => { ++ let elementRef = check(vm.stack.pop(), CheckReference); ++ let insertBeforeRef = check(vm.stack.pop(), CheckReference); ++ let guidRef = check(vm.stack.pop(), CheckReference); ++ let element = check(valueForRef(elementRef), CheckElement); ++ let insertBefore = check(valueForRef(insertBeforeRef), CheckMaybe(CheckOption(CheckNode))); ++ let guid = valueForRef(guidRef); ++ if (!isConstRef(elementRef)) { ++ vm.updateWith(new Assert(elementRef)); + } +- update(value) { +- const input = castToBrowser(this.attribute.element, ['input', 'textarea']); +- const currentValue = input.value; +- const normalizedValue = normalizeStringValue(value); +- if (currentValue !== normalizedValue) { +- input.value = normalizedValue; +- } ++ if (insertBefore !== undefined && !isConstRef(insertBeforeRef)) { ++ vm.updateWith(new Assert(insertBeforeRef)); + } +- } +- class OptionSelectedDynamicAttribute extends DefaultDynamicProperty { +- set(dom, value) { +- if (value !== null && value !== undefined && value !== false) { +- dom.__setProperty('selected', true); +- } ++ 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$1(block, () => { ++ vm.env.debugRenderTree?.willDestroy(block); ++ }); + } +- update(value) { +- const option = castToBrowser(this.attribute.element, 'option'); +- if (value) { +- option.selected = true; +- } else { +- option.selected = false; +- } ++ }); ++ APPEND_OPCODES.add(Op.PopRemoteElement, vm => { ++ let bounds = vm.elements().popRemoteElement(); ++ if (vm.env.debugRenderTree !== undefined) { ++ // The RemoteLiveBlock is also its bounds ++ vm.env.debugRenderTree.didRender(bounds, bounds); + } +- } +- function isOptionSelected(tagName, attribute) { +- return tagName === 'OPTION' && attribute === 'selected'; +- } +- function isUserInputValue(tagName, attribute) { +- return (tagName === 'INPUT' || tagName === 'TEXTAREA') && attribute === 'value'; +- } +- function normalizeValue(value) { +- if (value === false || value === undefined || value === null || typeof value.toString === 'undefined') { +- return null; ++ }); ++ APPEND_OPCODES.add(Op.FlushElement, vm => { ++ let operations = check(vm.fetchValue($t0), CheckOperations); ++ let modifiers = null; ++ if (operations) { ++ modifiers = operations.flush(vm); ++ vm.loadValue($t0, null); + } +- if (value === true) { +- return ''; ++ vm.elements().flushElement(modifiers); ++ }); ++ APPEND_OPCODES.add(Op.CloseElement, vm => { ++ let modifiers = vm.elements().closeElement(); ++ if (modifiers !== null) { ++ modifiers.forEach(modifier => { ++ vm.env.scheduleInstallModifier(modifier); ++ const d = modifier.manager.getDestroyable(modifier.state); ++ if (d !== null) { ++ vm.associateDestroyable(d); ++ } ++ }); + } +- // onclick function etc in SSR +- if (typeof value === 'function') { +- return null; ++ }); ++ APPEND_OPCODES.add(Op.Modifier, (vm, { ++ op1: handle ++ }) => { ++ if (vm.env.isInteractive === false) { ++ return; + } +- return String(value); +- } +- let DebugStyleAttributeManager; +- { +- DebugStyleAttributeManager = class extends SimpleDynamicAttribute { +- set(dom, value, env) { +- warnIfStyleNotTrusted(value); +- super.set(dom, value, env); +- } +- update(value, env) { +- warnIfStyleNotTrusted(value); +- super.update(value, env); +- } ++ let owner = vm.getOwner(); ++ let args = check(vm.stack.pop(), CheckArguments); ++ let definition = vm[CONSTANTS].getValue(handle); ++ let { ++ manager ++ } = definition; ++ let { ++ constructing ++ } = vm.elements(); ++ let capturedArgs = args.capture(); ++ let state = manager.create(owner, expect(constructing, 'BUG: ElementModifier could not find the element it applies to'), definition.state, capturedArgs); ++ let instance = { ++ manager, ++ state, ++ definition + }; +- } +- class First { +- constructor(node) { +- this.node = node; +- } +- firstNode() { +- return this.node; +- } +- } +- class Last { +- constructor(node) { +- this.node = node; +- } +- lastNode() { +- return this.node; +- } +- } +- const CURSOR_STACK = Symbol('CURSOR_STACK'); +- class NewElementBuilder { +- dom; +- updateOperations; +- constructing = null; +- operations = null; +- env; +- [CURSOR_STACK] = new StackImpl(); +- modifierStack = new StackImpl(); +- blockStack = new StackImpl(); +- static forInitialRender(env, cursor) { +- return new this(env, cursor.element, cursor.nextSibling).initialize(); +- } +- static resume(env, block) { +- let parentNode = block.parentElement(); +- let nextSibling = block.reset(env); +- let stack = new this(env, parentNode, nextSibling).initialize(); +- stack.pushLiveBlock(block); +- return stack; +- } +- constructor(env, parentNode, nextSibling) { +- this.pushElement(parentNode, nextSibling); +- this.env = env; +- this.dom = env.getAppendOperations(); +- this.updateOperations = env.getDOM(); +- } +- initialize() { +- this.pushSimpleBlock(); +- return this; +- } +- debugBlocks() { +- return this.blockStack.toArray(); +- } +- get element() { +- return this[CURSOR_STACK].current.element; +- } +- get nextSibling() { +- return this[CURSOR_STACK].current.nextSibling; +- } +- get hasBlocks() { +- return this.blockStack.size > 0; +- } +- block() { +- return expect(this.blockStack.current, 'Expected a current live block'); +- } +- popElement() { +- this[CURSOR_STACK].pop(); +- expect(this[CURSOR_STACK].current, "can't pop past the last element"); +- } +- pushSimpleBlock() { +- return this.pushLiveBlock(new SimpleLiveBlock(this.element)); +- } +- pushUpdatableBlock() { +- return this.pushLiveBlock(new UpdatableBlockImpl(this.element)); ++ let operations = expect(check(vm.fetchValue($t0), CheckOperations), 'BUG: ElementModifier could not find operations to append to'); ++ operations.addModifier(vm, instance, capturedArgs); ++ let tag = manager.getTag(state); ++ if (tag !== null) { ++ consumeTag(tag); ++ return vm.updateWith(new UpdateModifierOpcode(tag, instance)); + } +- pushBlockList(list) { +- return this.pushLiveBlock(new LiveBlockList(this.element, list)); ++ }); ++ APPEND_OPCODES.add(Op.DynamicModifier, vm => { ++ if (vm.env.isInteractive === false) { ++ return; + } +- pushLiveBlock(block, isRemote = false) { +- let current = this.blockStack.current; +- if (current !== null) { +- if (!isRemote) { +- current.didAppendBounds(block); ++ let { ++ stack ++ } = vm; ++ let ref = check(stack.pop(), CheckReference); ++ let args = check(stack.pop(), CheckArguments).capture(); ++ let { ++ positional: outerPositional, ++ named: outerNamed ++ } = args; ++ let { ++ constructing ++ } = vm.elements(); ++ let initialOwner = vm.getOwner(); ++ let instanceRef = createComputeRef(() => { ++ let value = valueForRef(ref); ++ let owner; ++ if (!isObject(value)) { ++ return; ++ } ++ let hostDefinition; ++ if (isCurriedType(value, CurriedTypes.Modifier)) { ++ let { ++ definition: resolvedDefinition, ++ owner: curriedOwner, ++ positional, ++ named ++ } = resolveCurriedValue(value); ++ hostDefinition = resolvedDefinition; ++ owner = curriedOwner; ++ if (positional !== undefined) { ++ args.positional = positional.concat(outerPositional); ++ } ++ if (named !== undefined) { ++ args.named = Object.assign({}, ...named, outerNamed); + } ++ } else { ++ hostDefinition = value; ++ owner = initialOwner; + } +- this.__openBlock(); +- this.blockStack.push(block); +- return block; +- } +- popBlock() { +- this.block().finalize(this); +- this.__closeBlock(); +- return expect(this.blockStack.pop(), 'Expected popBlock to return a block'); +- } +- __openBlock() {} +- __closeBlock() {} +- +- // todo return seems unused +- openElement(tag) { +- let element = this.__openElement(tag); +- this.constructing = element; +- return element; +- } +- __openElement(tag) { +- return this.dom.createElement(tag, this.element); +- } +- flushElement(modifiers) { +- let parent = this.element; +- let element = expect(this.constructing, `flushElement should only be called when constructing an element`); +- this.__flushElement(parent, element); +- this.constructing = null; +- this.operations = null; +- this.pushModifiers(modifiers); +- this.pushElement(element, null); +- this.didOpenElement(element); +- } +- __flushElement(parent, constructing) { +- this.dom.insertBefore(parent, constructing, this.nextSibling); +- } +- closeElement() { +- this.willCloseElement(); +- this.popElement(); +- return this.popModifiers(); +- } +- pushRemoteElement(element, guid, insertBefore) { +- return this.__pushRemoteElement(element, guid, insertBefore); +- } +- __pushRemoteElement(element, _guid, insertBefore) { +- this.pushElement(element, insertBefore); +- if (insertBefore === undefined) { +- while (element.lastChild) { +- element.removeChild(element.lastChild); ++ let manager = getInternalModifierManager(hostDefinition, true); ++ if (manager === null) { ++ { ++ throw new Error(`Expected a dynamic modifier definition, but received an object or function that did not have a modifier manager associated with it. The dynamic invocation was \`{{${ref.debugLabel}}}\`, and the incorrect definition is the value at the path \`${ref.debugLabel}\`, which was: ${debugToString$1(hostDefinition)}`); + } + } +- let block = new RemoteLiveBlock(element); +- return this.pushLiveBlock(block, true); +- } +- popRemoteElement() { +- const block = this.popBlock(); +- debugAssert(block instanceof RemoteLiveBlock, '[BUG] expecting a RemoteLiveBlock'); +- this.popElement(); +- return block; +- } +- pushElement(element, nextSibling = null) { +- this[CURSOR_STACK].push(new CursorImpl(element, nextSibling)); +- } +- pushModifiers(modifiers) { +- this.modifierStack.push(modifiers); +- } +- popModifiers() { +- return this.modifierStack.pop(); +- } +- didAppendBounds(bounds) { +- this.block().didAppendBounds(bounds); +- return bounds; +- } +- didAppendNode(node) { +- this.block().didAppendNode(node); +- return node; +- } +- didOpenElement(element) { +- this.block().openElement(element); +- return element; ++ let definition = { ++ resolvedName: null, ++ manager, ++ state: hostDefinition ++ }; ++ let state = manager.create(owner, expect(constructing, 'BUG: ElementModifier could not find the element it applies to'), definition.state, args); ++ return { ++ manager, ++ state, ++ definition ++ }; ++ }); ++ let instance = valueForRef(instanceRef); ++ let tag = null; ++ if (instance !== undefined) { ++ let operations = expect(check(vm.fetchValue($t0), CheckOperations), 'BUG: ElementModifier could not find operations to append to'); ++ operations.addModifier(vm, instance, args); ++ tag = instance.manager.getTag(instance.state); ++ if (tag !== null) { ++ consumeTag(tag); ++ } + } +- willCloseElement() { +- this.block().closeElement(); ++ if (!isConstRef(ref) || tag) { ++ return vm.updateWith(new UpdateDynamicModifierOpcode(tag, instance, instanceRef)); + } +- appendText(string) { +- return this.didAppendNode(this.__appendText(string)); ++ }); ++ class UpdateModifierOpcode { ++ lastUpdated; ++ constructor(tag, modifier) { ++ this.tag = tag; ++ this.modifier = modifier; ++ this.lastUpdated = valueForTag(tag); + } +- __appendText(text) { ++ evaluate(vm) { + let { +- dom, +- element, +- nextSibling ++ modifier, ++ tag, ++ lastUpdated + } = this; +- let node = dom.createTextNode(text); +- dom.insertBefore(element, node, nextSibling); +- return node; +- } +- __appendNode(node) { +- this.dom.insertBefore(this.element, node, this.nextSibling); +- return node; +- } +- __appendFragment(fragment) { +- let first = fragment.firstChild; +- if (first) { +- let ret = new ConcreteBounds(this.element, first, fragment.lastChild); +- this.dom.insertBefore(this.element, fragment, this.nextSibling); +- return ret; +- } else { +- const comment = this.__appendComment(''); +- return new ConcreteBounds(this.element, comment, comment); ++ consumeTag(tag); ++ if (!validateTag(tag, lastUpdated)) { ++ vm.env.scheduleUpdateModifier(modifier); ++ this.lastUpdated = valueForTag(tag); + } + } +- __appendHTML(html) { +- return this.dom.insertHTMLBefore(this.element, this.nextSibling, html); +- } +- appendDynamicHTML(value) { +- let bounds = this.trustedContent(value); +- this.didAppendBounds(bounds); +- } +- appendDynamicText(value) { +- let node = this.untrustedContent(value); +- this.didAppendNode(node); +- return node; +- } +- appendDynamicFragment(value) { +- let bounds = this.__appendFragment(value); +- this.didAppendBounds(bounds); +- } +- appendDynamicNode(value) { +- let node = this.__appendNode(value); +- let bounds = new ConcreteBounds(this.element, node, node); +- this.didAppendBounds(bounds); +- } +- trustedContent(value) { +- return this.__appendHTML(value); +- } +- untrustedContent(value) { +- return this.__appendText(value); +- } +- appendComment(string) { +- return this.didAppendNode(this.__appendComment(string)); ++ } ++ class UpdateDynamicModifierOpcode { ++ lastUpdated; ++ constructor(tag, instance, instanceRef) { ++ this.tag = tag; ++ this.instance = instance; ++ this.instanceRef = instanceRef; ++ this.lastUpdated = valueForTag(tag ?? CURRENT_TAG); + } +- __appendComment(string) { ++ evaluate(vm) { + let { +- dom, +- element, +- nextSibling ++ tag, ++ lastUpdated, ++ instance, ++ instanceRef + } = this; +- let node = dom.createComment(string); +- dom.insertBefore(element, node, nextSibling); +- return node; +- } +- __setAttribute(name, value, namespace) { +- this.dom.setAttribute(this.constructing, name, value, namespace); +- } +- __setProperty(name, value) { +- this.constructing[name] = value; +- } +- setStaticAttribute(name, value, namespace) { +- this.__setAttribute(name, value, namespace); +- } +- setDynamicAttribute(name, value, trusting, namespace) { +- let element = this.constructing; +- let attribute = dynamicAttribute(element, name, namespace, trusting); +- attribute.set(this, value, this.env); +- return attribute; ++ let newInstance = valueForRef(instanceRef); ++ if (newInstance !== instance) { ++ if (instance !== undefined) { ++ let destroyable = instance.manager.getDestroyable(instance.state); ++ if (destroyable !== null) { ++ destroy(destroyable); ++ } ++ } ++ if (newInstance !== undefined) { ++ let { ++ manager, ++ state ++ } = newInstance; ++ let destroyable = manager.getDestroyable(state); ++ if (destroyable !== null) { ++ associateDestroyableChild(this, destroyable); ++ } ++ tag = manager.getTag(state); ++ if (tag !== null) { ++ this.lastUpdated = valueForTag(tag); ++ } ++ this.tag = tag; ++ vm.env.scheduleInstallModifier(newInstance); ++ } ++ this.instance = newInstance; ++ } else if (tag !== null && !validateTag(tag, lastUpdated)) { ++ vm.env.scheduleUpdateModifier(instance); ++ this.lastUpdated = valueForTag(tag); ++ } ++ if (tag !== null) { ++ consumeTag(tag); ++ } + } + } +- class SimpleLiveBlock { +- first = null; +- last = null; +- nesting = 0; +- constructor(parent) { +- this.parent = parent; +- } +- parentElement() { +- return this.parent; +- } +- firstNode() { +- let first = expect(this.first, 'cannot call `firstNode()` while `SimpleLiveBlock` is still initializing'); +- return first.firstNode(); +- } +- lastNode() { +- let last = expect(this.last, 'cannot call `lastNode()` while `SimpleLiveBlock` is still initializing'); +- return last.lastNode(); +- } +- openElement(element) { +- this.didAppendNode(element); +- this.nesting++; ++ APPEND_OPCODES.add(Op.StaticAttr, (vm, { ++ op1: _name, ++ op2: _value, ++ op3: _namespace ++ }) => { ++ let name = vm[CONSTANTS].getValue(_name); ++ let value = vm[CONSTANTS].getValue(_value); ++ let namespace = _namespace ? vm[CONSTANTS].getValue(_namespace) : null; ++ vm.elements().setStaticAttribute(name, value, namespace); ++ }); ++ APPEND_OPCODES.add(Op.DynamicAttr, (vm, { ++ op1: _name, ++ op2: _trusting, ++ op3: _namespace ++ }) => { ++ let name = vm[CONSTANTS].getValue(_name); ++ let trusting = vm[CONSTANTS].getValue(_trusting); ++ let reference = check(vm.stack.pop(), CheckReference); ++ let value = valueForRef(reference); ++ let namespace = _namespace ? vm[CONSTANTS].getValue(_namespace) : null; ++ let attribute = vm.elements().setDynamicAttribute(name, value, trusting, namespace); ++ if (!isConstRef(reference)) { ++ vm.updateWith(new UpdateDynamicAttributeOpcode(reference, attribute, vm.env)); + } +- closeElement() { +- this.nesting--; ++ }); ++ class UpdateDynamicAttributeOpcode { ++ updateRef; ++ constructor(reference, attribute, env) { ++ let initialized = false; ++ this.updateRef = createComputeRef(() => { ++ let value = valueForRef(reference); ++ if (initialized === true) { ++ attribute.update(value, env); ++ } else { ++ initialized = true; ++ } ++ }); ++ valueForRef(this.updateRef); + } +- didAppendNode(node) { +- if (this.nesting !== 0) return; +- if (!this.first) { +- this.first = new First(node); +- } +- this.last = new Last(node); ++ evaluate() { ++ valueForRef(this.updateRef); + } +- didAppendBounds(bounds) { +- if (this.nesting !== 0) return; +- if (!this.first) { +- this.first = bounds; +- } +- this.last = bounds; +- } +- finalize(stack) { +- if (this.first === null) { +- stack.appendComment(''); +- } +- } +- } +- class RemoteLiveBlock extends SimpleLiveBlock { +- constructor(parent) { +- super(parent); +- registerDestructor$1(this, () => { +- // In general, you only need to clear the root of a hierarchy, and should never +- // need to clear any child nodes. This is an important constraint that gives us +- // a strong guarantee that clearing a subtree is a single DOM operation. +- // +- // Because remote blocks are not normally physically nested inside of the tree +- // that they are logically nested inside, we manually clear remote blocks when +- // a logical parent is cleared. +- // +- // HOWEVER, it is currently possible for a remote block to be physically nested +- // inside of the block it is logically contained inside of. This happens when +- // the remote block is appended to the end of the application's entire element. +- // +- // The problem with that scenario is that Glimmer believes that it owns more of +- // the DOM than it actually does. The code is attempting to write past the end +- // of the Glimmer-managed root, but Glimmer isn't aware of that. +- // +- // The correct solution to that problem is for Glimmer to be aware of the end +- // of the bounds that it owns, and once we make that change, this check could +- // be removed. +- // +- // For now, a more targeted fix is to check whether the node was already removed +- // and avoid clearing the node if it was. In most cases this shouldn't happen, +- // so this might hide bugs where the code clears nested nodes unnecessarily, +- // so we should eventually try to do the correct fix. +- if (this.parentElement() === this.firstNode().parentNode) { +- clear(this); +- } +- }); +- } +- } +- class UpdatableBlockImpl extends SimpleLiveBlock { +- reset() { +- destroy(this); +- let nextSibling = clear(this); +- this.first = null; +- this.last = null; +- this.nesting = 0; +- return nextSibling; +- } +- } +- +- // FIXME: All the noops in here indicate a modelling problem +- class LiveBlockList { +- constructor(parent, boundList) { +- this.parent = parent; +- this.boundList = boundList; +- this.parent = parent; +- this.boundList = boundList; +- } +- parentElement() { +- return this.parent; +- } +- firstNode() { +- let head = expect(this.boundList[0], 'cannot call `firstNode()` while `LiveBlockList` is still initializing'); +- return head.firstNode(); +- } +- lastNode() { +- let boundList = this.boundList; +- let tail = expect(boundList[boundList.length - 1], 'cannot call `lastNode()` while `LiveBlockList` is still initializing'); +- return tail.lastNode(); +- } +- openElement(_element) { +- debugAssert(false, 'Cannot openElement directly inside a block list'); +- } +- closeElement() { +- debugAssert(false, 'Cannot closeElement directly inside a block list'); +- } +- didAppendNode(_node) { +- debugAssert(false, 'Cannot create a new node directly inside a block list'); +- } +- didAppendBounds(_bounds) {} +- finalize(_stack) { +- debugAssert(this.boundList.length > 0, 'boundsList cannot be empty'); +- } +- } +- function clientBuilder(env, cursor) { +- return NewElementBuilder.forInitialRender(env, cursor); +- } +- class AppendOpcodes { +- evaluateOpcode = new Array(Op.Size).fill(null); +- add(name, evaluate, kind = 'syscall') { +- this.evaluateOpcode[name] = { +- syscall: kind !== 'machine', +- evaluate +- }; +- } +- debugBefore(vm, opcode) { +- let params = undefined; +- let opName = undefined; +- let sp; +- recordStackSize(vm.fetchValue($sp)); +- return { +- sp: sp, +- pc: vm.fetchValue($pc), +- name: opName, +- params, +- type: opcode.type, +- isMachine: opcode.isMachine, +- size: opcode.size, +- state: undefined +- }; +- } +- debugAfter(vm, pre) {} +- evaluate(vm, opcode, type) { +- let operation = unwrap$1(this.evaluateOpcode[type]); +- if (operation.syscall) { +- debugAssert(!opcode.isMachine, `BUG: Mismatch between operation.syscall (${operation.syscall}) and opcode.isMachine (${opcode.isMachine}) for ${opcode.type}`); +- operation.evaluate(vm, opcode); +- } else { +- debugAssert(opcode.isMachine, `BUG: Mismatch between operation.syscall (${operation.syscall}) and opcode.isMachine (${opcode.isMachine}) for ${opcode.type}`); +- operation.evaluate(vm[INNER_VM], opcode); +- } +- } +- } +- const APPEND_OPCODES = new AppendOpcodes(); +- const TYPE = Symbol('TYPE'); +- const INNER = Symbol('INNER'); +- const OWNER = Symbol('OWNER'); +- const ARGS$2 = Symbol('ARGS'); +- const RESOLVED = Symbol('RESOLVED'); +- const CURRIED_VALUES = new WeakSet(); +- function isCurriedValue(value) { +- return CURRIED_VALUES.has(value); +- } +- function isCurriedType(value, type) { +- return isCurriedValue(value) && value[TYPE] === type; + } +- class CurriedValue { +- [TYPE]; +- [INNER]; +- [OWNER]; +- [ARGS$2]; +- [RESOLVED]; + +- /** @internal */ +- constructor(type, inner, owner, args, resolved = false) { +- CURRIED_VALUES.add(this); +- this[TYPE] = type; +- this[INNER] = inner; +- this[OWNER] = owner; +- this[ARGS$2] = args; +- this[RESOLVED] = resolved; +- } +- } +- function resolveCurriedValue(curriedValue) { +- let currentWrapper = curriedValue; +- let positional; +- let named; +- let definition, owner, resolved; ++ /** ++ * The VM creates a new ComponentInstance data structure for every component ++ * invocation it encounters. ++ * ++ * Similar to how a ComponentDefinition contains state about all components of a ++ * particular type, a ComponentInstance contains state specific to a particular ++ * instance of a component type. It also contains a pointer back to its ++ * component type's ComponentDefinition. ++ */ + +- // eslint-disable-next-line no-constant-condition +- while (true) { +- let { +- [ARGS$2]: curriedArgs, +- [INNER]: inner +- } = currentWrapper; +- if (curriedArgs !== null) { +- let { +- named: curriedNamed, +- positional: curriedPositional +- } = curriedArgs; +- if (curriedPositional.length > 0) { +- positional = positional === undefined ? curriedPositional : curriedPositional.concat(positional); +- } +- if (named === undefined) { +- named = []; +- } +- named.unshift(curriedNamed); +- } +- if (!isCurriedValue(inner)) { +- // Save off the owner that this helper was curried with. Later on, +- // we'll fetch the value of this register and set it as the owner on the +- // new root scope. +- definition = inner; +- owner = currentWrapper[OWNER]; +- resolved = currentWrapper[RESOLVED]; +- break; +- } +- currentWrapper = inner; +- } +- return { ++ APPEND_OPCODES.add(Op.PushComponentDefinition, (vm, { ++ op1: handle ++ }) => { ++ let definition = vm[CONSTANTS].getValue(handle); ++ debugAssert(!!definition, `Missing component for ${handle}`); ++ let { ++ manager, ++ capabilities ++ } = definition; ++ let instance = { + definition, +- owner, +- resolved, +- positional, +- named ++ manager, ++ capabilities, ++ state: null, ++ handle: null, ++ table: null, ++ lookup: null + }; +- } +- function curry(type, spec, owner, args, resolved = false) { +- return new CurriedValue(type, spec, owner, args, resolved); +- } +- function createCurryRef(type, inner, owner, args, resolver, isStrict) { +- let lastValue, curriedDefinition; +- return createComputeRef(() => { +- let value = valueForRef(inner); +- if (value === lastValue) { +- return curriedDefinition; +- } +- if (isCurriedType(value, type)) { +- curriedDefinition = args ? curry(type, value, owner, args) : args; +- } else if (type === CurriedTypes.Component && typeof value === 'string' && value) { +- // Only components should enter this path, as helpers and modifiers do not +- // support string based resolution +- +- { +- if (isStrict) { +- throw new Error(`Attempted to resolve a dynamic component with a string definition, \`${value}\` in a strict mode template. In strict mode, using strings to resolve component definitions is prohibited. You can instead import the component definition and use it directly.`); +- } +- let resolvedDefinition = expect(resolver, 'BUG: expected resolver for curried component definitions').lookupComponent(value, owner); +- if (!resolvedDefinition) { +- throw new Error(`Attempted to resolve \`${value}\`, which was expected to be a component, but nothing was found.`); +- } +- } +- curriedDefinition = curry(type, value, owner, args); +- } else if (isObject(value)) { +- curriedDefinition = curry(type, value, owner, args); +- } else { +- curriedDefinition = null; +- } +- lastValue = value; +- return curriedDefinition; +- }); +- } +- +- /** @internal */ +- function hasCustomDebugRenderTreeLifecycle(manager) { +- return 'getDebugCustomRenderTree' in manager; +- } +- function resolveComponent(resolver, constants, name, owner) { +- let definition = resolver.lookupComponent(name, expect(owner, 'BUG: expected owner when looking up component')); +- if (!definition) { +- throw new Error(`Attempted to resolve \`${name}\`, which was expected to be a component, but nothing was found.`); +- } +- return constants.resolvedComponent(definition, name); +- } +- function createClassListRef(list) { +- return createComputeRef(() => { +- let ret = []; +- for (const ref of list) { +- let value = normalizeStringValue(typeof ref === 'string' ? ref : valueForRef(ref)); +- if (value) ret.push(value); +- } +- return ret.length === 0 ? null : ret.join(' '); +- }); +- } +- function stackAssert(name, top) { +- return `Expected top of stack to be ${name}, was ${String(top)}`; +- } +- APPEND_OPCODES.add(Op.ChildScope, vm => vm.pushChildScope()); +- APPEND_OPCODES.add(Op.PopScope, vm => vm.popScope()); +- APPEND_OPCODES.add(Op.PushDynamicScope, vm => vm.pushDynamicScope()); +- APPEND_OPCODES.add(Op.PopDynamicScope, vm => vm.popDynamicScope()); +- APPEND_OPCODES.add(Op.Constant, (vm, { +- op1: other +- }) => { +- vm.stack.push(vm[CONSTANTS].getValue(decodeHandle(other))); +- }); +- APPEND_OPCODES.add(Op.ConstantReference, (vm, { +- op1: other +- }) => { +- vm.stack.push(createConstRef(vm[CONSTANTS].getValue(decodeHandle(other)), false)); ++ vm.stack.push(instance); + }); +- APPEND_OPCODES.add(Op.Primitive, (vm, { +- op1: primitive ++ APPEND_OPCODES.add(Op.ResolveDynamicComponent, (vm, { ++ op1: _isStrict + }) => { + let stack = vm.stack; +- if (isHandle(primitive)) { +- // it is a handle which does not already exist on the stack +- let value = vm[CONSTANTS].getValue(decodeHandle(primitive)); +- stack.push(value); ++ let component = check(valueForRef(check(stack.pop(), CheckReference)), CheckOr(CheckString, CheckCurriedComponentDefinition)); ++ let constants = vm[CONSTANTS]; ++ let owner = vm.getOwner(); ++ let isStrict = constants.getValue(_isStrict); ++ vm.loadValue($t1, null); // Clear the temp register ++ ++ let definition; ++ if (typeof component === 'string') { ++ if (isStrict) { ++ throw new Error(`Attempted to resolve a dynamic component with a string definition, \`${component}\` in a strict mode template. In strict mode, using strings to resolve component definitions is prohibited. You can instead import the component definition and use it directly.`); ++ } ++ let resolvedDefinition = resolveComponent(vm.runtime.resolver, constants, component, owner); ++ definition = expect(resolvedDefinition, `Could not find a component named "${component}"`); ++ } else if (isCurriedValue(component)) { ++ definition = component; + } else { +- // is already an encoded immediate or primitive handle +- stack.push(decodeImmediate(primitive)); ++ definition = constants.component(component, owner); + } ++ stack.push(definition); + }); +- APPEND_OPCODES.add(Op.PrimitiveReference, vm => { ++ APPEND_OPCODES.add(Op.ResolveCurriedComponent, vm => { + let stack = vm.stack; +- let value = check(stack.pop(), CheckPrimitive); +- let ref; +- if (value === undefined) { +- ref = UNDEFINED_REFERENCE; +- } else if (value === null) { +- ref = NULL_REFERENCE; +- } else if (value === true) { +- ref = TRUE_REFERENCE; +- } else if (value === false) { +- ref = FALSE_REFERENCE; +- } else { +- ref = createPrimitiveRef(value); ++ let ref = check(stack.pop(), CheckReference); ++ let value = valueForRef(ref); ++ let constants = vm[CONSTANTS]; ++ let definition; ++ if (!(typeof value === 'function' || typeof value === 'object' && value !== null)) { ++ throw new Error(`Expected a component definition, but received ${value}. You may have accidentally done <${ref.debugLabel}>, where "${ref.debugLabel}" was a string instead of a curried component definition. You must either use the component definition directly, or use the {{component}} helper to create a curried component definition when invoking dynamically.`); + } +- stack.push(ref); +- }); +- APPEND_OPCODES.add(Op.Dup, (vm, { +- op1: register, +- op2: offset +- }) => { +- let position = check(vm.fetchValue(register), CheckNumber) - offset; +- vm.stack.dup(position); +- }); +- APPEND_OPCODES.add(Op.Pop, (vm, { +- op1: count +- }) => { +- vm.stack.pop(count); +- }); +- APPEND_OPCODES.add(Op.Load, (vm, { +- op1: register +- }) => { +- vm.load(register); +- }); +- APPEND_OPCODES.add(Op.Fetch, (vm, { +- op1: register +- }) => { +- vm.fetch(register); +- }); +- APPEND_OPCODES.add(Op.BindDynamicScope, (vm, { +- op1: _names +- }) => { +- let names = vm[CONSTANTS].getArray(_names); +- vm.bindDynamicScope(names); +- }); +- APPEND_OPCODES.add(Op.Enter, (vm, { +- op1: args +- }) => { +- vm.enter(args); +- }); +- APPEND_OPCODES.add(Op.Exit, vm => { +- vm.exit(); +- }); +- APPEND_OPCODES.add(Op.PushSymbolTable, (vm, { +- op1: _table +- }) => { +- let stack = vm.stack; +- stack.push(vm[CONSTANTS].getValue(_table)); +- }); +- APPEND_OPCODES.add(Op.PushBlockScope, vm => { +- let stack = vm.stack; +- stack.push(vm.scope()); +- }); +- APPEND_OPCODES.add(Op.CompileBlock, vm => { +- let stack = vm.stack; +- let block = stack.pop(); +- if (block) { +- stack.push(vm.compile(block)); ++ if (isCurriedValue(value)) { ++ definition = value; + } else { +- stack.push(null); ++ definition = constants.component(value, vm.getOwner(), true); ++ if (definition === null) { ++ throw new Error(`Expected a dynamic component definition, but received an object or function that did not have a component manager associated with it. The dynamic invocation was \`<${ref.debugLabel}>\` or \`{{${ref.debugLabel}}}\`, and the incorrect definition is the value at the path \`${ref.debugLabel}\`, which was: ${debugToString$1(value)}`); ++ } + } ++ stack.push(definition); + }); +- APPEND_OPCODES.add(Op.InvokeYield, vm => { ++ APPEND_OPCODES.add(Op.PushDynamicComponentInstance, vm => { + let { + stack + } = vm; +- let handle = check(stack.pop(), CheckOption(CheckHandle)); +- let scope = check(stack.pop(), CheckOption(CheckScope)); +- let table = check(stack.pop(), CheckOption(CheckBlockSymbolTable)); +- debugAssert(table === null || table && typeof table === 'object' && Array.isArray(table.parameters), stackAssert('Option', table)); +- let args = check(stack.pop(), CheckInstanceof(VMArgumentsImpl)); +- if (table === null) { +- // To balance the pop{Frame,Scope} +- vm.pushFrame(); +- vm.pushScope(scope ?? vm.scope()); +- return; +- } +- let invokingScope = expect(scope, 'BUG: expected scope'); +- +- // If necessary, create a child scope +- { +- let locals = table.parameters; +- let localsCount = locals.length; +- if (localsCount > 0) { +- invokingScope = invokingScope.child(); +- for (let i = 0; i < localsCount; i++) { +- invokingScope.bindSymbol(unwrap$1(locals[i]), args.at(i)); +- } +- } +- } +- vm.pushFrame(); +- vm.pushScope(invokingScope); +- vm.call(handle); +- }); +- APPEND_OPCODES.add(Op.JumpIf, (vm, { +- op1: target +- }) => { +- let reference = check(vm.stack.pop(), CheckReference); +- let value = Boolean(valueForRef(reference)); +- if (isConstRef(reference)) { +- if (value === true) { +- vm.goto(target); +- } +- } else { +- if (value === true) { +- vm.goto(target); +- } +- vm.updateWith(new Assert(reference)); +- } +- }); +- APPEND_OPCODES.add(Op.JumpUnless, (vm, { +- op1: target +- }) => { +- let reference = check(vm.stack.pop(), CheckReference); +- let value = Boolean(valueForRef(reference)); +- if (isConstRef(reference)) { +- if (value === false) { +- vm.goto(target); +- } ++ let definition = stack.pop(); ++ let capabilities, manager; ++ if (isCurriedValue(definition)) { ++ manager = capabilities = null; + } else { +- if (value === false) { +- vm.goto(target); +- } +- vm.updateWith(new Assert(reference)); ++ manager = definition.manager; ++ capabilities = definition.capabilities; + } ++ stack.push({ ++ definition, ++ capabilities, ++ manager, ++ state: null, ++ handle: null, ++ table: null ++ }); + }); +- APPEND_OPCODES.add(Op.JumpEq, (vm, { +- op1: target, +- op2: comparison ++ APPEND_OPCODES.add(Op.PushArgs, (vm, { ++ op1: _names, ++ op2: _blockNames, ++ op3: flags + }) => { +- let other = check(vm.stack.peek(), CheckNumber); +- if (other === comparison) { +- vm.goto(target); +- } +- }); +- APPEND_OPCODES.add(Op.AssertSame, vm => { +- let reference = check(vm.stack.peek(), CheckReference); +- if (isConstRef(reference) === false) { +- vm.updateWith(new Assert(reference)); +- } ++ let stack = vm.stack; ++ let names = vm[CONSTANTS].getArray(_names); ++ let positionalCount = flags >> 4; ++ let atNames = flags & 0b1000; ++ let blockNames = flags & 0b0111 ? vm[CONSTANTS].getArray(_blockNames) : EMPTY_STRING_ARRAY; ++ vm[ARGS$1].setup(stack, names, blockNames, positionalCount, !!atNames); ++ stack.push(vm[ARGS$1]); + }); +- APPEND_OPCODES.add(Op.ToBoolean, vm => { ++ APPEND_OPCODES.add(Op.PushEmptyArgs, vm => { + let { + stack + } = vm; +- let valueRef = check(stack.pop(), CheckReference); +- stack.push(createComputeRef(() => toBool$1(valueForRef(valueRef)))); +- }); +- class Assert { +- last; +- constructor(ref) { +- this.ref = ref; +- this.last = valueForRef(ref); +- } +- evaluate(vm) { +- let { +- last, +- ref +- } = this; +- let current = valueForRef(ref); +- if (last !== current) { +- vm.throw(); +- } +- } +- } +- class AssertFilter { +- last; +- constructor(ref, filter) { +- this.ref = ref; +- this.filter = filter; +- this.last = filter(valueForRef(ref)); +- } +- evaluate(vm) { +- let { +- last, +- ref, +- filter +- } = this; +- let current = filter(valueForRef(ref)); +- if (last !== current) { +- vm.throw(); +- } +- } +- } +- class JumpIfNotModifiedOpcode { +- tag = CONSTANT_TAG; +- lastRevision = INITIAL; +- target; +- finalize(tag, target) { +- this.target = target; +- this.didModify(tag); +- } +- evaluate(vm) { +- let { +- tag, +- target, +- lastRevision +- } = this; +- if (!vm.alwaysRevalidate && validateTag(tag, lastRevision)) { +- consumeTag(tag); +- vm.goto(expect(target, 'VM BUG: Target must be set before attempting to jump')); +- } +- } +- didModify(tag) { +- this.tag = tag; +- this.lastRevision = valueForTag(this.tag); +- consumeTag(tag); +- } +- } +- class BeginTrackFrameOpcode { +- constructor(debugLabel) { +- this.debugLabel = debugLabel; +- } +- evaluate() { +- beginTrackFrame(this.debugLabel); +- } +- } +- class EndTrackFrameOpcode { +- constructor(target) { +- this.target = target; +- } +- evaluate() { +- let tag = endTrackFrame(); +- this.target.didModify(tag); +- } +- } +- APPEND_OPCODES.add(Op.Text, (vm, { +- op1: text +- }) => { +- vm.elements().appendText(vm[CONSTANTS].getValue(text)); +- }); +- APPEND_OPCODES.add(Op.Comment, (vm, { +- op1: text +- }) => { +- vm.elements().appendComment(vm[CONSTANTS].getValue(text)); +- }); +- APPEND_OPCODES.add(Op.OpenElement, (vm, { +- op1: tag +- }) => { +- vm.elements().openElement(vm[CONSTANTS].getValue(tag)); +- }); +- APPEND_OPCODES.add(Op.OpenDynamicElement, vm => { +- let tagName = check(valueForRef(check(vm.stack.pop(), CheckReference)), CheckString); +- vm.elements().openElement(tagName); +- }); +- APPEND_OPCODES.add(Op.PushRemoteElement, vm => { +- let elementRef = check(vm.stack.pop(), CheckReference); +- let insertBeforeRef = check(vm.stack.pop(), CheckReference); +- let guidRef = check(vm.stack.pop(), CheckReference); +- let element = check(valueForRef(elementRef), CheckElement); +- let insertBefore = check(valueForRef(insertBeforeRef), CheckMaybe(CheckOption(CheckNode))); +- let guid = valueForRef(guidRef); +- if (!isConstRef(elementRef)) { +- vm.updateWith(new Assert(elementRef)); +- } +- if (insertBefore !== undefined && !isConstRef(insertBeforeRef)) { +- vm.updateWith(new Assert(insertBeforeRef)); +- } +- 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$1(block, () => { +- vm.env.debugRenderTree?.willDestroy(block); +- }); +- } +- }); +- APPEND_OPCODES.add(Op.PopRemoteElement, vm => { +- 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 => { +- let operations = check(vm.fetchValue($t0), CheckOperations); +- let modifiers = null; +- if (operations) { +- modifiers = operations.flush(vm); +- vm.loadValue($t0, null); +- } +- vm.elements().flushElement(modifiers); +- }); +- APPEND_OPCODES.add(Op.CloseElement, vm => { +- let modifiers = vm.elements().closeElement(); +- if (modifiers !== null) { +- modifiers.forEach(modifier => { +- vm.env.scheduleInstallModifier(modifier); +- const d = modifier.manager.getDestroyable(modifier.state); +- if (d !== null) { +- vm.associateDestroyable(d); +- } +- }); +- } +- }); +- APPEND_OPCODES.add(Op.Modifier, (vm, { +- op1: handle +- }) => { +- if (vm.env.isInteractive === false) { +- return; +- } +- let owner = vm.getOwner(); +- let args = check(vm.stack.pop(), CheckArguments); +- let definition = vm[CONSTANTS].getValue(handle); +- let { +- manager +- } = definition; +- let { +- constructing +- } = vm.elements(); +- let capturedArgs = args.capture(); +- let state = manager.create(owner, expect(constructing, 'BUG: ElementModifier could not find the element it applies to'), definition.state, capturedArgs); +- let instance = { +- manager, +- state, +- definition +- }; +- let operations = expect(check(vm.fetchValue($t0), CheckOperations), 'BUG: ElementModifier could not find operations to append to'); +- operations.addModifier(vm, instance, capturedArgs); +- let tag = manager.getTag(state); +- if (tag !== null) { +- consumeTag(tag); +- return vm.updateWith(new UpdateModifierOpcode(tag, instance)); +- } +- }); +- APPEND_OPCODES.add(Op.DynamicModifier, vm => { +- if (vm.env.isInteractive === false) { +- return; +- } +- let { +- stack +- } = vm; +- let ref = check(stack.pop(), CheckReference); +- let args = check(stack.pop(), CheckArguments).capture(); +- let { +- positional: outerPositional, +- named: outerNamed +- } = args; +- let { +- constructing +- } = vm.elements(); +- let initialOwner = vm.getOwner(); +- let instanceRef = createComputeRef(() => { +- let value = valueForRef(ref); +- let owner; +- if (!isObject(value)) { +- return; +- } +- let hostDefinition; +- if (isCurriedType(value, CurriedTypes.Modifier)) { +- let { +- definition: resolvedDefinition, +- owner: curriedOwner, +- positional, +- named +- } = resolveCurriedValue(value); +- hostDefinition = resolvedDefinition; +- owner = curriedOwner; +- if (positional !== undefined) { +- args.positional = positional.concat(outerPositional); +- } +- if (named !== undefined) { +- args.named = Object.assign({}, ...named, outerNamed); +- } +- } else { +- hostDefinition = value; +- owner = initialOwner; +- } +- let manager = getInternalModifierManager(hostDefinition, true); +- if (manager === null) { +- { +- throw new Error(`Expected a dynamic modifier definition, but received an object or function that did not have a modifier manager associated with it. The dynamic invocation was \`{{${ref.debugLabel}}}\`, and the incorrect definition is the value at the path \`${ref.debugLabel}\`, which was: ${debugToString$1(hostDefinition)}`); +- } +- } +- let definition = { +- resolvedName: null, +- manager, +- state: hostDefinition +- }; +- let state = manager.create(owner, expect(constructing, 'BUG: ElementModifier could not find the element it applies to'), definition.state, args); +- return { +- manager, +- state, +- definition +- }; +- }); +- let instance = valueForRef(instanceRef); +- let tag = null; +- if (instance !== undefined) { +- let operations = expect(check(vm.fetchValue($t0), CheckOperations), 'BUG: ElementModifier could not find operations to append to'); +- operations.addModifier(vm, instance, args); +- tag = instance.manager.getTag(instance.state); +- if (tag !== null) { +- consumeTag(tag); +- } +- } +- if (!isConstRef(ref) || tag) { +- return vm.updateWith(new UpdateDynamicModifierOpcode(tag, instance, instanceRef)); +- } +- }); +- class UpdateModifierOpcode { +- lastUpdated; +- constructor(tag, modifier) { +- this.tag = tag; +- this.modifier = modifier; +- this.lastUpdated = valueForTag(tag); +- } +- evaluate(vm) { +- let { +- modifier, +- tag, +- lastUpdated +- } = this; +- consumeTag(tag); +- if (!validateTag(tag, lastUpdated)) { +- vm.env.scheduleUpdateModifier(modifier); +- this.lastUpdated = valueForTag(tag); +- } +- } +- } +- class UpdateDynamicModifierOpcode { +- lastUpdated; +- constructor(tag, instance, instanceRef) { +- this.tag = tag; +- this.instance = instance; +- this.instanceRef = instanceRef; +- this.lastUpdated = valueForTag(tag ?? CURRENT_TAG); +- } +- evaluate(vm) { +- let { +- tag, +- lastUpdated, +- instance, +- instanceRef +- } = this; +- let newInstance = valueForRef(instanceRef); +- if (newInstance !== instance) { +- if (instance !== undefined) { +- let destroyable = instance.manager.getDestroyable(instance.state); +- if (destroyable !== null) { +- destroy(destroyable); +- } +- } +- if (newInstance !== undefined) { +- let { +- manager, +- state +- } = newInstance; +- let destroyable = manager.getDestroyable(state); +- if (destroyable !== null) { +- associateDestroyableChild(this, destroyable); +- } +- tag = manager.getTag(state); +- if (tag !== null) { +- this.lastUpdated = valueForTag(tag); +- } +- this.tag = tag; +- vm.env.scheduleInstallModifier(newInstance); +- } +- this.instance = newInstance; +- } else if (tag !== null && !validateTag(tag, lastUpdated)) { +- vm.env.scheduleUpdateModifier(instance); +- this.lastUpdated = valueForTag(tag); +- } +- if (tag !== null) { +- consumeTag(tag); +- } +- } +- } +- APPEND_OPCODES.add(Op.StaticAttr, (vm, { +- op1: _name, +- op2: _value, +- op3: _namespace +- }) => { +- let name = vm[CONSTANTS].getValue(_name); +- let value = vm[CONSTANTS].getValue(_value); +- let namespace = _namespace ? vm[CONSTANTS].getValue(_namespace) : null; +- vm.elements().setStaticAttribute(name, value, namespace); +- }); +- APPEND_OPCODES.add(Op.DynamicAttr, (vm, { +- op1: _name, +- op2: _trusting, +- op3: _namespace +- }) => { +- let name = vm[CONSTANTS].getValue(_name); +- let trusting = vm[CONSTANTS].getValue(_trusting); +- let reference = check(vm.stack.pop(), CheckReference); +- let value = valueForRef(reference); +- let namespace = _namespace ? vm[CONSTANTS].getValue(_namespace) : null; +- let attribute = vm.elements().setDynamicAttribute(name, value, trusting, namespace); +- if (!isConstRef(reference)) { +- vm.updateWith(new UpdateDynamicAttributeOpcode(reference, attribute, vm.env)); +- } +- }); +- class UpdateDynamicAttributeOpcode { +- updateRef; +- constructor(reference, attribute, env) { +- let initialized = false; +- this.updateRef = createComputeRef(() => { +- let value = valueForRef(reference); +- if (initialized === true) { +- attribute.update(value, env); +- } else { +- initialized = true; +- } +- }); +- valueForRef(this.updateRef); +- } +- evaluate() { +- valueForRef(this.updateRef); +- } +- } +- +- /** +- * The VM creates a new ComponentInstance data structure for every component +- * invocation it encounters. +- * +- * Similar to how a ComponentDefinition contains state about all components of a +- * particular type, a ComponentInstance contains state specific to a particular +- * instance of a component type. It also contains a pointer back to its +- * component type's ComponentDefinition. +- */ +- +- APPEND_OPCODES.add(Op.PushComponentDefinition, (vm, { +- op1: handle +- }) => { +- let definition = vm[CONSTANTS].getValue(handle); +- debugAssert(!!definition, `Missing component for ${handle}`); +- let { +- manager, +- capabilities +- } = definition; +- let instance = { +- definition, +- manager, +- capabilities, +- state: null, +- handle: null, +- table: null, +- lookup: null +- }; +- vm.stack.push(instance); +- }); +- APPEND_OPCODES.add(Op.ResolveDynamicComponent, (vm, { +- op1: _isStrict +- }) => { +- let stack = vm.stack; +- let component = check(valueForRef(check(stack.pop(), CheckReference)), CheckOr(CheckString, CheckCurriedComponentDefinition)); +- let constants = vm[CONSTANTS]; +- let owner = vm.getOwner(); +- let isStrict = constants.getValue(_isStrict); +- vm.loadValue($t1, null); // Clear the temp register +- +- let definition; +- if (typeof component === 'string') { +- if (isStrict) { +- throw new Error(`Attempted to resolve a dynamic component with a string definition, \`${component}\` in a strict mode template. In strict mode, using strings to resolve component definitions is prohibited. You can instead import the component definition and use it directly.`); +- } +- let resolvedDefinition = resolveComponent(vm.runtime.resolver, constants, component, owner); +- definition = expect(resolvedDefinition, `Could not find a component named "${component}"`); +- } else if (isCurriedValue(component)) { +- definition = component; +- } else { +- definition = constants.component(component, owner); +- } +- stack.push(definition); +- }); +- APPEND_OPCODES.add(Op.ResolveCurriedComponent, vm => { +- let stack = vm.stack; +- let ref = check(stack.pop(), CheckReference); +- let value = valueForRef(ref); +- let constants = vm[CONSTANTS]; +- let definition; +- if (!(typeof value === 'function' || typeof value === 'object' && value !== null)) { +- throw new Error(`Expected a component definition, but received ${value}. You may have accidentally done <${ref.debugLabel}>, where "${ref.debugLabel}" was a string instead of a curried component definition. You must either use the component definition directly, or use the {{component}} helper to create a curried component definition when invoking dynamically.`); +- } +- if (isCurriedValue(value)) { +- definition = value; +- } else { +- definition = constants.component(value, vm.getOwner(), true); +- if (definition === null) { +- throw new Error(`Expected a dynamic component definition, but received an object or function that did not have a component manager associated with it. The dynamic invocation was \`<${ref.debugLabel}>\` or \`{{${ref.debugLabel}}}\`, and the incorrect definition is the value at the path \`${ref.debugLabel}\`, which was: ${debugToString$1(value)}`); +- } +- } +- stack.push(definition); +- }); +- APPEND_OPCODES.add(Op.PushDynamicComponentInstance, vm => { +- let { +- stack +- } = vm; +- let definition = stack.pop(); +- let capabilities, manager; +- if (isCurriedValue(definition)) { +- manager = capabilities = null; +- } else { +- manager = definition.manager; +- capabilities = definition.capabilities; +- } +- stack.push({ +- definition, +- capabilities, +- manager, +- state: null, +- handle: null, +- table: null +- }); +- }); +- APPEND_OPCODES.add(Op.PushArgs, (vm, { +- op1: _names, +- op2: _blockNames, +- op3: flags +- }) => { +- let stack = vm.stack; +- let names = vm[CONSTANTS].getArray(_names); +- let positionalCount = flags >> 4; +- let atNames = flags & 0b1000; +- let blockNames = flags & 0b0111 ? vm[CONSTANTS].getArray(_blockNames) : EMPTY_STRING_ARRAY; +- vm[ARGS$1].setup(stack, names, blockNames, positionalCount, !!atNames); +- stack.push(vm[ARGS$1]); +- }); +- APPEND_OPCODES.add(Op.PushEmptyArgs, vm => { +- let { +- stack +- } = vm; +- stack.push(vm[ARGS$1].empty(stack)); ++ stack.push(vm[ARGS$1].empty(stack)); + }); + APPEND_OPCODES.add(Op.CaptureArgs, vm => { + let stack = vm.stack; +@@ -27444,7 +26321,7 @@ var define, require; + const newBounds = new ConcreteBounds(this.element, first.nextSibling, last.previousSibling); + const possibleEmptyMarker = this.remove(first); + this.remove(last); +- if (possibleEmptyMarker !== null && isEmpty(possibleEmptyMarker)) { ++ if (possibleEmptyMarker !== null && isEmpty$3(possibleEmptyMarker)) { + this.candidate = this.remove(possibleEmptyMarker); + if (this.candidate !== null) { + this.clearMismatch(this.candidate); +@@ -27488,7 +26365,7 @@ var define, require; + } else if (isSeparator(candidate)) { + this.candidate = this.remove(candidate); + return this.__appendText(string); +- } else if (isEmpty(candidate) && string === '') { ++ } else if (isEmpty$3(candidate) && string === '') { + this.candidate = this.remove(candidate); + return this.__appendText(string); + } else { +@@ -27645,7 +26522,7 @@ var define, require; + function isSeparator(node) { + return node.nodeType === 8 && node.nodeValue === '%|%'; + } +- function isEmpty(node) { ++ function isEmpty$3(node) { + return node.nodeType === 8 && node.nodeValue === '% %'; + } + function isSameNodeType(candidate, tag) { +@@ -27735,17 +26612,6 @@ var define, require; + // information from the type itself. + const on = on$1; + +- // NOTE: this uses assignment to *require* that the `glimmerSetModifierManager` +- // is legally assignable to this type, i.e. that variance is properly upheld. +- const setModifierManager = setModifierManager$1; +- +- const emberModifierIndex = /*#__PURE__*/Object.defineProperty({ +- __proto__: null, +- capabilities: modifierCapabilities, +- on, +- setModifierManager +- }, Symbol.toStringTag, { value: 'Module' }); +- + const InputTemplate = templateFactory( + /* + `, convenient as ++ // that would be for end users, because there is no actual contract to that ++ // effect with Ember -- and in the future this choice would allow us to have ++ // registered services which have no base class. + // eslint-disable-next-line @typescript-eslint/no-empty-interface + +- const emberControllerIndex = /*#__PURE__*/Object.defineProperty({ ++ const emberServiceIndex = /*#__PURE__*/Object.defineProperty({ + __proto__: null, +- ControllerMixin, +- default: Controller, +- inject: inject$1 ++ default: Service, ++ inject: inject$1, ++ service + }, Symbol.toStringTag, { value: 'Module' }); + +- /** +- @module @ember/application/namespace ++ const LinkToTemplate = templateFactory( ++ /* ++ {{yield}} + */ ++ { ++ "id": "Ub0nir+H", ++ "block": "[[[11,3],[16,1,[30,0,[\"id\"]]],[16,0,[30,0,[\"class\"]]],[16,\"role\",[30,0,[\"role\"]]],[16,\"title\",[30,0,[\"title\"]]],[16,\"rel\",[30,0,[\"rel\"]]],[16,\"tabindex\",[30,0,[\"tabindex\"]]],[16,\"target\",[30,0,[\"target\"]]],[17,1],[16,6,[30,0,[\"href\"]]],[4,[32,0],[\"click\",[30,0,[\"click\"]]],null],[12],[18,2,null],[13]],[\"&attrs\",\"&default\"],false,[\"yield\"]]", ++ "moduleName": "packages/@ember/-internals/glimmer/lib/templates/link-to.hbs", ++ "scope": () => [on], ++ "isStrictMode": true ++ }); + ++ const EMPTY_ARRAY$1 = []; ++ const EMPTY_QUERY_PARAMS = {}; ++ debugFreeze(EMPTY_ARRAY$1); ++ debugFreeze(EMPTY_QUERY_PARAMS); ++ function isMissing(value) { ++ return value === null || value === undefined; ++ } ++ function isPresent$1(value) { ++ return !isMissing(value); ++ } ++ function isQueryParams(value) { ++ return typeof value === 'object' && value !== null && value['isQueryParams'] === true; ++ } + + /** +- A Namespace is an object usually used to contain other objects or methods +- such as an application or framework. Create a namespace anytime you want +- to define one of these new containers. ++ The `LinkTo` component renders a link to the supplied `routeName` passing an optionally ++ supplied model to the route as its `model` context of the route. The block for `LinkTo` ++ becomes the contents of the rendered element: + +- # Example Usage ++ ```handlebars ++ ++ Great Hamster Photos ++ ++ ``` ++ ++ This will result in: ++ ++ ```html ++ ++ Great Hamster Photos ++ ++ ``` ++ ++ ### Disabling the `LinkTo` component ++ ++ The `LinkTo` component can be disabled by using the `disabled` argument. A disabled link ++ doesn't result in a transition when activated, and adds the `disabled` class to the `` ++ element. ++ ++ (The class name to apply to the element can be overridden by using the `disabledClass` ++ argument) ++ ++ ```handlebars ++ ++ Great Hamster Photos ++ ++ ``` ++ ++ ### Handling `href` ++ ++ `` will use your application's Router to fill the element's `href` property with a URL ++ that matches the path to the supplied `routeName`. ++ ++ ### Handling current route ++ ++ The `LinkTo` component will apply a CSS class name of 'active' when the application's current ++ route matches the supplied routeName. For example, if the application's current route is ++ 'photoGallery.recent', then the following invocation of `LinkTo`: ++ ++ ```handlebars ++ ++ Great Hamster Photos ++ ++ ``` ++ ++ will result in ++ ++ ```html ++ ++ Great Hamster Photos ++ ++ ``` ++ ++ The CSS class used for active classes can be customized by passing an `activeClass` argument: ++ ++ ```handlebars ++ ++ Great Hamster Photos ++ ++ ``` ++ ++ ```html ++ ++ Great Hamster Photos ++ ++ ``` ++ ++ ### Keeping a link active for other routes ++ ++ If you need a link to be 'active' even when it doesn't match the current route, you can use the ++ `current-when` argument. ++ ++ ```handlebars ++ ++ Photo Gallery ++ ++ ``` ++ ++ This may be helpful for keeping links active for: ++ ++ * non-nested routes that are logically related ++ * some secondary menu approaches ++ * 'top navigation' with 'sub navigation' scenarios ++ ++ A link will be active if `current-when` is `true` or the current ++ route is the route this link would transition to. ++ ++ To match multiple routes 'space-separate' the routes: ++ ++ ```handlebars ++ ++ Art Gallery ++ ++ ``` ++ ++ ### Supplying a model ++ ++ An optional `model` argument can be used for routes whose ++ paths contain dynamic segments. This argument will become ++ the model context of the linked route: + + ```javascript +- MyFramework = Ember.Namespace.create({ +- VERSION: '1.0.0' ++ Router.map(function() { ++ this.route("photoGallery", {path: "hamster-photos/:photo_id"}); + }); + ``` + +- @class Namespace +- @extends EmberObject ++ ```handlebars ++ ++ {{aPhoto.title}} ++ ++ ``` ++ ++ ```html ++ ++ Tomster ++ ++ ``` ++ ++ ### Supplying multiple models ++ ++ For deep-linking to route paths that contain multiple ++ dynamic segments, the `models` argument can be used. ++ ++ As the router transitions through the route path, each ++ supplied model argument will become the context for the ++ route with the dynamic segments: ++ ++ ```javascript ++ Router.map(function() { ++ this.route("photoGallery", { path: "hamster-photos/:photo_id" }, function() { ++ this.route("comment", {path: "comments/:comment_id"}); ++ }); ++ }); ++ ``` ++ ++ This argument will become the model context of the linked route: ++ ++ ```handlebars ++ ++ {{comment.body}} ++ ++ ``` ++ ++ ```html ++ ++ A+++ would snuggle again. ++ ++ ``` ++ ++ ### Supplying an explicit dynamic segment value ++ ++ If you don't have a model object available to pass to `LinkTo`, ++ an optional string or integer argument can be passed for routes whose ++ paths contain dynamic segments. This argument will become the value ++ of the dynamic segment: ++ ++ ```javascript ++ Router.map(function() { ++ this.route("photoGallery", { path: "hamster-photos/:photo_id" }); ++ }); ++ ``` ++ ++ ```handlebars ++ ++ {{this.aPhoto.title}} ++ ++ ``` ++ ++ ```html ++ ++ Tomster ++ ++ ``` ++ ++ When transitioning into the linked route, the `model` hook will ++ be triggered with parameters including this passed identifier. ++ ++ ### Supplying query parameters ++ ++ If you need to add optional key-value pairs that appear to the right of the ? in a URL, ++ you can use the `query` argument. ++ ++ ```handlebars ++ ++ Great Hamster Photos ++ ++ ``` ++ ++ This will result in: ++ ++ ```html ++ ++ Great Hamster Photos ++ ++ ``` ++ ++ @for Ember.Templates.components ++ @method LinkTo + @public + */ +- class Namespace extends EmberObject { +- static NAMESPACES = NAMESPACES; +- static NAMESPACES_BY_ID = NAMESPACES_BY_ID; +- static processAll = processAllNamespaces; +- static byName = findNamespace; +- init(properties) { +- super.init(properties); +- addNamespace(this); +- } +- toString() { +- let existing_name = get$2(this, 'name') || get$2(this, 'modulePrefix'); +- if (existing_name) { +- (!(typeof existing_name === 'string') && assert$1("name wasn't a string", typeof existing_name === 'string')); +- return existing_name; +- } +- findNamespaces(); +- let name = getName(this); +- if (name === undefined) { +- name = guidFor(this); +- setName(this, name); +- } +- return name; +- } +- nameClasses() { +- processNamespace(this); +- } +- destroy() { +- removeNamespace(this); +- return super.destroy(); +- } +- } + +- // Declare on the prototype to have a single shared value. +- Namespace.prototype.isNamespace = true; ++ /** ++ @module @ember/routing ++ */ + +- const emberApplicationNamespace = /*#__PURE__*/Object.defineProperty({ +- __proto__: null, +- default: Namespace +- }, Symbol.toStringTag, { value: 'Module' }); ++ /** ++ See [Ember.Templates.components.LinkTo](/ember/release/classes/Ember.Templates.components/methods/input?anchor=LinkTo). ++ ++ @for Ember.Templates.helpers ++ @method link-to ++ @see {Ember.Templates.components.LinkTo} ++ @public ++ **/ + + /** +- * A topologically ordered map of key/value pairs with a simple API for adding constraints. +- * +- * Edges can forward reference keys that have not been added yet (the forward reference will +- * map the key to undefined). +- */ +- var DAG = function () { +- function DAG() { +- this._vertices = new Vertices(); ++ An opaque interface which can be imported and used in strict-mode ++ templates to call . ++ ++ See [Ember.Templates.components.LinkTo](/ember/release/classes/Ember.Templates.components/methods/input?anchor=LinkTo). ++ ++ @for @ember/routing ++ @method LinkTo ++ @see {Ember.Templates.components.LinkTo} ++ @public ++ **/ ++ ++ class _LinkTo extends InternalComponent { ++ static toString() { ++ return 'LinkTo'; + } +- /** +- * Adds a key/value pair with dependencies on other key/value pairs. +- * +- * @public +- * @param key The key of the vertex to be added. +- * @param value The value of that vertex. +- * @param before A key or array of keys of the vertices that must +- * be visited before this vertex. +- * @param after An string or array of strings with the keys of the +- * vertices that must be after this vertex is visited. +- */ +- DAG.prototype.add = function (key, value, before, after) { +- if (!key) throw new Error('argument `key` is required'); +- var vertices = this._vertices; +- var v = vertices.add(key); +- v.val = value; +- if (before) { +- if (typeof before === "string") { +- vertices.addEdge(v, vertices.add(before)); +- } else { +- for (var i = 0; i < before.length; i++) { +- vertices.addEdge(v, vertices.add(before[i])); +- } ++ static { ++ decorateFieldV2(this.prototype, "routing", [service('-routing')]); ++ } ++ #routing = (initializeDeferredDecorator(this, "routing"), void 0); ++ validateArguments() { ++ (!(!this.isEngine || this.engineMountPoint !== undefined) && assert$1('You attempted to use the component within a routeless engine, this is not supported. ' + 'If you are using the ember-engines addon, use the component instead. ' + 'See https://ember-engines.com/docs/links for more info.', !this.isEngine || this.engineMountPoint !== undefined)); ++ (!('route' in this.args.named || 'model' in this.args.named || 'models' in this.args.named || 'query' in this.args.named) && assert$1('You must provide at least one of the `@route`, `@model`, `@models` or `@query` arguments to ``.', 'route' in this.args.named || 'model' in this.args.named || 'models' in this.args.named || 'query' in this.args.named)); ++ (!(!('model' in this.args.named && 'models' in this.args.named)) && assert$1('You cannot provide both the `@model` and `@models` arguments to the component.', !('model' in this.args.named && 'models' in this.args.named))); ++ super.validateArguments(); ++ } ++ get class() { ++ let classes = 'ember-view'; ++ if (this.isActive) { ++ classes += this.classFor('active'); ++ if (this.willBeActive === false) { ++ classes += ' ember-transitioning-out'; + } ++ } else if (this.willBeActive) { ++ classes += ' ember-transitioning-in'; + } +- if (after) { +- if (typeof after === "string") { +- vertices.addEdge(vertices.add(after), v); +- } else { +- for (var i = 0; i < after.length; i++) { +- vertices.addEdge(vertices.add(after[i]), v); ++ if (this.isLoading) { ++ classes += this.classFor('loading'); ++ } ++ if (this.isDisabled) { ++ classes += this.classFor('disabled'); ++ } ++ return classes; ++ } ++ get href() { ++ if (this.isLoading) { ++ return '#'; ++ } ++ let { ++ routing, ++ route, ++ models, ++ query ++ } = this; ++ (!(isPresent$1(route)) && assert$1('[BUG] route can only be missing if isLoading is true', isPresent$1(route))); // consume the current router state so we invalidate when QP changes ++ // TODO: can we narrow this down to QP changes only? ++ consumeTag(tagFor(routing, 'currentState')); ++ { ++ try { ++ return routing.generateURL(route, models, query); ++ } catch (e) { ++ let details = e instanceof Error ? e.message : inspect(e); ++ let message = `While generating link to route "${route}": ${details}`; ++ if (e instanceof Error) { ++ e.message = message; ++ throw e; ++ } else { ++ throw message; + } + } + } +- }; +- /** +- * @deprecated please use add. +- */ +- DAG.prototype.addEdges = function (key, value, before, after) { +- this.add(key, value, before, after); +- }; +- /** +- * Visits key/value pairs in topological order. +- * +- * @public +- * @param callback The function to be invoked with each key/value. +- */ +- DAG.prototype.each = function (callback) { +- this._vertices.walk(callback); +- }; +- /** +- * @deprecated please use each. +- */ +- DAG.prototype.topsort = function (callback) { +- this.each(callback); +- }; +- return DAG; +- }(); +- /** @private */ +- var Vertices = function () { +- function Vertices() { +- this.length = 0; +- this.stack = new IntStack(); +- this.path = new IntStack(); +- this.result = new IntStack(); + } +- Vertices.prototype.add = function (key) { +- if (!key) throw new Error("missing key"); +- var l = this.length | 0; +- var vertex; +- for (var i = 0; i < l; i++) { +- vertex = this[i]; +- if (vertex.key === key) return vertex; ++ click(event) { ++ if (!isSimpleClick(event)) { ++ return; + } +- this.length = l + 1; +- return this[l] = { +- idx: l, +- key: key, +- val: undefined, +- out: false, +- flag: false, +- length: 0 ++ let element = event.currentTarget; ++ (!(element instanceof HTMLAnchorElement) && assert$1('[BUG] must be an element', element instanceof HTMLAnchorElement)); ++ let isSelf = element.target === '' || element.target === '_self'; ++ if (isSelf) { ++ this.preventDefault(event); ++ } else { ++ return; ++ } ++ if (this.isDisabled) { ++ return; ++ } ++ if (this.isLoading) { ++ (warn('This link is in an inactive loading state because at least one of its models ' + 'currently has a null/undefined value, or the provided route name is invalid.', false, { ++ id: 'ember-glimmer.link-to.inactive-loading-state' ++ })); ++ return; ++ } ++ let { ++ routing, ++ route, ++ models, ++ query, ++ replace ++ } = this; ++ let payload = { ++ routeName: route, ++ queryParams: query, ++ transition: undefined + }; +- }; +- Vertices.prototype.addEdge = function (v, w) { +- this.check(v, w.key); +- var l = w.length | 0; +- for (var i = 0; i < l; i++) { +- if (w[i] === v.idx) return; ++ flaggedInstrument('interaction.link-to', payload, () => { ++ (!(isPresent$1(route)) && assert$1('[BUG] route can only be missing if isLoading is true', isPresent$1(route))); ++ payload.transition = routing.transitionTo(route, models, query, replace); ++ }); ++ } ++ static { ++ decorateMethodV2(this.prototype, "click", [action$1]); ++ } ++ get route() { ++ if ('route' in this.args.named) { ++ let route = this.named('route'); ++ (!(isMissing(route) || typeof route === 'string') && assert$1('The `@route` argument to the component must be a string', isMissing(route) || typeof route === 'string')); ++ return route && this.namespaceRoute(route); ++ } else { ++ return this.currentRoute; + } +- w.length = l + 1; +- w[l] = v.idx; +- v.out = true; +- }; +- Vertices.prototype.walk = function (cb) { +- this.reset(); +- for (var i = 0; i < this.length; i++) { +- var vertex = this[i]; +- if (vertex.out) continue; +- this.visit(vertex, ""); ++ } ++ ++ // GH #17963 ++ currentRouteCache = createCache(() => { ++ consumeTag(tagFor(this.routing, 'currentState')); ++ return untrack(() => this.routing.currentRouteName); ++ }); ++ get currentRoute() { ++ return getValue(this.currentRouteCache); ++ } ++ ++ // TODO: not sure why generateURL takes {}[] instead of unknown[] ++ get models() { ++ if ('models' in this.args.named) { ++ let models = this.named('models'); ++ (!(Array.isArray(models)) && assert$1('The `@models` argument to the component must be an array.', Array.isArray(models))); ++ return models; ++ } else if ('model' in this.args.named) { ++ return [this.named('model')]; ++ } else { ++ return EMPTY_ARRAY$1; + } +- this.each(this.result, cb); +- }; +- Vertices.prototype.check = function (v, w) { +- if (v.key === w) { +- throw new Error("cycle detected: " + w + " <- " + w); ++ } ++ get query() { ++ if ('query' in this.args.named) { ++ let query = this.named('query'); ++ (!(query !== null && typeof query === 'object') && assert$1('The `@query` argument to the component must be an object.', query !== null && typeof query === 'object')); ++ return { ++ ...query ++ }; ++ } else { ++ return EMPTY_QUERY_PARAMS; + } +- // quick check +- if (v.length === 0) return; +- // shallow check +- for (var i = 0; i < v.length; i++) { +- var key = this[v[i]].key; +- if (key === w) { +- throw new Error("cycle detected: " + w + " <- " + v.key + " <- " + w); +- } ++ } ++ get replace() { ++ return this.named('replace') === true; ++ } ++ get isActive() { ++ return this.isActiveForState(this.routing.currentState); ++ } ++ get willBeActive() { ++ let current = this.routing.currentState; ++ let target = this.routing.targetState; ++ if (current === target) { ++ return null; ++ } else { ++ return this.isActiveForState(target); + } +- // deep check +- this.reset(); +- this.visit(v, w); +- if (this.path.length > 0) { +- var msg_1 = "cycle detected: " + w; +- this.each(this.path, function (key) { +- msg_1 += " <- " + key; +- }); +- throw new Error(msg_1); ++ } ++ get isLoading() { ++ return isMissing(this.route) || this.models.some(model => isMissing(model)); ++ } ++ get isDisabled() { ++ return Boolean(this.named('disabled')); ++ } ++ get isEngine() { ++ let owner = this.owner; ++ return getEngineParent(owner) !== undefined; ++ } ++ get engineMountPoint() { ++ let owner = this.owner; ++ return owner.mountPoint; ++ } ++ classFor(state) { ++ let className = this.named(`${state}Class`); ++ (!(isMissing(className) || typeof className === 'string' || typeof className === 'boolean') && assert$1(`The \`@${state}Class\` argument to the component must be a string or boolean`, isMissing(className) || typeof className === 'string' || typeof className === 'boolean')); ++ if (className === true || isMissing(className)) { ++ return ` ${state}`; ++ } else if (className) { ++ return ` ${className}`; ++ } else { ++ return ''; + } +- }; +- Vertices.prototype.reset = function () { +- this.stack.length = 0; +- this.path.length = 0; +- this.result.length = 0; +- for (var i = 0, l = this.length; i < l; i++) { +- this[i].flag = false; ++ } ++ namespaceRoute(route) { ++ let { ++ engineMountPoint ++ } = this; ++ if (engineMountPoint === undefined) { ++ return route; ++ } else if (route === 'application') { ++ return engineMountPoint; ++ } else { ++ return `${engineMountPoint}.${route}`; + } +- }; +- Vertices.prototype.visit = function (start, search) { +- var _a = this, +- stack = _a.stack, +- path = _a.path, +- result = _a.result; +- stack.push(start.idx); +- while (stack.length) { +- var index = stack.pop() | 0; +- if (index >= 0) { +- // enter +- var vertex = this[index]; +- if (vertex.flag) continue; +- vertex.flag = true; +- path.push(index); +- if (search === vertex.key) break; +- // push exit +- stack.push(~index); +- this.pushIncoming(vertex); ++ } ++ isActiveForState(state) { ++ if (!isPresent$1(state)) { ++ return false; ++ } ++ if (this.isLoading) { ++ return false; ++ } ++ let currentWhen = this.named('current-when'); ++ if (typeof currentWhen === 'boolean') { ++ return currentWhen; ++ } else if (typeof currentWhen === 'string') { ++ let { ++ models, ++ routing ++ } = this; ++ return currentWhen.split(' ').some(route => routing.isActiveForRoute(models, undefined, this.namespaceRoute(route), state)); ++ } else { ++ let { ++ route, ++ models, ++ query, ++ routing ++ } = this; ++ (!(isPresent$1(route)) && assert$1('[BUG] route can only be missing if isLoading is true', isPresent$1(route))); ++ return routing.isActiveForRoute(models, query, route, state); ++ } ++ } ++ preventDefault(event) { ++ event.preventDefault(); ++ } ++ isSupportedArgument(name) { ++ let supportedArguments = ['route', 'model', 'models', 'query', 'replace', 'disabled', 'current-when', 'activeClass', 'loadingClass', 'disabledClass']; ++ return supportedArguments.indexOf(name) !== -1 || super.isSupportedArgument(name); ++ } ++ } ++ let { ++ prototype ++ } = _LinkTo; ++ let descriptorFor = (target, property) => { ++ if (target) { ++ return Object.getOwnPropertyDescriptor(target, property) || descriptorFor(Object.getPrototypeOf(target), property); ++ } else { ++ return null; ++ } ++ }; ++ ++ // @href ++ { ++ let superOnUnsupportedArgument = prototype['onUnsupportedArgument']; ++ Object.defineProperty(prototype, 'onUnsupportedArgument', { ++ configurable: true, ++ enumerable: false, ++ value: function onUnsupportedArgument(name) { ++ if (name === 'href') { ++ (assert$1(`Passing the \`@href\` argument to is not supported.`)); + } else { +- // exit +- path.pop(); +- result.push(~index); ++ superOnUnsupportedArgument.call(this, name); + } + } +- }; +- Vertices.prototype.pushIncoming = function (incomming) { +- var stack = this.stack; +- for (var i = incomming.length - 1; i >= 0; i--) { +- var index = incomming[i]; +- if (!this[index].flag) { +- stack.push(index); ++ }); ++ } ++ ++ // QP ++ { ++ let superModelsDescriptor = descriptorFor(prototype, 'models'); ++ (!(superModelsDescriptor && typeof superModelsDescriptor.get === 'function') && assert$1(`[BUG] expecting models to be a getter on `, superModelsDescriptor && typeof superModelsDescriptor.get === 'function')); ++ let superModelsGetter = superModelsDescriptor.get; ++ Object.defineProperty(prototype, 'models', { ++ configurable: true, ++ enumerable: false, ++ get: function models() { ++ let models = superModelsGetter.call(this); ++ if (models.length > 0 && !('query' in this.args.named)) { ++ if (isQueryParams(models[models.length - 1])) { ++ models = models.slice(0, -1); ++ } + } ++ return models; + } +- }; +- Vertices.prototype.each = function (indices, cb) { +- for (var i = 0, l = indices.length; i < l; i++) { +- var vertex = this[indices[i]]; +- cb(vertex.key, vertex.val); ++ }); ++ let superQueryDescriptor = descriptorFor(prototype, 'query'); ++ (!(superQueryDescriptor && typeof superQueryDescriptor.get === 'function') && assert$1(`[BUG] expecting query to be a getter on `, superQueryDescriptor && typeof superQueryDescriptor.get === 'function')); ++ let superQueryGetter = superQueryDescriptor.get; ++ Object.defineProperty(prototype, 'query', { ++ configurable: true, ++ enumerable: false, ++ get: function query() { ++ if ('query' in this.args.named) { ++ let qp = superQueryGetter.call(this); ++ if (isQueryParams(qp)) { ++ return qp.values ?? EMPTY_QUERY_PARAMS; ++ } else { ++ return qp; ++ } ++ } else { ++ let models = superModelsGetter.call(this); ++ if (models.length > 0) { ++ let qp = models[models.length - 1]; ++ if (isQueryParams(qp) && qp.values !== null) { ++ return qp.values; ++ } ++ } ++ return EMPTY_QUERY_PARAMS; ++ } + } +- }; +- return Vertices; +- }(); +- /** @private */ +- var IntStack = function () { +- function IntStack() { +- this.length = 0; +- } +- IntStack.prototype.push = function (n) { +- this[this.length++] = n | 0; +- }; +- IntStack.prototype.pop = function () { +- return this[--this.length] | 0; +- }; +- return IntStack; +- }(); +- +- const dagMap = /*#__PURE__*/Object.defineProperty({ +- __proto__: null, +- default: DAG +- }, Symbol.toStringTag, { value: 'Module' }); ++ }); ++ } + +- /** +- @module @ember/debug/container-debug-adapter +- */ ++ // Positional Arguments ++ { ++ let superOnUnsupportedArgument = prototype['onUnsupportedArgument']; ++ Object.defineProperty(prototype, 'onUnsupportedArgument', { ++ configurable: true, ++ enumerable: false, ++ value: function onUnsupportedArgument(name) { ++ if (name !== 'params') { ++ superOnUnsupportedArgument.call(this, name); ++ } ++ } ++ }); ++ } ++ const LinkTo = opaquify(_LinkTo, LinkToTemplate); + +- /** +- The `ContainerDebugAdapter` helps the container and resolver interface +- with tools that debug Ember such as the +- [Ember Inspector](https://github.com/emberjs/ember-inspector) +- for Chrome and Firefox. ++ const TextareaTemplate = templateFactory( ++ /* ++ \n```\n\nThe `@value` argument is two-way bound. If the user types text into the textarea, the `@value`\nargument is updated. If the `@value` argument is updated, the text in the textarea is updated.\n\nIn the following example, the `writtenWords` property on the component will be updated as the user\ntypes 'Lots of text' into the text area of their browser's window.\n\n```app/components/word-editor.js\nimport Component from '@glimmer/component';\nimport { tracked } from '@glimmer/tracking';\n\nexport default class WordEditorComponent extends Component {\n @tracked writtenWords = \"Lots of text that IS bound\";\n}\n```\n\n```handlebars\n\n```\n\nIf you wanted a one way binding, you could use the `\n```\n\nThe `@value` argument is two-way bound. If the user types text into the textarea, the `@value`\nargument is updated. If the `@value` argument is updated, the text in the textarea is updated.\n\nIn the following example, the `writtenWords` property on the component will be updated as the user\ntypes 'Lots of text' into the text area of their browser's window.\n\n```app/components/word-editor.js\nimport Component from '@glimmer/component';\nimport { tracked } from '@glimmer/tracking';\n\nexport default class WordEditorComponent extends Component {\n @tracked writtenWords = \"Lots of text that IS bound\";\n}\n```\n\n```handlebars\n\n```\n\nIf you wanted a one way binding, you could use the `