@@ -17,64 +17,98 @@ import {
1717} from "../reactivity" ;
1818import { calculateProps } from "./calculate-props" ;
1919
20+ /**
21+ * Checks if two style objects have non-overlapping properties
22+ */
23+ function hasNonOverlappingProperties (
24+ left : Record < string , any > ,
25+ right : Record < string , any > ,
26+ ) : boolean {
27+ // Null safety check
28+ if ( ! left || ! right ) {
29+ return false ;
30+ }
31+
32+ // Only check own properties to avoid prototype pollution
33+ for ( const key in left ) {
34+ if ( Object . prototype . hasOwnProperty . call ( left , key ) ) {
35+ if ( ! Object . prototype . hasOwnProperty . call ( right , key ) ) {
36+ return true ;
37+ }
38+ }
39+ }
40+ return false ;
41+ }
42+
2043/**
2144 * Flattens a style array into a single object, with rightmost values taking precedence
2245 */
2346function flattenStyleArray ( styleArray : any [ ] ) : any {
2447 // Check if we can flatten to a single object (all items are plain objects)
25- const allObjects = styleArray . every (
26- ( item ) =>
27- item &&
28- typeof item === "object" &&
29- ! Array . isArray ( item ) &&
30- ! ( VAR_SYMBOL in item ) ,
31- ) ;
32-
33- if ( ! allObjects ) {
34- return styleArray ;
48+ for ( const item of styleArray ) {
49+ // Use explicit null check instead of ! item to allow falsy values like 0 or false
50+ if (
51+ item == null ||
52+ typeof item !== "object" ||
53+ Array . isArray ( item ) ||
54+ Object . prototype . hasOwnProperty . call ( item , VAR_SYMBOL )
55+ ) {
56+ return styleArray ;
57+ }
3558 }
3659
37- // Merge all objects with right-side precedence (later values override earlier ones)
38- return Object . assign ( { } , ... styleArray ) ;
60+ // Use reduce to avoid spread operator performance issues with large arrays
61+ return styleArray . reduce ( ( acc , item ) => Object . assign ( acc , item ) , { } ) ;
3962}
4063
4164/**
4265 * Recursively filters out CSS variable objects (with VAR_SYMBOL) from style values
4366 */
44- function filterCssVariables ( value : any ) : any {
67+ function filterCssVariables ( value : any , depth = 0 ) : any {
68+ // Prevent stack overflow on deeply nested structures
69+ if ( depth > 100 ) {
70+ return value ;
71+ }
72+
4573 if ( value === null || value === undefined ) {
4674 return value ;
4775 }
4876
4977 if ( Array . isArray ( value ) ) {
50- const filtered = value
51- . map ( ( item ) => filterCssVariables ( item ) )
52- . filter ( ( item ) => {
53- // Remove undefined items (filtered out CSS variables)
54- if ( item === undefined ) {
55- return false ;
56- }
57- // Remove items that are objects with VAR_SYMBOL
58- if ( typeof item === "object" && item !== null && VAR_SYMBOL in item ) {
59- return false ;
60- }
61- return true ;
62- } ) ;
78+ // Single-pass filter with map operation
79+ const filtered : any [ ] = [ ] ;
80+
81+ for ( const item of value ) {
82+ const filteredItem = filterCssVariables ( item , depth + 1 ) ;
83+ if (
84+ filteredItem !== undefined &&
85+ ! (
86+ typeof filteredItem === "object" &&
87+ filteredItem !== null &&
88+ Object . prototype . hasOwnProperty . call ( filteredItem , VAR_SYMBOL )
89+ )
90+ ) {
91+ filtered . push ( filteredItem ) ;
92+ }
93+ }
94+
6395 return filtered . length > 0 ? filtered : undefined ;
6496 }
6597
6698 if ( typeof value === "object" ) {
67- // If the object itself has VAR_SYMBOL, filter it out
68- if ( VAR_SYMBOL in value ) {
99+ // If the object itself has VAR_SYMBOL, filter it out (check own property only)
100+ if ( Object . prototype . hasOwnProperty . call ( value , VAR_SYMBOL ) ) {
69101 return undefined ;
70102 }
71103
72104 // Otherwise, filter VAR_SYMBOL properties from nested objects
73105 const filtered : Record < string , any > = { } ;
74106 let hasProperties = false ;
75107
76- for ( const key in value ) {
77- const filteredValue = filterCssVariables ( value [ key ] ) ;
108+ // Use Object.keys to only iterate own string properties (not inherited, not Symbols)
109+ // This intentionally filters out Symbol properties for React Native compatibility
110+ for ( const key of Object . keys ( value ) ) {
111+ const filteredValue = filterCssVariables ( value [ key ] , depth + 1 ) ;
78112 if ( filteredValue !== undefined ) {
79113 filtered [ key ] = filteredValue ;
80114 hasProperties = true ;
@@ -240,16 +274,7 @@ function deepMergeConfig(
240274 ! Array . isArray ( filteredRightStyle ) ;
241275
242276 if ( leftIsObject && rightIsObject ) {
243- // Quick check: do any left properties NOT exist in right?
244- let hasNonOverlappingProperties = false ;
245- for ( const key in leftStyle ) {
246- if ( ! ( key in filteredRightStyle ) ) {
247- hasNonOverlappingProperties = true ;
248- break ; // Early exit for performance
249- }
250- }
251-
252- if ( hasNonOverlappingProperties ) {
277+ if ( hasNonOverlappingProperties ( leftStyle , filteredRightStyle ) ) {
253278 result . style = [ leftStyle , filteredRightStyle ] ;
254279 } else {
255280 // All left properties are in right, right overrides
@@ -281,14 +306,7 @@ function deepMergeConfig(
281306 typeof right . style === "object"
282307 ) {
283308 // Both are objects, check for overlaps
284- let hasNonOverlappingProperties = false ;
285- for ( const key in left . style ) {
286- if ( ! ( key in right . style ) ) {
287- hasNonOverlappingProperties = true ;
288- break ;
289- }
290- }
291- if ( hasNonOverlappingProperties ) {
309+ if ( hasNonOverlappingProperties ( left . style , right . style ) ) {
292310 result . style = flattenStyleArray ( [ left . style , right . style ] ) ;
293311 } else {
294312 // All left properties are overridden by right
0 commit comments