| 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 // Conversions for IDBKey. | 6 // Conversions for IDBKey. |
| 6 // | 7 // |
| 7 // Per http://www.w3.org/TR/IndexedDB/#key-construct | 8 // Per http://www.w3.org/TR/IndexedDB/#key-construct |
| 8 // | 9 // |
| 9 // "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 |
| 10 // JavaScript objects [ECMA-262], DOMString [WEBIDL], Date [ECMA-262] or float | 11 // JavaScript objects [ECMA-262], DOMString [WEBIDL], Date [ECMA-262] or float |
| 11 // [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 |
| 12 // 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 |
| 13 // 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 |
| 14 // 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 |
| 15 // 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 |
| 16 // 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 |
| 17 // [[PrimitiveValue]] internal property, as defined by [ECMA-262], is not NaN." | 18 // [[PrimitiveValue]] internal property, as defined by [ECMA-262], is not NaN." |
| 18 | 19 |
| 19 // 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 |
| 20 // JavaScript arrays, and any Dates are JavaScript Dates. | 21 // JavaScript arrays, and any Dates are JavaScript Dates. |
| 21 | 22 |
| 22 /** | |
| 23 * Converts a native IDBKey into a Dart object. | |
| 24 * | |
| 25 * May return the original input. May mutate the original input (but will be | |
| 26 * idempotent if mutation occurs). It is assumed that this conversion happens | |
| 27 * on native IDBKeys on all paths that return IDBKeys from native DOM calls. | |
| 28 * | |
| 29 * If necessary, JavaScript Dates are converted into Dart Dates. | |
| 30 */ | |
| 31 _convertNativeToDart_IDBKey(nativeKey) { | |
| 32 // TODO: Implement. | |
| 33 // TODO: Cache conversion somewhere. | |
| 34 return nativeKey; | |
| 35 } | |
| 36 | |
| 37 /** | |
| 38 * Converts a Dart object into a valid IDBKey. | |
| 39 * | |
| 40 * May return the original input. Does not mutate input. | |
| 41 * | |
| 42 * If necessary, [dartKey] may be copied to ensure all lists are converted into | |
| 43 * JavaScript Arrays and Dart Dates into JavaScript Dates. | |
| 44 */ | |
| 45 | |
| 46 _convertDartToNative_IDBKey(dartKey) { | |
| 47 // TODO: Implement. | |
| 48 // TODO: Cache conversion on object. | |
| 49 return dartKey; | |
| 50 } | |
| 51 | |
| 52 | |
| 53 // Conversions for ImageData | 23 // Conversions for ImageData |
| 54 // | 24 // |
| 55 // On Firefox, the returned ImageData is a plain object. | 25 // On Firefox, the returned ImageData is a plain object. |
| 56 | 26 |
| 57 class _TypedImageData implements ImageData { | 27 class _TypedImageData implements ImageData { |
| 58 final Uint8ClampedArray data; | 28 final Uint8ClampedArray data; |
| 59 final int height; | 29 final int height; |
| 60 final int width; | 30 final int width; |
| 61 | 31 |
| 62 _TypedImageData(this.data, this.height, this.width); | 32 _TypedImageData(this.data, this.height, this.width); |
| (...skipping 16 matching lines...) Expand all Loading... |
| 79 _convertDartToNative_ImageData(ImageData imageData) { | 49 _convertDartToNative_ImageData(ImageData imageData) { |
| 80 if (imageData is _ImageDataImpl) return imageData; | 50 if (imageData is _ImageDataImpl) return imageData; |
| 81 return JS('Object', '{data: #, height: #, width: #}', | 51 return JS('Object', '{data: #, height: #, width: #}', |
| 82 imageData.data, imageData.height, imageData.width); | 52 imageData.data, imageData.height, imageData.width); |
| 83 } | 53 } |
| 84 | 54 |
| 85 | 55 |
| 86 /// Converts a JavaScript object with properties into a Dart Map. | 56 /// Converts a JavaScript object with properties into a Dart Map. |
| 87 /// Not suitable for nested objects. | 57 /// Not suitable for nested objects. |
| 88 Map _convertNativeToDart_Dictionary(object) { | 58 Map _convertNativeToDart_Dictionary(object) { |
| 89 // TODO: Implement. | 59 if (object == null) return null; |
| 90 var dict = {}; | 60 var dict = {}; |
| 91 for (final key in JS('List', 'Object.getOwnPropertyNames(#)', object)) { | 61 for (final key in JS('List', 'Object.getOwnPropertyNames(#)', object)) { |
| 92 dict[key] = JS('var', '#[#]', object, key); | 62 dict[key] = JS('var', '#[#]', object, key); |
| 93 } | 63 } |
| 94 return dict; | 64 return dict; |
| 95 } | 65 } |
| 96 | 66 |
| 97 /// Converts a flat Dart map into a JavaScript object with properties. | 67 /// Converts a flat Dart map into a JavaScript object with properties. |
| 98 _convertDartToNative_Dictionary(Map dict) { | 68 _convertDartToNative_Dictionary(Map dict) { |
| 69 if (dict == null) return null; |
| 99 var object = JS('var', '{}'); | 70 var object = JS('var', '{}'); |
| 100 dict.forEach((String key, value) { | 71 dict.forEach((String key, value) { |
| 101 JS('void', '#[#] = #', object, key, value); | 72 JS('void', '#[#] = #', object, key, value); |
| 102 }); | 73 }); |
| 103 return object; | 74 return object; |
| 104 } | 75 } |
| 105 | 76 |
| 106 | 77 |
| 107 /** | 78 /** |
| 108 * Ensures that the input is a JavaScript Array. | 79 * Ensures that the input is a JavaScript Array. |
| 109 * | 80 * |
| 110 * Creates a new JavaScript array if necessary, otherwise returns the original. | 81 * Creates a new JavaScript array if necessary, otherwise returns the original. |
| 111 */ | 82 */ |
| 112 List _convertDartToNative_StringArray(List<String> input) { | 83 List _convertDartToNative_StringArray(List<String> input) { |
| 84 // TODO(sra). Implement this. |
| 113 return input; | 85 return input; |
| 114 } | 86 } |
| 87 |
| 88 |
| 89 // ----------------------------------------------------------------------------- |
| 90 |
| 91 /** |
| 92 * Converts a native IDBKey into a Dart object. |
| 93 * |
| 94 * May return the original input. May mutate the original input (but will be |
| 95 * idempotent if mutation occurs). It is assumed that this conversion happens |
| 96 * on native IDBKeys on all paths that return IDBKeys from native DOM calls. |
| 97 * |
| 98 * If necessary, JavaScript Dates are converted into Dart Dates. |
| 99 */ |
| 100 _convertNativeToDart_IDBKey(nativeKey) { |
| 101 containsDate(object) { |
| 102 if (_isJavaScriptDate(object)) return true; |
| 103 if (object is List) { |
| 104 for (int i = 0; i < object.length; i++) { |
| 105 if (containsDate(object[i])) return true; |
| 106 } |
| 107 } |
| 108 return false; // number, string. |
| 109 } |
| 110 if (containsDate(nativeKey)) { |
| 111 throw const NotImplementedException('IDBKey containing Date'); |
| 112 } |
| 113 // TODO: Cache conversion somewhere? |
| 114 return nativeKey; |
| 115 } |
| 116 |
| 117 /** |
| 118 * Converts a Dart object into a valid IDBKey. |
| 119 * |
| 120 * May return the original input. Does not mutate input. |
| 121 * |
| 122 * If necessary, [dartKey] may be copied to ensure all lists are converted into |
| 123 * JavaScript Arrays and Dart Dates into JavaScript Dates. |
| 124 */ |
| 125 _convertDartToNative_IDBKey(dartKey) { |
| 126 // TODO: Implement. |
| 127 return dartKey; |
| 128 } |
| 129 |
| 130 |
| 131 |
| 132 // May modify original. If so, action is idempotent. |
| 133 _convertNativeToDart_IDBAny(object) { |
| 134 return _convertNativeToDart_AcceptStructuredClone(object); |
| 135 } |
| 136 |
| 137 /// Converts a Dart value into |
| 138 _convertDartToNative_SerializedScriptValue(value) { |
| 139 return _convertDartToNative_PrepareForStructuredClone(value); |
| 140 } |
| 141 |
| 142 |
| 143 /** |
| 144 * Converts a Dart value into a JavaScript SerializedScriptValue. Returns the |
| 145 * original input or a functional 'copy'. Does not mutate the original. |
| 146 * |
| 147 * The main transformation is the translation of Dart Maps are converted to |
| 148 * JavaScript Objects. |
| 149 * |
| 150 * The algorithm is essentially a dry-run of the structured clone algorithm |
| 151 * described at |
| 152 * http://www.whatwg.org/specs/web-apps/current-work/multipage/common-dom-interf
aces.html#structured-clone |
| 153 * https://www.khronos.org/registry/typedarray/specs/latest/#9 |
| 154 * |
| 155 */ |
| 156 _convertDartToNative_PrepareForStructuredClone(value) { |
| 157 |
| 158 // TODO(sra): Replace slots with identity hash table. |
| 159 var values = []; |
| 160 var copies = []; // initially 'null', 'true' during initial DFS, then a copy. |
| 161 |
| 162 int findSlot(value) { |
| 163 int length = values.length; |
| 164 for (int i = 0; i < length; i++) { |
| 165 if (values[i] === value) return i; |
| 166 } |
| 167 values.add(value); |
| 168 copies.add(null); |
| 169 return length; |
| 170 } |
| 171 readSlot(int i) => copies[i]; |
| 172 writeSlot(int i, x) { copies[i] = x; } |
| 173 cleanupSlots() {} // Will be needed if we mark objects with a property. |
| 174 |
| 175 // Returns the input, or a clone of the input. |
| 176 walk(e) { |
| 177 if (e == null) return e; |
| 178 if (e is bool) return e; |
| 179 if (e is num) return e; |
| 180 if (e is String) return e; |
| 181 if (e is Date) { |
| 182 // TODO(sra). |
| 183 throw const NotImplementedException('structured clone of Date'); |
| 184 } |
| 185 if (e is RegExp) { |
| 186 // TODO(sra). |
| 187 throw const NotImplementedException('structured clone of RegExp'); |
| 188 } |
| 189 |
| 190 // The browser's internal structured cloning algorithm will copy certain |
| 191 // types of object, but it will copy only its own implementations and not |
| 192 // just any Dart implementations of the interface. |
| 193 |
| 194 // TODO(sra): The JavaScript objects suitable for direct cloning by the |
| 195 // structured clone algorithm could be tagged with an private interface. |
| 196 |
| 197 if (e is _FileImpl) return e; |
| 198 if (e is File) { |
| 199 throw const NotImplementedException('structured clone of File'); |
| 200 } |
| 201 |
| 202 if (e is _BlobImpl) return e; |
| 203 if (e is Blob) { |
| 204 throw const NotImplementedException('structured clone of Blob'); |
| 205 } |
| 206 |
| 207 if (e is _FileListImpl) return e; |
| 208 if (e is FileList) { |
| 209 throw const NotImplementedException('structured clone of FileList'); |
| 210 } |
| 211 |
| 212 // TODO(sra): Firefox: How to convert _TypedImageData on the other end? |
| 213 if (e is _ImageDataImpl) return e; |
| 214 if (e is ImageData) { |
| 215 throw const NotImplementedException('structured clone of FileList'); |
| 216 } |
| 217 |
| 218 if (e is _ArrayBufferImpl) return e; |
| 219 if (e is ArrayBuffer) { |
| 220 throw const NotImplementedException('structured clone of ArrayBuffer'); |
| 221 } |
| 222 |
| 223 if (e is _ArrayBufferViewImpl) return e; |
| 224 if (e is ArrayBufferView) { |
| 225 throw const NotImplementedException('structured clone of ArrayBufferView')
; |
| 226 } |
| 227 |
| 228 if (e is Map) { |
| 229 var slot = findSlot(e); |
| 230 var copy = readSlot(slot); |
| 231 if (copy != null) return copy; |
| 232 copy = JS('var', '{}'); |
| 233 writeSlot(slot, copy); |
| 234 e.forEach((key, value) { |
| 235 JS('void', '#[#] = #', copy, key, walk(value)); |
| 236 }); |
| 237 return copy; |
| 238 } |
| 239 |
| 240 if (e is List) { |
| 241 // Since a JavaScript Array is an instance of Dart List it is possible to |
| 242 // avoid making a copy of the list if there is no need to copy anything |
| 243 // reachable from the array. We defer creating a new array until a cycle |
| 244 // is detected or a subgraph was copied. |
| 245 int length = e.length; |
| 246 var slot = findSlot(e); |
| 247 var copy = readSlot(slot); |
| 248 if (copy != null) { |
| 249 if (true == copy) { // Cycle, so commit to making a copy. |
| 250 copy = JS('List', 'new Array(#)', length); |
| 251 writeSlot(slot, copy); |
| 252 } |
| 253 return copy; |
| 254 } |
| 255 |
| 256 int i = 0; |
| 257 |
| 258 if (_isJavaScriptArray(e) && |
| 259 // We have to copy immutable lists, otherwise the structured clone |
| 260 // algorithm will copy the .immutable$list marker property, making the |
| 261 // list immutable when received! |
| 262 !_isImmutableJavaScriptArray(e)) { |
| 263 writeSlot(slot, true); // Deferred copy. |
| 264 for ( ; i < length; i++) { |
| 265 var element = e[i]; |
| 266 var elementCopy = walk(element); |
| 267 if (elementCopy !== element) { |
| 268 copy = readSlot(slot); // Cyclic reference may have created it. |
| 269 if (true == copy) { |
| 270 copy = JS('List', 'new Array(#)', length); |
| 271 writeSlot(slot, copy); |
| 272 } |
| 273 for (int j = 0; j < i; j++) { |
| 274 copy[j] = e[j]; |
| 275 } |
| 276 copy[i] = elementCopy; |
| 277 i++; |
| 278 break; |
| 279 } |
| 280 } |
| 281 if (copy == null) { |
| 282 copy = e; |
| 283 writeSlot(slot, copy); |
| 284 } |
| 285 } else { |
| 286 // Not a JavaScript Array. We are forced to make a copy. |
| 287 copy = JS('List', 'new Array(#)', length); |
| 288 writeSlot(slot, copy); |
| 289 } |
| 290 |
| 291 for ( ; i < length; i++) { |
| 292 copy[i] = walk(e[i]); |
| 293 } |
| 294 return copy; |
| 295 } |
| 296 |
| 297 throw const NotImplementedException('structured clone of other type'); |
| 298 } |
| 299 |
| 300 var copy = walk(value); |
| 301 cleanupSlots(); |
| 302 return copy; |
| 303 } |
| 304 |
| 305 /** |
| 306 * Converts a native value into a Dart object. |
| 307 * |
| 308 * May return the original input. May mutate the original input (but will be |
| 309 * idempotent if mutation occurs). It is assumed that this conversion happens |
| 310 * on native serializable script values such values from native DOM calls. |
| 311 * |
| 312 * [object] is the result of a structured clone operation. |
| 313 * |
| 314 * If necessary, JavaScript Dates are converted into Dart Dates. |
| 315 */ |
| 316 _convertNativeToDart_AcceptStructuredClone(object) { |
| 317 |
| 318 // TODO(sra): Replace slots with identity hash table that works on non-dart |
| 319 // objects. |
| 320 var values = []; |
| 321 var copies = []; |
| 322 |
| 323 int findSlot(value) { |
| 324 int length = values.length; |
| 325 for (int i = 0; i < length; i++) { |
| 326 if (values[i] === value) return i; |
| 327 } |
| 328 values.add(value); |
| 329 copies.add(null); |
| 330 return length; |
| 331 } |
| 332 readSlot(int i) => copies[i]; |
| 333 writeSlot(int i, x) { copies[i] = x; } |
| 334 |
| 335 walk(e) { |
| 336 if (e == null) return e; |
| 337 if (e is bool) return e; |
| 338 if (e is num) return e; |
| 339 if (e is String) return e; |
| 340 |
| 341 if (_isJavaScriptDate(e)) { |
| 342 // TODO(sra). |
| 343 throw const NotImplementedException('structured clone of Date'); |
| 344 } |
| 345 |
| 346 if (_isJavaScriptRegExp(e)) { |
| 347 // TODO(sra). |
| 348 throw const NotImplementedException('structured clone of RegExp'); |
| 349 } |
| 350 |
| 351 if (_isJavaScriptSimpleObject(e)) { |
| 352 // TODO(sra): Swizzle the prototype for one of a Map implementation that |
| 353 // uses the properies as storage. |
| 354 var slot = findSlot(e); |
| 355 var copy = readSlot(slot); |
| 356 if (copy != null) return copy; |
| 357 copy = {}; |
| 358 |
| 359 writeSlot(slot, copy); |
| 360 for (final key in JS('List', 'Object.keys(#)', e)) { |
| 361 copy[key] = walk(JS('var', '#[#]', e, key)); |
| 362 } |
| 363 return copy; |
| 364 } |
| 365 |
| 366 if (_isJavaScriptArray(e)) { |
| 367 // Since a JavaScript Array is an instance of Dart List, we can modify it |
| 368 // in-place. |
| 369 var slot = findSlot(e); |
| 370 var copy = readSlot(slot); |
| 371 if (copy != null) return copy; |
| 372 writeSlot(slot, e); |
| 373 |
| 374 int length = e.length; |
| 375 for (int i = 0; i < length; i++) { |
| 376 e[i] = walk(e[i]); |
| 377 } |
| 378 return e; |
| 379 } |
| 380 |
| 381 // Assume anything else is already a valid Dart object, either by having |
| 382 // already been processed, or e.g. a clonable native class. |
| 383 return e; |
| 384 } |
| 385 |
| 386 var copy = walk(object); |
| 387 return copy; |
| 388 } |
| 389 |
| 390 |
| 391 bool _isJavaScriptDate(value) => JS('bool', '# instanceof Date', value); |
| 392 bool _isJavaScriptRegExp(value) => JS('bool', '# instanceof RegExp', value); |
| 393 bool _isJavaScriptArray(value) => JS('bool', '# instanceof Array', value); |
| 394 bool _isJavaScriptSimpleObject(value) => |
| 395 JS('bool', 'Object.getPrototypeOf(#) === Object.prototype', value); |
| 396 bool _isImmutableJavaScriptArray(value) => |
| 397 JS('bool', @'!!(#.immutable$list)', value); |
| OLD | NEW |