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 | |
32 /// Converts a JavaScript object with properties into a Dart Map. | |
33 /// Not suitable for nested objects. | |
34 Map convertNativeToDart_Dictionary(object) { | |
35 if (object == null) return null; | |
36 var dict = {}; | |
37 for (final key in JS('=List', 'Object.getOwnPropertyNames(#)', object)) { | |
38 dict[key] = JS('var', '#[#]', object, key); | |
39 } | |
40 return dict; | |
41 } | |
42 | |
43 /// Converts a flat Dart map into a JavaScript object with properties. | |
44 convertDartToNative_Dictionary(Map dict) { | |
45 if (dict == null) return null; | |
46 var object = JS('var', '{}'); | |
47 dict.forEach((String key, value) { | |
48 JS('void', '#[#] = #', object, key, value); | |
49 }); | |
50 return object; | |
51 } | |
52 | |
53 | |
54 /** | |
55 * Ensures that the input is a JavaScript Array. | |
56 * | |
57 * Creates a new JavaScript array if necessary, otherwise returns the original. | |
58 */ | |
59 List convertDartToNative_StringArray(List<String> input) { | |
60 // TODO(sra). Implement this. | |
61 return input; | |
62 } | |
63 | |
64 | |
65 // ----------------------------------------------------------------------------- | |
66 | |
67 /// Converts a Dart value into a JavaScript SerializedScriptValue. | |
68 convertDartToNative_SerializedScriptValue(value) { | |
69 return _convertDartToNative_PrepareForStructuredClone(value); | |
70 } | |
71 | |
72 /// Since the source object may be viewed via a JavaScript event listener the | |
73 /// original may not be modified. | |
74 convertNativeToDart_SerializedScriptValue(object) { | |
75 return convertNativeToDart_AcceptStructuredClone(object, mustCopy: true); | |
76 } | |
77 | |
78 | |
79 /** | |
80 * Converts a Dart value into a JavaScript SerializedScriptValue. Returns the | |
81 * original input or a functional 'copy'. Does not mutate the original. | |
82 * | |
83 * The main transformation is the translation of Dart Maps are converted to | |
84 * JavaScript Objects. | |
85 * | |
86 * The algorithm is essentially a dry-run of the structured clone algorithm | |
87 * described at | |
88 * http://www.whatwg.org/specs/web-apps/current-work/multipage/common-dom-interf
aces.html#structured-clone | |
89 * https://www.khronos.org/registry/typedarray/specs/latest/#9 | |
90 * | |
91 * Since the result of this function is expected to be passed only to JavaScript | |
92 * operations that perform the structured clone algorithm which does not mutate | |
93 * its output, the result may share structure with the input [value]. | |
94 */ | |
95 _convertDartToNative_PrepareForStructuredClone(value) { | |
96 | |
97 // TODO(sra): Replace slots with identity hash table. | |
98 var values = []; | |
99 var copies = []; // initially 'null', 'true' during initial DFS, then a copy. | |
100 | |
101 int findSlot(value) { | |
102 int length = values.length; | |
103 for (int i = 0; i < length; i++) { | |
104 if (identical(values[i], value)) return i; | |
105 } | |
106 values.add(value); | |
107 copies.add(null); | |
108 return length; | |
109 } | |
110 readSlot(int i) => copies[i]; | |
111 writeSlot(int i, x) { copies[i] = x; } | |
112 cleanupSlots() {} // Will be needed if we mark objects with a property. | |
113 | |
114 // Returns the input, or a clone of the input. | |
115 walk(e) { | |
116 if (e == null) return e; | |
117 if (e is bool) return e; | |
118 if (e is num) return e; | |
119 if (e is String) return e; | |
120 if (e is Date) { | |
121 // TODO(sra). | |
122 throw new UnimplementedError('structured clone of Date'); | |
123 } | |
124 if (e is RegExp) { | |
125 // TODO(sra). | |
126 throw new UnimplementedError('structured clone of RegExp'); | |
127 } | |
128 | |
129 // The browser's internal structured cloning algorithm will copy certain | |
130 // types of object, but it will copy only its own implementations and not | |
131 // just any Dart implementations of the interface. | |
132 | |
133 // TODO(sra): The JavaScript objects suitable for direct cloning by the | |
134 // structured clone algorithm could be tagged with an private interface. | |
135 | |
136 if (e is File) return e; | |
137 if (e is Blob) return e; | |
138 if (e is FileList) return e; | |
139 | |
140 // TODO(sra): Firefox: How to convert _TypedImageData on the other end? | |
141 if (e is ImageData) return e; | |
142 if (e is ArrayBuffer) return e; | |
143 | |
144 if (e is ArrayBufferView) return e; | |
145 | |
146 if (e is Map) { | |
147 var slot = findSlot(e); | |
148 var copy = readSlot(slot); | |
149 if (copy != null) return copy; | |
150 copy = JS('var', '{}'); | |
151 writeSlot(slot, copy); | |
152 e.forEach((key, value) { | |
153 JS('void', '#[#] = #', copy, key, walk(value)); | |
154 }); | |
155 return copy; | |
156 } | |
157 | |
158 if (e is List) { | |
159 // Since a JavaScript Array is an instance of Dart List it is possible to | |
160 // avoid making a copy of the list if there is no need to copy anything | |
161 // reachable from the array. We defer creating a new array until a cycle | |
162 // is detected or a subgraph was copied. | |
163 int length = e.length; | |
164 var slot = findSlot(e); | |
165 var copy = readSlot(slot); | |
166 if (copy != null) { | |
167 if (true == copy) { // Cycle, so commit to making a copy. | |
168 copy = JS('=List', 'new Array(#)', length); | |
169 writeSlot(slot, copy); | |
170 } | |
171 return copy; | |
172 } | |
173 | |
174 int i = 0; | |
175 | |
176 if (isJavaScriptArray(e) && | |
177 // We have to copy immutable lists, otherwise the structured clone | |
178 // algorithm will copy the .immutable$list marker property, making the | |
179 // list immutable when received! | |
180 !isImmutableJavaScriptArray(e)) { | |
181 writeSlot(slot, true); // Deferred copy. | |
182 for ( ; i < length; i++) { | |
183 var element = e[i]; | |
184 var elementCopy = walk(element); | |
185 if (!identical(elementCopy, element)) { | |
186 copy = readSlot(slot); // Cyclic reference may have created it. | |
187 if (true == copy) { | |
188 copy = JS('=List', 'new Array(#)', length); | |
189 writeSlot(slot, copy); | |
190 } | |
191 for (int j = 0; j < i; j++) { | |
192 copy[j] = e[j]; | |
193 } | |
194 copy[i] = elementCopy; | |
195 i++; | |
196 break; | |
197 } | |
198 } | |
199 if (copy == null) { | |
200 copy = e; | |
201 writeSlot(slot, copy); | |
202 } | |
203 } else { | |
204 // Not a JavaScript Array. We are forced to make a copy. | |
205 copy = JS('=List', 'new Array(#)', length); | |
206 writeSlot(slot, copy); | |
207 } | |
208 | |
209 for ( ; i < length; i++) { | |
210 copy[i] = walk(e[i]); | |
211 } | |
212 return copy; | |
213 } | |
214 | |
215 throw new UnimplementedError('structured clone of other type'); | |
216 } | |
217 | |
218 var copy = walk(value); | |
219 cleanupSlots(); | |
220 return copy; | |
221 } | |
222 | |
223 /** | |
224 * Converts a native value into a Dart object. | |
225 * | |
226 * If [mustCopy] is [:false:], may return the original input. May mutate the | |
227 * original input (but will be idempotent if mutation occurs). It is assumed | |
228 * that this conversion happens on native serializable script values such values | |
229 * from native DOM calls. | |
230 * | |
231 * [object] is the result of a structured clone operation. | |
232 * | |
233 * If necessary, JavaScript Dates are converted into Dart Dates. | |
234 * | |
235 * If [mustCopy] is [:true:], the entire object is copied and the original input | |
236 * is not mutated. This should be the case where Dart and JavaScript code can | |
237 * access the value, for example, via multiple event listeners for | |
238 * MessageEvents. Mutating the object to make it more 'Dart-like' would corrupt | |
239 * the value as seen from the JavaScript listeners. | |
240 */ | |
241 convertNativeToDart_AcceptStructuredClone(object, {mustCopy = false}) { | |
242 | |
243 // TODO(sra): Replace slots with identity hash table that works on non-dart | |
244 // objects. | |
245 var values = []; | |
246 var copies = []; | |
247 | |
248 int findSlot(value) { | |
249 int length = values.length; | |
250 for (int i = 0; i < length; i++) { | |
251 if (identical(values[i], value)) return i; | |
252 } | |
253 values.add(value); | |
254 copies.add(null); | |
255 return length; | |
256 } | |
257 readSlot(int i) => copies[i]; | |
258 writeSlot(int i, x) { copies[i] = x; } | |
259 | |
260 walk(e) { | |
261 if (e == null) return e; | |
262 if (e is bool) return e; | |
263 if (e is num) return e; | |
264 if (e is String) return e; | |
265 | |
266 if (isJavaScriptDate(e)) { | |
267 // TODO(sra). | |
268 throw new UnimplementedError('structured clone of Date'); | |
269 } | |
270 | |
271 if (isJavaScriptRegExp(e)) { | |
272 // TODO(sra). | |
273 throw new UnimplementedError('structured clone of RegExp'); | |
274 } | |
275 | |
276 if (isJavaScriptSimpleObject(e)) { | |
277 // TODO(sra): If mustCopy is false, swizzle the prototype for one of a Map | |
278 // implementation that uses the properies as storage. | |
279 var slot = findSlot(e); | |
280 var copy = readSlot(slot); | |
281 if (copy != null) return copy; | |
282 copy = {}; | |
283 | |
284 writeSlot(slot, copy); | |
285 for (final key in JS('=List', 'Object.keys(#)', e)) { | |
286 copy[key] = walk(JS('var', '#[#]', e, key)); | |
287 } | |
288 return copy; | |
289 } | |
290 | |
291 if (isJavaScriptArray(e)) { | |
292 var slot = findSlot(e); | |
293 var copy = readSlot(slot); | |
294 if (copy != null) return copy; | |
295 | |
296 int length = e.length; | |
297 // Since a JavaScript Array is an instance of Dart List, we can modify it | |
298 // in-place unless we must copy. | |
299 copy = mustCopy ? JS('=List', 'new Array(#)', length) : e; | |
300 writeSlot(slot, copy); | |
301 | |
302 for (int i = 0; i < length; i++) { | |
303 copy[i] = walk(e[i]); | |
304 } | |
305 return copy; | |
306 } | |
307 | |
308 // Assume anything else is already a valid Dart object, either by having | |
309 // already been processed, or e.g. a clonable native class. | |
310 return e; | |
311 } | |
312 | |
313 var copy = walk(object); | |
314 return copy; | |
315 } | |
316 | |
317 | |
318 bool isJavaScriptDate(value) => JS('bool', '# instanceof Date', value); | |
319 bool isJavaScriptRegExp(value) => JS('bool', '# instanceof RegExp', value); | |
320 bool isJavaScriptArray(value) => JS('bool', '# instanceof Array', value); | |
321 bool isJavaScriptSimpleObject(value) => | |
322 JS('bool', 'Object.getPrototypeOf(#) === Object.prototype', value); | |
323 bool isImmutableJavaScriptArray(value) => | |
324 JS('bool', r'!!(#.immutable$list)', value); | |
325 | |
326 | |
327 | |
328 const String _serializedScriptValue = | |
329 'num|String|bool|' | |
330 '=List|=Object|' | |
331 'Blob|File|ArrayBuffer|ArrayBufferView' | |
332 // TODO(sra): Add Date, RegExp. | |
333 ; | |
334 const annotation_Creates_SerializedScriptValue = | |
335 const Creates(_serializedScriptValue); | |
336 const annotation_Returns_SerializedScriptValue = | |
337 const Returns(_serializedScriptValue); | |
OLD | NEW |