diff --git a/CHANGELOG.md b/CHANGELOG.md
index a0d590a5b8b..4eae200db69 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,13 @@
+## [3.4.23](https://github.com/vuejs/core/compare/v3.4.22...v3.4.23) (2024-04-16)
+
+
+### Bug Fixes
+
+* **runtime-core:** fix regression for $attrs tracking in slots ([6930e60](https://github.com/vuejs/core/commit/6930e60787e4905a50417190263ae7dd46cf5409)), closes [#10710](https://github.com/vuejs/core/issues/10710)
+* **runtime-core:** use same internal object mechanism for slots ([6df53d8](https://github.com/vuejs/core/commit/6df53d85a207986128159d88565e6e7045db2add)), closes [#10709](https://github.com/vuejs/core/issues/10709)
+
+
+
## [3.4.22](https://github.com/vuejs/core/compare/v3.4.21...v3.4.22) (2024-04-15)
diff --git a/SECURITY.md b/SECURITY.md
index dac6018b5bc..41a58da2970 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -5,3 +5,9 @@ To report a vulnerability, please email security@vuejs.org.
While the discovery of new vulnerabilities is rare, we also recommend always using the latest versions of Vue and its official companion libraries to ensure your application remains as secure as possible.
Please note that we do not consider XSS via template expressions a valid attack vector, because it can only happen if the user intentionally uses untrusted content as template compilation source. This is similar to knowingly pasting untrusted scripts into a browser console. We explicitly warn users against using untrusted content as template compilation source in our documentation.
+
+## Security Hall of Fame
+
+We would like to thank the following security researchers for responsibly disclosing security issues to us.
+
+- Jeet Pal - [GitHub](https://github.com/jeetpal2007) | [Email](jeetpal2007@gmail.com) | [LinkedIn](https://in.linkedin.com/in/jeet-pal-22601a290 )
diff --git a/package.json b/package.json
index acb12ffc926..66c831a7b8c 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"private": true,
- "version": "3.4.22",
+ "version": "3.4.23",
"packageManager": "pnpm@8.15.6",
"type": "module",
"scripts": {
diff --git a/packages/compiler-core/package.json b/packages/compiler-core/package.json
index 19d0a416a98..f671478659a 100644
--- a/packages/compiler-core/package.json
+++ b/packages/compiler-core/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/compiler-core",
- "version": "3.4.22",
+ "version": "3.4.23",
"description": "@vue/compiler-core",
"main": "index.js",
"module": "dist/compiler-core.esm-bundler.js",
diff --git a/packages/compiler-dom/package.json b/packages/compiler-dom/package.json
index 3826fb2c60c..1b7700aa9ba 100644
--- a/packages/compiler-dom/package.json
+++ b/packages/compiler-dom/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/compiler-dom",
- "version": "3.4.22",
+ "version": "3.4.23",
"description": "@vue/compiler-dom",
"main": "index.js",
"module": "dist/compiler-dom.esm-bundler.js",
diff --git a/packages/compiler-sfc/package.json b/packages/compiler-sfc/package.json
index 9e732550000..d4f2cb4c717 100644
--- a/packages/compiler-sfc/package.json
+++ b/packages/compiler-sfc/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/compiler-sfc",
- "version": "3.4.22",
+ "version": "3.4.23",
"description": "@vue/compiler-sfc",
"main": "dist/compiler-sfc.cjs.js",
"module": "dist/compiler-sfc.esm-browser.js",
diff --git a/packages/compiler-ssr/package.json b/packages/compiler-ssr/package.json
index 6e9648f5400..a4972e5c014 100644
--- a/packages/compiler-ssr/package.json
+++ b/packages/compiler-ssr/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/compiler-ssr",
- "version": "3.4.22",
+ "version": "3.4.23",
"description": "@vue/compiler-ssr",
"main": "dist/compiler-ssr.cjs.js",
"types": "dist/compiler-ssr.d.ts",
diff --git a/packages/reactivity/package.json b/packages/reactivity/package.json
index 414083ef6f1..7966c34e412 100644
--- a/packages/reactivity/package.json
+++ b/packages/reactivity/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/reactivity",
- "version": "3.4.22",
+ "version": "3.4.23",
"description": "@vue/reactivity",
"main": "index.js",
"module": "dist/reactivity.esm-bundler.js",
diff --git a/packages/runtime-core/__tests__/rendererAttrsFallthrough.spec.ts b/packages/runtime-core/__tests__/rendererAttrsFallthrough.spec.ts
index 79e2867ad69..9c985379c1e 100644
--- a/packages/runtime-core/__tests__/rendererAttrsFallthrough.spec.ts
+++ b/packages/runtime-core/__tests__/rendererAttrsFallthrough.spec.ts
@@ -20,6 +20,7 @@ import {
render,
withModifiers,
} from '@vue/runtime-dom'
+import { createApp } from 'vue'
import { PatchFlags } from '@vue/shared'
describe('attribute fallthrough', () => {
@@ -783,4 +784,31 @@ describe('attribute fallthrough', () => {
expect(textBar).toBe('from GrandChild')
expect(textFoo).toBe('from Child')
})
+
+ // covers uncaught regression #10710
+ it('should track this.$attrs access in slots', async () => {
+ const GrandChild = {
+ template: ``,
+ }
+ const Child = {
+ components: { GrandChild },
+ template: `
{{ $attrs.foo }}
`,
+ }
+
+ const obj = ref(1)
+ const App = {
+ render() {
+ return h(Child, { foo: obj.value })
+ },
+ }
+
+ const root = document.createElement('div')
+ createApp(App).mount(root)
+
+ expect(root.innerHTML).toBe('1
')
+
+ obj.value = 2
+ await nextTick()
+ expect(root.innerHTML).toBe('2
')
+ })
})
diff --git a/packages/runtime-core/package.json b/packages/runtime-core/package.json
index 8edddcc32c8..134080b1012 100644
--- a/packages/runtime-core/package.json
+++ b/packages/runtime-core/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/runtime-core",
- "version": "3.4.22",
+ "version": "3.4.23",
"description": "@vue/runtime-core",
"main": "index.js",
"module": "dist/runtime-core.esm-bundler.js",
diff --git a/packages/runtime-core/src/componentProps.ts b/packages/runtime-core/src/componentProps.ts
index 1c87304185c..5a4292b6f36 100644
--- a/packages/runtime-core/src/componentProps.ts
+++ b/packages/runtime-core/src/componentProps.ts
@@ -38,6 +38,7 @@ import { createPropsDefaultThis } from './compat/props'
import { isCompatEnabled, softAssertCompatEnabled } from './compat/compatConfig'
import { DeprecationTypes } from './compat/compatConfig'
import { shouldSkipAttr } from './compat/attrsFallthrough'
+import { createInternalObject } from './internalObject'
export type ComponentPropsOptions =
| ComponentObjectPropsOptions
@@ -185,13 +186,6 @@ type NormalizedProp =
export type NormalizedProps = Record
export type NormalizedPropsOptions = [NormalizedProps, string[]] | []
-/**
- * Used during vnode props normalization to check if the vnode props is the
- * attrs object of a component via `Object.getPrototypeOf`. This is more
- * performant than defining a non-enumerable property.
- */
-export const attrsProto = {}
-
export function initProps(
instance: ComponentInternalInstance,
rawProps: Data | null,
@@ -199,7 +193,7 @@ export function initProps(
isSSR = false,
) {
const props: Data = {}
- const attrs: Data = Object.create(attrsProto)
+ const attrs: Data = createInternalObject()
instance.propsDefaults = Object.create(null)
diff --git a/packages/runtime-core/src/componentPublicInstance.ts b/packages/runtime-core/src/componentPublicInstance.ts
index a1b45e4f9cc..b43accfa0a3 100644
--- a/packages/runtime-core/src/componentPublicInstance.ts
+++ b/packages/runtime-core/src/componentPublicInstance.ts
@@ -368,9 +368,10 @@ export const PublicInstanceProxyHandlers: ProxyHandler = {
// public $xxx properties
if (publicGetter) {
if (key === '$attrs') {
- track(instance, TrackOpTypes.GET, key)
+ track(instance.attrs, TrackOpTypes.GET, '')
__DEV__ && markAttrsAccessed()
} else if (__DEV__ && key === '$slots') {
+ // for HMR only
track(instance, TrackOpTypes.GET, key)
}
return publicGetter(instance)
diff --git a/packages/runtime-core/src/componentSlots.ts b/packages/runtime-core/src/componentSlots.ts
index e0f051b3984..66a09e5fa1a 100644
--- a/packages/runtime-core/src/componentSlots.ts
+++ b/packages/runtime-core/src/componentSlots.ts
@@ -24,6 +24,7 @@ import { DeprecationTypes, isCompatEnabled } from './compat/compatConfig'
import { toRaw } from '@vue/reactivity'
import { trigger } from '@vue/reactivity'
import { TriggerOpTypes } from '@vue/reactivity'
+import { createInternalObject } from './internalObject'
export type Slot = (
...args: IfAny
@@ -177,12 +178,12 @@ export const initSlots = (
} else {
normalizeObjectSlots(
children as RawSlots,
- (instance.slots = {}),
+ (instance.slots = createInternalObject()),
instance,
)
}
} else {
- instance.slots = {}
+ instance.slots = createInternalObject()
if (children) {
normalizeVNodeSlots(instance, children)
}
diff --git a/packages/runtime-core/src/internalObject.ts b/packages/runtime-core/src/internalObject.ts
new file mode 100644
index 00000000000..0c0c39bef6d
--- /dev/null
+++ b/packages/runtime-core/src/internalObject.ts
@@ -0,0 +1,12 @@
+/**
+ * Used during vnode props/slots normalization to check if the vnode props/slots
+ * are the internal attrs / slots object of a component via
+ * `Object.getPrototypeOf`. This is more performant than defining a
+ * non-enumerable property. (one of the optimizations done for ssr-benchmark)
+ */
+const internalObjectProto = Object.create(null)
+
+export const createInternalObject = () => Object.create(internalObjectProto)
+
+export const isInternalObject = (obj: object) =>
+ Object.getPrototypeOf(obj) === internalObjectProto
diff --git a/packages/runtime-core/src/vnode.ts b/packages/runtime-core/src/vnode.ts
index 28b60be78f2..a1a6a908d2a 100644
--- a/packages/runtime-core/src/vnode.ts
+++ b/packages/runtime-core/src/vnode.ts
@@ -55,7 +55,7 @@ import { convertLegacyVModelProps } from './compat/componentVModel'
import { defineLegacyVNodeProperties } from './compat/renderFn'
import { ErrorCodes, callWithAsyncErrorHandling } from './errorHandling'
import type { ComponentPublicInstance } from './componentPublicInstance'
-import { attrsProto } from './componentProps'
+import { isInternalObject } from './internalObject'
export const Fragment = Symbol.for('v-fgt') as any as {
__isFragment: true
@@ -617,9 +617,7 @@ function _createVNode(
export function guardReactiveProps(props: (Data & VNodeProps) | null) {
if (!props) return null
- return isProxy(props) || Object.getPrototypeOf(props) === attrsProto
- ? extend({}, props)
- : props
+ return isProxy(props) || isInternalObject(props) ? extend({}, props) : props
}
export function cloneVNode(
@@ -791,7 +789,7 @@ export function normalizeChildren(vnode: VNode, children: unknown) {
} else {
type = ShapeFlags.SLOTS_CHILDREN
const slotFlag = (children as RawSlots)._
- if (!slotFlag) {
+ if (!slotFlag && !isInternalObject(children)) {
// if slots are not normalized, attach context instance
// (compiled / normalized slots already have context)
;(children as RawSlots)._ctx = currentRenderingInstance
diff --git a/packages/runtime-dom/package.json b/packages/runtime-dom/package.json
index 17712da8eed..2f579ff7b13 100644
--- a/packages/runtime-dom/package.json
+++ b/packages/runtime-dom/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/runtime-dom",
- "version": "3.4.22",
+ "version": "3.4.23",
"description": "@vue/runtime-dom",
"main": "index.js",
"module": "dist/runtime-dom.esm-bundler.js",
diff --git a/packages/server-renderer/package.json b/packages/server-renderer/package.json
index 8cd53da98b5..cc5ea9ad902 100644
--- a/packages/server-renderer/package.json
+++ b/packages/server-renderer/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/server-renderer",
- "version": "3.4.22",
+ "version": "3.4.23",
"description": "@vue/server-renderer",
"main": "index.js",
"module": "dist/server-renderer.esm-bundler.js",
diff --git a/packages/shared/package.json b/packages/shared/package.json
index a227ded9ecb..5d05a5371d9 100644
--- a/packages/shared/package.json
+++ b/packages/shared/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/shared",
- "version": "3.4.22",
+ "version": "3.4.23",
"description": "internal utils shared across @vue packages",
"main": "index.js",
"module": "dist/shared.esm-bundler.js",
diff --git a/packages/vue-compat/package.json b/packages/vue-compat/package.json
index 4c5dd04e3cc..70f9f23b1ba 100644
--- a/packages/vue-compat/package.json
+++ b/packages/vue-compat/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/compat",
- "version": "3.4.22",
+ "version": "3.4.23",
"description": "Vue 3 compatibility build for Vue 2",
"main": "index.js",
"module": "dist/vue.runtime.esm-bundler.js",
diff --git a/packages/vue/package.json b/packages/vue/package.json
index 1a297293d06..41dd36385b8 100644
--- a/packages/vue/package.json
+++ b/packages/vue/package.json
@@ -1,6 +1,6 @@
{
"name": "vue",
- "version": "3.4.22",
+ "version": "3.4.23",
"description": "The progressive JavaScript framework for building modern web UI.",
"main": "index.js",
"module": "dist/vue.runtime.esm-bundler.js",