windows: fixed javascript evaluation, added callAsyncJavaScript implementation, call DestroyWindow(parentWindow) on InAppWebView dealloc

This commit is contained in:
unknown 2024-01-23 16:01:09 +01:00
parent e4bfd68313
commit ab869e9703
10 changed files with 126 additions and 97 deletions

View File

@ -521,7 +521,7 @@ abstract class PlatformInAppWebViewController extends PlatformInterface
///- iOS ([Official API - WKWebView.evaluateJavascript](https://developer.apple.com/documentation/webkit/wkwebview/3656442-evaluatejavascript))
///- MacOS ([Official API - WKWebView.evaluateJavascript](https://developer.apple.com/documentation/webkit/wkwebview/3656442-evaluatejavascript))
///- Web ([Official API - Window.eval](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval?retiredLocale=it))
///- Windows ([Official API - ICoreWebView2.ExecuteScript](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2?view=webview2-1.0.2210.55#executescript))
///- Windows
///{@endtemplate}
Future<dynamic> evaluateJavascript(
{required String source, ContentWorld? contentWorld}) {
@ -1342,6 +1342,7 @@ abstract class PlatformInAppWebViewController extends PlatformInterface
///- Android native WebView
///- iOS ([Official API - WKWebView.callAsyncJavaScript](https://developer.apple.com/documentation/webkit/wkwebview/3656441-callasyncjavascript))
///- MacOS ([Official API - WKWebView.callAsyncJavaScript](https://developer.apple.com/documentation/webkit/wkwebview/3656441-callasyncjavascript))
///- Windows
///{@endtemplate}
Future<CallAsyncJavaScriptResult?> callAsyncJavaScript(
{required String functionBody,

View File

@ -2428,12 +2428,13 @@ class WindowsInAppWebViewController extends PlatformInAppWebViewController
ContentWorld? contentWorld}) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('functionBody', () => functionBody);
args.putIfAbsent('arguments', () => arguments);
args.putIfAbsent('arguments', () => jsonEncode(arguments));
args.putIfAbsent('contentWorld', () => contentWorld?.toMap());
var data = await channel?.invokeMethod('callAsyncJavaScript', args);
if (data == null) {
return null;
}
data = json.decode(data);
return CallAsyncJavaScriptResult(
value: data["value"], error: data["error"]);
}

View File

@ -73,96 +73,6 @@ namespace flutter_inappwebview_plugin
close();
}
});
// <-- WebView2 sample code starts here -->
// Step 3 - Create a single WebView within the parent window
// Locate the browser and set up the environment for WebView
//CreateCoreWebView2EnvironmentWithOptions(nullptr, nullptr, nullptr,
// Callback<ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler>(
// [hWnd = m_hWnd, inAppBrowser = this](HRESULT result, ICoreWebView2Environment* env) -> HRESULT {
// // Create a CoreWebView2Controller and get the associated CoreWebView2 whose parent is the main window hWnd
// env->CreateCoreWebView2Controller(hWnd, Callback<ICoreWebView2CreateCoreWebView2ControllerCompletedHandler>(
// [hWnd, inAppBrowser](HRESULT result, ICoreWebView2Controller* controller) -> HRESULT {
// if (controller != nullptr) {
// inAppBrowser->webviewController = controller;
// inAppBrowser->webviewController->get_CoreWebView2(&inAppBrowser->webview);
// }
// // Add a few settings for the webview
// // The demo step is redundant since the values are the default settings
// wil::com_ptr<ICoreWebView2Settings> settings;
// inAppBrowser->webview->get_Settings(&settings);
// settings->put_IsScriptEnabled(TRUE);
// settings->put_AreDefaultScriptDialogsEnabled(TRUE);
// settings->put_IsWebMessageEnabled(TRUE);
// // Resize WebView to fit the bounds of the parent window
// RECT bounds;
// GetClientRect(hWnd, &bounds);
// inAppBrowser->webviewController->put_Bounds(bounds);
// auto url = inAppBrowser->initialUrlRequest.value().url.value();
// std::wstring stemp = ansi_to_wide(url);
// // Schedule an async task to navigate to Bing
// inAppBrowser->webview->Navigate(stemp.c_str());
// // <NavigationEvents>
// // Step 4 - Navigation events
// // register an ICoreWebView2NavigationStartingEventHandler to cancel any non-https navigation
// EventRegistrationToken token;
// inAppBrowser->webview->add_NavigationStarting(Callback<ICoreWebView2NavigationStartingEventHandler>(
// [](ICoreWebView2* webview, ICoreWebView2NavigationStartingEventArgs* args) -> HRESULT {
// wil::unique_cotaskmem_string uri;
// args->get_Uri(&uri);
// std::wstring source(uri.get());
// if (source.substr(0, 5) != L"https") {
// args->put_Cancel(true);
// }
// return S_OK;
// }).Get(), &token);
// // </NavigationEvents>
// // <Scripting>
// // Step 5 - Scripting
// // Schedule an async task to add initialization script that freezes the Object object
// inAppBrowser->webview->AddScriptToExecuteOnDocumentCreated(L"Object.freeze(Object);", nullptr);
// // Schedule an async task to get the document URL
// inAppBrowser->webview->ExecuteScript(L"window.document.URL;", Callback<ICoreWebView2ExecuteScriptCompletedHandler>(
// [](HRESULT errorCode, LPCWSTR resultObjectAsJson) -> HRESULT {
// LPCWSTR URL = resultObjectAsJson;
// OutputDebugStringW(URL);
// //doSomethingWithURL(URL);
// return S_OK;
// }).Get());
// // </Scripting>
// // <CommunicationHostWeb>
// // Step 6 - Communication between host and web content
// // Set an event handler for the host to return received message back to the web content
// inAppBrowser->webview->add_WebMessageReceived(Callback<ICoreWebView2WebMessageReceivedEventHandler>(
// [](ICoreWebView2* webview, ICoreWebView2WebMessageReceivedEventArgs* args) -> HRESULT {
// wil::unique_cotaskmem_string message;
// args->TryGetWebMessageAsString(&message);
// // processMessage(&message);
// webview->PostWebMessageAsString(message.get());
// return S_OK;
// }).Get(), &token);
// // Schedule an async task to add initialization script that
// // 1) Add an listener to print message from the host
// // 2) Post document URL to the host
// inAppBrowser->webview->AddScriptToExecuteOnDocumentCreated(
// L"window.chrome.webview.addEventListener(\'message\', event => alert(event.data));" \
// L"window.chrome.webview.postMessage(window.document.URL);",
// nullptr);
// // </CommunicationHostWeb>
// return S_OK;
// }).Get());
// return S_OK;
// }).Get());
}
void InAppBrowser::close() const

View File

@ -785,12 +785,94 @@ namespace flutter_inappwebview_plugin
auto hr = webView->CallDevToolsProtocolMethod(L"Runtime.evaluate", utf8_to_wide(parameters.dump()).c_str(), Callback<ICoreWebView2CallDevToolsProtocolMethodCompletedHandler>(
[this, completionHandler](HRESULT errorCode, LPCWSTR returnObjectAsJson)
{
std::string result = "null";
nlohmann::json result;
if (succeededOrLog(errorCode)) {
result = wide_to_utf8(returnObjectAsJson);
nlohmann::json json = nlohmann::json::parse(wide_to_utf8(returnObjectAsJson));
result = json["result"].contains("value") ? json["result"]["value"] : nlohmann::json{};
if (json.contains("exceptionDetails")) {
nlohmann::json exceptionDetails = json["exceptionDetails"];
auto errorMessage = exceptionDetails.contains("exception") && exceptionDetails["exception"].contains("value")
? exceptionDetails["exception"]["value"].dump() :
(result["value"].is_null() ? exceptionDetails["text"].get<std::string>() : result["value"].dump());
result = nlohmann::json{};
debugLog(exceptionDetails.dump());
if (channelDelegate) {
channelDelegate->onConsoleMessage(errorMessage, 3);
}
}
}
if (completionHandler) {
completionHandler(result);
completionHandler(result.dump());
}
return S_OK;
}
).Get());
if (failedAndLog(hr) && completionHandler) {
completionHandler("null");
}
});
}
void InAppWebView::callAsyncJavaScript(const std::string& functionBody, const std::string& argumentsAsJson, const std::shared_ptr<ContentWorld> contentWorld, const std::function<void(std::string)> completionHandler) const
{
if (!webView || !userContentController) {
if (completionHandler) {
completionHandler("null");
}
return;
}
userContentController->createContentWorld(contentWorld,
[=](const int& contextId)
{
std::vector<std::string> functionArgumentNamesList;
std::vector<std::string> functionArgumentValuesList;
auto jsonVal = nlohmann::json::parse(argumentsAsJson);
for (auto const& [key, val] : jsonVal.items()) {
functionArgumentNamesList.push_back(key);
functionArgumentValuesList.push_back(val.dump());
}
auto source = replace_all_copy(CALL_ASYNC_JAVASCRIPT_WRAPPER_JS, VAR_FUNCTION_ARGUMENT_NAMES, join(functionArgumentNamesList, ", "));
replace_all(source, VAR_FUNCTION_ARGUMENT_VALUES, join(functionArgumentValuesList, ", "));
replace_all(source, VAR_FUNCTION_BODY, functionBody);
nlohmann::json parameters = {
{"expression", source},
{"awaitPromise", true}
};
if (contextId >= 0) {
parameters["contextId"] = contextId;
}
auto hr = webView->CallDevToolsProtocolMethod(L"Runtime.evaluate", utf8_to_wide(parameters.dump()).c_str(), Callback<ICoreWebView2CallDevToolsProtocolMethodCompletedHandler>(
[this, completionHandler](HRESULT errorCode, LPCWSTR returnObjectAsJson)
{
nlohmann::json result = {
{"value", nlohmann::json{}},
{"error", nlohmann::json{}}
};
if (succeededOrLog(errorCode)) {
nlohmann::json json = nlohmann::json::parse(wide_to_utf8(returnObjectAsJson));
result["value"] = json["result"].contains("value") ? json["result"]["value"] : nlohmann::json{};
if (json.contains("exceptionDetails")) {
nlohmann::json exceptionDetails = json["exceptionDetails"];
auto errorMessage = exceptionDetails.contains("exception") && exceptionDetails["exception"].contains("value")
? exceptionDetails["exception"]["value"].dump() :
(result["value"].is_null() ? exceptionDetails["text"].get<std::string>() : result["value"].dump());
result["value"] = nlohmann::json{};
result["error"] = errorMessage;
debugLog(exceptionDetails.dump());
if (channelDelegate) {
channelDelegate->onConsoleMessage(errorMessage, 3);
}
}
}
if (completionHandler) {
completionHandler(result.dump());
}
return S_OK;
}
@ -1105,6 +1187,8 @@ namespace flutter_inappwebview_plugin
InAppWebView::~InAppWebView()
{
debugLog("dealloc InAppWebView");
HWND parentWindow;
webViewController->get_ParentWindow(&parentWindow);
if (webView) {
webView->Stop();
}
@ -1114,5 +1198,10 @@ namespace flutter_inappwebview_plugin
navigationActions_.clear();
inAppBrowser = nullptr;
plugin = nullptr;
if (webViewCompositionController) {
// if it's an InAppWebView,
// then destroy the Window created with it
DestroyWindow(parentWindow);
}
}
}

View File

@ -9,6 +9,7 @@
#include <winrt/base.h>
#include "../flutter_inappwebview_windows_plugin.h"
#include "../plugin_scripts_js/plugin_scripts_util.h"
#include "../types/content_world.h"
#include "../types/navigation_action.h"
#include "../types/url_request.h"
@ -70,6 +71,10 @@ namespace flutter_inappwebview_plugin
}
};
const std::string CALL_ASYNC_JAVASCRIPT_WRAPPER_JS = "(async function(" + VAR_FUNCTION_ARGUMENT_NAMES + ") { \
" + VAR_FUNCTION_BODY + " \
})(" + VAR_FUNCTION_ARGUMENT_VALUES + ");";
struct InAppWebViewCreationParams {
const std::variant<std::string, int64_t> id;
const std::shared_ptr<InAppWebViewSettings> initialSettings;
@ -150,6 +155,7 @@ namespace flutter_inappwebview_plugin
}
void stopLoading() const;
void evaluateJavascript(const std::string& source, const std::shared_ptr<ContentWorld> contentWorld, const std::function<void(std::string)> completionHandler) const;
void callAsyncJavaScript(const std::string& functionBody, const std::string& argumentsAsJson, const std::shared_ptr<ContentWorld> contentWorld, const std::function<void(std::string)> completionHandler) const;
void getCopyBackForwardList(const std::function<void(std::unique_ptr<WebHistory>)> completionHandler) const;
void addUserScript(const std::shared_ptr<UserScript> userScript) const;
void removeUserScript(const int64_t index, const std::shared_ptr<UserScript> userScript) const;

View File

@ -89,7 +89,7 @@ namespace flutter_inappwebview_plugin
auto hwnd = CreateWindowEx(0, windowClass_.lpszClassName, L"", 0, 0,
0, bounds.right - bounds.left, bounds.bottom - bounds.top,
plugin->registrar->GetView()->GetNativeWindow(), // HWND_MESSAGE,
plugin->registrar->GetView()->GetNativeWindow(),
nullptr,
windowClass_.hInstance, nullptr);

View File

@ -120,6 +120,18 @@ namespace flutter_inappwebview_plugin
result_->Success(value);
});
}
else if (string_equals(methodName, "callAsyncJavaScript")) {
auto result_ = std::shared_ptr<flutter::MethodResult<flutter::EncodableValue>>(std::move(result));
auto functionBody = get_fl_map_value<std::string>(arguments, "functionBody");
auto argumentsAsJson = get_fl_map_value<std::string>(arguments, "arguments");
auto contentWorldMap = get_optional_fl_map_value<flutter::EncodableMap>(arguments, "contentWorld");
std::shared_ptr<ContentWorld> contentWorld = contentWorldMap.has_value() ? std::make_shared<ContentWorld>(contentWorldMap.value()) : ContentWorld::page();
webView->callAsyncJavaScript(functionBody, argumentsAsJson, std::move(contentWorld), [result_ = std::move(result_)](const std::string& value)
{
result_->Success(value);
});
}
else if (string_equals(methodName, "getCopyBackForwardList")) {
auto result_ = std::shared_ptr<flutter::MethodResult<flutter::EncodableValue>>(std::move(result));
webView->getCopyBackForwardList([result_ = std::move(result_)](const std::unique_ptr<WebHistory> value)

View File

@ -7,6 +7,9 @@ namespace flutter_inappwebview_plugin
{
const std::string VAR_PLACEHOLDER_VALUE = "$IN_APP_WEBVIEW_PLACEHOLDER_VALUE";
const std::string VAR_PLACEHOLDER_MEMORY_ADDRESS_VALUE = "$IN_APP_WEBVIEW_PLACEHOLDER_MEMORY_ADDRESS_VALUE";
const std::string VAR_FUNCTION_ARGUMENT_NAMES = "$IN_APP_WEBVIEW_FUNCTION_ARGUMENT_NAMES";
const std::string VAR_FUNCTION_ARGUMENT_VALUES = "$IN_APP_WEBVIEW_FUNCTION_ARGUMENT_VALUES";
const std::string VAR_FUNCTION_BODY = "$IN_APP_WEBVIEW_FUNCTION_BODY";
}
#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_PLUGIN_SCRIPTS_UTIL_H_

View File

@ -63,7 +63,8 @@ namespace flutter_inappwebview_plugin
static inline void debugLog(const HRESULT& hr, const std::string& filename = "", const int& line = 0)
{
auto isError = hr != S_OK;
debugLog((isError ? "Error: " : "Message: ") + getHRMessage(hr), isError, filename, line);
auto errorCode = std::to_string(hr);
debugLog((isError ? "Error " + errorCode + ": " : "Message: ") + getHRMessage(hr), isError, filename, line);
}
static inline bool succeededOrLog(const HRESULT& hr, const std::string& filename = "", const int& line = 0)

View File

@ -120,6 +120,12 @@ namespace flutter_inappwebview_plugin
[delim](auto& a, auto& b) { return a + delim + b; });
}
template <typename T>
static inline std::basic_string<T> join(const std::vector<std::basic_string<T>>& vec, const char* delim)
{
return join(vec, std::basic_string<T>{ delim });
}
template <typename T>
static inline std::vector<std::basic_string<T>> split(const std::basic_string<T>& s, std::basic_string<T> delimiter)
{