UNPKG

ember-source

Version:

A JavaScript framework for creating ambitious web applications

1,343 lines (1,336 loc) 167 kB
import { Op, $t0, $t1, $v0, $pc, CurriedType as CurriedTypes, InternalComponentCapabilities, ContentType, isLowLevelRegister, $s1, $s0, $sp, $fp, $ra, MachineOp } from '../vm/index.js'; import { createConstRef, UNDEFINED_REFERENCE, NULL_REFERENCE, TRUE_REFERENCE, FALSE_REFERENCE, createPrimitiveRef, valueForRef, isConstRef, createComputeRef, childRefFor, createIteratorRef, createIteratorItemRef, createDebugAliasRef, isInvokableRef, updateRef } from '../reference/index.js'; import { decodeHandle, isHandle, decodeImmediate, assert as debugAssert, expect, unwrap, debugToString as debugToString$1, EMPTY_STRING_ARRAY, assign, dict, unwrapTemplate, enumerate, castToSimple, buildUntouchableThis, NS_SVG, castToBrowser, Stack as StackImpl, isObject, INSERT_BEFORE_END, clearElement, INSERT_AFTER_BEGIN, unwrapHandle, reverse, COMMENT_NODE, emptyArray, INSERT_BEFORE_BEGIN, isDict } from '../util/index.js'; import { registerDestructor, destroy, associateDestroyableChild, _hasDestroyableChildren, isDestroying, isDestroyed, destroyChildren } from '../destroyable/index.js'; import { warnIfStyleNotTrusted, toBool, assertGlobalContextWasSet, setPath, getPath } from '../global-context/index.js'; import { managerHasCapability, setInternalComponentManager, setInternalModifierManager, getInternalModifierManager, hasInternalComponentManager, hasInternalHelperManager, setInternalHelperManager, getInternalHelperManager, hasValue, hasDestroyable } from '../manager/index.js'; import { consumeTag, valueForTag, validateTag, CURRENT_TAG, createCache, debug, resetTracking, beginTrackFrame, endTrackFrame, CONSTANT_TAG, track, updateTag as UPDATE_TAG, INITIAL, getValue, createUpdatableTag } from '../validator/index.js'; import { RuntimeProgramImpl } from '../program/index.js'; import { getOwner } from '../owner/index.js'; import { isDevelopingApp } from '@embroider/macros'; new Array(Op.Size).fill(null), new Array(Op.Size).fill(null); class DynamicScopeImpl { bucket; constructor(bucket) { this.bucket = bucket ? assign({}, bucket) : {}; } get(key) { return unwrap(this.bucket[key]); } 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: self }); } 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: self }) { return this.slots[0] = self, 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; } } // 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"), DESTROYABLE_STACK = Symbol("DESTROYABLE_STACK"), STACKS = Symbol("STACKS"), REGISTERS = Symbol("REGISTERS"), HEAP = Symbol("HEAP"), CONSTANTS = Symbol("CONSTANTS"), ARGS$1 = Symbol("ARGS"); 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; // eslint-disable-next-line no-constant-condition for (;;) { let next = current.nextSibling; if (parent.insertBefore(current, reference), current === last) return next; current = expect(next, "invalid bounds"); } } function clear(bounds) { let parent = bounds.parentElement(), first = bounds.firstNode(), last = bounds.lastNode(), current = first; // eslint-disable-next-line no-constant-condition for (;;) { let next = current.nextSibling; if (parent.removeChild(current), current === last) return next; current = expect(next, "invalid bounds"); } } function normalizeStringValue(value) { return isEmpty$2(value) ? "" : String(value); } function isEmpty$2(value) { return null == value || "function" != typeof value.toString; } function isSafeString(value) { return "object" == typeof value && null !== value && "function" == typeof value.toHTML; } function isNode(value) { return "object" == typeof value && null !== value && "number" == typeof value.nodeType; } function isString(value) { return "string" == typeof value; } /* * @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(); lower in element ? (type = "prop", normalized = lower) : (type = "attr", normalized = slotName); } return "prop" !== type || "style" !== normalized.toLowerCase() && !function (tagName, propName) { let tag = ATTR_OVERRIDES[tagName.toUpperCase()]; return tag && tag[propName.toLowerCase()] || !1; }(element.tagName, normalized) || (type = "attr"), { normalized: normalized, type: type }; } // properties that MUST be set as attributes, due to: // * browser bug // * strange spec outlier const ATTR_OVERRIDES = { INPUT: { form: !0, // 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: !0, // Chrome 54.0.2840.98: 'list' in document.createElement('input') === true // Safari 9.1.3: 'list' in document.createElement('input') === false list: !0 }, // element.form is actually a legitimate readOnly property, that is to be // mutated, but must be mutated by setAttribute... SELECT: { form: !0 }, OPTION: { form: !0 }, TEXTAREA: { form: !0 }, LABEL: { form: !0 }, FIELDSET: { form: !0 }, LEGEND: { form: !0 }, OBJECT: { form: !0 }, OUTPUT: { form: !0 }, BUTTON: { form: !0 } }, badProtocols = ["javascript:", "vbscript:"], badTags = ["A", "BODY", "LINK", "IMG", "IFRAME", "BASE", "FORM"], badTagsForDataURI = ["EMBED"], badAttributes = ["href", "src", "background", "action"], badAttributesForDataURI = ["src"]; function has(array, item) { return -1 !== array.indexOf(item); } function checkURI(tagName, attribute) { return (null === tagName || has(badTags, tagName)) && has(badAttributes, attribute); } function checkDataURI(tagName, attribute) { return null !== tagName && has(badTagsForDataURI, tagName) && has(badAttributesForDataURI, attribute); } function requiresSanitization(tagName, attribute) { return checkURI(tagName, attribute) || checkDataURI(tagName, attribute); } let _protocolForUrlImplementation, DebugStyleAttributeManager; function sanitizeAttributeValue(element, attribute, value) { let tagName = null; if (null == value) return value; if (isSafeString(value)) return value.toHTML(); tagName = element ? element.tagName.toUpperCase() : null; let str = normalizeStringValue(value); if (checkURI(tagName, attribute)) { let protocol = (url = str, _protocolForUrlImplementation || (_protocolForUrlImplementation = function () { if ("object" == typeof URL && null !== URL && // 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` "function" == typeof URL.parse) { // 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; return "string" == typeof url && (protocol = nodeURL.parse(url).protocol), null === protocol ? ":" : protocol; }; } if ("function" == typeof URL) return _url => { try { return new URL(_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 ":"; } }; throw new Error('@glimmer/runtime needs a valid "globalThis.URL"'); }()), _protocolForUrlImplementation(url)); if (has(badProtocols, protocol)) return `unsafe:${str}`; } var url; return checkDataURI(tagName, attribute) ? `unsafe:${str}` : str; } function dynamicAttribute(element, attr, namespace, isTrusting = !1) { const { tagName: tagName, namespaceURI: namespaceURI } = element, attribute = { element: element, name: attr, namespace: namespace }; if (isDevelopingApp() && "style" === attr && !isTrusting) return new DebugStyleAttributeManager(attribute); if (namespaceURI === NS_SVG) return buildDynamicAttribute(tagName, attr, attribute); const { type: type, normalized: normalized } = normalizeProperty(element, attr); return "attr" === type ? buildDynamicAttribute(tagName, normalized, attribute) : function (tagName, name, attribute) { return requiresSanitization(tagName, name) ? new SafeDynamicProperty(name, attribute) : function (tagName, attribute) { return ("INPUT" === tagName || "TEXTAREA" === tagName) && "value" === attribute; }(tagName, name) ? new InputValueDynamicAttribute(name, attribute) : function (tagName, attribute) { return "OPTION" === tagName && "selected" === attribute; }(tagName, name) ? new OptionSelectedDynamicAttribute(name, attribute) : new DefaultDynamicProperty(name, attribute); }(tagName, normalized, attribute); } function buildDynamicAttribute(tagName, name, attribute) { return requiresSanitization(tagName, name) ? new SafeDynamicAttribute(attribute) : new SimpleDynamicAttribute(attribute); } class DynamicAttribute { constructor(attribute) { this.attribute = attribute; } } class SimpleDynamicAttribute extends DynamicAttribute { set(dom, value, _env) { const normalizedValue = normalizeValue(value); if (null !== normalizedValue) { const { name: name, namespace: namespace } = this.attribute; dom.__setAttribute(name, normalizedValue, namespace); } } update(value, _env) { const normalizedValue = normalizeValue(value), { element: element, name: name } = this.attribute; null === normalizedValue ? element.removeAttribute(name) : element.setAttribute(name, normalizedValue); } } class DefaultDynamicProperty extends DynamicAttribute { constructor(normalizedName, attribute) { super(attribute), this.normalizedName = normalizedName; } value; set(dom, value, _env) { null != value && (this.value = value, dom.__setProperty(this.normalizedName, value)); } update(value, _env) { const { element: element } = this.attribute; this.value !== value && (element[this.normalizedName] = this.value = value, null == value && this.removeAttribute()); } removeAttribute() { // TODO this sucks but to preserve properties first and to meet current // semantics we must do this. const { element: element, namespace: namespace } = this.attribute; namespace ? element.removeAttributeNS(namespace, this.normalizedName) : element.removeAttribute(this.normalizedName); } } class SafeDynamicProperty extends DefaultDynamicProperty { set(dom, value, env) { const { element: element, name: name } = this.attribute, sanitized = sanitizeAttributeValue(element, name, value); super.set(dom, sanitized, env); } update(value, env) { const { element: element, name: name } = this.attribute, sanitized = sanitizeAttributeValue(element, name, value); super.update(sanitized, env); } } class SafeDynamicAttribute extends SimpleDynamicAttribute { set(dom, value, env) { const { element: element, name: name } = this.attribute, sanitized = sanitizeAttributeValue(element, name, value); super.set(dom, sanitized, env); } update(value, env) { const { element: element, name: name } = this.attribute, sanitized = sanitizeAttributeValue(element, name, value); super.update(sanitized, env); } } class InputValueDynamicAttribute extends DefaultDynamicProperty { set(dom, value) { dom.__setProperty("value", normalizeStringValue(value)); } update(value) { const input = castToBrowser(this.attribute.element, ["input", "textarea"]), currentValue = input.value, normalizedValue = normalizeStringValue(value); currentValue !== normalizedValue && (input.value = normalizedValue); } } class OptionSelectedDynamicAttribute extends DefaultDynamicProperty { set(dom, value) { null != value && !1 !== value && dom.__setProperty("selected", !0); } update(value) { castToBrowser(this.attribute.element, "option").selected = !!value; } } function normalizeValue(value) { return !1 === value || null == value || void 0 === value.toString ? null : !0 === value ? "" : // onclick function etc in SSR "function" == typeof value ? null : String(value); } isDevelopingApp() && (DebugStyleAttributeManager = class extends SimpleDynamicAttribute { set(dom, value, env) { warnIfStyleNotTrusted(value), super.set(dom, value, env); } update(value, env) { warnIfStyleNotTrusted(value), super.update(value, env); } }); 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 stack = new this(env, block.parentElement(), block.reset(env)).initialize(); return stack.pushLiveBlock(block), stack; } constructor(env, parentNode, nextSibling) { this.pushElement(parentNode, nextSibling), this.env = env, this.dom = env.getAppendOperations(), this.updateOperations = env.getDOM(); } initialize() { return this.pushSimpleBlock(), 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)); } pushBlockList(list) { return this.pushLiveBlock(new LiveBlockList(this.element, list)); } pushLiveBlock(block, isRemote = !1) { let current = this.blockStack.current; return null !== current && (isRemote || current.didAppendBounds(block)), this.__openBlock(), this.blockStack.push(block), block; } popBlock() { return this.block().finalize(this), this.__closeBlock(), expect(this.blockStack.pop(), "Expected popBlock to return a block"); } __openBlock() {} __closeBlock() {} // todo return seems unused openElement(tag) { let element = this.__openElement(tag); return this.constructing = element, element; } __openElement(tag) { return this.dom.createElement(tag, this.element); } flushElement(modifiers) { let parent = this.element, 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() { return this.willCloseElement(), this.popElement(), this.popModifiers(); } pushRemoteElement(element, guid, insertBefore) { return this.__pushRemoteElement(element, guid, insertBefore); } __pushRemoteElement(element, _guid, insertBefore) { if (this.pushElement(element, insertBefore), void 0 === insertBefore) for (; element.lastChild;) element.removeChild(element.lastChild); let block = new RemoteLiveBlock(element); return this.pushLiveBlock(block, !0); } popRemoteElement() { const block = this.popBlock(); return debugAssert(block instanceof RemoteLiveBlock, "[BUG] expecting a RemoteLiveBlock"), this.popElement(), 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) { return this.block().didAppendBounds(bounds), bounds; } didAppendNode(node) { return this.block().didAppendNode(node), node; } didOpenElement(element) { return this.block().openElement(element), element; } willCloseElement() { this.block().closeElement(); } appendText(string) { return this.didAppendNode(this.__appendText(string)); } __appendText(text) { let { dom: dom, element: element, nextSibling: nextSibling } = this, node = dom.createTextNode(text); return dom.insertBefore(element, node, nextSibling), node; } __appendNode(node) { return this.dom.insertBefore(this.element, node, this.nextSibling), node; } __appendFragment(fragment) { let first = fragment.firstChild; if (first) { let ret = new ConcreteBounds(this.element, first, fragment.lastChild); return this.dom.insertBefore(this.element, fragment, this.nextSibling), ret; } { const comment = this.__appendComment(""); return new ConcreteBounds(this.element, comment, comment); } } __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); return this.didAppendNode(node), node; } appendDynamicFragment(value) { let bounds = this.__appendFragment(value); this.didAppendBounds(bounds); } appendDynamicNode(value) { let node = this.__appendNode(value), 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)); } __appendComment(string) { let { dom: dom, element: element, nextSibling: nextSibling } = this, node = dom.createComment(string); return dom.insertBefore(element, node, nextSibling), 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 attribute = dynamicAttribute(this.constructing, name, namespace, trusting); return attribute.set(this, value, this.env), attribute; } } class SimpleLiveBlock { first = null; last = null; nesting = 0; constructor(parent) { this.parent = parent; } parentElement() { return this.parent; } firstNode() { return expect(this.first, "cannot call `firstNode()` while `SimpleLiveBlock` is still initializing").firstNode(); } lastNode() { return expect(this.last, "cannot call `lastNode()` while `SimpleLiveBlock` is still initializing").lastNode(); } openElement(element) { this.didAppendNode(element), this.nesting++; } closeElement() { this.nesting--; } didAppendNode(node) { 0 === this.nesting && (this.first || (this.first = new First(node)), this.last = new Last(node)); } didAppendBounds(bounds) { 0 === this.nesting && (this.first || (this.first = bounds), this.last = bounds); } finalize(stack) { null === this.first && stack.appendComment(""); } } class RemoteLiveBlock extends SimpleLiveBlock { constructor(parent) { super(parent), registerDestructor(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. this.parentElement() === this.firstNode().parentNode && clear(this); }); } } class UpdatableBlockImpl extends SimpleLiveBlock { reset() { destroy(this); let nextSibling = clear(this); return this.first = null, this.last = null, this.nesting = 0, 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() { return expect(this.boundList[0], "cannot call `firstNode()` while `LiveBlockList` is still initializing").firstNode(); } lastNode() { let boundList = this.boundList; return expect(boundList[boundList.length - 1], "cannot call `lastNode()` while `LiveBlockList` is still initializing").lastNode(); } openElement(_element) { debugAssert(!1, "Cannot openElement directly inside a block list"); } closeElement() { debugAssert(!1, "Cannot closeElement directly inside a block list"); } didAppendNode(_node) { debugAssert(!1, "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); } const APPEND_OPCODES = new class { evaluateOpcode = new Array(Op.Size).fill(null); add(name, evaluate, kind = "syscall") { this.evaluateOpcode[name] = { syscall: "machine" !== kind, evaluate: evaluate }; } debugBefore(vm, opcode) { return { sp: void 0, pc: vm.fetchValue($pc), name: void 0, params: void 0, type: opcode.type, isMachine: opcode.isMachine, size: opcode.size, state: void 0 }; } debugAfter(vm, pre) {} evaluate(vm, opcode, type) { let operation = unwrap(this.evaluateOpcode[type]); operation.syscall ? (debugAssert(!opcode.isMachine, `BUG: Mismatch between operation.syscall (${operation.syscall}) and opcode.isMachine (${opcode.isMachine}) for ${opcode.type}`), operation.evaluate(vm, opcode)) : (debugAssert(opcode.isMachine, `BUG: Mismatch between operation.syscall (${operation.syscall}) and opcode.isMachine (${opcode.isMachine}) for ${opcode.type}`), operation.evaluate(vm[INNER_VM], 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 { [TYPE]; [INNER]; [OWNER]; [ARGS]; [RESOLVED]; /** @internal */ constructor(type, inner, owner, args, resolved = !1) { 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; // eslint-disable-next-line no-constant-condition 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 = !1) { return new CurriedValue(type, spec, owner, args, resolved); } /** @internal */ function hasCustomDebugRenderTreeLifecycle(manager) { return "getDebugCustomRenderTree" in manager; } 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)), !1)); }), 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 // is already an encoded immediate or primitive handle stack.push(decodeImmediate(primitive)); }), APPEND_OPCODES.add(Op.PrimitiveReference, vm => { let ref, stack = vm.stack, value = stack.pop(); ref = void 0 === value ? UNDEFINED_REFERENCE : null === value ? NULL_REFERENCE : !0 === value ? TRUE_REFERENCE : !1 === value ? FALSE_REFERENCE : createPrimitiveRef(value), stack.push(ref); }), APPEND_OPCODES.add(Op.Dup, (vm, { op1: register, op2: offset }) => { let position = vm.fetchValue(register) - 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 }) => { vm.stack.push(vm[CONSTANTS].getValue(_table)); }), APPEND_OPCODES.add(Op.PushBlockScope, vm => { vm.stack.push(vm.scope()); }), APPEND_OPCODES.add(Op.CompileBlock, vm => { let stack = vm.stack, block = stack.pop(); block ? stack.push(vm.compile(block)) : stack.push(null); }), APPEND_OPCODES.add(Op.InvokeYield, vm => { let { stack: stack } = vm, handle = stack.pop(), scope = stack.pop(), table = stack.pop(); debugAssert(null === table || table && "object" == typeof table && Array.isArray(table.parameters), `Expected top of stack to be Option<BlockSymbolTable>, was ${String(table)}`); let args = stack.pop(); if (null === table) // To balance the pop{Frame,Scope} return vm.pushFrame(), void vm.pushScope(scope ?? vm.scope()); let invokingScope = expect(scope, "BUG: expected 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(unwrap(locals[i]), args.at(i)); } } vm.pushFrame(), vm.pushScope(invokingScope), vm.call(handle); }), APPEND_OPCODES.add(Op.JumpIf, (vm, { op1: target }) => { let reference = vm.stack.pop(), value = Boolean(valueForRef(reference)); isConstRef(reference) ? !0 === value && vm.goto(target) : (!0 === value && vm.goto(target), vm.updateWith(new Assert(reference))); }), APPEND_OPCODES.add(Op.JumpUnless, (vm, { op1: target }) => { let reference = vm.stack.pop(), value = Boolean(valueForRef(reference)); isConstRef(reference) ? !1 === value && vm.goto(target) : (!1 === value && vm.goto(target), vm.updateWith(new Assert(reference))); }), APPEND_OPCODES.add(Op.JumpEq, (vm, { op1: target, op2: comparison }) => { vm.stack.peek() === comparison && vm.goto(target); }), APPEND_OPCODES.add(Op.AssertSame, vm => { let reference = vm.stack.peek(); !1 === isConstRef(reference) && vm.updateWith(new Assert(reference)); }), APPEND_OPCODES.add(Op.ToBoolean, vm => { let { stack: stack } = vm, valueRef = stack.pop(); stack.push(createComputeRef(() => toBool(valueForRef(valueRef)))); }); class Assert { last; constructor(ref) { this.ref = ref, this.last = valueForRef(ref); } evaluate(vm) { let { last: last, ref: ref } = this; last !== valueForRef(ref) && vm.throw(); } } class AssertFilter { last; 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 { tag = CONSTANT_TAG; lastRevision = INITIAL; target; 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(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 = valueForRef(vm.stack.pop()); vm.elements().openElement(tagName); }), APPEND_OPCODES.add(Op.PushRemoteElement, 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.elements().pushRemoteElement(element, guid, insertBefore); if (block && 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(Op.PopRemoteElement, vm => { let bounds = vm.elements().popRemoteElement(); void 0 !== vm.env.debugRenderTree && // The RemoteLiveBlock is also its bounds vm.env.debugRenderTree.didRender(bounds, bounds); }), APPEND_OPCODES.add(Op.FlushElement, vm => { let operations = vm.fetchValue($t0), modifiers = null; operations && (modifiers = operations.flush(vm), vm.loadValue($t0, null)), vm.elements().flushElement(modifiers); }), APPEND_OPCODES.add(Op.CloseElement, vm => { let modifiers = vm.elements().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(Op.Modifier, (vm, { op1: handle }) => { if (!1 === vm.env.isInteractive) return; let owner = vm.getOwner(), args = vm.stack.pop(), definition = vm[CONSTANTS].getValue(handle), { manager: manager } = definition, { constructing: constructing } = vm.elements(), capturedArgs = args.capture(), state = manager.create(owner, expect(constructing, "BUG: ElementModifier could not find the element it applies to"), definition.state, capturedArgs), instance = { manager: manager, state: state, definition: definition }; expect(vm.fetchValue($t0), "BUG: ElementModifier could not find operations to append to").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(Op.DynamicModifier, vm => { if (!1 === vm.env.isInteractive) return; let { stack: stack } = vm, ref = stack.pop(), args = stack.pop().capture(), { positional: outerPositional, named: outerNamed } = args, { constructing: constructing } = vm.elements(), initialOwner = vm.getOwner(), instanceRef = createComputeRef(() => { let owner, hostDefinition, value = valueForRef(ref); if (!isObject(value)) return; if (isCurriedType(value, CurriedTypes.Modifier)) { 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 && (args.named = Object.assign({}, ...named, outerNamed)); } else hostDefinition = value, owner = initialOwner; let manager = getInternalModifierManager(hostDefinition, !0); 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, expect(constructing, "BUG: ElementModifier could not find the element it applies to"), definition.state, args); return { manager: manager, state: state, definition: definition }; }), instance = valueForRef(instanceRef), tag = null; return void 0 !== instance && (expect(vm.fetchValue($t0), "BUG: ElementModifier could not find operations to append to").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 { lastUpdated; 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 { lastUpdated; 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) || (vm.env.scheduleUpdateModifier(instance), this.lastUpdated = valueForTag(tag)); null !== tag && consumeTag(tag); } } APPEND_OPCODES.add(Op.StaticAttr, (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.elements().setStaticAttribute(name, value, namespace); }), APPEND_OPCODES.add(Op.DynamicAttr, (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.elements().setDynamicAttribute(name, value, trusting, namespace); isConstRef(reference) || vm.updateWith(new UpdateDynamicAttributeOpcode(reference, attribute, vm.env)); }); class UpdateDynamicAttributeOpcode { updateRef; constructor(reference, attribute, env) { let initialized = !1; this.updateRef = createComputeRef(() => { let value = valueForRef(reference); !0 === initialized ? attribute.update(value, env) : initialized = !0; }), 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: 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(Op.ResolveDynamicComponent, (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, expect(owner, "BUG: expected owner when looking up component")); if (isDevelopingApp() && !definition) throw new Error(`Attempted to resolve \`${name}\`, which was expected to be a component, but nothing was found.`); return constants.resolvedComponent(definition, name); }(vm.runtime.resolver, constants, component, owner); definition = expect(resolvedDefinition, `Could not find a component named "${component}"`); } else definition = isCurriedValue(component) ? component : constants.component(component, owner); stack.push(definition); }), APPEND_OPCODES.add(Op.ResolveCurriedComponent, 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(), !0), 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)}`); stack.push(definition); }), APPEND_OPCODES.add(Op.PushDynamicComponentInstance, 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(Op.PushArgs, (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$1].setup(stack, names, blockNames, positionalCount, !!atNames), stack.push(vm[ARGS$1]); }), APPEND_OPCODES.add(Op.PushEmptyArgs, vm => { let { stack: stack } = vm; stack.push(vm[ARGS$1].empty(stack)); }), APPEND_OPCODES.add(Op.CaptureArgs, vm => { let stack = vm.stack, capturedArgs = stack.pop().capture(); stack.push(capturedArgs); }), APPEND_OPCODES.add(Op.PrepareArgs, (vm, { op1: _state }) => { let stack = vm.stack, instance = vm.fetchValue(_state), args = stack.pop(), { definition: definition } = instance; if (isCurriedType(definition, CurriedTypes.Component)) { debugAssert(!definition.manager, "If the component definition was curried, we don't yet have a manager"); let constants = vm[CONSTANTS], { definition: resolvedDefinition, owner: owner, resolved: resolved, positional: positional, named: named } = resolveCurriedValue(definition); if (!0 === resolved) definition = resolvedDefinition;else if ("string" == typeof resolvedDefinition) { let resolvedValue = vm.runtime.resolver.lookupComponent(resolvedDefinition, owner); definition = constants.resolvedComponent(expect(resolvedValue, "BUG: expected resolved component"), resolvedDefinition); } else definition = constants.component(resolvedDefinition, owner); void 0 !== named && args.named.merge(assign({}, ...named)), void 0 !== positional && (args.realloc(positional.length), args.positional.prepend(positional)); let { manager: manager } = definition; debugAssert(null === instance.manager, "component instance manager should not be populated yet"), debugAssert(null === instance.capabilities, "component instance manager should not be populated yet"), instance.definition = definition, instance.manager = manager, instance.capabilities = definition.capabilities, // Save off the owner that this component was curried with. Later on