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 |
11 // JavaScript objects [ECMA-262], DOMString [WEBIDL], Date [ECMA-262] or float | 11 // JavaScript objects [ECMA-262], DOMString [WEBIDL], Date [ECMA-262] or float |
12 // [WEBIDL]. However Arrays are only valid keys if every item in the array is | 12 // [WEBIDL]. However Arrays are only valid keys if every item in the array is |
13 // defined and is a valid key (i.e. sparse arrays can not be valid keys) and if | 13 // defined and is a valid key (i.e. sparse arrays can not be valid keys) and if |
14 // the Array doesn't directly or indirectly contain itself. Any non-numeric | 14 // the Array doesn't directly or indirectly contain itself. Any non-numeric |
15 // properties are ignored, and thus does not affect whether the Array is a valid | 15 // properties are ignored, and thus does not affect whether the Array is a valid |
16 // key. Additionally, if the value is of type float, it is only a valid key if | 16 // key. Additionally, if the value is of type float, it is only a valid key if |
17 // it is not NaN, and if the value is of type Date it is only a valid key if its | 17 // it is not NaN, and if the value is of type Date it is only a valid key if its |
18 // [[PrimitiveValue]] internal property, as defined by [ECMA-262], is not NaN." | 18 // [[PrimitiveValue]] internal property, as defined by [ECMA-262], is not NaN." |
19 | 19 |
20 // What is required is to ensure that an Lists in the key are actually | 20 // What is required is to ensure that an Lists in the key are actually |
21 // JavaScript arrays, and any Dates are JavaScript Dates. | 21 // JavaScript arrays, and any Dates are JavaScript Dates. |
22 | 22 |
23 // Conversions for Window. These check if the window is the local | 23 // Conversions for Window. These check if the window is the local |
24 // window, and if it's not, wraps or unwraps it with a secure wrapper. | 24 // window, and if it's not, wraps or unwraps it with a secure wrapper. |
25 // We need to test for EventTarget here as well as it's a base type. | 25 // We need to test for EventTarget here as well as it's a base type. |
26 // We omit an unwrapper for Window as no methods take a non-local | 26 // We omit an unwrapper for Window as no methods take a non-local |
27 // window as a parameter. | 27 // window as a parameter. |
28 | 28 |
29 part of html; | 29 part of html_common; |
30 | |
31 Window _convertNativeToDart_Window(win) { | |
32 return _DOMWindowCrossFrame._createSafe(win); | |
33 } | |
34 | |
35 EventTarget _convertNativeToDart_EventTarget(e) { | |
36 // Assume it's a Window if it contains the setInterval property. It may be | |
37 // from a different frame - without a patched prototype - so we cannot | |
38 // rely on Dart type checking. | |
39 if (JS('bool', r'"setInterval" in #', e)) | |
40 return _DOMWindowCrossFrame._createSafe(e); | |
41 else | |
42 return e; | |
43 } | |
44 | |
45 EventTarget _convertDartToNative_EventTarget(e) { | |
46 if (e is _DOMWindowCrossFrame) { | |
47 return e._window; | |
48 } else { | |
49 return e; | |
50 } | |
51 } | |
52 | |
53 // Conversions for ImageData | |
54 // | |
55 // On Firefox, the returned ImageData is a plain object. | |
56 | |
57 class _TypedImageData implements ImageData { | |
58 final Uint8ClampedArray data; | |
59 final int height; | |
60 final int width; | |
61 | |
62 _TypedImageData(this.data, this.height, this.width); | |
63 } | |
64 | |
65 ImageData _convertNativeToDart_ImageData(nativeImageData) { | |
66 | |
67 // None of the native getters that return ImageData have the type ImageData | |
68 // since that is incorrect for FireFox (which returns a plain Object). So we | |
69 // need something that tells the compiler that the ImageData class has been | |
70 // instantiated. | |
71 // TODO(sra): Remove this when all the ImageData returning APIs have been | |
72 // annotated as returning the union ImageData + Object. | |
73 JS('ImageData', '0'); | |
74 | |
75 if (nativeImageData is ImageData) return nativeImageData; | |
76 | |
77 // On Firefox the above test fails because imagedata is a plain object. | |
78 // So we create a _TypedImageData. | |
79 | |
80 return new _TypedImageData( | |
81 JS('var', '#.data', nativeImageData), | |
82 JS('var', '#.height', nativeImageData), | |
83 JS('var', '#.width', nativeImageData)); | |
84 } | |
85 | |
86 // We can get rid of this conversion if _TypedImageData implements the fields | |
87 // with native names. | |
88 _convertDartToNative_ImageData(ImageData imageData) { | |
89 if (imageData is _TypedImageData) { | |
90 return JS('', '{data: #, height: #, width: #}', | |
91 imageData.data, imageData.height, imageData.width); | |
92 } | |
93 return imageData; | |
94 } | |
95 | 30 |
96 | 31 |
97 /// Converts a JavaScript object with properties into a Dart Map. | 32 /// Converts a JavaScript object with properties into a Dart Map. |
98 /// Not suitable for nested objects. | 33 /// Not suitable for nested objects. |
99 Map _convertNativeToDart_Dictionary(object) { | 34 Map convertNativeToDart_Dictionary(object) { |
100 if (object == null) return null; | 35 if (object == null) return null; |
101 var dict = {}; | 36 var dict = {}; |
102 for (final key in JS('=List', 'Object.getOwnPropertyNames(#)', object)) { | 37 for (final key in JS('=List', 'Object.getOwnPropertyNames(#)', object)) { |
103 dict[key] = JS('var', '#[#]', object, key); | 38 dict[key] = JS('var', '#[#]', object, key); |
104 } | 39 } |
105 return dict; | 40 return dict; |
106 } | 41 } |
107 | 42 |
108 /// Converts a flat Dart map into a JavaScript object with properties. | 43 /// Converts a flat Dart map into a JavaScript object with properties. |
109 _convertDartToNative_Dictionary(Map dict) { | 44 convertDartToNative_Dictionary(Map dict) { |
110 if (dict == null) return null; | 45 if (dict == null) return null; |
111 var object = JS('var', '{}'); | 46 var object = JS('var', '{}'); |
112 dict.forEach((String key, value) { | 47 dict.forEach((String key, value) { |
113 JS('void', '#[#] = #', object, key, value); | 48 JS('void', '#[#] = #', object, key, value); |
114 }); | 49 }); |
115 return object; | 50 return object; |
116 } | 51 } |
117 | 52 |
118 | 53 |
119 /** | 54 /** |
120 * Ensures that the input is a JavaScript Array. | 55 * Ensures that the input is a JavaScript Array. |
121 * | 56 * |
122 * Creates a new JavaScript array if necessary, otherwise returns the original. | 57 * Creates a new JavaScript array if necessary, otherwise returns the original. |
123 */ | 58 */ |
124 List _convertDartToNative_StringArray(List<String> input) { | 59 List convertDartToNative_StringArray(List<String> input) { |
125 // TODO(sra). Implement this. | 60 // TODO(sra). Implement this. |
126 return input; | 61 return input; |
127 } | 62 } |
128 | 63 |
129 | 64 |
130 // ----------------------------------------------------------------------------- | 65 // ----------------------------------------------------------------------------- |
131 | 66 |
132 /** | |
133 * Converts a native IDBKey into a Dart object. | |
134 * | |
135 * 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 | |
137 * on native IDBKeys on all paths that return IDBKeys from native DOM calls. | |
138 * | |
139 * If necessary, JavaScript Dates are converted into Dart Dates. | |
140 */ | |
141 _convertNativeToDart_IDBKey(nativeKey) { | |
142 containsDate(object) { | |
143 if (_isJavaScriptDate(object)) return true; | |
144 if (object is List) { | |
145 for (int i = 0; i < object.length; i++) { | |
146 if (containsDate(object[i])) return true; | |
147 } | |
148 } | |
149 return false; // number, string. | |
150 } | |
151 if (containsDate(nativeKey)) { | |
152 throw new UnimplementedError('IDBKey containing Date'); | |
153 } | |
154 // TODO: Cache conversion somewhere? | |
155 return nativeKey; | |
156 } | |
157 | |
158 /** | |
159 * Converts a Dart object into a valid IDBKey. | |
160 * | |
161 * May return the original input. Does not mutate input. | |
162 * | |
163 * If necessary, [dartKey] may be copied to ensure all lists are converted into | |
164 * JavaScript Arrays and Dart Dates into JavaScript Dates. | |
165 */ | |
166 _convertDartToNative_IDBKey(dartKey) { | |
167 // TODO: Implement. | |
168 return dartKey; | |
169 } | |
170 | |
171 | |
172 | |
173 /// May modify original. If so, action is idempotent. | |
174 _convertNativeToDart_IDBAny(object) { | |
175 return _convertNativeToDart_AcceptStructuredClone(object, mustCopy: false); | |
176 } | |
177 | |
178 /// Converts a Dart value into a JavaScript SerializedScriptValue. | 67 /// Converts a Dart value into a JavaScript SerializedScriptValue. |
179 _convertDartToNative_SerializedScriptValue(value) { | 68 convertDartToNative_SerializedScriptValue(value) { |
180 return _convertDartToNative_PrepareForStructuredClone(value); | 69 return _convertDartToNative_PrepareForStructuredClone(value); |
181 } | 70 } |
182 | 71 |
183 /// Since the source object may be viewed via a JavaScript event listener the | 72 /// Since the source object may be viewed via a JavaScript event listener the |
184 /// original may not be modified. | 73 /// original may not be modified. |
185 _convertNativeToDart_SerializedScriptValue(object) { | 74 convertNativeToDart_SerializedScriptValue(object) { |
186 return _convertNativeToDart_AcceptStructuredClone(object, mustCopy: true); | 75 return convertNativeToDart_AcceptStructuredClone(object, mustCopy: true); |
187 } | 76 } |
188 | 77 |
189 | 78 |
190 /** | 79 /** |
191 * Converts a Dart value into a JavaScript SerializedScriptValue. Returns the | 80 * Converts a Dart value into a JavaScript SerializedScriptValue. Returns the |
192 * original input or a functional 'copy'. Does not mutate the original. | 81 * original input or a functional 'copy'. Does not mutate the original. |
193 * | 82 * |
194 * The main transformation is the translation of Dart Maps are converted to | 83 * The main transformation is the translation of Dart Maps are converted to |
195 * JavaScript Objects. | 84 * JavaScript Objects. |
196 * | 85 * |
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
239 | 128 |
240 // The browser's internal structured cloning algorithm will copy certain | 129 // The browser's internal structured cloning algorithm will copy certain |
241 // types of object, but it will copy only its own implementations and not | 130 // types of object, but it will copy only its own implementations and not |
242 // just any Dart implementations of the interface. | 131 // just any Dart implementations of the interface. |
243 | 132 |
244 // TODO(sra): The JavaScript objects suitable for direct cloning by the | 133 // TODO(sra): The JavaScript objects suitable for direct cloning by the |
245 // structured clone algorithm could be tagged with an private interface. | 134 // structured clone algorithm could be tagged with an private interface. |
246 | 135 |
247 if (e is File) return e; | 136 if (e is File) return e; |
248 if (e is Blob) return e; | 137 if (e is Blob) return e; |
249 if (e is _FileList) return e; | 138 if (e is FileList) return e; |
250 | 139 |
251 // TODO(sra): Firefox: How to convert _TypedImageData on the other end? | 140 // TODO(sra): Firefox: How to convert _TypedImageData on the other end? |
252 if (e is ImageData) return e; | 141 if (e is ImageData) return e; |
253 if (e is ArrayBuffer) return e; | 142 if (e is ArrayBuffer) return e; |
254 | 143 |
255 if (e is ArrayBufferView) return e; | 144 if (e is ArrayBufferView) return e; |
256 | 145 |
257 if (e is Map) { | 146 if (e is Map) { |
258 var slot = findSlot(e); | 147 var slot = findSlot(e); |
259 var copy = readSlot(slot); | 148 var copy = readSlot(slot); |
(...skipping 17 matching lines...) Expand all Loading... |
277 if (copy != null) { | 166 if (copy != null) { |
278 if (true == copy) { // Cycle, so commit to making a copy. | 167 if (true == copy) { // Cycle, so commit to making a copy. |
279 copy = JS('=List', 'new Array(#)', length); | 168 copy = JS('=List', 'new Array(#)', length); |
280 writeSlot(slot, copy); | 169 writeSlot(slot, copy); |
281 } | 170 } |
282 return copy; | 171 return copy; |
283 } | 172 } |
284 | 173 |
285 int i = 0; | 174 int i = 0; |
286 | 175 |
287 if (_isJavaScriptArray(e) && | 176 if (isJavaScriptArray(e) && |
288 // We have to copy immutable lists, otherwise the structured clone | 177 // We have to copy immutable lists, otherwise the structured clone |
289 // algorithm will copy the .immutable$list marker property, making the | 178 // algorithm will copy the .immutable$list marker property, making the |
290 // list immutable when received! | 179 // list immutable when received! |
291 !_isImmutableJavaScriptArray(e)) { | 180 !isImmutableJavaScriptArray(e)) { |
292 writeSlot(slot, true); // Deferred copy. | 181 writeSlot(slot, true); // Deferred copy. |
293 for ( ; i < length; i++) { | 182 for ( ; i < length; i++) { |
294 var element = e[i]; | 183 var element = e[i]; |
295 var elementCopy = walk(element); | 184 var elementCopy = walk(element); |
296 if (!identical(elementCopy, element)) { | 185 if (!identical(elementCopy, element)) { |
297 copy = readSlot(slot); // Cyclic reference may have created it. | 186 copy = readSlot(slot); // Cyclic reference may have created it. |
298 if (true == copy) { | 187 if (true == copy) { |
299 copy = JS('=List', 'new Array(#)', length); | 188 copy = JS('=List', 'new Array(#)', length); |
300 writeSlot(slot, copy); | 189 writeSlot(slot, copy); |
301 } | 190 } |
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
342 * [object] is the result of a structured clone operation. | 231 * [object] is the result of a structured clone operation. |
343 * | 232 * |
344 * If necessary, JavaScript Dates are converted into Dart Dates. | 233 * If necessary, JavaScript Dates are converted into Dart Dates. |
345 * | 234 * |
346 * If [mustCopy] is [:true:], the entire object is copied and the original input | 235 * 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 | 236 * 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 | 237 * access the value, for example, via multiple event listeners for |
349 * MessageEvents. Mutating the object to make it more 'Dart-like' would corrupt | 238 * MessageEvents. Mutating the object to make it more 'Dart-like' would corrupt |
350 * the value as seen from the JavaScript listeners. | 239 * the value as seen from the JavaScript listeners. |
351 */ | 240 */ |
352 _convertNativeToDart_AcceptStructuredClone(object, {mustCopy = false}) { | 241 convertNativeToDart_AcceptStructuredClone(object, {mustCopy = false}) { |
353 | 242 |
354 // TODO(sra): Replace slots with identity hash table that works on non-dart | 243 // TODO(sra): Replace slots with identity hash table that works on non-dart |
355 // objects. | 244 // objects. |
356 var values = []; | 245 var values = []; |
357 var copies = []; | 246 var copies = []; |
358 | 247 |
359 int findSlot(value) { | 248 int findSlot(value) { |
360 int length = values.length; | 249 int length = values.length; |
361 for (int i = 0; i < length; i++) { | 250 for (int i = 0; i < length; i++) { |
362 if (identical(values[i], value)) return i; | 251 if (identical(values[i], value)) return i; |
363 } | 252 } |
364 values.add(value); | 253 values.add(value); |
365 copies.add(null); | 254 copies.add(null); |
366 return length; | 255 return length; |
367 } | 256 } |
368 readSlot(int i) => copies[i]; | 257 readSlot(int i) => copies[i]; |
369 writeSlot(int i, x) { copies[i] = x; } | 258 writeSlot(int i, x) { copies[i] = x; } |
370 | 259 |
371 walk(e) { | 260 walk(e) { |
372 if (e == null) return e; | 261 if (e == null) return e; |
373 if (e is bool) return e; | 262 if (e is bool) return e; |
374 if (e is num) return e; | 263 if (e is num) return e; |
375 if (e is String) return e; | 264 if (e is String) return e; |
376 | 265 |
377 if (_isJavaScriptDate(e)) { | 266 if (isJavaScriptDate(e)) { |
378 // TODO(sra). | 267 // TODO(sra). |
379 throw new UnimplementedError('structured clone of Date'); | 268 throw new UnimplementedError('structured clone of Date'); |
380 } | 269 } |
381 | 270 |
382 if (_isJavaScriptRegExp(e)) { | 271 if (isJavaScriptRegExp(e)) { |
383 // TODO(sra). | 272 // TODO(sra). |
384 throw new UnimplementedError('structured clone of RegExp'); | 273 throw new UnimplementedError('structured clone of RegExp'); |
385 } | 274 } |
386 | 275 |
387 if (_isJavaScriptSimpleObject(e)) { | 276 if (isJavaScriptSimpleObject(e)) { |
388 // TODO(sra): If mustCopy is false, swizzle the prototype for one of a Map | 277 // TODO(sra): If mustCopy is false, swizzle the prototype for one of a Map |
389 // implementation that uses the properies as storage. | 278 // implementation that uses the properies as storage. |
390 var slot = findSlot(e); | 279 var slot = findSlot(e); |
391 var copy = readSlot(slot); | 280 var copy = readSlot(slot); |
392 if (copy != null) return copy; | 281 if (copy != null) return copy; |
393 copy = {}; | 282 copy = {}; |
394 | 283 |
395 writeSlot(slot, copy); | 284 writeSlot(slot, copy); |
396 for (final key in JS('=List', 'Object.keys(#)', e)) { | 285 for (final key in JS('=List', 'Object.keys(#)', e)) { |
397 copy[key] = walk(JS('var', '#[#]', e, key)); | 286 copy[key] = walk(JS('var', '#[#]', e, key)); |
398 } | 287 } |
399 return copy; | 288 return copy; |
400 } | 289 } |
401 | 290 |
402 if (_isJavaScriptArray(e)) { | 291 if (isJavaScriptArray(e)) { |
403 var slot = findSlot(e); | 292 var slot = findSlot(e); |
404 var copy = readSlot(slot); | 293 var copy = readSlot(slot); |
405 if (copy != null) return copy; | 294 if (copy != null) return copy; |
406 | 295 |
407 int length = e.length; | 296 int length = e.length; |
408 // Since a JavaScript Array is an instance of Dart List, we can modify it | 297 // Since a JavaScript Array is an instance of Dart List, we can modify it |
409 // in-place unless we must copy. | 298 // in-place unless we must copy. |
410 copy = mustCopy ? JS('=List', 'new Array(#)', length) : e; | 299 copy = mustCopy ? JS('=List', 'new Array(#)', length) : e; |
411 writeSlot(slot, copy); | 300 writeSlot(slot, copy); |
412 | 301 |
413 for (int i = 0; i < length; i++) { | 302 for (int i = 0; i < length; i++) { |
414 copy[i] = walk(e[i]); | 303 copy[i] = walk(e[i]); |
415 } | 304 } |
416 return copy; | 305 return copy; |
417 } | 306 } |
418 | 307 |
419 // Assume anything else is already a valid Dart object, either by having | 308 // Assume anything else is already a valid Dart object, either by having |
420 // already been processed, or e.g. a clonable native class. | 309 // already been processed, or e.g. a clonable native class. |
421 return e; | 310 return e; |
422 } | 311 } |
423 | 312 |
424 var copy = walk(object); | 313 var copy = walk(object); |
425 return copy; | 314 return copy; |
426 } | 315 } |
427 | 316 |
428 | 317 |
429 bool _isJavaScriptDate(value) => JS('bool', '# instanceof Date', value); | 318 bool isJavaScriptDate(value) => JS('bool', '# instanceof Date', value); |
430 bool _isJavaScriptRegExp(value) => JS('bool', '# instanceof RegExp', value); | 319 bool isJavaScriptRegExp(value) => JS('bool', '# instanceof RegExp', value); |
431 bool _isJavaScriptArray(value) => JS('bool', '# instanceof Array', value); | 320 bool isJavaScriptArray(value) => JS('bool', '# instanceof Array', value); |
432 bool _isJavaScriptSimpleObject(value) => | 321 bool isJavaScriptSimpleObject(value) => |
433 JS('bool', 'Object.getPrototypeOf(#) === Object.prototype', value); | 322 JS('bool', 'Object.getPrototypeOf(#) === Object.prototype', value); |
434 bool _isImmutableJavaScriptArray(value) => | 323 bool isImmutableJavaScriptArray(value) => |
435 JS('bool', r'!!(#.immutable$list)', value); | 324 JS('bool', r'!!(#.immutable$list)', value); |
436 | 325 |
437 | 326 |
438 | 327 |
439 const String _serializedScriptValue = | 328 const String _serializedScriptValue = |
440 'num|String|bool|' | 329 'num|String|bool|' |
441 '=List|=Object|' | 330 '=List|=Object|' |
442 'Blob|File|ArrayBuffer|ArrayBufferView' | 331 'Blob|File|ArrayBuffer|ArrayBufferView' |
443 // TODO(sra): Add Date, RegExp. | 332 // TODO(sra): Add Date, RegExp. |
444 ; | 333 ; |
445 const _annotation_Creates_SerializedScriptValue = | 334 const annotation_Creates_SerializedScriptValue = |
446 const Creates(_serializedScriptValue); | 335 const Creates(_serializedScriptValue); |
447 const _annotation_Returns_SerializedScriptValue = | 336 const annotation_Returns_SerializedScriptValue = |
448 const Returns(_serializedScriptValue); | 337 const Returns(_serializedScriptValue); |
449 | |
450 const String _idbKey = '=List|=Object|num|String'; // TODO(sra): Add Date. | |
451 const _annotation_Creates_IDBKey = const Creates(_idbKey); | |
452 const _annotation_Returns_IDBKey = const Returns(_idbKey); | |
OLD | NEW |