Commit d70d4322 authored by Jay Civelli's avatar Jay Civelli Committed by Commit Bot

Servicifying WinWifiCredentialsGetter

As part of the effort to deprecate UtilityProcessMojoClient, convert
WinWifiCredentialsGetter to a mojo service.

To access WinWifiCredentialsGetter clients now have to pass a connector
to the service manager.

WinWifiCredentialsGetter is OS_WIN only, requires UAC elevation, and
is used by the chrome extensions private networking API.

Bug: 782300
Change-Id: I6b536b3359509ebc46b194f1336964b711e39e21
Reviewed-on: https://chromium-review.googlesource.com/756355Reviewed-by: default avatarMisha Efimov <mef@chromium.org>
Reviewed-by: default avatarNoel Gordon <noel@chromium.org>
Reviewed-by: default avatarLei Zhang <thestig@chromium.org>
Reviewed-by: default avatarSteven Bennetts <stevenjb@chromium.org>
Reviewed-by: default avatarTom Sepez <tsepez@chromium.org>
Commit-Queue: Jay Civelli <jcivelli@chromium.org>
Cr-Commit-Position: refs/heads/master@{#516390}
parent 46b37f68
......@@ -415,7 +415,10 @@ if (is_chromeos) {
}
if (is_win) {
chrome_packaged_services += [ "//chrome/services/util_win:manifest" ]
chrome_packaged_services += [
"//chrome/services/util_win:manifest",
"//chrome/services/wifi_util_win:manifest",
]
}
if (!is_android) {
......
......@@ -8,7 +8,8 @@ include_rules = [
"+chrome/installer/util",
"+chrome/services/file_util/public",
"+chrome/services/media_gallery_util/public",
"+chrome/services/util_win/public/interfaces",
"+chrome/services/util_win/public",
"+chrome/services/wifi_util_win/public",
"+chrome_elf/blacklist",
"+chrome_elf/chrome_elf_constants.h",
"+chrome_elf/dll_hash",
......
......@@ -250,6 +250,7 @@
#include "chrome/browser/conflicts/module_database_win.h"
#include "chrome/browser/conflicts/module_event_sink_impl_win.h"
#include "chrome/services/util_win/public/interfaces/constants.mojom.h"
#include "chrome/services/wifi_util_win/public/interfaces/constants.mojom.h"
#include "sandbox/win/src/sandbox_policy.h"
#elif defined(OS_MACOSX)
#include "chrome/browser/chrome_browser_main_mac.h"
......@@ -3132,6 +3133,17 @@ void ChromeContentBrowserClient::RegisterOutOfProcessServices(
l10n_util::GetStringUTF16(IDS_UTILITY_PROCESS_MEDIA_GALLERY_UTILITY_NAME);
#endif
#if defined(OS_WIN)
(*services)[chrome::mojom::kUtilWinServiceName] =
l10n_util::GetStringUTF16(IDS_UTILITY_PROCESS_UTILITY_WIN_NAME);
#endif
#if defined(OS_WIN) && BUILDFLAG(ENABLE_EXTENSIONS)
(*services)[chrome::mojom::kWifiUtilWinServiceName] =
l10n_util::GetStringUTF16(
IDS_UTILITY_PROCESS_WIFI_CREDENTIALS_GETTER_NAME);
#endif
#if !defined(OS_ANDROID)
(*services)[chrome::mojom::kProfileImportServiceName] =
l10n_util::GetStringUTF16(IDS_UTILITY_PROCESS_PROFILE_IMPORTER_NAME);
......@@ -3140,11 +3152,6 @@ void ChromeContentBrowserClient::RegisterOutOfProcessServices(
l10n_util::GetStringUTF16(IDS_UTILITY_PROCESS_PROXY_RESOLVER_NAME);
#endif
#if defined(OS_WIN)
(*services)[chrome::mojom::kUtilWinServiceName] =
l10n_util::GetStringUTF16(IDS_UTILITY_PROCESS_UTILITY_WIN_NAME);
#endif
#if defined(FULL_SAFE_BROWSING) || defined(OS_CHROMEOS)
(*services)[chrome::mojom::kFileUtilServiceName] =
l10n_util::GetStringUTF16(IDS_UTILITY_PROCESS_FILE_UTILITY_NAME);
......
......@@ -53,7 +53,8 @@
"input_device_controller",
"window_manager"
],
"util_win" : [ "shell_util_win" ]
"util_win" : [ "shell_util_win" ],
"wifi_util_win": [ "wifi_credentials" ]
}
},
"navigation:frame": {
......
......@@ -12,7 +12,6 @@
"extensions::mojom::ManifestParser",
"extensions::mojom::MediaParser",
"extensions::mojom::RemovableStorageWriter",
"extensions::mojom::WiFiCredentialsGetter",
"payments::mojom::PaymentManifestParser",
"profiling::mojom::ProfilingClient",
"proxy_resolver::mojom::ProxyResolverFactory",
......
......@@ -1076,6 +1076,7 @@ static_library("extensions") {
if (is_win) {
deps += [
"//chrome/services/wifi_util_win/public/interfaces",
"//third_party/iaccessible2",
"//third_party/isimpledom",
]
......
......@@ -8,7 +8,7 @@
#include "base/run_loop.h"
#include "base/task_scheduler/post_task.h"
#include "chrome/browser/extensions/api/networking_private/networking_private_credentials_getter.h"
#include "chrome/common/extensions/wifi_credentials_getter.mojom.h"
#include "chrome/services/wifi_util_win/public/interfaces/wifi_credentials_getter.mojom.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "content/public/browser/browser_thread.h"
......@@ -25,7 +25,7 @@ class NetworkingPrivateCredentialsGetterTest : public InProcessBrowserTest {
quit_closure_ = run_loop.QuitClosure();
if (use_test_network)
network_ = extensions::mojom::WiFiCredentialsGetter::kWiFiTestNetwork;
network_ = chrome::mojom::WiFiCredentialsGetter::kWiFiTestNetwork;
done_called_ = false;
base::PostTaskWithTraits(
......
......@@ -11,11 +11,11 @@
#include "base/macros.h"
#include "base/strings/string_piece.h"
#include "chrome/browser/extensions/api/networking_private/networking_private_crypto.h"
#include "chrome/common/extensions/wifi_credentials_getter.mojom.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/services/wifi_util_win/public/interfaces/constants.mojom.h"
#include "chrome/services/wifi_util_win/public/interfaces/wifi_credentials_getter.mojom.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/utility_process_mojo_client.h"
#include "ui/base/l10n/l10n_util.h"
#include "content/public/common/service_manager_connection.h"
#include "services/service_manager/public/cpp/connector.h"
namespace {
......@@ -29,7 +29,8 @@ class CredentialsGetterHostClient {
void GetWiFiCredentialsOnIOThread(
const std::string& network_guid,
const NetworkingPrivateCredentialsGetter::CredentialsCallback& callback);
const NetworkingPrivateCredentialsGetter::CredentialsCallback& callback,
std::unique_ptr<service_manager::Connector> connector);
private:
explicit CredentialsGetterHostClient(const std::string& public_key)
......@@ -50,9 +51,7 @@ class CredentialsGetterHostClient {
NetworkingPrivateCredentialsGetter::CredentialsCallback callback_;
// Utility process used to get the credentials.
std::unique_ptr<content::UtilityProcessMojoClient<
extensions::mojom::WiFiCredentialsGetter>>
utility_process_mojo_client_;
chrome::mojom::WiFiCredentialsGetterPtr wifi_credentials_getter_;
// WiFi network to get the credentials from.
std::string wifi_network_;
......@@ -62,29 +61,22 @@ class CredentialsGetterHostClient {
void CredentialsGetterHostClient::GetWiFiCredentialsOnIOThread(
const std::string& network_guid,
const NetworkingPrivateCredentialsGetter::CredentialsCallback& callback) {
const NetworkingPrivateCredentialsGetter::CredentialsCallback& callback,
std::unique_ptr<service_manager::Connector> connector) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
DCHECK(!utility_process_mojo_client_);
DCHECK(!wifi_credentials_getter_);
DCHECK(!callback.is_null());
wifi_network_ = network_guid;
callback_ = callback;
const base::string16 utility_process_name = l10n_util::GetStringUTF16(
IDS_UTILITY_PROCESS_WIFI_CREDENTIALS_GETTER_NAME);
connector->BindInterface(chrome::mojom::kWifiUtilWinServiceName,
&wifi_credentials_getter_);
utility_process_mojo_client_.reset(
new content::UtilityProcessMojoClient<
extensions::mojom::WiFiCredentialsGetter>(utility_process_name));
utility_process_mojo_client_->set_error_callback(
wifi_credentials_getter_.set_connection_error_handler(
base::Bind(&CredentialsGetterHostClient::GetWiFiCredentialsDone,
base::Unretained(this), false, std::string()));
utility_process_mojo_client_->set_run_elevated();
utility_process_mojo_client_->Start(); // Start the utility process.
utility_process_mojo_client_->service()->GetWiFiCredentials(
wifi_credentials_getter_->GetWiFiCredentials(
wifi_network_,
base::Bind(&CredentialsGetterHostClient::GetWiFiCredentialsDone,
base::Unretained(this)));
......@@ -95,7 +87,7 @@ void CredentialsGetterHostClient::GetWiFiCredentialsDone(
const std::string& key_data) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
utility_process_mojo_client_.reset(); // Terminate the utility process.
wifi_credentials_getter_.reset();
ReportResult(success, key_data);
delete this;
}
......@@ -107,8 +99,7 @@ void CredentialsGetterHostClient::ReportResult(bool success,
return;
}
if (wifi_network_ ==
extensions::mojom::WiFiCredentialsGetter::kWiFiTestNetwork) {
if (wifi_network_ == chrome::mojom::WiFiCredentialsGetter::kWiFiTestNetwork) {
DCHECK_EQ(wifi_network_, key_data);
callback_.Run(key_data, std::string());
return;
......@@ -141,25 +132,44 @@ class NetworkingPrivateCredentialsGetterWin
void Start(const std::string& network_guid,
const std::string& public_key,
const CredentialsCallback& callback) override {
// Bounce to the UI thread to retrieve a service_manager::Connector.
content::BrowserThread::PostTask(
content::BrowserThread::IO, FROM_HERE,
base::Bind(
&NetworkingPrivateCredentialsGetterWin::GetCredentialsOnIOThread,
network_guid, public_key, callback));
content::BrowserThread::UI, FROM_HERE,
base::Bind(&NetworkingPrivateCredentialsGetterWin::StartOnUIThread,
network_guid, public_key, callback));
}
private:
~NetworkingPrivateCredentialsGetterWin() override = default;
static void GetCredentialsOnIOThread(const std::string& network_guid,
const std::string& public_key,
const CredentialsCallback& callback) {
static void StartOnUIThread(const std::string& network_guid,
const std::string& public_key,
const CredentialsCallback& callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
std::unique_ptr<service_manager::Connector> connector =
content::ServiceManagerConnection::GetForProcess()
->GetConnector()
->Clone();
content::BrowserThread::PostTask(
content::BrowserThread::IO, FROM_HERE,
base::BindOnce(
&NetworkingPrivateCredentialsGetterWin::GetCredentialsOnIOThread,
network_guid, public_key, callback, std::move(connector)));
}
static void GetCredentialsOnIOThread(
const std::string& network_guid,
const std::string& public_key,
const CredentialsCallback& callback,
std::unique_ptr<service_manager::Connector> connector) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
// CredentialsGetterHostClient is self deleting.
CredentialsGetterHostClient* client =
CredentialsGetterHostClient::Create(public_key);
client->GetWiFiCredentialsOnIOThread(network_guid, callback);
client->GetWiFiCredentialsOnIOThread(network_guid, callback,
std::move(connector));
}
DISALLOW_COPY_AND_ASSIGN(NetworkingPrivateCredentialsGetterWin);
......
......@@ -29,10 +29,6 @@ mojom("mojo_bindings") {
"removable_storage_writer.mojom",
]
if (is_win) {
sources += [ "wifi_credentials_getter.mojom" ]
}
public_deps = [
"//mojo/common:common_custom_types",
]
......
# Copyright 2017 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import("//services/service_manager/public/cpp/service.gni")
import("//services/service_manager/public/service_manifest.gni")
source_set("lib") {
sources = [
"wifi_credentials_getter.cc",
"wifi_credentials_getter.h",
"wifi_util_win_service.cc",
"wifi_util_win_service.h",
]
deps = [
"//base",
"//mojo/public/cpp/bindings",
]
public_deps = [
"//chrome/services/wifi_util_win/public/interfaces",
"//services/service_manager/public/cpp",
]
}
service_manifest("manifest") {
name = "wifi_util_win"
source = "manifest.json"
}
include_rules = [
"+components/wifi"
]
\ No newline at end of file
noel@chromium.org
per-file manifest.json=set noparent
per-file manifest.json=file://ipc/SECURITY_OWNERS
{
"name": "wifi_util_win",
"display_name": "Windows WiFi Utilities",
"sandbox_type": "none_and_elevated",
"interface_provider_specs": {
"service_manager:connector": {
"provides": {
"wifi_credentials": [ "chrome::mojom::WiFiCredentialsGetter" ]
},
"requires": {
"service_manager": [ "service_manager:all_users" ]
}
}
}
}
\ No newline at end of file
# Copyright 2017 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import("//mojo/public/tools/bindings/mojom.gni")
mojom("interfaces") {
sources = [
"wifi_credentials_getter.mojom",
]
public_deps = [
":constants",
]
}
mojom("constants") {
sources = [
"constants.mojom",
]
}
per-file *.mojom=set noparent
per-file *.mojom=file://ipc/SECURITY_OWNERS
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
module chrome.mojom;
const string kWifiUtilWinServiceName = "wifi_util_win";
......@@ -2,11 +2,10 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// WiFi credentials interface provided by the utility process and exposed
// by mojo policy control to the chrome browser process on OS_WIN.
module extensions.mojom;
module chrome.mojom;
// Interface for retrieving WiFi credentials for WiFi networks.
// Must be run in a non sandboxed process with elevated privileges.
interface WiFiCredentialsGetter {
const string kWiFiTestNetwork = "chrome://test-get-wifi-credentials";
......
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/services/wifi_util_win/wifi_credentials_getter.h"
#include "components/wifi/wifi_service.h"
namespace chrome {
WiFiCredentialsGetter::WiFiCredentialsGetter(
std::unique_ptr<service_manager::ServiceContextRef> service_ref)
: service_ref_(std::move(service_ref)) {}
WiFiCredentialsGetter::~WiFiCredentialsGetter() = default;
void WiFiCredentialsGetter::GetWiFiCredentials(
const std::string& ssid,
GetWiFiCredentialsCallback callback) {
if (ssid == kWiFiTestNetwork) {
// test-mode: return the ssid in key_data.
std::move(callback).Run(true, ssid);
return;
}
std::unique_ptr<wifi::WiFiService> wifi_service(wifi::WiFiService::Create());
wifi_service->Initialize(nullptr);
std::string key_data;
std::string error;
wifi_service->GetKeyFromSystem(ssid, &key_data, &error);
const bool success = error.empty();
if (!success)
key_data.clear();
std::move(callback).Run(success, key_data);
}
} // namespace chrome
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_SERVICES_WIFI_UTIL_WIN_WIFI_CREDENTIALS_GETTER_H_
#define CHROME_SERVICES_WIFI_UTIL_WIN_WIFI_CREDENTIALS_GETTER_H_
#include <memory>
#include "chrome/services/wifi_util_win/public/interfaces/wifi_credentials_getter.mojom.h"
#include "services/service_manager/public/cpp/service_context_ref.h"
namespace chrome {
class WiFiCredentialsGetter : public chrome::mojom::WiFiCredentialsGetter {
public:
explicit WiFiCredentialsGetter(
std::unique_ptr<service_manager::ServiceContextRef> service_ref);
~WiFiCredentialsGetter() override;
private:
// chrome::mojom::WiFiCredentialsGetter:
void GetWiFiCredentials(const std::string& ssid,
GetWiFiCredentialsCallback callback) override;
const std::unique_ptr<service_manager::ServiceContextRef> service_ref_;
DISALLOW_COPY_AND_ASSIGN(WiFiCredentialsGetter);
};
} // namespace chrome
#endif // CHROME_SERVICES_WIFI_UTIL_WIN_WIFI_CREDENTIALS_GETTER_H_
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/services/wifi_util_win/wifi_util_win_service.h"
#include <memory>
#include "build/build_config.h"
#include "chrome/services/wifi_util_win/wifi_credentials_getter.h"
#include "mojo/public/cpp/bindings/strong_binding.h"
namespace chrome {
namespace {
void OnWifiCredentialsGetterRequest(
service_manager::ServiceContextRefFactory* ref_factory,
chrome::mojom::WiFiCredentialsGetterRequest request) {
mojo::MakeStrongBinding(
std::make_unique<WiFiCredentialsGetter>(ref_factory->CreateRef()),
std::move(request));
}
} // namespace
WifiUtilWinService::WifiUtilWinService() = default;
WifiUtilWinService::~WifiUtilWinService() = default;
std::unique_ptr<service_manager::Service> WifiUtilWinService::CreateService() {
return std::make_unique<WifiUtilWinService>();
}
void WifiUtilWinService::OnStart() {
ref_factory_ = std::make_unique<service_manager::ServiceContextRefFactory>(
base::Bind(&service_manager::ServiceContext::RequestQuit,
base::Unretained(context())));
registry_.AddInterface(
base::Bind(&OnWifiCredentialsGetterRequest, ref_factory_.get()));
}
void WifiUtilWinService::OnBindInterface(
const service_manager::BindSourceInfo& source_info,
const std::string& interface_name,
mojo::ScopedMessagePipeHandle interface_pipe) {
registry_.BindInterface(interface_name, std::move(interface_pipe));
}
} // namespace chrome
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_SERVICES_WIFI_UTIL_WIN_WIFI_UTIL_WIN_SERVICE_H_
#define CHROME_SERVICES_WIFI_UTIL_WIN_WIFI_UTIL_WIN_SERVICE_H_
#include "services/service_manager/public/cpp/binder_registry.h"
#include "services/service_manager/public/cpp/service_context.h"
#include "services/service_manager/public/cpp/service_context_ref.h"
namespace chrome {
class WifiUtilWinService : public service_manager::Service {
public:
WifiUtilWinService();
~WifiUtilWinService() override;
// Factory method for creating the service.
static std::unique_ptr<service_manager::Service> CreateService();
// Lifescycle events that occur after the service has started to spinup.
void OnStart() override;
void OnBindInterface(const service_manager::BindSourceInfo& source_info,
const std::string& interface_name,
mojo::ScopedMessagePipeHandle interface_pipe) override;
private:
// State needed to manage service lifecycle and lifecycle of bound clients.
std::unique_ptr<service_manager::ServiceContextRefFactory> ref_factory_;
service_manager::BinderRegistry registry_;
DISALLOW_COPY_AND_ASSIGN(WifiUtilWinService);
};
} // namespace chrome
#endif // CHROME_SERVICES_WIFI_UTIL_WIN_WIFI_UTIL_WIN_SERVICE_H_
......@@ -132,7 +132,10 @@ static_library("utility") {
libs = [ "esent.lib" ]
ldflags += [ "/DELAYLOAD:esent.dll" ]
deps += [ "//chrome/services/util_win:lib" ]
deps += [
"//chrome/services/util_win:lib",
"//chrome/services/wifi_util_win:lib",
]
}
if (is_win || is_mac) {
......
......@@ -8,6 +8,8 @@ include_rules = [
"+chrome/services/media_gallery_util/public/interfaces",
"+chrome/services/util_win/util_win_service.h",
"+chrome/services/util_win/public/interfaces",
"+chrome/services/wifi_util_win/wifi_util_win_service.h",
"+chrome/services/wifi_util_win/public/interfaces",
"+components/font_service/font_service_app.h",
"+components/patch_service",
"+components/payments/content/utility",
......
......@@ -54,6 +54,10 @@
#include "chrome/services/media_gallery_util/media_gallery_util_service.h"
#include "chrome/services/media_gallery_util/public/interfaces/constants.mojom.h"
#include "chrome/utility/extensions/extensions_handler.h"
#if defined(OS_WIN)
#include "chrome/services/wifi_util_win/public/interfaces/constants.mojom.h"
#include "chrome/services/wifi_util_win/wifi_util_win_service.h"
#endif
#endif
#if BUILDFLAG(ENABLE_MUS)
......@@ -188,6 +192,16 @@ bool ChromeContentUtilityClient::OnMessageReceived(
void ChromeContentUtilityClient::RegisterServices(
ChromeContentUtilityClient::StaticServiceMap* services) {
if (utility_process_running_elevated_) {
#if defined(OS_WIN) && BUILDFLAG(ENABLE_EXTENSIONS)
service_manager::EmbeddedServiceInfo service_info;
service_info.factory =
base::Bind(&chrome::WifiUtilWinService::CreateService);
services->emplace(chrome::mojom::kWifiUtilWinServiceName, service_info);
#endif
return;
}
#if BUILDFLAG(ENABLE_PRINTING)
service_manager::EmbeddedServiceInfo pdf_compositor_info;
pdf_compositor_info.factory =
......@@ -241,7 +255,7 @@ void ChromeContentUtilityClient::RegisterServices(
}
#endif
if (!utility_process_running_elevated_) {
{
service_manager::EmbeddedServiceInfo service_info;
service_info.factory = base::Bind(&patch::PatchService::CreateService);
services->emplace(patch::mojom::kServiceName, service_info);
......
......@@ -53,46 +53,6 @@ class RemovableStorageWriterImpl
DISALLOW_COPY_AND_ASSIGN(RemovableStorageWriterImpl);
};
#if defined(OS_WIN)
class WiFiCredentialsGetterImpl
: public extensions::mojom::WiFiCredentialsGetter {
public:
WiFiCredentialsGetterImpl() = default;
~WiFiCredentialsGetterImpl() override = default;
static void Create(extensions::mojom::WiFiCredentialsGetterRequest request) {
mojo::MakeStrongBinding(base::MakeUnique<WiFiCredentialsGetterImpl>(),
std::move(request));
}
private:
// extensions::mojom::WiFiCredentialsGetter:
void GetWiFiCredentials(const std::string& ssid,
GetWiFiCredentialsCallback callback) override {
if (ssid == kWiFiTestNetwork) {
// test-mode: return the ssid in key_data.
std::move(callback).Run(true, ssid);
return;
}
std::unique_ptr<wifi::WiFiService> wifi_service(
wifi::WiFiService::Create());
wifi_service->Initialize(nullptr);
std::string key_data;
std::string error;
wifi_service->GetKeyFromSystem(ssid, &key_data, &error);
const bool success = error.empty();
if (!success)
key_data.clear();
std::move(callback).Run(success, key_data);
}
DISALLOW_COPY_AND_ASSIGN(WiFiCredentialsGetterImpl);
};
#endif // defined(OS_WIN)
} // namespace
......@@ -117,8 +77,6 @@ void ExposeInterfacesToBrowser(service_manager::BinderRegistry* registry,
#if defined(OS_WIN)
registry->AddInterface(base::Bind(&RemovableStorageWriterImpl::Create),
base::ThreadTaskRunnerHandle::Get());
registry->AddInterface(base::Bind(&WiFiCredentialsGetterImpl::Create),
base::ThreadTaskRunnerHandle::Get());
#endif
return;
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment