diff --git a/packages/compiler-dom/src/parserOptions.ts b/packages/compiler-dom/src/parserOptions.ts index 2b5e9f01084..244251b9ba9 100644 --- a/packages/compiler-dom/src/parserOptions.ts +++ b/packages/compiler-dom/src/parserOptions.ts @@ -1,12 +1,12 @@ import { ParserOptions, NodeTypes, Namespaces } from '@vue/compiler-core' -import { isVoidTag, isHTMLTag, isSVGTag } from '@vue/shared' +import { isVoidTag, isHTMLTag, isSVGTag, isMathMLTag } from '@vue/shared' import { TRANSITION, TRANSITION_GROUP } from './runtimeHelpers' import { decodeHtmlBrowser } from './decodeHtmlBrowser' export const parserOptions: ParserOptions = { parseMode: 'html', isVoidTag, - isNativeTag: tag => isHTMLTag(tag) || isSVGTag(tag), + isNativeTag: tag => isHTMLTag(tag) || isSVGTag(tag) || isMathMLTag(tag), isPreTag: tag => tag === 'pre', decodeEntities: __BROWSER__ ? decodeHtmlBrowser : undefined, diff --git a/packages/runtime-core/src/apiCreateApp.ts b/packages/runtime-core/src/apiCreateApp.ts index 7c0a15b2e98..423c81d7441 100644 --- a/packages/runtime-core/src/apiCreateApp.ts +++ b/packages/runtime-core/src/apiCreateApp.ts @@ -16,7 +16,7 @@ import { ComponentPublicInstance } from './componentPublicInstance' import { Directive, validateDirectiveName } from './directives' -import { RootRenderFunction } from './renderer' +import { ElementNamespace, RootRenderFunction } from './renderer' import { InjectionKey } from './apiInject' import { warn } from './warning' import { createVNode, cloneVNode, VNode } from './vnode' @@ -47,7 +47,7 @@ export interface App { mount( rootContainer: HostElement | string, isHydrate?: boolean, - isSVG?: boolean + namespace?: boolean | ElementNamespace ): ComponentPublicInstance unmount(): void provide(key: InjectionKey | string, value: T): this @@ -297,7 +297,7 @@ export function createAppAPI( mount( rootContainer: HostElement, isHydrate?: boolean, - isSVG?: boolean + namespace?: boolean | ElementNamespace ): any { if (!isMounted) { // #5571 @@ -313,17 +313,29 @@ export function createAppAPI( // this will be set on the root instance on initial mount. vnode.appContext = context + if (namespace === true) { + namespace = 'svg' + } else if (namespace === false) { + namespace = undefined + } + // HMR root reload if (__DEV__) { context.reload = () => { - render(cloneVNode(vnode), rootContainer, isSVG) + // casting to ElementNamespace because TS doesn't guarantee type narrowing + // over function boundaries + render( + cloneVNode(vnode), + rootContainer, + namespace as ElementNamespace + ) } } if (isHydrate && hydrate) { hydrate(vnode as VNode, rootContainer as any) } else { - render(vnode, rootContainer, isSVG) + render(vnode, rootContainer, namespace) } isMounted = true app._container = rootContainer diff --git a/packages/runtime-core/src/compat/global.ts b/packages/runtime-core/src/compat/global.ts index 2efceb87fa6..0379bb67e8e 100644 --- a/packages/runtime-core/src/compat/global.ts +++ b/packages/runtime-core/src/compat/global.ts @@ -17,7 +17,7 @@ import { } from '@vue/shared' import { warn } from '../warning' import { cloneVNode, createVNode } from '../vnode' -import { RootRenderFunction } from '../renderer' +import { ElementNamespace, RootRenderFunction } from '../renderer' import { App, AppConfig, @@ -503,7 +503,13 @@ function installCompatMount( container = selectorOrEl || document.createElement('div') } - const isSVG = container instanceof SVGElement + let namespace: ElementNamespace + if (container instanceof SVGElement) namespace = 'svg' + else if ( + typeof MathMLElement === 'function' && + container instanceof MathMLElement + ) + namespace = 'mathml' // HMR root reload if (__DEV__) { @@ -511,7 +517,7 @@ function installCompatMount( const cloned = cloneVNode(vnode) // compat mode will use instance if not reset to null cloned.component = null - render(cloned, container, isSVG) + render(cloned, container, namespace) } } @@ -538,7 +544,7 @@ function installCompatMount( container.innerHTML = '' // TODO hydration - render(vnode, container, isSVG) + render(vnode, container, namespace) if (container instanceof Element) { container.removeAttribute('v-cloak') diff --git a/packages/runtime-core/src/components/KeepAlive.ts b/packages/runtime-core/src/components/KeepAlive.ts index 8c1b6318887..8f9a94f1109 100644 --- a/packages/runtime-core/src/components/KeepAlive.ts +++ b/packages/runtime-core/src/components/KeepAlive.ts @@ -37,7 +37,8 @@ import { queuePostRenderEffect, MoveType, RendererElement, - RendererNode + RendererNode, + ElementNamespace } from '../renderer' import { setTransitionHooks } from './BaseTransition' import { ComponentRenderContext } from '../componentPublicInstance' @@ -64,7 +65,7 @@ export interface KeepAliveContext extends ComponentRenderContext { vnode: VNode, container: RendererElement, anchor: RendererNode | null, - isSVG: boolean, + namespace: ElementNamespace, optimized: boolean ) => void deactivate: (vnode: VNode) => void @@ -125,7 +126,13 @@ const KeepAliveImpl: ComponentOptions = { } = sharedContext const storageContainer = createElement('div') - sharedContext.activate = (vnode, container, anchor, isSVG, optimized) => { + sharedContext.activate = ( + vnode, + container, + anchor, + namespace, + optimized + ) => { const instance = vnode.component! move(vnode, container, anchor, MoveType.ENTER, parentSuspense) // in case props have changed @@ -136,7 +143,7 @@ const KeepAliveImpl: ComponentOptions = { anchor, instance, parentSuspense, - isSVG, + namespace, vnode.slotScopeIds, optimized ) diff --git a/packages/runtime-core/src/components/Suspense.ts b/packages/runtime-core/src/components/Suspense.ts index 5e5521b09be..3668418090b 100644 --- a/packages/runtime-core/src/components/Suspense.ts +++ b/packages/runtime-core/src/components/Suspense.ts @@ -18,7 +18,8 @@ import { MoveType, SetupRenderEffectFn, RendererNode, - RendererElement + RendererElement, + ElementNamespace } from '../renderer' import { queuePostFlushCb } from '../scheduler' import { filterSingleRoot, updateHOCHostEl } from '../componentRenderUtils' @@ -63,7 +64,7 @@ export const SuspenseImpl = { anchor: RendererNode | null, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, - isSVG: boolean, + namespace: ElementNamespace, slotScopeIds: string[] | null, optimized: boolean, // platform-specific impl passed from renderer @@ -76,7 +77,7 @@ export const SuspenseImpl = { anchor, parentComponent, parentSuspense, - isSVG, + namespace, slotScopeIds, optimized, rendererInternals @@ -88,7 +89,7 @@ export const SuspenseImpl = { container, anchor, parentComponent, - isSVG, + namespace, slotScopeIds, optimized, rendererInternals @@ -130,7 +131,7 @@ function mountSuspense( anchor: RendererNode | null, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, - isSVG: boolean, + namespace: ElementNamespace, slotScopeIds: string[] | null, optimized: boolean, rendererInternals: RendererInternals @@ -147,7 +148,7 @@ function mountSuspense( container, hiddenContainer, anchor, - isSVG, + namespace, slotScopeIds, optimized, rendererInternals @@ -161,7 +162,7 @@ function mountSuspense( null, parentComponent, suspense, - isSVG, + namespace, slotScopeIds ) // now check if we have encountered any async deps @@ -179,7 +180,7 @@ function mountSuspense( anchor, parentComponent, null, // fallback tree will not have suspense context - isSVG, + namespace, slotScopeIds ) setActiveBranch(suspense, vnode.ssFallback!) @@ -195,7 +196,7 @@ function patchSuspense( container: RendererElement, anchor: RendererNode | null, parentComponent: ComponentInternalInstance | null, - isSVG: boolean, + namespace: ElementNamespace, slotScopeIds: string[] | null, optimized: boolean, { p: patch, um: unmount, o: { createElement } }: RendererInternals @@ -218,7 +219,7 @@ function patchSuspense( null, parentComponent, suspense, - isSVG, + namespace, slotScopeIds, optimized ) @@ -232,7 +233,7 @@ function patchSuspense( anchor, parentComponent, null, // fallback tree will not have suspense context - isSVG, + namespace, slotScopeIds, optimized ) @@ -267,7 +268,7 @@ function patchSuspense( null, parentComponent, suspense, - isSVG, + namespace, slotScopeIds, optimized ) @@ -281,7 +282,7 @@ function patchSuspense( anchor, parentComponent, null, // fallback tree will not have suspense context - isSVG, + namespace, slotScopeIds, optimized ) @@ -296,7 +297,7 @@ function patchSuspense( anchor, parentComponent, suspense, - isSVG, + namespace, slotScopeIds, optimized ) @@ -311,7 +312,7 @@ function patchSuspense( null, parentComponent, suspense, - isSVG, + namespace, slotScopeIds, optimized ) @@ -330,7 +331,7 @@ function patchSuspense( anchor, parentComponent, suspense, - isSVG, + namespace, slotScopeIds, optimized ) @@ -349,7 +350,7 @@ function patchSuspense( null, parentComponent, suspense, - isSVG, + namespace, slotScopeIds, optimized ) @@ -376,7 +377,7 @@ export interface SuspenseBoundary { vnode: VNode parent: SuspenseBoundary | null parentComponent: ComponentInternalInstance | null - isSVG: boolean + namespace: ElementNamespace container: RendererElement hiddenContainer: RendererElement anchor: RendererNode | null @@ -413,7 +414,7 @@ function createSuspenseBoundary( container: RendererElement, hiddenContainer: RendererElement, anchor: RendererNode | null, - isSVG: boolean, + namespace: ElementNamespace, slotScopeIds: string[] | null, optimized: boolean, rendererInternals: RendererInternals, @@ -455,7 +456,7 @@ function createSuspenseBoundary( vnode, parent: parentSuspense, parentComponent, - isSVG, + namespace, container, hiddenContainer, anchor, @@ -576,7 +577,7 @@ function createSuspenseBoundary( return } - const { vnode, activeBranch, parentComponent, container, isSVG } = + const { vnode, activeBranch, parentComponent, container, namespace } = suspense // invoke @fallback event @@ -594,7 +595,7 @@ function createSuspenseBoundary( next(activeBranch!), parentComponent, null, // fallback tree will not have suspense context - isSVG, + namespace, slotScopeIds, optimized ) @@ -675,7 +676,7 @@ function createSuspenseBoundary( // consider the comment placeholder case. hydratedEl ? null : next(instance.subTree), suspense, - isSVG, + namespace, optimized ) if (placeholder) { @@ -721,7 +722,7 @@ function hydrateSuspense( vnode: VNode, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, - isSVG: boolean, + namespace: ElementNamespace, slotScopeIds: string[] | null, optimized: boolean, rendererInternals: RendererInternals, @@ -742,7 +743,7 @@ function hydrateSuspense( node.parentNode!, document.createElement('div'), null, - isSVG, + namespace, slotScopeIds, optimized, rendererInternals, diff --git a/packages/runtime-core/src/components/Teleport.ts b/packages/runtime-core/src/components/Teleport.ts index d1327db8ee8..aae7deff336 100644 --- a/packages/runtime-core/src/components/Teleport.ts +++ b/packages/runtime-core/src/components/Teleport.ts @@ -6,7 +6,8 @@ import { RendererElement, RendererNode, RendererOptions, - traverseStaticChildren + traverseStaticChildren, + ElementNamespace } from '../renderer' import { VNode, VNodeArrayChildren, VNodeProps } from '../vnode' import { isString, ShapeFlags } from '@vue/shared' @@ -28,6 +29,9 @@ const isTeleportDisabled = (props: VNode['props']): boolean => const isTargetSVG = (target: RendererElement): boolean => typeof SVGElement !== 'undefined' && target instanceof SVGElement +const isTargetMathML = (target: RendererElement): boolean => + typeof MathMLElement === 'function' && target instanceof MathMLElement + const resolveTarget = ( props: TeleportProps | null, select: RendererOptions['querySelector'] @@ -72,7 +76,7 @@ export const TeleportImpl = { anchor: RendererNode | null, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, - isSVG: boolean, + namespace: ElementNamespace, slotScopeIds: string[] | null, optimized: boolean, internals: RendererInternals @@ -109,7 +113,11 @@ export const TeleportImpl = { if (target) { insert(targetAnchor, target) // #2652 we could be teleporting from a non-SVG tree into an SVG tree - isSVG = isSVG || isTargetSVG(target) + if (namespace === 'svg' || isTargetSVG(target)) { + namespace = 'svg' + } else if (namespace === 'mathml' || isTargetMathML(target)) { + namespace = 'mathml' + } } else if (__DEV__ && !disabled) { warn('Invalid Teleport target on mount:', target, `(${typeof target})`) } @@ -124,7 +132,7 @@ export const TeleportImpl = { anchor, parentComponent, parentSuspense, - isSVG, + namespace, slotScopeIds, optimized ) @@ -145,7 +153,12 @@ export const TeleportImpl = { const wasDisabled = isTeleportDisabled(n1.props) const currentContainer = wasDisabled ? container : target const currentAnchor = wasDisabled ? mainAnchor : targetAnchor - isSVG = isSVG || isTargetSVG(target) + + if (namespace === 'svg' || isTargetSVG(target)) { + namespace = 'svg' + } else if (namespace === 'mathml' || isTargetMathML(target)) { + namespace = 'mathml' + } if (dynamicChildren) { // fast path when the teleport happens to be a block root @@ -155,7 +168,7 @@ export const TeleportImpl = { currentContainer, parentComponent, parentSuspense, - isSVG, + namespace, slotScopeIds ) // even in block tree mode we need to make sure all root-level nodes @@ -170,7 +183,7 @@ export const TeleportImpl = { currentAnchor, parentComponent, parentSuspense, - isSVG, + namespace, slotScopeIds, false ) diff --git a/packages/runtime-core/src/hydration.ts b/packages/runtime-core/src/hydration.ts index d79c09d3d36..156327dd880 100644 --- a/packages/runtime-core/src/hydration.ts +++ b/packages/runtime-core/src/hydration.ts @@ -39,7 +39,17 @@ enum DOMNodeTypes { let hasMismatch = false const isSVGContainer = (container: Element) => - /svg/.test(container.namespaceURI!) && container.tagName !== 'foreignObject' + container.namespaceURI!.includes('svg') && + container.tagName !== 'foreignObject' + +const isMathMLContainer = (container: Element) => + container.namespaceURI!.includes('MathML') + +const getContainerType = (container: Element): 'svg' | 'mathml' | undefined => { + if (isSVGContainer(container)) return 'svg' + if (isMathMLContainer(container)) return 'mathml' + return undefined +} const isComment = (node: Node): node is Comment => node.nodeType === DOMNodeTypes.COMMENT @@ -263,7 +273,7 @@ export function createHydrationFunctions( null, parentComponent, parentSuspense, - isSVGContainer(container), + getContainerType(container), optimized ) @@ -306,7 +316,7 @@ export function createHydrationFunctions( vnode, parentComponent, parentSuspense, - isSVGContainer(parentNode(node)!), + getContainerType(parentNode(node)!), slotScopeIds, optimized, rendererInternals, @@ -364,7 +374,7 @@ export function createHydrationFunctions( key, null, props[key], - false, + undefined, undefined, parentComponent ) @@ -378,7 +388,7 @@ export function createHydrationFunctions( 'onClick', null, props.onClick, - false, + undefined, undefined, parentComponent ) @@ -519,7 +529,7 @@ export function createHydrationFunctions( null, parentComponent, parentSuspense, - isSVGContainer(container), + getContainerType(container), slotScopeIds ) } @@ -611,7 +621,7 @@ export function createHydrationFunctions( next, parentComponent, parentSuspense, - isSVGContainer(container), + getContainerType(container), slotScopeIds ) return next diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index fcc460c43d0..f8d21242b83 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -260,7 +260,8 @@ export type { RendererElement, HydrationRenderer, RendererOptions, - RootRenderFunction + RootRenderFunction, + ElementNamespace } from './renderer' export type { RootHydrateFunction } from './hydration' export type { Slot, Slots, SlotsType } from './componentSlots' diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index fc762af3d96..52a3aa1404c 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -83,10 +83,12 @@ export interface HydrationRenderer extends Renderer { hydrate: RootHydrateFunction } +export type ElementNamespace = 'svg' | 'mathml' | undefined + export type RootRenderFunction = ( vnode: VNode | null, container: HostElement, - isSVG?: boolean + namespace?: ElementNamespace ) => void export interface RendererOptions< @@ -98,7 +100,7 @@ export interface RendererOptions< key: string, prevValue: any, nextValue: any, - isSVG?: boolean, + namespace?: ElementNamespace, prevChildren?: VNode[], parentComponent?: ComponentInternalInstance | null, parentSuspense?: SuspenseBoundary | null, @@ -108,7 +110,7 @@ export interface RendererOptions< remove(el: HostNode): void createElement( type: string, - isSVG?: boolean, + namespace?: ElementNamespace, isCustomizedBuiltIn?: string, vnodeProps?: (VNodeProps & { [key: string]: any }) | null ): HostElement @@ -125,7 +127,7 @@ export interface RendererOptions< content: string, parent: HostElement, anchor: HostNode | null, - isSVG: boolean, + namespace: ElementNamespace, start?: HostNode | null, end?: HostNode | null ): [HostNode, HostNode] @@ -170,7 +172,7 @@ type PatchFn = ( anchor?: RendererNode | null, parentComponent?: ComponentInternalInstance | null, parentSuspense?: SuspenseBoundary | null, - isSVG?: boolean, + namespace?: ElementNamespace, slotScopeIds?: string[] | null, optimized?: boolean ) => void @@ -181,7 +183,7 @@ type MountChildrenFn = ( anchor: RendererNode | null, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, - isSVG: boolean, + namespace: ElementNamespace, slotScopeIds: string[] | null, optimized: boolean, start?: number @@ -194,7 +196,7 @@ type PatchChildrenFn = ( anchor: RendererNode | null, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, - isSVG: boolean, + namespace: ElementNamespace, slotScopeIds: string[] | null, optimized: boolean ) => void @@ -205,7 +207,7 @@ type PatchBlockChildrenFn = ( fallbackContainer: RendererElement, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, - isSVG: boolean, + namespace: ElementNamespace, slotScopeIds: string[] | null ) => void @@ -244,7 +246,7 @@ export type MountComponentFn = ( anchor: RendererNode | null, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, - isSVG: boolean, + namespace: ElementNamespace, optimized: boolean ) => void @@ -261,7 +263,7 @@ export type SetupRenderEffectFn = ( container: RendererElement, anchor: RendererNode | null, parentSuspense: SuspenseBoundary | null, - isSVG: boolean, + namespace: ElementNamespace, optimized: boolean ) => void @@ -362,7 +364,7 @@ function baseCreateRenderer( anchor = null, parentComponent = null, parentSuspense = null, - isSVG = false, + namespace = undefined, slotScopeIds = null, optimized = __DEV__ && isHmrUpdating ? false : !!n2.dynamicChildren ) => { @@ -392,9 +394,9 @@ function baseCreateRenderer( break case Static: if (n1 == null) { - mountStaticNode(n2, container, anchor, isSVG) + mountStaticNode(n2, container, anchor, namespace) } else if (__DEV__) { - patchStaticNode(n1, n2, container, isSVG) + patchStaticNode(n1, n2, container, namespace) } break case Fragment: @@ -405,7 +407,7 @@ function baseCreateRenderer( anchor, parentComponent, parentSuspense, - isSVG, + namespace, slotScopeIds, optimized ) @@ -419,7 +421,7 @@ function baseCreateRenderer( anchor, parentComponent, parentSuspense, - isSVG, + namespace, slotScopeIds, optimized ) @@ -431,7 +433,7 @@ function baseCreateRenderer( anchor, parentComponent, parentSuspense, - isSVG, + namespace, slotScopeIds, optimized ) @@ -443,7 +445,7 @@ function baseCreateRenderer( anchor, parentComponent, parentSuspense, - isSVG, + namespace, slotScopeIds, optimized, internals @@ -456,7 +458,7 @@ function baseCreateRenderer( anchor, parentComponent, parentSuspense, - isSVG, + namespace, slotScopeIds, optimized, internals @@ -509,7 +511,7 @@ function baseCreateRenderer( n2: VNode, container: RendererElement, anchor: RendererNode | null, - isSVG: boolean + namespace: ElementNamespace ) => { // static nodes are only present when used with compiler-dom/runtime-dom // which guarantees presence of hostInsertStaticContent. @@ -517,7 +519,7 @@ function baseCreateRenderer( n2.children as string, container, anchor, - isSVG, + namespace, n2.el, n2.anchor ) @@ -530,7 +532,7 @@ function baseCreateRenderer( n1: VNode, n2: VNode, container: RendererElement, - isSVG: boolean + namespace: ElementNamespace ) => { // static nodes are only patched during dev for HMR if (n2.children !== n1.children) { @@ -542,7 +544,7 @@ function baseCreateRenderer( n2.children as string, container, anchor, - isSVG + namespace ) } else { n2.el = n1.el @@ -581,11 +583,16 @@ function baseCreateRenderer( anchor: RendererNode | null, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, - isSVG: boolean, + namespace: ElementNamespace, slotScopeIds: string[] | null, optimized: boolean ) => { - isSVG = isSVG || n2.type === 'svg' + if (n2.type === 'svg') { + namespace = 'svg' + } else if (n2.type === 'math') { + namespace = 'mathml' + } + if (n1 == null) { mountElement( n2, @@ -593,7 +600,7 @@ function baseCreateRenderer( anchor, parentComponent, parentSuspense, - isSVG, + namespace, slotScopeIds, optimized ) @@ -603,7 +610,7 @@ function baseCreateRenderer( n2, parentComponent, parentSuspense, - isSVG, + namespace, slotScopeIds, optimized ) @@ -616,17 +623,17 @@ function baseCreateRenderer( anchor: RendererNode | null, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, - isSVG: boolean, + namespace: ElementNamespace, slotScopeIds: string[] | null, optimized: boolean ) => { let el: RendererElement let vnodeHook: VNodeHook | undefined | null - const { type, props, shapeFlag, transition, dirs } = vnode + const { props, shapeFlag, transition, dirs } = vnode el = vnode.el = hostCreateElement( vnode.type as string, - isSVG, + namespace, props && props.is, props ) @@ -642,7 +649,7 @@ function baseCreateRenderer( null, parentComponent, parentSuspense, - isSVG && type !== 'foreignObject', + resolveChildrenNamespace(vnode, namespace), slotScopeIds, optimized ) @@ -662,7 +669,7 @@ function baseCreateRenderer( key, null, props[key], - isSVG, + namespace, vnode.children as VNode[], parentComponent, parentSuspense, @@ -680,7 +687,7 @@ function baseCreateRenderer( * affect non-DOM renderers) */ if ('value' in props) { - hostPatchProp(el, 'value', null, props.value) + hostPatchProp(el, 'value', null, props.value, namespace) } if ((vnodeHook = props.onVnodeBeforeMount)) { invokeVNodeHook(vnodeHook, parentComponent, vnode) @@ -764,7 +771,7 @@ function baseCreateRenderer( anchor, parentComponent, parentSuspense, - isSVG, + namespace: ElementNamespace, slotScopeIds, optimized, start = 0 @@ -780,7 +787,7 @@ function baseCreateRenderer( anchor, parentComponent, parentSuspense, - isSVG, + namespace, slotScopeIds, optimized ) @@ -792,7 +799,7 @@ function baseCreateRenderer( n2: VNode, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, - isSVG: boolean, + namespace: ElementNamespace, slotScopeIds: string[] | null, optimized: boolean ) => { @@ -822,7 +829,6 @@ function baseCreateRenderer( dynamicChildren = null } - const areChildrenSVG = isSVG && n2.type !== 'foreignObject' if (dynamicChildren) { patchBlockChildren( n1.dynamicChildren!, @@ -830,7 +836,7 @@ function baseCreateRenderer( el, parentComponent, parentSuspense, - areChildrenSVG, + resolveChildrenNamespace(n2, namespace), slotScopeIds ) if (__DEV__) { @@ -846,7 +852,7 @@ function baseCreateRenderer( null, parentComponent, parentSuspense, - areChildrenSVG, + resolveChildrenNamespace(n2, namespace), slotScopeIds, false ) @@ -866,21 +872,21 @@ function baseCreateRenderer( newProps, parentComponent, parentSuspense, - isSVG + namespace ) } else { // class // this flag is matched when the element has dynamic class bindings. if (patchFlag & PatchFlags.CLASS) { if (oldProps.class !== newProps.class) { - hostPatchProp(el, 'class', null, newProps.class, isSVG) + hostPatchProp(el, 'class', null, newProps.class, namespace) } } // style // this flag is matched when the element has dynamic style bindings if (patchFlag & PatchFlags.STYLE) { - hostPatchProp(el, 'style', oldProps.style, newProps.style, isSVG) + hostPatchProp(el, 'style', oldProps.style, newProps.style, namespace) } // props @@ -903,7 +909,7 @@ function baseCreateRenderer( key, prev, next, - isSVG, + namespace, n1.children as VNode[], parentComponent, parentSuspense, @@ -930,7 +936,7 @@ function baseCreateRenderer( newProps, parentComponent, parentSuspense, - isSVG + namespace ) } @@ -949,7 +955,7 @@ function baseCreateRenderer( fallbackContainer, parentComponent, parentSuspense, - isSVG, + namespace: ElementNamespace, slotScopeIds ) => { for (let i = 0; i < newChildren.length; i++) { @@ -979,7 +985,7 @@ function baseCreateRenderer( null, parentComponent, parentSuspense, - isSVG, + namespace, slotScopeIds, true ) @@ -993,7 +999,7 @@ function baseCreateRenderer( newProps: Data, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, - isSVG: boolean + namespace: ElementNamespace ) => { if (oldProps !== newProps) { if (oldProps !== EMPTY_OBJ) { @@ -1004,7 +1010,7 @@ function baseCreateRenderer( key, oldProps[key], null, - isSVG, + namespace, vnode.children as VNode[], parentComponent, parentSuspense, @@ -1025,7 +1031,7 @@ function baseCreateRenderer( key, prev, next, - isSVG, + namespace, vnode.children as VNode[], parentComponent, parentSuspense, @@ -1034,7 +1040,7 @@ function baseCreateRenderer( } } if ('value' in newProps) { - hostPatchProp(el, 'value', oldProps.value, newProps.value) + hostPatchProp(el, 'value', oldProps.value, newProps.value, namespace) } } } @@ -1046,7 +1052,7 @@ function baseCreateRenderer( anchor: RendererNode | null, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, - isSVG: boolean, + namespace: ElementNamespace, slotScopeIds: string[] | null, optimized: boolean ) => { @@ -1085,7 +1091,7 @@ function baseCreateRenderer( fragmentEndAnchor, parentComponent, parentSuspense, - isSVG, + namespace, slotScopeIds, optimized ) @@ -1106,7 +1112,7 @@ function baseCreateRenderer( container, parentComponent, parentSuspense, - isSVG, + namespace, slotScopeIds ) if (__DEV__) { @@ -1134,7 +1140,7 @@ function baseCreateRenderer( fragmentEndAnchor, parentComponent, parentSuspense, - isSVG, + namespace, slotScopeIds, optimized ) @@ -1149,7 +1155,7 @@ function baseCreateRenderer( anchor: RendererNode | null, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, - isSVG: boolean, + namespace: ElementNamespace, slotScopeIds: string[] | null, optimized: boolean ) => { @@ -1160,7 +1166,7 @@ function baseCreateRenderer( n2, container, anchor, - isSVG, + namespace, optimized ) } else { @@ -1170,7 +1176,7 @@ function baseCreateRenderer( anchor, parentComponent, parentSuspense, - isSVG, + namespace, optimized ) } @@ -1185,7 +1191,7 @@ function baseCreateRenderer( anchor, parentComponent, parentSuspense, - isSVG, + namespace: ElementNamespace, optimized ) => { // 2.x compat may pre-create the component instance before actually @@ -1245,7 +1251,7 @@ function baseCreateRenderer( container, anchor, parentSuspense, - isSVG, + namespace, optimized ) @@ -1296,7 +1302,7 @@ function baseCreateRenderer( container, anchor, parentSuspense, - isSVG, + namespace: ElementNamespace, optimized ) => { const componentUpdateFn = () => { @@ -1380,7 +1386,7 @@ function baseCreateRenderer( anchor, instance, parentSuspense, - isSVG + namespace ) if (__DEV__) { endMeasure(instance, `patch`) @@ -1499,7 +1505,7 @@ function baseCreateRenderer( getNextHostNode(prevTree), instance, parentSuspense, - isSVG + namespace ) if (__DEV__) { endMeasure(instance, `patch`) @@ -1599,7 +1605,7 @@ function baseCreateRenderer( anchor, parentComponent, parentSuspense, - isSVG, + namespace: ElementNamespace, slotScopeIds, optimized = false ) => { @@ -1620,7 +1626,7 @@ function baseCreateRenderer( anchor, parentComponent, parentSuspense, - isSVG, + namespace, slotScopeIds, optimized ) @@ -1634,7 +1640,7 @@ function baseCreateRenderer( anchor, parentComponent, parentSuspense, - isSVG, + namespace, slotScopeIds, optimized ) @@ -1663,7 +1669,7 @@ function baseCreateRenderer( anchor, parentComponent, parentSuspense, - isSVG, + namespace, slotScopeIds, optimized ) @@ -1685,7 +1691,7 @@ function baseCreateRenderer( anchor, parentComponent, parentSuspense, - isSVG, + namespace, slotScopeIds, optimized ) @@ -1701,7 +1707,7 @@ function baseCreateRenderer( anchor: RendererNode | null, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, - isSVG: boolean, + namespace: ElementNamespace, slotScopeIds: string[] | null, optimized: boolean ) => { @@ -1722,7 +1728,7 @@ function baseCreateRenderer( null, parentComponent, parentSuspense, - isSVG, + namespace, slotScopeIds, optimized ) @@ -1745,7 +1751,7 @@ function baseCreateRenderer( anchor, parentComponent, parentSuspense, - isSVG, + namespace, slotScopeIds, optimized, commonLength @@ -1761,7 +1767,7 @@ function baseCreateRenderer( parentAnchor: RendererNode | null, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, - isSVG: boolean, + namespace: ElementNamespace, slotScopeIds: string[] | null, optimized: boolean ) => { @@ -1786,7 +1792,7 @@ function baseCreateRenderer( null, parentComponent, parentSuspense, - isSVG, + namespace, slotScopeIds, optimized ) @@ -1812,7 +1818,7 @@ function baseCreateRenderer( null, parentComponent, parentSuspense, - isSVG, + namespace, slotScopeIds, optimized ) @@ -1844,7 +1850,7 @@ function baseCreateRenderer( anchor, parentComponent, parentSuspense, - isSVG, + namespace, slotScopeIds, optimized ) @@ -1947,7 +1953,7 @@ function baseCreateRenderer( null, parentComponent, parentSuspense, - isSVG, + namespace, slotScopeIds, optimized ) @@ -1976,7 +1982,7 @@ function baseCreateRenderer( anchor, parentComponent, parentSuspense, - isSVG, + namespace, slotScopeIds, optimized ) @@ -2321,13 +2327,21 @@ function baseCreateRenderer( return hostNextSibling((vnode.anchor || vnode.el)!) } - const render: RootRenderFunction = (vnode, container, isSVG) => { + const render: RootRenderFunction = (vnode, container, namespace) => { if (vnode == null) { if (container._vnode) { unmount(container._vnode, null, null, true) } } else { - patch(container._vnode || null, vnode, container, null, null, null, isSVG) + patch( + container._vnode || null, + vnode, + container, + null, + null, + null, + namespace + ) } flushPreFlushCbs() flushPostFlushCbs() @@ -2362,6 +2376,20 @@ function baseCreateRenderer( } } +function resolveChildrenNamespace( + { type, props }: VNode, + currentNamespace: ElementNamespace +): ElementNamespace { + return (currentNamespace === 'svg' && type === 'foreignObject') || + (currentNamespace === 'mathml' && + type === 'annotation-xml' && + props && + props.encoding && + props.encoding.includes('html')) + ? undefined + : currentNamespace +} + function toggleRecurse( { effect, update }: ComponentInternalInstance, allowed: boolean diff --git a/packages/runtime-dom/__tests__/nodeOps.spec.ts b/packages/runtime-dom/__tests__/nodeOps.spec.ts index 2573974c955..87adafd6696 100644 --- a/packages/runtime-dom/__tests__/nodeOps.spec.ts +++ b/packages/runtime-dom/__tests__/nodeOps.spec.ts @@ -2,7 +2,7 @@ import { nodeOps, svgNS } from '../src/nodeOps' describe('runtime-dom: node-ops', () => { test("the