Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
Prev Previous commit
Next Next commit
Update native implementation and unit tests
  • Loading branch information
stuartmorgan-g committed Jan 18, 2023
commit f40164dc745e593848105e01fb865a0e601ba0d9
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ project(${PROJECT_NAME} LANGUAGES CXX)
set(PLUGIN_NAME "${PROJECT_NAME}_plugin")

list(APPEND PLUGIN_SOURCES
"messages.g.cpp"
"messages.g.h"
"system_apis.cpp"
"system_apis.h"
"url_launcher_plugin.cpp"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
#include <optional>
#include <string>

namespace url_launcher_windows {

/// The codec used by UrlLauncherApi.
const flutter::StandardMessageCodec& UrlLauncherApi::GetCodec() {
return flutter::StandardMessageCodec::GetInstance(
Expand Down Expand Up @@ -107,3 +109,5 @@ flutter::EncodableValue UrlLauncherApi::WrapError(const FlutterError& error) {
flutter::EncodableValue(error.message()),
flutter::EncodableValue(error.code()), error.details()});
}

} // namespace url_launcher_windows
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
#include <optional>
#include <string>

namespace url_launcher_windows {

// Generated class from Pigeon.

class FlutterError {
Expand Down Expand Up @@ -78,4 +80,7 @@ class UrlLauncherApi {
protected:
UrlLauncherApi() = default;
};

} // namespace url_launcher_windows

#endif // PIGEON_H_
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

#include <windows.h>

namespace url_launcher_plugin {
namespace url_launcher_windows {

SystemApis::SystemApis() {}

Expand Down Expand Up @@ -35,4 +35,4 @@ HINSTANCE SystemApisImpl::ShellExecuteW(HWND hwnd, LPCWSTR operation,
show_flags);
}

} // namespace url_launcher_plugin
} // namespace url_launcher_windows
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// found in the LICENSE file.
#include <windows.h>

namespace url_launcher_plugin {
namespace url_launcher_windows {

// An interface wrapping system APIs used by the plugin, for mocking.
class SystemApis {
Expand Down Expand Up @@ -53,4 +53,4 @@ class SystemApisImpl : public SystemApis {
int show_flags);
};

} // namespace url_launcher_plugin
} // namespace url_launcher_windows
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@
#include <windows.h>

#include <memory>
#include <optional>
#include <string>

#include "messages.g.h"
#include "url_launcher_plugin.h"

namespace url_launcher_plugin {
namespace url_launcher_windows {
namespace test {

namespace {
Expand Down Expand Up @@ -42,51 +44,27 @@ class MockSystemApis : public SystemApis {
(override));
};

class MockMethodResult : public flutter::MethodResult<> {
public:
MOCK_METHOD(void, SuccessInternal, (const EncodableValue* result),
(override));
MOCK_METHOD(void, ErrorInternal,
(const std::string& error_code, const std::string& error_message,
const EncodableValue* details),
(override));
MOCK_METHOD(void, NotImplementedInternal, (), (override));
};

std::unique_ptr<EncodableValue> CreateArgumentsWithUrl(const std::string& url) {
EncodableMap args = {
{EncodableValue("url"), EncodableValue(url)},
};
return std::make_unique<EncodableValue>(args);
}

} // namespace

TEST(UrlLauncherPlugin, CanLaunchSuccessTrue) {
std::unique_ptr<MockSystemApis> system = std::make_unique<MockSystemApis>();
std::unique_ptr<MockMethodResult> result =
std::make_unique<MockMethodResult>();

// Return success values from the registery commands.
HKEY fake_key = reinterpret_cast<HKEY>(1);
EXPECT_CALL(*system, RegOpenKeyExW)
.WillOnce(DoAll(SetArgPointee<4>(fake_key), Return(ERROR_SUCCESS)));
EXPECT_CALL(*system, RegQueryValueExW).WillOnce(Return(ERROR_SUCCESS));
EXPECT_CALL(*system, RegCloseKey(fake_key)).WillOnce(Return(ERROR_SUCCESS));
// Expect a success response.
EXPECT_CALL(*result, SuccessInternal(Pointee(EncodableValue(true))));

UrlLauncherPlugin plugin(std::move(system));
plugin.HandleMethodCall(
flutter::MethodCall("canLaunch",
CreateArgumentsWithUrl("https://some.url.com")),
std::move(result));
ErrorOr<bool> result = plugin.CanLaunchUrl("https://some.url.com");

ASSERT_FALSE(result.has_error());
EXPECT_TRUE(result.value());
}

TEST(UrlLauncherPlugin, CanLaunchQueryFailure) {
std::unique_ptr<MockSystemApis> system = std::make_unique<MockSystemApis>();
std::unique_ptr<MockMethodResult> result =
std::make_unique<MockMethodResult>();

// Return success values from the registery commands, except for the query,
// to simulate a scheme that is in the registry, but has no URL handler.
Expand All @@ -95,68 +73,52 @@ TEST(UrlLauncherPlugin, CanLaunchQueryFailure) {
.WillOnce(DoAll(SetArgPointee<4>(fake_key), Return(ERROR_SUCCESS)));
EXPECT_CALL(*system, RegQueryValueExW).WillOnce(Return(ERROR_FILE_NOT_FOUND));
EXPECT_CALL(*system, RegCloseKey(fake_key)).WillOnce(Return(ERROR_SUCCESS));
// Expect a success response.
EXPECT_CALL(*result, SuccessInternal(Pointee(EncodableValue(false))));

UrlLauncherPlugin plugin(std::move(system));
plugin.HandleMethodCall(
flutter::MethodCall("canLaunch",
CreateArgumentsWithUrl("https://some.url.com")),
std::move(result));
ErrorOr<bool> result = plugin.CanLaunchUrl("https://some.url.com");

ASSERT_FALSE(result.has_error());
EXPECT_FALSE(result.value());
}

TEST(UrlLauncherPlugin, CanLaunchHandlesOpenFailure) {
std::unique_ptr<MockSystemApis> system = std::make_unique<MockSystemApis>();
std::unique_ptr<MockMethodResult> result =
std::make_unique<MockMethodResult>();

// Return failure for opening.
EXPECT_CALL(*system, RegOpenKeyExW).WillOnce(Return(ERROR_BAD_PATHNAME));
// Expect a success response.
EXPECT_CALL(*result, SuccessInternal(Pointee(EncodableValue(false))));

UrlLauncherPlugin plugin(std::move(system));
plugin.HandleMethodCall(
flutter::MethodCall("canLaunch",
CreateArgumentsWithUrl("https://some.url.com")),
std::move(result));
ErrorOr<bool> result = plugin.CanLaunchUrl("https://some.url.com");

ASSERT_FALSE(result.has_error());
EXPECT_FALSE(result.value());
}

TEST(UrlLauncherPlugin, LaunchSuccess) {
std::unique_ptr<MockSystemApis> system = std::make_unique<MockSystemApis>();
std::unique_ptr<MockMethodResult> result =
std::make_unique<MockMethodResult>();

// Return a success value (>32) from launching.
EXPECT_CALL(*system, ShellExecuteW)
.WillOnce(Return(reinterpret_cast<HINSTANCE>(33)));
// Expect a success response.
EXPECT_CALL(*result, SuccessInternal(Pointee(EncodableValue(true))));

UrlLauncherPlugin plugin(std::move(system));
plugin.HandleMethodCall(
flutter::MethodCall("launch",
CreateArgumentsWithUrl("https://some.url.com")),
std::move(result));
std::optional<FlutterError> error = plugin.LaunchUrl("https://some.url.com");

EXPECT_FALSE(error.has_value());
}

TEST(UrlLauncherPlugin, LaunchReportsFailure) {
std::unique_ptr<MockSystemApis> system = std::make_unique<MockSystemApis>();
std::unique_ptr<MockMethodResult> result =
std::make_unique<MockMethodResult>();

// Return a faile value (<=32) from launching.
EXPECT_CALL(*system, ShellExecuteW)
.WillOnce(Return(reinterpret_cast<HINSTANCE>(32)));
// Expect an error response.
EXPECT_CALL(*result, ErrorInternal);

UrlLauncherPlugin plugin(std::move(system));
plugin.HandleMethodCall(
flutter::MethodCall("launch",
CreateArgumentsWithUrl("https://some.url.com")),
std::move(result));
std::optional<FlutterError> error = plugin.LaunchUrl("https://some.url.com");

EXPECT_TRUE(error.has_value());
}

} // namespace test
} // namespace url_launcher_plugin
} // namespace url_launcher_windows
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
#include <sstream>
#include <string>

namespace url_launcher_plugin {
#include "messages.g.h"

namespace url_launcher_windows {

namespace {

Expand Down Expand Up @@ -62,18 +64,9 @@ std::string GetUrlArgument(const flutter::MethodCall<>& method_call) {
// static
void UrlLauncherPlugin::RegisterWithRegistrar(
flutter::PluginRegistrar* registrar) {
auto channel = std::make_unique<flutter::MethodChannel<>>(
registrar->messenger(), "plugins.flutter.io/url_launcher_windows",
&flutter::StandardMethodCodec::GetInstance());

std::unique_ptr<UrlLauncherPlugin> plugin =
std::make_unique<UrlLauncherPlugin>();

channel->SetMethodCallHandler(
[plugin_pointer = plugin.get()](const auto& call, auto result) {
plugin_pointer->HandleMethodCall(call, std::move(result));
});

UrlLauncherApi::SetUp(registrar->messenger(), plugin.get());
registrar->AddPlugin(std::move(plugin));
}

Expand All @@ -85,37 +78,7 @@ UrlLauncherPlugin::UrlLauncherPlugin(std::unique_ptr<SystemApis> system_apis)

UrlLauncherPlugin::~UrlLauncherPlugin() = default;

void UrlLauncherPlugin::HandleMethodCall(
const flutter::MethodCall<>& method_call,
std::unique_ptr<flutter::MethodResult<>> result) {
if (method_call.method_name().compare("launch") == 0) {
std::string url = GetUrlArgument(method_call);
if (url.empty()) {
result->Error("argument_error", "No URL provided");
return;
}

std::optional<std::string> error = LaunchUrl(url);
if (error) {
result->Error("open_error", error.value());
return;
}
result->Success(EncodableValue(true));
} else if (method_call.method_name().compare("canLaunch") == 0) {
std::string url = GetUrlArgument(method_call);
if (url.empty()) {
result->Error("argument_error", "No URL provided");
return;
}

bool can_launch = CanLaunchUrl(url);
result->Success(EncodableValue(can_launch));
} else {
result->NotImplemented();
}
}

bool UrlLauncherPlugin::CanLaunchUrl(const std::string& url) {
ErrorOr<bool> UrlLauncherPlugin::CanLaunchUrl(const std::string& url) {
size_t separator_location = url.find(":");
if (separator_location == std::string::npos) {
return false;
Expand All @@ -134,7 +97,7 @@ bool UrlLauncherPlugin::CanLaunchUrl(const std::string& url) {
return has_handler;
}

std::optional<std::string> UrlLauncherPlugin::LaunchUrl(
std::optional<FlutterError> UrlLauncherPlugin::LaunchUrl(
const std::string& url) {
std::wstring url_wide = Utf16FromUtf8(url);

Expand All @@ -147,9 +110,9 @@ std::optional<std::string> UrlLauncherPlugin::LaunchUrl(
std::ostringstream error_message;
error_message << "Failed to open " << url << ": ShellExecute error code "
<< status;
return std::optional<std::string>(error_message.str());
return FlutterError("open_error", error_message.str());
}
return std::nullopt;
}

} // namespace url_launcher_plugin
} // namespace url_launcher_windows
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@
#include <sstream>
#include <string>

#include "messages.g.h"
#include "system_apis.h"

namespace url_launcher_plugin {
namespace url_launcher_windows {

class UrlLauncherPlugin : public flutter::Plugin {
class UrlLauncherPlugin : public flutter::Plugin, public UrlLauncherApi {
public:
static void RegisterWithRegistrar(flutter::PluginRegistrar* registrar);

Expand All @@ -31,18 +32,12 @@ class UrlLauncherPlugin : public flutter::Plugin {
UrlLauncherPlugin(const UrlLauncherPlugin&) = delete;
UrlLauncherPlugin& operator=(const UrlLauncherPlugin&) = delete;

// Called when a method is called on the plugin channel.
void HandleMethodCall(const flutter::MethodCall<>& method_call,
std::unique_ptr<flutter::MethodResult<>> result);
// UrlLauncherApi:
ErrorOr<bool> CanLaunchUrl(const std::string& url) override;
std::optional<FlutterError> LaunchUrl(const std::string& url) override;

private:
// Returns whether or not the given URL has a registered handler.
bool CanLaunchUrl(const std::string& url);

// Attempts to launch the given URL. On failure, returns an error string.
std::optional<std::string> LaunchUrl(const std::string& url);

std::unique_ptr<SystemApis> system_apis_;
};

} // namespace url_launcher_plugin
} // namespace url_launcher_windows
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

void UrlLauncherWindowsRegisterWithRegistrar(
FlutterDesktopPluginRegistrarRef registrar) {
url_launcher_plugin::UrlLauncherPlugin::RegisterWithRegistrar(
url_launcher_windows::UrlLauncherPlugin::RegisterWithRegistrar(
flutter::PluginRegistrarManager::GetInstance()
->GetRegistrar<flutter::PluginRegistrarWindows>(registrar));
}