]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(runtime-dom): properly handle innerHTML unmount into new children (#11159)
authorlinzhe <40790268+linzhe141@users.noreply.github.com>
Wed, 17 Jul 2024 08:37:14 +0000 (16:37 +0800)
committerGitHub <noreply@github.com>
Wed, 17 Jul 2024 08:37:14 +0000 (16:37 +0800)
close #9135

packages/runtime-core/src/hydration.ts
packages/runtime-core/src/renderer.ts
packages/runtime-dom/__tests__/patchProps.spec.ts
packages/runtime-dom/src/modules/props.ts
packages/runtime-dom/src/patchProp.ts

index fe267f4fef61a0907982166c20a9122f09255f32..c371542607da7840b95885c3c9acd76420e5b22a 100644 (file)
@@ -465,15 +465,7 @@ export function createHydrationFunctions(
               // force hydrate v-bind with .prop modifiers
               key[0] === '.'
             ) {
-              patchProp(
-                el,
-                key,
-                null,
-                props[key],
-                undefined,
-                undefined,
-                parentComponent,
-              )
+              patchProp(el, key, null, props[key], undefined, parentComponent)
             }
           }
         } else if (props.onClick) {
@@ -485,7 +477,6 @@ export function createHydrationFunctions(
             null,
             props.onClick,
             undefined,
-            undefined,
             parentComponent,
           )
         } else if (patchFlag & PatchFlags.STYLE && isReactive(props.style)) {
index 088d1565cbb6bd80778203dd6e876c473820cf0e..db674f987d81eefaaecf4f191991c91c3ddb3f4a 100644 (file)
@@ -107,10 +107,7 @@ export interface RendererOptions<
     prevValue: any,
     nextValue: any,
     namespace?: ElementNamespace,
-    prevChildren?: VNode<HostNode, HostElement>[],
     parentComponent?: ComponentInternalInstance | null,
-    parentSuspense?: SuspenseBoundary | null,
-    unmountChildren?: UnmountChildrenFn,
   ): void
   insert(el: HostNode, parent: HostElement, anchor?: HostNode | null): void
   remove(el: HostNode): void
@@ -670,17 +667,7 @@ function baseCreateRenderer(
     if (props) {
       for (const key in props) {
         if (key !== 'value' && !isReservedProp(key)) {
-          hostPatchProp(
-            el,
-            key,
-            null,
-            props[key],
-            namespace,
-            vnode.children as VNode[],
-            parentComponent,
-            parentSuspense,
-            unmountChildren,
-          )
+          hostPatchProp(el, key, null, props[key], namespace, parentComponent)
         }
       }
       /**
@@ -833,6 +820,15 @@ function baseCreateRenderer(
       dynamicChildren = null
     }
 
+    // #9135 innerHTML / textContent unset needs to happen before possible
+    // new children mount
+    if (
+      (oldProps.innerHTML && newProps.innerHTML == null) ||
+      (oldProps.textContent && newProps.textContent == null)
+    ) {
+      hostSetElementText(el, '')
+    }
+
     if (dynamicChildren) {
       patchBlockChildren(
         n1.dynamicChildren!,
@@ -869,15 +865,7 @@ function baseCreateRenderer(
       // (i.e. at the exact same position in the source template)
       if (patchFlag & PatchFlags.FULL_PROPS) {
         // element props contain dynamic keys, full diff needed
-        patchProps(
-          el,
-          n2,
-          oldProps,
-          newProps,
-          parentComponent,
-          parentSuspense,
-          namespace,
-        )
+        patchProps(el, oldProps, newProps, parentComponent, namespace)
       } else {
         // class
         // this flag is matched when the element has dynamic class bindings.
@@ -908,17 +896,7 @@ function baseCreateRenderer(
             const next = newProps[key]
             // #1471 force patch value
             if (next !== prev || key === 'value') {
-              hostPatchProp(
-                el,
-                key,
-                prev,
-                next,
-                namespace,
-                n1.children as VNode[],
-                parentComponent,
-                parentSuspense,
-                unmountChildren,
-              )
+              hostPatchProp(el, key, prev, next, namespace, parentComponent)
             }
           }
         }
@@ -933,15 +911,7 @@ function baseCreateRenderer(
       }
     } else if (!optimized && dynamicChildren == null) {
       // unoptimized, full diff
-      patchProps(
-        el,
-        n2,
-        oldProps,
-        newProps,
-        parentComponent,
-        parentSuspense,
-        namespace,
-      )
+      patchProps(el, oldProps, newProps, parentComponent, namespace)
     }
 
     if ((vnodeHook = newProps.onVnodeUpdated) || dirs) {
@@ -998,11 +968,9 @@ function baseCreateRenderer(
 
   const patchProps = (
     el: RendererElement,
-    vnode: VNode,
     oldProps: Data,
     newProps: Data,
     parentComponent: ComponentInternalInstance | null,
-    parentSuspense: SuspenseBoundary | null,
     namespace: ElementNamespace,
   ) => {
     if (oldProps !== newProps) {
@@ -1015,10 +983,7 @@ function baseCreateRenderer(
               oldProps[key],
               null,
               namespace,
-              vnode.children as VNode[],
               parentComponent,
-              parentSuspense,
-              unmountChildren,
             )
           }
         }
@@ -1030,17 +995,7 @@ function baseCreateRenderer(
         const prev = oldProps[key]
         // defer patching value
         if (next !== prev && key !== 'value') {
-          hostPatchProp(
-            el,
-            key,
-            prev,
-            next,
-            namespace,
-            vnode.children as VNode[],
-            parentComponent,
-            parentSuspense,
-            unmountChildren,
-          )
+          hostPatchProp(el, key, prev, next, namespace, parentComponent)
         }
       }
       if ('value' in newProps) {
index 61dd98513ce3ad84eea2575cd94ec4545039d5a7..7f418847f5f8a854398ca5d6f1a0714e3b004a7e 100644 (file)
@@ -1,5 +1,5 @@
 import { patchProp } from '../src/patchProp'
-import { h, render } from '../src'
+import { h, nextTick, ref, render } from '../src'
 
 describe('runtime-dom: props patching', () => {
   test('basic', () => {
@@ -133,6 +133,25 @@ describe('runtime-dom: props patching', () => {
     expect(fn).toHaveBeenCalled()
   })
 
+  test('patch innerHTML porp', async () => {
+    const root = document.createElement('div')
+    const state = ref(false)
+    const Comp = {
+      render: () => {
+        if (state.value) {
+          return h('div', [h('del', null, 'baz')])
+        } else {
+          return h('div', { innerHTML: 'baz' })
+        }
+      },
+    }
+    render(h(Comp), root)
+    expect(root.innerHTML).toBe(`<div>baz</div>`)
+    state.value = true
+    await nextTick()
+    expect(root.innerHTML).toBe(`<div><del>baz</del></div>`)
+  })
+
   test('textContent unmount prev children', () => {
     const fn = vi.fn()
     const comp = {
index 2eb83ea39f74303312fdbcf609a3524f999258f5..04f0d0e866d48c342dd6e56b112212827981b7fa 100644 (file)
@@ -10,19 +10,13 @@ export function patchDOMProp(
   el: any,
   key: string,
   value: any,
-  // the following args are passed only due to potential innerHTML/textContent
-  // overriding existing VNodes, in which case the old tree must be properly
-  // unmounted.
-  prevChildren: any,
   parentComponent: any,
-  parentSuspense: any,
-  unmountChildren: any,
 ) {
   if (key === 'innerHTML' || key === 'textContent') {
-    if (prevChildren) {
-      unmountChildren(prevChildren, parentComponent, parentSuspense)
-    }
-    el[key] = value == null ? '' : value
+    // null value case is handled in renderer patchElement before patching
+    // children
+    if (value === null) return
+    el[key] = value
     return
   }
 
index e7b733c74aff93ed52acb860a86cf506b91e2d0e..f3ef14ee83c3313b85c2f43e62e6585132d4e9db 100644 (file)
@@ -21,10 +21,7 @@ export const patchProp: DOMRendererOptions['patchProp'] = (
   prevValue,
   nextValue,
   namespace,
-  prevChildren,
   parentComponent,
-  parentSuspense,
-  unmountChildren,
 ) => {
   const isSVG = namespace === 'svg'
   if (key === 'class') {
@@ -43,15 +40,7 @@ export const patchProp: DOMRendererOptions['patchProp'] = (
         ? ((key = key.slice(1)), false)
         : shouldSetAsProp(el, key, nextValue, isSVG)
   ) {
-    patchDOMProp(
-      el,
-      key,
-      nextValue,
-      prevChildren,
-      parentComponent,
-      parentSuspense,
-      unmountChildren,
-    )
+    patchDOMProp(el, key, nextValue, parentComponent)
     // #6007 also set form state as attributes so they work with
     // <input type="reset"> or libs / extensions that expect attributes
     // #11163 custom elements may use value as an prop and set it as object