From 6e6a496eff9e40b404c692f42b4b5a459b7bc6bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20G=C3=BCndel?= <5798652+sto3psl@users.noreply.github.com> Date: Fri, 3 Mar 2023 15:33:07 +0100 Subject: [PATCH 01/14] feat: MathML support --- packages/compiler-dom/src/parserOptions.ts | 4 +- packages/runtime-core/src/apiCreateApp.ts | 8 +- packages/runtime-core/src/compat/global.ts | 8 +- .../runtime-core/src/components/KeepAlive.ts | 12 +- .../runtime-core/src/components/Suspense.ts | 48 ++--- .../runtime-core/src/components/Teleport.ts | 26 ++- packages/runtime-core/src/hydration.ts | 21 +- packages/runtime-core/src/renderer.ts | 179 ++++++++++-------- .../runtime-dom/__tests__/nodeOps.spec.ts | 22 ++- .../runtime-dom/__tests__/patchAttrs.spec.ts | 6 +- .../runtime-dom/__tests__/patchClass.spec.ts | 2 +- packages/runtime-dom/src/index.ts | 15 +- packages/runtime-dom/src/modules/attrs.ts | 4 +- packages/runtime-dom/src/modules/class.ts | 8 +- packages/runtime-dom/src/nodeOps.ts | 37 +++- packages/runtime-dom/src/patchProp.ts | 12 +- packages/shared/src/domTagConfig.ts | 12 ++ .../vue/__tests__/mathmlNamespace.spec.ts | 74 ++++++++ packages/vue/__tests__/svgNamespace.spec.ts | 6 +- scripts/setupVitest.ts | 2 + 20 files changed, 351 insertions(+), 155 deletions(-) create mode 100644 packages/vue/__tests__/mathmlNamespace.spec.ts 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..88cedb2830d 100644 --- a/packages/runtime-core/src/apiCreateApp.ts +++ b/packages/runtime-core/src/apiCreateApp.ts @@ -47,7 +47,7 @@ export interface App { mount( rootContainer: HostElement | string, isHydrate?: boolean, - isSVG?: boolean + namespace?: 'svg' | 'mathml' ): 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?: 'svg' | 'mathml' ): any { if (!isMounted) { // #5571 @@ -316,14 +316,14 @@ export function createAppAPI( // HMR root reload if (__DEV__) { context.reload = () => { - render(cloneVNode(vnode), rootContainer, isSVG) + render(cloneVNode(vnode), rootContainer, namespace) } } 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..413c42e10a4 100644 --- a/packages/runtime-core/src/compat/global.ts +++ b/packages/runtime-core/src/compat/global.ts @@ -503,7 +503,9 @@ function installCompatMount( container = selectorOrEl || document.createElement('div') } - const isSVG = container instanceof SVGElement + let namespace: 'svg' | 'mathml' | undefined + if (container instanceof SVGElement) namespace = 'svg' + if (container instanceof MathMLElement) namespace = 'mathml' // HMR root reload if (__DEV__) { @@ -511,7 +513,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 +540,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..44bbde6d3f8 100644 --- a/packages/runtime-core/src/components/KeepAlive.ts +++ b/packages/runtime-core/src/components/KeepAlive.ts @@ -64,7 +64,7 @@ export interface KeepAliveContext extends ComponentRenderContext { vnode: VNode, container: RendererElement, anchor: RendererNode | null, - isSVG: boolean, + namespace: 'svg' | 'mathml' | undefined, optimized: boolean ) => void deactivate: (vnode: VNode) => void @@ -125,7 +125,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 +142,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..262fe973e0a 100644 --- a/packages/runtime-core/src/components/Suspense.ts +++ b/packages/runtime-core/src/components/Suspense.ts @@ -63,7 +63,7 @@ export const SuspenseImpl = { anchor: RendererNode | null, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, - isSVG: boolean, + namespace: 'svg' | 'mathml' | undefined, slotScopeIds: string[] | null, optimized: boolean, // platform-specific impl passed from renderer @@ -76,7 +76,7 @@ export const SuspenseImpl = { anchor, parentComponent, parentSuspense, - isSVG, + namespace, slotScopeIds, optimized, rendererInternals @@ -88,7 +88,7 @@ export const SuspenseImpl = { container, anchor, parentComponent, - isSVG, + namespace, slotScopeIds, optimized, rendererInternals @@ -130,7 +130,7 @@ function mountSuspense( anchor: RendererNode | null, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, - isSVG: boolean, + namespace: 'svg' | 'mathml' | undefined, slotScopeIds: string[] | null, optimized: boolean, rendererInternals: RendererInternals @@ -147,7 +147,7 @@ function mountSuspense( container, hiddenContainer, anchor, - isSVG, + namespace, slotScopeIds, optimized, rendererInternals @@ -161,7 +161,7 @@ function mountSuspense( null, parentComponent, suspense, - isSVG, + namespace, slotScopeIds ) // now check if we have encountered any async deps @@ -179,7 +179,7 @@ function mountSuspense( anchor, parentComponent, null, // fallback tree will not have suspense context - isSVG, + namespace, slotScopeIds ) setActiveBranch(suspense, vnode.ssFallback!) @@ -195,7 +195,7 @@ function patchSuspense( container: RendererElement, anchor: RendererNode | null, parentComponent: ComponentInternalInstance | null, - isSVG: boolean, + namespace: 'svg' | 'mathml' | undefined, slotScopeIds: string[] | null, optimized: boolean, { p: patch, um: unmount, o: { createElement } }: RendererInternals @@ -218,7 +218,7 @@ function patchSuspense( null, parentComponent, suspense, - isSVG, + namespace, slotScopeIds, optimized ) @@ -232,7 +232,7 @@ function patchSuspense( anchor, parentComponent, null, // fallback tree will not have suspense context - isSVG, + namespace, slotScopeIds, optimized ) @@ -267,7 +267,7 @@ function patchSuspense( null, parentComponent, suspense, - isSVG, + namespace, slotScopeIds, optimized ) @@ -281,7 +281,7 @@ function patchSuspense( anchor, parentComponent, null, // fallback tree will not have suspense context - isSVG, + namespace, slotScopeIds, optimized ) @@ -296,7 +296,7 @@ function patchSuspense( anchor, parentComponent, suspense, - isSVG, + namespace, slotScopeIds, optimized ) @@ -311,7 +311,7 @@ function patchSuspense( null, parentComponent, suspense, - isSVG, + namespace, slotScopeIds, optimized ) @@ -330,7 +330,7 @@ function patchSuspense( anchor, parentComponent, suspense, - isSVG, + namespace, slotScopeIds, optimized ) @@ -349,7 +349,7 @@ function patchSuspense( null, parentComponent, suspense, - isSVG, + namespace, slotScopeIds, optimized ) @@ -376,7 +376,7 @@ export interface SuspenseBoundary { vnode: VNode parent: SuspenseBoundary | null parentComponent: ComponentInternalInstance | null - isSVG: boolean + namespace: 'svg' | 'mathml' | undefined container: RendererElement hiddenContainer: RendererElement anchor: RendererNode | null @@ -413,7 +413,7 @@ function createSuspenseBoundary( container: RendererElement, hiddenContainer: RendererElement, anchor: RendererNode | null, - isSVG: boolean, + namespace: 'svg' | 'mathml' | undefined, slotScopeIds: string[] | null, optimized: boolean, rendererInternals: RendererInternals, @@ -455,7 +455,7 @@ function createSuspenseBoundary( vnode, parent: parentSuspense, parentComponent, - isSVG, + namespace, container, hiddenContainer, anchor, @@ -576,7 +576,7 @@ function createSuspenseBoundary( return } - const { vnode, activeBranch, parentComponent, container, isSVG } = + const { vnode, activeBranch, parentComponent, container, namespace } = suspense // invoke @fallback event @@ -594,7 +594,7 @@ function createSuspenseBoundary( next(activeBranch!), parentComponent, null, // fallback tree will not have suspense context - isSVG, + namespace, slotScopeIds, optimized ) @@ -675,7 +675,7 @@ function createSuspenseBoundary( // consider the comment placeholder case. hydratedEl ? null : next(instance.subTree), suspense, - isSVG, + namespace, optimized ) if (placeholder) { @@ -721,7 +721,7 @@ function hydrateSuspense( vnode: VNode, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, - isSVG: boolean, + namespace: 'svg' | 'mathml' | undefined, slotScopeIds: string[] | null, optimized: boolean, rendererInternals: RendererInternals, @@ -742,7 +742,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..7e8fb1ed9fb 100644 --- a/packages/runtime-core/src/components/Teleport.ts +++ b/packages/runtime-core/src/components/Teleport.ts @@ -28,6 +28,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 !== 'undefined' && target instanceof MathMLElement + const resolveTarget = ( props: TeleportProps | null, select: RendererOptions['querySelector'] @@ -72,7 +75,7 @@ export const TeleportImpl = { anchor: RendererNode | null, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, - isSVG: boolean, + namespace: 'svg' | 'mathml' | undefined, slotScopeIds: string[] | null, optimized: boolean, internals: RendererInternals @@ -109,7 +112,12 @@ 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' + } + 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,13 @@ 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' + } + if (namespace === 'mathml' || isTargetMathML(target)) { + namespace = 'mathml' + } if (dynamicChildren) { // fast path when the teleport happens to be a block root @@ -155,7 +169,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 +184,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..52395791561 100644 --- a/packages/runtime-core/src/hydration.ts +++ b/packages/runtime-core/src/hydration.ts @@ -41,6 +41,15 @@ let hasMismatch = false const isSVGContainer = (container: Element) => /svg/.test(container.namespaceURI!) && container.tagName !== 'foreignObject' +const isMathMLContainer = (container: Element) => + /MathML/.test(container.namespaceURI!) + +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 +272,7 @@ export function createHydrationFunctions( null, parentComponent, parentSuspense, - isSVGContainer(container), + getContainerType(container), optimized ) @@ -306,7 +315,7 @@ export function createHydrationFunctions( vnode, parentComponent, parentSuspense, - isSVGContainer(parentNode(node)!), + getContainerType(parentNode(node)!), slotScopeIds, optimized, rendererInternals, @@ -364,7 +373,7 @@ export function createHydrationFunctions( key, null, props[key], - false, + undefined, undefined, parentComponent ) @@ -378,7 +387,7 @@ export function createHydrationFunctions( 'onClick', null, props.onClick, - false, + undefined, undefined, parentComponent ) @@ -519,7 +528,7 @@ export function createHydrationFunctions( null, parentComponent, parentSuspense, - isSVGContainer(container), + getContainerType(container), slotScopeIds ) } @@ -611,7 +620,7 @@ export function createHydrationFunctions( next, parentComponent, parentSuspense, - isSVGContainer(container), + getContainerType(container), slotScopeIds ) return next diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index fc762af3d96..e82bb07fad6 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -86,7 +86,7 @@ export interface HydrationRenderer extends Renderer { export type RootRenderFunction = ( vnode: VNode | null, container: HostElement, - isSVG?: boolean + namespace?: 'svg' | 'mathml' ) => void export interface RendererOptions< @@ -98,7 +98,7 @@ export interface RendererOptions< key: string, prevValue: any, nextValue: any, - isSVG?: boolean, + namespace?: 'svg' | 'mathml', prevChildren?: VNode[], parentComponent?: ComponentInternalInstance | null, parentSuspense?: SuspenseBoundary | null, @@ -108,7 +108,7 @@ export interface RendererOptions< remove(el: HostNode): void createElement( type: string, - isSVG?: boolean, + namespace?: 'svg' | 'mathml', isCustomizedBuiltIn?: string, vnodeProps?: (VNodeProps & { [key: string]: any }) | null ): HostElement @@ -125,7 +125,7 @@ export interface RendererOptions< content: string, parent: HostElement, anchor: HostNode | null, - isSVG: boolean, + namespace: 'svg' | 'mathml' | undefined, start?: HostNode | null, end?: HostNode | null ): [HostNode, HostNode] @@ -170,7 +170,7 @@ type PatchFn = ( anchor?: RendererNode | null, parentComponent?: ComponentInternalInstance | null, parentSuspense?: SuspenseBoundary | null, - isSVG?: boolean, + namespace?: 'svg' | 'mathml', slotScopeIds?: string[] | null, optimized?: boolean ) => void @@ -181,7 +181,7 @@ type MountChildrenFn = ( anchor: RendererNode | null, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, - isSVG: boolean, + namespace: 'svg' | 'mathml' | undefined, slotScopeIds: string[] | null, optimized: boolean, start?: number @@ -194,7 +194,7 @@ type PatchChildrenFn = ( anchor: RendererNode | null, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, - isSVG: boolean, + namespace: 'svg' | 'mathml' | undefined, slotScopeIds: string[] | null, optimized: boolean ) => void @@ -205,7 +205,7 @@ type PatchBlockChildrenFn = ( fallbackContainer: RendererElement, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, - isSVG: boolean, + namespace: 'svg' | 'mathml' | undefined, slotScopeIds: string[] | null ) => void @@ -244,7 +244,7 @@ export type MountComponentFn = ( anchor: RendererNode | null, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, - isSVG: boolean, + namespace: 'svg' | 'mathml' | undefined, optimized: boolean ) => void @@ -261,7 +261,7 @@ export type SetupRenderEffectFn = ( container: RendererElement, anchor: RendererNode | null, parentSuspense: SuspenseBoundary | null, - isSVG: boolean, + namespace: 'svg' | 'mathml' | undefined, optimized: boolean ) => void @@ -362,7 +362,7 @@ function baseCreateRenderer( anchor = null, parentComponent = null, parentSuspense = null, - isSVG = false, + namespace = undefined, slotScopeIds = null, optimized = __DEV__ && isHmrUpdating ? false : !!n2.dynamicChildren ) => { @@ -392,9 +392,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 +405,7 @@ function baseCreateRenderer( anchor, parentComponent, parentSuspense, - isSVG, + namespace, slotScopeIds, optimized ) @@ -419,7 +419,7 @@ function baseCreateRenderer( anchor, parentComponent, parentSuspense, - isSVG, + namespace, slotScopeIds, optimized ) @@ -431,7 +431,7 @@ function baseCreateRenderer( anchor, parentComponent, parentSuspense, - isSVG, + namespace, slotScopeIds, optimized ) @@ -443,7 +443,7 @@ function baseCreateRenderer( anchor, parentComponent, parentSuspense, - isSVG, + namespace, slotScopeIds, optimized, internals @@ -456,7 +456,7 @@ function baseCreateRenderer( anchor, parentComponent, parentSuspense, - isSVG, + namespace, slotScopeIds, optimized, internals @@ -509,7 +509,7 @@ function baseCreateRenderer( n2: VNode, container: RendererElement, anchor: RendererNode | null, - isSVG: boolean + namespace?: 'svg' | 'mathml' ) => { // static nodes are only present when used with compiler-dom/runtime-dom // which guarantees presence of hostInsertStaticContent. @@ -517,7 +517,7 @@ function baseCreateRenderer( n2.children as string, container, anchor, - isSVG, + namespace, n2.el, n2.anchor ) @@ -530,7 +530,7 @@ function baseCreateRenderer( n1: VNode, n2: VNode, container: RendererElement, - isSVG: boolean + namespace?: 'svg' | 'mathml' ) => { // static nodes are only patched during dev for HMR if (n2.children !== n1.children) { @@ -542,7 +542,7 @@ function baseCreateRenderer( n2.children as string, container, anchor, - isSVG + namespace ) } else { n2.el = n1.el @@ -581,11 +581,17 @@ function baseCreateRenderer( anchor: RendererNode | null, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, - isSVG: boolean, + namespace: 'svg' | 'mathml' | undefined, slotScopeIds: string[] | null, optimized: boolean ) => { - isSVG = isSVG || n2.type === 'svg' + if (n2.type === 'svg') { + namespace = 'svg' + } + if ((n2.type as string) === 'math') { + namespace = 'mathml' + } + if (n1 == null) { mountElement( n2, @@ -593,7 +599,7 @@ function baseCreateRenderer( anchor, parentComponent, parentSuspense, - isSVG, + namespace, slotScopeIds, optimized ) @@ -603,7 +609,7 @@ function baseCreateRenderer( n2, parentComponent, parentSuspense, - isSVG, + namespace, slotScopeIds, optimized ) @@ -616,7 +622,7 @@ function baseCreateRenderer( anchor: RendererNode | null, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, - isSVG: boolean, + namespace: 'svg' | 'mathml' | undefined, slotScopeIds: string[] | null, optimized: boolean ) => { @@ -626,7 +632,7 @@ function baseCreateRenderer( el = vnode.el = hostCreateElement( vnode.type as string, - isSVG, + namespace, props && props.is, props ) @@ -636,13 +642,22 @@ function baseCreateRenderer( if (shapeFlag & ShapeFlags.TEXT_CHILDREN) { hostSetElementText(el, vnode.children as string) } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { + // foreignObject (svg), annotation and annotation-xml (MathML) can embed content + // that is different from their namespace + if ( + type === 'foreignObject' || + type === 'annotation' || + type === 'annotation-xml' + ) { + namespace = undefined + } mountChildren( vnode.children as VNodeArrayChildren, el, null, parentComponent, parentSuspense, - isSVG && type !== 'foreignObject', + namespace, slotScopeIds, optimized ) @@ -662,7 +677,7 @@ function baseCreateRenderer( key, null, props[key], - isSVG, + namespace, vnode.children as VNode[], parentComponent, parentSuspense, @@ -764,7 +779,7 @@ function baseCreateRenderer( anchor, parentComponent, parentSuspense, - isSVG, + namespace: 'svg' | 'mathml' | undefined, slotScopeIds, optimized, start = 0 @@ -780,7 +795,7 @@ function baseCreateRenderer( anchor, parentComponent, parentSuspense, - isSVG, + namespace, slotScopeIds, optimized ) @@ -792,7 +807,7 @@ function baseCreateRenderer( n2: VNode, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, - isSVG: boolean, + namespace: 'svg' | 'mathml' | undefined, slotScopeIds: string[] | null, optimized: boolean ) => { @@ -822,7 +837,15 @@ function baseCreateRenderer( dynamicChildren = null } - const areChildrenSVG = isSVG && n2.type !== 'foreignObject' + // foreignObject (svg), annotation and annotation-xml (MathML) can embed content + // that is different from their namespace + if ( + n2.type === 'foreignObject' || + n2.type === 'annotation' || + n2.type === 'annotation-xml' + ) { + namespace = undefined + } if (dynamicChildren) { patchBlockChildren( n1.dynamicChildren!, @@ -830,7 +853,7 @@ function baseCreateRenderer( el, parentComponent, parentSuspense, - areChildrenSVG, + namespace, slotScopeIds ) if (__DEV__) { @@ -846,7 +869,7 @@ function baseCreateRenderer( null, parentComponent, parentSuspense, - areChildrenSVG, + namespace, slotScopeIds, false ) @@ -866,21 +889,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 +926,7 @@ function baseCreateRenderer( key, prev, next, - isSVG, + namespace, n1.children as VNode[], parentComponent, parentSuspense, @@ -930,7 +953,7 @@ function baseCreateRenderer( newProps, parentComponent, parentSuspense, - isSVG + namespace ) } @@ -949,7 +972,7 @@ function baseCreateRenderer( fallbackContainer, parentComponent, parentSuspense, - isSVG, + namespace: 'svg' | 'mathml' | undefined, slotScopeIds ) => { for (let i = 0; i < newChildren.length; i++) { @@ -979,7 +1002,7 @@ function baseCreateRenderer( null, parentComponent, parentSuspense, - isSVG, + namespace, slotScopeIds, true ) @@ -993,7 +1016,7 @@ function baseCreateRenderer( newProps: Data, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, - isSVG: boolean + namespace: 'svg' | 'mathml' | undefined ) => { if (oldProps !== newProps) { if (oldProps !== EMPTY_OBJ) { @@ -1004,7 +1027,7 @@ function baseCreateRenderer( key, oldProps[key], null, - isSVG, + namespace, vnode.children as VNode[], parentComponent, parentSuspense, @@ -1025,7 +1048,7 @@ function baseCreateRenderer( key, prev, next, - isSVG, + namespace, vnode.children as VNode[], parentComponent, parentSuspense, @@ -1046,7 +1069,7 @@ function baseCreateRenderer( anchor: RendererNode | null, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, - isSVG: boolean, + namespace: 'svg' | 'mathml' | undefined, slotScopeIds: string[] | null, optimized: boolean ) => { @@ -1085,7 +1108,7 @@ function baseCreateRenderer( fragmentEndAnchor, parentComponent, parentSuspense, - isSVG, + namespace, slotScopeIds, optimized ) @@ -1106,7 +1129,7 @@ function baseCreateRenderer( container, parentComponent, parentSuspense, - isSVG, + namespace, slotScopeIds ) if (__DEV__) { @@ -1134,7 +1157,7 @@ function baseCreateRenderer( fragmentEndAnchor, parentComponent, parentSuspense, - isSVG, + namespace, slotScopeIds, optimized ) @@ -1149,7 +1172,7 @@ function baseCreateRenderer( anchor: RendererNode | null, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, - isSVG: boolean, + namespace: 'svg' | 'mathml' | undefined, slotScopeIds: string[] | null, optimized: boolean ) => { @@ -1160,7 +1183,7 @@ function baseCreateRenderer( n2, container, anchor, - isSVG, + namespace, optimized ) } else { @@ -1170,7 +1193,7 @@ function baseCreateRenderer( anchor, parentComponent, parentSuspense, - isSVG, + namespace, optimized ) } @@ -1185,7 +1208,7 @@ function baseCreateRenderer( anchor, parentComponent, parentSuspense, - isSVG, + namespace: 'svg' | 'mathml' | undefined, optimized ) => { // 2.x compat may pre-create the component instance before actually @@ -1245,7 +1268,7 @@ function baseCreateRenderer( container, anchor, parentSuspense, - isSVG, + namespace, optimized ) @@ -1296,7 +1319,7 @@ function baseCreateRenderer( container, anchor, parentSuspense, - isSVG, + namespace: 'svg' | 'mathml' | undefined, optimized ) => { const componentUpdateFn = () => { @@ -1380,7 +1403,7 @@ function baseCreateRenderer( anchor, instance, parentSuspense, - isSVG + namespace ) if (__DEV__) { endMeasure(instance, `patch`) @@ -1499,7 +1522,7 @@ function baseCreateRenderer( getNextHostNode(prevTree), instance, parentSuspense, - isSVG + namespace ) if (__DEV__) { endMeasure(instance, `patch`) @@ -1599,7 +1622,7 @@ function baseCreateRenderer( anchor, parentComponent, parentSuspense, - isSVG, + namespace: 'svg' | 'mathml' | undefined, slotScopeIds, optimized = false ) => { @@ -1620,7 +1643,7 @@ function baseCreateRenderer( anchor, parentComponent, parentSuspense, - isSVG, + namespace, slotScopeIds, optimized ) @@ -1634,7 +1657,7 @@ function baseCreateRenderer( anchor, parentComponent, parentSuspense, - isSVG, + namespace, slotScopeIds, optimized ) @@ -1663,7 +1686,7 @@ function baseCreateRenderer( anchor, parentComponent, parentSuspense, - isSVG, + namespace, slotScopeIds, optimized ) @@ -1685,7 +1708,7 @@ function baseCreateRenderer( anchor, parentComponent, parentSuspense, - isSVG, + namespace, slotScopeIds, optimized ) @@ -1701,7 +1724,7 @@ function baseCreateRenderer( anchor: RendererNode | null, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, - isSVG: boolean, + namespace: 'svg' | 'mathml' | undefined, slotScopeIds: string[] | null, optimized: boolean ) => { @@ -1722,7 +1745,7 @@ function baseCreateRenderer( null, parentComponent, parentSuspense, - isSVG, + namespace, slotScopeIds, optimized ) @@ -1745,7 +1768,7 @@ function baseCreateRenderer( anchor, parentComponent, parentSuspense, - isSVG, + namespace, slotScopeIds, optimized, commonLength @@ -1761,7 +1784,7 @@ function baseCreateRenderer( parentAnchor: RendererNode | null, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, - isSVG: boolean, + namespace: 'svg' | 'mathml' | undefined, slotScopeIds: string[] | null, optimized: boolean ) => { @@ -1786,7 +1809,7 @@ function baseCreateRenderer( null, parentComponent, parentSuspense, - isSVG, + namespace, slotScopeIds, optimized ) @@ -1812,7 +1835,7 @@ function baseCreateRenderer( null, parentComponent, parentSuspense, - isSVG, + namespace, slotScopeIds, optimized ) @@ -1844,7 +1867,7 @@ function baseCreateRenderer( anchor, parentComponent, parentSuspense, - isSVG, + namespace, slotScopeIds, optimized ) @@ -1947,7 +1970,7 @@ function baseCreateRenderer( null, parentComponent, parentSuspense, - isSVG, + namespace, slotScopeIds, optimized ) @@ -1976,7 +1999,7 @@ function baseCreateRenderer( anchor, parentComponent, parentSuspense, - isSVG, + namespace, slotScopeIds, optimized ) @@ -2321,13 +2344,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() 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