Skip to content

Commit ea318b9

Browse files
[url_launcher] Return false on Windows when there is no handler (flutter#5359)
Special-cases the handling of the error for "no registered handler" to return false, rather than throw, which better matches the behavior on other platforms. Also updates Pigeon to 13 while I'm modifying the Pigeon definition. Fixes flutter#138142
1 parent 73d2f3e commit ea318b9

File tree

11 files changed

+97
-37
lines changed

11 files changed

+97
-37
lines changed

packages/url_launcher/url_launcher_windows/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
## NEXT
1+
## 3.1.1
22

3+
* Updates `launchUrl` to return false instead of throwing when there is no handler.
34
* Updates minimum supported SDK version to Flutter 3.10/Dart 3.0.
45

56
## 3.1.0

packages/url_launcher/url_launcher_windows/lib/src/messages.g.dart

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright 2013 The Flutter Authors. All rights reserved.
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
4-
// Autogenerated from Pigeon (v10.1.2), do not edit directly.
4+
// Autogenerated from Pigeon (v13.0.0), do not edit directly.
55
// See also: https://pub.dev/packages/pigeon
66
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import
77

@@ -11,6 +11,17 @@ import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List;
1111
import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer;
1212
import 'package:flutter/services.dart';
1313

14+
List<Object?> wrapResponse(
15+
{Object? result, PlatformException? error, bool empty = false}) {
16+
if (empty) {
17+
return <Object?>[];
18+
}
19+
if (error == null) {
20+
return <Object?>[result];
21+
}
22+
return <Object?>[error.code, error.message, error.details];
23+
}
24+
1425
class UrlLauncherApi {
1526
/// Constructor for [UrlLauncherApi]. The [binaryMessenger] named argument is
1627
/// available for dependency injection. If it is left null, the default
@@ -23,7 +34,8 @@ class UrlLauncherApi {
2334

2435
Future<bool> canLaunchUrl(String arg_url) async {
2536
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
26-
'dev.flutter.pigeon.UrlLauncherApi.canLaunchUrl', codec,
37+
'dev.flutter.pigeon.url_launcher_windows.UrlLauncherApi.canLaunchUrl',
38+
codec,
2739
binaryMessenger: _binaryMessenger);
2840
final List<Object?>? replyList =
2941
await channel.send(<Object?>[arg_url]) as List<Object?>?;
@@ -48,9 +60,10 @@ class UrlLauncherApi {
4860
}
4961
}
5062

51-
Future<void> launchUrl(String arg_url) async {
63+
Future<bool> launchUrl(String arg_url) async {
5264
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
53-
'dev.flutter.pigeon.UrlLauncherApi.launchUrl', codec,
65+
'dev.flutter.pigeon.url_launcher_windows.UrlLauncherApi.launchUrl',
66+
codec,
5467
binaryMessenger: _binaryMessenger);
5568
final List<Object?>? replyList =
5669
await channel.send(<Object?>[arg_url]) as List<Object?>?;
@@ -65,8 +78,13 @@ class UrlLauncherApi {
6578
message: replyList[1] as String?,
6679
details: replyList[2],
6780
);
81+
} else if (replyList[0] == null) {
82+
throw PlatformException(
83+
code: 'null-error',
84+
message: 'Host platform returned null value for non-null return value.',
85+
);
6886
} else {
69-
return;
87+
return (replyList[0] as bool?)!;
7088
}
7189
}
7290
}

packages/url_launcher/url_launcher_windows/lib/url_launcher_windows.dart

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,7 @@ class UrlLauncherWindows extends UrlLauncherPlatform {
4141
required Map<String, String> headers,
4242
String? webOnlyWindowName,
4343
}) async {
44-
await _hostApi.launchUrl(url);
45-
// Failure is handled via a PlatformException from `launchUrl`.
46-
return true;
44+
return _hostApi.launchUrl(url);
4745
}
4846

4947
@override

packages/url_launcher/url_launcher_windows/pigeons/messages.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,5 @@ import 'package:pigeon/pigeon.dart';
1414
@HostApi(dartHostTestHandler: 'TestUrlLauncherApi')
1515
abstract class UrlLauncherApi {
1616
bool canLaunchUrl(String url);
17-
void launchUrl(String url);
17+
bool launchUrl(String url);
1818
}

packages/url_launcher/url_launcher_windows/pubspec.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: url_launcher_windows
22
description: Windows implementation of the url_launcher plugin.
33
repository: https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_windows
44
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22
5-
version: 3.1.0
5+
version: 3.1.1
66

77
environment:
88
sdk: ">=3.0.0 <4.0.0"
@@ -24,7 +24,7 @@ dependencies:
2424
dev_dependencies:
2525
flutter_test:
2626
sdk: flutter
27-
pigeon: ^10.1.2
27+
pigeon: ^13.0.0
2828
test: ^1.16.3
2929

3030
topics:

packages/url_launcher/url_launcher_windows/test/url_launcher_windows_test.dart

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ void main() {
4747
api.canLaunch = true;
4848

4949
expect(
50-
plugin.launch(
50+
await plugin.launch(
5151
'http://example.com/',
5252
useSafariVC: true,
5353
useWebView: false,
@@ -56,13 +56,30 @@ void main() {
5656
universalLinksOnly: false,
5757
headers: const <String, String>{},
5858
),
59-
completes);
59+
true);
6060
expect(api.argument, 'http://example.com/');
6161
});
6262

6363
test('handles failure', () async {
6464
api.canLaunch = false;
6565

66+
expect(
67+
await plugin.launch(
68+
'http://example.com/',
69+
useSafariVC: true,
70+
useWebView: false,
71+
enableJavaScript: false,
72+
enableDomStorage: false,
73+
universalLinksOnly: false,
74+
headers: const <String, String>{},
75+
),
76+
false);
77+
expect(api.argument, 'http://example.com/');
78+
});
79+
80+
test('handles errors', () async {
81+
api.throwError = true;
82+
6683
await expectLater(
6784
plugin.launch(
6885
'http://example.com/',
@@ -122,23 +139,27 @@ class _FakeUrlLauncherApi implements UrlLauncherApi {
122139
/// The argument that was passed to an API call.
123140
String? argument;
124141

125-
/// Controls the behavior of the fake implementations.
142+
/// Controls the behavior of the fake canLaunch implementations.
126143
///
127144
/// - [canLaunchUrl] returns this value.
128-
/// - [launchUrl] throws if this is false.
145+
/// - [launchUrl] returns this value if [throwError] is false.
129146
bool canLaunch = false;
130147

148+
/// Whether to throw a platform exception.
149+
bool throwError = false;
150+
131151
@override
132152
Future<bool> canLaunchUrl(String url) async {
133153
argument = url;
134154
return canLaunch;
135155
}
136156

137157
@override
138-
Future<void> launchUrl(String url) async {
158+
Future<bool> launchUrl(String url) async {
139159
argument = url;
140-
if (!canLaunch) {
160+
if (throwError) {
141161
throw PlatformException(code: 'Failed');
142162
}
163+
return canLaunch;
143164
}
144165
}

packages/url_launcher/url_launcher_windows/windows/messages.g.cpp

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright 2013 The Flutter Authors. All rights reserved.
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
4-
// Autogenerated from Pigeon (v10.1.2), do not edit directly.
4+
// Autogenerated from Pigeon (v13.0.0), do not edit directly.
55
// See also: https://pub.dev/packages/pigeon
66

77
#undef _HAS_EXCEPTIONS
@@ -36,7 +36,8 @@ void UrlLauncherApi::SetUp(flutter::BinaryMessenger* binary_messenger,
3636
UrlLauncherApi* api) {
3737
{
3838
auto channel = std::make_unique<BasicMessageChannel<>>(
39-
binary_messenger, "dev.flutter.pigeon.UrlLauncherApi.canLaunchUrl",
39+
binary_messenger,
40+
"dev.flutter.pigeon.url_launcher_windows.UrlLauncherApi.canLaunchUrl",
4041
&GetCodec());
4142
if (api != nullptr) {
4243
channel->SetMessageHandler(
@@ -68,7 +69,8 @@ void UrlLauncherApi::SetUp(flutter::BinaryMessenger* binary_messenger,
6869
}
6970
{
7071
auto channel = std::make_unique<BasicMessageChannel<>>(
71-
binary_messenger, "dev.flutter.pigeon.UrlLauncherApi.launchUrl",
72+
binary_messenger,
73+
"dev.flutter.pigeon.url_launcher_windows.UrlLauncherApi.launchUrl",
7274
&GetCodec());
7375
if (api != nullptr) {
7476
channel->SetMessageHandler(
@@ -82,13 +84,13 @@ void UrlLauncherApi::SetUp(flutter::BinaryMessenger* binary_messenger,
8284
return;
8385
}
8486
const auto& url_arg = std::get<std::string>(encodable_url_arg);
85-
std::optional<FlutterError> output = api->LaunchUrl(url_arg);
86-
if (output.has_value()) {
87-
reply(WrapError(output.value()));
87+
ErrorOr<bool> output = api->LaunchUrl(url_arg);
88+
if (output.has_error()) {
89+
reply(WrapError(output.error()));
8890
return;
8991
}
9092
EncodableList wrapped;
91-
wrapped.push_back(EncodableValue());
93+
wrapped.push_back(EncodableValue(std::move(output).TakeValue()));
9294
reply(EncodableValue(std::move(wrapped)));
9395
} catch (const std::exception& exception) {
9496
reply(WrapError(exception.what()));

packages/url_launcher/url_launcher_windows/windows/messages.g.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright 2013 The Flutter Authors. All rights reserved.
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
4-
// Autogenerated from Pigeon (v10.1.2), do not edit directly.
4+
// Autogenerated from Pigeon (v13.0.0), do not edit directly.
55
// See also: https://pub.dev/packages/pigeon
66

77
#ifndef PIGEON_MESSAGES_G_H_
@@ -66,7 +66,7 @@ class UrlLauncherApi {
6666
UrlLauncherApi& operator=(const UrlLauncherApi&) = delete;
6767
virtual ~UrlLauncherApi() {}
6868
virtual ErrorOr<bool> CanLaunchUrl(const std::string& url) = 0;
69-
virtual std::optional<FlutterError> LaunchUrl(const std::string& url) = 0;
69+
virtual ErrorOr<bool> LaunchUrl(const std::string& url) = 0;
7070

7171
// The codec used by UrlLauncherApi.
7272
static const flutter::StandardMessageCodec& GetCodec();

packages/url_launcher/url_launcher_windows/windows/test/url_launcher_windows_test.cpp

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -94,30 +94,45 @@ TEST(UrlLauncherPlugin, CanLaunchHandlesOpenFailure) {
9494
EXPECT_FALSE(result.value());
9595
}
9696

97-
TEST(UrlLauncherPlugin, LaunchSuccess) {
97+
TEST(UrlLauncherPlugin, LaunchReportsSuccess) {
9898
std::unique_ptr<MockSystemApis> system = std::make_unique<MockSystemApis>();
9999

100100
// Return a success value (>32) from launching.
101101
EXPECT_CALL(*system, ShellExecuteW)
102102
.WillOnce(Return(reinterpret_cast<HINSTANCE>(33)));
103103

104104
UrlLauncherPlugin plugin(std::move(system));
105-
std::optional<FlutterError> error = plugin.LaunchUrl("https://some.url.com");
105+
ErrorOr<bool> result = plugin.LaunchUrl("https://some.url.com");
106106

107-
EXPECT_FALSE(error.has_value());
107+
ASSERT_FALSE(result.has_error());
108+
EXPECT_TRUE(result.value());
108109
}
109110

110111
TEST(UrlLauncherPlugin, LaunchReportsFailure) {
111112
std::unique_ptr<MockSystemApis> system = std::make_unique<MockSystemApis>();
112113

113-
// Return a faile value (<=32) from launching.
114+
// Return error 31 from launching, indicating no handler.
115+
EXPECT_CALL(*system, ShellExecuteW)
116+
.WillOnce(Return(reinterpret_cast<HINSTANCE>(SE_ERR_NOASSOC)));
117+
118+
UrlLauncherPlugin plugin(std::move(system));
119+
ErrorOr<bool> result = plugin.LaunchUrl("https://some.url.com");
120+
121+
ASSERT_FALSE(result.has_error());
122+
EXPECT_FALSE(result.value());
123+
}
124+
125+
TEST(UrlLauncherPlugin, LaunchReportsError) {
126+
std::unique_ptr<MockSystemApis> system = std::make_unique<MockSystemApis>();
127+
128+
// Return a failure value (<=32) from launching.
114129
EXPECT_CALL(*system, ShellExecuteW)
115130
.WillOnce(Return(reinterpret_cast<HINSTANCE>(32)));
116131

117132
UrlLauncherPlugin plugin(std::move(system));
118-
std::optional<FlutterError> error = plugin.LaunchUrl("https://some.url.com");
133+
ErrorOr<bool> result = plugin.LaunchUrl("https://some.url.com");
119134

120-
EXPECT_TRUE(error.has_value());
135+
EXPECT_TRUE(result.has_error());
121136
}
122137

123138
} // namespace test

packages/url_launcher/url_launcher_windows/windows/url_launcher_plugin.cpp

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,7 @@ ErrorOr<bool> UrlLauncherPlugin::CanLaunchUrl(const std::string& url) {
9797
return has_handler;
9898
}
9999

100-
std::optional<FlutterError> UrlLauncherPlugin::LaunchUrl(
101-
const std::string& url) {
100+
ErrorOr<bool> UrlLauncherPlugin::LaunchUrl(const std::string& url) {
102101
std::wstring url_wide = Utf16FromUtf8(url);
103102

104103
int status = static_cast<int>(reinterpret_cast<INT_PTR>(
@@ -107,12 +106,18 @@ std::optional<FlutterError> UrlLauncherPlugin::LaunchUrl(
107106

108107
// Per ::ShellExecuteW documentation, anything >32 indicates success.
109108
if (status <= 32) {
109+
if (status == SE_ERR_NOASSOC) {
110+
// NOASSOC just means there's nothing registered to handle launching;
111+
// return false rather than an error for better consistency with other
112+
// platforms.
113+
return false;
114+
}
110115
std::ostringstream error_message;
111116
error_message << "Failed to open " << url << ": ShellExecute error code "
112117
<< status;
113118
return FlutterError("open_error", error_message.str());
114119
}
115-
return std::nullopt;
120+
return true;
116121
}
117122

118123
} // namespace url_launcher_windows

packages/url_launcher/url_launcher_windows/windows/url_launcher_plugin.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class UrlLauncherPlugin : public flutter::Plugin, public UrlLauncherApi {
3434

3535
// UrlLauncherApi:
3636
ErrorOr<bool> CanLaunchUrl(const std::string& url) override;
37-
std::optional<FlutterError> LaunchUrl(const std::string& url) override;
37+
ErrorOr<bool> LaunchUrl(const std::string& url) override;
3838

3939
private:
4040
std::unique_ptr<SystemApis> system_apis_;

0 commit comments

Comments
 (0)