ember-source
Version:
A JavaScript framework for creating ambitious web applications
1,343 lines (1,336 loc) • 167 kB
JavaScript
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