11/** @import { Source } from '#client' */
22import { DEV } from 'esm-env' ;
3- import { get , active_effect , active_reaction , set_active_reaction } from './runtime.js' ;
3+ import { get , active_effect , active_reaction , set_active_reaction , untrack } from './runtime.js' ;
44import {
55 array_prototype ,
66 get_descriptor ,
77 get_prototype_of ,
88 is_array ,
9- object_prototype
9+ object_prototype ,
10+ define_property
1011} from '../shared/utils.js' ;
1112import { state as source , set } from './reactivity/sources.js' ;
1213import { PROXY_PATH_SYMBOL , STATE_SYMBOL } from '#client/constants' ;
@@ -18,6 +19,19 @@ import { tracing_mode_flag } from '../flags/index.js';
1819// TODO move all regexes into shared module?
1920const regex_is_valid_identifier = / ^ [ a - z A - Z _ $ ] [ a - z A - Z _ $ 0 - 9 ] * $ / ;
2021
22+ // Array methods that mutate the array, according to the ECMAScript specification
23+ const MUTATING_ARRAY_METHODS = new Set ( [
24+ 'push' ,
25+ 'pop' ,
26+ 'shift' ,
27+ 'unshift' ,
28+ 'splice' ,
29+ 'sort' ,
30+ 'reverse' ,
31+ 'copyWithin' ,
32+ 'fill'
33+ ] ) ;
34+
2135/**
2236 * @template T
2337 * @param {T } value
@@ -43,6 +57,9 @@ export function proxy(value) {
4357 var stack = DEV && tracing_mode_flag ? get_stack ( 'CreatedAt' ) : null ;
4458 var reaction = active_reaction ;
4559
60+ /** @type {Map<string, Function> | null } */
61+ var proxied_array_mutating_methods_cache = is_proxied_array ? new Map ( ) : null ;
62+
4663 /**
4764 * @template T
4865 * @param {() => T } fn
@@ -176,6 +193,44 @@ export function proxy(value) {
176193 return v === UNINITIALIZED ? undefined : v ;
177194 }
178195
196+ // if this is a proxied array and the property is a mutating method, return a
197+ // wrapper that executes the native method inside `untrack`, preventing the
198+ // current reaction from accidentally depending on `length` (or other
199+ // internals) that the algorithm reads.
200+ if ( is_proxied_array && typeof prop === 'string' && MUTATING_ARRAY_METHODS . has ( prop ) ) {
201+ /** @type {Map<string, Function> } */
202+ const mutating_methods_cache = /** @type {Map<string, Function> } */ ( proxied_array_mutating_methods_cache ) ;
203+
204+ var cached_method = mutating_methods_cache . get ( prop ) ;
205+
206+ if ( cached_method === undefined ) {
207+ /**
208+ * wrapper executes the native mutating method inside `untrack` so that
209+ * any implicit `length` reads are ignored.
210+ * @this any
211+ * @param {...any } args
212+ */
213+ cached_method = function ( ...args ) {
214+ // preserve correct `this` binding and forward result.
215+ // eslint-disable-next-line prefer-spread
216+ return untrack ( ( ) => /** @type {any } */ ( array_prototype ) [ prop ] . apply ( this , args ) ) ;
217+ } ;
218+
219+ // give the wrapper a meaningful name for better debugging
220+ try {
221+ define_property ( cached_method , 'name' , {
222+ value : `proxied_array_untracked_${ /** @type {string } */ ( prop ) } `
223+ } ) ;
224+ } catch {
225+ // property might be non-configurable in some engines
226+ }
227+
228+ mutating_methods_cache . set ( prop , cached_method ) ;
229+ }
230+
231+ return cached_method ;
232+ }
233+
179234 return Reflect . get ( target , prop , receiver ) ;
180235 } ,
181236
0 commit comments