-
Notifications
You must be signed in to change notification settings - Fork 1.5k
/
js_util.dart
155 lines (131 loc) · 5.27 KB
/
js_util.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// @dart = 2.6
/// Utility methods to efficiently manipulate typed JSInterop objects in cases
/// where the name to call is not known at runtime. You should only use these
/// methods when the same effect cannot be achieved with @JS annotations.
/// These methods would be extension methods on JSObject if Dart supported
/// extension methods.
///
/// {@category Web}
library dart.js_util;
import 'dart:_foreign_helper' show JS;
import 'dart:collection' show HashMap;
import 'dart:async' show Completer;
import 'dart:_js_helper' show convertDartClosureToJS;
/// WARNING: performance of this method is much worse than other util
/// methods in this library. Only use this method as a last resort.
///
/// Recursively converts a JSON-like collection of Dart objects to a
/// collection of JavaScript objects and returns a [JsObject] proxy to it.
///
/// [object] must be a [Map] or [Iterable], the contents of which are also
/// converted. Maps and Iterables are copied to a new JavaScript object.
/// Primitives and other transferable values are directly converted to their
/// JavaScript type, and all other objects are proxied.
jsify(object) {
if ((object is! Map) && (object is! Iterable)) {
throw ArgumentError("object must be a Map or Iterable");
}
return _convertDataTree(object);
}
_convertDataTree(data) {
var _convertedObjects = HashMap.identity();
_convert(o) {
if (_convertedObjects.containsKey(o)) {
return _convertedObjects[o];
}
if (o is Map) {
final convertedMap = JS('=Object', '{}');
_convertedObjects[o] = convertedMap;
for (var key in o.keys) {
JS('=Object', '#[#]=#', convertedMap, key, _convert(o[key]));
}
return convertedMap;
} else if (o is Iterable) {
var convertedList = [];
_convertedObjects[o] = convertedList;
convertedList.addAll(o.map(_convert));
return convertedList;
} else {
return o;
}
}
return _convert(data);
}
newObject() => JS('=Object', '{}');
bool hasProperty(o, name) => JS('bool', '# in #', name, o);
getProperty(o, name) => JS('Object|Null', '#[#]', o, name);
setProperty(o, name, value) => JS('', '#[#]=#', o, name, value);
callMethod(o, String method, List args) =>
JS('Object|Null', '#[#].apply(#, #)', o, method, o, args);
bool instanceof(o, Function type) => JS('bool', '# instanceof #', o, type);
callConstructor(Function constr, List arguments) {
if (arguments == null) {
return JS('Object', 'new #()', constr);
}
if (JS('bool', '# instanceof Array', arguments)) {
int argumentCount = JS('int', '#.length', arguments);
switch (argumentCount) {
case 0:
return JS('Object', 'new #()', constr);
case 1:
var arg0 = JS('', '#[0]', arguments);
return JS('Object', 'new #(#)', constr, arg0);
case 2:
var arg0 = JS('', '#[0]', arguments);
var arg1 = JS('', '#[1]', arguments);
return JS('Object', 'new #(#, #)', constr, arg0, arg1);
case 3:
var arg0 = JS('', '#[0]', arguments);
var arg1 = JS('', '#[1]', arguments);
var arg2 = JS('', '#[2]', arguments);
return JS('Object', 'new #(#, #, #)', constr, arg0, arg1, arg2);
case 4:
var arg0 = JS('', '#[0]', arguments);
var arg1 = JS('', '#[1]', arguments);
var arg2 = JS('', '#[2]', arguments);
var arg3 = JS('', '#[3]', arguments);
return JS(
'Object', 'new #(#, #, #, #)', constr, arg0, arg1, arg2, arg3);
}
}
// The following code solves the problem of invoking a JavaScript
// constructor with an unknown number arguments.
// First bind the constructor to the argument list using bind.apply().
// The first argument to bind() is the binding of 't', so add 'null' to
// the arguments list passed to apply().
// After that, use the JavaScript 'new' operator which overrides any binding
// of 'this' with the new instance.
var args = <dynamic>[null]..addAll(arguments);
var factoryFunction = JS('', '#.bind.apply(#, #)', constr, constr, args);
// Without this line, calling factoryFunction as a constructor throws
JS('String', 'String(#)', factoryFunction);
// This could return an UnknownJavaScriptObject, or a native
// object for which there is an interceptor
return JS('Object', 'new #()', factoryFunction);
// TODO(sra): Investigate:
//
// var jsObj = JS('', 'Object.create(#.prototype)', constr);
// JS('', '#.apply(#, #)', constr, jsObj,
// []..addAll(arguments.map(_convertToJS)));
// return _wrapToDart(jsObj);
}
/// Converts a JavaScript Promise to a Dart [Future].
///
/// ```dart
/// @JS()
/// external Promise<num> get threePromise; // Resolves to 3
///
/// final Future<num> threeFuture = promiseToFuture(threePromise);
///
/// final three = await threeFuture; // == 3
/// ```
Future<T> promiseToFuture<T>(jsPromise) {
final completer = Completer<T>();
final success = convertDartClosureToJS((r) => completer.complete(r), 1);
final error = convertDartClosureToJS((e) => completer.completeError(e), 1);
JS('', '#.then(#, #)', jsPromise, success, error);
return completer.future;
}