| 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 |