added implementation for callback result, impl for shouldOverrideUrlLoading

This commit is contained in:
unknown 2024-01-07 03:26:14 +01:00
parent 81b72b3213
commit 43aa01d944
16 changed files with 343 additions and 43 deletions

View File

@ -57,8 +57,11 @@ list(APPEND PLUGIN_SOURCES
"utils/util.h"
"types/channel_delegate.cpp"
"types/channel_delegate.h"
"types/base_callback_result.h"
"types/url_request.cpp"
"types/url_request.h"
"types/navigation_action.cpp"
"types/navigation_action.h"
"in_app_webview/in_app_webview.cpp"
"in_app_webview/in_app_webview.h"
"in_app_webview/webview_channel_delegate.cpp"

View File

@ -1,17 +1,7 @@
#include "flutter_inappwebview_windows_plugin.h"
// This must be included before many other Windows headers.
#include <windows.h>
// For getPlatformVersion; remove unless needed for your plugin implementation.
#include <VersionHelpers.h>
#include <flutter/method_channel.h>
#include <flutter/plugin_registrar_windows.h>
#include <flutter/standard_method_codec.h>
#include <memory>
#include <sstream>
namespace flutter_inappwebview_plugin
{
@ -30,6 +20,6 @@ namespace flutter_inappwebview_plugin
FlutterInappwebviewWindowsPlugin::~FlutterInappwebviewWindowsPlugin()
{
inAppBrowserManager.release();
}
}

View File

@ -16,6 +16,7 @@
#include <wil/wrl.h>
#include <wil/com.h>
#include "../utils/strconv.h"
#include "../utils/util.h"
#include <WebView2.h>
#include <WebView2EnvironmentOptions.h>
@ -229,7 +230,7 @@ namespace flutter_inappwebview_plugin
InAppBrowser::~InAppBrowser()
{
std::cout << "dealloc InAppBrowser\n";
debugLog("dealloc InAppBrowser");
webView.reset();
SetWindowLongPtr(m_hWnd, GWLP_USERDATA, 0);
plugin = nullptr;

View File

@ -35,6 +35,6 @@ namespace flutter_inappwebview_plugin
InAppBrowserChannelDelegate::~InAppBrowserChannelDelegate()
{
std::cout << "dealloc InAppBrowserChannelDelegate\n";
debugLog("dealloc InAppBrowserChannelDelegate");
}
}

View File

@ -45,11 +45,7 @@ namespace flutter_inappwebview_plugin
InAppBrowserManager::~InAppBrowserManager()
{
std::cout << "dealloc InAppBrowserManager\n";
for (std::map<std::string, std::unique_ptr<InAppBrowser>>::iterator itr = browsers.begin(); itr != browsers.end(); itr++)
{
browsers.erase(itr->first);
}
debugLog("dealloc InAppBrowserManager");
browsers.clear();
plugin = nullptr;
}

View File

@ -1,7 +1,11 @@
#pragma comment(lib, "Shlwapi.lib")
#include "in_app_webview.h"
#include <WebView2EnvironmentOptions.h>
#include <wil/wrl.h>
#include <Shlwapi.h>
#include "../utils/strconv.h"
#include "../utils/util.h"
namespace flutter_inappwebview_plugin
{
@ -24,6 +28,7 @@ namespace flutter_inappwebview_plugin
CreateCoreWebView2EnvironmentWithOptions(nullptr, nullptr, nullptr,
Callback<ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler>(
[parentWindow, completionHandler, this](HRESULT result, ICoreWebView2Environment* env) -> HRESULT {
webViewEnv = env;
// Create a CoreWebView2Controller and get the associated CoreWebView2 whose parent is the main window hWnd
env->CreateCoreWebView2Controller(parentWindow, Callback<ICoreWebView2CreateCoreWebView2ControllerCompletedHandler>(
[parentWindow, completionHandler, this](HRESULT result, ICoreWebView2Controller* controller) -> HRESULT {
@ -56,13 +61,74 @@ namespace flutter_inappwebview_plugin
webView->add_NavigationStarting(
Callback<ICoreWebView2NavigationStartingEventHandler>(
[this](ICoreWebView2* sender, ICoreWebView2NavigationStartingEventArgs* args) {
if (channelDelegate) {
LPWSTR uri = nullptr;
std::optional<std::string> url = SUCCEEDED(args->get_Uri(&uri)) ? wide_to_utf8(std::wstring(uri)) : std::optional<std::string>{};
channelDelegate->onLoadStart(url);
if (!channelDelegate) {
args->put_Cancel(false);
return S_OK;
}
wil::unique_cotaskmem_string uri = nullptr;
std::optional<std::string> url = SUCCEEDED(args->get_Uri(&uri)) ? wide_to_utf8(std::wstring(uri.get())) : std::optional<std::string>{};
wil::unique_cotaskmem_string method = nullptr;
wil::com_ptr<ICoreWebView2HttpRequestHeaders> requestHeaders = nullptr;
std::optional<std::map<std::string, std::string>> headers = std::optional<std::map<std::string, std::string>>{};
if (SUCCEEDED(args->get_RequestHeaders(&requestHeaders))) {
headers = std::make_optional<std::map<std::string, std::string>>({});
wil::com_ptr<ICoreWebView2HttpHeadersCollectionIterator> iterator;
requestHeaders->GetIterator(&iterator);
BOOL hasCurrent = FALSE;
while (SUCCEEDED(iterator->get_HasCurrentHeader(&hasCurrent)) && hasCurrent)
{
wil::unique_cotaskmem_string name;
wil::unique_cotaskmem_string value;
if (SUCCEEDED(iterator->GetCurrentHeader(&name, &value))) {
headers->insert({ wide_to_utf8(std::wstring(name.get())), wide_to_utf8(std::wstring(value.get())) });
}
BOOL hasNext = FALSE;
iterator->MoveNext(&hasNext);
}
requestHeaders->GetHeader(L"Flutter-InAppWebView-Request-Method", &method);
requestHeaders->RemoveHeader(L"Flutter-InAppWebView-Request-Method");
}
args->put_Cancel(false);
if (callShouldOverrideUrlLoading && method == nullptr) {
// for some reason, we can't cancel and load an URL with other HTTP methods other than GET,
// so ignore the shouldOverrideUrlLoading event.
auto urlRequest = std::make_shared<URLRequest>(url, std::nullopt, headers, std::nullopt);
auto navigationAction = std::make_unique<NavigationAction>(
urlRequest,
true
);
auto callback = std::make_unique<WebViewChannelDelegate::ShouldOverrideUrlLoadingCallback>();
callback->nonNullSuccess = [this, urlRequest](const NavigationActionPolicy actionPolicy) {
callShouldOverrideUrlLoading = false;
if (actionPolicy == allow) {
loadUrl(*urlRequest);
}
return false;
};
auto defaultBehaviour = [this, urlRequest](const std::optional<NavigationActionPolicy> actionPolicy) {
callShouldOverrideUrlLoading = false;
loadUrl(*urlRequest);
};
callback->defaultBehaviour = defaultBehaviour;
callback->error = [defaultBehaviour](const std::string& error_code, const std::string& error_message, const flutter::EncodableValue* error_details) {
debugLog(error_code + ", " + error_message);
defaultBehaviour(std::nullopt);
};
channelDelegate->shouldOverrideUrlLoading(std::move(navigationAction), std::move(callback));
args->put_Cancel(true);
}
else {
callShouldOverrideUrlLoading = true;
channelDelegate->onLoadStart(url);
args->put_Cancel(false);
}
return S_OK;
}
@ -71,10 +137,19 @@ namespace flutter_inappwebview_plugin
webView->add_NavigationCompleted(
Callback<ICoreWebView2NavigationCompletedEventHandler>(
[this](ICoreWebView2* sender, ICoreWebView2NavigationCompletedEventArgs* args) {
COREWEBVIEW2_WEB_ERROR_STATUS web_error_status;
args->get_WebErrorStatus(&web_error_status);
debugLog("WebErrorStatus " + std::to_string(web_error_status) + "\n");
BOOL isSuccess;
args->get_IsSuccess(&isSuccess);
if (channelDelegate) {
LPWSTR uri = nullptr;
std::optional<std::string> url = SUCCEEDED(webView->get_Source(&uri)) ? wide_to_utf8(std::wstring(uri)) : std::optional<std::string>{};
channelDelegate->onLoadStop(url);
if (isSuccess) {
channelDelegate->onLoadStop(url);
}
}
return S_OK;
}
@ -89,20 +164,53 @@ namespace flutter_inappwebview_plugin
void InAppWebView::loadUrl(const URLRequest urlRequest) const
{
if (!webView) {
if (!webView || !urlRequest.url.has_value()) {
return;
}
auto url = urlRequest.url.value();
std::wstring stemp = ansi_to_wide(url);
std::wstring url = ansi_to_wide(urlRequest.url.value());
// Schedule an async task to navigate to Bing
webView->Navigate(stemp.c_str());
wil::com_ptr<ICoreWebView2Environment2> webViewEnv2;
wil::com_ptr<ICoreWebView2_2> webView2;
if (SUCCEEDED(webViewEnv->QueryInterface(IID_PPV_ARGS(&webViewEnv2))) && SUCCEEDED(webView->QueryInterface(IID_PPV_ARGS(&webView2)))) {
wil::com_ptr<ICoreWebView2WebResourceRequest> webResourceRequest;
std::wstring method = urlRequest.method.has_value() ? ansi_to_wide(urlRequest.method.value()) : L"GET";
wil::com_ptr<IStream> postDataStream = nullptr;
if (urlRequest.body.has_value()) {
auto postData = std::string(urlRequest.body->begin(), urlRequest.body->end());
postDataStream = SHCreateMemStream(
reinterpret_cast<const BYTE*>(postData.data()), static_cast<UINT>(postData.length()));
}
webViewEnv2->CreateWebResourceRequest(
url.c_str(),
method.c_str(),
postDataStream.get(),
L"",
&webResourceRequest
);
wil::com_ptr<ICoreWebView2HttpRequestHeaders> requestHeaders;
if (SUCCEEDED(webResourceRequest->get_Headers(&requestHeaders))) {
if (urlRequest.method.has_value() && urlRequest.method.value().compare("GET") != 0) {
requestHeaders->SetHeader(L"Flutter-InAppWebView-Request-Method", method.c_str());
}
if (urlRequest.headers.has_value()) {
auto& headers = urlRequest.headers.value();
for (auto const& [key, val] : headers) {
requestHeaders->SetHeader(ansi_to_wide(key).c_str(), ansi_to_wide(val).c_str());
}
}
}
webView2->NavigateWithWebResourceRequest(webResourceRequest.get());
}
else {
webView->Navigate(url.c_str());
}
}
InAppWebView::~InAppWebView()
{
std::cout << "dealloc InAppWebView\n";
debugLog("dealloc InAppWebView");
if (webView) {
webView->Stop();
}

View File

@ -20,6 +20,7 @@ namespace flutter_inappwebview_plugin
FlutterInappwebviewWindowsBasePlugin* plugin;
std::variant<std::string, int> id;
wil::com_ptr<ICoreWebView2Environment> webViewEnv;
wil::com_ptr<ICoreWebView2Controller> webViewController;
wil::com_ptr<ICoreWebView2> webView;
std::unique_ptr<WebViewChannelDelegate> channelDelegate;
@ -32,6 +33,7 @@ namespace flutter_inappwebview_plugin
void loadUrl(const URLRequest urlRequest) const;
private:
bool callShouldOverrideUrlLoading = true;
void createWebView(const HWND parentWindow, const std::function<void()> completionHandler);
void InAppWebView::registerEventHandlers();
};

View File

@ -3,6 +3,7 @@
#include "../utils/util.h"
#include "../utils/strconv.h"
#include "../types/base_callback_result.h"
namespace flutter_inappwebview_plugin
{
@ -18,6 +19,16 @@ namespace flutter_inappwebview_plugin
}
WebViewChannelDelegate::ShouldOverrideUrlLoadingCallback::ShouldOverrideUrlLoadingCallback() {
decodeResult = [](const flutter::EncodableValue* value) {
if (value->IsNull()) {
return cancel;
}
auto navigationPolicy = std::get<int>(*value);
return static_cast<NavigationActionPolicy>(navigationPolicy);
};
}
void WebViewChannelDelegate::HandleMethodCall(const flutter::MethodCall<flutter::EncodableValue>& method_call,
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result)
{
@ -27,8 +38,12 @@ namespace flutter_inappwebview_plugin
}
if (method_call.method_name().compare("getUrl") == 0) {
std::optional<std::string> url = webView->getUrl();
result->Success(url.has_value() ? flutter::EncodableValue(url.value()) : flutter::EncodableValue());
result->Success(optional_to_fl_value(webView->getUrl()));
} else if (method_call.method_name().compare("loadUrl") == 0) {
auto& arguments = std::get<flutter::EncodableMap>(*method_call.arguments());
auto urlRequest = std::make_unique<URLRequest>(get_fl_map_value<flutter::EncodableMap>(arguments, "urlRequest"));
webView->loadUrl(*urlRequest);
result->Success(flutter::EncodableValue(true));
}
else {
result->NotImplemented();
@ -42,7 +57,7 @@ namespace flutter_inappwebview_plugin
}
auto arguments = std::make_unique<flutter::EncodableValue>(flutter::EncodableMap {
{flutter::EncodableValue("url"), url.has_value() ? flutter::EncodableValue(url.value()) : flutter::EncodableValue()},
{flutter::EncodableValue("url"), optional_to_fl_value(url)},
});
channel->InvokeMethod("onLoadStart", std::move(arguments));
}
@ -54,14 +69,24 @@ namespace flutter_inappwebview_plugin
}
auto arguments = std::make_unique<flutter::EncodableValue>(flutter::EncodableMap{
{flutter::EncodableValue("url"), url.has_value() ? flutter::EncodableValue(url.value()) : flutter::EncodableValue()},
{flutter::EncodableValue("url"), optional_to_fl_value(url)},
});
channel->InvokeMethod("onLoadStop", std::move(arguments));
}
void WebViewChannelDelegate::shouldOverrideUrlLoading(std::shared_ptr<NavigationAction> navigationAction, std::unique_ptr<ShouldOverrideUrlLoadingCallback> callback)
{
if (!channel) {
return;
}
auto arguments = std::make_unique<flutter::EncodableValue>(navigationAction->toEncodableMap());
channel->InvokeMethod("shouldOverrideUrlLoading", std::move(arguments), std::move(callback));
}
WebViewChannelDelegate::~WebViewChannelDelegate()
{
std::cout << "dealloc WebViewChannelDelegate\n";
debugLog("dealloc WebViewChannelDelegate");
webView = nullptr;
}
}

View File

@ -5,16 +5,26 @@
#include <flutter/standard_message_codec.h>
#include "../types/channel_delegate.h"
#include "../types/base_callback_result.h"
#include "../types/navigation_action.h"
namespace flutter_inappwebview_plugin
{
class InAppWebView;
enum NavigationActionPolicy {cancel = 0, allow = 1};
class WebViewChannelDelegate : public ChannelDelegate
{
public:
InAppWebView* webView;
class ShouldOverrideUrlLoadingCallback : public BaseCallbackResult<NavigationActionPolicy> {
public:
ShouldOverrideUrlLoadingCallback();
~ShouldOverrideUrlLoadingCallback() = default;
};
WebViewChannelDelegate(InAppWebView* webView, flutter::BinaryMessenger* messenger);
WebViewChannelDelegate(InAppWebView* webView, flutter::BinaryMessenger* messenger, const std::string& name);
~WebViewChannelDelegate();
@ -25,6 +35,7 @@ namespace flutter_inappwebview_plugin
void onLoadStart(const std::optional<std::string> url) const;
void onLoadStop(const std::optional<std::string> url) const;
void shouldOverrideUrlLoading(std::shared_ptr<NavigationAction> navigationAction, std::unique_ptr<ShouldOverrideUrlLoadingCallback> callback);
};
}

View File

@ -0,0 +1,53 @@
#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_BASE_CALLBACK_RESULT_H_
#define FLUTTER_INAPPWEBVIEW_PLUGIN_BASE_CALLBACK_RESULT_H_
#include <flutter/standard_method_codec.h>
#include <flutter/method_result_functions.h>
#include <optional>
namespace flutter_inappwebview_plugin
{
template <typename T>
class BaseCallbackResult : public flutter::MethodResultFunctions<flutter::EncodableValue>
{
public:
flutter::ResultHandlerError<flutter::EncodableValue> error;
flutter::ResultHandlerNotImplemented<flutter::EncodableValue> notImplemented;
std::function<bool(const T result)> nonNullSuccess = [](const T result) { return true; };
std::function<bool()> nullSuccess = []() { return true; };
std::function<void(const std::optional<T> result)> defaultBehaviour = [](const std::optional<T> result) {};
std::function<std::optional<T>(const flutter::EncodableValue* result)> decodeResult = [](const flutter::EncodableValue* result) { return std::nullopt; };
BaseCallbackResult<T>() :
MethodResultFunctions(
[this](const flutter::EncodableValue* val) {
std::optional<T> result = decodeResult ? decodeResult(val) : std::nullopt;
auto shouldRunDefaultBehaviour = false;
if (result.has_value()) {
shouldRunDefaultBehaviour = nonNullSuccess ? nonNullSuccess(result.value()) : shouldRunDefaultBehaviour;
}
else {
shouldRunDefaultBehaviour = nullSuccess ? nullSuccess() : shouldRunDefaultBehaviour;
}
if (shouldRunDefaultBehaviour && defaultBehaviour) {
defaultBehaviour(result);
}
},
[this](const std::string& error_code, const std::string& error_message, const flutter::EncodableValue* error_details) {
if (error) {
error(error_code, error_message, error_details);
}
},
[this]() {
if (defaultBehaviour) {
defaultBehaviour(std::nullopt);
}
if (notImplemented) {
notImplemented();
}
}) {};
virtual ~BaseCallbackResult<T>() {};
};
}
#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_BASE_CALLBACK_RESULT_H_

View File

@ -1,4 +1,5 @@
#pragma once
#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_CHANNEL_DELEGATE_H_
#define FLUTTER_INAPPWEBVIEW_PLUGIN_CHANNEL_DELEGATE_H_
#include <flutter/method_channel.h>
@ -18,10 +19,12 @@ namespace flutter_inappwebview_plugin
flutter::BinaryMessenger* messenger;
ChannelDelegate(flutter::BinaryMessenger* messenger, const std::string& name);
~ChannelDelegate();
virtual ~ChannelDelegate();
virtual void HandleMethodCall(
const flutter::MethodCall<flutter::EncodableValue>& method_call,
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result);
};
}
}
#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_CHANNEL_DELEGATE_H_

View File

@ -0,0 +1,20 @@
#include "navigation_action.h"
#include "../utils/util.h"
namespace flutter_inappwebview_plugin
{
NavigationAction::NavigationAction(std::shared_ptr<URLRequest> request, bool isForMainFrame)
: request(std::move(request)), isForMainFrame(isForMainFrame)
{
}
flutter::EncodableMap NavigationAction::toEncodableMap()
{
return flutter::EncodableMap{
{flutter::EncodableValue("request"), request->toEncodableMap()},
{flutter::EncodableValue("isForMainFrame"), flutter::EncodableValue(isForMainFrame)}
};
}
}

View File

@ -0,0 +1,22 @@
#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_NAVIGATION_ACTION_H_
#define FLUTTER_INAPPWEBVIEW_PLUGIN_NAVIGATION_ACTION_H_
#include <flutter/standard_method_codec.h>
#include <optional>
#include "url_request.h"
namespace flutter_inappwebview_plugin
{
class NavigationAction
{
public:
const std::shared_ptr<URLRequest> request;
const bool isForMainFrame;
NavigationAction(std::shared_ptr<URLRequest> request, bool isForMainFrame);
~NavigationAction() = default;
flutter::EncodableMap toEncodableMap();
};
}
#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_NAVIGATION_ACTION_H_

View File

@ -4,10 +4,28 @@
namespace flutter_inappwebview_plugin
{
URLRequest::URLRequest(const flutter::EncodableMap map)
: url(get_optional_flutter_value<std::string>(map, "url")),
method(get_optional_flutter_value<std::string>(map, "method"))
URLRequest::URLRequest(std::optional<std::string> url, std::optional<std::string> method, std::optional<std::map<std::string, std::string>> headers, std::optional<std::vector<uint8_t>> body)
: url(url), method(method), headers(headers), body(body)
{
}
URLRequest::URLRequest(const flutter::EncodableMap map)
: url(get_optional_fl_map_value<std::string>(map, "url")),
method(get_optional_fl_map_value<std::string>(map, "method")),
headers(get_optional_fl_map_value(map, "headers")),
body(get_optional_fl_map_value<std::vector<uint8_t>>(map, "body"))
{
}
flutter::EncodableMap URLRequest::toEncodableMap()
{
return flutter::EncodableMap{
{flutter::EncodableValue("url"), optional_to_fl_value(url)},
{flutter::EncodableValue("method"), optional_to_fl_value(method)},
{flutter::EncodableValue("headers"), optional_to_fl_value(headers)},
{flutter::EncodableValue("body"), optional_to_fl_value(body)}
};
}
}

View File

@ -12,8 +12,13 @@ namespace flutter_inappwebview_plugin
public:
const std::optional<std::string> url;
const std::optional<std::string> method;
const std::optional<std::map<std::string, std::string>> headers;
const std::optional<std::vector<uint8_t>> body;
URLRequest(std::optional<std::string> url, std::optional<std::string> method, std::optional<std::map<std::string, std::string>> headers, std::optional<std::vector<uint8_t>> body);
URLRequest(const flutter::EncodableMap map);
~URLRequest() = default;
flutter::EncodableMap toEncodableMap();
};
}

View File

@ -1,11 +1,21 @@
#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_H_
#define FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_H_
#include <iostream>
#include <string>
#include <optional>
#include <algorithm>
#include "strconv.h"
namespace flutter_inappwebview_plugin
{
static inline void debugLog(const std::string& msg) {
#ifndef NDEBUG
std::cout << msg << std::endl;
OutputDebugString(ansi_to_wide(msg + "\n").c_str());
#endif
}
template<typename T>
static inline std::optional<T> make_pointer_optional(const T* value)
{
@ -13,17 +23,50 @@ namespace flutter_inappwebview_plugin
}
template<typename T>
static inline T get_flutter_value(const flutter::EncodableMap map, const char* string)
static inline T get_fl_map_value(const flutter::EncodableMap map, const char* string)
{
return std::get<T>(map.at(flutter::EncodableValue(string)));
}
template<typename T>
static inline std::optional<T> get_optional_flutter_value(const flutter::EncodableMap map, const char* string)
static inline std::optional<T> get_optional_fl_map_value(const flutter::EncodableMap map, const char* string)
{
return make_pointer_optional<T>(std::get_if<T>(&map.at(flutter::EncodableValue(string))));
}
static inline std::optional<std::map<std::string, std::string>> get_optional_fl_map_value(const flutter::EncodableMap map, const char* string)
{
auto mapValue = std::map<std::string, std::string>{};
auto flMap = std::get_if<flutter::EncodableMap>(&map.at(flutter::EncodableValue(string)));
if (flMap) {
for (auto itr = flMap->begin(); itr != flMap->end(); itr++)
{
mapValue.insert({ std::get<std::string>(itr->first), std::get<std::string>(itr->second) });
}
}
return make_pointer_optional<std::map<std::string, std::string>>(&mapValue);
}
template<typename T>
static inline flutter::EncodableValue optional_to_fl_value(const std::optional<T> optional)
{
return optional.has_value() ? flutter::EncodableValue(optional.value()) : flutter::EncodableValue();
}
static inline flutter::EncodableValue optional_to_fl_value(const std::optional<std::map<std::string, std::string>> optional)
{
if (!optional.has_value()) {
return flutter::EncodableValue();
}
auto& mapValue = optional.value();
auto encodableMap = flutter::EncodableMap{};
for (auto const& [key, val] : mapValue)
{
encodableMap.insert({ flutter::EncodableValue(key), flutter::EncodableValue(val) });
}
return encodableMap;
}
static inline std::string variant_to_string(std::variant<std::string, int> var)
{
return std::visit([](auto&& arg) {