UNPKG

ember-source

Version:

A JavaScript framework for creating ambitious web applications

1,603 lines (1,571 loc) 116 kB
import { r as VM_SYSCALL_SIZE, C as CURRIED_COMPONENT, s as VM_CHILD_SCOPE_OP, t as VM_POP_SCOPE_OP, u as VM_PUSH_DYNAMIC_SCOPE_OP, v as VM_POP_DYNAMIC_SCOPE_OP, w as VM_CONSTANT_OP, x as decodeHandle, y as VM_CONSTANT_REFERENCE_OP, z as VM_PRIMITIVE_OP, A as isHandle, B as decodeImmediate, D as VM_PRIMITIVE_REFERENCE_OP, E as VM_DUP_OP, F as VM_POP_OP, G as VM_LOAD_OP, H as VM_FETCH_OP, I as VM_BIND_DYNAMIC_SCOPE_OP, J as VM_ENTER_OP, K as VM_EXIT_OP, L as VM_PUSH_SYMBOL_TABLE_OP, M as VM_PUSH_BLOCK_SCOPE_OP, N as VM_COMPILE_BLOCK_OP, O as VM_INVOKE_YIELD_OP, P as VM_JUMP_IF_OP, Q as VM_JUMP_UNLESS_OP, R as VM_JUMP_EQ_OP, b as VM_ASSERT_SAME_OP, S as VM_TO_BOOLEAN_OP, T as VM_TEXT_OP, U as VM_COMMENT_OP, W as VM_OPEN_ELEMENT_OP, X as VM_OPEN_DYNAMIC_ELEMENT_OP, Y as VM_PUSH_REMOTE_ELEMENT_OP, Z as VM_POP_REMOTE_ELEMENT_OP, _ as VM_FLUSH_ELEMENT_OP, $ as VM_CLOSE_ELEMENT_OP, a0 as VM_MODIFIER_OP, a1 as VM_DYNAMIC_MODIFIER_OP, a2 as VM_STATIC_ATTR_OP, a3 as VM_DYNAMIC_ATTR_OP, a4 as CURRIED_MODIFIER, a5 as VM_PUSH_COMPONENT_DEFINITION_OP, a6 as VM_RESOLVE_DYNAMIC_COMPONENT_OP, f as VM_RESOLVE_CURRIED_COMPONENT_OP, g as VM_PUSH_DYNAMIC_COMPONENT_INSTANCE_OP, a7 as VM_PUSH_ARGS_OP, a8 as VM_PUSH_EMPTY_ARGS_OP, a9 as VM_CAPTURE_ARGS_OP, aa as VM_PREPARE_ARGS_OP, ab as VM_CREATE_COMPONENT_OP, ac as VM_REGISTER_COMPONENT_DESTRUCTOR_OP, ad as VM_BEGIN_COMPONENT_TRANSACTION_OP, ae as VM_PUT_COMPONENT_OPERATIONS_OP, af as VM_COMPONENT_ATTR_OP, ag as VM_STATIC_COMPONENT_ATTR_OP, ah as VM_DID_CREATE_ELEMENT_OP, ai as VM_GET_COMPONENT_SELF_OP, aj as VM_GET_COMPONENT_TAG_NAME_OP, ak as VM_GET_COMPONENT_LAYOUT_OP, V as VM_MAIN_OP, al as VM_POPULATE_LAYOUT_OP, am as VM_VIRTUAL_ROOT_SCOPE_OP, an as VM_SET_NAMED_VARIABLES_OP, ao as VM_SET_BLOCKS_OP, ap as VM_INVOKE_COMPONENT_LAYOUT_OP, aq as VM_DID_RENDER_LAYOUT_OP, ar as VM_COMMIT_COMPONENT_TRANSACTION_OP, as as VM_CURRY_OP, at as VM_DYNAMIC_HELPER_OP, au as CURRIED_HELPER, av as VM_HELPER_OP, aw as VM_GET_VARIABLE_OP, ax as VM_SET_VARIABLE_OP, ay as VM_SET_BLOCK_OP, az as VM_ROOT_SCOPE_OP, aA as VM_GET_PROPERTY_OP, aB as VM_GET_BLOCK_OP, aC as VM_SPREAD_BLOCK_OP, aD as VM_HAS_BLOCK_OP, aE as VM_HAS_BLOCK_PARAMS_OP, aF as VM_CONCAT_OP, aG as VM_IF_INLINE_OP, aH as VM_NOT_OP, aI as VM_GET_DYNAMIC_VAR_OP, aJ as VM_LOG_OP, a as VM_CONTENT_TYPE_OP, aK as VM_DYNAMIC_CONTENT_TYPE_OP, d as VM_APPEND_HTML_OP, h as VM_APPEND_SAFE_HTML_OP, e as VM_APPEND_TEXT_OP, i as VM_APPEND_DOCUMENT_FRAGMENT_OP, j as VM_APPEND_NODE_OP, aL as VM_DEBUGGER_OP, aM as VM_ENTER_LIST_OP, aN as VM_EXIT_LIST_OP, aO as VM_ITERATE_OP } from './fragment-Cc5k9Oy4.js'; import { d as debugToString } from './debug-to-string-CFb7h0lY.js'; import { u as unwrap, e as expect, a as isIndexable$1, S as StackImpl, d as dict, i as isDict } from './collections-D_nY_0UJ.js'; import { a as assign } from './object-utils-AijlD-JH.js'; import { toBool, debugAssert, setPath, getPath, warnIfStyleNotTrusted } from '../@glimmer/global-context/index.js'; import { CONSTANT_TAG, INITIAL, validateTag, consumeTag, valueForTag, beginTrackFrame, endTrackFrame, CURRENT_TAG, createUpdatableTag } from '../@glimmer/validator/index.js'; import { c as createComputeRef, v as valueForRef, a as createConstRef, d as createPrimitiveRef, i as isConstRef, U as UNDEFINED_REFERENCE, N as NULL_REFERENCE, T as TRUE_REFERENCE, F as FALSE_REFERENCE, R as REFERENCE, e as createDebugAliasRef, b as childRefFor, f as isInvokableRef, u as updateRef } from './reference-C3TKDRnP.js'; import { e as $v0, f as $t1, g as $t0, a as $pc, b as $ra, c as $fp, d as $sp, h as $s1, $ as $s0 } from './registers-ylirb0dq.js'; import { registerDestructor, destroy, associateDestroyableChild, _hasDestroyableChildren } from '../@glimmer/destroyable/index.js'; import { d as getInternalModifierManager, g as getInternalHelperManager, b as hasInternalComponentManager, f as hasInternalHelperManager, s as setInternalComponentManager, i as setInternalHelperManager, j as setInternalModifierManager } from './api-BqXkkT0p.js'; import { m as managerHasCapability } from './capabilities-O_xc7Yqk.js'; import { ContentType } from '../@glimmer/vm/index.js'; import { createIteratorRef } from '../@glimmer/reference/index.js'; import { s as setLocalDebugType } from './debug-brand-B1TWjOCH.js'; import { b as EMPTY_STRING_ARRAY, e as enumerate, c as emptyArray } from './array-utils-CZQxrdD3.js'; import { a as assert } from './assert-CUCJBR2C.js'; import { a as unwrapTemplate } from './constants-eoaL3OJQ.js'; import { I as InternalComponentCapabilities } from './flags-B9qxc-pB.js'; import { a as castToBrowser } from './simple-cast-DCvJLSin.js'; /* eslint-disable @typescript-eslint/no-empty-object-type */ function buildUntouchableThis(source) { let context = null; { let assertOnProperty = property => { let access = typeof property === 'symbol' || typeof property === 'number' ? `[${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); return false; }, has(_target, property) { assertOnProperty(property); return false; } }); } return context; } const ELEMENT_NODE = 1; const TEXT_NODE = 3; const COMMENT_NODE = 8; const NS_MATHML = 'http://www.w3.org/1998/Math/MathML'; const NS_SVG = 'http://www.w3.org/2000/svg'; const INSERT_BEFORE_BEGIN = 'beforebegin'; const INSERT_BEFORE_END = 'beforeend'; function CheckInstanceof(Class) { } const CheckRegister = new class { validate(value) { switch (value) { case $s0: case $s1: case $sp: case $fp: case $ra: case $pc: case $t0: case $t1: case $v0: return true; default: return false; } } expected() { return `Register`; } }(); /*@__NO_SIDE_EFFECTS__*/ function check(value, checker, message) { { return value; } } class AppendOpcodes { // 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 evaluateOpcode = new Array(VM_SYSCALL_SIZE).fill(null); constructor() { } add(name, evaluate, kind = 'syscall') { this.evaluateOpcode[name] = { syscall: kind !== 'machine', evaluate }; } evaluate(vm, opcode, type) { let operation = unwrap(this.evaluateOpcode[type]); if (operation.syscall) { assert(!opcode.isMachine, `BUG: Mismatch between operation.syscall (${operation.syscall}) and opcode.isMachine (${opcode.isMachine}) for ${opcode.type}`); operation.evaluate(vm, opcode); } else { assert(opcode.isMachine, `BUG: Mismatch between operation.syscall (${operation.syscall}) and opcode.isMachine (${opcode.isMachine}) for ${opcode.type}`); operation.evaluate(vm.lowlevel, opcode); } } } function externs(vm) { return undefined; } const APPEND_OPCODES = new AppendOpcodes(); const TYPE = Symbol('TYPE'); const INNER = Symbol('INNER'); const OWNER = Symbol('OWNER'); const ARGS = 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]; [RESOLVED]; /** @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 currentWrapper = curriedValue; let positional; let named; let definition, owner, resolved; while (true) { let { [ARGS]: 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 { definition, owner, resolved, positional, named }; } 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 === CURRIED_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).lookupComponent?.(value, owner) ?? null; 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 (isIndexable$1(value)) { curriedDefinition = curry(type, value, owner, args); } else { curriedDefinition = null; } lastValue = value; return curriedDefinition; }); } class CursorImpl { constructor(element, nextSibling) { this.element = element; this.nextSibling = nextSibling; setLocalDebugType('cursor', this); } } 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(); let first = bounds.firstNode(); let last = bounds.lastNode(); let current = first; while (true) { let next = current.nextSibling; parent.insertBefore(current, reference); if (current === last) { return next; } current = expect(next); } } function clear(bounds) { let parent = bounds.parentElement(); let first = bounds.firstNode(); let last = bounds.lastNode(); let current = first; while (true) { let next = current.nextSibling; parent.removeChild(current); if (current === last) { return next; } current = expect(next); } } /** @internal */ function hasCustomDebugRenderTreeLifecycle(manager) { return 'getDebugCustomRenderTree' in manager; } function resolveComponent(resolver, constants, name, owner) { let definition = resolver?.lookupComponent?.(name, expect(owner)) ?? null; if (!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); } let GUID = 0; class Ref { id = GUID++; value; constructor(value) { this.value = value; } get() { return this.value; } release() { if (this.value === null) { throw new Error('BUG: double release?'); } this.value = null; } toString() { let label = `Ref ${this.id}`; if (this.value === null) { return `${label} (released)`; } else { try { // eslint-disable-next-line @typescript-eslint/no-base-to-string return `${label}: ${this.value}`; } catch { return label; } } } } class DebugRenderTreeImpl { stack = new StackImpl(); refs = new WeakMap(); roots = new Set(); nodes = new WeakMap(); 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 (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) { expect(this.refs.get(state)).release(); } commit() { this.reset(); } capture() { return this.captureRefs(this.roots); } reset() { if (this.stack.size !== 0) { // 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 = expect(this.stack.toArray()[0]); let ref = this.refs.get(root); if (ref !== undefined) { this.roots.delete(ref); } while (!this.stack.isEmpty()) { this.stack.pop(); } } } enter(state) { this.stack.push(state); } exit() { if (this.stack.size === 0) { throw new Error('BUG: unbalanced pop'); } this.stack.pop(); } nodeFor(state) { return expect(this.nodes.get(state)); } appendChild(node, state) { if (this.refs.has(state)) { throw new Error('BUG: child already appended'); } let parent = this.stack.current; let ref = new Ref(state); this.refs.set(state, ref); if (parent) { let parentNode = this.nodeFor(parent); parentNode.refs.add(ref); node.parent = parentNode; } else { this.roots.add(ref); } } captureRefs(refs) { let captured = []; refs.forEach(ref => { let state = ref.get(); if (state) { captured.push(this.captureNode(`render-node:${ref.id}`, state)); } else { refs.delete(ref); } }); return captured; } captureNode(id, state) { let node = this.nodeFor(state); let { type, name, args, instance, refs } = node; let template = this.captureTemplate(node); let bounds = this.captureBounds(node); let children = this.captureRefs(refs); return { id, type, name, args: reifyArgsDebug(args), instance, template, bounds, children }; } captureTemplate({ template }) { return template || null; } captureBounds(node) { let bounds = expect(node.bounds); let parentElement = bounds.parentElement(); let firstNode = bounds.firstNode(); let lastNode = bounds.lastNode(); return { parentElement, firstNode, lastNode }; } } function getDebugName(definition, manager = definition.manager) { return definition.resolvedName ?? definition.debugName ?? manager.getDebugName(definition.state); } function normalizeStringValue(value) { if (isEmpty$1(value)) { return ''; } return String(value); } function shouldCoerce(value) { return isString(value) || isEmpty$1(value) || typeof value === 'boolean' || typeof value === 'number'; } function isEmpty$1(value) { return value === null || value === undefined || typeof value.toString !== 'function'; } function isIndexable(value) { return value !== null && typeof value === 'object'; } function isSafeString(value) { return isIndexable(value) && typeof value['toHTML'] === 'function'; } function isNode(value) { return isIndexable(value) && typeof value['nodeType'] === 'number'; } function isFragment(value) { return isIndexable(value) && value['nodeType'] === 11; } function isString(value) { return typeof value === 'string'; } 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(' '); }); } APPEND_OPCODES.add(VM_CHILD_SCOPE_OP, vm => vm.pushChildScope()); APPEND_OPCODES.add(VM_POP_SCOPE_OP, vm => vm.popScope()); APPEND_OPCODES.add(VM_PUSH_DYNAMIC_SCOPE_OP, vm => vm.pushDynamicScope()); APPEND_OPCODES.add(VM_POP_DYNAMIC_SCOPE_OP, vm => vm.popDynamicScope()); APPEND_OPCODES.add(VM_CONSTANT_OP, (vm, { op1: other }) => { vm.stack.push(vm.constants.getValue(decodeHandle(other))); }); APPEND_OPCODES.add(VM_CONSTANT_REFERENCE_OP, (vm, { op1: other }) => { vm.stack.push(createConstRef(vm.constants.getValue(decodeHandle(other)), false)); }); APPEND_OPCODES.add(VM_PRIMITIVE_OP, (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 { // is already an encoded immediate or primitive handle stack.push(decodeImmediate(primitive)); } }); APPEND_OPCODES.add(VM_PRIMITIVE_REFERENCE_OP, vm => { let stack = vm.stack; let value = check(stack.pop()); 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); } stack.push(ref); }); APPEND_OPCODES.add(VM_DUP_OP, (vm, { op1: register, op2: offset }) => { let position = check(vm.fetchValue(check(register, CheckRegister))) - offset; vm.stack.dup(position); }); APPEND_OPCODES.add(VM_POP_OP, (vm, { op1: count }) => { vm.stack.pop(count); }); APPEND_OPCODES.add(VM_LOAD_OP, (vm, { op1: register }) => { vm.load(check(register)); }); APPEND_OPCODES.add(VM_FETCH_OP, (vm, { op1: register }) => { vm.fetch(check(register)); }); APPEND_OPCODES.add(VM_BIND_DYNAMIC_SCOPE_OP, (vm, { op1: _names }) => { let names = vm.constants.getArray(_names); vm.bindDynamicScope(names); }); APPEND_OPCODES.add(VM_ENTER_OP, (vm, { op1: args }) => { vm.enter(args); }); APPEND_OPCODES.add(VM_EXIT_OP, vm => { vm.exit(); }); APPEND_OPCODES.add(VM_PUSH_SYMBOL_TABLE_OP, (vm, { op1: _table }) => { let stack = vm.stack; stack.push(vm.constants.getValue(_table)); }); APPEND_OPCODES.add(VM_PUSH_BLOCK_SCOPE_OP, vm => { let stack = vm.stack; stack.push(vm.scope()); }); APPEND_OPCODES.add(VM_COMPILE_BLOCK_OP, vm => { let stack = vm.stack; let block = stack.pop(); if (block) { stack.push(vm.compile(block)); } else { stack.push(null); } }); APPEND_OPCODES.add(VM_INVOKE_YIELD_OP, vm => { let { stack } = vm; let handle = check(stack.pop()); let scope = check(stack.pop()); let table = check(stack.pop()); let args = check(stack.pop()); if (table === null || handle === null) { // To balance the pop{Frame,Scope} vm.lowlevel.pushFrame(); vm.pushScope(scope ?? vm.scope()); return; } let invokingScope = expect(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(locals[i]), args.at(i)); } } } vm.lowlevel.pushFrame(); vm.pushScope(invokingScope); vm.call(handle); }); APPEND_OPCODES.add(VM_JUMP_IF_OP, (vm, { op1: target }) => { let reference = check(vm.stack.pop()); let value = Boolean(valueForRef(reference)); if (isConstRef(reference)) { if (value) { vm.lowlevel.goto(target); } } else { if (value) { vm.lowlevel.goto(target); } vm.updateWith(new Assert(reference)); } }); APPEND_OPCODES.add(VM_JUMP_UNLESS_OP, (vm, { op1: target }) => { let reference = check(vm.stack.pop()); let value = Boolean(valueForRef(reference)); if (isConstRef(reference)) { if (!value) { vm.lowlevel.goto(target); } } else { if (!value) { vm.lowlevel.goto(target); } vm.updateWith(new Assert(reference)); } }); APPEND_OPCODES.add(VM_JUMP_EQ_OP, (vm, { op1: target, op2: comparison }) => { let other = check(vm.stack.peek()); if (other === comparison) { vm.lowlevel.goto(target); } }); APPEND_OPCODES.add(VM_ASSERT_SAME_OP, vm => { let reference = check(vm.stack.peek()); if (!isConstRef(reference)) { vm.updateWith(new Assert(reference)); } }); APPEND_OPCODES.add(VM_TO_BOOLEAN_OP, vm => { let { stack } = vm; let valueRef = check(stack.pop()); stack.push(createComputeRef(() => toBool(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)); } } 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(VM_TEXT_OP, (vm, { op1: text }) => { vm.tree().appendText(vm.constants.getValue(text)); }); APPEND_OPCODES.add(VM_COMMENT_OP, (vm, { op1: text }) => { vm.tree().appendComment(vm.constants.getValue(text)); }); APPEND_OPCODES.add(VM_OPEN_ELEMENT_OP, (vm, { op1: tag }) => { vm.tree().openElement(vm.constants.getValue(tag)); }); APPEND_OPCODES.add(VM_OPEN_DYNAMIC_ELEMENT_OP, vm => { let tagName = check(valueForRef(check(vm.stack.pop(), CheckReference))); vm.tree().openElement(tagName); }); APPEND_OPCODES.add(VM_PUSH_REMOTE_ELEMENT_OP, vm => { let elementRef = check(vm.stack.pop()); let insertBeforeRef = check(vm.stack.pop()); let guidRef = check(vm.stack.pop()); let element = check(valueForRef(elementRef)); let insertBefore = check(valueForRef(insertBeforeRef)); 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.tree().pushRemoteElement(element, guid, insertBefore); 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(block, () => { vm.env.debugRenderTree?.willDestroy(block); }); } }); APPEND_OPCODES.add(VM_POP_REMOTE_ELEMENT_OP, vm => { let bounds = vm.tree().popRemoteElement(); if (vm.env.debugRenderTree !== undefined) { // The RemoteBlock is also its bounds vm.env.debugRenderTree.didRender(bounds, bounds); } }); APPEND_OPCODES.add(VM_FLUSH_ELEMENT_OP, vm => { let operations = check(vm.fetchValue($t0)); let modifiers = null; if (operations) { modifiers = operations.flush(vm); vm.loadValue($t0, null); } vm.tree().flushElement(modifiers); }); APPEND_OPCODES.add(VM_CLOSE_ELEMENT_OP, vm => { let modifiers = vm.tree().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(VM_MODIFIER_OP, (vm, { op1: handle }) => { let args = check(vm.stack.pop()); if (!vm.env.isInteractive) { return; } let owner = vm.getOwner(); let definition = vm.constants.getValue(handle); let { manager } = definition; let { constructing } = vm.tree(); let capturedArgs = args.capture(); let state = manager.create(owner, expect(constructing), definition.state, capturedArgs); let instance = { manager, state, definition }; let operations = expect(check(vm.fetchValue($t0))); 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(VM_DYNAMIC_MODIFIER_OP, vm => { let { stack } = vm; let ref = check(stack.pop()); let args = check(stack.pop()); if (!vm.env.isInteractive) { return; } let capturedArgs = args.capture(); let { positional: outerPositional, named: outerNamed } = capturedArgs; let { constructing } = vm.tree(); let initialOwner = vm.getOwner(); let instanceRef = createComputeRef(() => { let value = valueForRef(ref); let owner; if (!isIndexable$1(value)) { return; } let hostDefinition; if (isCurriedType(value, CURRIED_MODIFIER)) { let { definition: resolvedDefinition, owner: curriedOwner, positional, named } = resolveCurriedValue(value); hostDefinition = resolvedDefinition; owner = curriedOwner; if (positional !== undefined) { capturedArgs.positional = positional.concat(outerPositional); } if (named !== undefined) { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment capturedArgs.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?.(hostDefinition)}`); } } let definition = { resolvedName: null, manager, state: hostDefinition }; let state = manager.create(owner, expect(constructing), definition.state, capturedArgs); return { manager, state, definition }; }); let instance = valueForRef(instanceRef); let tag = null; if (instance !== undefined) { let operations = expect(check(vm.fetchValue($t0))); operations.addModifier(vm, instance, capturedArgs); 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)) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- @fixme vm.env.scheduleUpdateModifier(instance); this.lastUpdated = valueForTag(tag); } if (tag !== null) { consumeTag(tag); } } } APPEND_OPCODES.add(VM_STATIC_ATTR_OP, (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.tree().setStaticAttribute(name, value, namespace); }); APPEND_OPCODES.add(VM_DYNAMIC_ATTR_OP, (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()); let value = valueForRef(reference); let namespace = _namespace ? vm.constants.getValue(_namespace) : null; let attribute = vm.tree().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) { 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(VM_PUSH_COMPONENT_DEFINITION_OP, (vm, { op1: handle }) => { let definition = vm.constants.getValue(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(VM_RESOLVE_DYNAMIC_COMPONENT_OP, (vm, { op1: _isStrict }) => { let stack = vm.stack; let ref = check(stack.pop()); let component = check(valueForRef(ref)); 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.context.resolver, constants, component, owner); definition = expect(resolvedDefinition); } else if (isCurriedValue(component)) { definition = component; } else { definition = constants.component(component, owner); } if (!isCurriedValue(definition) && !definition.resolvedName && !definition.debugName) { let debugLabel = ref.debugLabel; if (debugLabel) { definition.debugName = debugLabel; } } stack.push(definition); }); APPEND_OPCODES.add(VM_RESOLVE_CURRIED_COMPONENT_OP, vm => { let stack = vm.stack; let ref = check(stack.pop()); 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?.(value) ?? value}`); } } if (definition && !isCurriedValue(definition) && !definition.resolvedName && !definition.debugName) { let debugLabel = ref.debugLabel; if (debugLabel) { definition.debugName = debugLabel; } } stack.push(definition); }); APPEND_OPCODES.add(VM_PUSH_DYNAMIC_COMPONENT_INSTANCE_OP, 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(VM_PUSH_ARGS_OP, (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.setup(stack, names, blockNames, positionalCount, !!atNames); stack.push(vm.args); }); APPEND_OPCODES.add(VM_PUSH_EMPTY_ARGS_OP, vm => { let { stack } = vm; stack.push(vm.args.empty(stack)); }); APPEND_OPCODES.add(VM_CAPTURE_ARGS_OP, vm => { let stack = vm.stack; let args = check(stack.pop()); let capturedArgs = args.capture(); stack.push(capturedArgs); }); APPEND_OPCODES.add(VM_PREPARE_ARGS_OP, (vm, { op1: register }) => { let stack = vm.stack; let instance = vm.fetchValue(check(register)); let args = check(stack.pop()); let { definition } = instance; if (isCurriedType(definition, CURRIED_COMPONENT)) { assert(!definition.manager); let constants = vm.constants; let { definition: resolvedDefinition, owner, resolved, positional, named } = resolveCurriedValue(definition); if (resolved) { definition = resolvedDefinition; } else if (typeof resolvedDefinition === 'string') { let resolvedValue = vm.context.resolver?.lookupComponent?.(resolvedDefinition, owner) ?? null; definition = constants.resolvedComponent(expect(resolvedValue), resolvedDefinition); } else { definition = constants.component(resolvedDefinition, owner); } if (named !== undefined) { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument args.named.merge(assign({}, ...named)); } if (positional !== undefined) { args.realloc(positional.length); args.positional.prepend(positional); } let { 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, state } = definition; let capabilities = instance.capabilities; if (!managerHasCapability(manager, capabilities, InternalComponentCapabilities.prepareArgs)) { stack.push(args); return; } let blocks = args.blocks.values; let blockNames = args.blocks.names; let preparedArgs = manager.prepareArgs(state, args); if (preparedArgs) { args.clear(); for (let i = 0; i < blocks.length; i++) { stack.push(blocks[i]); } let { positional, named } = preparedArgs; let 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[unwrap(names[i])]); } args.setup(stack, names, blockNames, positionalCount, false); } stack.push(args); }); APPEND_OPCODES.add(VM_CREATE_COMPONENT_OP, (vm, { op1: flags }) => { let instance = check(vm.fetchValue($s0)); let { definition, manager, 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; if (managerHasCapability(manager, capabilities, InternalComponentCapabilities.dynamicScope)) { dynamicScope = vm.dynamicScope(); } let hasDefaultBlock = flags & 1; let args = null; if (managerHasCapability(manager, capabilities, InternalComponentCapabilities.createArgs)) { args = check(vm.stack.peek()); } let self = null; if (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; if (managerHasCapability(manager, capabilities, InternalComponentCapabilities.updateHook)) { vm.updateWith(new UpdateComponentOpcode(state, manager, dynamicScope)); } }); APPEND_OPCODES.add(VM_REGISTER_COMPONENT_DESTRUCTOR_OP, (vm, { op1: register }) => { let { manager, state, capabilities } = check(vm.fetchValue(check(register, CheckRegister))); let d = manager.getDestroyable(state); if (!managerHasCapability(manager, capabilities, InternalComponentCapabilities.willDestroy) && d !== null && "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'); } if (d) vm.associateDestroyable(d); }); APPEND_OPCODES.add(VM_BEGIN_COMPONENT_TRANSACTION_OP, (vm, { op1: register }) => { let name; { let { definition, manager } = check(vm.fetchValue(check(register, CheckRegister))); name = getDebugName(definition, manager); } vm.beginCacheGroup(name); vm.tree().pushAppendingBlock(); }); APPEND_OPCODES.add(VM_PUT_COMPONENT_OPERATIONS_OP, vm => { vm.loadValue($t0, new ComponentElementOperations()); }); APPEND_OPCODES.add(VM_COMPONENT_ATTR_OP, (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()); let namespace = _namespace ? vm.constants.getValue(_namespace) : null; check(vm.fetchValue($t0), CheckInstanceof(ComponentElementOperations)).setAttribute(name, reference, trusting, namespace); }); APPEND_OPCODES.add(VM_STATIC_COMPONENT_ATTR_OP, (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; check(vm.fetchValue($t0), CheckInstanceof(ComponentElementOperations)).setStaticAttribute(name, value, namespace); }); class ComponentElementOperations { attributes = dict(); classes = []; modifiers = []; setAttribute(name, value, trusting, namespace) { let deferred = { value, namespace, trusting }; if (name === 'class') { this.classes.push(value); } this.attributes[name] = deferred; } setStaticAttribute(name, value, namespace) { let deferred = { value, namespace }; if (name === 'class') { this.classes.push(value); } this.attributes[name] = deferred; } addModifier(vm, modifier, capturedArgs) { this.modifiers.push(modifier); if (vm.env.debugRenderTree !== undefined) { const { manager, definition, 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 (state === null || typeof state !== 'object' && typeof state !== 'function') { return; } let { element, constructing } = vm.tree(); let name = definition.resolvedName ?? manager.getDebugName(definition.state); let instance = manager.getDebugInstance(state); let bounds = new ConcreteBounds(element, constructing, constructing); vm.env.debugRenderTree.create(state, { type: 'modifier', name, args: capturedArgs, 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; let attributes = this.attributes; for (let name in this.attributes) { if (name === 'type') { type = attributes[name]; continue; } let attr = unwrap(this.attributes[name]); if (name === 'class') { setDeferredAttr(vm, 'class', mergeClasses(this.classes), attr.namespace, attr.trusting); } else { setDeferredAttr(vm, name, attr.value, attr.namespace, attr.trusting); } } if (type !== undefined) { setDeferredAttr(vm, 'type', type.value, type.namespace, type.trusting); } return this.modifiers; } } function mergeClasses(classes) { if (classes.length === 0) { return ''; } if (classes.length === 1) { return unwrap(classes[0]); } if (allStringClasses(classes)) { return classes.join(' '); } return createClassListRef(classes); } function allStringClasses(classes) { return classes.every(c => typeof c === 'string'); } function setDeferredAttr(vm, name, value, namespace, trusting = false) { if (typeof value === 'string') { vm.tree().setStaticAttribute(name, value, namespace); } else { let attribute = vm.tree().setDynamicAttribute(name, valueForRef(value), trusting, namespace); if (!isConstRef(value)) { vm.updateWith(new UpdateDynamicAttributeOpcode(value, attribute, vm.env)); } } } APPEND_OPCODES.add(VM_DID_CREATE_ELEMENT_OP, (vm, { op1: register }) => { let { definition, state } = check(vm.fetchValue(check(register, CheckRegister))); let { manager } = definition; let operations = check(vm.fetchValue($t0)); manager.didCreateElement(state, expect(vm.tree().constructing), operations); }); APPEND_OPCODES.add(VM_GET_COMPONENT_SELF_OP, (vm, { op1: register, op2: _names }) => { let instance = check(vm.fetchValue(check(register, CheckRegister))); let { definition, state } = instance; let { manager } = definition; let selfRef = manager.getSelf(state); if (vm.env.debugRenderTree !== undefined) { let instance = check(vm.fetchValue(check(register, CheckRegister))); let { definition, manager } = instance; let args; 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 moduleName; let compilable = definition.compilable; if (compilable === null) { assert(managerHasCapability(manager, instance.capabilities, InternalComponentCapabilities.dynamicLayout)); let resolver = vm.context.resolver; compilable = resolver === null ? null : manager.getDynamicLayout(state, resolver); if (compilable !== null) { moduleName = compilable.moduleName; } else { moduleName = '__default__.hbs'; } } else { moduleName = compilable.moduleName; } // For tearing down the debugRenderTree vm.associateDestroyable(instance); if (hasCustomDebugRenderTreeLifecycle(manager)) { let nodes = manager.getDebugCustomRenderTree(instance.definition.state, instance.state, args, moduleName); nodes.forEach(node => { let { 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, 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(VM_GET_COMPONENT_TAG_NAME_OP, (vm, { op1: register }) => { let { definition, state } = check(vm.fetchValue(check(register, CheckRegister))); let { manager } = definition; let 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(VM_GET_COMPONENT_LAYOUT_OP, (vm, { op1: register }) => { let instance = check(vm.fetchValue(check(register, CheckRegister))); let { manager, definition } = instance; let { stack } = vm; let { compilable } = definition; if (compilable === null) { let { capabilities } = instance; assert(managerHasCapability(manager, capabilities, InternalComponentCapabilities.dynamicLayout)); let resolver = vm.context.resolver; compilable = resolver === null ? null : manager.getDynamicLayout(instance.state, resolver); if (compilable === null) { if (managerHasCapability(manager, capabilities, InternalComponentCapabilities.wrapped)) { compilable = unwrapTemplate(vm.constants.defaultTemplate).asWrappedLayout(); } else { co