Skip to content

Commit c75430c

Browse files
koji-1009jonasfj
andauthored
More support for wasm (#178)
* fix: Using return values (wasm support) * test: Update getRandomValue test and support wasm error * feat: Use js_interop instead of html (wasm support) * feat: Add wasm ci test and set timeout-minutes * test: Stop separating jobs by strategy * fix: getRandomValues * feat: Unify the behavior of dart2js and dart2wasm * fix: Simplify * feat: Create UnknownError class * test: fix * refactor: Simplify UnknownError * refactor: Added processing branching by kIsWasm * fix: Show only kIsWasm * remove: Remove kIsWasm * Update lib/src/crypto_subtle.dart Co-authored-by: Jonas Finnemann Jensen <[email protected]> * Apply suggestions from code review Co-authored-by: Jonas Finnemann Jensen <[email protected]> * fix: Use jsArray and dartArray * fix: minor fix * test: Remove skip option * test: Add tests for various lists * fix: Remove kIsWasm * fix: throw ArgumentError when type is not supported * chore: Check wasm coverage * chore: Remove unused step * chore: Remove wasm option from windows --------- Co-authored-by: Jonas Finnemann Jensen <[email protected]>
1 parent f7a668c commit c75430c

File tree

5 files changed

+217
-61
lines changed

5 files changed

+217
-61
lines changed

.github/workflows/test.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ jobs:
4141
- run: flutter pub run webcrypto:setup
4242
- run: flutter test
4343
- run: flutter test --platform chrome
44+
- run: flutter test --platform chrome --wasm
4445
- run: xvfb-run flutter test integration_test/webcrypto_test.dart -d linux
4546
working-directory: ./example
4647
- uses: nanasess/setup-chromedriver@v2
@@ -69,6 +70,7 @@ jobs:
6970
- run: flutter pub run webcrypto:setup
7071
- run: flutter test
7172
- run: flutter test --platform chrome
73+
- run: flutter test --platform chrome --wasm
7274
- run: flutter test integration_test/webcrypto_test.dart -d macos
7375
working-directory: ./example
7476
# TODO: Enable chromedriver testing on MacOS when it works reliably
@@ -105,6 +107,7 @@ jobs:
105107
- run: flutter pub run webcrypto:setup
106108
- run: flutter test
107109
- run: flutter test --platform chrome
110+
- run: flutter test --platform chrome --wasm
108111
- run: flutter test integration_test/webcrypto_test.dart -d macos
109112
working-directory: ./example
110113
# TODO: Enable chromedriver testing on MacOS when it works reliably
@@ -134,6 +137,7 @@ jobs:
134137
- run: flutter pub run webcrypto:setup
135138
- run: flutter test
136139
#- run: flutter test --platform chrome
140+
#- run: flutter test --platform chrome --wasm
137141
- run: flutter test integration_test/webcrypto_test.dart -d windows
138142
working-directory: ./example
139143
- uses: nanasess/setup-chromedriver@v2
@@ -211,7 +215,7 @@ jobs:
211215
flutter config --no-analytics
212216
- run: flutter pub get
213217
- run: flutter pub run webcrypto:setup
214-
- run: xvfb-run flutter pub run test -p vm,chrome,firefox --coverage ./coverage
218+
- run: xvfb-run flutter pub run test -p vm,chrome,firefox -c dart2js,dart2wasm --coverage ./coverage
215219
# Report collected coverage
216220
- name: Convert coverage to lcov
217221
run: dart run coverage:format_coverage -i ./coverage -o ./coverage/lcov.info --lcov --report-on lib/
@@ -235,7 +239,6 @@ jobs:
235239
flutter config --no-analytics
236240
- run: flutter pub get
237241
- run: flutter pub run webcrypto:setup
238-
- run: flutter test
239242
- run: flutter pub run test -p vm,chrome,firefox --coverage ./coverage
240243
# Report collected coverage
241244
- name: Convert coverage to lcov

lib/src/crypto_subtle.dart

Lines changed: 52 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -333,31 +333,71 @@ extension type JSRsaOtherPrimesInfo(JSObject _) implements JSObject {
333333
}
334334

335335
TypedData getRandomValues(TypedData array) {
336+
// The `.toJS` on `Uint8List` (and friends) may:
337+
// * cast,
338+
// * create a wrapper, or,
339+
// * clone.
340+
// See: https://api.dart.dev/dart-js_interop/Uint8ListToJSUint8Array/toJS.html
341+
//
342+
// Thus, when we do `.toJS` and pass the resulting object to `getRandomValues`,
343+
// we don't know if `.toJS` simply cast or created a wrapper such that changes
344+
// made by `getRandomBytes` are reflected in `array`.
345+
//
346+
// For this reason, we must use `.setAll` to copy the values into `array`, if it
347+
// was not cast.
348+
//
349+
// See also: https://github.com/dart-lang/sdk/issues/59651
336350
if (array is Uint8List) {
337-
window.crypto.getRandomValues(array.toJS);
338-
return array;
351+
final jsArray = array.toJS;
352+
window.crypto.getRandomValues(jsArray);
353+
final dartArray = jsArray.toDart;
354+
if (array != dartArray) {
355+
array.setAll(0, dartArray);
356+
}
339357
} else if (array is Uint16List) {
340-
window.crypto.getRandomValues(array.toJS);
341-
return array;
358+
final jsArray = array.toJS;
359+
window.crypto.getRandomValues(jsArray);
360+
final dartArray = jsArray.toDart;
361+
if (array != dartArray) {
362+
array.setAll(0, dartArray);
363+
}
342364
} else if (array is Uint32List) {
343-
window.crypto.getRandomValues(array.toJS);
344-
return array;
365+
final jsArray = array.toJS;
366+
window.crypto.getRandomValues(jsArray);
367+
final dartArray = jsArray.toDart;
368+
if (array != dartArray) {
369+
array.setAll(0, dartArray);
370+
}
345371
} else if (array is Int8List) {
346-
window.crypto.getRandomValues(array.toJS);
347-
return array;
372+
final jsArray = array.toJS;
373+
window.crypto.getRandomValues(jsArray);
374+
final dartArray = jsArray.toDart;
375+
if (array != dartArray) {
376+
array.setAll(0, dartArray);
377+
}
348378
} else if (array is Int16List) {
349-
window.crypto.getRandomValues(array.toJS);
350-
return array;
379+
final jsArray = array.toJS;
380+
window.crypto.getRandomValues(jsArray);
381+
final dartArray = jsArray.toDart;
382+
if (array != dartArray) {
383+
array.setAll(0, dartArray);
384+
}
351385
} else if (array is Int32List) {
352-
window.crypto.getRandomValues(array.toJS);
353-
return array;
386+
final jsArray = array.toJS;
387+
window.crypto.getRandomValues(jsArray);
388+
final dartArray = jsArray.toDart;
389+
if (array != dartArray) {
390+
array.setAll(0, dartArray);
391+
}
354392
} else {
355393
throw ArgumentError.value(
356394
array,
357395
'array',
358396
'Unsupported TypedData type',
359397
);
360398
}
399+
400+
return array;
361401
}
362402

363403
Future<ByteBuffer> decrypt(

lib/src/impl_js/impl_js.random.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,12 @@ void fillRandomBytes(TypedData destination) {
1919
subtle.getRandomValues(destination);
2020
} on subtle.JSDomException catch (e) {
2121
throw _translateDomException(e);
22+
} on Error catch (e) {
23+
final errorName = e.toString();
24+
if (errorName != 'JavaScriptError') {
25+
rethrow;
26+
}
27+
28+
throw _translateJavaScriptException();
2229
}
2330
}

lib/src/impl_js/impl_js.utils.dart

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,25 @@ Object _translateDomException(
9999
'"${e.name}", message: $message');
100100
}
101101

102+
/// Convert [Error] to [UnknownError].
103+
/// dart2wasm throws _JavaScriptError, but _JavaScriptError is not exposed.
104+
///
105+
/// [1]: https://github.com/dart-lang/sdk/issues/55496
106+
/// [2]: https://api.dart.dev/stable/latest/dart-core/Error-class.html
107+
Object _translateJavaScriptException() {
108+
return UnknownError._();
109+
}
110+
111+
/// Error class for handling JavaScriptError that occurred in package:webcrypto.
112+
final class UnknownError extends Error {
113+
UnknownError._();
114+
115+
@override
116+
String toString() => 'UnknownError: Browser threw JavaScriptError. '
117+
'Note: This version of package:webcrypto cannot distinguish between error types from the browser. '
118+
'See: https://github.com/google/webcrypto.dart/issues/182';
119+
}
120+
102121
/// Handle instances of [subtle.JSDomException] specified in the
103122
/// [Web Cryptograpy specification][1].
104123
///

test/crypto_subtle_test.dart

Lines changed: 134 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -22,37 +22,100 @@ import 'package:test/test.dart';
2222
import 'package:webcrypto/src/crypto_subtle.dart' as subtle;
2323
import 'package:webcrypto/src/impl_js/impl_js.dart';
2424

25+
// Cannot import `package:flutter/foundation.dart` because it depends on `dart:ui`. Define it independently for testing.
26+
const bool kIsWasm = bool.fromEnvironment('dart.tool.dart2wasm');
27+
2528
void main() {
2629
group('fillRandomBytes', () {
27-
test('Uint8List: success', () {
28-
final data = Uint8List(16 * 1024);
29-
expect(
30-
data.every((e) => e == 0),
31-
isTrue,
32-
);
33-
fillRandomBytes(data);
34-
expect(
35-
data.any((e) => e != 0),
36-
isTrue,
37-
);
30+
test('success', () {
31+
final list = [
32+
Uint8List(16 * 1024),
33+
Uint16List(16 * 1024),
34+
Uint32List(16 * 1024),
35+
Int8List(16 * 1024),
36+
Int16List(16 * 1024),
37+
Int32List(16 * 1024),
38+
];
39+
for (final data in list) {
40+
expect(
41+
data.every((e) => e == 0),
42+
isTrue,
43+
);
44+
fillRandomBytes(data);
45+
expect(
46+
data.any((e) => e != 0),
47+
isTrue,
48+
);
49+
}
3850
});
3951

40-
test('Uint8List: too long', () {
41-
expect(
42-
() => fillRandomBytes(Uint8List(1000000)),
43-
throwsA(
44-
isA<ArgumentError>(),
45-
),
46-
);
52+
test('too long', () {
53+
final list = [
54+
Uint8List(1000000),
55+
Uint16List(1000000),
56+
Uint32List(1000000),
57+
Int8List(1000000),
58+
Int16List(1000000),
59+
Int32List(1000000),
60+
];
61+
for (final data in list) {
62+
expect(
63+
() => fillRandomBytes(data),
64+
throwsA(
65+
// dart2js throws ArgumentError
66+
// dart2wasm throws UnknownError
67+
anyOf(
68+
isA<ArgumentError>(),
69+
isA<UnknownError>(),
70+
),
71+
),
72+
);
73+
}
4774
});
4875

49-
test('Uint64List: not supported type', () {
50-
expect(
51-
() => fillRandomBytes(Uint64List(32)),
52-
throwsA(
53-
isA<UnsupportedError>(),
54-
),
55-
);
76+
test('not supported type', () {
77+
final list = [
78+
Float32List(32),
79+
Float64List(32),
80+
];
81+
for (final data in list) {
82+
expect(
83+
() => fillRandomBytes(data),
84+
throwsA(
85+
isA<ArgumentError>(),
86+
),
87+
);
88+
}
89+
});
90+
91+
test('list that is supported depending on the environment', () {
92+
if (kIsWasm) {
93+
final list = [
94+
Uint64List(32),
95+
Int64List(32),
96+
];
97+
98+
for (final data in list) {
99+
expect(
100+
() => fillRandomBytes(data),
101+
throwsA(
102+
// dart2wasm throws ArgumentError in fillRandomBytes method
103+
isA<ArgumentError>(),
104+
),
105+
);
106+
}
107+
} else {
108+
try {
109+
final _ = [
110+
Uint64List(32),
111+
Int64List(32),
112+
];
113+
fail('dart2js does not reach this line');
114+
} catch (e) {
115+
// dart2js throws UnsupportedError in list creation
116+
expect(e, isA<UnsupportedError>());
117+
}
118+
}
56119
});
57120
});
58121

@@ -63,37 +126,61 @@ void main() {
63126
data.every((e) => e == 0),
64127
isTrue,
65128
);
66-
subtle.window.crypto.getRandomValues(data.toJS);
129+
final values = data.toJS;
130+
subtle.window.crypto.getRandomValues(values);
131+
if (kIsWasm) {
132+
// In dart2wasm, the value is not reflected in Uint8List.
133+
expect(
134+
data.every((e) => e == 0),
135+
isTrue,
136+
);
137+
} else {
138+
// In dart2js, the value is reflected in Uint8List.
139+
expect(
140+
data.every((e) => e == 0),
141+
isFalse,
142+
);
143+
}
67144
expect(
68-
data.any((e) => e != 0),
145+
values.toDart.any((e) => e != 0),
69146
isTrue,
70147
);
71148
});
72149

73150
test('getRandomValues: too long', () {
74-
expect(
75-
() => subtle.window.crypto.getRandomValues(Uint8List(1000000).toJS),
76-
throwsA(
77-
isA<subtle.JSDomException>().having(
78-
(e) => e.name,
79-
'name',
80-
'QuotaExceededError',
81-
),
82-
),
83-
);
151+
try {
152+
subtle.window.crypto.getRandomValues(Uint8List(1000000).toJS);
153+
} on subtle.JSDomException catch (e) {
154+
// dart2js throws QuotaExceededError
155+
expect(
156+
e.name,
157+
'QuotaExceededError',
158+
);
159+
} on Error catch (e) {
160+
// dart2wasm throws JavaScriptError
161+
expect(
162+
e.toString(),
163+
'JavaScriptError',
164+
);
165+
}
84166
});
85167

86168
test('getRandomValues: not supported type', () {
87-
expect(
88-
() => subtle.window.crypto.getRandomValues(Float32List(32).toJS),
89-
throwsA(
90-
isA<subtle.JSDomException>().having(
91-
(e) => e.name,
92-
'name',
93-
'TypeMismatchError',
94-
),
95-
),
96-
);
169+
try {
170+
subtle.window.crypto.getRandomValues(Float32List(32).toJS);
171+
} on subtle.JSDomException catch (e) {
172+
// dart2js throws TypeMismatchError
173+
expect(
174+
e.name,
175+
'TypeMismatchError',
176+
);
177+
} on Error catch (e) {
178+
// dart2wasm throws JavaScriptError
179+
expect(
180+
e.toString(),
181+
'JavaScriptError',
182+
);
183+
}
97184
});
98185
});
99186

0 commit comments

Comments
 (0)