UNPKG

ember-source

Version:

A JavaScript framework for creating ambitious web applications

1,380 lines (1,374 loc) 167 kB
import { registerDestructor, destroy, associateDestroyableChild, _hasDestroyableChildren, isDestroying, isDestroyed, destroyChildren } from '../destroyable/index.js'; import { toBool, warnIfStyleNotTrusted, debugAssert, assertGlobalContextWasSet, setPath, getPath } from '../global-context/index.js'; import { getInternalModifierManager, managerHasCapability, setInternalComponentManager, setInternalModifierManager, getInternalHelperManager, hasInternalComponentManager, hasInternalHelperManager, setInternalHelperManager, hasValue, hasDestroyable } from '../manager/index.js'; import { createConstRef, UNDEFINED_REFERENCE, NULL_REFERENCE, TRUE_REFERENCE, FALSE_REFERENCE, createPrimitiveRef, valueForRef, isConstRef, createComputeRef, childRefFor, createIteratorRef, createIteratorItemRef, updateRef, createDebugAliasRef, isInvokableRef } from '../reference/index.js'; import { isIndexable as isIndexable$1, EMPTY_STRING_ARRAY, dict, enumerate, emptyArray, assign, clearElement, Stack as StackImpl, reverse, isDict } from '../util/index.js'; import { $t0, $t1, $s0, $v0, ContentType, $pc, $ra, $fp, $sp, isLowLevelRegister, InternalComponentCapabilities } from '../vm/index.js'; import { consumeTag, valueForTag, validateTag, CURRENT_TAG, track, updateTag as UPDATE_TAG, createCache, getValue, debug, resetTracking, beginTrackFrame, endTrackFrame, CONSTANT_TAG, INITIAL, createUpdatableTag } from '../validator/index.js'; import { ProgramImpl } from '../program/index.js'; import { getOwner } from '../owner/index.js'; import { isDevelopingApp } from '@embroider/macros'; const NS_MATHML = "http://www.w3.org/1998/Math/MathML", NS_SVG = "http://www.w3.org/2000/svg"; let debugToString; if (isDevelopingApp()) { let getFunctionName = fn => { let functionName = fn.name; if ("" === functionName) { let match = /function (\w+)\s*\(/u.exec(String(fn)); functionName = match && match[1] || ""; } return functionName.replace(/^bound /u, ""); }, getObjectName = obj => { let name, className; // If the class has a decent looking name, and the `toString` is one of the // default Ember toStrings, replace the constructor portion of the toString // with the class name. We check the length of the class name to prevent doing // this when the value is minified. return "function" == typeof obj.constructor && (className = getFunctionName(obj.constructor)), "toString" in obj && obj.toString !== Object.prototype.toString && obj.toString !== Function.prototype.toString && ( // eslint-disable-next-line @typescript-eslint/no-base-to-string name = obj.toString()), name && /<.*:ember\d+>/u.test(name) && className && "_" !== className[0] && className.length > 2 && "Class" !== className ? name.replace(/<.*:/u, `<${className}:`) : name || className; }, getPrimitiveName = value => String(value); debugToString = value => "function" == typeof value ? getFunctionName(value) || "(unknown function)" : "object" == typeof value && null !== value ? getObjectName(value) || "(unknown object)" : getPrimitiveName(value); } var debugToString$1 = debugToString; function castToSimple(node) { return function (node) { node.nodeType; }(node), node; } function unwrapHandle(handle) { if ("number" == typeof handle) return handle; { let error = handle.errors[0]; throw new Error(`Compile Error: ${error.problem} @ ${error.span.start}..${error.span.end}`); } } function unwrapTemplate(template) { if ("error" === template.result) throw new Error(`Compile Error: ${template.problem} @ ${template.span.start}..${template.span.end}`); return template; } /* eslint-disable @typescript-eslint/no-empty-object-type */ function buildUntouchableThis(source) { let context = null; if (isDevelopingApp()) { let assertOnProperty = property => { let access = "symbol" == typeof property || "number" == typeof property ? `[${String(property)}]` : `.${property}`; throw new Error(`You accessed \`this${access}\` from a function passed to the ${source}, but the function itself was not bound to a valid \`this\` context. Consider updating to use a bound function (for instance, use an arrow function, \`() => {}\`).`); }; context = new Proxy({}, { get(_target, property) { assertOnProperty(property); }, set: (_target, property) => (assertOnProperty(property), false), has: (_target, property) => (assertOnProperty(property), false) }); } return context; } function decodeImmediate(num) { return (num |= 0) > -536870913 ? function (num) { return ~num; }(num) : function (num) { return 536870912 | num; }(num); } [1, -1].forEach(x => { return decodeImmediate((num = x, (num |= 0) < 0 ? function (num) { return -536870913 & num; }(num) : function (num) { return ~num; }(num))); var num; }); const APPEND_OPCODES = new class { constructor() { // This code is intentionally putting unsafe `null`s into the array that it // will intentionally overwrite before anyone can see them. // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment this.evaluateOpcode = new Array(113).fill(null); } add(name, evaluate, kind = "syscall") { this.evaluateOpcode[name] = { syscall: "machine" !== kind, evaluate: evaluate }; } evaluate(vm, opcode, type) { let operation = this.evaluateOpcode[type]; operation.syscall ? (opcode.isMachine, operation.syscall, opcode.isMachine, opcode.type, operation.evaluate(vm, opcode)) : (opcode.isMachine, operation.syscall, opcode.isMachine, opcode.type, operation.evaluate(vm.lowlevel, opcode)); } }(), TYPE = Symbol("TYPE"), INNER = Symbol("INNER"), OWNER = Symbol("OWNER"), ARGS = Symbol("ARGS"), RESOLVED = Symbol("RESOLVED"), CURRIED_VALUES = new WeakSet(); function isCurriedValue(value) { return CURRIED_VALUES.has(value); } function isCurriedType(value, type) { return isCurriedValue(value) && value[TYPE] === type; } class CurriedValue { /** @internal */constructor(type, inner, owner, args, resolved = false) { CURRIED_VALUES.add(this), this[TYPE] = type, this[INNER] = inner, this[OWNER] = owner, this[ARGS] = args, this[RESOLVED] = resolved; } } function resolveCurriedValue(curriedValue) { let positional, named, definition, owner, resolved, currentWrapper = curriedValue; for (;;) { let { [ARGS]: curriedArgs, [INNER]: inner } = currentWrapper; if (null !== curriedArgs) { let { named: curriedNamed, positional: curriedPositional } = curriedArgs; curriedPositional.length > 0 && (positional = void 0 === positional ? curriedPositional : curriedPositional.concat(positional)), void 0 === named && (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 { definition: definition, owner: owner, resolved: resolved, positional: positional, named: named }; } function curry(type, spec, owner, args, resolved = false) { return new CurriedValue(type, spec, owner, args, resolved); } class DynamicScopeImpl { constructor(bucket) { this.bucket = bucket ? assign({}, bucket) : {}; } get(key) { return this.bucket[key]; } set(key, reference) { return this.bucket[key] = reference; } child() { return new DynamicScopeImpl(this.bucket); } } class ScopeImpl { static root(owner, { self: self, size = 0 }) { let refs = new Array(size + 1).fill(UNDEFINED_REFERENCE); return new ScopeImpl(owner, refs, null).init({ self: self }); } static sized(owner, size = 0) { let refs = new Array(size + 1).fill(UNDEFINED_REFERENCE); return new ScopeImpl(owner, refs, null); } constructor(owner, // the 0th slot is `self` slots, // a single program can mix owners via curried components, and the state lives on root scopes callerScope) { this.owner = owner, this.slots = slots, this.callerScope = callerScope; } init({ self: self }) { return this.slots[0] = self, this; } /** * @debug */ snapshot() { return this.slots.slice(); } getSelf() { return this.get(0); } getSymbol(symbol) { return this.get(symbol); } getBlock(symbol) { let block = this.get(symbol); return block === UNDEFINED_REFERENCE ? null : block; } 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); } bindCallerScope(scope) { this.callerScope = scope; } getCallerScope() { return this.callerScope; } child() { return new ScopeImpl(this.owner, this.slots.slice(), this.callerScope); } 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; } } class CursorImpl { constructor(element, nextSibling) { this.element = element, this.nextSibling = nextSibling; } } class ConcreteBounds { constructor(parentNode, first, last) { this.parentNode = parentNode, this.first = first, this.last = last; } parentElement() { return this.parentNode; } firstNode() { return this.first; } lastNode() { return this.last; } } function move(bounds, reference) { let parent = bounds.parentElement(), first = bounds.firstNode(), last = bounds.lastNode(), current = first; for (;;) { let next = current.nextSibling; if (parent.insertBefore(current, reference), current === last) return next; current = next; } } function clear(bounds) { let parent = bounds.parentElement(), first = bounds.firstNode(), last = bounds.lastNode(), current = first; for (;;) { let next = current.nextSibling; if (parent.removeChild(current), current === last) return next; current = next; } } /** @internal */ function hasCustomDebugRenderTreeLifecycle(manager) { return "getDebugCustomRenderTree" in manager; } let GUID = 0; class Ref { constructor(value) { this.id = GUID++, this.value = value; } get() { return this.value; } release() { if (isDevelopingApp() && null === this.value) throw new Error("BUG: double release?"); this.value = null; } toString() { let label = `Ref ${this.id}`; if (null === this.value) return `${label} (released)`; try { // eslint-disable-next-line @typescript-eslint/no-base-to-string return `${label}: ${this.value}`; } catch { return label; } } } class DebugRenderTreeImpl { begin() { this.reset(); } create(state, node) { let internalNode = assign({}, node, { bounds: null, refs: new Set() }); this.nodes.set(state, internalNode), this.appendChild(internalNode, state), this.enter(state); } update(state) { this.enter(state); } didRender(state, bounds) { if (isDevelopingApp() && this.stack.current !== state) // eslint-disable-next-line @typescript-eslint/no-base-to-string throw new Error(`BUG: expecting ${this.stack.current}, got ${state}`); this.nodeFor(state).bounds = bounds, this.exit(); } willDestroy(state) { this.refs.get(state).release(); } commit() { this.reset(); } capture() { return this.captureRefs(this.roots); } reset() { if (0 !== this.stack.size) { // We probably encountered an error during the rendering loop. This will // likely trigger undefined behavior and memory leaks as the error left // things in an inconsistent state. It is recommended that the user // refresh the page. // TODO: We could warn here? But this happens all the time in our tests? // Clean up the root reference to prevent errors from happening if we // attempt to capture the render tree (Ember Inspector may do this) let root = this.stack.toArray()[0], ref = this.refs.get(root); for (void 0 !== ref && this.roots.delete(ref); !this.stack.isEmpty();) this.stack.pop(); } } enter(state) { this.stack.push(state); } exit() { if (isDevelopingApp() && 0 === this.stack.size) throw new Error("BUG: unbalanced pop"); this.stack.pop(); } nodeFor(state) { return this.nodes.get(state); } appendChild(node, state) { if (isDevelopingApp() && this.refs.has(state)) throw new Error("BUG: child already appended"); let parent = this.stack.current, ref = new Ref(state); if (this.refs.set(state, ref), parent) { let parentNode = this.nodeFor(parent); parentNode.refs.add(ref), node.parent = parentNode; } else this.roots.add(ref); } captureRefs(refs) { let captured = []; return refs.forEach(ref => { let state = ref.get(); state ? captured.push(this.captureNode(`render-node:${ref.id}`, state)) : refs.delete(ref); }), captured; } captureNode(id, state) { let node = this.nodeFor(state), { type: type, name: name, args: args, instance: instance, refs: refs } = node, template = this.captureTemplate(node), bounds = this.captureBounds(node), children = this.captureRefs(refs); return { id: id, type: type, name: name, args: reifyArgsDebug(args), instance: instance, template: template, bounds: bounds, children: children }; } captureTemplate({ template: template }) { return template || null; } captureBounds(node) { let bounds = node.bounds; return { parentElement: bounds.parentElement(), firstNode: bounds.firstNode(), lastNode: bounds.lastNode() }; } constructor() { this.stack = new StackImpl(), this.refs = new WeakMap(), this.roots = new Set(), this.nodes = new WeakMap(); } } function getDebugName(definition, manager = definition.manager) { return definition.resolvedName ?? definition.debugName ?? manager.getDebugName(definition.state); } function normalizeStringValue(value) { return isEmpty$2(value) ? "" : String(value); } function isEmpty$2(value) { return null == value || "function" != typeof value.toString; } function isIndexable(value) { return null !== value && "object" == typeof value; } function isSafeString(value) { return isIndexable(value) && "function" == typeof value.toHTML; } function isString(value) { return "string" == typeof value; } APPEND_OPCODES.add(39, vm => vm.pushChildScope()), APPEND_OPCODES.add(40, vm => vm.popScope()), APPEND_OPCODES.add(59, vm => vm.pushDynamicScope()), APPEND_OPCODES.add(60, vm => vm.popDynamicScope()), APPEND_OPCODES.add(28, (vm, { op1: other }) => { vm.stack.push(vm.constants.getValue(other)); }), APPEND_OPCODES.add(29, (vm, { op1: other }) => { vm.stack.push(createConstRef(vm.constants.getValue(other), false)); }), APPEND_OPCODES.add(30, (vm, { op1: primitive }) => { let stack = vm.stack; if (primitive >= 0) { // it is a handle which does not already exist on the stack let value = vm.constants.getValue(primitive); stack.push(value); } else // is already an encoded immediate or primitive handle stack.push(decodeImmediate(primitive)); }), APPEND_OPCODES.add(31, vm => { let ref, stack = vm.stack, value = stack.pop(); ref = void 0 === value ? UNDEFINED_REFERENCE : null === value ? NULL_REFERENCE : true === value ? TRUE_REFERENCE : false === value ? FALSE_REFERENCE : createPrimitiveRef(value), stack.push(ref); }), APPEND_OPCODES.add(33, (vm, { op1: register, op2: offset }) => { let position = vm.fetchValue(register) - offset; vm.stack.dup(position); }), APPEND_OPCODES.add(34, (vm, { op1: count }) => { vm.stack.pop(count); }), APPEND_OPCODES.add(35, (vm, { op1: register }) => { vm.load(register); }), APPEND_OPCODES.add(36, (vm, { op1: register }) => { vm.fetch(register); }), APPEND_OPCODES.add(58, (vm, { op1: _names }) => { let names = vm.constants.getArray(_names); vm.bindDynamicScope(names); }), APPEND_OPCODES.add(69, (vm, { op1: args }) => { vm.enter(args); }), APPEND_OPCODES.add(70, vm => { vm.exit(); }), APPEND_OPCODES.add(63, (vm, { op1: _table }) => { vm.stack.push(vm.constants.getValue(_table)); }), APPEND_OPCODES.add(62, vm => { vm.stack.push(vm.scope()); }), APPEND_OPCODES.add(61, vm => { let stack = vm.stack, block = stack.pop(); block ? stack.push(vm.compile(block)) : stack.push(null); }), APPEND_OPCODES.add(64, vm => { let { stack: stack } = vm, handle = stack.pop(), scope = stack.pop(), table = stack.pop(), args = stack.pop(); if (null === table || null === handle) // To balance the pop{Frame,Scope} return vm.lowlevel.pushFrame(), void vm.pushScope(scope ?? vm.scope()); let invokingScope = scope; // If necessary, create a child scope { let locals = table.parameters, localsCount = locals.length; if (localsCount > 0) { invokingScope = invokingScope.child(); for (let i = 0; i < localsCount; i++) invokingScope.bindSymbol(locals[i], args.at(i)); } } vm.lowlevel.pushFrame(), vm.pushScope(invokingScope), vm.call(handle); }), APPEND_OPCODES.add(65, (vm, { op1: target }) => { let reference = vm.stack.pop(), value = Boolean(valueForRef(reference)); isConstRef(reference) ? value && vm.lowlevel.goto(target) : (value && vm.lowlevel.goto(target), vm.updateWith(new Assert(reference))); }), APPEND_OPCODES.add(66, (vm, { op1: target }) => { let reference = vm.stack.pop(), value = Boolean(valueForRef(reference)); isConstRef(reference) ? value || vm.lowlevel.goto(target) : (value || vm.lowlevel.goto(target), vm.updateWith(new Assert(reference))); }), APPEND_OPCODES.add(67, (vm, { op1: target, op2: comparison }) => { vm.stack.peek() === comparison && vm.lowlevel.goto(target); }), APPEND_OPCODES.add(68, vm => { let reference = vm.stack.peek(); isConstRef(reference) || vm.updateWith(new Assert(reference)); }), APPEND_OPCODES.add(71, vm => { let { stack: stack } = vm, valueRef = stack.pop(); stack.push(createComputeRef(() => toBool(valueForRef(valueRef)))); }); class Assert { constructor(ref) { this.ref = ref, this.last = valueForRef(ref); } evaluate(vm) { let { last: last, ref: ref } = this; last !== valueForRef(ref) && vm.throw(); } } class AssertFilter { constructor(ref, filter) { this.ref = ref, this.filter = filter, this.last = filter(valueForRef(ref)); } evaluate(vm) { let { last: last, ref: ref, filter: filter } = this; last !== filter(valueForRef(ref)) && vm.throw(); } } class JumpIfNotModifiedOpcode { finalize(tag, target) { this.target = target, this.didModify(tag); } evaluate(vm) { let { tag: tag, target: target, lastRevision: lastRevision } = this; !vm.alwaysRevalidate && validateTag(tag, lastRevision) && (consumeTag(tag), vm.goto(target)); } didModify(tag) { this.tag = tag, this.lastRevision = valueForTag(this.tag), consumeTag(tag); } constructor() { this.tag = CONSTANT_TAG, this.lastRevision = INITIAL; } } 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(41, (vm, { op1: text }) => { vm.tree().appendText(vm.constants.getValue(text)); }), APPEND_OPCODES.add(42, (vm, { op1: text }) => { vm.tree().appendComment(vm.constants.getValue(text)); }), APPEND_OPCODES.add(48, (vm, { op1: tag }) => { vm.tree().openElement(vm.constants.getValue(tag)); }), APPEND_OPCODES.add(49, vm => { let tagName = valueForRef(vm.stack.pop()); vm.tree().openElement(tagName); }), APPEND_OPCODES.add(50, vm => { let elementRef = vm.stack.pop(), insertBeforeRef = vm.stack.pop(), guidRef = vm.stack.pop(), element = valueForRef(elementRef), insertBefore = valueForRef(insertBeforeRef), guid = valueForRef(guidRef); isConstRef(elementRef) || vm.updateWith(new Assert(elementRef)), void 0 === insertBefore || isConstRef(insertBeforeRef) || vm.updateWith(new Assert(insertBeforeRef)); let block = vm.tree().pushRemoteElement(element, guid, insertBefore); if (vm.associateDestroyable(block), void 0 !== vm.env.debugRenderTree) { // 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(void 0 === insertBefore ? {} : { insertBefore: insertBeforeRef }, [elementRef]); vm.env.debugRenderTree.create(block, { type: "keyword", name: "in-element", args: args, instance: null }), registerDestructor(block, () => { vm.env.debugRenderTree?.willDestroy(block); }); } }), APPEND_OPCODES.add(56, vm => { let bounds = vm.tree().popRemoteElement(); void 0 !== vm.env.debugRenderTree && // The RemoteBlock is also its bounds vm.env.debugRenderTree.didRender(bounds, bounds); }), APPEND_OPCODES.add(54, vm => { let operations = vm.fetchValue($t0), modifiers = null; operations && (modifiers = operations.flush(vm), vm.loadValue($t0, null)), vm.tree().flushElement(modifiers); }), APPEND_OPCODES.add(55, vm => { let modifiers = vm.tree().closeElement(); null !== modifiers && modifiers.forEach(modifier => { vm.env.scheduleInstallModifier(modifier); const d = modifier.manager.getDestroyable(modifier.state); null !== d && vm.associateDestroyable(d); }); }), APPEND_OPCODES.add(57, (vm, { op1: handle }) => { if (!vm.env.isInteractive) return; let owner = vm.getOwner(), args = vm.stack.pop(), definition = vm.constants.getValue(handle), { manager: manager } = definition, { constructing: constructing } = vm.tree(), capturedArgs = args.capture(), state = manager.create(owner, constructing, definition.state, capturedArgs), instance = { manager: manager, state: state, definition: definition }; vm.fetchValue($t0).addModifier(vm, instance, capturedArgs); let tag = manager.getTag(state); return null !== tag ? (consumeTag(tag), vm.updateWith(new UpdateModifierOpcode(tag, instance))) : void 0; }), APPEND_OPCODES.add(108, vm => { if (!vm.env.isInteractive) return; let { stack: stack } = vm, ref = stack.pop(), args = stack.pop().capture(), { positional: outerPositional, named: outerNamed } = args, { constructing: constructing } = vm.tree(), initialOwner = vm.getOwner(), instanceRef = createComputeRef(() => { let owner, hostDefinition, value = valueForRef(ref); if (!isIndexable$1(value)) return; if (isCurriedType(value, 2)) { let { definition: resolvedDefinition, owner: curriedOwner, positional: positional, named: named } = resolveCurriedValue(value); hostDefinition = resolvedDefinition, owner = curriedOwner, void 0 !== positional && (args.positional = positional.concat(outerPositional)), void 0 !== named && ( // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment args.named = Object.assign({}, ...named, outerNamed)); } else hostDefinition = value, owner = initialOwner; let manager = getInternalModifierManager(hostDefinition, true); if (null === manager) throw isDevelopingApp() ? 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)}`) : new Error("BUG: modifier manager expected"); let definition = { resolvedName: null, manager: manager, state: hostDefinition }, state = manager.create(owner, constructing, definition.state, args); return { manager: manager, state: state, definition: definition }; }), instance = valueForRef(instanceRef), tag = null; return void 0 !== instance && (vm.fetchValue($t0).addModifier(vm, instance, args), tag = instance.manager.getTag(instance.state), null !== tag && consumeTag(tag)), !isConstRef(ref) || tag ? vm.updateWith(new UpdateDynamicModifierOpcode(tag, instance, instanceRef)) : void 0; }); class UpdateModifierOpcode { constructor(tag, modifier) { this.tag = tag, this.modifier = modifier, this.lastUpdated = valueForTag(tag); } evaluate(vm) { let { modifier: modifier, tag: tag, lastUpdated: lastUpdated } = this; consumeTag(tag), validateTag(tag, lastUpdated) || (vm.env.scheduleUpdateModifier(modifier), this.lastUpdated = valueForTag(tag)); } } class UpdateDynamicModifierOpcode { constructor(tag, instance, instanceRef) { this.tag = tag, this.instance = instance, this.instanceRef = instanceRef, this.lastUpdated = valueForTag(tag ?? CURRENT_TAG); } evaluate(vm) { let { tag: tag, lastUpdated: lastUpdated, instance: instance, instanceRef: instanceRef } = this, newInstance = valueForRef(instanceRef); if (newInstance !== instance) { if (void 0 !== instance) { let destroyable = instance.manager.getDestroyable(instance.state); null !== destroyable && destroy(destroyable); } if (void 0 !== newInstance) { let { manager: manager, state: state } = newInstance, destroyable = manager.getDestroyable(state); null !== destroyable && associateDestroyableChild(this, destroyable), tag = manager.getTag(state), null !== tag && (this.lastUpdated = valueForTag(tag)), this.tag = tag, vm.env.scheduleInstallModifier(newInstance); } this.instance = newInstance; } else null === tag || validateTag(tag, lastUpdated) || ( // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- @fixme vm.env.scheduleUpdateModifier(instance), this.lastUpdated = valueForTag(tag)); null !== tag && consumeTag(tag); } } APPEND_OPCODES.add(51, (vm, { op1: _name, op2: _value, op3: _namespace }) => { let name = vm.constants.getValue(_name), value = vm.constants.getValue(_value), namespace = _namespace ? vm.constants.getValue(_namespace) : null; vm.tree().setStaticAttribute(name, value, namespace); }), APPEND_OPCODES.add(52, (vm, { op1: _name, op2: _trusting, op3: _namespace }) => { let name = vm.constants.getValue(_name), trusting = vm.constants.getValue(_trusting), reference = vm.stack.pop(), value = valueForRef(reference), namespace = _namespace ? vm.constants.getValue(_namespace) : null, attribute = vm.tree().setDynamicAttribute(name, value, trusting, namespace); isConstRef(reference) || vm.updateWith(new UpdateDynamicAttributeOpcode(reference, attribute, vm.env)); }); class UpdateDynamicAttributeOpcode { constructor(reference, attribute, env) { let initialized = false; this.updateRef = createComputeRef(() => { let value = valueForRef(reference); initialized ? attribute.update(value, env) : initialized = true; }), valueForRef(this.updateRef); } evaluate() { valueForRef(this.updateRef); } } APPEND_OPCODES.add(78, (vm, { op1: handle }) => { let definition = vm.constants.getValue(handle), { manager: manager, capabilities: capabilities } = definition, instance = { definition: definition, manager: manager, capabilities: capabilities, state: null, handle: null, table: null, lookup: null }; vm.stack.push(instance); }), APPEND_OPCODES.add(80, (vm, { op1: _isStrict }) => { let definition, stack = vm.stack, component = valueForRef(stack.pop()), constants = vm.constants, owner = vm.getOwner(), isStrict = constants.getValue(_isStrict); if (vm.loadValue($t1, null), "string" == typeof component) { if (isDevelopingApp() && 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 = function (resolver, constants, name, owner) { let definition = resolver?.lookupComponent?.(name, owner) ?? null; if (isDevelopingApp() && !definition) throw new Error(`Attempted to resolve \`${name}\`, which was expected to be a component, but nothing was found.`); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- @fixme return constants.resolvedComponent(definition, name); }(vm.context.resolver, constants, component, owner); definition = resolvedDefinition; } else definition = isCurriedValue(component) ? component : constants.component(component, owner); stack.push(definition); }), APPEND_OPCODES.add(81, vm => { let definition, stack = vm.stack, ref = stack.pop(), value = valueForRef(ref), constants = vm.constants; if (isDevelopingApp() && "function" != typeof value && ("object" != typeof value || null === value)) 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 if (definition = constants.component(value, vm.getOwner(), true), isDevelopingApp() && null === definition) 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) ?? value}`); stack.push(definition); }), APPEND_OPCODES.add(79, vm => { let capabilities, manager, { stack: stack } = vm, definition = stack.pop(); isCurriedValue(definition) ? manager = capabilities = null : (manager = definition.manager, capabilities = definition.capabilities), stack.push({ definition: definition, capabilities: capabilities, manager: manager, state: null, handle: null, table: null }); }), APPEND_OPCODES.add(82, (vm, { op1: _names, op2: _blockNames, op3: flags }) => { let stack = vm.stack, names = vm.constants.getArray(_names), positionalCount = flags >> 4, atNames = 8 & flags, blockNames = 7 & flags ? vm.constants.getArray(_blockNames) : EMPTY_STRING_ARRAY; vm.args.setup(stack, names, blockNames, positionalCount, !!atNames), stack.push(vm.args); }), APPEND_OPCODES.add(83, vm => { let { stack: stack } = vm; stack.push(vm.args.empty(stack)); }), APPEND_OPCODES.add(86, vm => { let stack = vm.stack, capturedArgs = stack.pop().capture(); stack.push(capturedArgs); }), APPEND_OPCODES.add(85, (vm, { op1: register }) => { let stack = vm.stack, instance = vm.fetchValue(register), args = stack.pop(), { definition: definition } = instance; if (isCurriedType(definition, 0)) { definition.manager; let constants = vm.constants, { definition: resolvedDefinition, owner: owner, resolved: resolved, positional: positional, named: named } = resolveCurriedValue(definition); if (resolved) definition = resolvedDefinition;else if ("string" == typeof resolvedDefinition) { let resolvedValue = vm.context.resolver?.lookupComponent?.(resolvedDefinition, owner) ?? null; definition = constants.resolvedComponent(resolvedValue, resolvedDefinition); } else definition = constants.component(resolvedDefinition, owner); void 0 !== named && // eslint-disable-next-line @typescript-eslint/no-unsafe-argument args.named.merge(assign({}, ...named)), void 0 !== positional && (args.realloc(positional.length), args.positional.prepend(positional)); let { manager: manager } = definition; instance.definition = definition, instance.manager = manager, instance.capabilities = definition.capabilities, // Save off the owner that this component was curried with. Later on, // we'll fetch the value of this register and set it as the owner on the // new root scope. vm.loadValue($t1, owner); } let { manager: manager, state: state } = definition, capabilities = instance.capabilities; if (!managerHasCapability(manager, capabilities, InternalComponentCapabilities.prepareArgs)) return void stack.push(args); let blocks = args.blocks.values, blockNames = args.blocks.names, preparedArgs = manager.prepareArgs(state, args); if (preparedArgs) { args.clear(); for (let i = 0; i < blocks.length; i++) stack.push(blocks[i]); let { positional: positional, named: named } = preparedArgs, positionalCount = positional.length; for (let i = 0; i < positionalCount; i++) stack.push(positional[i]); let names = Object.keys(named); for (let i = 0; i < names.length; i++) stack.push(named[names[i]]); args.setup(stack, names, blockNames, positionalCount, false); } stack.push(args); }), APPEND_OPCODES.add(87, (vm, { op1: flags }) => { let instance = vm.fetchValue($s0), { definition: definition, manager: manager, capabilities: capabilities } = instance; if (!managerHasCapability(manager, capabilities, InternalComponentCapabilities.createInstance)) // TODO: Closure and Main components are always invoked dynamically, so this // opcode may run even if this capability is not enabled. In the future we // should handle this in a better way. return; let dynamicScope = null; managerHasCapability(manager, capabilities, InternalComponentCapabilities.dynamicScope) && (dynamicScope = vm.dynamicScope()); let hasDefaultBlock = 1 & flags, args = null; managerHasCapability(manager, capabilities, InternalComponentCapabilities.createArgs) && (args = vm.stack.peek()); let self = null; managerHasCapability(manager, capabilities, InternalComponentCapabilities.createCaller) && (self = vm.getSelf()); let state = manager.create(vm.getOwner(), definition.state, args, vm.env, dynamicScope, self, !!hasDefaultBlock); // We want to reuse the `state` POJO here, because we know that the opcodes // only transition at exactly one place. instance.state = state, managerHasCapability(manager, capabilities, InternalComponentCapabilities.updateHook) && vm.updateWith(new UpdateComponentOpcode(state, manager, dynamicScope)); }), APPEND_OPCODES.add(88, (vm, { op1: register }) => { let { manager: manager, state: state, capabilities: capabilities } = vm.fetchValue(register), d = manager.getDestroyable(state); if (isDevelopingApp() && !managerHasCapability(manager, capabilities, InternalComponentCapabilities.willDestroy) && null !== d && "string" in d) throw new Error("BUG: Destructor has willDestroy, but the willDestroy capability was not enabled for this component. Pre-destruction hooks must be explicitly opted into"); d && vm.associateDestroyable(d); }), APPEND_OPCODES.add(97, (vm, { op1: register }) => { let name; if (isDevelopingApp()) { let { definition: definition, manager: manager } = vm.fetchValue(register); name = getDebugName(definition, manager); } vm.beginCacheGroup(name), vm.tree().pushAppendingBlock(); }), APPEND_OPCODES.add(89, vm => { vm.loadValue($t0, new ComponentElementOperations()); }), APPEND_OPCODES.add(53, (vm, { op1: _name, op2: _trusting, op3: _namespace }) => { let name = vm.constants.getValue(_name), trusting = vm.constants.getValue(_trusting), reference = vm.stack.pop(), namespace = _namespace ? vm.constants.getValue(_namespace) : null; vm.fetchValue($t0).setAttribute(name, reference, trusting, namespace); }), APPEND_OPCODES.add(105, (vm, { op1: _name, op2: _value, op3: _namespace }) => { let name = vm.constants.getValue(_name), value = vm.constants.getValue(_value), namespace = _namespace ? vm.constants.getValue(_namespace) : null; vm.fetchValue($t0).setStaticAttribute(name, value, namespace); }); class ComponentElementOperations { setAttribute(name, value, trusting, namespace) { let deferred = { value: value, namespace: namespace, trusting: trusting }; "class" === name && this.classes.push(value), this.attributes[name] = deferred; } setStaticAttribute(name, value, namespace) { let deferred = { value: value, namespace: namespace }; "class" === name && this.classes.push(value), this.attributes[name] = deferred; } addModifier(vm, modifier, capturedArgs) { if (this.modifiers.push(modifier), void 0 !== vm.env.debugRenderTree) { const { manager: manager, definition: definition, state: state } = modifier; // TODO: we need a stable object for the debugRenderTree as the key, add support for // the case where the state is a primitive, or if in practice we always have/require // an object, then change the internal types to reflect that if (null === state || "object" != typeof state && "function" != typeof state) return; let { element: element, constructing: constructing } = vm.tree(), name = definition.resolvedName ?? manager.getDebugName(definition.state), instance = manager.getDebugInstance(state), bounds = new ConcreteBounds(element, constructing, constructing); vm.env.debugRenderTree.create(state, { type: "modifier", name: name, args: capturedArgs, instance: instance }), vm.env.debugRenderTree.didRender(state, bounds), // For tearing down the debugRenderTree vm.associateDestroyable(state), vm.updateWith(new DebugRenderTreeUpdateOpcode(state)), vm.updateWith(new DebugRenderTreeDidRenderOpcode(state, bounds)), registerDestructor(state, () => { vm.env.debugRenderTree?.willDestroy(state); }); } } flush(vm) { let type, attributes = this.attributes; for (let name in this.attributes) { if ("type" === name) { type = attributes[name]; continue; } let attr = this.attributes[name]; "class" === name ? setDeferredAttr(vm, "class", mergeClasses(this.classes), attr.namespace, attr.trusting) : setDeferredAttr(vm, name, attr.value, attr.namespace, attr.trusting); } return void 0 !== type && setDeferredAttr(vm, "type", type.value, type.namespace, type.trusting), this.modifiers; } constructor() { this.attributes = dict(), this.classes = [], this.modifiers = []; } } function mergeClasses(classes) { return 0 === classes.length ? "" : 1 === classes.length ? classes[0] : function (classes) { return classes.every(c => "string" == typeof c); }(classes) ? classes.join(" ") : (list = classes, createComputeRef(() => { let ret = []; for (const ref of list) { let value = normalizeStringValue("string" == typeof ref ? ref : valueForRef(ref)); value && ret.push(value); } return 0 === ret.length ? null : ret.join(" "); })); var list; } function setDeferredAttr(vm, name, value, namespace, trusting = false) { if ("string" == typeof value) vm.tree().setStaticAttribute(name, value, namespace);else { let attribute = vm.tree().setDynamicAttribute(name, valueForRef(value), trusting, namespace); isConstRef(value) || vm.updateWith(new UpdateDynamicAttributeOpcode(value, attribute, vm.env)); } } function bindBlock(symbolName, blockName, state, blocks, vm) { let symbol = state.table.symbols.indexOf(symbolName), block = blocks.get(blockName); -1 !== symbol && vm.scope().bindBlock(symbol + 1, block), state.lookup && (state.lookup[symbolName] = block); } APPEND_OPCODES.add(99, (vm, { op1: register }) => { let { definition: definition, state: state } = vm.fetchValue(register), { manager: manager } = definition, operations = vm.fetchValue($t0); manager.didCreateElement(state, vm.tree().constructing, operations); }), APPEND_OPCODES.add(90, (vm, { op1: register, op2: _names }) => { let instance = vm.fetchValue(register), { definition: definition, state: state } = instance, { manager: manager } = definition, selfRef = manager.getSelf(state); if (void 0 !== vm.env.debugRenderTree) { let args, moduleName, instance = vm.fetchValue(register), { definition: definition, manager: manager } = instance; if (vm.stack.peek() === vm.args) args = vm.args.capture();else { let names = vm.constants.getArray(_names); vm.args.setup(vm.stack, names, [], 0, true), args = vm.args.capture(); } let compilable = definition.compilable; if (null === compilable) { managerHasCapability(manager, instance.capabilities, InternalComponentCapabilities.dynamicLayout); let resolver = vm.context.resolver; compilable = null === resolver ? null : manager.getDynamicLayout(state, resolver), moduleName = null !== compilable ? compilable.moduleName : "__default__.hbs"; } else moduleName = compilable.moduleName; // For tearing down the debugRenderTree if (vm.associateDestroyable(instance), hasCustomDebugRenderTreeLifecycle(manager)) manager.getDebugCustomRenderTree(instance.definition.state, instance.state, args, moduleName).forEach(node => { let { bucket: bucket } = node; // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- @fixme vm.env.debugRenderTree.create(bucket, node), registerDestructor(instance, () => { vm.env.debugRenderTree?.willDestroy(bucket); }), vm.updateWith(new DebugRenderTreeUpdateOpcode(bucket)); });else { let name = getDebugName(definition, manager); vm.env.debugRenderTree.create(instance, { type: "component", name: name, args: args, template: moduleName, instance: valueForRef(selfRef) }), registerDestructor(instance, () => { vm.env.debugRenderTree?.willDestroy(instance); }), vm.updateWith(new DebugRenderTreeUpdateOpcode(instance)); } } vm.stack.push(selfRef); }), APPEND_OPCODES.add(91, (vm, { op1: register }) => { let { definition: definition, state: state } = vm.fetchValue(register), { manager: manager } = definition, tagName = manager.getTagName(state); // User provided value from JS, so we don't bother to encode vm.stack.push(tagName); }), // Dynamic Invocation Only APPEND_OPCODES.add(92, (vm, { op1: register }) => { let instance = vm.fetchValue(register), { manager: manager, definition: definition } = instance, { stack: stack } = vm, { compilable: compilable } = definition; if (null === compilable) { let { capabilities: capabilities } = instance; managerHasCapability(manager, capabilities, InternalComponentCapabilities.dynamicLayout); let resolver = vm.context.resolver; compilable = null === resolver ? null : manager.getDynamicLayout(instance.state, resolver), null === compilable && (compilable = managerHasCapability(manager, capabilities, InternalComponentCapabilities.wrapped) ? unwrapTemplate(vm.constants.defaultTemplate).asWrappedLayout() : unwrapTemplate(vm.constants.defaultTemplate).asLayout()); } let handle = compilable.compile(vm.context); stack.push(compilable.symbolTable), stack.push(handle); }), APPEND_OPCODES.add(75, (vm, { op1: register }) => { let definition = vm.stack.pop(), invocation = vm.stack.pop(), { manager: manager, capabilities: capabilities } = definition, state = { definition: definition, manager: manager, capabilities: capabilities, state: null, handle: invocation.handle, table: invocation.symbolTable, lookup: null }; vm.loadValue(register, state); }), APPEND_OPCODES.add(95, (vm, { op1: register }) => { let { stack: stack } = vm, handle = stack.pop(), table = stack.pop(), state = vm.fetchValue(register); // In DEBUG handles could be ErrHandle objects state.handle = handle, state.table = table; }), APPEND_OPCODES.add(38, (vm, { op1: register }) => { let owner, { table: table, manager: manager, capabilities: capabilities, state: state } = vm.fetchValue(register); managerHasCapability(manager, capabilities, InternalComponentCapabilities.hasSubOwner) ? (owner = manager.getOwner(state), vm.loadValue($t1, null)) : ( // Check the temp register to see if an owner was resolved from currying owner = vm.fetchValue($t1), null === owner ? // If an owner wasn't found, default to using the current owner. This // will happen for normal dynamic component invocation, // e.g. <SomeClassicEmberComponent/> owner = vm.getOwner() : // Else the owner was found, so clear the temp register. This will happen // if we are loading a curried component, e.g. <@someCurriedComponent/> vm.loadValue($t1, null)), vm.pushRootScope(table.symbols.length + 1, owner); }), APPEND_OPCODES.add(17, (vm, { op1: register }) => { let state = vm.fetchValue(register), scope = vm.scope(), args = vm.stack.peek(), callerNames = args.named.atNames; for (let i = callerNames.length - 1; i >= 0; i--) { let atName = callerNames[i], symbol = state.table.symbols.indexOf(atName), value = args.named.get(atName, true); -1 !== symbol && scope.bindSymbol(symbol + 1, value), state.lookup && (state.lookup[atName] = value); } }), APPEND_OPCODES.add(18, (vm, { op1: register }) => { let state = vm.fetchValue(register), { blocks: blocks } = vm.stack.peek(); for (const [i] of enumerate(blocks.names)) bindBlock(blocks.symbolNames[i], blocks.names[i], state, blocks, vm); }), // Dynamic Invocation Only APPEND_OPCODES.add(96, (vm, { op1: register }) => { let state = vm.fetchValue(register); vm.call(state.handle); }), APPEND_OPCODES.add(100, (vm, { op1: register }) => { let instance = vm.fetchValue(register), { manager: manager, state: state, capabilities: capabilities } = instance, bounds = vm.tree().popBlock(); void 0 !== vm.env.debugRenderTree && (hasCustomDebugRenderTreeLifecycle(manager) ? manager.getDebugCustomRenderTree(instance.definition.state, state, EMPTY_ARGS).reverse().forEach(node => { let { bucket: bucket } = node; // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- @fixme vm.env.debugRenderTree.didRender(bucket, bounds), vm.updateWith(new DebugRenderTreeDidRenderOpcode(bucket, bounds)); }) : (vm.env.debugRenderTree.didRender(instance, bounds), vm.updateWith(new DebugRenderTreeDidRenderOpcode(instance, bounds)))), managerHasCapability(manager, capabilities, InternalComponentCapabilities.createInstance) && ( // eslint-disable-next-line @typescript-eslint/no-unsafe-call -- @fixme manager.didRenderLayout(state, bounds), vm.env.didCreate(instance), vm.updateWith(new DidUpdateLayoutOpcode(instance, bounds))); }), APPEND_OPCODES.add(98, vm => { vm.commitCacheGroup(); }); class UpdateComponentOpcode { constructor(component, manager, dynamicScope) { this.component = component, this.manager = manager, this.dynamicScope = dynamicScope; } evaluate(_vm) { let { component: component, manager: manager, dynamicScope: dynamicScope } = this; manager.update(component, dynamicScope); } } class DidUpdateLayoutOpcode { constructor(component, bounds) { this.component = component, this.bounds = bounds; } evaluate(vm) { let { component: component, bounds: bounds } = this, { manager: manager, state: state } = component; manager.didUpdateLayout(state, bounds), vm.env.didUpdate(component); } } class DebugRenderTreeUpdateOpcode { constructor(bucket) { this.bucket = bucket; } evaluate(vm) { vm.env.debugRenderTree?.update(this.bucket); } } class DebugRenderTreeDidRenderOpcode { constructor(bucket, bounds) { this.bucket = bucket, this.bounds = bounds; } evaluate(vm) { vm.env.debugRenderTree?.didRender(this.bucket, this.bounds); } } /* The calling convention is: * 0-N block arguments at the bottom * 0-N positional arguments next (left-to-right) * 0-N named arguments next */ class VMArgumentsImpl {