diff --git a/flutter_inappwebview_platform_interface/lib/src/in_app_webview/platform_inappwebview_controller.dart b/flutter_inappwebview_platform_interface/lib/src/in_app_webview/platform_inappwebview_controller.dart index a8f2d9ba..9bde75a2 100644 --- a/flutter_inappwebview_platform_interface/lib/src/in_app_webview/platform_inappwebview_controller.dart +++ b/flutter_inappwebview_platform_interface/lib/src/in_app_webview/platform_inappwebview_controller.dart @@ -783,6 +783,7 @@ abstract class PlatformInAppWebViewController extends PlatformInterface ///- Android native WebView ([Official API - WebView.copyBackForwardList](https://developer.android.com/reference/android/webkit/WebView#copyBackForwardList())) ///- iOS ([Official API - WKWebView.backForwardList](https://developer.apple.com/documentation/webkit/wkwebview/1414977-backforwardlist)) ///- MacOS ([Official API - WKWebView.backForwardList](https://developer.apple.com/documentation/webkit/wkwebview/1414977-backforwardlist)) + ///- Windows ///{@endtemplate} Future getCopyBackForwardList() { throw UnimplementedError( diff --git a/flutter_inappwebview_windows/windows/CMakeLists.txt b/flutter_inappwebview_windows/windows/CMakeLists.txt index e662976f..4ccf72af 100644 --- a/flutter_inappwebview_windows/windows/CMakeLists.txt +++ b/flutter_inappwebview_windows/windows/CMakeLists.txt @@ -6,6 +6,7 @@ cmake_minimum_required(VERSION 3.14) set(WIL_VERSION "1.0.231216.1") set(WEBVIEW_VERSION "1.0.2210.55") +set(NLOHMANN_JSON "3.11.2") message(VERBOSE "CMake system version is ${CMAKE_SYSTEM_VERSION} (using SDK ${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION})") @@ -31,6 +32,7 @@ add_custom_command( TARGET ${PROJECT_NAME}_DEPENDENCIES_DOWNLOAD PRE_BUILD COMMAND ${NUGET} install Microsoft.Windows.ImplementationLibrary -Version ${WIL_VERSION} -ExcludeVersion -OutputDirectory ${CMAKE_BINARY_DIR}/packages COMMAND ${NUGET} install Microsoft.Web.WebView2 -Version ${WEBVIEW_VERSION} -ExcludeVersion -OutputDirectory ${CMAKE_BINARY_DIR}/packages + COMMAND ${NUGET} install nlohmann.json -Version ${NLOHMANN_JSON} -ExcludeVersion -OutputDirectory ${CMAKE_BINARY_DIR}/packages DEPENDS ${NUGET} ) @@ -54,6 +56,10 @@ list(APPEND PLUGIN_SOURCES "types/web_resource_request.h" "types/web_resource_response.cpp" "types/web_resource_response.h" + "types/web_history.cpp" + "types/web_history.h" + "types/web_history_item.cpp" + "types/web_history_item.h" "custom_platform_view/custom_platform_view.cc" "custom_platform_view/custom_platform_view.h" "custom_platform_view/texture_bridge.cc" @@ -123,6 +129,7 @@ apply_standard_settings(${PLUGIN_NAME}) target_link_libraries(${PLUGIN_NAME} PRIVATE ${CMAKE_BINARY_DIR}/packages/Microsoft.Web.WebView2/build/native/Microsoft.Web.WebView2.targets) target_link_libraries(${PLUGIN_NAME} PRIVATE ${CMAKE_BINARY_DIR}/packages/Microsoft.Windows.ImplementationLibrary/build/native/Microsoft.Windows.ImplementationLibrary.targets) +target_link_libraries(${PLUGIN_NAME} PRIVATE ${CMAKE_BINARY_DIR}/packages/nlohmann.json/build/native/nlohmann.json.targets) # Symbols are hidden by default to reduce the chance of accidental conflicts # between plugins. This should not be removed; any symbols that should be diff --git a/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview.cpp b/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview.cpp index bb66d8f9..85f1077f 100644 --- a/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview.cpp +++ b/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -21,11 +22,15 @@ namespace flutter_inappwebview_plugin : plugin(plugin), id(params.id), webViewEnv(std::move(webViewEnv)), webViewController(std::move(webViewController)), webViewCompositionController(std::move(webViewCompositionController)), settings(params.initialSettings) { - this->webViewController->get_CoreWebView2(webView.put()); + auto hrWebView2 = this->webViewController->get_CoreWebView2(webView.put()); + if (FAILED(hrWebView2)) { + std::cerr << "Cannot create CoreWebView2." << std::endl; + debugLog(getErrorMessage(hrWebView2)); + } if (this->webViewCompositionController) { if (!createSurface(parentWindow, plugin->inAppWebViewManager->compositor())) { - std::cerr << "Cannot create InAppWebView surface\n"; + std::cerr << "Cannot create InAppWebView surface." << std::endl; } registerSurfaceEventHandlers(); } @@ -37,7 +42,8 @@ namespace flutter_inappwebview_plugin } wil::com_ptr webView2Settings; - if (SUCCEEDED(webView->get_Settings(&webView2Settings))) { + auto hrWebView2Settings = webView->get_Settings(&webView2Settings); + if (SUCCEEDED(hrWebView2Settings)) { webView2Settings->put_IsScriptEnabled(settings->javaScriptEnabled); webView2Settings->put_IsZoomControlEnabled(settings->supportZoom); @@ -48,6 +54,9 @@ namespace flutter_inappwebview_plugin } } } + else { + debugLog(getErrorMessage(hrWebView2Settings)); + } registerEventHandlers(); } @@ -182,7 +191,7 @@ namespace flutter_inappwebview_plugin UINT64 navigationId; if (SUCCEEDED(args->get_NavigationId(&navigationId))) { - navigationActions.insert({ navigationId, navigationAction }); callShouldOverrideUrlLoading_ = + navigationActions.insert({ navigationId, navigationAction }); } if (callShouldOverrideUrlLoading_ && requestMethod == nullptr) { @@ -363,16 +372,55 @@ namespace flutter_inappwebview_plugin webView->Reload(); } - void InAppWebView::goBack() const + void InAppWebView::goBack() { + callShouldOverrideUrlLoading_ = false; webView->GoBack(); } - void InAppWebView::goForward() const + void InAppWebView::goForward() { + callShouldOverrideUrlLoading_ = false; webView->GoForward(); } + void InAppWebView::getCopyBackForwardList(const std::function)> completionHandler) const + { + webView->CallDevToolsProtocolMethod(L"Page.getNavigationHistory", L"{}", Callback( + [completionHandler](HRESULT errorCode, LPCWSTR returnObjectAsJson) + { + if (errorCode == S_OK) { + auto historyJson = nlohmann::json::parse(wide_to_ansi(returnObjectAsJson)); + + int64_t currentIndex = historyJson.at("currentIndex").is_number_unsigned() ? historyJson.at("currentIndex").get() : 0; + auto entries = historyJson.at("entries").is_array() ? historyJson.at("entries").get>() : std::vector{}; + + std::vector> webHistoryItems; + webHistoryItems.reserve(entries.size()); + int64_t i = 0; + for (auto const& entry : entries) { + int64_t offset = i - currentIndex; + webHistoryItems.push_back(std::make_shared( + i, + offset, + entry.at("userTypedURL").is_string() ? entry.at("userTypedURL").get() : std::optional{}, + entry.at("title").is_string() ? entry.at("title").get() : std::optional{}, + entry.at("url").is_string() ? entry.at("url").get() : std::optional{} + )); + i++; + } + + completionHandler(std::make_unique(currentIndex, webHistoryItems)); + } + else { + debugLog(getErrorMessage(errorCode)); + } + + return S_OK; + } + ).Get()); + } + void InAppWebView::evaluateJavascript(const std::string& source, std::function completionHanlder) const { webView->ExecuteScript(ansi_to_wide(source).c_str(), diff --git a/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview.h b/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview.h index 61bbca31..0012d1df 100644 --- a/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview.h +++ b/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview.h @@ -11,6 +11,7 @@ #include "../flutter_inappwebview_windows_plugin.h" #include "../types/navigation_action.h" #include "../types/url_request.h" +#include "../types/web_history.h" #include "in_app_webview_settings.h" #include "webview_channel_delegate.h" @@ -130,9 +131,10 @@ namespace flutter_inappwebview_plugin std::optional getTitle() const; void loadUrl(const URLRequest& urlRequest) const; void reload() const; - void goBack() const; - void goForward() const; + void goBack(); + void goForward(); void evaluateJavascript(const std::string& source, std::function completionHanlder) const; + void getCopyBackForwardList(const std::function)> completionHandler) const; static bool isSslError(const COREWEBVIEW2_WEB_ERROR_STATUS& webErrorStatus); private: diff --git a/flutter_inappwebview_windows/windows/in_app_webview/webview_channel_delegate.cpp b/flutter_inappwebview_windows/windows/in_app_webview/webview_channel_delegate.cpp index 6d267177..066a66aa 100644 --- a/flutter_inappwebview_windows/windows/in_app_webview/webview_channel_delegate.cpp +++ b/flutter_inappwebview_windows/windows/in_app_webview/webview_channel_delegate.cpp @@ -46,19 +46,19 @@ namespace flutter_inappwebview_plugin else if (method_call.method_name().compare("loadUrl") == 0) { auto urlRequest = std::make_unique(get_fl_map_value(arguments, "urlRequest")); webView->loadUrl(*urlRequest); - result->Success(make_fl_value(true)); + result->Success(true); } else if (method_call.method_name().compare("reload") == 0) { webView->reload(); - result->Success(make_fl_value(true)); + result->Success(true); } else if (method_call.method_name().compare("goBack") == 0) { webView->goBack(); - result->Success(make_fl_value(true)); + result->Success(true); } else if (method_call.method_name().compare("goForward") == 0) { webView->goForward(); - result->Success(make_fl_value(true)); + result->Success(true); } else if (method_call.method_name().compare("evaluateJavascript") == 0) { auto result_ = std::shared_ptr>(std::move(result)); @@ -66,21 +66,28 @@ namespace flutter_inappwebview_plugin auto source = get_fl_map_value(arguments, "source"); webView->evaluateJavascript(source, [result_ = std::move(result_)](const std::string& value) { - result_->Success(make_fl_value(value)); + result_->Success(value); + }); + } + else if (method_call.method_name().compare("getCopyBackForwardList") == 0) { + auto result_ = std::shared_ptr>(std::move(result)); + webView->getCopyBackForwardList([result_ = std::move(result_)](const std::unique_ptr value) + { + result_->Success(value->toEncodableMap()); }); } // for inAppBrowser else if (webView->inAppBrowser && method_call.method_name().compare("show") == 0) { webView->inAppBrowser->show(); - result->Success(make_fl_value(true)); + result->Success(true); } else if (webView->inAppBrowser && method_call.method_name().compare("hide") == 0) { webView->inAppBrowser->hide(); - result->Success(make_fl_value(true)); + result->Success(true); } else if (webView->inAppBrowser && method_call.method_name().compare("close") == 0) { webView->inAppBrowser->close(); - result->Success(make_fl_value(true)); + result->Success(true); } else { result->NotImplemented(); diff --git a/flutter_inappwebview_windows/windows/types/web_history.cpp b/flutter_inappwebview_windows/windows/types/web_history.cpp new file mode 100644 index 00000000..b7cb9dee --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/web_history.cpp @@ -0,0 +1,22 @@ +#include "../utils/util.h" +#include "web_history.h" + +namespace flutter_inappwebview_plugin +{ + WebHistory::WebHistory(const std::optional currentIndex, const std::optional>>& list) + : currentIndex(currentIndex), list(list) + {} + + WebHistory::WebHistory(const flutter::EncodableMap& map) + : currentIndex(get_optional_fl_map_value(map, "currentIndex")), + list(functional_map(get_optional_fl_map_value(map, "list"), [](const flutter::EncodableValue& m) { return std::make_shared(std::get(m)); })) + {} + + flutter::EncodableMap WebHistory::toEncodableMap() const + { + return flutter::EncodableMap{ + {"currentIndex", make_fl_value(currentIndex)}, + {"list", make_fl_value(functional_map(list, [](const std::shared_ptr& item) { return item->toEncodableMap(); }))} + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/web_history.h b/flutter_inappwebview_windows/windows/types/web_history.h new file mode 100644 index 00000000..95c41bad --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/web_history.h @@ -0,0 +1,26 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_HISTORY_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_HISTORY_H_ + +#include +#include + +#include "../utils/flutter.h" +#include "web_history_item.h" + +namespace flutter_inappwebview_plugin +{ + class WebHistory + { + public: + const std::optional currentIndex; + const std::optional>> list; + + WebHistory(const std::optional currentIndex, const std::optional>>& list); + WebHistory(const flutter::EncodableMap& map); + ~WebHistory() = default; + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_HISTORY_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/web_history_item.cpp b/flutter_inappwebview_windows/windows/types/web_history_item.cpp new file mode 100644 index 00000000..bc222022 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/web_history_item.cpp @@ -0,0 +1,29 @@ +#include "web_history_item.h" + +namespace flutter_inappwebview_plugin +{ + WebHistoryItem::WebHistoryItem(const std::optional& index, const std::optional& offset, + const std::optional& originalUrl, const std::optional& title, + const std::optional& url) + : index(index), offset(offset), originalUrl(originalUrl), title(title), url(url) + {} + + WebHistoryItem::WebHistoryItem(const flutter::EncodableMap& map) + : index(get_optional_fl_map_value(map, "index")), + offset(get_optional_fl_map_value(map, "offset")), + originalUrl(get_optional_fl_map_value(map, "originalUrl")), + title(get_optional_fl_map_value(map, "title")), + url(get_optional_fl_map_value(map, "url")) + {} + + flutter::EncodableMap WebHistoryItem::toEncodableMap() const + { + return flutter::EncodableMap{ + {"index", make_fl_value(index)}, + {"offset", make_fl_value(offset)}, + {"originalUrl", make_fl_value(originalUrl)}, + {"title", make_fl_value(title)}, + {"url", make_fl_value(url)} + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/web_history_item.h b/flutter_inappwebview_windows/windows/types/web_history_item.h new file mode 100644 index 00000000..8e63c1b7 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/web_history_item.h @@ -0,0 +1,30 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_HISTORY_ITEM_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_HISTORY_ITEM_H_ + +#include +#include + +#include "../utils/flutter.h" + +namespace flutter_inappwebview_plugin +{ + class WebHistoryItem + { + public: + const std::optional index; + const std::optional offset; + const std::optional originalUrl; + const std::optional title; + const std::optional url; + + WebHistoryItem(const std::optional& index, const std::optional& offset, + const std::optional& originalUrl, const std::optional& title, + const std::optional& url); + WebHistoryItem(const flutter::EncodableMap& map); + ~WebHistoryItem() = default; + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_HISTORY_ITEM_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/utils/util.h b/flutter_inappwebview_windows/windows/utils/util.h index 88822691..b4820d5c 100644 --- a/flutter_inappwebview_windows/windows/utils/util.h +++ b/flutter_inappwebview_windows/windows/utils/util.h @@ -87,6 +87,39 @@ namespace flutter_inappwebview_plugin auto itr = map.find(key); return itr != map.end() ? itr->second : nullptr; } + + template + static inline auto functional_map(Iterator begin, Iterator end, Func&& func) -> + std::vector()))> + { + using value_type = decltype(func(std::declval())); + + std::vector out_vector; + out_vector.reserve(std::distance(begin, end)); + + std::transform(begin, end, std::back_inserter(out_vector), + std::forward(func)); + + return out_vector; + } + + template + static inline auto functional_map(const T& iterable, Func&& func) -> + std::vector()))> + { + return functional_map(std::begin(iterable), std::end(iterable), + std::forward(func)); + } + + template + static inline auto functional_map(const std::optional& iterable, Func&& func) -> + std::vector()))> + { + if (!iterable.has_value()) { + return {}; + } + return functional_map(iterable.value(), std::forward(func)); + } } #endif //FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_H_ \ No newline at end of file