[js_util] Generalize dartify, and make it more robust.
Change-Id: I3608bcbdf320f6d4da2a52119d48fb02df2ad0e5
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/252565
Reviewed-by: Srujan Gaddam <srujzs@google.com>
Commit-Queue: Joshua Litt <joshualitt@google.com>
diff --git a/sdk/lib/_internal/js_shared/lib/js_util_patch.dart b/sdk/lib/_internal/js_shared/lib/js_util_patch.dart
index f67886e..139f411 100644
--- a/sdk/lib/_internal/js_shared/lib/js_util_patch.dart
+++ b/sdk/lib/_internal/js_shared/lib/js_util_patch.dart
@@ -374,14 +374,55 @@
@patch
List<Object?> objectKeys(Object? object) => JS('', 'Object.keys(#)', object);
+// TODO(joshualitt): Move these `is` checks to a helper library to help
+// declutter this patch file.
+bool _isJavaScriptDate(value) => JS('bool', '# instanceof Date', value);
+bool _isJavaScriptRegExp(value) => JS('bool', '# instanceof RegExp', value);
+
+@patch
+bool isJavaScriptArray(value) => JS('bool', '# instanceof Array', value);
+
+// Although it may be tempting to try and rewrite [isJavaScriptSimpleObject]
+// using `js_util` calls, it turns out this can be fragile on some browsers
+// under some situations.
+@patch
+bool isJavaScriptSimpleObject(value) {
+ var proto = JS('', 'Object.getPrototypeOf(#)', value);
+ return JS('bool', '# === Object.prototype', proto) ||
+ JS('bool', '# === null', proto);
+}
+
+bool _isJavaScriptPromise(value) =>
+ JS('bool', r'typeof Promise != "undefined" && # instanceof Promise', value);
+
+DateTime _dateToDateTime(date) {
+ int millisSinceEpoch = JS('int', '#.getTime()', date);
+ return new DateTime.fromMillisecondsSinceEpoch(millisSinceEpoch, isUtc: true);
+}
+
@patch
Object? dartify(Object? o) {
var _convertedObjects = HashMap.identity();
- Object? convert() {
+ Object? convert(Object? o) {
if (_convertedObjects.containsKey(o)) {
return _convertedObjects[o];
}
if (o == null || o is bool || o is num || o is String) return o;
+
+ if (_isJavaScriptDate(o)) {
+ return _dateToDateTime(o);
+ }
+
+ if (_isJavaScriptRegExp(o)) {
+ // TODO(joshualitt): Consider investigating if there is a way to convert
+ // from `JSRegExp` to `RegExp`.
+ throw new ArgumentError('structured clone of RegExp');
+ }
+
+ if (_isJavaScriptPromise(o)) {
+ return promiseToFuture(o);
+ }
+
if (isJavaScriptSimpleObject(o)) {
Map<Object?, Object?> dartObject = {};
_convertedObjects[o] = dartObject;
@@ -394,23 +435,27 @@
Object? jsKey = originalKeys[i];
Object? dartKey = dartKeys[i];
if (jsKey != null) {
- dartObject[dartKey] = dartify(getProperty(o, jsKey));
+ dartObject[dartKey] = convert(getProperty(o, jsKey));
}
}
return dartObject;
}
+
if (isJavaScriptArray(o)) {
+ var l = JS<List>('returns:List;creates:;', '#', o);
List<Object?> dartObject = [];
_convertedObjects[o] = dartObject;
int length = getProperty(o, 'length');
for (int i = 0; i < length; i++) {
- dartObject.add(dartify(getProperty(o, i)));
+ dartObject.add(convert(l[i]));
}
return dartObject;
}
- throw ArgumentError(
- "JavaScriptObject $o must be a primitive, simple object, or array");
+
+ // Assume anything else is already a valid Dart object, either by having
+ // already been processed, or e.g. a cloneable native class.
+ return o;
}
- return convert();
+ return convert(o);
}
diff --git a/sdk/lib/js_util/js_util.dart b/sdk/lib/js_util/js_util.dart
index 301f486..e47e6eb 100644
--- a/sdk/lib/js_util/js_util.dart
+++ b/sdk/lib/js_util/js_util.dart
@@ -150,13 +150,10 @@
external List<Object?> objectKeys(Object? object);
/// Returns `true` if a given object is a JavaScript array.
-bool isJavaScriptArray(value) => instanceOfString(value, 'Array');
+external bool isJavaScriptArray(value);
/// Returns `true` if a given object is a simple JavaScript object.
-bool isJavaScriptSimpleObject(value) {
- final Object? proto = objectGetPrototypeOf(value);
- return proto == null || proto == objectPrototype;
-}
+external bool isJavaScriptSimpleObject(value);
/// Effectively the inverse of [jsify], [dartify] Takes a JavaScript object, and
/// converts it to a Dart based object. Only JS primitives, arrays, or 'map'
diff --git a/tests/lib/js/js_util/dartify_test.dart b/tests/lib/js/js_util/dartify_test.dart
index 9fef877..2d9d08d 100644
--- a/tests/lib/js/js_util/dartify_test.dart
+++ b/tests/lib/js/js_util/dartify_test.dart
@@ -30,7 +30,15 @@
};
globalThis.recObjectData = {};
globalThis.recObjectData = {'foo': globalThis.recObjectData}
- globalThis.throwData = function() {};
+ globalThis.throwData = new RegExp();
+ globalThis.complexData = {
+ 'a': new Date(0),
+ 'b': new Promise((resolve, reject) => {}),
+ };
+ globalThis.complexList = [
+ new Date(0),
+ new Promise((resolve, reject) => {}),
+ ];
""");
test('convert an array', () {
@@ -79,7 +87,22 @@
Expect.deepEquals(expectedValues, dartObject);
});
- test('throws if object is not an object literal or array', () {
+ test('complex types convert in an Object', () {
+ Object? jsObject = js_util.getProperty(js_util.globalThis, 'complexData');
+ Map<Object?, Object?> dartObject =
+ js_util.dartify(jsObject) as Map<Object?, Object?>;
+ Expect.isTrue(dartObject['a']! is DateTime);
+ Expect.isTrue(dartObject['b']! is Future);
+ });
+
+ test('complex types convert in a List', () {
+ Object? jsArray = js_util.getProperty(js_util.globalThis, 'complexList');
+ List<Object?> dartList = js_util.dartify(jsArray) as List<Object?>;
+ Expect.isTrue(dartList[0] is DateTime);
+ Expect.isTrue(dartList[1] is Future);
+ });
+
+ test('throws if object is a regexp', () {
expect(
() => js_util
.dartify(js_util.getProperty(js_util.globalThis, 'throwData')),
diff --git a/tests/lib_2/js/js_util/dartify_test.dart b/tests/lib_2/js/js_util/dartify_test.dart
index 9fef877..4019af8 100644
--- a/tests/lib_2/js/js_util/dartify_test.dart
+++ b/tests/lib_2/js/js_util/dartify_test.dart
@@ -2,6 +2,8 @@
// 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.9
+
// Tests the dartify functionality of the js_util library.
@JS()
@@ -30,13 +32,21 @@
};
globalThis.recObjectData = {};
globalThis.recObjectData = {'foo': globalThis.recObjectData}
- globalThis.throwData = function() {};
+ globalThis.throwData = new RegExp();
+ globalThis.complexData = {
+ 'a': new Date(0),
+ 'b': new Promise((resolve, reject) => {}),
+ };
+ globalThis.complexList = [
+ new Date(0),
+ new Promise((resolve, reject) => {}),
+ ];
""");
test('convert an array', () {
- Object? jsArray = js_util.getProperty(js_util.globalThis, 'arrayData');
- Object? dartArray = js_util.dartify(jsArray);
- List<Object?> expectedValues = [
+ Object jsArray = js_util.getProperty(js_util.globalThis, 'arrayData');
+ Object dartArray = js_util.dartify(jsArray);
+ List<Object> expectedValues = [
1,
2,
false,
@@ -50,16 +60,16 @@
});
test('convert a recursive array', () {
- Object? jsArray = js_util.getProperty(js_util.globalThis, 'recArrayData');
- Object? dartArray = js_util.dartify(jsArray);
- List<Object?> expectedValues = [[]];
+ Object jsArray = js_util.getProperty(js_util.globalThis, 'recArrayData');
+ Object dartArray = js_util.dartify(jsArray);
+ List<Object> expectedValues = [[]];
Expect.deepEquals(expectedValues, dartArray);
});
test('convert an object literal', () {
- Object? jsObject = js_util.getProperty(js_util.globalThis, 'objectData');
- Object? dartObject = js_util.dartify(jsObject);
- Map<Object?, Object?> expectedValues = {
+ Object jsObject = js_util.getProperty(js_util.globalThis, 'objectData');
+ Object dartObject = js_util.dartify(jsObject);
+ Map<Object, Object> expectedValues = {
'a': 1,
'b': [1, 2, 3],
'c': {
@@ -71,15 +81,31 @@
});
test('convert a recursive object literal', () {
- Object? jsObject = js_util.getProperty(js_util.globalThis, 'recObjectData');
- Object? dartObject = js_util.dartify(jsObject);
- Map<Object?, Object?> expectedValues = {
+ Object jsObject = js_util.getProperty(js_util.globalThis, 'recObjectData');
+ Object dartObject = js_util.dartify(jsObject);
+ Map<Object, Object> expectedValues = {
'foo': {},
};
Expect.deepEquals(expectedValues, dartObject);
});
- test('throws if object is not an object literal or array', () {
+ test('complex types convert in an Object', () {
+ Object jsObject = js_util.getProperty(js_util.globalThis, 'complexData');
+ Map<Object, Object> dartObject =
+ js_util.dartify(jsObject) as Map<Object, Object>;
+ print(dartObject);
+ Expect.isTrue(dartObject['a'] is DateTime);
+ Expect.isTrue(dartObject['b'] is Future);
+ });
+
+ test('complex types convert in a List', () {
+ Object jsArray = js_util.getProperty(js_util.globalThis, 'complexList');
+ List<Object> dartList = js_util.dartify(jsArray) as List<Object>;
+ Expect.isTrue(dartList[0] is DateTime);
+ Expect.isTrue(dartList[1] is Future);
+ });
+
+ test('throws if object is a regexp', () {
expect(
() => js_util
.dartify(js_util.getProperty(js_util.globalThis, 'throwData')),