Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More support for wasm #178

Open
wants to merge 26 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
68e6af1
fix: Using return values (wasm support)
koji-1009 Nov 30, 2024
e7d9517
test: Update getRandomValue test and support wasm error
koji-1009 Dec 1, 2024
2df83f9
feat: Use js_interop instead of html (wasm support)
koji-1009 Dec 1, 2024
3af9272
feat: Add wasm ci test and set timeout-minutes
koji-1009 Dec 1, 2024
e09f999
Merge branch 'master' into fix/support_wasm
koji-1009 Dec 3, 2024
88f1cc4
test: Stop separating jobs by strategy
koji-1009 Dec 3, 2024
9539b5f
fix: getRandomValues
koji-1009 Dec 3, 2024
bac32e9
feat: Unify the behavior of dart2js and dart2wasm
koji-1009 Dec 3, 2024
1a620f4
fix: Simplify
koji-1009 Dec 3, 2024
45ff7e3
feat: Create UnknownError class
koji-1009 Dec 3, 2024
b44ce75
test: fix
koji-1009 Dec 3, 2024
4e8e9de
refactor: Simplify UnknownError
koji-1009 Dec 4, 2024
7856777
refactor: Added processing branching by kIsWasm
koji-1009 Dec 4, 2024
9d36a73
fix: Show only kIsWasm
koji-1009 Dec 5, 2024
2d3497d
remove: Remove kIsWasm
koji-1009 Dec 5, 2024
e4e0e06
Update lib/src/crypto_subtle.dart
koji-1009 Dec 6, 2024
d27cadf
Apply suggestions from code review
koji-1009 Dec 6, 2024
aeeb3cc
fix: Use jsArray and dartArray
koji-1009 Dec 6, 2024
77991b6
fix: minor fix
koji-1009 Dec 6, 2024
b9f7954
test: Remove skip option
koji-1009 Dec 10, 2024
4efab3b
test: Add tests for various lists
koji-1009 Dec 10, 2024
69aa772
fix: Remove kIsWasm
koji-1009 Dec 10, 2024
bd9425a
fix: throw ArgumentError when type is not supported
koji-1009 Dec 10, 2024
d293552
chore: Check wasm coverage
koji-1009 Dec 10, 2024
4b13a9a
chore: Remove unused step
koji-1009 Dec 10, 2024
33aa079
chore: Remove wasm option from windows
koji-1009 Dec 10, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ jobs:
- run: flutter pub run webcrypto:setup
- run: flutter test
- run: flutter test --platform chrome
- run: flutter test --platform chrome --wasm
- run: xvfb-run flutter test integration_test/webcrypto_test.dart -d linux
working-directory: ./example
- uses: nanasess/setup-chromedriver@v2
Expand Down Expand Up @@ -69,6 +70,7 @@ jobs:
- run: flutter pub run webcrypto:setup
- run: flutter test
- run: flutter test --platform chrome
- run: flutter test --platform chrome --wasm
- run: flutter test integration_test/webcrypto_test.dart -d macos
working-directory: ./example
# TODO: Enable chromedriver testing on MacOS when it works reliably
Expand Down Expand Up @@ -105,6 +107,7 @@ jobs:
- run: flutter pub run webcrypto:setup
- run: flutter test
- run: flutter test --platform chrome
- run: flutter test --platform chrome --wasm
- run: flutter test integration_test/webcrypto_test.dart -d macos
working-directory: ./example
# TODO: Enable chromedriver testing on MacOS when it works reliably
Expand Down Expand Up @@ -134,6 +137,7 @@ jobs:
- run: flutter pub run webcrypto:setup
- run: flutter test
#- run: flutter test --platform chrome
#- run: flutter test --platform chrome --wasm
- run: flutter test integration_test/webcrypto_test.dart -d windows
working-directory: ./example
- uses: nanasess/setup-chromedriver@v2
Expand Down
52 changes: 35 additions & 17 deletions lib/src/crypto_subtle.dart
Original file line number Diff line number Diff line change
Expand Up @@ -333,31 +333,49 @@ extension type JSRsaOtherPrimesInfo(JSObject _) implements JSObject {
}

TypedData getRandomValues(TypedData array) {
// Since dart2wasm does not reflect values in the array, use setAll to reflect them.
// see: https://github.com/dart-lang/sdk/issues/59651
koji-1009 marked this conversation as resolved.
Show resolved Hide resolved
if (array is Uint8List) {
window.crypto.getRandomValues(array.toJS);
return array;
final values = array.toJS;
window.crypto.getRandomValues(values);
if (array != values.toDart) {
array.setAll(0, values.toDart);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's avoid calling .toDart twice, in wasm mode it'll create a wrapper, there is no need to create the wrapper twice.

Suggested change
final values = array.toJS;
window.crypto.getRandomValues(values);
if (array != values.toDart) {
array.setAll(0, values.toDart);
}
final jsarray = array.toJS;
window.crypto.getRandomValues(jsarray);
final dartArray = jsarray.toDart;
if (array != dartArray) {
array.setAll(0, dartArray);
}

See: https://api.dart.dev/dart-js_interop/JSUint8ArrayToUint8List/toDart.html

} else if (array is Uint16List) {
window.crypto.getRandomValues(array.toJS);
return array;
final values = array.toJS;
window.crypto.getRandomValues(values);
if (array != values.toDart) {
array.setAll(0, values.toDart);
}
} else if (array is Uint32List) {
window.crypto.getRandomValues(array.toJS);
return array;
final values = array.toJS;
window.crypto.getRandomValues(values);
if (array != values.toDart) {
array.setAll(0, values.toDart);
}
} else if (array is Int8List) {
window.crypto.getRandomValues(array.toJS);
return array;
final values = array.toJS;
window.crypto.getRandomValues(values);
if (array != values.toDart) {
array.setAll(0, values.toDart);
}
} else if (array is Int16List) {
window.crypto.getRandomValues(array.toJS);
return array;
final values = array.toJS;
window.crypto.getRandomValues(values);
if (array != values.toDart) {
array.setAll(0, values.toDart);
}
} else if (array is Int32List) {
window.crypto.getRandomValues(array.toJS);
return array;
final values = array.toJS;
window.crypto.getRandomValues(values);
if (array != values.toDart) {
array.setAll(0, values.toDart);
}
} else {
throw ArgumentError.value(
array,
'array',
'Unsupported TypedData type',
);
throw UnsupportedError('Unsupported TypedData type ${array.runtimeType}');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we changing behavior here?

Is this deliberate? If so should it not be orthogonal to this PR, and happen in a separate PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed that when generating unsupported lists (e.g. Uint64List) in dart2js, an UnsupportedError is thrown. For example, the following test succeeds.

test('Uint64List: not supported type', () {
  expect(
    () => Uint64List(32),
    throwsA(
      isA<UnsupportedError>(),
    ),
  );
});

The test fails in dart2wasm.


There is no JS class corresponding to Uint64List. Therefore, when fillRandomBytes(Uint64List(32)) is called in dart2wasm, the process reaches this code.

I changed it to UnsupportedError to keep the process consistent between dart2js and dart2wasm. It is possible to split a PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added test case. 4efab3b

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And restore previous code. The addition of the test makes it easier to understand the scope of the impact. bd9425a

}

return array;
}

Future<ByteBuffer> decrypt(
Expand Down
7 changes: 7 additions & 0 deletions lib/src/impl_js/impl_js.random.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,12 @@ void fillRandomBytes(TypedData destination) {
subtle.getRandomValues(destination);
} on subtle.JSDomException catch (e) {
throw _translateDomException(e);
} on Error catch (e) {
final errorName = e.toString();
if (errorName != 'JavaScriptError') {
rethrow;
}

throw _translateJavaScriptException();
}
}
19 changes: 19 additions & 0 deletions lib/src/impl_js/impl_js.utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,25 @@ Object _translateDomException(
'"${e.name}", message: $message');
}

/// Convert [Error] to [UnknownError].
/// dart2wasm throws _JavaScriptError, but _JavaScriptError is not exposed.
///
/// [1]: https://github.com/dart-lang/sdk/issues/55496
/// [2]: https://api.dart.dev/stable/latest/dart-core/Error-class.html
Object _translateJavaScriptException() {
return UnknownError();
}

/// Error class for handling JavaScriptError that occurred in package:webcrypto.
class UnknownError extends Error {
koji-1009 marked this conversation as resolved.
Show resolved Hide resolved
UnknownError();
koji-1009 marked this conversation as resolved.
Show resolved Hide resolved

@override
String toString() => 'UnknownError: browser threw JavaScriptError. '
'Note: This version of package:webcrypto cannot distinguish between Error types from the browser. '
'Please check the issue: https://github.com/google/webcrypto.dart/issues/182';
koji-1009 marked this conversation as resolved.
Show resolved Hide resolved
}

/// Handle instances of [subtle.JSDomException] specified in the
/// [Web Cryptograpy specification][1].
///
Expand Down
75 changes: 54 additions & 21 deletions test/crypto_subtle_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,12 @@ void main() {
expect(
() => fillRandomBytes(Uint8List(1000000)),
throwsA(
isA<ArgumentError>(),
// dart2js throws ArgumentError
// dart2wasm throws UnknownError
anyOf(
isA<ArgumentError>(),
isA<UnknownError>(),
),
),
);
});
Expand All @@ -68,32 +73,60 @@ void main() {
data.any((e) => e != 0),
isTrue,
);
});
}, skip: 'dart2wasm');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious, why do we need to skip this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is because with dart2wasm, the value is not reflected in the UInt8List. All data values are 0.


test('getRandomValues: too long', () {
test('getRandomValues: success', () {
final data = Uint8List(16 * 1024);
expect(
() => subtle.window.crypto.getRandomValues(Uint8List(1000000).toJS),
throwsA(
isA<subtle.JSDomException>().having(
(e) => e.name,
'name',
'QuotaExceededError',
),
),
data.every((e) => e == 0),
isTrue,
);
final values = data.toJS;
subtle.window.crypto.getRandomValues(values);
expect(
data.every((e) => e == 0),
isTrue,
);
expect(
values.toDart.any((e) => e != 0),
isTrue,
);
}, skip: 'dart2js');

test('getRandomValues: too long', () {
try {
subtle.window.crypto.getRandomValues(Uint8List(1000000).toJS);
} on subtle.JSDomException catch (e) {
// dart2js throws QuotaExceededError
expect(
e.name,
'QuotaExceededError',
);
} on Error catch (e) {
// dart2wasm throws JavaScriptError
expect(
e.toString(),
'JavaScriptError',
);
}
});

test('getRandomValues: not supported type', () {
expect(
() => subtle.window.crypto.getRandomValues(Float32List(32).toJS),
throwsA(
isA<subtle.JSDomException>().having(
(e) => e.name,
'name',
'TypeMismatchError',
),
),
);
try {
subtle.window.crypto.getRandomValues(Float32List(32).toJS);
} on subtle.JSDomException catch (e) {
// dart2js throws TypeMismatchError
expect(
e.name,
'TypeMismatchError',
);
} on Error catch (e) {
// dart2wasm throws JavaScriptError
expect(
e.toString(),
'JavaScriptError',
);
}
});
});

Expand Down
Loading