From 74f118818bfd38b8e3968f026da04d6a98ca9ef0 Mon Sep 17 00:00:00 2001 From: Jukka Kurkela Date: Sat, 3 Apr 2021 18:10:52 +0300 Subject: [PATCH] Proxy: Only create scopes when setting values (#8799) --- src/helpers/helpers.config.js | 20 ++++++++++++-------- test/specs/helpers.config.tests.js | 19 +++++++++++++++++++ 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/src/helpers/helpers.config.js b/src/helpers/helpers.config.js index 3c3e33c46..1c70d7a78 100644 --- a/src/helpers/helpers.config.js +++ b/src/helpers/helpers.config.js @@ -6,10 +6,11 @@ import {defined, isArray, isFunction, isObject, resolveObjectKey, _capitalize} f * @param {string[]} [prefixes] - The prefixes for values, in resolution order. * @param {object[]} [rootScopes] - The root option scopes * @param {string|boolean} [fallback] - Parent scopes fallback + * @param {function} [getTarget] - callback for getting the target for changed values * @returns Proxy * @private */ -export function _createResolver(scopes, prefixes = [''], rootScopes = scopes, fallback) { +export function _createResolver(scopes, prefixes = [''], rootScopes = scopes, fallback, getTarget = () => scopes[0]) { if (!defined(fallback)) { fallback = _resolve('_fallback', scopes); } @@ -19,6 +20,7 @@ export function _createResolver(scopes, prefixes = [''], rootScopes = scopes, fa _scopes: scopes, _rootScopes: rootScopes, _fallback: fallback, + _getTarget: getTarget, override: (scope) => _createResolver([scope, ...scopes], prefixes, rootScopes, fallback), }; return new Proxy(cache, { @@ -73,7 +75,8 @@ export function _createResolver(scopes, prefixes = [''], rootScopes = scopes, fa * A trap for setting property values. */ set(target, prop, value) { - scopes[0][prop] = value; // set to top level scope + const storage = target._storage || (target._storage = getTarget()); + storage[prop] = value; // set to top level scope delete target[prop]; // remove from cache delete target._keys; // remove cached keys return true; @@ -276,11 +279,6 @@ function createSubResolver(parentScopes, resolver, prop, value) { const fallback = resolveFallback(resolver._fallback, prop, value); const allScopes = [...parentScopes, ...rootScopes]; const set = new Set(); - const firstParent = parentScopes[0]; - if (isObject(firstParent) && !(prop in firstParent)) { - // create an empty scope for possible stored values, so we always set the values in top scope. - set.add(firstParent[prop] = {}); - } set.add(value); let key = addScopesFromKey(set, allScopes, prop, fallback || prop); if (key === null) { @@ -292,7 +290,13 @@ function createSubResolver(parentScopes, resolver, prop, value) { return false; } } - return _createResolver([...set], [''], rootScopes, fallback); + return _createResolver([...set], [''], rootScopes, fallback, () => { + const parent = resolver._getTarget(); + if (!(prop in parent)) { + parent[prop] = {}; + } + return parent[prop]; + }); } function addScopesFromKey(set, allScopes, key, fallback) { diff --git a/test/specs/helpers.config.tests.js b/test/specs/helpers.config.tests.js index dd7d88784..2c2da9635 100644 --- a/test/specs/helpers.config.tests.js +++ b/test/specs/helpers.config.tests.js @@ -99,6 +99,15 @@ describe('Chart.helpers.config', function() { expect(resolver.getter).toEqual('options getter'); }); + it('should not fail on when options are frozen', function() { + function create() { + const defaults = Object.freeze({default: true}); + const options = Object.freeze({value: true}); + return _createResolver([options, defaults]); + } + expect(create).not.toThrow(); + }); + describe('_fallback', function() { it('should follow simple _fallback', function() { const defaults = { @@ -497,6 +506,16 @@ describe('Chart.helpers.config', function() { expect(options.sub.value).toBeFalse(); expect(defaults.sub.value).toBeTrue(); }); + + it('should throw when setting a value and options is frozen', function() { + const defaults = Object.freeze({default: true}); + const options = Object.freeze({value: true}); + const resolver = _createResolver([options, defaults]); + function set() { + resolver.value = false; + } + expect(set).toThrow(); + }); }); });