From 58491dc63adb2e483cfc1b2268b5e8b2b2882bd7 Mon Sep 17 00:00:00 2001 From: Adrien Foulon <6115458+Tofandel@users.noreply.github.com> Date: Thu, 10 Aug 2023 12:41:59 +0200 Subject: [PATCH 1/4] fix(runtime-dom): invoker value is actually unknown at runtime --- .../runtime-dom/__tests__/patchEvents.spec.ts | 7 +++++++ packages/runtime-dom/src/modules/events.ts | 21 ++++++++++++++----- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/packages/runtime-dom/__tests__/patchEvents.spec.ts b/packages/runtime-dom/__tests__/patchEvents.spec.ts index d332977df96..a5b42af5fa1 100644 --- a/packages/runtime-dom/__tests__/patchEvents.spec.ts +++ b/packages/runtime-dom/__tests__/patchEvents.spec.ts @@ -192,4 +192,11 @@ describe(`runtime-dom: events patching`, () => { testElement.dispatchEvent(new CustomEvent('foobar')) expect(fn2).toHaveBeenCalledTimes(1) }) + + it('handles an unknown type', () => { + const el = document.createElement('div') + patchProp(el, 'onClick', null, 'test') + el.dispatchEvent(new Event('click')) + expect('[Vue warn]: Wrong type passed to the event invoker, did you maybe forget @ or : in front of your prop? Received test').toHaveBeenWarned() + }) }) diff --git a/packages/runtime-dom/src/modules/events.ts b/packages/runtime-dom/src/modules/events.ts index 272cededcf4..867cb5800e5 100644 --- a/packages/runtime-dom/src/modules/events.ts +++ b/packages/runtime-dom/src/modules/events.ts @@ -1,8 +1,8 @@ -import { hyphenate, isArray } from '@vue/shared' +import { hyphenate, isArray, isString, isFunction } from '@vue/shared' import { ErrorCodes, ComponentInternalInstance, - callWithAsyncErrorHandling + callWithAsyncErrorHandling, warn } from '@vue/runtime-core' interface Invoker extends EventListener { @@ -81,7 +81,7 @@ const getNow = () => cachedNow || (p.then(() => (cachedNow = 0)), (cachedNow = Date.now())) function createInvoker( - initialValue: EventValue, + initialValue: EventValue | unknown, instance: ComponentInternalInstance | null ) { const invoker: Invoker = (e: Event & { _vts?: number }) => { @@ -109,11 +109,22 @@ function createInvoker( [e] ) } - invoker.value = initialValue + invoker.value = sanitizeEventValue(initialValue) invoker.attached = getNow() return invoker } +function sanitizeEventValue(value: unknown): EventValue { + if (isFunction(value) || isArray(value)) { + return value as EventValue + } + + if (__DEV__) { + warn('Wrong type passed to the event invoker, did you maybe forget @ or : in front of your prop? Received ' + (isString(value) ? value : typeof value)) + } + return () => {} +} + function patchStopImmediatePropagation( e: Event, value: EventValue @@ -124,7 +135,7 @@ function patchStopImmediatePropagation( originalStop.call(e) ;(e as any)._stopped = true } - return value.map(fn => (e: Event) => !(e as any)._stopped && fn && fn(e)) + return (value as Function[]).map(fn => (e: Event) => !(e as any)._stopped && fn && fn(e)) } else { return value } From 42db610fd4a0a18d7365fac65df60066481d00df Mon Sep 17 00:00:00 2001 From: Tofandel Date: Mon, 25 Sep 2023 12:50:29 +0200 Subject: [PATCH 2/4] fix: move sanitization to patchEvent --- packages/runtime-dom/src/modules/events.ts | 29 +++++++++++++++------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/packages/runtime-dom/src/modules/events.ts b/packages/runtime-dom/src/modules/events.ts index 867cb5800e5..724c98e244c 100644 --- a/packages/runtime-dom/src/modules/events.ts +++ b/packages/runtime-dom/src/modules/events.ts @@ -2,7 +2,8 @@ import { hyphenate, isArray, isString, isFunction } from '@vue/shared' import { ErrorCodes, ComponentInternalInstance, - callWithAsyncErrorHandling, warn + callWithAsyncErrorHandling, + warn } from '@vue/runtime-core' interface Invoker extends EventListener { @@ -34,7 +35,7 @@ export function patchEvent( el: Element & { _vei?: Record }, rawName: string, prevValue: EventValue | null, - nextValue: EventValue | null, + nextValue: EventValue | unknown, instance: ComponentInternalInstance | null = null ) { // vei = vue event invokers @@ -42,12 +43,15 @@ export function patchEvent( const existingInvoker = invokers[rawName] if (nextValue && existingInvoker) { // patch - existingInvoker.value = nextValue + existingInvoker.value = sanitizeEventValue(nextValue, rawName) } else { const [name, options] = parseName(rawName) if (nextValue) { // add - const invoker = (invokers[rawName] = createInvoker(nextValue, instance)) + const invoker = (invokers[rawName] = createInvoker( + sanitizeEventValue(nextValue, rawName), + instance + )) addEventListener(el, name, invoker, options) } else if (existingInvoker) { // remove @@ -81,7 +85,7 @@ const getNow = () => cachedNow || (p.then(() => (cachedNow = 0)), (cachedNow = Date.now())) function createInvoker( - initialValue: EventValue | unknown, + initialValue: EventValue, instance: ComponentInternalInstance | null ) { const invoker: Invoker = (e: Event & { _vts?: number }) => { @@ -109,18 +113,23 @@ function createInvoker( [e] ) } - invoker.value = sanitizeEventValue(initialValue) + invoker.value = initialValue invoker.attached = getNow() return invoker } -function sanitizeEventValue(value: unknown): EventValue { +function sanitizeEventValue(value: unknown, propName: string): EventValue { if (isFunction(value) || isArray(value)) { return value as EventValue } if (__DEV__) { - warn('Wrong type passed to the event invoker, did you maybe forget @ or : in front of your prop? Received ' + (isString(value) ? value : typeof value)) + warn( + 'Wrong type passed to the event invoker, did you maybe forget @ or : in front of your prop? Received ' + + propName + + '=' + + (isString(value) ? value : typeof value) + ) } return () => {} } @@ -135,7 +144,9 @@ function patchStopImmediatePropagation( originalStop.call(e) ;(e as any)._stopped = true } - return (value as Function[]).map(fn => (e: Event) => !(e as any)._stopped && fn && fn(e)) + return (value as Function[]).map( + fn => (e: Event) => !(e as any)._stopped && fn && fn(e) + ) } else { return value } From 08d45b0b4b9f850b55e40360dca1b2bd4ce1f9d1 Mon Sep 17 00:00:00 2001 From: Tofandel Date: Mon, 25 Sep 2023 14:38:02 +0200 Subject: [PATCH 3/4] fix: test --- packages/runtime-dom/__tests__/patchEvents.spec.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/runtime-dom/__tests__/patchEvents.spec.ts b/packages/runtime-dom/__tests__/patchEvents.spec.ts index a5b42af5fa1..21665c6be0d 100644 --- a/packages/runtime-dom/__tests__/patchEvents.spec.ts +++ b/packages/runtime-dom/__tests__/patchEvents.spec.ts @@ -197,6 +197,8 @@ describe(`runtime-dom: events patching`, () => { const el = document.createElement('div') patchProp(el, 'onClick', null, 'test') el.dispatchEvent(new Event('click')) - expect('[Vue warn]: Wrong type passed to the event invoker, did you maybe forget @ or : in front of your prop? Received test').toHaveBeenWarned() + expect( + '[Vue warn]: Wrong type passed to the event invoker, did you maybe forget @ or : in front of your prop? Received onClick=test' + ).toHaveBeenWarned() }) }) From bf95a065788ec12647d5bcc7cc4e893de4d40b20 Mon Sep 17 00:00:00 2001 From: Evan You Date: Mon, 15 Apr 2024 22:25:11 +0800 Subject: [PATCH 4/4] refactor: make sanitize dev only --- .../runtime-dom/__tests__/patchEvents.spec.ts | 4 ++- packages/runtime-dom/src/modules/events.ts | 26 +++++++++---------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/packages/runtime-dom/__tests__/patchEvents.spec.ts b/packages/runtime-dom/__tests__/patchEvents.spec.ts index 73d10b94089..b7a5af0ed0e 100644 --- a/packages/runtime-dom/__tests__/patchEvents.spec.ts +++ b/packages/runtime-dom/__tests__/patchEvents.spec.ts @@ -198,7 +198,9 @@ describe(`runtime-dom: events patching`, () => { patchProp(el, 'onClick', null, 'test') el.dispatchEvent(new Event('click')) expect( - '[Vue warn]: Wrong type passed to the event invoker, did you maybe forget @ or : in front of your prop? Received onClick=test', + '[Vue warn]: Wrong type passed to the event invoker, ' + + 'did you maybe forget @ or : in front of your prop?' + + '\nReceived onClick="test"', ).toHaveBeenWarned() }) }) diff --git a/packages/runtime-dom/src/modules/events.ts b/packages/runtime-dom/src/modules/events.ts index 08b2802b7c7..09e4a22a84c 100644 --- a/packages/runtime-dom/src/modules/events.ts +++ b/packages/runtime-dom/src/modules/events.ts @@ -1,4 +1,4 @@ -import { hyphenate, isArray, isFunction, isString } from '@vue/shared' +import { NOOP, hyphenate, isArray, isFunction, isString } from '@vue/shared' import { type ComponentInternalInstance, ErrorCodes, @@ -45,13 +45,17 @@ export function patchEvent( const existingInvoker = invokers[rawName] if (nextValue && existingInvoker) { // patch - existingInvoker.value = sanitizeEventValue(nextValue, rawName) + existingInvoker.value = __DEV__ + ? sanitizeEventValue(nextValue, rawName) + : (nextValue as EventValue) } else { const [name, options] = parseName(rawName) if (nextValue) { // add const invoker = (invokers[rawName] = createInvoker( - sanitizeEventValue(nextValue, rawName), + __DEV__ + ? sanitizeEventValue(nextValue, rawName) + : (nextValue as EventValue), instance, )) addEventListener(el, name, invoker, options) @@ -124,16 +128,12 @@ function sanitizeEventValue(value: unknown, propName: string): EventValue { if (isFunction(value) || isArray(value)) { return value as EventValue } - - if (__DEV__) { - warn( - 'Wrong type passed to the event invoker, did you maybe forget @ or : in front of your prop? Received ' + - propName + - '=' + - (isString(value) ? value : typeof value), - ) - } - return () => {} + warn( + `Wrong type passed to the event invoker, did you maybe forget @ or : ` + + `in front of your prop?\nReceived ` + + `${propName}=${isString(value) ? JSON.stringify(value) : `[${typeof value}]`}`, + ) + return NOOP } function patchStopImmediatePropagation(