-
Notifications
You must be signed in to change notification settings - Fork 29.8k
Remove dart:io/dart:isolate from transitive closure of web applications #38773
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
Remove dart:io/dart:isolate from transitive closure of web applications #38773
Conversation
|
It looks like this pull request may not have tests. Please make sure to add tests before merging. While there are exceptions to this rule, if this patch modifies code it is probably not an exception. Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. |
Codecov Report
@@ Coverage Diff @@
## master #38773 +/- ##
==========================================
- Coverage 56.53% 56.41% -0.12%
==========================================
Files 195 195
Lines 18422 18422
==========================================
- Hits 10414 10392 -22
- Misses 8008 8030 +22
Continue to review full report at Codecov.
|
| /// | ||
| /// This implementation is unsupported and throws when invoked. | ||
| Future<Uint8List> consolidateHttpClientResponseBytes( | ||
| Object response, { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This type can't be HttpClient since that comes from dart:io
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we make it be a Stream<List<int>>?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure what that would accomplish. You won't get a Stream<List> from an XmlHttpRequest, so if you're calling this method at all you've done something wrong
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can't.
| // can provide an explicit applicationName to the widgets defined in this | ||
| // file, instead of relying on the default. | ||
| final Title ancestorTitle = context.ancestorWidgetOfExactType(Title); | ||
| return ancestorTitle?.title ?? Platform.resolvedExecutable.split(Platform.pathSeparator).last; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This returns /bin/system-something on release mode android
| return true; | ||
| } | ||
| if (Platform.isIOS) { | ||
| if (defaultTargetPlatform == TargetPlatform.iOS) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like we have a lot of tests that rely on this being false for both :(
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think these asserts were essentially never tested. Maybe we should remove them?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The tester would resolve this to the host platform right?
We should fix the test. I believe the asserts work as intended even if they're not correctly tested. And this should be a switch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is definitely not default Target Platform fodder. Changing the UI style to Android doesn't magically make it support iOS actions.
|
|
||
| Future<bool> isIOSSimulator() async { | ||
| return Platform.isIOS && !(await deviceInfoPlugin.iosInfo).isPhysicalDevice; | ||
| return defaultTargetPlatform == TargetPlatform.iOS && !(await deviceInfoPlugin.iosInfo).isPhysicalDevice; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we switch to TargetPlatform, this should be a switch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
default Target Platform is wrong here. It does not reliably say if you're on the iOS simulator: it just says what your preferred UI style is. you can toggle it by pressing "o" at the console in flutter run, for instance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahh yes. But we could still expose something like TargetPlatform currentPlatform somewhere and have that delegate to Platform when io is available
|
|
||
| /// The file to decode into an image. | ||
| final File file; | ||
| final Object file; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we come up with a foundation type for this instead of Object? Maybe for a future PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think anything else (besides dynamic) is actually a breaking change. If the type implements File, then dart:io is still a dependency of the API. If it doesn't, then it breaks callsites.
If we decide to make a breaking change, it would be easier to maintain this API if it took a path String instead.
| // This only affects the initial value: the ongoing value is updated as soon | ||
| // as any input events are received. | ||
| _lastInteractionWasTouch ??= Platform.isAndroid || Platform.isIOS || !WidgetsBinding.instance.mouseTracker.mouseIsConnected; | ||
| _lastInteractionWasTouch ??= defaultTargetPlatform == TargetPlatform.android || |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
switch
| /// parameter to false. | ||
| Future<Uint8List> consolidateHttpClientResponseBytes( | ||
| HttpClientResponse response, { | ||
| Object response, { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is ok. we lose type safety too.
| final Title ancestorTitle = context.ancestorWidgetOfExactType(Title); | ||
| return ancestorTitle?.title ?? Platform.resolvedExecutable.split(Platform.pathSeparator).last; | ||
| return ancestorTitle?.title ?? 'Flutter'; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Flutter is definitely not correct here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated to use Platform.resolved executable on mobile/desktop and "browser" on the web
| /// If [excludeFromSemantics] is true, then [semanticLabel] will be ignored. | ||
| Image.file( | ||
| File file, { | ||
| Object file, { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this seems highly undesireable
| return true; | ||
| } | ||
| if (Platform.isIOS) { | ||
| if (defaultTargetPlatform == TargetPlatform.iOS) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is definitely not default Target Platform fodder. Changing the UI style to Android doesn't magically make it support iOS actions.
…divergence_on_modules
…illiams/flutter into dart2js_ddc_divergence_on_modules
| /// The platform that the application is currently running on. | ||
| /// | ||
| /// The [currentHostPlatform] getter returns the current platform. | ||
| enum HostPlatform { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did not use target platform for this since I think I need an enum for each Platform.is*, regardless of whether it is supported.
|
I've updated this PR with some changes to make the types less bad.
|
|
|
||
| /// The dart:html implementation of [platform.currentHostPlatform]. | ||
| platform.HostPlatform get currentHostPlatform { | ||
| return platform.HostPlatform.browser; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this resolve to specific browsers? E.g. Safari vs Chrome?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we could return the user agent?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That feels more like a plugin of some sort to me.
HostPlatform.browser feels something more like HostPlatform.mobileApplication instead of HostPlatform.androidor HostPlatform.ios. I'm not really sure what the plans for browser support are, but it seems like it would fit better to have HostPlatform.chrome and HostPlatform.safari here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is some slight need for browser detection features, but that is handled entirely in the engine - similarly to how the android/ios shells handle certain platform specific features.
I don't really know that we'd ever need to know safari vs chrome, not to mention with user agent spoofing there are no guarantees that our guess is accurate.
| /// | ||
| /// This is a no-op, as web applications do not have sufficient permissions to | ||
| /// exit their own browser. | ||
| void exit() { } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this throw, or at least print an error log?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure, we don't call this today otherwise we would crash on exit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As a user I might find it confusing if I found this, called it, and had nothing happen. I'd probably go file a bug before reading the documentation.
| /// Unlike [TargetPlatform], this value is not intended to be overriden | ||
| /// for theming or physics. Instead, it is only for guarding features | ||
| /// which are highly platform specific, such as accessibility adaptions. | ||
| HostPlatform get currentHostPlatform => _platform.currentHostPlatform; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we make this a compile time constant so it works with tree shaking?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, Platform.is* is not a constant so the overall getter can't be const. I'm also pretty sure that redirecting a getter through a const won't tree shake for the case of the browser impl
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe for another PR then, but it seems like we should be able to just define these as const .fromEnvironments or something.
| return parser(await loadString(key)); | ||
| } | ||
|
|
||
| // TODO(ianh): Once the underlying network logic learns about caching, we |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't need this here.
| class NetworkAssetBundle extends asset_bundle.AssetBundle implements asset_bundle.NetworkAssetBundle { | ||
| /// Creates an network asset bundle that resolves asset keys as URLs relative | ||
| /// to the given base URL. | ||
| NetworkAssetBundle(this._baseUrl); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we throw here for a better stack trace?
Can we give them some suggestion of what they should use instead? Just a NetworkImage?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like it should just be the same as NetworkImage anyway, but it's not clear to me how it's supposed to be different. Either way, I think we should just suggest using NetworkImage which should be portable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've updated this to throw. I don't feel great making suggestions here because I don't even know when NetworkAssetBundle would be useful
| this.filterQuality = FilterQuality.low, | ||
| }) : loadingBuilder = null, | ||
| image = null { | ||
| throw UnsupportedError('Image.file is not supported on the web.'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be great if we provided a migration suggestion here, such as Use Image.memory instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
| Iterable<FocusNode> get traversalChildren { | ||
| return children.where( | ||
| (FocusNode node) => !node.skipTraversal && node.canRequestFocus, | ||
| (FocusNode node) => !node.skipTraversal && node.canRequestFocus, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: spacing in this file seems all off. Probably can be reverted?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm.. not sure why these changed. will revert
|
Updated goldens in flutter_test to conditionally not depend on package:image, which does not support the web |
| /// | ||
| /// Used by [debugNetworkImageHttpClientProvider]. | ||
| typedef HttpClientProvider = HttpClient Function(); | ||
| typedef HttpClientProvider = Object Function(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this is for debug, can we just break this and have it return an interface that implements io when it's available?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated to export API
| Iterable<FocusNode> get traversalChildren { | ||
| return children.where( | ||
| (FocusNode node) => !node.skipTraversal && node.canRequestFocus, | ||
| (FocusNode node) => !node.skipTraversal && node.canRequestFocus, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This still seems off?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
Piinks
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Golden changes LGTM. Thanks for doing this. :)
There are some doc changes in the related golden classes that need to be merged.
| if (errors.length == 1) { | ||
| print('${bold}An error was detected when looking at dart:* libraries within the Flutter package:$reset\n'); | ||
| } else { | ||
| print('${bold}Multiple errors were detected when looking at dart:* librarie within the Flutter package:$reset\n'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo nit:
| print('${bold}Multiple errors were detected when looking at dart:* librarie within the Flutter package:$reset\n'); | |
| print('${bold}Multiple errors were detected when looking at dart:* libraries within the Flutter package:$reset\n'); |
| if (dart.library.html) '../demo/video_demo_web.dart'; | ||
| import 'icons.dart'; | ||
|
|
||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: extra line
| /// | ||
| /// The future returned will forward any error emitted by `response`. | ||
| /// | ||
| /// |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: extra line
|
|
||
| /// The `dart:io` implementation of [asset_bundle.NetworkAssetBundle]. | ||
| class NetworkAssetBundle extends asset_bundle.AssetBundle implements asset_bundle.NetworkAssetBundle { | ||
| /// Creates an network asset bundle that resolves asset keys as URLs relative |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo nit:
| /// Creates an network asset bundle that resolves asset keys as URLs relative | |
| /// Creates a network asset bundle that resolves asset keys as URLs relative |
|
|
||
| import 'goldens.dart'; | ||
|
|
||
| /// The default [GoldenFileComparator] implementation for `flutter test`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are doc updates that landed in #39082 for GoldenFileComparator and LocalFileComparator.
| const String jsEntrypointSourceMapExtension = '.dart.js.map'; | ||
| const String jsEntrypointArchiveExtension = '.dart.js.tar.gz'; | ||
| const String digestsEntrypointExtension = '.digests'; | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: extra line
| '--define', 'flutter_tools:entrypoint=release=$release', | ||
| '--define', 'flutter_tools:entrypoint=profile=$profile', | ||
| '--define', 'flutter_tools:shell=flutterWebSdk=$flutterWebSdk', | ||
| '--define', 'flutter_tools:ddc_modules=release=$release' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit:
| '--define', 'flutter_tools:ddc_modules=release=$release' | |
| '--define', 'flutter_tools:ddc_modules=release=$release', |
| Size get size { | ||
| assert( | ||
| context != null, | ||
| context != null, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nits: It looks like the indentation is off for the changes in this file.
|
FYI: I realize there are already merge conflicts to resolve here, but I noticed #39589 adds |
dnfield
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM once the nits are fixed
|
I'm going to break this PR up so it is landable into cleanups of the various separate libraries, starting with flutter_test. |
Description
I'm pulling the thread on #34858. I've discovered that the hack I added to psuedo-support dart:io has lead to debug and release web builds resolving config specific imports differently. The fix is to remove dart:io usage from framework, which will allow the incremental web compiler to correctly select the right config import.
Background
Flutter for web and Flutter applications have access to different sets of dart:* libraries.
dart:ioanddart:isolateare not available* for the web anddart:htmlis not available for the vm. One tool we have to write code that works in both environments is config specific imports. These allow us to resolve import uri's based on the set of currently supported dart libraries. For example:In the above code, the analyzer will only see
a.dart. When the code is built for a platform the CFE will check each condition in order to see if the library is supported. If none are, then the default import is selected. A default import is always required.Supported usually means that there is no "supported:false" pair in the libraries.json for the particular platform. In the case of dartdevc/k the incremental web compiler, package:build uses the
DartPlatformtype instead, which lists all supported libraries.Since the incremental compiler cannot compile unsupported libraries, I added """support""" for
dart:ioanddart:isolateto the platform we use. At the time I was not aware this would alter the config specific imports. Furthermore, since dart2js, the release compiler, only uses the libraries.json this leads to disagreements between the two. Specifically:The above would return
b.dartin both dartdevc/k and dart2jsThe above would return 'b.dart' in dartdevc/k and 'c.dart' in dart2js.
Unfortunately a common pattern used by package:http is to have the first library be a "stub", while each real implementation is guarded below.
Overview
I think this can be done in a mostly non-breaking way. We have some usage of Platform.isFoo that could really be defaultTargetPlatform, and Platform.resolvedExecutable doesn't actually print useful information in release applications, so that can go.
There are a few places like FileImage where the type is unfortunately part of the public API. One solution would be to change the type to Object/dynamic, and then have the runtime implementation perform the type check. Another would be to define our own stub and substitute that using more config specific imports.
Then of course, there are breaking changes to fix it, like making FileImage take a String path instead.
Some specific changes here:
Related Issues