[js_util] Add dartify and some helpers.

Change-Id: I40822d87a7fef9e4de563ccb73046eee78f34b21
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/243846
Reviewed-by: Srujan Gaddam <srujzs@google.com>
Reviewed-by: Riley Porter <rileyporter@google.com>
Commit-Queue: Joshua Litt <joshualitt@google.com>
This commit is contained in:
Joshua Litt 2022-05-16 21:31:47 +00:00 committed by Commit Bot
parent 2b8429252e
commit c6d5cd043b
6 changed files with 255 additions and 0 deletions

View file

@ -7,6 +7,10 @@
- Add `connectionState` attribute and `connectionstatechange` listener to
`RtcPeerConnection`.
#### `dart:js_util`
- Added `dartify` and a number of minor helper functions.
### Tools
#### Linter

View file

@ -417,3 +417,76 @@ Future<T> promiseToFuture<T>(Object jsPromise) {
JS('', '#.then(#, #)', jsPromise, success, error);
return completer.future;
}
Object? _getConstructor(String constructorName) =>
getProperty(globalThis, constructorName);
/// Like [instanceof] only takes a [String] for the object name instead of a
/// constructor object.
bool instanceOfString(Object? element, String objectType) {
Object? constructor = _getConstructor(objectType);
return constructor != null && instanceof(element, constructor);
}
/// Returns the prototype of a given object. Equivalent to
/// `Object.getPrototypeOf`.
Object? objectGetPrototypeOf(Object? object) =>
JS('', 'Object.getPrototypeOf(#)', object);
/// Returns the `Object` prototype. Equivalent to `Object.prototype`.
Object? get objectPrototype => JS('', 'Object.prototype');
/// Returns the keys for a given object. Equivalent to `Object.keys(object)`.
List<Object?> objectKeys(Object? object) => JS('', 'Object.keys(#)', object);
/// Returns `true` if a given object is a JavaScript array.
bool isJavaScriptArray(value) => instanceOfString(value, 'Array');
/// Returns `true` if a given object is a simple JavaScript object.
bool isJavaScriptSimpleObject(value) {
final Object? proto = objectGetPrototypeOf(value);
return proto == null || proto == objectPrototype;
}
/// Effectively the inverse of [jsify], [dartify] Takes a JavaScript object, and
/// converts it to a Dart based object. Only JS primitives, arrays, or 'map'
/// like JS objects are supported.
Object? dartify(Object? o) {
var _convertedObjects = HashMap.identity();
Object? convert() {
if (_convertedObjects.containsKey(o)) {
return _convertedObjects[o];
}
if (o == null || o is bool || o is num || o is String) return o;
if (isJavaScriptSimpleObject(o)) {
Map<Object?, Object?> dartObject = {};
_convertedObjects[o] = dartObject;
List<Object?> originalKeys = objectKeys(o);
List<Object?> dartKeys = [];
for (Object? key in originalKeys) {
dartKeys.add(dartify(key));
}
for (int i = 0; i < originalKeys.length; i++) {
Object? jsKey = originalKeys[i];
Object? dartKey = dartKeys[i];
if (jsKey != null) {
dartObject[dartKey] = dartify(getProperty(o, jsKey));
}
}
return dartObject;
}
if (isJavaScriptArray(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)));
}
return dartObject;
}
throw ArgumentError(
"JavaScriptObject $o must be a primitive, simple object, or array");
}
return convert();
}

View file

@ -0,0 +1,88 @@
// Copyright (c) 2022, 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.
// Tests the dartify functionality of the js_util library.
@JS()
library js_util_jsify_test;
import 'package:js/js.dart';
import 'package:js/js_util.dart' as js_util;
import 'package:expect/expect.dart';
import 'package:expect/minitest.dart';
@JS()
external void eval(String code);
main() {
eval(r"""
globalThis.arrayData = [1, 2, false, 4, 'hello', 6, [1, 2], {'foo': 'bar'}];
globalThis.recArrayData = [];
globalThis.recArrayData = [globalThis.recArrayData];
globalThis.objectData = {
'a': 1,
'b': [1, 2, 3],
'c': {
'a': true,
'b': 'foo',
},
};
globalThis.recObjectData = {};
globalThis.recObjectData = {'foo': globalThis.recObjectData}
globalThis.throwData = function() {};
""");
test('convert an array', () {
Object? jsArray = js_util.getProperty(js_util.globalThis, 'arrayData');
Object? dartArray = js_util.dartify(jsArray);
List<Object?> expectedValues = [
1,
2,
false,
4,
'hello',
6,
[1, 2],
{'foo': 'bar'}
];
Expect.deepEquals(expectedValues, dartArray);
});
test('convert a recursive array', () {
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 = {
'a': 1,
'b': [1, 2, 3],
'c': {
'a': true,
'b': 'foo',
},
};
Expect.deepEquals(expectedValues, dartObject);
});
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 = {
'foo': {},
};
Expect.deepEquals(expectedValues, dartObject);
});
test('throws if object is not an object literal or array', () {
expect(
() => js_util
.dartify(js_util.getProperty(js_util.globalThis, 'throwData')),
throwsArgumentError);
});
}

View file

@ -88,6 +88,7 @@ html/js_typed_interop_type_test: SkipByDesign
html/js_typed_interop_window_property_test: SkipByDesign
html/js_util_test: SkipByDesign
html/postmessage_structured_test: SkipByDesign
js/js_util/dartify_test: SkipByDesign
[ $compiler == dart2js && ($runtime == chrome || $runtime == ff) ]
async/slow_consumer2_test: SkipSlow # Times out. Issue 22050

View file

@ -0,0 +1,88 @@
// Copyright (c) 2022, 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.
// Tests the dartify functionality of the js_util library.
@JS()
library js_util_jsify_test;
import 'package:js/js.dart';
import 'package:js/js_util.dart' as js_util;
import 'package:expect/expect.dart';
import 'package:expect/minitest.dart';
@JS()
external void eval(String code);
main() {
eval(r"""
globalThis.arrayData = [1, 2, false, 4, 'hello', 6, [1, 2], {'foo': 'bar'}];
globalThis.recArrayData = [];
globalThis.recArrayData = [globalThis.recArrayData];
globalThis.objectData = {
'a': 1,
'b': [1, 2, 3],
'c': {
'a': true,
'b': 'foo',
},
};
globalThis.recObjectData = {};
globalThis.recObjectData = {'foo': globalThis.recObjectData}
globalThis.throwData = function() {};
""");
test('convert an array', () {
Object? jsArray = js_util.getProperty(js_util.globalThis, 'arrayData');
Object? dartArray = js_util.dartify(jsArray);
List<Object?> expectedValues = [
1,
2,
false,
4,
'hello',
6,
[1, 2],
{'foo': 'bar'}
];
Expect.deepEquals(expectedValues, dartArray);
});
test('convert a recursive array', () {
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 = {
'a': 1,
'b': [1, 2, 3],
'c': {
'a': true,
'b': 'foo',
},
};
Expect.deepEquals(expectedValues, dartObject);
});
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 = {
'foo': {},
};
Expect.deepEquals(expectedValues, dartObject);
});
test('throws if object is not an object literal or array', () {
expect(
() => js_util
.dartify(js_util.getProperty(js_util.globalThis, 'throwData')),
throwsArgumentError);
});
}

View file

@ -86,6 +86,7 @@ html/js_typed_interop_type_test: SkipByDesign
html/js_typed_interop_window_property_test: SkipByDesign
html/js_util_test: SkipByDesign
html/postmessage_structured_test: SkipByDesign
js/js_util/dartify_test: SkipByDesign
[ $compiler == dart2js && ($runtime == chrome || $runtime == ff) ]
async/slow_consumer2_test: SkipSlow # Times out. Issue 22050