OLD | NEW |
(Empty) | |
| 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 |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 |
| 6 // Conversions for IDBKey. |
| 7 // |
| 8 // Per http://www.w3.org/TR/IndexedDB/#key-construct |
| 9 // |
| 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 |
| 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 |
| 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 |
| 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 |
| 18 // [[PrimitiveValue]] internal property, as defined by [ECMA-262], is not NaN." |
| 19 |
| 20 // What is required is to ensure that an Lists in the key are actually |
| 21 // JavaScript arrays, and any Dates are JavaScript Dates. |
| 22 |
| 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. |
| 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 |
| 27 // window as a parameter. |
| 28 |
| 29 part of html_common; |
| 30 |
| 31 /// Converts a Dart value into a JavaScript SerializedScriptValue. |
| 32 convertDartToNative_SerializedScriptValue(value) { |
| 33 return convertDartToNative_PrepareForStructuredClone(value); |
| 34 } |
| 35 |
| 36 /// Since the source object may be viewed via a JavaScript event listener the |
| 37 /// original may not be modified. |
| 38 convertNativeToDart_SerializedScriptValue(object) { |
| 39 return convertNativeToDart_AcceptStructuredClone(object, mustCopy: true); |
| 40 } |
| 41 |
| 42 |
| 43 /** |
| 44 * Converts a Dart value into a JavaScript SerializedScriptValue. Returns the |
| 45 * original input or a functional 'copy'. Does not mutate the original. |
| 46 * |
| 47 * The main transformation is the translation of Dart Maps are converted to |
| 48 * JavaScript Objects. |
| 49 * |
| 50 * The algorithm is essentially a dry-run of the structured clone algorithm |
| 51 * described at |
| 52 * http://www.whatwg.org/specs/web-apps/current-work/multipage/common-dom-interf
aces.html#structured-clone |
| 53 * https://www.khronos.org/registry/typedarray/specs/latest/#9 |
| 54 * |
| 55 * Since the result of this function is expected to be passed only to JavaScript |
| 56 * operations that perform the structured clone algorithm which does not mutate |
| 57 * its output, the result may share structure with the input [value]. |
| 58 */ |
| 59 abstract class _StructuredClone { |
| 60 |
| 61 // TODO(sra): Replace slots with identity hash table. |
| 62 var values = []; |
| 63 var copies = []; // initially 'null', 'true' during initial DFS, then a copy. |
| 64 |
| 65 int findSlot(value) { |
| 66 int length = values.length; |
| 67 for (int i = 0; i < length; i++) { |
| 68 if (identical(values[i], value)) return i; |
| 69 } |
| 70 values.add(value); |
| 71 copies.add(null); |
| 72 return length; |
| 73 } |
| 74 readSlot(int i) => copies[i]; |
| 75 writeSlot(int i, x) { copies[i] = x; } |
| 76 cleanupSlots() {} // Will be needed if we mark objects with a property. |
| 77 bool cloneNotRequired(object); |
| 78 newJsMap(); |
| 79 newJsList(length); |
| 80 void putIntoMap(map, key, value); |
| 81 |
| 82 // Returns the input, or a clone of the input. |
| 83 walk(e) { |
| 84 if (e == null) return e; |
| 85 if (e is bool) return e; |
| 86 if (e is num) return e; |
| 87 if (e is String) return e; |
| 88 if (e is DateTime) { |
| 89 return convertDartToNative_DateTime(e); |
| 90 } |
| 91 if (e is RegExp) { |
| 92 // TODO(sra). |
| 93 throw new UnimplementedError('structured clone of RegExp'); |
| 94 } |
| 95 |
| 96 // The browser's internal structured cloning algorithm will copy certain |
| 97 // types of object, but it will copy only its own implementations and not |
| 98 // just any Dart implementations of the interface. |
| 99 |
| 100 // TODO(sra): The JavaScript objects suitable for direct cloning by the |
| 101 // structured clone algorithm could be tagged with an private interface. |
| 102 |
| 103 if (e is File) return e; |
| 104 if (e is Blob) return e; |
| 105 if (e is FileList) return e; |
| 106 |
| 107 // TODO(sra): Firefox: How to convert _TypedImageData on the other end? |
| 108 if (e is ImageData) return e; |
| 109 if (cloneNotRequired(e)) return e; |
| 110 |
| 111 if (e is Map) { |
| 112 var slot = findSlot(e); |
| 113 var copy = readSlot(slot); |
| 114 if (copy != null) return copy; |
| 115 copy = newJsMap(); |
| 116 writeSlot(slot, copy); |
| 117 e.forEach((key, value) { |
| 118 putIntoMap(copy, key, walk(value)); |
| 119 }); |
| 120 return copy; |
| 121 } |
| 122 |
| 123 if (e is List) { |
| 124 // Since a JavaScript Array is an instance of Dart List it is tempting |
| 125 // in dart2js to avoid making a copy of the list if there is no need |
| 126 // to copy anything reachable from the array. However, the list may have |
| 127 // non-native properties or methods from interceptors and such, e.g. |
| 128 // an immutability marker. So we had to stop doing that. |
| 129 var slot = findSlot(e); |
| 130 var copy = readSlot(slot); |
| 131 if (copy != null) return copy; |
| 132 copy = copyList(e, slot); |
| 133 return copy; |
| 134 } |
| 135 |
| 136 throw new UnimplementedError('structured clone of other type'); |
| 137 } |
| 138 |
| 139 copyList(List e, int slot) { |
| 140 int i = 0; |
| 141 int length = e.length; |
| 142 var copy = newJsList(length); |
| 143 writeSlot(slot, copy); |
| 144 for ( ; i < length; i++) { |
| 145 copy[i] = walk(e[i]); |
| 146 } |
| 147 return copy; |
| 148 } |
| 149 |
| 150 convertDartToNative_PrepareForStructuredClone(value) { |
| 151 var copy = walk(value); |
| 152 cleanupSlots(); |
| 153 return copy; |
| 154 } |
| 155 } |
| 156 |
| 157 /** |
| 158 * Converts a native value into a Dart object. |
| 159 * |
| 160 * If [mustCopy] is [:false:], may return the original input. May mutate the |
| 161 * original input (but will be idempotent if mutation occurs). It is assumed |
| 162 * that this conversion happens on native serializable script values such values |
| 163 * from native DOM calls. |
| 164 * |
| 165 * [object] is the result of a structured clone operation. |
| 166 * |
| 167 * If necessary, JavaScript Dates are converted into Dart Dates. |
| 168 * |
| 169 * If [mustCopy] is [:true:], the entire object is copied and the original input |
| 170 * is not mutated. This should be the case where Dart and JavaScript code can |
| 171 * access the value, for example, via multiple event listeners for |
| 172 * MessageEvents. Mutating the object to make it more 'Dart-like' would corrupt |
| 173 * the value as seen from the JavaScript listeners. |
| 174 */ |
| 175 abstract class _AcceptStructuredClone { |
| 176 |
| 177 // TODO(sra): Replace slots with identity hash table. |
| 178 var values = []; |
| 179 var copies = []; // initially 'null', 'true' during initial DFS, then a copy. |
| 180 bool mustCopy = false; |
| 181 |
| 182 int findSlot(value) { |
| 183 int length = values.length; |
| 184 for (int i = 0; i < length; i++) { |
| 185 if (identicalInJs(values[i], value)) return i; |
| 186 } |
| 187 values.add(value); |
| 188 copies.add(null); |
| 189 return length; |
| 190 } |
| 191 |
| 192 /// Are the two objects identical, but taking into account that two JsObject |
| 193 /// wrappers may not be identical, but their underlying Js Object might be. |
| 194 bool identicalInJs(a, b); |
| 195 readSlot(int i) => copies[i]; |
| 196 writeSlot(int i, x) { copies[i] = x; } |
| 197 |
| 198 /// Iterate over the JS properties. |
| 199 forEachJsField(object, action); |
| 200 |
| 201 /// Create a new Dart list of the given length. May create a native List or |
| 202 /// a JsArray, depending if we're in Dartium or dart2js. |
| 203 newDartList(length); |
| 204 |
| 205 walk(e) { |
| 206 if (e == null) return e; |
| 207 if (e is bool) return e; |
| 208 if (e is num) return e; |
| 209 if (e is String) return e; |
| 210 |
| 211 if (isJavaScriptDate(e)) { |
| 212 return convertNativeToDart_DateTime(e); |
| 213 } |
| 214 |
| 215 if (isJavaScriptRegExp(e)) { |
| 216 // TODO(sra). |
| 217 throw new UnimplementedError('structured clone of RegExp'); |
| 218 } |
| 219 |
| 220 if (isJavaScriptPromise(e)) { |
| 221 return convertNativePromiseToDartFuture(e); |
| 222 } |
| 223 |
| 224 if (isJavaScriptSimpleObject(e)) { |
| 225 // TODO(sra): If mustCopy is false, swizzle the prototype for one of a Map |
| 226 // implementation that uses the properies as storage. |
| 227 var slot = findSlot(e); |
| 228 var copy = readSlot(slot); |
| 229 if (copy != null) return copy; |
| 230 copy = {}; |
| 231 |
| 232 writeSlot(slot, copy); |
| 233 forEachJsField(e, (key, value) => copy[key] = walk(value)); |
| 234 return copy; |
| 235 } |
| 236 |
| 237 if (isJavaScriptArray(e)) { |
| 238 var slot = findSlot(e); |
| 239 var copy = readSlot(slot); |
| 240 if (copy != null) return copy; |
| 241 |
| 242 int length = e.length; |
| 243 // Since a JavaScript Array is an instance of Dart List, we can modify it |
| 244 // in-place unless we must copy. |
| 245 copy = mustCopy ? newDartList(length) : e; |
| 246 writeSlot(slot, copy); |
| 247 |
| 248 for (int i = 0; i < length; i++) { |
| 249 copy[i] = walk(e[i]); |
| 250 } |
| 251 return copy; |
| 252 } |
| 253 |
| 254 // Assume anything else is already a valid Dart object, either by having |
| 255 // already been processed, or e.g. a clonable native class. |
| 256 return e; |
| 257 } |
| 258 |
| 259 convertNativeToDart_AcceptStructuredClone(object, {mustCopy: false}) { |
| 260 this.mustCopy = mustCopy; |
| 261 var copy = walk(object); |
| 262 return copy; |
| 263 } |
| 264 } |
| 265 |
| 266 // Conversions for ContextAttributes. |
| 267 // |
| 268 // On Firefox, the returned ContextAttributes is a plain object. |
| 269 class _TypedContextAttributes implements gl.ContextAttributes { |
| 270 bool alpha; |
| 271 bool antialias; |
| 272 bool depth; |
| 273 bool premultipliedAlpha; |
| 274 bool preserveDrawingBuffer; |
| 275 bool stencil; |
| 276 bool failIfMajorPerformanceCaveat; |
| 277 |
| 278 _TypedContextAttributes(this.alpha, this.antialias, this.depth, |
| 279 this.failIfMajorPerformanceCaveat, this.premultipliedAlpha, |
| 280 this.preserveDrawingBuffer, this.stencil); |
| 281 } |
| 282 |
| 283 gl.ContextAttributes convertNativeToDart_ContextAttributes( |
| 284 nativeContextAttributes) { |
| 285 if (nativeContextAttributes is gl.ContextAttributes) { |
| 286 return nativeContextAttributes; |
| 287 } |
| 288 |
| 289 // On Firefox the above test fails because ContextAttributes is a plain |
| 290 // object so we create a _TypedContextAttributes. |
| 291 |
| 292 return new _TypedContextAttributes( |
| 293 JS('var', '#.alpha', nativeContextAttributes), |
| 294 JS('var', '#.antialias', nativeContextAttributes), |
| 295 JS('var', '#.depth', nativeContextAttributes), |
| 296 JS('var', '#.failIfMajorPerformanceCaveat', nativeContextAttributes), |
| 297 JS('var', '#.premultipliedAlpha', nativeContextAttributes), |
| 298 JS('var', '#.preserveDrawingBuffer', nativeContextAttributes), |
| 299 JS('var', '#.stencil', nativeContextAttributes)); |
| 300 } |
| 301 |
| 302 // Conversions for ImageData |
| 303 // |
| 304 // On Firefox, the returned ImageData is a plain object. |
| 305 |
| 306 class _TypedImageData implements ImageData { |
| 307 final Uint8ClampedList data; |
| 308 final int height; |
| 309 final int width; |
| 310 |
| 311 _TypedImageData(this.data, this.height, this.width); |
| 312 } |
| 313 |
| 314 ImageData convertNativeToDart_ImageData(nativeImageData) { |
| 315 |
| 316 // None of the native getters that return ImageData are declared as returning |
| 317 // [ImageData] since that is incorrect for FireFox, which returns a plain |
| 318 // Object. So we need something that tells the compiler that the ImageData |
| 319 // class has been instantiated. |
| 320 // TODO(sra): Remove this when all the ImageData returning APIs have been |
| 321 // annotated as returning the union ImageData + Object. |
| 322 JS('ImageData', '0'); |
| 323 |
| 324 if (nativeImageData is ImageData) { |
| 325 |
| 326 // Fix for Issue 16069: on IE, the `data` field is a CanvasPixelArray which |
| 327 // has Array as the constructor property. This interferes with finding the |
| 328 // correct interceptor. Fix it by overwriting the constructor property. |
| 329 var data = nativeImageData.data; |
| 330 if (JS('bool', '#.constructor === Array', data)) { |
| 331 if (JS('bool', 'typeof CanvasPixelArray !== "undefined"')) { |
| 332 JS('void', '#.constructor = CanvasPixelArray', data); |
| 333 // This TypedArray property is missing from CanvasPixelArray. |
| 334 JS('void', '#.BYTES_PER_ELEMENT = 1', data); |
| 335 } |
| 336 } |
| 337 |
| 338 return nativeImageData; |
| 339 } |
| 340 |
| 341 // On Firefox the above test fails because [nativeImageData] is a plain |
| 342 // object. So we create a _TypedImageData. |
| 343 |
| 344 return new _TypedImageData( |
| 345 JS('NativeUint8ClampedList', '#.data', nativeImageData), |
| 346 JS('var', '#.height', nativeImageData), |
| 347 JS('var', '#.width', nativeImageData)); |
| 348 } |
| 349 |
| 350 // We can get rid of this conversion if _TypedImageData implements the fields |
| 351 // with native names. |
| 352 convertDartToNative_ImageData(ImageData imageData) { |
| 353 if (imageData is _TypedImageData) { |
| 354 return JS('', '{data: #, height: #, width: #}', |
| 355 imageData.data, imageData.height, imageData.width); |
| 356 } |
| 357 return imageData; |
| 358 } |
| 359 |
| 360 const String _serializedScriptValue = |
| 361 'num|String|bool|' |
| 362 'JSExtendableArray|=Object|' |
| 363 'Blob|File|NativeByteBuffer|NativeTypedData' |
| 364 // TODO(sra): Add Date, RegExp. |
| 365 ; |
| 366 const annotation_Creates_SerializedScriptValue = |
| 367 const Creates(_serializedScriptValue); |
| 368 const annotation_Returns_SerializedScriptValue = |
| 369 const Returns(_serializedScriptValue); |
OLD | NEW |