|
1 | 1 | /** @typedef {{ file: string, line: number, column: number }} Location */ |
2 | 2 |
|
3 | 3 | import { STATE_SYMBOL } from '../constants.js'; |
4 | | -import { untrack } from '../runtime.js'; |
| 4 | +import { render_effect } from '../reactivity/effects.js'; |
| 5 | +import { current_component_context, untrack } from '../runtime.js'; |
| 6 | +import { get_prototype_of } from '../utils.js'; |
5 | 7 |
|
6 | 8 | /** @type {Record<string, Array<{ start: Location, end: Location, component: Function }>>} */ |
7 | 9 | const boundaries = {}; |
@@ -63,6 +65,8 @@ function get_component() { |
63 | 65 | return null; |
64 | 66 | } |
65 | 67 |
|
| 68 | +export const ADD_OWNER = Symbol('ADD_OWNER'); |
| 69 | + |
66 | 70 | /** |
67 | 71 | * Together with `mark_module_end`, this function establishes the boundaries of a `.svelte` file, |
68 | 72 | * such that subsequent calls to `get_component` can tell us which component is responsible |
@@ -98,60 +102,130 @@ export function mark_module_end(component) { |
98 | 102 | } |
99 | 103 |
|
100 | 104 | /** |
101 | | - * |
102 | 105 | * @param {any} object |
103 | 106 | * @param {any} owner |
| 107 | + * @param {boolean} [global] |
104 | 108 | */ |
105 | | -export function add_owner(object, owner) { |
106 | | - untrack(() => { |
107 | | - add_owner_to_object(object, owner); |
108 | | - }); |
| 109 | +export function add_owner(object, owner, global = false) { |
| 110 | + if (object && !global) { |
| 111 | + // @ts-expect-error |
| 112 | + const component = current_component_context.function; |
| 113 | + const metadata = object[STATE_SYMBOL]; |
| 114 | + if (metadata && !has_owner(metadata, component)) { |
| 115 | + let original = get_owner(metadata); |
| 116 | + |
| 117 | + if (owner.filename !== component.filename) { |
| 118 | + let message = `${component.filename} passed a value to ${owner.filename} with \`bind:\`, but the value is owned by ${original.filename}. Consider creating a binding between ${original.filename} and ${component.filename}`; |
| 119 | + |
| 120 | + // eslint-disable-next-line no-console |
| 121 | + console.warn(message); |
| 122 | + } |
| 123 | + } |
| 124 | + } |
| 125 | + |
| 126 | + add_owner_to_object(object, owner, new Set()); |
109 | 127 | } |
110 | 128 |
|
111 | 129 | /** |
112 | | - * @param {any} object |
113 | | - * @param {Function} owner |
| 130 | + * @param {import('#client').ProxyMetadata | null} from |
| 131 | + * @param {import('#client').ProxyMetadata} to |
114 | 132 | */ |
115 | | -function add_owner_to_object(object, owner) { |
116 | | - if (object?.[STATE_SYMBOL]?.o && !object[STATE_SYMBOL].o.has(owner)) { |
117 | | - object[STATE_SYMBOL].o.add(owner); |
| 133 | +export function widen_ownership(from, to) { |
| 134 | + if (to.owners === null) { |
| 135 | + return; |
| 136 | + } |
| 137 | + |
| 138 | + while (from) { |
| 139 | + if (from.owners === null) { |
| 140 | + to.owners = null; |
| 141 | + break; |
| 142 | + } |
118 | 143 |
|
119 | | - for (const key in object) { |
120 | | - add_owner_to_object(object[key], owner); |
| 144 | + for (const owner of from.owners) { |
| 145 | + to.owners.add(owner); |
121 | 146 | } |
| 147 | + |
| 148 | + from = from.parent; |
122 | 149 | } |
123 | 150 | } |
124 | 151 |
|
125 | 152 | /** |
126 | 153 | * @param {any} object |
| 154 | + * @param {Function} owner |
| 155 | + * @param {Set<any>} seen |
127 | 156 | */ |
128 | | -export function strip_owner(object) { |
129 | | - untrack(() => { |
130 | | - strip_owner_from_object(object); |
131 | | - }); |
| 157 | +function add_owner_to_object(object, owner, seen) { |
| 158 | + const metadata = /** @type {import('#client').ProxyMetadata} */ (object?.[STATE_SYMBOL]); |
| 159 | + |
| 160 | + if (metadata) { |
| 161 | + // this is a state proxy, add owner directly, if not globally shared |
| 162 | + if (metadata.owners !== null) { |
| 163 | + metadata.owners.add(owner); |
| 164 | + } |
| 165 | + } else if (object && typeof object === 'object') { |
| 166 | + if (seen.has(object)) return; |
| 167 | + seen.add(object); |
| 168 | + |
| 169 | + if (object[ADD_OWNER]) { |
| 170 | + // this is a class with state fields. we put this in a render effect |
| 171 | + // so that if state is replaced (e.g. `instance.name = { first, last }`) |
| 172 | + // the new state is also co-owned by the caller of `getContext` |
| 173 | + render_effect(() => { |
| 174 | + object[ADD_OWNER](owner); |
| 175 | + }); |
| 176 | + } else { |
| 177 | + var proto = get_prototype_of(object); |
| 178 | + |
| 179 | + if (proto === Object.prototype) { |
| 180 | + // recurse until we find a state proxy |
| 181 | + for (const key in object) { |
| 182 | + add_owner_to_object(object[key], owner, seen); |
| 183 | + } |
| 184 | + } else if (proto === Array.prototype) { |
| 185 | + // recurse until we find a state proxy |
| 186 | + for (let i = 0; i < object.length; i += 1) { |
| 187 | + add_owner_to_object(object[i], owner, seen); |
| 188 | + } |
| 189 | + } |
| 190 | + } |
| 191 | + } |
132 | 192 | } |
133 | 193 |
|
134 | 194 | /** |
135 | | - * @param {any} object |
| 195 | + * @param {import('#client').ProxyMetadata} metadata |
| 196 | + * @param {Function} component |
| 197 | + * @returns {boolean} |
136 | 198 | */ |
137 | | -function strip_owner_from_object(object) { |
138 | | - if (object?.[STATE_SYMBOL]?.o) { |
139 | | - object[STATE_SYMBOL].o = null; |
140 | | - |
141 | | - for (const key in object) { |
142 | | - strip_owner(object[key]); |
143 | | - } |
| 199 | +function has_owner(metadata, component) { |
| 200 | + if (metadata.owners === null) { |
| 201 | + return true; |
144 | 202 | } |
| 203 | + |
| 204 | + return ( |
| 205 | + metadata.owners.has(component) || |
| 206 | + (metadata.parent !== null && has_owner(metadata.parent, component)) |
| 207 | + ); |
| 208 | +} |
| 209 | + |
| 210 | +/** |
| 211 | + * @param {import('#client').ProxyMetadata} metadata |
| 212 | + * @returns {any} |
| 213 | + */ |
| 214 | +function get_owner(metadata) { |
| 215 | + return ( |
| 216 | + metadata?.owners?.values().next().value ?? |
| 217 | + get_owner(/** @type {import('#client').ProxyMetadata} */ (metadata.parent)) |
| 218 | + ); |
145 | 219 | } |
146 | 220 |
|
147 | 221 | /** |
148 | | - * @param {Set<Function>} owners |
| 222 | + * @param {import('#client').ProxyMetadata} metadata |
149 | 223 | */ |
150 | | -export function check_ownership(owners) { |
| 224 | +export function check_ownership(metadata) { |
151 | 225 | const component = get_component(); |
152 | 226 |
|
153 | | - if (component && !owners.has(component)) { |
154 | | - let original = [...owners][0]; |
| 227 | + if (component && !has_owner(metadata, component)) { |
| 228 | + let original = get_owner(metadata); |
155 | 229 |
|
156 | 230 | let message = |
157 | 231 | // @ts-expect-error |
|
0 commit comments