]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(runtime-core): errors during component patch should be caught by error handlers
authorEvan You <evan@vuejs.org>
Thu, 11 Jul 2024 17:24:17 +0000 (01:24 +0800)
committerEvan You <evan@vuejs.org>
Thu, 11 Jul 2024 17:24:17 +0000 (01:24 +0800)
packages/runtime-core/__tests__/errorHandling.spec.ts
packages/runtime-core/src/errorHandling.ts
packages/runtime-core/src/renderer.ts
packages/runtime-core/src/scheduler.ts

index 085127677baedc134585d2be4823ff04c61da9dc..cf148bd2fe6dbf375820dd30f09c2a816eeade50 100644 (file)
@@ -1,4 +1,5 @@
 import {
+  type VNode,
   createApp,
   defineComponent,
   h,
@@ -11,6 +12,7 @@ import {
   watch,
   watchEffect,
 } from '@vue/runtime-test'
+import { ErrorCodes, ErrorTypeStrings } from '../src/errorHandling'
 
 describe('error handling', () => {
   test('propagation', () => {
@@ -609,5 +611,33 @@ describe('error handling', () => {
     expect(handler).toHaveBeenCalledTimes(1)
   })
 
+  test('errors in scheduler job with owner instance should be caught', async () => {
+    let vnode: VNode
+    const x = ref(0)
+    const app = createApp({
+      render() {
+        return (vnode = vnode || h('div', x.value))
+      },
+    })
+
+    app.config.errorHandler = vi.fn()
+    app.mount(nodeOps.createElement('div'))
+
+    const error = new Error('error')
+    Object.defineProperty(vnode!, 'el', {
+      get() {
+        throw error
+      },
+    })
+
+    x.value++
+    await nextTick()
+    expect(app.config.errorHandler).toHaveBeenCalledWith(
+      error,
+      {},
+      ErrorTypeStrings[ErrorCodes.COMPONENT_UPDATE],
+    )
+  })
+
   // native event handler handling should be tested in respective renderers
 })
index 41c92cbd34a967f46614f59a344b675118f0b620..d243db5bffd0e0ec356915c975942bd50db8a1d2 100644 (file)
@@ -23,6 +23,7 @@ export enum ErrorCodes {
   FUNCTION_REF,
   ASYNC_COMPONENT_LOADER,
   SCHEDULER,
+  COMPONENT_UPDATE,
 }
 
 export const ErrorTypeStrings: Record<LifecycleHooks | ErrorCodes, string> = {
@@ -54,16 +55,15 @@ export const ErrorTypeStrings: Record<LifecycleHooks | ErrorCodes, string> = {
   [ErrorCodes.APP_WARN_HANDLER]: 'app warnHandler',
   [ErrorCodes.FUNCTION_REF]: 'ref function',
   [ErrorCodes.ASYNC_COMPONENT_LOADER]: 'async component loader',
-  [ErrorCodes.SCHEDULER]:
-    'scheduler flush. This is likely a Vue internals bug. ' +
-    'Please open an issue at https://github.com/vuejs/core .',
+  [ErrorCodes.SCHEDULER]: 'scheduler flush',
+  [ErrorCodes.COMPONENT_UPDATE]: 'component update',
 }
 
 export type ErrorTypes = LifecycleHooks | ErrorCodes
 
 export function callWithErrorHandling(
   fn: Function,
-  instance: ComponentInternalInstance | null,
+  instance: ComponentInternalInstance | null | undefined,
   type: ErrorTypes,
   args?: unknown[],
 ) {
@@ -105,7 +105,7 @@ export function callWithAsyncErrorHandling(
 
 export function handleError(
   err: unknown,
-  instance: ComponentInternalInstance | null,
+  instance: ComponentInternalInstance | null | undefined,
   type: ErrorTypes,
   throwInDev = true,
 ) {
index 1f36502c713929c409eb6b0b6239a7bfbe6d45aa..3f4a6f4a26554dd88856c9f26a5c71bffc0ac0ad 100644 (file)
@@ -1587,6 +1587,7 @@ function baseCreateRenderer(
         effect.run()
       }
     })
+    update.i = instance
     update.id = instance.uid
     // allowRecurse
     // #1801, #2043 component render effects should allow recursive updates
@@ -1599,7 +1600,6 @@ function baseCreateRenderer(
       effect.onTrigger = instance.rtg
         ? e => invokeArrayFns(instance.rtg!, e)
         : void 0
-      update.ownerInstance = instance
     }
 
     update()
index 4ae1c6d46e750b93eafbb1fe7269acb747867c90..c1f9d4c9141ecfbef4f402767845e8bfbdc9d243 100644 (file)
@@ -26,9 +26,8 @@ export interface SchedulerJob extends Function {
   /**
    * Attached by renderer.ts when setting up a component's render effect
    * Used to obtain component information when reporting max recursive updates.
-   * dev only.
    */
-  ownerInstance?: ComponentInternalInstance
+  i?: ComponentInternalInstance
 }
 
 export type SchedulerJobs = SchedulerJob | SchedulerJob[]
@@ -240,7 +239,11 @@ function flushJobs(seen?: CountMap) {
         if (__DEV__ && check(job)) {
           continue
         }
-        callWithErrorHandling(job, null, ErrorCodes.SCHEDULER)
+        callWithErrorHandling(
+          job,
+          job.i,
+          job.i ? ErrorCodes.COMPONENT_UPDATE : ErrorCodes.SCHEDULER,
+        )
       }
     }
   } finally {
@@ -265,7 +268,7 @@ function checkRecursiveUpdates(seen: CountMap, fn: SchedulerJob) {
   } else {
     const count = seen.get(fn)!
     if (count > RECURSION_LIMIT) {
-      const instance = fn.ownerInstance
+      const instance = fn.i
       const componentName = instance && getComponentName(instance.type)
       handleError(
         `Maximum recursive updates exceeded${