OLD | NEW |
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 | 5 |
6 // Conversions for IDBKey. | 6 // Conversions for IDBKey. |
7 // | 7 // |
8 // Per http://www.w3.org/TR/IndexedDB/#key-construct | 8 // Per http://www.w3.org/TR/IndexedDB/#key-construct |
9 // | 9 // |
10 // "A value is said to be a valid key if it is one of the following types: Array | 10 // "A value is said to be a valid key if it is one of the following types: Array |
(...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
87 // with native names. | 87 // with native names. |
88 _convertDartToNative_ImageData(ImageData imageData) { | 88 _convertDartToNative_ImageData(ImageData imageData) { |
89 if (imageData is _TypedImageData) { | 89 if (imageData is _TypedImageData) { |
90 return JS('', '{data: #, height: #, width: #}', | 90 return JS('', '{data: #, height: #, width: #}', |
91 imageData.data, imageData.height, imageData.width); | 91 imageData.data, imageData.height, imageData.width); |
92 } | 92 } |
93 return imageData; | 93 return imageData; |
94 } | 94 } |
95 | 95 |
96 | 96 |
97 /// Converts a JavaScript object with properties into a Dart Map. | |
98 /// Not suitable for nested objects. | |
99 Map _convertNativeToDart_Dictionary(object) { | |
100 if (object == null) return null; | |
101 var dict = {}; | |
102 for (final key in JS('=List', 'Object.getOwnPropertyNames(#)', object)) { | |
103 dict[key] = JS('var', '#[#]', object, key); | |
104 } | |
105 return dict; | |
106 } | |
107 | |
108 /// Converts a flat Dart map into a JavaScript object with properties. | |
109 _convertDartToNative_Dictionary(Map dict) { | |
110 if (dict == null) return null; | |
111 var object = JS('var', '{}'); | |
112 dict.forEach((String key, value) { | |
113 JS('void', '#[#] = #', object, key, value); | |
114 }); | |
115 return object; | |
116 } | |
117 | |
118 | |
119 /** | |
120 * Ensures that the input is a JavaScript Array. | |
121 * | |
122 * Creates a new JavaScript array if necessary, otherwise returns the original. | |
123 */ | |
124 List _convertDartToNative_StringArray(List<String> input) { | |
125 // TODO(sra). Implement this. | |
126 return input; | |
127 } | |
128 | |
129 | |
130 // ----------------------------------------------------------------------------- | 97 // ----------------------------------------------------------------------------- |
131 | 98 |
132 /** | 99 /** |
133 * Converts a native IDBKey into a Dart object. | 100 * Converts a native IDBKey into a Dart object. |
134 * | 101 * |
135 * May return the original input. May mutate the original input (but will be | 102 * May return the original input. May mutate the original input (but will be |
136 * idempotent if mutation occurs). It is assumed that this conversion happens | 103 * idempotent if mutation occurs). It is assumed that this conversion happens |
137 * on native IDBKeys on all paths that return IDBKeys from native DOM calls. | 104 * on native IDBKeys on all paths that return IDBKeys from native DOM calls. |
138 * | 105 * |
139 * If necessary, JavaScript Dates are converted into Dart Dates. | 106 * If necessary, JavaScript Dates are converted into Dart Dates. |
140 */ | 107 */ |
141 _convertNativeToDart_IDBKey(nativeKey) { | 108 _convertNativeToDart_IDBKey(nativeKey) { |
142 containsDate(object) { | 109 containsDate(object) { |
143 if (_isJavaScriptDate(object)) return true; | 110 if (isJavaScriptDate(object)) return true; |
144 if (object is List) { | 111 if (object is List) { |
145 for (int i = 0; i < object.length; i++) { | 112 for (int i = 0; i < object.length; i++) { |
146 if (containsDate(object[i])) return true; | 113 if (containsDate(object[i])) return true; |
147 } | 114 } |
148 } | 115 } |
149 return false; // number, string. | 116 return false; // number, string. |
150 } | 117 } |
151 if (containsDate(nativeKey)) { | 118 if (containsDate(nativeKey)) { |
152 throw new UnimplementedError('IDBKey containing Date'); | 119 throw new UnimplementedError('IDBKey containing Date'); |
153 } | 120 } |
(...skipping 11 matching lines...) Expand all Loading... |
165 */ | 132 */ |
166 _convertDartToNative_IDBKey(dartKey) { | 133 _convertDartToNative_IDBKey(dartKey) { |
167 // TODO: Implement. | 134 // TODO: Implement. |
168 return dartKey; | 135 return dartKey; |
169 } | 136 } |
170 | 137 |
171 | 138 |
172 | 139 |
173 /// May modify original. If so, action is idempotent. | 140 /// May modify original. If so, action is idempotent. |
174 _convertNativeToDart_IDBAny(object) { | 141 _convertNativeToDart_IDBAny(object) { |
175 return _convertNativeToDart_AcceptStructuredClone(object, mustCopy: false); | 142 return convertNativeToDart_AcceptStructuredClone(object, mustCopy: false); |
176 } | 143 } |
177 | 144 |
178 /// Converts a Dart value into a JavaScript SerializedScriptValue. | |
179 _convertDartToNative_SerializedScriptValue(value) { | |
180 return _convertDartToNative_PrepareForStructuredClone(value); | |
181 } | |
182 | |
183 /// Since the source object may be viewed via a JavaScript event listener the | |
184 /// original may not be modified. | |
185 _convertNativeToDart_SerializedScriptValue(object) { | |
186 return _convertNativeToDart_AcceptStructuredClone(object, mustCopy: true); | |
187 } | |
188 | |
189 | |
190 /** | |
191 * Converts a Dart value into a JavaScript SerializedScriptValue. Returns the | |
192 * original input or a functional 'copy'. Does not mutate the original. | |
193 * | |
194 * The main transformation is the translation of Dart Maps are converted to | |
195 * JavaScript Objects. | |
196 * | |
197 * The algorithm is essentially a dry-run of the structured clone algorithm | |
198 * described at | |
199 * http://www.whatwg.org/specs/web-apps/current-work/multipage/common-dom-interf
aces.html#structured-clone | |
200 * https://www.khronos.org/registry/typedarray/specs/latest/#9 | |
201 * | |
202 * Since the result of this function is expected to be passed only to JavaScript | |
203 * operations that perform the structured clone algorithm which does not mutate | |
204 * its output, the result may share structure with the input [value]. | |
205 */ | |
206 _convertDartToNative_PrepareForStructuredClone(value) { | |
207 | |
208 // TODO(sra): Replace slots with identity hash table. | |
209 var values = []; | |
210 var copies = []; // initially 'null', 'true' during initial DFS, then a copy. | |
211 | |
212 int findSlot(value) { | |
213 int length = values.length; | |
214 for (int i = 0; i < length; i++) { | |
215 if (identical(values[i], value)) return i; | |
216 } | |
217 values.add(value); | |
218 copies.add(null); | |
219 return length; | |
220 } | |
221 readSlot(int i) => copies[i]; | |
222 writeSlot(int i, x) { copies[i] = x; } | |
223 cleanupSlots() {} // Will be needed if we mark objects with a property. | |
224 | |
225 // Returns the input, or a clone of the input. | |
226 walk(e) { | |
227 if (e == null) return e; | |
228 if (e is bool) return e; | |
229 if (e is num) return e; | |
230 if (e is String) return e; | |
231 if (e is Date) { | |
232 // TODO(sra). | |
233 throw new UnimplementedError('structured clone of Date'); | |
234 } | |
235 if (e is RegExp) { | |
236 // TODO(sra). | |
237 throw new UnimplementedError('structured clone of RegExp'); | |
238 } | |
239 | |
240 // The browser's internal structured cloning algorithm will copy certain | |
241 // types of object, but it will copy only its own implementations and not | |
242 // just any Dart implementations of the interface. | |
243 | |
244 // TODO(sra): The JavaScript objects suitable for direct cloning by the | |
245 // structured clone algorithm could be tagged with an private interface. | |
246 | |
247 if (e is File) return e; | |
248 if (e is Blob) return e; | |
249 if (e is _FileList) return e; | |
250 | |
251 // TODO(sra): Firefox: How to convert _TypedImageData on the other end? | |
252 if (e is ImageData) return e; | |
253 if (e is ArrayBuffer) return e; | |
254 | |
255 if (e is ArrayBufferView) return e; | |
256 | |
257 if (e is Map) { | |
258 var slot = findSlot(e); | |
259 var copy = readSlot(slot); | |
260 if (copy != null) return copy; | |
261 copy = JS('var', '{}'); | |
262 writeSlot(slot, copy); | |
263 e.forEach((key, value) { | |
264 JS('void', '#[#] = #', copy, key, walk(value)); | |
265 }); | |
266 return copy; | |
267 } | |
268 | |
269 if (e is List) { | |
270 // Since a JavaScript Array is an instance of Dart List it is possible to | |
271 // avoid making a copy of the list if there is no need to copy anything | |
272 // reachable from the array. We defer creating a new array until a cycle | |
273 // is detected or a subgraph was copied. | |
274 int length = e.length; | |
275 var slot = findSlot(e); | |
276 var copy = readSlot(slot); | |
277 if (copy != null) { | |
278 if (true == copy) { // Cycle, so commit to making a copy. | |
279 copy = JS('=List', 'new Array(#)', length); | |
280 writeSlot(slot, copy); | |
281 } | |
282 return copy; | |
283 } | |
284 | |
285 int i = 0; | |
286 | |
287 if (_isJavaScriptArray(e) && | |
288 // We have to copy immutable lists, otherwise the structured clone | |
289 // algorithm will copy the .immutable$list marker property, making the | |
290 // list immutable when received! | |
291 !_isImmutableJavaScriptArray(e)) { | |
292 writeSlot(slot, true); // Deferred copy. | |
293 for ( ; i < length; i++) { | |
294 var element = e[i]; | |
295 var elementCopy = walk(element); | |
296 if (!identical(elementCopy, element)) { | |
297 copy = readSlot(slot); // Cyclic reference may have created it. | |
298 if (true == copy) { | |
299 copy = JS('=List', 'new Array(#)', length); | |
300 writeSlot(slot, copy); | |
301 } | |
302 for (int j = 0; j < i; j++) { | |
303 copy[j] = e[j]; | |
304 } | |
305 copy[i] = elementCopy; | |
306 i++; | |
307 break; | |
308 } | |
309 } | |
310 if (copy == null) { | |
311 copy = e; | |
312 writeSlot(slot, copy); | |
313 } | |
314 } else { | |
315 // Not a JavaScript Array. We are forced to make a copy. | |
316 copy = JS('=List', 'new Array(#)', length); | |
317 writeSlot(slot, copy); | |
318 } | |
319 | |
320 for ( ; i < length; i++) { | |
321 copy[i] = walk(e[i]); | |
322 } | |
323 return copy; | |
324 } | |
325 | |
326 throw new UnimplementedError('structured clone of other type'); | |
327 } | |
328 | |
329 var copy = walk(value); | |
330 cleanupSlots(); | |
331 return copy; | |
332 } | |
333 | |
334 /** | |
335 * Converts a native value into a Dart object. | |
336 * | |
337 * If [mustCopy] is [:false:], may return the original input. May mutate the | |
338 * original input (but will be idempotent if mutation occurs). It is assumed | |
339 * that this conversion happens on native serializable script values such values | |
340 * from native DOM calls. | |
341 * | |
342 * [object] is the result of a structured clone operation. | |
343 * | |
344 * If necessary, JavaScript Dates are converted into Dart Dates. | |
345 * | |
346 * If [mustCopy] is [:true:], the entire object is copied and the original input | |
347 * is not mutated. This should be the case where Dart and JavaScript code can | |
348 * access the value, for example, via multiple event listeners for | |
349 * MessageEvents. Mutating the object to make it more 'Dart-like' would corrupt | |
350 * the value as seen from the JavaScript listeners. | |
351 */ | |
352 _convertNativeToDart_AcceptStructuredClone(object, {mustCopy = false}) { | |
353 | |
354 // TODO(sra): Replace slots with identity hash table that works on non-dart | |
355 // objects. | |
356 var values = []; | |
357 var copies = []; | |
358 | |
359 int findSlot(value) { | |
360 int length = values.length; | |
361 for (int i = 0; i < length; i++) { | |
362 if (identical(values[i], value)) return i; | |
363 } | |
364 values.add(value); | |
365 copies.add(null); | |
366 return length; | |
367 } | |
368 readSlot(int i) => copies[i]; | |
369 writeSlot(int i, x) { copies[i] = x; } | |
370 | |
371 walk(e) { | |
372 if (e == null) return e; | |
373 if (e is bool) return e; | |
374 if (e is num) return e; | |
375 if (e is String) return e; | |
376 | |
377 if (_isJavaScriptDate(e)) { | |
378 // TODO(sra). | |
379 throw new UnimplementedError('structured clone of Date'); | |
380 } | |
381 | |
382 if (_isJavaScriptRegExp(e)) { | |
383 // TODO(sra). | |
384 throw new UnimplementedError('structured clone of RegExp'); | |
385 } | |
386 | |
387 if (_isJavaScriptSimpleObject(e)) { | |
388 // TODO(sra): If mustCopy is false, swizzle the prototype for one of a Map | |
389 // implementation that uses the properies as storage. | |
390 var slot = findSlot(e); | |
391 var copy = readSlot(slot); | |
392 if (copy != null) return copy; | |
393 copy = {}; | |
394 | |
395 writeSlot(slot, copy); | |
396 for (final key in JS('=List', 'Object.keys(#)', e)) { | |
397 copy[key] = walk(JS('var', '#[#]', e, key)); | |
398 } | |
399 return copy; | |
400 } | |
401 | |
402 if (_isJavaScriptArray(e)) { | |
403 var slot = findSlot(e); | |
404 var copy = readSlot(slot); | |
405 if (copy != null) return copy; | |
406 | |
407 int length = e.length; | |
408 // Since a JavaScript Array is an instance of Dart List, we can modify it | |
409 // in-place unless we must copy. | |
410 copy = mustCopy ? JS('=List', 'new Array(#)', length) : e; | |
411 writeSlot(slot, copy); | |
412 | |
413 for (int i = 0; i < length; i++) { | |
414 copy[i] = walk(e[i]); | |
415 } | |
416 return copy; | |
417 } | |
418 | |
419 // Assume anything else is already a valid Dart object, either by having | |
420 // already been processed, or e.g. a clonable native class. | |
421 return e; | |
422 } | |
423 | |
424 var copy = walk(object); | |
425 return copy; | |
426 } | |
427 | |
428 | |
429 bool _isJavaScriptDate(value) => JS('bool', '# instanceof Date', value); | |
430 bool _isJavaScriptRegExp(value) => JS('bool', '# instanceof RegExp', value); | |
431 bool _isJavaScriptArray(value) => JS('bool', '# instanceof Array', value); | |
432 bool _isJavaScriptSimpleObject(value) => | |
433 JS('bool', 'Object.getPrototypeOf(#) === Object.prototype', value); | |
434 bool _isImmutableJavaScriptArray(value) => | |
435 JS('bool', r'!!(#.immutable$list)', value); | |
436 | |
437 | |
438 | |
439 const String _serializedScriptValue = | |
440 'num|String|bool|' | |
441 '=List|=Object|' | |
442 'Blob|File|ArrayBuffer|ArrayBufferView' | |
443 // TODO(sra): Add Date, RegExp. | |
444 ; | |
445 const _annotation_Creates_SerializedScriptValue = | |
446 const Creates(_serializedScriptValue); | |
447 const _annotation_Returns_SerializedScriptValue = | |
448 const Returns(_serializedScriptValue); | |
449 | 145 |
450 const String _idbKey = '=List|=Object|num|String'; // TODO(sra): Add Date. | 146 const String _idbKey = '=List|=Object|num|String'; // TODO(sra): Add Date. |
451 const _annotation_Creates_IDBKey = const Creates(_idbKey); | 147 const _annotation_Creates_IDBKey = const Creates(_idbKey); |
452 const _annotation_Returns_IDBKey = const Returns(_idbKey); | 148 const _annotation_Returns_IDBKey = const Returns(_idbKey); |
OLD | NEW |