import { OwlError } from "../error_handling"; import { attrsSetter, attrsUpdater, createAttrUpdater, isProp, makePropSetter, setClass, updateClass, } from "./attributes"; import { config } from "./config"; import { createEventHandler } from "./events"; import type { VNode } from "./index"; import { VMulti } from "./multi"; import { toText } from "./text"; const getDescriptor = (o: any, p: any) => Object.getOwnPropertyDescriptor(o, p)!; const nodeProto = Node.prototype; const elementProto = Element.prototype; const characterDataProto = CharacterData.prototype; const characterDataSetData = getDescriptor(characterDataProto, "data").set!; const nodeGetFirstChild = getDescriptor(nodeProto, "firstChild").get!; const nodeGetNextSibling = getDescriptor(nodeProto, "nextSibling").get!; const NO_OP = () => {}; // ----------------------------------------------------------------------------- // Main compiler code // ----------------------------------------------------------------------------- type BlockType = (data?: any[], children?: VNode[]) => VNode; const cache: { [key: string]: BlockType } = {}; /** * Compiling blocks is a multi-step process: * * 1. build an IntermediateTree from the HTML element. This intermediate tree * is a binary tree structure that encode dynamic info sub nodes, and the * path required to reach them * 2. process the tree to build a block context, which is an object that aggregate * all dynamic info in a list, and also, all ref indexes. * 3. process the context to build appropriate builder/setter functions * 4. make a dynamic block class, which will efficiently collect references and * create/update dynamic locations/children * * @param str * @returns a new block type, that can build concrete blocks */ export function createBlock(str: string): BlockType { if (str in cache) { return cache[str]; } // step 0: prepare html base element const doc = new DOMParser().parseFromString(`${str}`, "text/xml"); const node = doc.firstChild!.firstChild!; if (config.shouldNormalizeDom) { normalizeNode(node as any); } // step 1: prepare intermediate tree const tree = buildTree(node); // step 2: prepare block context const context = buildContext(tree); // step 3: build the final block class const template = tree.el as HTMLElement; const Block = buildBlock(template, context); cache[str] = Block; return Block; } // ----------------------------------------------------------------------------- // Helper // ----------------------------------------------------------------------------- function normalizeNode(node: HTMLElement | Text) { if (node.nodeType === Node.TEXT_NODE) { if (!/\S/.test((node as Text).textContent!)) { (node as Text).remove(); return; } } if (node.nodeType === Node.ELEMENT_NODE) { if ((node as HTMLElement).tagName === "pre") { return; } } for (let i = node.childNodes.length - 1; i >= 0; --i) { normalizeNode(node.childNodes.item(i) as any); } } // ----------------------------------------------------------------------------- // building a intermediate tree // ----------------------------------------------------------------------------- interface DynamicInfo { idx: number; refIdx?: number; type: "text" | "child" | "handler" | "attribute" | "attributes" | "ref"; isOnlyChild?: boolean; name?: string; tag?: string; event?: string; } interface IntermediateTree { parent: IntermediateTree | null; firstChild: IntermediateTree | null; nextSibling: IntermediateTree | null; el: Node; info: DynamicInfo[]; isRef?: boolean; refIdx?: number; refN: number; currentNS: string | null; } function buildTree( node: Node, parent: IntermediateTree | null = null, domParentTree: IntermediateTree | null = null ): IntermediateTree { switch (node.nodeType) { case Node.ELEMENT_NODE: { // HTMLElement let currentNS = domParentTree && domParentTree.currentNS; const tagName = (node as Element).tagName; let el: Node | undefined = undefined; const info: DynamicInfo[] = []; if (tagName.startsWith("block-text-")) { const index = parseInt(tagName.slice(11), 10); info.push({ type: "text", idx: index }); el = document.createTextNode(""); } if (tagName.startsWith("block-child-")) { if (!domParentTree!.isRef) { addRef(domParentTree!); } const index = parseInt(tagName.slice(12), 10); info.push({ type: "child", idx: index }); el = document.createTextNode(""); } const attrs = (node as Element).attributes; const ns = attrs.getNamedItem("block-ns"); if (ns) { attrs.removeNamedItem("block-ns"); currentNS = ns.value; } if (!el) { el = currentNS ? document.createElementNS(currentNS, tagName) : document.createElement(tagName); } if (el instanceof Element) { if (!domParentTree) { // some html elements may have side effects when setting their attributes. // For example, setting the src attribute of an will trigger a // request to get the corresponding image. This is something that we // don't want at compile time. We avoid that by putting the content of // the block in a