windows: added headless inappwebview initial implementation

This commit is contained in:
unknown 2024-01-24 02:11:28 +01:00
parent ab869e9703
commit 7721deb062
15 changed files with 446 additions and 23 deletions

View File

@ -274,7 +274,7 @@ class WindowsHeadlessInAppWebView extends PlatformHeadlessInAppWebView
dynamic _controllerFromPlatform;
WindowsHeadlessInAppWebViewCreationParams get _macosParams =>
WindowsHeadlessInAppWebViewCreationParams get _windowsParams =>
params as WindowsHeadlessInAppWebViewCreationParams;
_init() {
@ -284,7 +284,7 @@ class WindowsHeadlessInAppWebView extends PlatformHeadlessInAppWebView
_controllerFromPlatform =
params.controllerFromPlatform?.call(_webViewController!) ??
_webViewController!;
_macosParams.findInteractionController?.init(id);
_windowsParams.findInteractionController?.init(id);
channel =
MethodChannel('com.pichillilorenzo/flutter_headless_inappwebview_$id');
handler = _handleMethod;
@ -320,8 +320,8 @@ class WindowsHeadlessInAppWebView extends PlatformHeadlessInAppWebView
initialSettings.toMap();
Map<String, dynamic> pullToRefreshSettings =
_macosParams.pullToRefreshController?.params.settings.toMap() ??
_macosParams.pullToRefreshController?.params.options.toMap() ??
_windowsParams.pullToRefreshController?.params.settings.toMap() ??
_windowsParams.pullToRefreshController?.params.options.toMap() ??
PullToRefreshSettings(enabled: false).toMap();
Map<String, dynamic> args = <String, dynamic>{};
@ -341,8 +341,14 @@ class WindowsHeadlessInAppWebView extends PlatformHeadlessInAppWebView
'pullToRefreshSettings': pullToRefreshSettings,
'initialSize': params.initialSize.toMap()
});
try {
await _sharedChannel.invokeMethod('run', args);
_running = true;
} catch (e) {
_running = false;
_started = false;
throw e;
}
}
void _inferInitialSettings(InAppWebViewSettings settings) {
@ -420,8 +426,8 @@ class WindowsHeadlessInAppWebView extends PlatformHeadlessInAppWebView
_webViewController?.dispose();
_webViewController = null;
_controllerFromPlatform = null;
_macosParams.pullToRefreshController?.dispose();
_macosParams.findInteractionController?.dispose();
_windowsParams.pullToRefreshController?.dispose();
_windowsParams.findInteractionController?.dispose();
}
}

View File

@ -3,6 +3,7 @@ import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_pla
import 'in_app_browser/in_app_browser.dart';
import 'in_app_webview/in_app_webview.dart';
import 'in_app_webview/in_app_webview_controller.dart';
import 'in_app_webview/headless_in_app_webview.dart';
/// Implementation of [InAppWebViewPlatform] using the WebKit API.
class WindowsInAppWebViewPlatform extends InAppWebViewPlatform {
@ -61,4 +62,15 @@ class WindowsInAppWebViewPlatform extends InAppWebViewPlatform {
WindowsInAppBrowser createPlatformInAppBrowserStatic() {
return WindowsInAppBrowser.static();
}
/// Creates a new [WindowsHeadlessInAppWebView].
///
/// This function should only be called by the app-facing package.
/// Look at using [HeadlessInAppWebView] in `flutter_inappwebview` instead.
@override
WindowsHeadlessInAppWebView createPlatformHeadlessInAppWebView(
PlatformHeadlessInAppWebViewCreationParams params,
) {
return WindowsHeadlessInAppWebView(params);
}
}

View File

@ -70,6 +70,8 @@ list(APPEND PLUGIN_SOURCES
"types/user_script.h"
"types/plugin_script.cpp"
"types/plugin_script.h"
"types/size_2d.cpp"
"types/size_2d.h"
"custom_platform_view/custom_platform_view.cc"
"custom_platform_view/custom_platform_view.h"
"custom_platform_view/texture_bridge.cc"
@ -96,6 +98,12 @@ list(APPEND PLUGIN_SOURCES
"in_app_webview/in_app_webview_manager.h"
"in_app_webview/webview_channel_delegate.cpp"
"in_app_webview/webview_channel_delegate.h"
"headless_in_app_webview/headless_in_app_webview.cpp"
"headless_in_app_webview/headless_in_app_webview.h"
"headless_in_app_webview/headless_in_app_webview_manager.cpp"
"headless_in_app_webview/headless_in_app_webview_manager.h"
"headless_in_app_webview/headless_webview_channel_delegate.cpp"
"headless_in_app_webview/headless_webview_channel_delegate.h"
"in_app_browser/in_app_browser_settings.cpp"
"in_app_browser/in_app_browser_settings.h"
"in_app_browser/in_app_browser_manager.cpp"

View File

@ -1,8 +1,8 @@
#include "flutter_inappwebview_windows_plugin.h"
#include <flutter/method_channel.h>
#include <flutter/plugin_registrar_windows.h>
#include "headless_in_app_webview/headless_in_app_webview_manager.h"
#include "in_app_browser/in_app_browser_manager.h"
#include "in_app_webview/in_app_webview_manager.h"
@ -25,6 +25,7 @@ namespace flutter_inappwebview_plugin
{
inAppWebViewManager = std::make_unique<InAppWebViewManager>(this);
inAppBrowserManager = std::make_unique<InAppBrowserManager>(this);
headlessInAppWebViewManager = std::make_unique<HeadlessInAppWebViewManager>(this);
}
FlutterInappwebviewWindowsPlugin::~FlutterInappwebviewWindowsPlugin()

View File

@ -8,12 +8,14 @@ namespace flutter_inappwebview_plugin
{
class InAppWebViewManager;
class InAppBrowserManager;
class HeadlessInAppWebViewManager;
class FlutterInappwebviewWindowsPlugin : public flutter::Plugin {
public:
flutter::PluginRegistrarWindows* registrar;
std::unique_ptr<InAppWebViewManager> inAppWebViewManager;
std::unique_ptr<InAppBrowserManager> inAppBrowserManager;
std::unique_ptr<HeadlessInAppWebViewManager> headlessInAppWebViewManager;
static void RegisterWithRegistrar(flutter::PluginRegistrarWindows* registrar);

View File

@ -0,0 +1,59 @@
#include "../utils/log.h"
#include "headless_in_app_webview.h"
namespace flutter_inappwebview_plugin
{
using namespace Microsoft::WRL;
HeadlessInAppWebView::HeadlessInAppWebView(const FlutterInappwebviewWindowsPlugin* plugin, const HeadlessInAppWebViewCreationParams& params, const HWND parentWindow, std::unique_ptr<InAppWebView> webView)
: plugin(plugin), id(params.id),
webView(std::move(webView)),
channelDelegate(std::make_unique<HeadlessWebViewChannelDelegate>(this, plugin->registrar->messenger()))
{
prepare(params);
ShowWindow(parentWindow, SW_HIDE);
}
void HeadlessInAppWebView::prepare(const HeadlessInAppWebViewCreationParams& params)
{
if (!webView) {
return;
}
webView->webViewController->put_IsVisible(false);
}
void HeadlessInAppWebView::setSize(const std::shared_ptr<Size2D> size) const
{
if (!webView) {
return;
}
RECT rect = {
0, 0, (LONG)size->width, (LONG)size->height
};
webView->webViewController->put_Bounds(rect);
}
std::shared_ptr<Size2D> HeadlessInAppWebView::getSize() const
{
if (!webView) {
return std::make_shared<Size2D>(-1.0, -1.0);
}
RECT rect;
webView->webViewController->get_Bounds(&rect);
auto width = rect.right - rect.left;
auto height = rect.bottom - rect.top;
return std::make_shared<Size2D>((double)width, (double)height);
}
HeadlessInAppWebView::~HeadlessInAppWebView()
{
debugLog("dealloc HeadlessInAppWebView");
if (webView && webView->webViewController) {
HWND parentWindow;
if (succeededOrLog(webView->webViewController->get_ParentWindow(&parentWindow))) {
DestroyWindow(parentWindow);
}
}
webView = nullptr;
}
}

View File

@ -0,0 +1,35 @@
#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_HEADLESS_IN_APP_WEBVIEW_H_
#define FLUTTER_INAPPWEBVIEW_PLUGIN_HEADLESS_IN_APP_WEBVIEW_H_
#include "../flutter_inappwebview_windows_plugin.h"
#include "../in_app_webview/in_app_webview.h"
#include "../types/size_2d.h"
#include "headless_webview_channel_delegate.h"
namespace flutter_inappwebview_plugin
{
struct HeadlessInAppWebViewCreationParams {
const std::string id;
const std::shared_ptr<Size2D> initialSize;
};
class HeadlessInAppWebView
{
public:
static inline const wchar_t* CLASS_NAME = L"HeadlessInAppWebView";
static inline const std::string METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_headless_inappwebview_";
const FlutterInappwebviewWindowsPlugin* plugin;
std::string id;
std::unique_ptr<InAppWebView> webView;
std::unique_ptr<HeadlessWebViewChannelDelegate> channelDelegate;
HeadlessInAppWebView(const FlutterInappwebviewWindowsPlugin* plugin, const HeadlessInAppWebViewCreationParams& params, const HWND parentWindow, std::unique_ptr<InAppWebView> webView);
~HeadlessInAppWebView();
void prepare(const HeadlessInAppWebViewCreationParams& params);
void setSize(const std::shared_ptr<Size2D> size) const;
std::shared_ptr<Size2D> getSize() const;
};
}
#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_HEADLESS_IN_APP_WEBVIEW_H_

View File

@ -0,0 +1,136 @@
#include <DispatcherQueue.h>
#include <flutter/method_channel.h>
#include <flutter/standard_method_codec.h>
#include <shlobj.h>
#include <windows.graphics.capture.h>
#include "../in_app_webview/in_app_webview_settings.h"
#include "../types/size_2d.h"
#include "../types/url_request.h"
#include "../types/user_script.h"
#include "../utils/flutter.h"
#include "../utils/log.h"
#include "../utils/string.h"
#include "../utils/vector.h"
#include "headless_in_app_webview_manager.h"
namespace flutter_inappwebview_plugin
{
HeadlessInAppWebViewManager::HeadlessInAppWebViewManager(const FlutterInappwebviewWindowsPlugin* plugin)
: plugin(plugin),
ChannelDelegate(plugin->registrar->messenger(), HeadlessInAppWebViewManager::METHOD_CHANNEL_NAME)
{
windowClass_.lpszClassName = HeadlessInAppWebView::CLASS_NAME;
windowClass_.lpfnWndProc = &DefWindowProc;
RegisterClass(&windowClass_);
}
void HeadlessInAppWebViewManager::HandleMethodCall(const flutter::MethodCall<flutter::EncodableValue>& method_call,
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result)
{
auto* arguments = std::get_if<flutter::EncodableMap>(method_call.arguments());
auto& methodName = method_call.method_name();
if (string_equals(methodName, "run")) {
run(arguments, std::move(result));
}
else {
result->NotImplemented();
}
}
void HeadlessInAppWebViewManager::run(const flutter::EncodableMap* arguments, std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result)
{
auto result_ = std::shared_ptr<flutter::MethodResult<flutter::EncodableValue>>(std::move(result));
auto id = get_fl_map_value<std::string>(*arguments, "id");
auto params = get_fl_map_value<flutter::EncodableMap>(*arguments, "params");
auto initialSize = std::make_shared<Size2D>(get_fl_map_value<flutter::EncodableMap>(params, "initialSize"));
auto settingsMap = get_fl_map_value<flutter::EncodableMap>(params, "initialSettings");
auto urlRequestMap = get_optional_fl_map_value<flutter::EncodableMap>(params, "initialUrlRequest");
auto initialFile = get_optional_fl_map_value<std::string>(params, "initialFile");
auto initialDataMap = get_optional_fl_map_value<flutter::EncodableMap>(params, "initialData");
auto initialUserScriptList = get_optional_fl_map_value<flutter::EncodableList>(params, "initialUserScripts");
RECT bounds;
GetClientRect(plugin->registrar->GetView()->GetNativeWindow(), &bounds);
auto initialWidth = initialSize->width >= 0 ? initialSize->width : bounds.right - bounds.left;
auto initialHeight = initialSize->height >= 0 ? initialSize->height : bounds.bottom - bounds.top;
auto hwnd = CreateWindowEx(0, windowClass_.lpszClassName, L"", 0, 0,
0, (int)initialWidth, (int)initialHeight,
plugin->registrar->GetView()->GetNativeWindow(),
nullptr,
windowClass_.hInstance, nullptr);
InAppWebView::createInAppWebViewEnv(hwnd, false,
[=](wil::com_ptr<ICoreWebView2Environment> webViewEnv,
wil::com_ptr<ICoreWebView2Controller> webViewController,
wil::com_ptr<ICoreWebView2CompositionController> webViewCompositionController)
{
if (webViewEnv && webViewController) {
auto initialSettings = std::make_unique<InAppWebViewSettings>(settingsMap);
std::optional<std::vector<std::shared_ptr<UserScript>>> initialUserScripts = initialUserScriptList.has_value() ?
functional_map(initialUserScriptList.value(), [](const flutter::EncodableValue& map) { return std::make_shared<UserScript>(std::get<flutter::EncodableMap>(map)); }) :
std::optional<std::vector<std::shared_ptr<UserScript>>>{};
InAppWebViewCreationParams params = {
id,
std::move(initialSettings),
initialUserScripts
};
auto inAppWebView = std::make_unique<InAppWebView>(plugin, params, hwnd,
std::move(webViewEnv), std::move(webViewController), nullptr
);
HeadlessInAppWebViewCreationParams headlessParams = {
id,
std::move(initialSize)
};
auto headlessInAppWebView = std::make_unique<HeadlessInAppWebView>(plugin,
headlessParams,
hwnd,
std::move(inAppWebView));
headlessInAppWebView->webView->initChannel(std::nullopt, std::nullopt);
if (headlessInAppWebView->channelDelegate) {
headlessInAppWebView->channelDelegate->onWebViewCreated();
}
std::optional<std::shared_ptr<URLRequest>> urlRequest = urlRequestMap.has_value() ? std::make_shared<URLRequest>(urlRequestMap.value()) : std::optional<std::shared_ptr<URLRequest>>{};
if (urlRequest.has_value()) {
headlessInAppWebView->webView->loadUrl(urlRequest.value());
}
else if (initialFile.has_value()) {
headlessInAppWebView->webView->loadFile(initialFile.value());
}
else if (initialDataMap.has_value()) {
headlessInAppWebView->webView->loadData(get_fl_map_value<std::string>(initialDataMap.value(), "data"));
}
webViews.insert({ id, std::move(headlessInAppWebView) });
result_->Success(true);
}
else {
result_->Error("0", "Cannot create the HeadlessInAppWebView instance!");
}
}
);
}
HeadlessInAppWebViewManager::~HeadlessInAppWebViewManager()
{
debugLog("dealloc HeadlessInAppWebViewManager");
webViews.clear();
UnregisterClass(windowClass_.lpszClassName, nullptr);
plugin = nullptr;
}
}

View File

@ -0,0 +1,36 @@
#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_HEADLESS_IN_APP_WEBVIEW_MANAGER_H_
#define FLUTTER_INAPPWEBVIEW_PLUGIN_HEADLESS_IN_APP_WEBVIEW_MANAGER_H_
#include <flutter/method_channel.h>
#include <map>
#include <string>
#include <wil/com.h>
#include <winrt/base.h>
#include "../flutter_inappwebview_windows_plugin.h"
#include "../types/channel_delegate.h"
#include "headless_in_app_webview.h"
namespace flutter_inappwebview_plugin
{
class HeadlessInAppWebViewManager : public ChannelDelegate
{
public:
static inline const std::string METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_headless_inappwebview";
const FlutterInappwebviewWindowsPlugin* plugin;
std::map<std::string, std::unique_ptr<HeadlessInAppWebView>> webViews;
HeadlessInAppWebViewManager(const FlutterInappwebviewWindowsPlugin* plugin);
~HeadlessInAppWebViewManager();
void HandleMethodCall(
const flutter::MethodCall<flutter::EncodableValue>& method_call,
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result);
void run(const flutter::EncodableMap* arguments, std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result);
private:
WNDCLASS windowClass_ = {};
};
}
#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_HEADLESS_IN_APP_WEBVIEW_MANAGER_H_

View File

@ -0,0 +1,56 @@
#include "../utils/flutter.h"
#include "../utils/log.h"
#include "../utils/strconv.h"
#include "../utils/string.h"
#include "headless_in_app_webview.h"
#include "headless_webview_channel_delegate.h"
#include "headless_in_app_webview_manager.h"
namespace flutter_inappwebview_plugin
{
HeadlessWebViewChannelDelegate::HeadlessWebViewChannelDelegate(HeadlessInAppWebView* webView, flutter::BinaryMessenger* messenger)
: webView(webView), ChannelDelegate(messenger, HeadlessInAppWebView::METHOD_CHANNEL_NAME_PREFIX + variant_to_string(webView->id))
{}
void HeadlessWebViewChannelDelegate::HandleMethodCall(const flutter::MethodCall<flutter::EncodableValue>& method_call,
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result)
{
if (!webView) {
result->Success();
return;
}
// auto& arguments = std::get<flutter::EncodableMap>(*method_call.arguments());
auto& methodName = method_call.method_name();
if (string_equals(methodName, "dispose")) {
if (webView->plugin && webView->plugin->headlessInAppWebViewManager) {
std::map<std::string, std::unique_ptr<HeadlessInAppWebView>>& webViews = webView->plugin->headlessInAppWebViewManager->webViews;
auto& id = webView->id;
if (map_contains(webViews, id)) {
webViews.erase(id);
}
}
result->Success();
}
else {
result->NotImplemented();
}
}
void HeadlessWebViewChannelDelegate::onWebViewCreated() const
{
if (!channel) {
return;
}
channel->InvokeMethod("onWebViewCreated", nullptr);
}
HeadlessWebViewChannelDelegate::~HeadlessWebViewChannelDelegate()
{
debugLog("dealloc HeadlessWebViewChannelDelegate");
webView = nullptr;
}
}

View File

@ -0,0 +1,27 @@
#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_HEADLESS_WEBVIEW_CHANNEL_DELEGATE_H_
#define FLUTTER_INAPPWEBVIEW_PLUGIN_HEADLESS_WEBVIEW_CHANNEL_DELEGATE_H_
#include "../types/channel_delegate.h"
#include <flutter/method_channel.h>
namespace flutter_inappwebview_plugin
{
class HeadlessInAppWebView;
class HeadlessWebViewChannelDelegate : public ChannelDelegate
{
public:
HeadlessInAppWebView* webView;
HeadlessWebViewChannelDelegate(HeadlessInAppWebView* webView, flutter::BinaryMessenger* messenger);
~HeadlessWebViewChannelDelegate();
void HandleMethodCall(
const flutter::MethodCall<flutter::EncodableValue>& method_call,
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result);
void onWebViewCreated() const;
};
}
#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_HEADLESS_WEBVIEW_CHANNEL_DELEGATE_H_

View File

@ -274,7 +274,7 @@ namespace flutter_inappwebview_plugin
navigationActions_.insert({ navigationId, navigationAction });
}
if (callShouldOverrideUrlLoading_ && requestMethod == nullptr) {
if (settings->useShouldOverrideUrlLoading && callShouldOverrideUrlLoading_ && requestMethod == nullptr) {
// for some reason, we can't cancel and load an URL with other HTTP methods than GET,
// so ignore the shouldOverrideUrlLoading event.
@ -1187,21 +1187,20 @@ namespace flutter_inappwebview_plugin
InAppWebView::~InAppWebView()
{
debugLog("dealloc InAppWebView");
HWND parentWindow;
webViewController->get_ParentWindow(&parentWindow);
if (webView) {
webView->Stop();
}
if (webViewController) {
webViewController->Close();
}
navigationActions_.clear();
inAppBrowser = nullptr;
plugin = nullptr;
if (webViewCompositionController) {
HWND parentWindow = nullptr;
if (webViewCompositionController && succeededOrLog(webViewController->get_ParentWindow(&parentWindow))) {
// if it's an InAppWebView,
// then destroy the Window created with it
DestroyWindow(parentWindow);
}
if (webView) {
failedLog(webView->Stop());
}
if (webViewController) {
failedLog(webViewController->Close());
}
navigationActions_.clear();
inAppBrowser = nullptr;
plugin = nullptr;
}
}

View File

@ -22,7 +22,7 @@ namespace flutter_inappwebview_plugin
{
decodeResult = [](const flutter::EncodableValue* value)
{
if (value->IsNull()) {
if (!value || value->IsNull()) {
return NavigationActionPolicy::cancel;
}
auto navigationPolicy = std::get<int>(*value);

View File

@ -0,0 +1,21 @@
#include "size_2d.h"
namespace flutter_inappwebview_plugin
{
Size2D::Size2D(const double& width, const double& height)
: width(width), height(height)
{}
Size2D::Size2D(const flutter::EncodableMap& map)
: width(get_fl_map_value<double>(map, "width")),
height(get_fl_map_value<double>(map, "height"))
{}
flutter::EncodableMap Size2D::toEncodableMap() const
{
return flutter::EncodableMap{
{"width", width},
{"height", height}
};
}
}

View File

@ -0,0 +1,25 @@
#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_SIZE_2D_H_
#define FLUTTER_INAPPWEBVIEW_PLUGIN_SIZE_2D_H_
#include <flutter/standard_method_codec.h>
#include <optional>
#include "../utils/flutter.h"
namespace flutter_inappwebview_plugin
{
class Size2D
{
public:
const double width;
const double height;
Size2D(const double& width, const double& height);
Size2D(const flutter::EncodableMap& map);
~Size2D() = default;
flutter::EncodableMap toEncodableMap() const;
};
}
#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_SIZE_2D_H_