diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..70f8824f --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,75 @@ +{ + "cmake.configureOnOpen": false, + "files.associations": { + "algorithm": "cpp", + "any": "cpp", + "array": "cpp", + "atomic": "cpp", + "bit": "cpp", + "cctype": "cpp", + "charconv": "cpp", + "chrono": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "compare": "cpp", + "concepts": "cpp", + "coroutine": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "exception": "cpp", + "format": "cpp", + "forward_list": "cpp", + "functional": "cpp", + "initializer_list": "cpp", + "iomanip": "cpp", + "ios": "cpp", + "iosfwd": "cpp", + "iostream": "cpp", + "istream": "cpp", + "iterator": "cpp", + "limits": "cpp", + "list": "cpp", + "locale": "cpp", + "map": "cpp", + "memory": "cpp", + "new": "cpp", + "optional": "cpp", + "ostream": "cpp", + "ratio": "cpp", + "set": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "stop_token": "cpp", + "streambuf": "cpp", + "string": "cpp", + "system_error": "cpp", + "thread": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "typeinfo": "cpp", + "unordered_map": "cpp", + "utility": "cpp", + "variant": "cpp", + "vector": "cpp", + "xfacet": "cpp", + "xhash": "cpp", + "xiosbase": "cpp", + "xlocale": "cpp", + "xlocbuf": "cpp", + "xlocinfo": "cpp", + "xlocmes": "cpp", + "xlocmon": "cpp", + "xlocnum": "cpp", + "xloctime": "cpp", + "xmemory": "cpp", + "xstring": "cpp", + "xtr1common": "cpp", + "xtree": "cpp", + "xutility": "cpp" + } +} \ No newline at end of file diff --git a/dev_packages/generators/lib/src/exchangeable_enum_generator.dart b/dev_packages/generators/lib/src/exchangeable_enum_generator.dart index c362e681..c7802cfa 100644 --- a/dev_packages/generators/lib/src/exchangeable_enum_generator.dart +++ b/dev_packages/generators/lib/src/exchangeable_enum_generator.dart @@ -140,6 +140,7 @@ class ExchangeableEnumGenerator []; var hasWebSupport = false; var webSupportValue = null; + var allPlatformsWithoutValue = true; if (platforms.isNotEmpty) { for (var platform in platforms) { final targetPlatformName = @@ -150,6 +151,9 @@ class ExchangeableEnumGenerator ? platformValueField.toIntValue() ?? "'${platformValueField.toStringValue()}'" : null; + if (allPlatformsWithoutValue && platformValue != null) { + allPlatformsWithoutValue = false; + } if (targetPlatformName == "web") { hasWebSupport = true; webSupportValue = platformValue; @@ -170,8 +174,13 @@ class ExchangeableEnumGenerator nativeValueBody += "return $defaultValue;"; nativeValueBody += "}"; - classBuffer.writeln( - "static final $fieldName = $extClassName._internalMultiPlatform($constantValue, $nativeValueBody);"); + if (!allPlatformsWithoutValue) { + classBuffer.writeln( + "static final $fieldName = $extClassName._internalMultiPlatform($constantValue, $nativeValueBody);"); + } else { + classBuffer.writeln( + "static const $fieldName = $extClassName._internal($constantValue, ${defaultValue ?? constantValue});"); + } } else { classBuffer.writeln( "static const $fieldName = $extClassName._internal($constantValue, $constantValue);"); diff --git a/flutter_inappwebview/example/.metadata b/flutter_inappwebview/example/.metadata index 8f550764..ba5723a2 100755 --- a/flutter_inappwebview/example/.metadata +++ b/flutter_inappwebview/example/.metadata @@ -4,5 +4,27 @@ # This file should be version controlled and should not be manually edited. version: - revision: d927c9331005f81157fa39dff7b5dab415ad330b - channel: master + revision: "78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 + base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 + - platform: windows + create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 + base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/flutter_inappwebview/example/lib/headless_in_app_webview.screen.dart b/flutter_inappwebview/example/lib/headless_in_app_webview.screen.dart index 87e3dc20..5bfa5db1 100755 --- a/flutter_inappwebview/example/lib/headless_in_app_webview.screen.dart +++ b/flutter_inappwebview/example/lib/headless_in_app_webview.screen.dart @@ -24,6 +24,7 @@ class _HeadlessInAppWebViewExampleScreenState : WebUri("http://localhost:${Uri.base.port}/page.html"); headlessWebView = HeadlessInAppWebView( + webViewEnvironment: webViewEnvironment, initialUrlRequest: URLRequest(url: url), initialSettings: InAppWebViewSettings( isInspectable: kDebugMode, diff --git a/flutter_inappwebview/example/lib/in_app_browser_example.screen.dart b/flutter_inappwebview/example/lib/in_app_browser_example.screen.dart index 15f2bd7b..e063f85c 100755 --- a/flutter_inappwebview/example/lib/in_app_browser_example.screen.dart +++ b/flutter_inappwebview/example/lib/in_app_browser_example.screen.dart @@ -17,7 +17,8 @@ class MyInAppBrowser extends InAppBrowser { : super( windowId: windowId, initialUserScripts: initialUserScripts, - pullToRefreshController: pullToRefreshController); + pullToRefreshController: pullToRefreshController, + webViewEnvironment: webViewEnvironment,); @override Future onBrowserCreated() async { diff --git a/flutter_inappwebview/example/lib/in_app_webiew_example.screen.dart b/flutter_inappwebview/example/lib/in_app_webiew_example.screen.dart index 79dc016f..0cd746dd 100755 --- a/flutter_inappwebview/example/lib/in_app_webiew_example.screen.dart +++ b/flutter_inappwebview/example/lib/in_app_webiew_example.screen.dart @@ -115,6 +115,7 @@ class _InAppWebViewExampleScreenState extends State { children: [ InAppWebView( key: webViewKey, + webViewEnvironment: webViewEnvironment, initialUrlRequest: URLRequest(url: WebUri('https://flutter.dev')), // initialUrlRequest: diff --git a/flutter_inappwebview/example/lib/main.dart b/flutter_inappwebview/example/lib/main.dart index 21760f26..ab93bf9d 100755 --- a/flutter_inappwebview/example/lib/main.dart +++ b/flutter_inappwebview/example/lib/main.dart @@ -15,6 +15,7 @@ import 'package:pointer_interceptor/pointer_interceptor.dart'; // import 'package:permission_handler/permission_handler.dart'; final localhostServer = InAppLocalhostServer(documentRoot: 'assets'); +WebViewEnvironment? webViewEnvironment; Future main() async { WidgetsFlutterBinding.ensureInitialized(); @@ -22,12 +23,18 @@ Future main() async { // await Permission.microphone.request(); // await Permission.storage.request(); - if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) { - await InAppWebViewController.setWebContentsDebuggingEnabled(kDebugMode); + if (!kIsWeb && defaultTargetPlatform == TargetPlatform.windows) { + final availableVersion = await WebViewEnvironment.getAvailableVersion(); + assert(availableVersion != null, 'Failed to find an installed WebView2 runtime or non-stable Microsoft Edge installation.'); + + webViewEnvironment = await WebViewEnvironment.create(settings: + WebViewEnvironmentSettings( + userDataFolder: 'custom_path' + )); } - if (!kIsWeb) { - await localhostServer.start(); + if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) { + await InAppWebViewController.setWebContentsDebuggingEnabled(kDebugMode); } runApp(MyApp()); @@ -108,6 +115,28 @@ PointerInterceptor myDrawer({required BuildContext context}) { }, ), ]; + } else if (defaultTargetPlatform == TargetPlatform.windows || + defaultTargetPlatform == TargetPlatform.linux) { + children = [ + ListTile( + title: Text('InAppWebView'), + onTap: () { + Navigator.pushReplacementNamed(context, '/'); + }, + ), + ListTile( + title: Text('InAppBrowser'), + onTap: () { + Navigator.pushReplacementNamed(context, '/InAppBrowser'); + }, + ), + ListTile( + title: Text('HeadlessInAppWebView'), + onTap: () { + Navigator.pushReplacementNamed(context, '/HeadlessInAppWebView'); + }, + ), + ]; } return PointerInterceptor( child: Drawer( @@ -160,6 +189,14 @@ class _MyAppState extends State { '/WebAuthenticationSession': (context) => WebAuthenticationSessionExampleScreen(), }); + } else if (defaultTargetPlatform == TargetPlatform.windows || + defaultTargetPlatform == TargetPlatform.linux) { + return MaterialApp(initialRoute: '/', routes: { + '/': (context) => InAppWebViewExampleScreen(), + '/InAppBrowser': (context) => InAppBrowserExampleScreen(), + '/HeadlessInAppWebView': (context) => + HeadlessInAppWebViewExampleScreen(), + }); } return MaterialApp(initialRoute: '/', routes: { '/': (context) => InAppWebViewExampleScreen(), diff --git a/flutter_inappwebview/example/pubspec.yaml b/flutter_inappwebview/example/pubspec.yaml index fa737046..59aa16aa 100755 --- a/flutter_inappwebview/example/pubspec.yaml +++ b/flutter_inappwebview/example/pubspec.yaml @@ -43,6 +43,8 @@ dependency_overrides: path: ../../flutter_inappwebview_macos flutter_inappwebview_web: path: ../../flutter_inappwebview_web + flutter_inappwebview_windows: + path: ../../flutter_inappwebview_windows dev_dependencies: flutter_test: diff --git a/flutter_inappwebview/example/test/widget_test.dart b/flutter_inappwebview/example/test/widget_test.dart new file mode 100644 index 00000000..092d222f --- /dev/null +++ b/flutter_inappwebview/example/test/widget_test.dart @@ -0,0 +1,30 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:example/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +} diff --git a/flutter_inappwebview/example/windows/.gitignore b/flutter_inappwebview/example/windows/.gitignore new file mode 100644 index 00000000..5d57396c --- /dev/null +++ b/flutter_inappwebview/example/windows/.gitignore @@ -0,0 +1,19 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +cmake-build-* \ No newline at end of file diff --git a/flutter_inappwebview/example/windows/CMakeLists.txt b/flutter_inappwebview/example/windows/CMakeLists.txt new file mode 100644 index 00000000..d960948a --- /dev/null +++ b/flutter_inappwebview/example/windows/CMakeLists.txt @@ -0,0 +1,108 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(example LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "example") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/flutter_inappwebview/example/windows/flutter/CMakeLists.txt b/flutter_inappwebview/example/windows/flutter/CMakeLists.txt new file mode 100644 index 00000000..903f4899 --- /dev/null +++ b/flutter_inappwebview/example/windows/flutter/CMakeLists.txt @@ -0,0 +1,109 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + ${FLUTTER_TARGET_PLATFORM} $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/flutter_inappwebview/example/windows/flutter/generated_plugin_registrant.cc b/flutter_inappwebview/example/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 00000000..031b8695 --- /dev/null +++ b/flutter_inappwebview/example/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,20 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include +#include +#include + +void RegisterPlugins(flutter::PluginRegistry* registry) { + FlutterInappwebviewWindowsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FlutterInappwebviewWindowsPluginCApi")); + PermissionHandlerWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); +} diff --git a/flutter_inappwebview/example/windows/flutter/generated_plugin_registrant.h b/flutter_inappwebview/example/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 00000000..dc139d85 --- /dev/null +++ b/flutter_inappwebview/example/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/flutter_inappwebview/example/windows/flutter/generated_plugins.cmake b/flutter_inappwebview/example/windows/flutter/generated_plugins.cmake new file mode 100644 index 00000000..997d0b80 --- /dev/null +++ b/flutter_inappwebview/example/windows/flutter/generated_plugins.cmake @@ -0,0 +1,26 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + flutter_inappwebview_windows + permission_handler_windows + url_launcher_windows +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/flutter_inappwebview/example/windows/runner/CMakeLists.txt b/flutter_inappwebview/example/windows/runner/CMakeLists.txt new file mode 100644 index 00000000..394917c0 --- /dev/null +++ b/flutter_inappwebview/example/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/flutter_inappwebview/example/windows/runner/Runner.rc b/flutter_inappwebview/example/windows/runner/Runner.rc new file mode 100644 index 00000000..302ceb4b --- /dev/null +++ b/flutter_inappwebview/example/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.pichillilorenzo" "\0" + VALUE "FileDescription", "example" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "example" "\0" + VALUE "LegalCopyright", "Copyright (C) 2023 com.pichillilorenzo. All rights reserved." "\0" + VALUE "OriginalFilename", "example.exe" "\0" + VALUE "ProductName", "example" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/flutter_inappwebview/example/windows/runner/flutter_window.cpp b/flutter_inappwebview/example/windows/runner/flutter_window.cpp new file mode 100644 index 00000000..955ee303 --- /dev/null +++ b/flutter_inappwebview/example/windows/runner/flutter_window.cpp @@ -0,0 +1,71 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/flutter_inappwebview/example/windows/runner/flutter_window.h b/flutter_inappwebview/example/windows/runner/flutter_window.h new file mode 100644 index 00000000..6da0652f --- /dev/null +++ b/flutter_inappwebview/example/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/flutter_inappwebview/example/windows/runner/main.cpp b/flutter_inappwebview/example/windows/runner/main.cpp new file mode 100644 index 00000000..a61bf80d --- /dev/null +++ b/flutter_inappwebview/example/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"example", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/flutter_inappwebview/example/windows/runner/resource.h b/flutter_inappwebview/example/windows/runner/resource.h new file mode 100644 index 00000000..66a65d1e --- /dev/null +++ b/flutter_inappwebview/example/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/flutter_inappwebview/example/windows/runner/resources/app_icon.ico b/flutter_inappwebview/example/windows/runner/resources/app_icon.ico new file mode 100644 index 00000000..c04e20ca Binary files /dev/null and b/flutter_inappwebview/example/windows/runner/resources/app_icon.ico differ diff --git a/flutter_inappwebview/example/windows/runner/runner.exe.manifest b/flutter_inappwebview/example/windows/runner/runner.exe.manifest new file mode 100644 index 00000000..a42ea768 --- /dev/null +++ b/flutter_inappwebview/example/windows/runner/runner.exe.manifest @@ -0,0 +1,20 @@ + + + + + PerMonitorV2 + + + + + + + + + + + + + + + diff --git a/flutter_inappwebview/example/windows/runner/utils.cpp b/flutter_inappwebview/example/windows/runner/utils.cpp new file mode 100644 index 00000000..b2b08734 --- /dev/null +++ b/flutter_inappwebview/example/windows/runner/utils.cpp @@ -0,0 +1,65 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length <= 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/flutter_inappwebview/example/windows/runner/utils.h b/flutter_inappwebview/example/windows/runner/utils.h new file mode 100644 index 00000000..3879d547 --- /dev/null +++ b/flutter_inappwebview/example/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/flutter_inappwebview/example/windows/runner/win32_window.cpp b/flutter_inappwebview/example/windows/runner/win32_window.cpp new file mode 100644 index 00000000..60608d0f --- /dev/null +++ b/flutter_inappwebview/example/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/flutter_inappwebview/example/windows/runner/win32_window.h b/flutter_inappwebview/example/windows/runner/win32_window.h new file mode 100644 index 00000000..e901dde6 --- /dev/null +++ b/flutter_inappwebview/example/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/flutter_inappwebview/lib/src/cookie_manager.dart b/flutter_inappwebview/lib/src/cookie_manager.dart index 19afa277..11247bfe 100755 --- a/flutter_inappwebview/lib/src/cookie_manager.dart +++ b/flutter_inappwebview/lib/src/cookie_manager.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; import 'in_app_webview/in_app_webview_controller.dart'; +import 'webview_environment/webview_environment.dart'; ///{@macro flutter_inappwebview_platform_interface.PlatformCookieManager} class CookieManager { @@ -31,12 +32,22 @@ class CookieManager { static CookieManager? _instance; + WebViewEnvironment? _webViewEnvironment; + ///Gets the [CookieManager] shared instance. - static CookieManager instance() { - if (_instance == null) { - _instance = CookieManager(); + /// + ///[webViewEnvironment] (Supported only on Windows) - Used to create the [CookieManager] using the specified environment. + static CookieManager instance({WebViewEnvironment? webViewEnvironment}) { + if (webViewEnvironment == null) { + if (_instance == null) { + _instance = CookieManager(); + } + return _instance!; + } else { + return CookieManager.fromPlatformCreationParams( + PlatformCookieManagerCreationParams(webViewEnvironment: webViewEnvironment.platform) + ); } - return _instance!; } ///{@macro flutter_inappwebview_platform_interface.PlatformCookieManager.setCookie} diff --git a/flutter_inappwebview/lib/src/in_app_browser/in_app_browser.dart b/flutter_inappwebview/lib/src/in_app_browser/in_app_browser.dart index 3393cafd..b17a68a8 100755 --- a/flutter_inappwebview/lib/src/in_app_browser/in_app_browser.dart +++ b/flutter_inappwebview/lib/src/in_app_browser/in_app_browser.dart @@ -11,6 +11,7 @@ import '../find_interaction/find_interaction_controller.dart'; import '../pull_to_refresh/main.dart'; import '../in_app_webview/in_app_webview_controller.dart'; +import '../webview_environment/webview_environment.dart'; ///{@macro flutter_inappwebview_platform_interface.PlatformInAppBrowser} class InAppBrowser implements PlatformInAppBrowserEvents { @@ -22,14 +23,17 @@ class InAppBrowser implements PlatformInAppBrowserEvents { PullToRefreshController? pullToRefreshController, FindInteractionController? findInteractionController, UnmodifiableListView? initialUserScripts, - int? windowId}) + int? windowId, + WebViewEnvironment? webViewEnvironment, + }) : this.fromPlatformCreationParams( PlatformInAppBrowserCreationParams( contextMenu: contextMenu, pullToRefreshController: pullToRefreshController?.platform, findInteractionController: findInteractionController?.platform, initialUserScripts: initialUserScripts, - windowId: windowId), + windowId: windowId, + webViewEnvironment: webViewEnvironment?.platform,), ); /// Constructs a [InAppBrowser] from creation params for a specific diff --git a/flutter_inappwebview/lib/src/in_app_webview/headless_in_app_webview.dart b/flutter_inappwebview/lib/src/in_app_webview/headless_in_app_webview.dart index 5f714d67..f7c21662 100644 --- a/flutter_inappwebview/lib/src/in_app_webview/headless_in_app_webview.dart +++ b/flutter_inappwebview/lib/src/in_app_webview/headless_in_app_webview.dart @@ -5,6 +5,7 @@ import 'dart:ui'; import 'package:flutter/services.dart'; import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; import '../find_interaction/find_interaction_controller.dart'; +import '../webview_environment/webview_environment.dart'; import 'in_app_webview_controller.dart'; import '../pull_to_refresh/pull_to_refresh_controller.dart'; @@ -42,6 +43,7 @@ class HeadlessInAppWebView { HeadlessInAppWebView? headlessWebView, InAppWebViewKeepAlive? keepAlive, bool? preventGestureDelay, + WebViewEnvironment? webViewEnvironment, @Deprecated('Use onGeolocationPermissionsHidePrompt instead') void Function(InAppWebViewController controller)? androidOnGeolocationPermissionsHidePrompt, @@ -307,6 +309,7 @@ class HeadlessInAppWebView { pullToRefreshController: pullToRefreshController?.platform, findInteractionController: findInteractionController?.platform, contextMenu: contextMenu, + webViewEnvironment: webViewEnvironment?.platform, onWebViewCreated: onWebViewCreated != null ? (controller) => onWebViewCreated.call(controller) : null, diff --git a/flutter_inappwebview/lib/src/in_app_webview/in_app_webview.dart b/flutter_inappwebview/lib/src/in_app_webview/in_app_webview.dart index e2490bfc..b885aa3c 100755 --- a/flutter_inappwebview/lib/src/in_app_webview/in_app_webview.dart +++ b/flutter_inappwebview/lib/src/in_app_webview/in_app_webview.dart @@ -9,6 +9,7 @@ import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; +import '../webview_environment/webview_environment.dart'; import 'headless_in_app_webview.dart'; import 'in_app_webview_controller.dart'; import '../find_interaction/find_interaction_controller.dart'; @@ -42,6 +43,7 @@ class InAppWebView extends StatefulWidget { InAppWebViewKeepAlive? keepAlive, bool? preventGestureDelay, TextDirection? layoutDirection, + WebViewEnvironment? webViewEnvironment, @Deprecated('Use onGeolocationPermissionsHidePrompt instead') void Function(InAppWebViewController controller)? androidOnGeolocationPermissionsHidePrompt, @@ -310,6 +312,7 @@ class InAppWebView extends StatefulWidget { findInteractionController: findInteractionController?.platform, contextMenu: contextMenu, layoutDirection: layoutDirection, + webViewEnvironment: webViewEnvironment?.platform, onWebViewCreated: onWebViewCreated != null ? (controller) => onWebViewCreated.call(controller) : null, diff --git a/flutter_inappwebview/lib/src/in_app_webview/in_app_webview_controller.dart b/flutter_inappwebview/lib/src/in_app_webview/in_app_webview_controller.dart index ee98e38d..bbff4d46 100644 --- a/flutter_inappwebview/lib/src/in_app_webview/in_app_webview_controller.dart +++ b/flutter_inappwebview/lib/src/in_app_webview/in_app_webview_controller.dart @@ -484,6 +484,21 @@ class InAppWebViewController { URLResponse? urlResponse}) => platform.loadSimulatedRequest(urlRequest: urlRequest, data: data); + ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewController.openDevTools} + Future openDevTools() => platform.openDevTools(); + + ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewController.callDevToolsProtocolMethod} + Future callDevToolsProtocolMethod({required String methodName, Map? parameters}) => + platform.callDevToolsProtocolMethod(methodName: methodName, parameters: parameters); + + ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewController.addDevToolsProtocolEventListener} + Future addDevToolsProtocolEventListener({required String eventName, required Function(dynamic data) callback}) => + platform.addDevToolsProtocolEventListener(eventName: eventName, callback: callback); + + ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewController.removeDevToolsProtocolEventListener} + Future removeDevToolsProtocolEventListener({required String eventName}) => + platform.removeDevToolsProtocolEventListener(eventName: eventName); + ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewController.getIFrameId} Future getIFrameId() => platform.getIFrameId(); diff --git a/flutter_inappwebview/lib/src/main.dart b/flutter_inappwebview/lib/src/main.dart index 2b968fee..64e28fb3 100644 --- a/flutter_inappwebview/lib/src/main.dart +++ b/flutter_inappwebview/lib/src/main.dart @@ -15,3 +15,4 @@ export 'webview_asset_loader.dart'; export 'tracing_controller.dart'; export 'process_global_config.dart'; export 'in_app_localhost_server.dart'; +export 'webview_environment/main.dart'; diff --git a/flutter_inappwebview/lib/src/webview_environment/main.dart b/flutter_inappwebview/lib/src/webview_environment/main.dart new file mode 100644 index 00000000..ec25d6bb --- /dev/null +++ b/flutter_inappwebview/lib/src/webview_environment/main.dart @@ -0,0 +1 @@ +export 'webview_environment.dart'; \ No newline at end of file diff --git a/flutter_inappwebview/lib/src/webview_environment/webview_environment.dart b/flutter_inappwebview/lib/src/webview_environment/webview_environment.dart new file mode 100644 index 00000000..b355df71 --- /dev/null +++ b/flutter_inappwebview/lib/src/webview_environment/webview_environment.dart @@ -0,0 +1,43 @@ +import 'dart:core'; + +import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; + +///{@macro flutter_inappwebview_platform_interface.PlatformWebViewEnvironment} +class WebViewEnvironment { + /// Constructs a [WebViewEnvironment]. + /// + /// See [WebViewEnvironment.fromPlatformCreationParams] for setting parameters for + /// a specific platform. + WebViewEnvironment.fromPlatformCreationParams({ + required PlatformWebViewEnvironmentCreationParams params, + }) : this.fromPlatform(platform: PlatformWebViewEnvironment(params)); + + /// Constructs a [WebViewEnvironment] from a specific platform implementation. + WebViewEnvironment.fromPlatform({required this.platform}); + + /// Implementation of [PlatformWebViewEnvironment] for the current platform. + final PlatformWebViewEnvironment platform; + + ///{@macro flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.id} + String get id => platform.id; + + ///{@macro flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.settings} + WebViewEnvironmentSettings? get settings => platform.settings; + + ///{@macro flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.create} + static Future create( + {WebViewEnvironmentSettings? settings}) async { + return WebViewEnvironment.fromPlatform(platform: await PlatformWebViewEnvironment.static().create(settings: settings)); + } + + ///{@macro flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.getAvailableVersion} + static Future getAvailableVersion( + {String? browserExecutableFolder}) => PlatformWebViewEnvironment.static().getAvailableVersion(browserExecutableFolder: browserExecutableFolder); + + ///{@macro flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.getAvailableVersion} + static Future compareBrowserVersions( + {required String version1, required String version2}) => PlatformWebViewEnvironment.static().compareBrowserVersions(version1: version1, version2: version2); + + ///{@macro flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.dispose} + Future dispose() => platform.dispose(); +} \ No newline at end of file diff --git a/flutter_inappwebview_android/example/pubspec.lock b/flutter_inappwebview_android/example/pubspec.lock index 42f7e459..f3b10fd2 100644 --- a/flutter_inappwebview_android/example/pubspec.lock +++ b/flutter_inappwebview_android/example/pubspec.lock @@ -81,7 +81,7 @@ packages: path: ".." relative: true source: path - version: "1.0.11" + version: "1.0.12" flutter_inappwebview_internal_annotations: dependency: transitive description: diff --git a/flutter_inappwebview_android/lib/src/inappwebview_platform.dart b/flutter_inappwebview_android/lib/src/inappwebview_platform.dart index 65e1b426..aa5c4207 100644 --- a/flutter_inappwebview_android/lib/src/inappwebview_platform.dart +++ b/flutter_inappwebview_android/lib/src/inappwebview_platform.dart @@ -155,7 +155,7 @@ class AndroidInAppWebViewPlatform extends InAppWebViewPlatform { /// Creates a new [AndroidWebStorage]. /// /// This function should only be called by the app-facing package. - /// Look at using [AndroidWebStorage] in `flutter_inappwebview` instead. + /// Look at using [WebStorage] in `flutter_inappwebview` instead. @override AndroidWebStorage createPlatformWebStorage( PlatformWebStorageCreationParams params, @@ -166,7 +166,7 @@ class AndroidInAppWebViewPlatform extends InAppWebViewPlatform { /// Creates a new [AndroidLocalStorage]. /// /// This function should only be called by the app-facing package. - /// Look at using [AndroidLocalStorage] in `flutter_inappwebview` instead. + /// Look at using [LocalStorage] in `flutter_inappwebview` instead. @override AndroidLocalStorage createPlatformLocalStorage( PlatformLocalStorageCreationParams params, @@ -177,7 +177,7 @@ class AndroidInAppWebViewPlatform extends InAppWebViewPlatform { /// Creates a new [AndroidSessionStorage]. /// /// This function should only be called by the app-facing package. - /// Look at using [PlatformSessionStorage] in `flutter_inappwebview` instead. + /// Look at using [SessionStorage] in `flutter_inappwebview` instead. @override AndroidSessionStorage createPlatformSessionStorage( PlatformSessionStorageCreationParams params, diff --git a/flutter_inappwebview_ios/lib/src/inappwebview_platform.dart b/flutter_inappwebview_ios/lib/src/inappwebview_platform.dart index 504f1f1a..fc27a56d 100644 --- a/flutter_inappwebview_ios/lib/src/inappwebview_platform.dart +++ b/flutter_inappwebview_ios/lib/src/inappwebview_platform.dart @@ -150,7 +150,7 @@ class IOSInAppWebViewPlatform extends InAppWebViewPlatform { /// Creates a new [IOSWebStorage]. /// /// This function should only be called by the app-facing package. - /// Look at using [IOSWebStorage] in `flutter_inappwebview` instead. + /// Look at using [WebStorage] in `flutter_inappwebview` instead. @override IOSWebStorage createPlatformWebStorage( PlatformWebStorageCreationParams params, @@ -161,7 +161,7 @@ class IOSInAppWebViewPlatform extends InAppWebViewPlatform { /// Creates a new [IOSLocalStorage]. /// /// This function should only be called by the app-facing package. - /// Look at using [IOSLocalStorage] in `flutter_inappwebview` instead. + /// Look at using [LocalStorage] in `flutter_inappwebview` instead. @override IOSLocalStorage createPlatformLocalStorage( PlatformLocalStorageCreationParams params, @@ -172,7 +172,7 @@ class IOSInAppWebViewPlatform extends InAppWebViewPlatform { /// Creates a new [IOSSessionStorage]. /// /// This function should only be called by the app-facing package. - /// Look at using [PlatformSessionStorage] in `flutter_inappwebview` instead. + /// Look at using [SessionStorage] in `flutter_inappwebview` instead. @override IOSSessionStorage createPlatformSessionStorage( PlatformSessionStorageCreationParams params, diff --git a/flutter_inappwebview_macos/lib/src/inappwebview_platform.dart b/flutter_inappwebview_macos/lib/src/inappwebview_platform.dart index 3e13e4f5..98f1e04a 100644 --- a/flutter_inappwebview_macos/lib/src/inappwebview_platform.dart +++ b/flutter_inappwebview_macos/lib/src/inappwebview_platform.dart @@ -138,7 +138,7 @@ class MacOSInAppWebViewPlatform extends InAppWebViewPlatform { /// Creates a new [MacOSWebStorage]. /// /// This function should only be called by the app-facing package. - /// Look at using [MacOSWebStorage] in `flutter_inappwebview` instead. + /// Look at using [WebStorage] in `flutter_inappwebview` instead. @override MacOSWebStorage createPlatformWebStorage( PlatformWebStorageCreationParams params, @@ -149,7 +149,7 @@ class MacOSInAppWebViewPlatform extends InAppWebViewPlatform { /// Creates a new [MacOSLocalStorage]. /// /// This function should only be called by the app-facing package. - /// Look at using [MacOSLocalStorage] in `flutter_inappwebview` instead. + /// Look at using [LocalStorage] in `flutter_inappwebview` instead. @override MacOSLocalStorage createPlatformLocalStorage( PlatformLocalStorageCreationParams params, @@ -160,7 +160,7 @@ class MacOSInAppWebViewPlatform extends InAppWebViewPlatform { /// Creates a new [MacOSSessionStorage]. /// /// This function should only be called by the app-facing package. - /// Look at using [PlatformSessionStorage] in `flutter_inappwebview` instead. + /// Look at using [SessionStorage] in `flutter_inappwebview` instead. @override MacOSSessionStorage createPlatformSessionStorage( PlatformSessionStorageCreationParams params, diff --git a/flutter_inappwebview_platform_interface/CHANGELOG.md b/flutter_inappwebview_platform_interface/CHANGELOG.md index 6aba9680..1cc18371 100644 --- a/flutter_inappwebview_platform_interface/CHANGELOG.md +++ b/flutter_inappwebview_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.0.11 + +- Added `PlatformWebViewEnvironment` class + ## 1.0.10 - Merged "Added == operator and hashCode to WebUri" [#1941](https://github.com/pichillilorenzo/flutter_inappwebview/pull/1941) (thanks to [daisukeueta](https://github.com/daisukeueta)) diff --git a/flutter_inappwebview_platform_interface/lib/src/in_app_browser/in_app_browser_settings.dart b/flutter_inappwebview_platform_interface/lib/src/in_app_browser/in_app_browser_settings.dart index 038f8358..b9c287ab 100755 --- a/flutter_inappwebview_platform_interface/lib/src/in_app_browser/in_app_browser_settings.dart +++ b/flutter_inappwebview_platform_interface/lib/src/in_app_browser/in_app_browser_settings.dart @@ -98,194 +98,198 @@ class InAppBrowserSettings_ implements BrowserOptions, AndroidOptions, IosOptions { ///Set to `true` to create the browser and load the page, but not show it. Omit or set to `false` to have the browser open and load normally. ///The default value is `false`. - /// - ///**Officially Supported Platforms/Implementations**: - ///- Android native WebView - ///- iOS - ///- MacOS + @SupportedPlatforms(platforms: [ + AndroidPlatform(), + IOSPlatform(), + MacOSPlatform(), + WindowsPlatform() + ]) bool? hidden; ///Set to `true` to hide the toolbar at the top of the WebView. The default value is `false`. - /// - ///**Officially Supported Platforms/Implementations**: - ///- Android native WebView - ///- iOS - ///- MacOS + @SupportedPlatforms(platforms: [ + AndroidPlatform(), + IOSPlatform(), + MacOSPlatform() + ]) bool? hideToolbarTop; ///Set the custom background color of the toolbar at the top. - /// - ///**Officially Supported Platforms/Implementations**: - ///- Android native WebView - ///- iOS - ///- MacOS + @SupportedPlatforms(platforms: [ + AndroidPlatform(), + IOSPlatform(), + MacOSPlatform() + ]) Color_? toolbarTopBackgroundColor; ///Set to `true` to hide the url bar on the toolbar at the top. The default value is `false`. - /// - ///**Officially Supported Platforms/Implementations**: - ///- Android native WebView - ///- iOS - ///- MacOS + @SupportedPlatforms(platforms: [ + AndroidPlatform(), + IOSPlatform(), + MacOSPlatform() + ]) bool? hideUrlBar; ///Set to `true` to hide the progress bar when the WebView is loading a page. The default value is `false`. - /// - ///**Officially Supported Platforms/Implementations**: - ///- Android native WebView - ///- iOS - ///- MacOS + @SupportedPlatforms(platforms: [ + AndroidPlatform(), + IOSPlatform(), + MacOSPlatform() + ]) bool? hideProgressBar; ///Set to `true` to hide the default menu items. The default value is `false`. - /// - ///**Officially Supported Platforms/Implementations**: - ///- Android native WebView - ///- iOS + @SupportedPlatforms(platforms: [ + AndroidPlatform(), + IOSPlatform(), + MacOSPlatform() + ]) bool? hideDefaultMenuItems; ///Set to `true` if you want the title should be displayed. The default value is `false`. - /// - ///**Officially Supported Platforms/Implementations**: - ///- Android native WebView + @SupportedPlatforms(platforms: [ + AndroidPlatform() + ]) bool? hideTitleBar; ///Set the action bar's title. - /// - ///**Officially Supported Platforms/Implementations**: - ///- Android native WebView - ///- MacOS + @SupportedPlatforms(platforms: [ + AndroidPlatform(), + MacOSPlatform(), + WindowsPlatform() + ]) String? toolbarTopFixedTitle; ///Set to `false` to not close the InAppBrowser when the user click on the Android back button and the WebView cannot go back to the history. The default value is `true`. - /// - ///**Officially Supported Platforms/Implementations**: - ///- Android native WebView + @SupportedPlatforms(platforms: [ + AndroidPlatform() + ]) bool? closeOnCannotGoBack; ///Set to `false` to block the InAppBrowser WebView going back when the user click on the Android back button. The default value is `true`. - /// - ///**Officially Supported Platforms/Implementations**: - ///- Android native WebView + @SupportedPlatforms(platforms: [ + AndroidPlatform() + ]) bool? allowGoBackWithBackButton; ///Set to `true` to close the InAppBrowser when the user click on the Android back button. The default value is `false`. - /// - ///**Officially Supported Platforms/Implementations**: - ///- Android native WebView + @SupportedPlatforms(platforms: [ + AndroidPlatform() + ]) bool? shouldCloseOnBackButtonPressed; ///Set to `true` to set the toolbar at the top translucent. The default value is `true`. - /// - ///**Officially Supported Platforms/Implementations**: - ///- iOS + @SupportedPlatforms(platforms: [ + IOSPlatform() + ]) bool? toolbarTopTranslucent; ///Set the tint color to apply to the navigation bar background. - /// - ///**Officially Supported Platforms/Implementations**: - ///- iOS + @SupportedPlatforms(platforms: [ + IOSPlatform() + ]) Color_? toolbarTopBarTintColor; ///Set the tint color to apply to the navigation items and bar button items. - /// - ///**Officially Supported Platforms/Implementations**: - ///- iOS + @SupportedPlatforms(platforms: [ + IOSPlatform() + ]) Color_? toolbarTopTintColor; ///Set to `true` to hide the toolbar at the bottom of the WebView. The default value is `false`. - /// - ///**Officially Supported Platforms/Implementations**: - ///- iOS + @SupportedPlatforms(platforms: [ + IOSPlatform() + ]) bool? hideToolbarBottom; ///Set the custom background color of the toolbar at the bottom. - /// - ///**Officially Supported Platforms/Implementations**: - ///- iOS + @SupportedPlatforms(platforms: [ + IOSPlatform() + ]) Color_? toolbarBottomBackgroundColor; ///Set the tint color to apply to the bar button items. - /// - ///**Officially Supported Platforms/Implementations**: - ///- iOS + @SupportedPlatforms(platforms: [ + IOSPlatform() + ]) Color_? toolbarBottomTintColor; ///Set to `true` to set the toolbar at the bottom translucent. The default value is `true`. - /// - ///**Officially Supported Platforms/Implementations**: - ///- iOS + @SupportedPlatforms(platforms: [ + IOSPlatform() + ]) bool? toolbarBottomTranslucent; ///Set the custom text for the close button. - /// - ///**Officially Supported Platforms/Implementations**: - ///- iOS + @SupportedPlatforms(platforms: [ + IOSPlatform() + ]) String? closeButtonCaption; ///Set the custom color for the close button. - /// - ///**Officially Supported Platforms/Implementations**: - ///- iOS + @SupportedPlatforms(platforms: [ + IOSPlatform() + ]) Color_? closeButtonColor; ///Set to `true` to hide the close button. The default value is `false`. - /// - ///**Officially Supported Platforms/Implementations**: - ///- iOS + @SupportedPlatforms(platforms: [ + IOSPlatform() + ]) bool? hideCloseButton; ///Set the custom color for the menu button. - /// - ///**Officially Supported Platforms/Implementations**: - ///- iOS + @SupportedPlatforms(platforms: [ + IOSPlatform() + ]) Color_? menuButtonColor; ///Set the custom modal presentation style when presenting the WebView. The default value is [ModalPresentationStyle.FULL_SCREEN]. - /// - ///**Officially Supported Platforms/Implementations**: - ///- iOS + @SupportedPlatforms(platforms: [ + IOSPlatform() + ]) ModalPresentationStyle_? presentationStyle; ///Set to the custom transition style when presenting the WebView. The default value is [ModalTransitionStyle.COVER_VERTICAL]. - /// - ///**Officially Supported Platforms/Implementations**: - ///- iOS + @SupportedPlatforms(platforms: [ + IOSPlatform() + ]) ModalTransitionStyle_? transitionStyle; ///How the browser window should be added to the main window. ///The default value is [WindowType.WINDOW]. - /// - ///**Officially Supported Platforms/Implementations**: - ///- MacOS + @SupportedPlatforms(platforms: [ + MacOSPlatform(), + WindowsPlatform() + ]) WindowType_? windowType; ///The window’s alpha value. ///The default value is `1.0`. - /// - ///**Officially Supported Platforms/Implementations**: - ///- MacOS + @SupportedPlatforms(platforms: [ + MacOSPlatform(), + WindowsPlatform() + ]) double? windowAlphaValue; ///Flags that describe the window’s current style, such as if it’s resizable or in full-screen mode. - /// - ///**Officially Supported Platforms/Implementations**: - ///- MacOS + @SupportedPlatforms(platforms: [ + MacOSPlatform() + ]) WindowStyleMask_? windowStyleMask; ///The type of separator that the app displays between the title bar and content of a window. - /// - ///**NOTE for MacOS**: available on MacOS 11.0+. - /// - ///**Officially Supported Platforms/Implementations**: - ///- MacOS + @SupportedPlatforms(platforms: [ + MacOSPlatform(available: '11.0') + ]) WindowTitlebarSeparatorStyle_? windowTitlebarSeparatorStyle; ///Sets the origin and size of the window’s frame rectangle according to a given frame rectangle, ///thereby setting its position and size onscreen. - /// - ///**Officially Supported Platforms/Implementations**: - ///- MacOS + @SupportedPlatforms(platforms: [ + MacOSPlatform(), + WindowsPlatform() + ]) InAppWebViewRect_? windowFrame; InAppBrowserSettings_( diff --git a/flutter_inappwebview_platform_interface/lib/src/in_app_browser/platform_in_app_browser.dart b/flutter_inappwebview_platform_interface/lib/src/in_app_browser/platform_in_app_browser.dart index 999b92a3..45c29bb6 100755 --- a/flutter_inappwebview_platform_interface/lib/src/in_app_browser/platform_in_app_browser.dart +++ b/flutter_inappwebview_platform_interface/lib/src/in_app_browser/platform_in_app_browser.dart @@ -19,6 +19,7 @@ import '../in_app_webview/in_app_webview_settings.dart'; import '../print_job/main.dart'; import '../web_uri.dart'; +import '../webview_environment/platform_webview_environment.dart'; import 'in_app_browser_menu_item.dart'; import 'in_app_browser_settings.dart'; import '../debug_logging_settings.dart'; @@ -36,7 +37,9 @@ class PlatformInAppBrowserCreationParams { this.pullToRefreshController, this.findInteractionController, this.initialUserScripts, - this.windowId}); + this.windowId, + this.webViewEnvironment, + }); ///{@macro flutter_inappwebview_platform_interface.PlatformInAppBrowser.contextMenu} final ContextMenu? contextMenu; @@ -52,6 +55,12 @@ class PlatformInAppBrowserCreationParams { ///{@macro flutter_inappwebview_platform_interface.PlatformInAppBrowser.windowId} final int? windowId; + + ///Used to create the [PlatformInAppBrowser] using the specified environment. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows + final PlatformWebViewEnvironment? webViewEnvironment; } ///{@template flutter_inappwebview_platform_interface.PlatformInAppBrowser} @@ -64,6 +73,7 @@ class PlatformInAppBrowserCreationParams { ///- Android native WebView ///- iOS ///- MacOS +///- Windows ///{@endtemplate} abstract class PlatformInAppBrowser extends PlatformInterface implements Disposable { @@ -82,29 +92,53 @@ abstract class PlatformInAppBrowser extends PlatformInterface ///{@template flutter_inappwebview_platform_interface.PlatformInAppBrowser.contextMenu} ///Context menu used by the browser. It should be set before opening the browser. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS ///{@endtemplate} ContextMenu? get contextMenu => params.contextMenu; ///{@template flutter_inappwebview_platform_interface.PlatformInAppBrowser.pullToRefreshController} ///Represents the pull-to-refresh feature controller. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS ///{@endtemplate} PlatformPullToRefreshController? get pullToRefreshController => params.pullToRefreshController; ///{@template flutter_inappwebview_platform_interface.PlatformInAppBrowser.findInteractionController} ///Represents the find interaction feature controller. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS + ///- MacOS ///{@endtemplate} PlatformFindInteractionController? get findInteractionController => params.findInteractionController; ///{@template flutter_inappwebview_platform_interface.PlatformInAppBrowser.initialUserScripts} ///Initial list of user scripts to be loaded at start or end of a page loading. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS + ///- MacOS + ///- Windows ///{@endtemplate} UnmodifiableListView? get initialUserScripts => params.initialUserScripts; ///{@template flutter_inappwebview_platform_interface.PlatformInAppBrowser.windowId} ///The window id of a [CreateWindowAction.windowId]. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS + ///- MacOS ///{@endtemplate} int? get windowId => params.windowId; @@ -172,6 +206,7 @@ abstract class PlatformInAppBrowser extends PlatformInterface ///- Android native WebView ///- iOS ///- MacOS + ///- Windows ///{@endtemplate} Future openUrlRequest( {required URLRequest urlRequest, @@ -225,6 +260,7 @@ abstract class PlatformInAppBrowser extends PlatformInterface ///- Android native WebView ///- iOS ///- MacOS + ///- Windows ///{@endtemplate} Future openFile( {required String assetFilePath, @@ -252,6 +288,7 @@ abstract class PlatformInAppBrowser extends PlatformInterface ///- Android native WebView ///- iOS ///- MacOS + ///- Windows ///{@endtemplate} Future openData( {required String data, @@ -274,6 +311,7 @@ abstract class PlatformInAppBrowser extends PlatformInterface ///- Android native WebView ///- iOS ///- MacOS + ///- Windows ///{@endtemplate} Future openWithSystemBrowser({required WebUri url}) { throw UnimplementedError( @@ -370,6 +408,7 @@ abstract class PlatformInAppBrowser extends PlatformInterface ///- Android native WebView ///- iOS ///- MacOS + ///- Windows ///{@endtemplate} Future show() { throw UnimplementedError('show is not implemented on the current platform'); @@ -382,6 +421,7 @@ abstract class PlatformInAppBrowser extends PlatformInterface ///- Android native WebView ///- iOS ///- MacOS + ///- Windows ///{@endtemplate} Future hide() { throw UnimplementedError('hide is not implemented on the current platform'); @@ -394,6 +434,7 @@ abstract class PlatformInAppBrowser extends PlatformInterface ///- Android native WebView ///- iOS ///- MacOS + ///- Windows ///{@endtemplate} Future close() { throw UnimplementedError( @@ -407,6 +448,7 @@ abstract class PlatformInAppBrowser extends PlatformInterface ///- Android native WebView ///- iOS ///- MacOS + ///- Windows ///{@endtemplate} Future isHidden() { throw UnimplementedError( @@ -464,6 +506,7 @@ abstract class PlatformInAppBrowser extends PlatformInterface ///- Android native WebView ///- iOS ///- MacOS + ///- Windows ///{@endtemplate} bool isOpened() { throw UnimplementedError( @@ -487,6 +530,7 @@ abstract class PlatformInAppBrowserEvents { ///- Android native WebView ///- iOS ///- MacOS + ///- Windows void onBrowserCreated() {} ///Event fired when the [PlatformInAppBrowser] window is closed. @@ -495,6 +539,7 @@ abstract class PlatformInAppBrowserEvents { ///- Android native WebView ///- iOS ///- MacOS + ///- Windows void onExit() {} ///Event fired when the main window is about to close. @@ -509,6 +554,7 @@ abstract class PlatformInAppBrowserEvents { ///- Android native WebView ([Official API - WebViewClient.onPageStarted](https://developer.android.com/reference/android/webkit/WebViewClient#onPageStarted(android.webkit.WebView,%20java.lang.String,%20android.graphics.Bitmap))) ///- iOS ([Official API - WKNavigationDelegate.webView](https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455621-webview)) ///- MacOS ([Official API - WKNavigationDelegate.webView](https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455621-webview)) + ///- Windows ([Official API - ICoreWebView2.add_NavigationStarting](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/iwebview2webview?view=webview2-0.8.355#add_navigationstarting)) void onLoadStart(WebUri? url) {} ///Event fired when the [PlatformInAppBrowser] finishes loading an [url]. @@ -517,6 +563,7 @@ abstract class PlatformInAppBrowserEvents { ///- Android native WebView ([Official API - WebViewClient.onPageFinished](https://developer.android.com/reference/android/webkit/WebViewClient#onPageFinished(android.webkit.WebView,%20java.lang.String))) ///- iOS ([Official API - WKNavigationDelegate.webView](https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455629-webview)) ///- MacOS ([Official API - WKNavigationDelegate.webView](https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455629-webview)) + ///- Windows ([Official API - ICoreWebView2.add_NavigationCompleted](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/iwebview2webview?view=webview2-0.8.355#add_navigationcompleted)) void onLoadStop(WebUri? url) {} ///Use [onReceivedError] instead. @@ -529,6 +576,7 @@ abstract class PlatformInAppBrowserEvents { ///- Android native WebView ([Official API - WebViewClient.onReceivedError](https://developer.android.com/reference/android/webkit/WebViewClient#onReceivedError(android.webkit.WebView,%20android.webkit.WebResourceRequest,%20android.webkit.WebResourceError))) ///- iOS ([Official API - WKNavigationDelegate.webView](https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455623-webview)) ///- MacOS ([Official API - WKNavigationDelegate.webView](https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455623-webview)) + ///- Windows ([Official API - ICoreWebView2.add_NavigationCompleted](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/iwebview2webview?view=webview2-0.8.355#add_navigationcompleted)) void onReceivedError(WebResourceRequest request, WebResourceError error) {} ///Use [onReceivedHttpError] instead. @@ -547,6 +595,7 @@ abstract class PlatformInAppBrowserEvents { ///- Android native WebView ([Official API - WebViewClient.onReceivedHttpError](https://developer.android.com/reference/android/webkit/WebViewClient#onReceivedHttpError(android.webkit.WebView,%20android.webkit.WebResourceRequest,%20android.webkit.WebResourceResponse))) ///- iOS ([Official API - WKNavigationDelegate.webView](https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455643-webview)) ///- MacOS ([Official API - WKNavigationDelegate.webView](https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455643-webview)) + ///- Windows ([Official API - ICoreWebView2.add_NavigationCompleted](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/iwebview2webview?view=webview2-0.8.355#add_navigationcompleted)) void onReceivedHttpError( WebResourceRequest request, WebResourceResponse errorResponse) {} @@ -886,6 +935,7 @@ abstract class PlatformInAppBrowserEvents { ///- Android native WebView ([Official API - WebViewClient.doUpdateVisitedHistory](https://developer.android.com/reference/android/webkit/WebViewClient#doUpdateVisitedHistory(android.webkit.WebView,%20java.lang.String,%20boolean))) ///- iOS ///- MacOS + ///- Windows ([Official API - ICoreWebView2.add_HistoryChanged](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2?view=webview2-1.0.2210.55#add_historychanged)) void onUpdateVisitedHistory(WebUri? url, bool? isReload) {} ///Use [onPrintRequest] instead @@ -955,6 +1005,7 @@ abstract class PlatformInAppBrowserEvents { ///- Android native WebView ([Official API - WebChromeClient.onReceivedTitle](https://developer.android.com/reference/android/webkit/WebChromeClient#onReceivedTitle(android.webkit.WebView,%20java.lang.String))) ///- iOS ///- MacOS + ///- Windows ([Official API - ICoreWebView2.add_DocumentTitleChanged](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2?view=webview2-1.0.2210.55#add_documenttitlechanged)) void onTitleChanged(String? title) {} ///Event fired to respond to the results of an over-scroll operation. diff --git a/flutter_inappwebview_platform_interface/lib/src/in_app_webview/in_app_webview_keep_alive.dart b/flutter_inappwebview_platform_interface/lib/src/in_app_webview/in_app_webview_keep_alive.dart index e026022f..cb7328de 100644 --- a/flutter_inappwebview_platform_interface/lib/src/in_app_webview/in_app_webview_keep_alive.dart +++ b/flutter_inappwebview_platform_interface/lib/src/in_app_webview/in_app_webview_keep_alive.dart @@ -28,12 +28,15 @@ class InAppWebViewControllerKeepAliveProps { Map injectedScriptsFromURL; Set webMessageChannels = Set(); Set webMessageListeners = Set(); + Map devToolsProtocolEventListenerMap; InAppWebViewControllerKeepAliveProps( - {required this.javaScriptHandlersMap, - required this.userScripts, - required this.webMessageListenerObjNames, - required this.injectedScriptsFromURL, - required this.webMessageChannels, - required this.webMessageListeners}); + {this.javaScriptHandlersMap = const {}, + this.userScripts = const {}, + this.webMessageListenerObjNames = const {}, + this.injectedScriptsFromURL = const {}, + this.webMessageChannels = const {}, + this.webMessageListeners = const {}, + this.devToolsProtocolEventListenerMap = const {} + }); } diff --git a/flutter_inappwebview_platform_interface/lib/src/in_app_webview/in_app_webview_settings.dart b/flutter_inappwebview_platform_interface/lib/src/in_app_webview/in_app_webview_settings.dart index a53edcc2..e3b059ad 100755 --- a/flutter_inappwebview_platform_interface/lib/src/in_app_webview/in_app_webview_settings.dart +++ b/flutter_inappwebview_platform_interface/lib/src/in_app_webview/in_app_webview_settings.dart @@ -58,7 +58,7 @@ class InAppWebViewSettings_ { ///it will be automatically inferred as `true`, otherwise, the default value is `false`. ///This logic will not be applied for [PlatformInAppBrowser], where you must set the value manually. @SupportedPlatforms( - platforms: [AndroidPlatform(), IOSPlatform(), MacOSPlatform()]) + platforms: [AndroidPlatform(), IOSPlatform(), MacOSPlatform(), WindowsPlatform()]) bool? useShouldOverrideUrlLoading; ///Set to `true` to be able to listen at the [PlatformWebViewCreationParams.onLoadResource] event. @@ -98,7 +98,11 @@ class InAppWebViewSettings_ { MacOSPlatform( apiName: "WKWebView.customUserAgent", apiUrl: - "https://developer.apple.com/documentation/webkit/wkwebview/1414950-customuseragent") + "https://developer.apple.com/documentation/webkit/wkwebview/1414950-customuseragent"), + WindowsPlatform( + apiName: 'ICoreWebView2Settings2.put_UserAgent', + apiUrl: 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2settings2?view=webview2-1.0.2210.55#put_useragent' + ) ]) String? userAgent; @@ -130,7 +134,12 @@ class InAppWebViewSettings_ { apiName: "WKWebpagePreferences.allowsContentJavaScript", apiUrl: "https://developer.apple.com/documentation/webkit/wkwebpagepreferences/3552422-allowscontentjavascript/"), - WebPlatform(requiresSameOrigin: false) + WebPlatform(requiresSameOrigin: false), + WindowsPlatform( + apiName: "ICoreWebView2Settings.put_IsScriptEnabled", + apiUrl: + "https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2settings?view=webview2-1.0.2210.55#put_isscriptenabled" + ) ]) bool? javaScriptEnabled; @@ -307,7 +316,12 @@ because there isn't any way to make the website data store non-persistent for th @SupportedPlatforms(platforms: [ AndroidPlatform(), IOSPlatform(), - MacOSPlatform(available: "12.0") + MacOSPlatform(available: "12.0"), + WindowsPlatform( + available: '1.0.774.44', + apiName: 'ICoreWebView2Controller2.put_DefaultBackgroundColor', + apiUrl: 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2controller2?view=webview2-1.0.2210.55#put_defaultbackgroundcolor' + ) ]) bool? transparentBackground; @@ -323,7 +337,13 @@ because there isn't any way to make the website data store non-persistent for th ///Set to `true` to disable context menu. The default value is `false`. @SupportedPlatforms( - platforms: [AndroidPlatform(), IOSPlatform(), WebPlatform()]) + platforms: [AndroidPlatform(), IOSPlatform(), WebPlatform(), + WindowsPlatform( + apiName: "ICoreWebView2Settings.put_AreDefaultContextMenusEnabled", + apiUrl: + "https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2settings?view=webview2-1.0.2210.55#put_aredefaultcontextmenusenabled" + ) + ]) bool? disableContextMenu; ///Set to `false` if the WebView should not support zooming using its on-screen zoom controls and gestures. The default value is `true`. @@ -333,7 +353,12 @@ because there isn't any way to make the website data store non-persistent for th apiUrl: "https://developer.android.com/reference/android/webkit/WebSettings?hl=en#setSupportZoom(boolean)"), IOSPlatform(), - MacOSPlatform() + MacOSPlatform(), + WindowsPlatform( + apiName: "ICoreWebView2Settings.put_IsZoomControlEnabled", + apiUrl: + "https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2settings?view=webview2-1.0.2210.55#put_iszoomcontrolenabled" + ) ]) bool? supportZoom; @@ -1537,7 +1562,12 @@ as it can cause framerate drops on animations in Android 9 and lower (see [Hybri available: "13.3", apiName: "WKWebView.isInspectable", apiUrl: - "https://developer.apple.com/documentation/webkit/wkwebview/4111163-isinspectable") + "https://developer.apple.com/documentation/webkit/wkwebview/4111163-isinspectable"), + WindowsPlatform( + apiName: "ICoreWebView2Settings.put_AreDevToolsEnabled", + apiUrl: + "https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2settings?view=webview2-1.0.2210.55#put_aredevtoolsenabled" + ) ]) bool? isInspectable; diff --git a/flutter_inappwebview_platform_interface/lib/src/in_app_webview/in_app_webview_settings.g.dart b/flutter_inappwebview_platform_interface/lib/src/in_app_webview/in_app_webview_settings.g.dart index 3ae73665..7c30031d 100644 --- a/flutter_inappwebview_platform_interface/lib/src/in_app_webview/in_app_webview_settings.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/in_app_webview/in_app_webview_settings.g.dart @@ -371,6 +371,7 @@ class InAppWebViewSettings { ///- Android native WebView ///- iOS ///- Web but iframe requires same origin + ///- Windows ([Official API - ICoreWebView2Settings.put_AreDefaultContextMenusEnabled](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2settings?view=webview2-1.0.2210.55#put_aredefaultcontextmenusenabled)) bool? disableContextMenu; ///Sets whether the default Android WebView’s internal error page should be suppressed or displayed for bad navigations. @@ -648,6 +649,7 @@ class InAppWebViewSettings { ///**Officially Supported Platforms/Implementations**: ///- iOS 16.4+ ([Official API - WKWebView.isInspectable](https://developer.apple.com/documentation/webkit/wkwebview/4111163-isinspectable)) ///- MacOS 13.3+ ([Official API - WKWebView.isInspectable](https://developer.apple.com/documentation/webkit/wkwebview/4111163-isinspectable)) + ///- Windows ([Official API - ICoreWebView2Settings.put_AreDevToolsEnabled](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2settings?view=webview2-1.0.2210.55#put_aredevtoolsenabled)) bool? isInspectable; ///A Boolean value that determines whether paging is enabled for the scroll view. @@ -691,6 +693,7 @@ class InAppWebViewSettings { ///- iOS ([Official API - WKWebpagePreferences.allowsContentJavaScript](https://developer.apple.com/documentation/webkit/wkwebpagepreferences/3552422-allowscontentjavascript/)) ///- MacOS ([Official API - WKWebpagePreferences.allowsContentJavaScript](https://developer.apple.com/documentation/webkit/wkwebpagepreferences/3552422-allowscontentjavascript/)) ///- Web + ///- Windows ([Official API - ICoreWebView2Settings.put_IsScriptEnabled](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2settings?view=webview2-1.0.2210.55#put_isscriptenabled)) bool? javaScriptEnabled; ///Sets the underlying layout algorithm. This will cause a re-layout of the WebView. @@ -983,6 +986,7 @@ class InAppWebViewSettings { ///- Android native WebView ([Official API - WebSettings.setSupportZoom](https://developer.android.com/reference/android/webkit/WebSettings?hl=en#setSupportZoom(boolean))) ///- iOS ///- MacOS + ///- Windows ([Official API - ICoreWebView2Settings.put_IsZoomControlEnabled](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2settings?view=webview2-1.0.2210.55#put_iszoomcontrolenabled)) bool? supportZoom; ///Set to `true` if you want the WebView suppresses content rendering until it is fully loaded into memory. The default value is `false`. @@ -1012,6 +1016,7 @@ class InAppWebViewSettings { ///- Android native WebView ///- iOS ///- MacOS 12.0+ + ///- Windows 1.0.774.44+ ([Official API - ICoreWebView2Controller2.put_DefaultBackgroundColor](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2controller2?view=webview2-1.0.2210.55#put_defaultbackgroundcolor)) bool? transparentBackground; ///The color the web view displays behind the active page, visible when the user scrolls beyond the bounds of the page. @@ -1137,6 +1142,7 @@ class InAppWebViewSettings { ///- Android native WebView ///- iOS ///- MacOS + ///- Windows bool? useShouldOverrideUrlLoading; ///Set to `true` if the WebView should enable support for the "viewport" HTML meta tag or should use a wide viewport. @@ -1154,6 +1160,7 @@ class InAppWebViewSettings { ///- Android native WebView ([Official API - WebSettings.setUserAgentString](https://developer.android.com/reference/android/webkit/WebSettings?hl=en#setUserAgentString(java.lang.String))) ///- iOS ([Official API - WKWebView.customUserAgent](https://developer.apple.com/documentation/webkit/wkwebview/1414950-customuseragent)) ///- MacOS ([Official API - WKWebView.customUserAgent](https://developer.apple.com/documentation/webkit/wkwebview/1414950-customuseragent)) + ///- Windows ([Official API - ICoreWebView2Settings2.put_UserAgent](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2settings2?view=webview2-1.0.2210.55#put_useragent)) String? userAgent; ///Define whether the vertical scrollbar should be drawn or not. The default value is `true`. diff --git a/flutter_inappwebview_platform_interface/lib/src/in_app_webview/platform_headless_in_app_webview.dart b/flutter_inappwebview_platform_interface/lib/src/in_app_webview/platform_headless_in_app_webview.dart index b953d3b4..99ea3b4f 100644 --- a/flutter_inappwebview_platform_interface/lib/src/in_app_webview/platform_headless_in_app_webview.dart +++ b/flutter_inappwebview_platform_interface/lib/src/in_app_webview/platform_headless_in_app_webview.dart @@ -5,6 +5,7 @@ import 'package:flutter/services.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import '../inappwebview_platform.dart'; import '../types/disposable.dart'; +import '../webview_environment/platform_webview_environment.dart'; import 'platform_inappwebview_controller.dart'; import 'platform_webview.dart'; @@ -17,6 +18,7 @@ class PlatformHeadlessInAppWebViewCreationParams /// Used by the platform implementation to create a new [PlatformHeadlessInAppWebView]. const PlatformHeadlessInAppWebViewCreationParams( {this.initialSize = const Size(-1, -1), + this.webViewEnvironment, super.controllerFromPlatform, super.windowId, super.onWebViewCreated, @@ -141,6 +143,12 @@ class PlatformHeadlessInAppWebViewCreationParams ///- Web ///- MacOS final Size initialSize; + + ///Used to create the [PlatformHeadlessInAppWebView] using the specified environment. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows + final PlatformWebViewEnvironment? webViewEnvironment; } ///{@template flutter_inappwebview_platform_interface.PlatformHeadlessInAppWebView} 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 952ab49d..54e2d51f 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 @@ -134,6 +134,7 @@ abstract class PlatformInAppWebViewController extends PlatformInterface ///- iOS ([Official API - WKWebView.url](https://developer.apple.com/documentation/webkit/wkwebview/1415005-url)) ///- MacOS ([Official API - WKWebView.url](https://developer.apple.com/documentation/webkit/wkwebview/1415005-url)) ///- Web + ///- Windows ([Official API - ICoreWebView2.get_Source](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2?view=webview2-1.0.2210.55#get_source)) ///{@endtemplate} Future getUrl() { throw UnimplementedError( @@ -150,6 +151,7 @@ abstract class PlatformInAppWebViewController extends PlatformInterface ///- iOS ([Official API - WKWebView.title](https://developer.apple.com/documentation/webkit/wkwebview/1415015-title)) ///- MacOS ([Official API - WKWebView.title](https://developer.apple.com/documentation/webkit/wkwebview/1415015-title)) ///- Web + ///- Windows ([Official API - ICoreWebView2.get_DocumentTitle](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2?view=webview2-1.0.2210.55#get_documenttitle)) ///{@endtemplate} Future getTitle() { throw UnimplementedError( @@ -224,6 +226,7 @@ abstract class PlatformInAppWebViewController extends PlatformInterface ///- iOS ([Official API - WKWebView.load](https://developer.apple.com/documentation/webkit/wkwebview/1414954-load). If [allowingReadAccessTo] is used, [Official API - WKWebView.loadFileURL](https://developer.apple.com/documentation/webkit/wkwebview/1414973-loadfileurl)) ///- MacOS ([Official API - WKWebView.load](https://developer.apple.com/documentation/webkit/wkwebview/1414954-load). If [allowingReadAccessTo] is used, [Official API - WKWebView.loadFileURL](https://developer.apple.com/documentation/webkit/wkwebview/1414973-loadfileurl)) ///- Web + ///- Windows ([Official API - ICoreWebView2_2.NavigateWithWebResourceRequest](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2_2?view=webview2-1.0.2210.55#navigatewithwebresourcerequest)) ///{@endtemplate} Future loadUrl( {required URLRequest urlRequest, @@ -271,11 +274,14 @@ abstract class PlatformInAppWebViewController extends PlatformInterface ///Specify a directory to give WebView permission to read additional files in the specified directory. ///**NOTE**: available only on iOS and MacOS. /// + ///**NOTE for Windows**: only the [data] parameter is used. + /// ///**Officially Supported Platforms/Implementations**: ///- Android native WebView ([Official API - WebView.loadDataWithBaseURL](https://developer.android.com/reference/android/webkit/WebView#loadDataWithBaseURL(java.lang.String,%20java.lang.String,%20java.lang.String,%20java.lang.String,%20java.lang.String))) ///- iOS ([Official API - WKWebView.loadHTMLString](https://developer.apple.com/documentation/webkit/wkwebview/1415004-loadhtmlstring) or [Official API - WKWebView.load](https://developer.apple.com/documentation/webkit/wkwebview/1415011-load)) ///- MacOS ([Official API - WKWebView.loadHTMLString](https://developer.apple.com/documentation/webkit/wkwebview/1415004-loadhtmlstring) or [Official API - WKWebView.load](https://developer.apple.com/documentation/webkit/wkwebview/1415011-load)) ///- Web + ///- Windows ([Official API - ICoreWebView2.NavigateToString](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2?view=webview2-1.0.2210.55#navigatetostring)) ///{@endtemplate} Future loadData( {required String data, @@ -327,6 +333,7 @@ abstract class PlatformInAppWebViewController extends PlatformInterface ///- iOS ([Official API - WKWebView.load](https://developer.apple.com/documentation/webkit/wkwebview/1414954-load)) ///- MacOS ([Official API - WKWebView.load](https://developer.apple.com/documentation/webkit/wkwebview/1414954-load)) ///- Web + ///- Windows ([Official API - ICoreWebView2.Navigate](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2?view=webview2-1.0.2210.55#navigate)) ///{@endtemplate} Future loadFile({required String assetFilePath}) { throw UnimplementedError( @@ -343,6 +350,7 @@ abstract class PlatformInAppWebViewController extends PlatformInterface ///- iOS ([Official API - WKWebView.reload](https://developer.apple.com/documentation/webkit/wkwebview/1414969-reload)) ///- MacOS ([Official API - WKWebView.reload](https://developer.apple.com/documentation/webkit/wkwebview/1414969-reload)) ///- Web ([Official API - Location.reload](https://developer.mozilla.org/en-US/docs/Web/API/Location/reload)) + ///- Windows ([Official API - ICoreWebView2.Reload](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2?view=webview2-1.0.2210.55#reload)) ///{@endtemplate} Future reload() { throw UnimplementedError( @@ -359,6 +367,7 @@ abstract class PlatformInAppWebViewController extends PlatformInterface ///- iOS ([Official API - WKWebView.goBack](https://developer.apple.com/documentation/webkit/wkwebview/1414952-goback)) ///- MacOS ([Official API - WKWebView.goBack](https://developer.apple.com/documentation/webkit/wkwebview/1414952-goback)) ///- Web ([Official API - History.back](https://developer.mozilla.org/en-US/docs/Web/API/History/back)) + ///- Windows ([Official API - ICoreWebView2.GoBack](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2?view=webview2-1.0.2210.55#goback)) ///{@endtemplate} Future goBack() { throw UnimplementedError( @@ -372,6 +381,7 @@ abstract class PlatformInAppWebViewController extends PlatformInterface ///- Android native WebView ([Official API - WebView.canGoBack](https://developer.android.com/reference/android/webkit/WebView#canGoBack())) ///- iOS ([Official API - WKWebView.canGoBack](https://developer.apple.com/documentation/webkit/wkwebview/1414966-cangoback)) ///- MacOS ([Official API - WKWebView.canGoBack](https://developer.apple.com/documentation/webkit/wkwebview/1414966-cangoback)) + ///- Windows ([Official API - ICoreWebView2.get_CanGoBack](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2?view=webview2-1.0.2210.55#get_cangoback)) ///{@endtemplate} Future canGoBack() { throw UnimplementedError( @@ -388,6 +398,7 @@ abstract class PlatformInAppWebViewController extends PlatformInterface ///- iOS ([Official API - WKWebView.goForward](https://developer.apple.com/documentation/webkit/wkwebview/1414993-goforward)) ///- MacOS ([Official API - WKWebView.goForward](https://developer.apple.com/documentation/webkit/wkwebview/1414993-goforward)) ///- Web ([Official API - History.forward](https://developer.mozilla.org/en-US/docs/Web/API/History/forward)) + ///- Windows ([Official API - ICoreWebView2.GoForward](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2?view=webview2-1.0.2210.55#goforward)) ///{@endtemplate} Future goForward() { throw UnimplementedError( @@ -401,6 +412,7 @@ abstract class PlatformInAppWebViewController extends PlatformInterface ///- Android native WebView ([Official API - WebView.canGoForward](https://developer.android.com/reference/android/webkit/WebView#canGoForward())) ///- iOS ([Official API - WKWebView.canGoForward](https://developer.apple.com/documentation/webkit/wkwebview/1414962-cangoforward)) ///- MacOS ([Official API - WKWebView.canGoForward](https://developer.apple.com/documentation/webkit/wkwebview/1414962-cangoforward)) + ///- Windows ([Official API - ICoreWebView2.get_CanGoForward](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2?view=webview2-1.0.2210.55#get_cangoforward)) ///{@endtemplate} Future canGoForward() { throw UnimplementedError( @@ -417,6 +429,7 @@ abstract class PlatformInAppWebViewController extends PlatformInterface ///- iOS ([Official API - WKWebView.go](https://developer.apple.com/documentation/webkit/wkwebview/1414991-go)) ///- MacOS ([Official API - WKWebView.go](https://developer.apple.com/documentation/webkit/wkwebview/1414991-go)) ///- Web ([Official API - History.go](https://developer.mozilla.org/en-US/docs/Web/API/History/go)) + ///- Windows ///{@endtemplate} Future goBackOrForward({required int steps}) { throw UnimplementedError( @@ -430,6 +443,7 @@ abstract class PlatformInAppWebViewController extends PlatformInterface ///- Android native WebView ([Official API - WebView.canGoBackOrForward](https://developer.android.com/reference/android/webkit/WebView#canGoBackOrForward(int))) ///- iOS ///- MacOS + ///- Windows ///{@endtemplate} Future canGoBackOrForward({required int steps}) { throw UnimplementedError( @@ -446,6 +460,7 @@ abstract class PlatformInAppWebViewController extends PlatformInterface ///- iOS ///- MacOS ///- Web + ///- Windows ///{@endtemplate} Future goTo({required WebHistoryItem historyItem}) { throw UnimplementedError('goTo is not implemented on the current platform'); @@ -459,6 +474,7 @@ abstract class PlatformInAppWebViewController extends PlatformInterface ///- iOS ///- MacOS ///- Web + ///- Windows ///{@endtemplate} Future isLoading() { throw UnimplementedError( @@ -475,6 +491,7 @@ abstract class PlatformInAppWebViewController extends PlatformInterface ///- iOS ([Official API - WKWebView.stopLoading](https://developer.apple.com/documentation/webkit/wkwebview/1414981-stoploading)) ///- MacOS ([Official API - WKWebView.stopLoading](https://developer.apple.com/documentation/webkit/wkwebview/1414981-stoploading)) ///- Web ([Official API - Window.stop](https://developer.mozilla.org/en-US/docs/Web/API/Window/stop)) + ///- Windows ([Official API - ICoreWebView2.Stop](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2?view=webview2-1.0.2210.55#stop)) ///{@endtemplate} Future stopLoading() { throw UnimplementedError( @@ -490,7 +507,7 @@ abstract class PlatformInAppWebViewController extends PlatformInterface ///Those changes remain visible to all scripts, regardless of which content world you specify. ///For more information about content worlds, see [ContentWorld]. ///Available on iOS 14.0+ and MacOS 11.0+. - ///**NOTE**: not used on Web. + ///**NOTE**: not used on Web and on Windows platforms. /// ///**NOTE**: This method shouldn't be called in the [PlatformWebViewCreationParams.onWebViewCreated] or [PlatformWebViewCreationParams.onLoadStart] events, ///because, in these events, the `WebView` is not ready to handle it yet. @@ -504,6 +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 ///{@endtemplate} Future evaluateJavascript( {required String source, ContentWorld? contentWorld}) { @@ -551,6 +569,7 @@ abstract class PlatformInAppWebViewController extends PlatformInterface ///- iOS ///- MacOS ///- Web + ///- Windows ///{@endtemplate} Future injectJavascriptFileFromAsset( {required String assetFilePath}) { @@ -731,6 +750,7 @@ abstract class PlatformInAppWebViewController extends PlatformInterface ///- Android native WebView ///- iOS ([Official API - WKWebView.takeSnapshot](https://developer.apple.com/documentation/webkit/wkwebview/2873260-takesnapshot)) ///- MacOS ([Official API - WKWebView.takeSnapshot](https://developer.apple.com/documentation/webkit/wkwebview/2873260-takesnapshot)) + ///- Windows ///{@endtemplate} Future takeScreenshot( {ScreenshotConfiguration? screenshotConfiguration}) { @@ -776,6 +796,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( @@ -1173,6 +1194,7 @@ abstract class PlatformInAppWebViewController extends PlatformInterface ///- Android native WebView ///- iOS ([Official API - WKUserContentController.addUserScript](https://developer.apple.com/documentation/webkit/wkusercontentcontroller/1537448-adduserscript)) ///- MacOS ([Official API - WKUserContentController.addUserScript](https://developer.apple.com/documentation/webkit/wkusercontentcontroller/1537448-adduserscript)) + ///- Windows ///{@endtemplate} Future addUserScript({required UserScript userScript}) { throw UnimplementedError( @@ -1190,6 +1212,7 @@ abstract class PlatformInAppWebViewController extends PlatformInterface ///- Android native WebView ///- iOS ///- MacOS + ///- Windows ///{@endtemplate} Future addUserScripts({required List userScripts}) { throw UnimplementedError( @@ -1209,6 +1232,7 @@ abstract class PlatformInAppWebViewController extends PlatformInterface ///- Android native WebView ///- iOS ///- MacOS + ///- Windows ///{@endtemplate} Future removeUserScript({required UserScript userScript}) { throw UnimplementedError( @@ -1227,6 +1251,7 @@ abstract class PlatformInAppWebViewController extends PlatformInterface ///- Android native WebView ///- iOS ///- MacOS + ///- Windows ///{@endtemplate} Future removeUserScriptsByGroupName({required String groupName}) { throw UnimplementedError( @@ -1245,6 +1270,7 @@ abstract class PlatformInAppWebViewController extends PlatformInterface ///- Android native WebView ///- iOS ///- MacOS + ///- Windows ///{@endtemplate} Future removeUserScripts({required List userScripts}) { throw UnimplementedError( @@ -1262,6 +1288,7 @@ abstract class PlatformInAppWebViewController extends PlatformInterface ///- Android native WebView ///- iOS ([Official API - WKUserContentController.removeAllUserScripts](https://developer.apple.com/documentation/webkit/wkusercontentcontroller/1536540-removealluserscripts)) ///- MacOS ([Official API - WKUserContentController.removeAllUserScripts](https://developer.apple.com/documentation/webkit/wkusercontentcontroller/1536540-removealluserscripts)) + ///- Windows ///{@endtemplate} Future removeAllUserScripts() { throw UnimplementedError( @@ -1275,6 +1302,7 @@ abstract class PlatformInAppWebViewController extends PlatformInterface ///- Android native WebView ///- iOS ///- MacOS + ///- Windows ///{@endtemplate} bool hasUserScript({required UserScript userScript}) { throw UnimplementedError( @@ -1315,6 +1343,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 callAsyncJavaScript( {required String functionBody, @@ -2011,6 +2040,62 @@ abstract class PlatformInAppWebViewController extends PlatformInterface 'loadSimulatedRequest is not implemented on the current platform'); } + ///{@template flutter_inappwebview_platform_interface.PlatformInAppWebViewController.openDevTools} + ///Opens the DevTools window for the current document in the WebView. + ///Does nothing if run when the DevTools window is already open. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - ICoreWebView2.OpenDevToolsWindow](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2?view=webview2-1.0.2210.55#opendevtoolswindow)) + ///{@endtemplate} + Future openDevTools() { + throw UnimplementedError( + 'openDevTools is not implemented on the current platform'); + } + + ///{@template flutter_inappwebview_platform_interface.PlatformInAppWebViewController.callDevToolsProtocolMethod} + ///Runs an asynchronous `DevToolsProtocol` method. + /// + ///For more information about available methods, navigate to [DevTools Protocol Viewer](https://chromedevtools.github.io/devtools-protocol/tot). + ///The [methodName] parameter is the full name of the method in the `{domain}.{method}` format. + ///The [parameters] will be a JSON formatted string containing the parameters for the corresponding method. + ///This function throws an error if the [methodName] is unknown or the [parameters] has an error. + ///In the case of such an error, the [parameters] parameter of the + ///handler will include information about the error. + ///Note even though WebView dispatches the CDP messages in the order called, + ///CDP method calls may be processed out of order. + ///If you require CDP methods to run in a particular order, you should wait for + ///the previous method's completed handler to run before calling the next method. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - ICoreWebView2.CallDevToolsProtocolMethod](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2?view=webview2-1.0.2210.55#calldevtoolsprotocolmethod)) + ///{@endtemplate} + Future callDevToolsProtocolMethod({required String methodName, Map? parameters}) { + throw UnimplementedError( + 'callDevToolsProtocolMethod is not implemented on the current platform'); + } + + ///{@template flutter_inappwebview_platform_interface.PlatformInAppWebViewController.addDevToolsProtocolEventListener} + ///Subscribe to a `DevToolsProtocol` event. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - ICoreWebView2DevToolsProtocolEventReceiver.add_DevToolsProtocolEventReceived](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2devtoolsprotocoleventreceiver?view=webview2-1.0.2210.55#add_devtoolsprotocoleventreceived)) + ///{@endtemplate} + Future addDevToolsProtocolEventListener({required String eventName, required Function(dynamic data) callback}) { + throw UnimplementedError( + 'addDevToolsProtocolEventListener is not implemented on the current platform'); + } + + ///{@template flutter_inappwebview_platform_interface.PlatformInAppWebViewController.removeDevToolsProtocolEventListener} + ///Remove an event handler previously added with [addDevToolsProtocolEventListener]. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - ICoreWebView2DevToolsProtocolEventReceiver.remove_DevToolsProtocolEventReceived](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2devtoolsprotocoleventreceiver?view=webview2-1.0.2210.55#remove_devtoolsprotocoleventreceived)) + ///{@endtemplate} + Future removeDevToolsProtocolEventListener({required String eventName}) { + throw UnimplementedError( + 'removeDevToolsProtocolEventListener is not implemented on the current platform'); + } + ///{@template flutter_inappwebview_platform_interface.PlatformInAppWebViewController.getIFrameId} ///Returns the iframe `id` attribute used on the Web platform. /// diff --git a/flutter_inappwebview_platform_interface/lib/src/in_app_webview/platform_inappwebview_widget.dart b/flutter_inappwebview_platform_interface/lib/src/in_app_webview/platform_inappwebview_widget.dart index 6f926191..e8194a92 100644 --- a/flutter_inappwebview_platform_interface/lib/src/in_app_webview/platform_inappwebview_widget.dart +++ b/flutter_inappwebview_platform_interface/lib/src/in_app_webview/platform_inappwebview_widget.dart @@ -5,6 +5,7 @@ import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import '../inappwebview_platform.dart'; import '../types/disposable.dart'; +import '../webview_environment/platform_webview_environment.dart'; import 'in_app_webview_keep_alive.dart'; import 'platform_webview.dart'; import 'platform_headless_in_app_webview.dart'; @@ -24,6 +25,7 @@ class PlatformInAppWebViewWidgetCreationParams this.headlessWebView, this.keepAlive, this.preventGestureDelay, + this.webViewEnvironment, super.controllerFromPlatform, super.windowId, super.onWebViewCreated, @@ -181,6 +183,12 @@ class PlatformInAppWebViewWidgetCreationParams ///**Officially Supported Platforms/Implementations**: ///- iOS final bool? preventGestureDelay; + + ///Used to create the [PlatformInAppWebViewWidget] using the specified environment. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows + final PlatformWebViewEnvironment? webViewEnvironment; } /// Interface for a platform implementation of a web view widget. @@ -192,6 +200,7 @@ class PlatformInAppWebViewWidgetCreationParams ///- Android native WebView ///- iOS ///- Web +///- Windows ///{@endtemplate} abstract class PlatformInAppWebViewWidget extends PlatformInterface implements Disposable { diff --git a/flutter_inappwebview_platform_interface/lib/src/in_app_webview/platform_webview.dart b/flutter_inappwebview_platform_interface/lib/src/in_app_webview/platform_webview.dart index a4d4890d..d0c21f0f 100644 --- a/flutter_inappwebview_platform_interface/lib/src/in_app_webview/platform_webview.dart +++ b/flutter_inappwebview_platform_interface/lib/src/in_app_webview/platform_webview.dart @@ -38,6 +38,7 @@ class PlatformWebViewCreationParams { ///- iOS ///- MacOS ///- Web + ///- Windows ///{@endtemplate} final void Function(T controller)? onWebViewCreated; @@ -54,6 +55,7 @@ class PlatformWebViewCreationParams { ///- iOS ([Official API - WKNavigationDelegate.webView](https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455621-webview)) ///- MacOS ([Official API - WKNavigationDelegate.webView](https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455621-webview)) ///- Web + ///- Windows ([Official API - ICoreWebView2.add_NavigationStarting](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/iwebview2webview?view=webview2-0.8.355#add_navigationstarting)) ///{@endtemplate} final void Function(T controller, WebUri? url)? onLoadStart; @@ -68,6 +70,7 @@ class PlatformWebViewCreationParams { ///- iOS ([Official API - WKNavigationDelegate.webView](https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455629-webview)) ///- MacOS ([Official API - WKNavigationDelegate.webView](https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455629-webview)) ///- Web ([Official API - Window.onload](https://developer.mozilla.org/en-US/docs/Web/API/Window/load_event)) + ///- Windows ([Official API - ICoreWebView2.add_NavigationCompleted](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/iwebview2webview?view=webview2-0.8.355#add_navigationcompleted)) ///{@endtemplate} final void Function(T controller, WebUri? url)? onLoadStop; @@ -83,6 +86,7 @@ class PlatformWebViewCreationParams { ///- Android native WebView ([Official API - WebViewClient.onReceivedError](https://developer.android.com/reference/android/webkit/WebViewClient#onReceivedError(android.webkit.WebView,%20android.webkit.WebResourceRequest,%20android.webkit.WebResourceError))) ///- iOS ([Official API - WKNavigationDelegate.webView](https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455623-webview)) ///- MacOS ([Official API - WKNavigationDelegate.webView](https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455623-webview)) + ///- Windows ([Official API - ICoreWebView2.add_NavigationCompleted](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/iwebview2webview?view=webview2-0.8.355#add_navigationcompleted)) ///{@endtemplate} final void Function( T controller, WebResourceRequest request, WebResourceError error)? @@ -107,6 +111,7 @@ class PlatformWebViewCreationParams { ///- Android native WebView ([Official API - WebViewClient.onReceivedHttpError](https://developer.android.com/reference/android/webkit/WebViewClient#onReceivedHttpError(android.webkit.WebView,%20android.webkit.WebResourceRequest,%20android.webkit.WebResourceResponse))) ///- iOS ([Official API - WKNavigationDelegate.webView](https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455643-webview)) ///- MacOS ([Official API - WKNavigationDelegate.webView](https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455643-webview)) + ///- Windows ([Official API - ICoreWebView2.add_NavigationCompleted](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/iwebview2webview?view=webview2-0.8.355#add_navigationcompleted)) ///{@endtemplate} final void Function(T controller, WebResourceRequest request, WebResourceResponse errorResponse)? onReceivedHttpError; @@ -499,6 +504,7 @@ class PlatformWebViewCreationParams { ///- iOS ///- MacOS ///- Web + ///- Windows ([Official API - ICoreWebView2.add_HistoryChanged](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2?view=webview2-1.0.2210.55#add_historychanged)) ///{@endtemplate} final void Function(T controller, WebUri? url, bool? isReload)? onUpdateVisitedHistory; @@ -593,6 +599,7 @@ class PlatformWebViewCreationParams { ///- iOS ///- MacOS ///- Web + ///- Windows ([Official API - ICoreWebView2.add_DocumentTitleChanged](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2?view=webview2-1.0.2210.55#add_documenttitlechanged)) ///{@endtemplate} final void Function(T controller, String? title)? onTitleChanged; @@ -1052,6 +1059,7 @@ class PlatformWebViewCreationParams { ///- iOS ///- MacOS ///- Web + ///- Windows ///{@endtemplate} final URLRequest? initialUrlRequest; @@ -1063,6 +1071,7 @@ class PlatformWebViewCreationParams { ///- iOS ///- MacOS ///- Web + ///- Windows ///{@endtemplate} final String? initialFile; @@ -1074,6 +1083,7 @@ class PlatformWebViewCreationParams { ///- iOS ///- MacOS ///- Web + ///- Windows ///{@endtemplate} final InAppWebViewInitialData? initialData; @@ -1114,6 +1124,7 @@ class PlatformWebViewCreationParams { ///- Android native WebView ///- iOS ///- MacOS + ///- Windows ///{@endtemplate} final UnmodifiableListView? initialUserScripts; diff --git a/flutter_inappwebview_platform_interface/lib/src/inappwebview_platform.dart b/flutter_inappwebview_platform_interface/lib/src/inappwebview_platform.dart index a216e5b9..683ffbdb 100644 --- a/flutter_inappwebview_platform_interface/lib/src/inappwebview_platform.dart +++ b/flutter_inappwebview_platform_interface/lib/src/inappwebview_platform.dart @@ -24,6 +24,7 @@ import 'platform_tracing_controller.dart'; import 'platform_webview_asset_loader.dart'; import 'platform_webview_feature.dart'; import 'in_app_localhost_server.dart'; +import 'webview_environment/platform_webview_environment.dart'; /// Interface for a platform implementation of a WebView. abstract class InAppWebViewPlatform extends PlatformInterface { @@ -430,4 +431,24 @@ abstract class InAppWebViewPlatform extends PlatformInterface { throw UnimplementedError( 'createPlatformChromeSafariBrowserStatic is not implemented on the current platform.'); } + + /// Creates a new [PlatformWebViewEnvironment]. + /// + /// This function should only be called by the app-facing package. + /// Look at using [WebViewEnvironment] in `flutter_inappwebview` instead. + PlatformWebViewEnvironment createPlatformWebViewEnvironment( + PlatformWebViewEnvironmentCreationParams params, + ) { + throw UnimplementedError( + 'createPlatformWebViewEnvironment is not implemented on the current platform.'); + } + + /// Creates a new empty [PlatformWebViewEnvironment] to access static methods. + /// + /// This function should only be called by the app-facing package. + /// Look at using [WebViewEnvironment] in `flutter_inappwebview` instead. + PlatformWebViewEnvironment createPlatformWebViewEnvironmentStatic() { + throw UnimplementedError( + 'createPlatformWebViewEnvironmentStatic is not implemented on the current platform.'); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/main.dart b/flutter_inappwebview_platform_interface/lib/src/main.dart index 5b8d6cb7..bf9626ae 100644 --- a/flutter_inappwebview_platform_interface/lib/src/main.dart +++ b/flutter_inappwebview_platform_interface/lib/src/main.dart @@ -1,5 +1,6 @@ export 'inappwebview_platform.dart'; export 'types/main.dart'; +export 'webview_environment/main.dart'; export 'in_app_webview/main.dart'; export 'in_app_browser/main.dart'; export 'chrome_safari_browser/main.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/platform_cookie_manager.dart b/flutter_inappwebview_platform_interface/lib/src/platform_cookie_manager.dart index a3e98c5e..700cc487 100755 --- a/flutter_inappwebview_platform_interface/lib/src/platform_cookie_manager.dart +++ b/flutter_inappwebview_platform_interface/lib/src/platform_cookie_manager.dart @@ -8,6 +8,7 @@ import 'types/main.dart'; import 'web_uri.dart'; import 'inappwebview_platform.dart'; import 'in_app_webview/platform_headless_in_app_webview.dart'; +import 'webview_environment/platform_webview_environment.dart'; /// Object specifying creation parameters for creating a [PlatformCookieManager]. /// @@ -16,7 +17,13 @@ import 'in_app_webview/platform_headless_in_app_webview.dart'; @immutable class PlatformCookieManagerCreationParams { /// Used by the platform implementation to create a new [PlatformCookieManager]. - const PlatformCookieManagerCreationParams(); + const PlatformCookieManagerCreationParams({this.webViewEnvironment}); + + ///Used to create the [PlatformCookieManager] using the specified environment. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows + final PlatformWebViewEnvironment? webViewEnvironment; } ///{@template flutter_inappwebview_platform_interface.PlatformCookieManager} @@ -33,6 +40,7 @@ class PlatformCookieManagerCreationParams { ///- iOS ///- MacOS ///- Web +///- Windows ///{@endtemplate} abstract class PlatformCookieManager extends PlatformInterface { /// Creates a new [PlatformCookieManager] @@ -87,6 +95,7 @@ abstract class PlatformCookieManager extends PlatformInterface { ///- iOS ([Official API - WKHTTPCookieStore.setCookie](https://developer.apple.com/documentation/webkit/wkhttpcookiestore/2882007-setcookie)) ///- MacOS ([Official API - WKHTTPCookieStore.setCookie](https://developer.apple.com/documentation/webkit/wkhttpcookiestore/2882007-setcookie)) ///- Web + ///- Windows ///{@endtemplate} Future setCookie( {required WebUri url, @@ -126,6 +135,7 @@ abstract class PlatformCookieManager extends PlatformInterface { ///- iOS ([Official API - WKHTTPCookieStore.getAllCookies](https://developer.apple.com/documentation/webkit/wkhttpcookiestore/2882005-getallcookies)) ///- MacOS ([Official API - WKHTTPCookieStore.getAllCookies](https://developer.apple.com/documentation/webkit/wkhttpcookiestore/2882005-getallcookies)) ///- Web + ///- Windows ///{@endtemplate} Future> getCookies( {required WebUri url, @@ -156,6 +166,7 @@ abstract class PlatformCookieManager extends PlatformInterface { ///- iOS ///- MacOS ///- Web + ///- Windows ///{@endtemplate} Future getCookie( {required WebUri url, @@ -191,6 +202,7 @@ abstract class PlatformCookieManager extends PlatformInterface { ///- iOS ([Official API - WKHTTPCookieStore.delete](https://developer.apple.com/documentation/webkit/wkhttpcookiestore/2882009-delete) ///- MacOS ([Official API - WKHTTPCookieStore.delete](https://developer.apple.com/documentation/webkit/wkhttpcookiestore/2882009-delete) ///- Web + ///- Windows ///{@endtemplate} Future deleteCookie( {required WebUri url, @@ -228,6 +240,7 @@ abstract class PlatformCookieManager extends PlatformInterface { ///- iOS ///- MacOS ///- Web + ///- Windows ///{@endtemplate} Future deleteCookies( {required WebUri url, @@ -254,6 +267,7 @@ abstract class PlatformCookieManager extends PlatformInterface { ///- Android native WebView ([Official API - CookieManager.removeAllCookies](https://developer.android.com/reference/android/webkit/CookieManager#removeAllCookies(android.webkit.ValueCallback%3Cjava.lang.Boolean%3E))) ///- iOS ([Official API - WKWebsiteDataStore.removeData](https://developer.apple.com/documentation/webkit/wkwebsitedatastore/1532938-removedata)) ///- MacOS ([Official API - WKWebsiteDataStore.removeData](https://developer.apple.com/documentation/webkit/wkwebsitedatastore/1532938-removedata)) + ///- Windows ///{@endtemplate} Future deleteAllCookies() { throw UnimplementedError( diff --git a/flutter_inappwebview_platform_interface/lib/src/types/compress_format.dart b/flutter_inappwebview_platform_interface/lib/src/types/compress_format.dart index f7967e24..23a59412 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/compress_format.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/compress_format.dart @@ -11,36 +11,48 @@ class CompressFormat_ { ///Compress to the `PNG` format. ///PNG is lossless, so `quality` is ignored. + @EnumSupportedPlatforms(platforms: [ + EnumAndroidPlatform(), + EnumIOSPlatform(), + EnumMacOSPlatform(), + EnumWindowsPlatform(), + ]) static const PNG = const CompressFormat_._internal("PNG"); ///Compress to the `JPEG` format. ///Quality of `0` means compress for the smallest size. ///`100` means compress for max visual quality. + @EnumSupportedPlatforms(platforms: [ + EnumAndroidPlatform(), + EnumIOSPlatform(), + EnumMacOSPlatform(), + EnumWindowsPlatform(), + ]) static const JPEG = const CompressFormat_._internal("JPEG"); ///Compress to the `WEBP` lossy format. ///Quality of `0` means compress for the smallest size. ///`100` means compress for max visual quality. - /// - ///**NOTE**: available only on Android. + @EnumSupportedPlatforms(platforms: [ + EnumAndroidPlatform(), + EnumWindowsPlatform(), + ]) static const WEBP = const CompressFormat_._internal("WEBP"); ///Compress to the `WEBP` lossy format. ///Quality of `0` means compress for the smallest size. ///`100` means compress for max visual quality. - /// - ///**NOTE**: available only on Android. - /// - ///**NOTE for Android**: available on Android 30+. + @EnumSupportedPlatforms(platforms: [ + EnumAndroidPlatform(available: '30'), + ]) static const WEBP_LOSSY = const CompressFormat_._internal("WEBP_LOSSY"); ///Compress to the `WEBP` lossless format. ///Quality refers to how much effort to put into compression. ///A value of `0` means to compress quickly, resulting in a relatively large file size. ///`100` means to spend more time compressing, resulting in a smaller file. - /// - ///**NOTE**: available only on Android. - /// - ///**NOTE for Android**: available on Android 30+. + @EnumSupportedPlatforms(platforms: [ + EnumAndroidPlatform(available: '30'), + ]) static const WEBP_LOSSLESS = const CompressFormat_._internal("WEBP_LOSSLESS"); } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/compress_format.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/compress_format.g.dart index bad4b339..befe838f 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/compress_format.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/compress_format.g.dart @@ -19,17 +19,31 @@ class CompressFormat { ///Compress to the `JPEG` format. ///Quality of `0` means compress for the smallest size. ///`100` means compress for max visual quality. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS + ///- MacOS + ///- Windows static const JPEG = CompressFormat._internal('JPEG', 'JPEG'); ///Compress to the `PNG` format. ///PNG is lossless, so `quality` is ignored. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS + ///- MacOS + ///- Windows static const PNG = CompressFormat._internal('PNG', 'PNG'); ///Compress to the `WEBP` lossy format. ///Quality of `0` means compress for the smallest size. ///`100` means compress for max visual quality. /// - ///**NOTE**: available only on Android. + ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView + ///- Windows static const WEBP = CompressFormat._internal('WEBP', 'WEBP'); ///Compress to the `WEBP` lossless format. @@ -37,9 +51,8 @@ class CompressFormat { ///A value of `0` means to compress quickly, resulting in a relatively large file size. ///`100` means to spend more time compressing, resulting in a smaller file. /// - ///**NOTE**: available only on Android. - /// - ///**NOTE for Android**: available on Android 30+. + ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView 30+ static const WEBP_LOSSLESS = CompressFormat._internal('WEBP_LOSSLESS', 'WEBP_LOSSLESS'); @@ -47,9 +60,8 @@ class CompressFormat { ///Quality of `0` means compress for the smallest size. ///`100` means compress for max visual quality. /// - ///**NOTE**: available only on Android. - /// - ///**NOTE for Android**: available on Android 30+. + ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView 30+ static const WEBP_LOSSY = CompressFormat._internal('WEBP_LOSSY', 'WEBP_LOSSY'); diff --git a/flutter_inappwebview_platform_interface/lib/src/types/cookie.dart b/flutter_inappwebview_platform_interface/lib/src/types/cookie.dart index d0edfb4b..da4f3803 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/cookie.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/cookie.dart @@ -14,7 +14,8 @@ class Cookie_ { IOSPlatform(), MacOSPlatform(), AndroidPlatform(), - WebPlatform() + WebPlatform(), + WindowsPlatform() ]) String name; @@ -23,7 +24,8 @@ class Cookie_ { IOSPlatform(), MacOSPlatform(), AndroidPlatform(), - WebPlatform() + WebPlatform(), + WindowsPlatform() ]) dynamic value; @@ -33,12 +35,17 @@ class Cookie_ { MacOSPlatform(), AndroidPlatform( note: - "available on Android only if [WebViewFeature.GET_COOKIE_INFO] feature is supported.") + "available on Android only if [WebViewFeature.GET_COOKIE_INFO] feature is supported."), + WindowsPlatform() ]) int? expiresDate; ///Indicates if the cookie is a session only cookie. - @SupportedPlatforms(platforms: [IOSPlatform(), MacOSPlatform()]) + @SupportedPlatforms(platforms: [ + IOSPlatform(), + MacOSPlatform(), + WindowsPlatform() + ]) bool? isSessionOnly; ///The cookie domain. @@ -47,7 +54,8 @@ class Cookie_ { MacOSPlatform(), AndroidPlatform( note: - "available on Android only if [WebViewFeature.GET_COOKIE_INFO] feature is supported.") + "available on Android only if [WebViewFeature.GET_COOKIE_INFO] feature is supported."), + WindowsPlatform() ]) String? domain; @@ -57,7 +65,8 @@ class Cookie_ { MacOSPlatform(), AndroidPlatform( note: - "available on Android only if [WebViewFeature.GET_COOKIE_INFO] feature is supported.") + "available on Android only if [WebViewFeature.GET_COOKIE_INFO] feature is supported."), + WindowsPlatform() ]) HTTPCookieSameSitePolicy_? sameSite; @@ -67,7 +76,8 @@ class Cookie_ { MacOSPlatform(), AndroidPlatform( note: - "available on Android only if [WebViewFeature.GET_COOKIE_INFO] feature is supported.") + "available on Android only if [WebViewFeature.GET_COOKIE_INFO] feature is supported."), + WindowsPlatform() ]) bool? isSecure; @@ -77,7 +87,8 @@ class Cookie_ { MacOSPlatform(), AndroidPlatform( note: - "available on Android only if [WebViewFeature.GET_COOKIE_INFO] feature is supported.") + "available on Android only if [WebViewFeature.GET_COOKIE_INFO] feature is supported."), + WindowsPlatform() ]) bool? isHttpOnly; @@ -87,7 +98,8 @@ class Cookie_ { MacOSPlatform(), AndroidPlatform( note: - "available on Android only if [WebViewFeature.GET_COOKIE_INFO] feature is supported.") + "available on Android only if [WebViewFeature.GET_COOKIE_INFO] feature is supported."), + WindowsPlatform() ]) String? path; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/cookie.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/cookie.g.dart index 8b5e268d..ada5df95 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/cookie.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/cookie.g.dart @@ -16,6 +16,7 @@ class Cookie { ///- iOS ///- MacOS ///- Android native WebView + ///- Windows String? domain; ///The cookie expiration date in milliseconds. @@ -26,6 +27,7 @@ class Cookie { ///- iOS ///- MacOS ///- Android native WebView + ///- Windows int? expiresDate; ///Indicates if the cookie is a http only cookie. @@ -36,6 +38,7 @@ class Cookie { ///- iOS ///- MacOS ///- Android native WebView + ///- Windows bool? isHttpOnly; ///Indicates if the cookie is secure or not. @@ -46,6 +49,7 @@ class Cookie { ///- iOS ///- MacOS ///- Android native WebView + ///- Windows bool? isSecure; ///Indicates if the cookie is a session only cookie. @@ -53,6 +57,7 @@ class Cookie { ///**Officially Supported Platforms/Implementations**: ///- iOS ///- MacOS + ///- Windows bool? isSessionOnly; ///The cookie name. @@ -62,6 +67,7 @@ class Cookie { ///- MacOS ///- Android native WebView ///- Web but iframe requires same origin + ///- Windows String name; ///The cookie path. @@ -72,6 +78,7 @@ class Cookie { ///- iOS ///- MacOS ///- Android native WebView + ///- Windows String? path; ///The cookie same site policy. @@ -82,6 +89,7 @@ class Cookie { ///- iOS ///- MacOS ///- Android native WebView + ///- Windows HTTPCookieSameSitePolicy? sameSite; ///The cookie value. @@ -91,6 +99,7 @@ class Cookie { ///- MacOS ///- Android native WebView ///- Web but iframe requires same origin + ///- Windows dynamic value; Cookie( {this.domain, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/navigation_action.dart b/flutter_inappwebview_platform_interface/lib/src/types/navigation_action.dart index d2495be5..1d12aa35 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/navigation_action.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/navigation_action.dart @@ -54,7 +54,8 @@ class NavigationAction_ { available: "21", apiName: "WebResourceRequest.isRedirect", apiUrl: - "https://developer.android.com/reference/android/webkit/WebResourceRequest#isRedirect()") + "https://developer.android.com/reference/android/webkit/WebResourceRequest#isRedirect()"), + WindowsPlatform() ]) bool? isRedirect; @@ -62,7 +63,7 @@ class NavigationAction_ { @Deprecated("Use navigationType instead") IOSWKNavigationType_? iosWKNavigationType; - ///The type of action triggering the navigation.ì + ///The type of action triggering the navigation. @SupportedPlatforms(platforms: [ IOSPlatform( apiName: "WKNavigationAction.navigationType", @@ -71,7 +72,8 @@ class NavigationAction_ { MacOSPlatform( apiName: "WKNavigationAction.navigationType", apiUrl: - "https://developer.apple.com/documentation/webkit/wknavigationaction/1401914-navigationtype") + "https://developer.apple.com/documentation/webkit/wknavigationaction/1401914-navigationtype"), + WindowsPlatform() ]) NavigationType_? navigationType; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/navigation_action.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/navigation_action.g.dart index 9b24689e..64593a9f 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/navigation_action.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/navigation_action.g.dart @@ -52,13 +52,15 @@ class NavigationAction { /// ///**Officially Supported Platforms/Implementations**: ///- Android native WebView 21+ ([Official API - WebResourceRequest.isRedirect](https://developer.android.com/reference/android/webkit/WebResourceRequest#isRedirect())) + ///- Windows bool? isRedirect; - ///The type of action triggering the navigation.ì + ///The type of action triggering the navigation. /// ///**Officially Supported Platforms/Implementations**: ///- iOS ([Official API - WKNavigationAction.navigationType](https://developer.apple.com/documentation/webkit/wknavigationaction/1401914-navigationtype)) ///- MacOS ([Official API - WKNavigationAction.navigationType](https://developer.apple.com/documentation/webkit/wknavigationaction/1401914-navigationtype)) + ///- Windows NavigationType? navigationType; ///The URL request object associated with the navigation action. diff --git a/flutter_inappwebview_platform_interface/lib/src/types/navigation_type.dart b/flutter_inappwebview_platform_interface/lib/src/types/navigation_type.dart index 72c07c8c..297046fa 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/navigation_type.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/navigation_type.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; import '../in_app_webview/platform_webview.dart'; part 'navigation_type.g.dart'; @@ -6,26 +7,119 @@ part 'navigation_type.g.dart'; @ExchangeableEnum() class NavigationType_ { // ignore: unused_field - final int _value; + final String _value; + // ignore: unused_field + final int? _nativeValue = null; + const NavigationType_._internal(this._value); ///A link with an href attribute was activated by the user. - static const LINK_ACTIVATED = const NavigationType_._internal(0); + @EnumSupportedPlatforms(platforms: [ + EnumIOSPlatform( + apiName: 'WKNavigationType.linkActivated', + apiUrl: + 'https://developer.apple.com/documentation/webkit/wknavigationtype/linkactivated', + value: 0), + EnumMacOSPlatform( + apiName: 'WKNavigationType.linkActivated', + apiUrl: + 'https://developer.apple.com/documentation/webkit/wknavigationtype/linkactivated', + value: 0), + EnumWindowsPlatform( + value: 0 + ), + ]) + static const LINK_ACTIVATED = const NavigationType_._internal('LINK_ACTIVATED'); ///A form was submitted. - static const FORM_SUBMITTED = const NavigationType_._internal(1); + @EnumSupportedPlatforms(platforms: [ + EnumIOSPlatform( + apiName: 'WKNavigationType.formSubmitted', + apiUrl: + 'https://developer.apple.com/documentation/webkit/wknavigationtype/formsubmitted', + value: 1), + EnumMacOSPlatform( + apiName: 'WKNavigationType.formSubmitted', + apiUrl: + 'https://developer.apple.com/documentation/webkit/wknavigationtype/formsubmitted', + value: 1), + ]) + static const FORM_SUBMITTED = const NavigationType_._internal('FORM_SUBMITTED'); ///An item from the back-forward list was requested. - static const BACK_FORWARD = const NavigationType_._internal(2); + @EnumSupportedPlatforms(platforms: [ + EnumIOSPlatform( + apiName: 'WKNavigationType.formSubmitted', + apiUrl: + 'https://developer.apple.com/documentation/webkit/wknavigationtype/formsubmitted', + value: 2), + EnumMacOSPlatform( + apiName: 'WKNavigationType.formSubmitted', + apiUrl: + 'https://developer.apple.com/documentation/webkit/wknavigationtype/formsubmitted', + value: 2), + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_NAVIGATION_KIND_BACK_OR_FORWARD', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_navigation_kind', + value: 1 + ), + ]) + static const BACK_FORWARD = const NavigationType_._internal('BACK_FORWARD'); ///The webpage was reloaded. - static const RELOAD = const NavigationType_._internal(3); + @EnumSupportedPlatforms(platforms: [ + EnumIOSPlatform( + apiName: 'WKNavigationType.reload', + apiUrl: + 'https://developer.apple.com/documentation/webkit/wknavigationtype/reload', + value: 3), + EnumMacOSPlatform( + apiName: 'WKNavigationType.reload', + apiUrl: + 'https://developer.apple.com/documentation/webkit/wknavigationtype/reload', + value: 3), + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_NAVIGATION_KIND_RELOAD', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_navigation_kind', + value: 2 + ), + ]) + static const RELOAD = const NavigationType_._internal('RELOAD'); ///A form was resubmitted (for example by going back, going forward, or reloading). - static const FORM_RESUBMITTED = const NavigationType_._internal(4); + @EnumSupportedPlatforms(platforms: [ + EnumIOSPlatform( + apiName: 'WKNavigationType.formSubmitted', + apiUrl: + 'https://developer.apple.com/documentation/webkit/wknavigationtype/formresubmitted', + value: 4), + EnumMacOSPlatform( + apiName: 'WKNavigationType.formSubmitted', + apiUrl: + 'https://developer.apple.com/documentation/webkit/wknavigationtype/formresubmitted', + value: 4), + ]) + static const FORM_RESUBMITTED = const NavigationType_._internal('FORM_RESUBMITTED'); ///Navigation is taking place for some other reason. - static const OTHER = const NavigationType_._internal(-1); + @EnumSupportedPlatforms(platforms: [ + EnumIOSPlatform( + apiName: 'WKNavigationType.other', + apiUrl: + 'https://developer.apple.com/documentation/webkit/wknavigationtype/other', + value: -1), + EnumMacOSPlatform( + apiName: 'WKNavigationType.other', + apiUrl: + 'https://developer.apple.com/documentation/webkit/wknavigationtype/other', + value: -1), + EnumWindowsPlatform( + value: 3 + ), + ]) + static const OTHER = const NavigationType_._internal('OTHER'); } ///Class that represents the type of action triggering a navigation on iOS for the [PlatformWebViewCreationParams.shouldOverrideUrlLoading] event. diff --git a/flutter_inappwebview_platform_interface/lib/src/types/navigation_type.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/navigation_type.g.dart index bdf1b525..7615cd66 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/navigation_type.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/navigation_type.g.dart @@ -8,31 +8,131 @@ part of 'navigation_type.dart'; ///Class that represents the type of action triggering a navigation for the [PlatformWebViewCreationParams.shouldOverrideUrlLoading] event. class NavigationType { - final int _value; - final int _nativeValue; + final String _value; + final int? _nativeValue; const NavigationType._internal(this._value, this._nativeValue); // ignore: unused_element factory NavigationType._internalMultiPlatform( - int value, Function nativeValue) => + String value, Function nativeValue) => NavigationType._internal(value, nativeValue()); ///An item from the back-forward list was requested. - static const BACK_FORWARD = NavigationType._internal(2, 2); + /// + ///**Officially Supported Platforms/Implementations**: + ///- iOS ([Official API - WKNavigationType.formSubmitted](https://developer.apple.com/documentation/webkit/wknavigationtype/formsubmitted)) + ///- MacOS ([Official API - WKNavigationType.formSubmitted](https://developer.apple.com/documentation/webkit/wknavigationtype/formsubmitted)) + ///- Windows ([Official API - COREWEBVIEW2_NAVIGATION_KIND_BACK_OR_FORWARD](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_navigation_kind)) + static final BACK_FORWARD = + NavigationType._internalMultiPlatform('BACK_FORWARD', () { + switch (defaultTargetPlatform) { + case TargetPlatform.iOS: + return 2; + case TargetPlatform.macOS: + return 2; + case TargetPlatform.windows: + return 1; + default: + break; + } + return null; + }); ///A form was resubmitted (for example by going back, going forward, or reloading). - static const FORM_RESUBMITTED = NavigationType._internal(4, 4); + /// + ///**Officially Supported Platforms/Implementations**: + ///- iOS ([Official API - WKNavigationType.formSubmitted](https://developer.apple.com/documentation/webkit/wknavigationtype/formresubmitted)) + ///- MacOS ([Official API - WKNavigationType.formSubmitted](https://developer.apple.com/documentation/webkit/wknavigationtype/formresubmitted)) + static final FORM_RESUBMITTED = + NavigationType._internalMultiPlatform('FORM_RESUBMITTED', () { + switch (defaultTargetPlatform) { + case TargetPlatform.iOS: + return 4; + case TargetPlatform.macOS: + return 4; + default: + break; + } + return null; + }); ///A form was submitted. - static const FORM_SUBMITTED = NavigationType._internal(1, 1); + /// + ///**Officially Supported Platforms/Implementations**: + ///- iOS ([Official API - WKNavigationType.formSubmitted](https://developer.apple.com/documentation/webkit/wknavigationtype/formsubmitted)) + ///- MacOS ([Official API - WKNavigationType.formSubmitted](https://developer.apple.com/documentation/webkit/wknavigationtype/formsubmitted)) + static final FORM_SUBMITTED = + NavigationType._internalMultiPlatform('FORM_SUBMITTED', () { + switch (defaultTargetPlatform) { + case TargetPlatform.iOS: + return 1; + case TargetPlatform.macOS: + return 1; + default: + break; + } + return null; + }); ///A link with an href attribute was activated by the user. - static const LINK_ACTIVATED = NavigationType._internal(0, 0); + /// + ///**Officially Supported Platforms/Implementations**: + ///- iOS ([Official API - WKNavigationType.linkActivated](https://developer.apple.com/documentation/webkit/wknavigationtype/linkactivated)) + ///- MacOS ([Official API - WKNavigationType.linkActivated](https://developer.apple.com/documentation/webkit/wknavigationtype/linkactivated)) + ///- Windows + static final LINK_ACTIVATED = + NavigationType._internalMultiPlatform('LINK_ACTIVATED', () { + switch (defaultTargetPlatform) { + case TargetPlatform.iOS: + return 0; + case TargetPlatform.macOS: + return 0; + case TargetPlatform.windows: + return 0; + default: + break; + } + return null; + }); ///Navigation is taking place for some other reason. - static const OTHER = NavigationType._internal(-1, -1); + /// + ///**Officially Supported Platforms/Implementations**: + ///- iOS ([Official API - WKNavigationType.other](https://developer.apple.com/documentation/webkit/wknavigationtype/other)) + ///- MacOS ([Official API - WKNavigationType.other](https://developer.apple.com/documentation/webkit/wknavigationtype/other)) + ///- Windows + static final OTHER = NavigationType._internalMultiPlatform('OTHER', () { + switch (defaultTargetPlatform) { + case TargetPlatform.iOS: + return -1; + case TargetPlatform.macOS: + return -1; + case TargetPlatform.windows: + return 3; + default: + break; + } + return null; + }); ///The webpage was reloaded. - static const RELOAD = NavigationType._internal(3, 3); + /// + ///**Officially Supported Platforms/Implementations**: + ///- iOS ([Official API - WKNavigationType.reload](https://developer.apple.com/documentation/webkit/wknavigationtype/reload)) + ///- MacOS ([Official API - WKNavigationType.reload](https://developer.apple.com/documentation/webkit/wknavigationtype/reload)) + ///- Windows ([Official API - COREWEBVIEW2_NAVIGATION_KIND_RELOAD](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_navigation_kind)) + static final RELOAD = NavigationType._internalMultiPlatform('RELOAD', () { + switch (defaultTargetPlatform) { + case TargetPlatform.iOS: + return 3; + case TargetPlatform.macOS: + return 3; + case TargetPlatform.windows: + return 2; + default: + break; + } + return null; + }); ///Set of all values of [NavigationType]. static final Set values = [ @@ -44,8 +144,8 @@ class NavigationType { NavigationType.RELOAD, ].toSet(); - ///Gets a possible [NavigationType] instance from [int] value. - static NavigationType? fromValue(int? value) { + ///Gets a possible [NavigationType] instance from [String] value. + static NavigationType? fromValue(String? value) { if (value != null) { try { return NavigationType.values @@ -70,11 +170,11 @@ class NavigationType { return null; } - ///Gets [int] value. - int toValue() => _value; + ///Gets [String] value. + String toValue() => _value; - ///Gets [int] native value. - int toNativeValue() => _nativeValue; + ///Gets [int?] native value. + int? toNativeValue() => _nativeValue; @override int get hashCode => _value.hashCode; @@ -84,21 +184,7 @@ class NavigationType { @override String toString() { - switch (_value) { - case 2: - return 'BACK_FORWARD'; - case 4: - return 'FORM_RESUBMITTED'; - case 1: - return 'FORM_SUBMITTED'; - case 0: - return 'LINK_ACTIVATED'; - case -1: - return 'OTHER'; - case 3: - return 'RELOAD'; - } - return _value.toString(); + return _value; } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/screenshot_configuration.dart b/flutter_inappwebview_platform_interface/lib/src/types/screenshot_configuration.dart index ce033a58..8d364fb0 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/screenshot_configuration.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/screenshot_configuration.dart @@ -12,6 +12,12 @@ class ScreenshotConfiguration_ { ///The portion of your web view to capture, specified as a rectangle in the view’s coordinate system. ///The default value of this property is `null`, which captures everything in the view’s bounds rectangle. ///If you specify a custom rectangle, it must lie within the bounds rectangle of the `WebView` object. + @SupportedPlatforms(platforms: [ + AndroidPlatform(), + IOSPlatform(), + MacOSPlatform(), + WindowsPlatform() + ]) InAppWebViewRect_? rect; ///The width of the captured image, in points. @@ -19,14 +25,31 @@ class ScreenshotConfiguration_ { ///The web view maintains the aspect ratio of the captured content, but scales it to match the width you specify. /// ///The default value of this property is `null`, which returns an image whose size matches the original size of the captured rectangle. + @SupportedPlatforms(platforms: [ + AndroidPlatform(), + IOSPlatform(), + MacOSPlatform() + ]) double? snapshotWidth; ///The compression format of the captured image. ///The default value is [CompressFormat.PNG]. + @SupportedPlatforms(platforms: [ + AndroidPlatform(), + IOSPlatform(), + MacOSPlatform(), + WindowsPlatform() + ]) CompressFormat_ compressFormat; ///Hint to the compressor, `0-100`. The value is interpreted differently depending on the [CompressFormat]. ///[CompressFormat.PNG] is lossless, so this value is ignored. + @SupportedPlatforms(platforms: [ + AndroidPlatform(), + IOSPlatform(), + MacOSPlatform(), + WindowsPlatform() + ]) int quality; ///Use [afterScreenUpdates] instead. @@ -36,10 +59,10 @@ class ScreenshotConfiguration_ { ///A Boolean value that indicates whether to take the snapshot after incorporating any pending screen updates. ///The default value of this property is `true`, which causes the web view to incorporate any recent changes to the view’s content and then generate the snapshot. ///If you change the value to `false`, the `WebView` takes the snapshot immediately, and before incorporating any new changes. - /// - ///**NOTE**: available only on iOS. - /// - ///**NOTE for iOS**: Available from iOS 13.0+. + @SupportedPlatforms(platforms: [ + IOSPlatform(available: '13.0'), + MacOSPlatform(available: '10.15'), + ]) bool afterScreenUpdates; @ExchangeableObjectConstructor() diff --git a/flutter_inappwebview_platform_interface/lib/src/types/screenshot_configuration.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/screenshot_configuration.g.dart index 1a22c107..460c7890 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/screenshot_configuration.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/screenshot_configuration.g.dart @@ -12,13 +12,19 @@ class ScreenshotConfiguration { ///The default value of this property is `true`, which causes the web view to incorporate any recent changes to the view’s content and then generate the snapshot. ///If you change the value to `false`, the `WebView` takes the snapshot immediately, and before incorporating any new changes. /// - ///**NOTE**: available only on iOS. - /// - ///**NOTE for iOS**: Available from iOS 13.0+. + ///**Officially Supported Platforms/Implementations**: + ///- iOS 13.0+ + ///- MacOS 10.15+ bool afterScreenUpdates; ///The compression format of the captured image. ///The default value is [CompressFormat.PNG]. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS + ///- MacOS + ///- Windows CompressFormat compressFormat; ///Use [afterScreenUpdates] instead. @@ -27,11 +33,23 @@ class ScreenshotConfiguration { ///Hint to the compressor, `0-100`. The value is interpreted differently depending on the [CompressFormat]. ///[CompressFormat.PNG] is lossless, so this value is ignored. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS + ///- MacOS + ///- Windows int quality; ///The portion of your web view to capture, specified as a rectangle in the view’s coordinate system. ///The default value of this property is `null`, which captures everything in the view’s bounds rectangle. ///If you specify a custom rectangle, it must lie within the bounds rectangle of the `WebView` object. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS + ///- MacOS + ///- Windows InAppWebViewRect? rect; ///The width of the captured image, in points. @@ -39,6 +57,11 @@ class ScreenshotConfiguration { ///The web view maintains the aspect ratio of the captured content, but scales it to match the width you specify. /// ///The default value of this property is `null`, which returns an image whose size matches the original size of the captured rectangle. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS + ///- MacOS double? snapshotWidth; ScreenshotConfiguration( {this.rect, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/web_history_item.dart b/flutter_inappwebview_platform_interface/lib/src/types/web_history_item.dart index 84fc615f..973f85d3 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/web_history_item.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/web_history_item.dart @@ -24,6 +24,12 @@ class WebHistoryItem_ { ///Position offset respect to the currentIndex of the back-forward [WebHistory.list]. int? offset; + ///Unique id of the navigation history entry. + @SupportedPlatforms(platforms: [ + WindowsPlatform() + ]) + int? entryId; + WebHistoryItem_( - {this.originalUrl, this.title, this.url, this.index, this.offset}); + {this.originalUrl, this.title, this.url, this.index, this.offset, this.entryId}); } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/web_history_item.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/web_history_item.g.dart index 790e503d..bcffea28 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/web_history_item.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/web_history_item.g.dart @@ -9,6 +9,12 @@ part of 'web_history_item.dart'; ///A convenience class for accessing fields in an entry in the back/forward list of a `WebView`. ///Each [WebHistoryItem] is a snapshot of the requested history item. class WebHistoryItem { + ///Unique id of the navigation history entry. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows + int? entryId; + ///0-based position index in the back-forward [WebHistory.list]. int? index; @@ -24,7 +30,12 @@ class WebHistoryItem { ///Url of this history item. WebUri? url; WebHistoryItem( - {this.index, this.offset, this.originalUrl, this.title, this.url}); + {this.entryId, + this.index, + this.offset, + this.originalUrl, + this.title, + this.url}); ///Gets a possible [WebHistoryItem] instance from a [Map] value. static WebHistoryItem? fromMap(Map? map) { @@ -32,6 +43,7 @@ class WebHistoryItem { return null; } final instance = WebHistoryItem( + entryId: map['entryId'], index: map['index'], offset: map['offset'], originalUrl: @@ -45,6 +57,7 @@ class WebHistoryItem { ///Converts instance to a map. Map toMap() { return { + "entryId": entryId, "index": index, "offset": offset, "originalUrl": originalUrl?.toString(), @@ -60,6 +73,6 @@ class WebHistoryItem { @override String toString() { - return 'WebHistoryItem{index: $index, offset: $offset, originalUrl: $originalUrl, title: $title, url: $url}'; + return 'WebHistoryItem{entryId: $entryId, index: $index, offset: $offset, originalUrl: $originalUrl, title: $title, url: $url}'; } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/web_resource_error_type.dart b/flutter_inappwebview_platform_interface/lib/src/types/web_resource_error_type.dart index 822de4cb..49b91fa8 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/web_resource_error_type.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/web_resource_error_type.dart @@ -60,7 +60,12 @@ class WebResourceErrorType_ { apiName: 'URLError.cannotConnectToHost', apiUrl: 'https://developer.apple.com/documentation/foundation/urlerror/code/2883001-cannotconnecttohost', - value: -1004) + value: -1004), + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_WEB_ERROR_STATUS_CANNOT_CONNECT', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status', + value: 12) ]) static const CANNOT_CONNECT_TO_HOST = WebResourceErrorType_._internal("CANNOT_CONNECT_TO_HOST"); @@ -124,7 +129,12 @@ class WebResourceErrorType_ { apiName: 'URLError.cannotFindHost', apiUrl: 'https://developer.apple.com/documentation/foundation/urlerror/code/2883157-cannotfindhost', - value: -1003) + value: -1003), + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_WEB_ERROR_STATUS_HOST_NAME_NOT_RESOLVED', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status', + value: 13) ]) static const HOST_LOOKUP = WebResourceErrorType_._internal("HOST_LOOKUP"); @@ -186,7 +196,12 @@ class WebResourceErrorType_ { apiName: 'URLError.timedOut', apiUrl: 'https://developer.apple.com/documentation/foundation/urlerror/code/2883027-timedout', - value: -1001) + value: -1001), + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_WEB_ERROR_STATUS_TIMEOUT', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status', + value: 7) ]) static const TIMEOUT = WebResourceErrorType_._internal("TIMEOUT"); @@ -217,7 +232,12 @@ class WebResourceErrorType_ { apiName: 'URLError.unknown', apiUrl: 'https://developer.apple.com/documentation/foundation/urlerror/2293357-unknown', - value: -1) + value: -1), + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_WEB_ERROR_STATUS_UNKNOWN', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status', + value: 0) ]) static const UNKNOWN = WebResourceErrorType_._internal("UNKNOWN"); @@ -276,7 +296,12 @@ class WebResourceErrorType_ { apiName: 'URLError.cancelled', apiUrl: 'https://developer.apple.com/documentation/foundation/urlerror/code/2883178-cancelled', - value: -999) + value: -999), + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_WEB_ERROR_STATUS_OPERATION_CANCELED', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status', + value: 14) ]) static const CANCELLED = WebResourceErrorType_._internal("CANCELLED"); @@ -291,7 +316,12 @@ class WebResourceErrorType_ { apiName: 'URLError.networkConnectionLost', apiUrl: 'https://developer.apple.com/documentation/foundation/urlerror/2293759-networkconnectionlost', - value: -1005) + value: -1005), + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_WEB_ERROR_STATUS_DISCONNECTED', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status', + value: 11) ]) static const NETWORK_CONNECTION_LOST = WebResourceErrorType_._internal("NETWORK_CONNECTION_LOST"); @@ -356,7 +386,12 @@ class WebResourceErrorType_ { apiName: 'URLError.badServerResponse', apiUrl: 'https://developer.apple.com/documentation/foundation/urlerror/2293606-badserverresponse', - value: -1011) + value: -1011), + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_WEB_ERROR_STATUS_ERROR_HTTP_INVALID_SERVER_RESPONSE', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status', + value: 8) ]) static const BAD_SERVER_RESPONSE = WebResourceErrorType_._internal("BAD_SERVER_RESPONSE"); @@ -389,7 +424,12 @@ class WebResourceErrorType_ { apiName: 'URLError.userAuthenticationRequired', apiUrl: 'https://developer.apple.com/documentation/foundation/urlerror/2293560-userauthenticationrequired', - value: -1013) + value: -1013), + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_WEB_ERROR_STATUS_VALID_AUTHENTICATION_CREDENTIALS_REQUIRED', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status', + value: 17), ]) static const USER_AUTHENTICATION_REQUIRED = WebResourceErrorType_._internal("USER_AUTHENTICATION_REQUIRED"); @@ -892,4 +932,64 @@ class WebResourceErrorType_ { ]) static const BACKGROUND_SESSION_WAS_DISCONNECTED = WebResourceErrorType_._internal("BACKGROUND_SESSION_WAS_DISCONNECTED"); + + ///Indicates that the host is unreachable. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_WEB_ERROR_STATUS_SERVER_UNREACHABLE', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status', + value: 6), + ]) + static const SERVER_UNREACHABLE = WebResourceErrorType_._internal("SERVER_UNREACHABLE"); + + ///Indicates that the connection was stopped. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_WEB_ERROR_STATUS_CONNECTION_ABORTED', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status', + value: 9) + ]) + static const CONNECTION_ABORTED = WebResourceErrorType_._internal("CONNECTION_ABORTED"); + + ///Indicates that the connection was reset. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_WEB_ERROR_STATUS_CONNECTION_RESET', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status', + value: 10), + ]) + static const RESET = WebResourceErrorType_._internal("RESET"); + + ///Indicates that the request redirect failed. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_WEB_ERROR_STATUS_REDIRECT_FAILED', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status', + value: 15), + ]) + static const REDIRECT_FAILED = WebResourceErrorType_._internal("REDIRECT_FAILED"); + + ///Indicates that an unexpected error occurred. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_WEB_ERROR_STATUS_UNEXPECTED_ERROR', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status', + value: 16), + ]) + static const UNEXPECTED_ERROR = WebResourceErrorType_._internal("UNEXPECTED_ERROR"); + + ///Indicates that user lacks proper authentication credentials for a proxy server. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_WEB_ERROR_STATUS_VALID_PROXY_AUTHENTICATION_REQUIRED', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status', + value: 18), + ]) + static const VALID_PROXY_AUTHENTICATION_REQUIRED = WebResourceErrorType_._internal("VALID_PROXY_AUTHENTICATION_REQUIRED"); } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/web_resource_error_type.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/web_resource_error_type.g.dart index 4a4f905d..bbf3f6d8 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/web_resource_error_type.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/web_resource_error_type.g.dart @@ -97,6 +97,7 @@ class WebResourceErrorType { ///**Officially Supported Platforms/Implementations**: ///- iOS ([Official API - URLError.badServerResponse](https://developer.apple.com/documentation/foundation/urlerror/2293606-badserverresponse)) ///- MacOS ([Official API - URLError.badServerResponse](https://developer.apple.com/documentation/foundation/urlerror/2293606-badserverresponse)) + ///- Windows ([Official API - COREWEBVIEW2_WEB_ERROR_STATUS_ERROR_HTTP_INVALID_SERVER_RESPONSE](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status)) static final BAD_SERVER_RESPONSE = WebResourceErrorType._internalMultiPlatform('BAD_SERVER_RESPONSE', () { switch (defaultTargetPlatform) { @@ -104,6 +105,8 @@ class WebResourceErrorType { return -1011; case TargetPlatform.macOS: return -1011; + case TargetPlatform.windows: + return 8; default: break; } @@ -154,6 +157,7 @@ class WebResourceErrorType { ///**Officially Supported Platforms/Implementations**: ///- iOS ([Official API - URLError.cancelled](https://developer.apple.com/documentation/foundation/urlerror/code/2883178-cancelled)) ///- MacOS ([Official API - URLError.cancelled](https://developer.apple.com/documentation/foundation/urlerror/code/2883178-cancelled)) + ///- Windows ([Official API - COREWEBVIEW2_WEB_ERROR_STATUS_OPERATION_CANCELED](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status)) static final CANCELLED = WebResourceErrorType._internalMultiPlatform('CANCELLED', () { switch (defaultTargetPlatform) { @@ -161,6 +165,8 @@ class WebResourceErrorType { return -999; case TargetPlatform.macOS: return -999; + case TargetPlatform.windows: + return 14; default: break; } @@ -191,6 +197,7 @@ class WebResourceErrorType { ///- Android native WebView ([Official API - WebViewClient.ERROR_CONNECT](https://developer.android.com/reference/android/webkit/WebViewClient#ERROR_CONNECT)) ///- iOS ([Official API - URLError.cannotConnectToHost](https://developer.apple.com/documentation/foundation/urlerror/code/2883001-cannotconnecttohost)) ///- MacOS ([Official API - URLError.cannotConnectToHost](https://developer.apple.com/documentation/foundation/urlerror/code/2883001-cannotconnecttohost)) + ///- Windows ([Official API - COREWEBVIEW2_WEB_ERROR_STATUS_CANNOT_CONNECT](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status)) static final CANNOT_CONNECT_TO_HOST = WebResourceErrorType._internalMultiPlatform('CANNOT_CONNECT_TO_HOST', () { switch (defaultTargetPlatform) { @@ -200,6 +207,8 @@ class WebResourceErrorType { return -1004; case TargetPlatform.macOS: return -1004; + case TargetPlatform.windows: + return 12; default: break; } @@ -408,6 +417,21 @@ class WebResourceErrorType { return null; }); + ///Indicates that the connection was stopped. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_WEB_ERROR_STATUS_CONNECTION_ABORTED](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status)) + static final CONNECTION_ABORTED = + WebResourceErrorType._internalMultiPlatform('CONNECTION_ABORTED', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 9; + default: + break; + } + return null; + }); + ///The length of the resource data exceeds the maximum allowed. /// ///**Officially Supported Platforms/Implementations**: @@ -558,6 +582,7 @@ class WebResourceErrorType { ///- Android native WebView ([Official API - WebViewClient.ERROR_HOST_LOOKUP](https://developer.android.com/reference/android/webkit/WebViewClient#ERROR_HOST_LOOKUP)) ///- iOS ([Official API - URLError.cannotFindHost](https://developer.apple.com/documentation/foundation/urlerror/code/2883157-cannotfindhost)) ///- MacOS ([Official API - URLError.cannotFindHost](https://developer.apple.com/documentation/foundation/urlerror/code/2883157-cannotfindhost)) + ///- Windows ([Official API - COREWEBVIEW2_WEB_ERROR_STATUS_HOST_NAME_NOT_RESOLVED](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status)) static final HOST_LOOKUP = WebResourceErrorType._internalMultiPlatform('HOST_LOOKUP', () { switch (defaultTargetPlatform) { @@ -567,6 +592,8 @@ class WebResourceErrorType { return -1003; case TargetPlatform.macOS: return -1003; + case TargetPlatform.windows: + return 13; default: break; } @@ -611,6 +638,7 @@ class WebResourceErrorType { ///**Officially Supported Platforms/Implementations**: ///- iOS ([Official API - URLError.networkConnectionLost](https://developer.apple.com/documentation/foundation/urlerror/2293759-networkconnectionlost)) ///- MacOS ([Official API - URLError.networkConnectionLost](https://developer.apple.com/documentation/foundation/urlerror/2293759-networkconnectionlost)) + ///- Windows ([Official API - COREWEBVIEW2_WEB_ERROR_STATUS_DISCONNECTED](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status)) static final NETWORK_CONNECTION_LOST = WebResourceErrorType._internalMultiPlatform('NETWORK_CONNECTION_LOST', () { @@ -619,6 +647,8 @@ class WebResourceErrorType { return -1005; case TargetPlatform.macOS: return -1005; + case TargetPlatform.windows: + return 11; default: break; } @@ -678,6 +708,21 @@ class WebResourceErrorType { return null; }); + ///Indicates that the request redirect failed. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_WEB_ERROR_STATUS_REDIRECT_FAILED](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status)) + static final REDIRECT_FAILED = + WebResourceErrorType._internalMultiPlatform('REDIRECT_FAILED', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 15; + default: + break; + } + return null; + }); + ///A redirect was specified by way of server response code, but the server didn’t accompany this code with a redirect URL. /// ///**Officially Supported Platforms/Implementations**: @@ -716,6 +761,20 @@ class WebResourceErrorType { return null; }); + ///Indicates that the connection was reset. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_WEB_ERROR_STATUS_CONNECTION_RESET](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status)) + static final RESET = WebResourceErrorType._internalMultiPlatform('RESET', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 10; + default: + break; + } + return null; + }); + ///A requested resource couldn't be retrieved. ///This error can indicate a file-not-found situation, or decoding problems that prevent data from being processed correctly. /// @@ -830,12 +889,28 @@ class WebResourceErrorType { return null; }); + ///Indicates that the host is unreachable. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_WEB_ERROR_STATUS_SERVER_UNREACHABLE](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status)) + static final SERVER_UNREACHABLE = + WebResourceErrorType._internalMultiPlatform('SERVER_UNREACHABLE', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 6; + default: + break; + } + return null; + }); + ///Connection timed out. /// ///**Officially Supported Platforms/Implementations**: ///- Android native WebView ([Official API - WebViewClient.ERROR_TIMEOUT](https://developer.android.com/reference/android/webkit/WebViewClient#ERROR_TIMEOUT)) ///- iOS ([Official API - URLError.timedOut](https://developer.apple.com/documentation/foundation/urlerror/code/2883027-timedout)) ///- MacOS ([Official API - URLError.timedOut](https://developer.apple.com/documentation/foundation/urlerror/code/2883027-timedout)) + ///- Windows ([Official API - COREWEBVIEW2_WEB_ERROR_STATUS_TIMEOUT](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status)) static final TIMEOUT = WebResourceErrorType._internalMultiPlatform('TIMEOUT', () { switch (defaultTargetPlatform) { @@ -845,6 +920,8 @@ class WebResourceErrorType { return -1001; case TargetPlatform.macOS: return -1001; + case TargetPlatform.windows: + return 7; default: break; } @@ -887,12 +964,28 @@ class WebResourceErrorType { return null; }); + ///Indicates that an unexpected error occurred. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_WEB_ERROR_STATUS_UNEXPECTED_ERROR](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status)) + static final UNEXPECTED_ERROR = + WebResourceErrorType._internalMultiPlatform('UNEXPECTED_ERROR', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 16; + default: + break; + } + return null; + }); + ///The URL Loading System encountered an error that it can’t interpret. /// ///**Officially Supported Platforms/Implementations**: ///- Android native WebView ([Official API - WebViewClient.ERROR_UNKNOWN](https://developer.android.com/reference/android/webkit/WebViewClient#ERROR_UNKNOWN)) ///- iOS ([Official API - URLError.unknown](https://developer.apple.com/documentation/foundation/urlerror/2293357-unknown)) ///- MacOS ([Official API - URLError.unknown](https://developer.apple.com/documentation/foundation/urlerror/2293357-unknown)) + ///- Windows ([Official API - COREWEBVIEW2_WEB_ERROR_STATUS_UNKNOWN](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status)) static final UNKNOWN = WebResourceErrorType._internalMultiPlatform('UNKNOWN', () { switch (defaultTargetPlatform) { @@ -902,6 +995,8 @@ class WebResourceErrorType { return -1; case TargetPlatform.macOS: return -1; + case TargetPlatform.windows: + return 0; default: break; } @@ -982,6 +1077,7 @@ class WebResourceErrorType { ///**Officially Supported Platforms/Implementations**: ///- iOS ([Official API - URLError.userAuthenticationRequired](https://developer.apple.com/documentation/foundation/urlerror/2293560-userauthenticationrequired)) ///- MacOS ([Official API - URLError.userAuthenticationRequired](https://developer.apple.com/documentation/foundation/urlerror/2293560-userauthenticationrequired)) + ///- Windows ([Official API - COREWEBVIEW2_WEB_ERROR_STATUS_VALID_AUTHENTICATION_CREDENTIALS_REQUIRED](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status)) static final USER_AUTHENTICATION_REQUIRED = WebResourceErrorType._internalMultiPlatform( 'USER_AUTHENTICATION_REQUIRED', () { @@ -990,6 +1086,8 @@ class WebResourceErrorType { return -1013; case TargetPlatform.macOS: return -1013; + case TargetPlatform.windows: + return 17; default: break; } @@ -1016,6 +1114,22 @@ class WebResourceErrorType { return null; }); + ///Indicates that user lacks proper authentication credentials for a proxy server. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_WEB_ERROR_STATUS_VALID_PROXY_AUTHENTICATION_REQUIRED](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#corewebview2_web_error_status)) + static final VALID_PROXY_AUTHENTICATION_REQUIRED = + WebResourceErrorType._internalMultiPlatform( + 'VALID_PROXY_AUTHENTICATION_REQUIRED', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 18; + default: + break; + } + return null; + }); + ///A server reported that a URL has a non-zero content length, but terminated the network connection gracefully without sending any data. /// ///**Officially Supported Platforms/Implementations**: @@ -1057,6 +1171,7 @@ class WebResourceErrorType { WebResourceErrorType.CANNOT_WRITE_TO_FILE, WebResourceErrorType.CLIENT_CERTIFICATE_REJECTED, WebResourceErrorType.CLIENT_CERTIFICATE_REQUIRED, + WebResourceErrorType.CONNECTION_ABORTED, WebResourceErrorType.DATA_LENGTH_EXCEEDS_MAXIMUM, WebResourceErrorType.DATA_NOT_ALLOWED, WebResourceErrorType.DOWNLOAD_DECODING_FAILED_MID_STREAM, @@ -1072,17 +1187,21 @@ class WebResourceErrorType { WebResourceErrorType.NOT_CONNECTED_TO_INTERNET, WebResourceErrorType.NO_PERMISSIONS_TO_READ_FILE, WebResourceErrorType.PROXY_AUTHENTICATION, + WebResourceErrorType.REDIRECT_FAILED, WebResourceErrorType.REDIRECT_TO_NON_EXISTENT_LOCATION, WebResourceErrorType.REQUEST_BODY_STREAM_EXHAUSTED, + WebResourceErrorType.RESET, WebResourceErrorType.RESOURCE_UNAVAILABLE, WebResourceErrorType.SECURE_CONNECTION_FAILED, WebResourceErrorType.SERVER_CERTIFICATE_HAS_BAD_DATE, WebResourceErrorType.SERVER_CERTIFICATE_HAS_UNKNOWN_ROOT, WebResourceErrorType.SERVER_CERTIFICATE_NOT_YET_VALID, WebResourceErrorType.SERVER_CERTIFICATE_UNTRUSTED, + WebResourceErrorType.SERVER_UNREACHABLE, WebResourceErrorType.TIMEOUT, WebResourceErrorType.TOO_MANY_REDIRECTS, WebResourceErrorType.TOO_MANY_REQUESTS, + WebResourceErrorType.UNEXPECTED_ERROR, WebResourceErrorType.UNKNOWN, WebResourceErrorType.UNSAFE_RESOURCE, WebResourceErrorType.UNSUPPORTED_AUTH_SCHEME, @@ -1090,6 +1209,7 @@ class WebResourceErrorType { WebResourceErrorType.USER_AUTHENTICATION_FAILED, WebResourceErrorType.USER_AUTHENTICATION_REQUIRED, WebResourceErrorType.USER_CANCELLED_AUTHENTICATION, + WebResourceErrorType.VALID_PROXY_AUTHENTICATION_REQUIRED, WebResourceErrorType.ZERO_BYTE_RESOURCE, ].toSet(); diff --git a/flutter_inappwebview_platform_interface/lib/src/types/window_type.dart b/flutter_inappwebview_platform_interface/lib/src/types/window_type.dart index 2d49b9c9..74673224 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/window_type.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/window_type.dart @@ -12,11 +12,11 @@ class WindowType_ { const WindowType_._internal(this._value); ///Adds the new browser window as a separate new window from the main window. - @EnumSupportedPlatforms(platforms: [EnumMacOSPlatform(value: 'WINDOW')]) + @EnumSupportedPlatforms(platforms: [EnumMacOSPlatform(value: 'WINDOW'), EnumWindowsPlatform(value: 'WINDOW')]) static const WINDOW = const WindowType_._internal('WINDOW'); ///Adds the new browser window as a child window of the main window. - @EnumSupportedPlatforms(platforms: [EnumMacOSPlatform(value: 'CHILD')]) + @EnumSupportedPlatforms(platforms: [EnumMacOSPlatform(value: 'CHILD'), EnumWindowsPlatform(value: 'CHILD')]) static const CHILD = const WindowType_._internal('CHILD'); ///Adds the new browser window as a new tab in a tabbed window of the main window. diff --git a/flutter_inappwebview_platform_interface/lib/src/types/window_type.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/window_type.g.dart index b9be001c..78d757c1 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/window_type.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/window_type.g.dart @@ -20,10 +20,13 @@ class WindowType { /// ///**Officially Supported Platforms/Implementations**: ///- MacOS + ///- Windows static final CHILD = WindowType._internalMultiPlatform('CHILD', () { switch (defaultTargetPlatform) { case TargetPlatform.macOS: return 'CHILD'; + case TargetPlatform.windows: + return 'CHILD'; default: break; } @@ -48,10 +51,13 @@ class WindowType { /// ///**Officially Supported Platforms/Implementations**: ///- MacOS + ///- Windows static final WINDOW = WindowType._internalMultiPlatform('WINDOW', () { switch (defaultTargetPlatform) { case TargetPlatform.macOS: return 'WINDOW'; + case TargetPlatform.windows: + return 'WINDOW'; default: break; } diff --git a/flutter_inappwebview_platform_interface/lib/src/web_storage/platform_web_storage.dart b/flutter_inappwebview_platform_interface/lib/src/web_storage/platform_web_storage.dart index 84ecd089..c47eb753 100644 --- a/flutter_inappwebview_platform_interface/lib/src/web_storage/platform_web_storage.dart +++ b/flutter_inappwebview_platform_interface/lib/src/web_storage/platform_web_storage.dart @@ -33,6 +33,7 @@ class PlatformWebStorageCreationParams { ///- iOS ///- MacOS ///- Web +///- Windows ///{@endtemplate} abstract class PlatformWebStorage extends PlatformInterface implements Disposable { diff --git a/flutter_inappwebview_platform_interface/lib/src/webview_environment/main.dart b/flutter_inappwebview_platform_interface/lib/src/webview_environment/main.dart new file mode 100644 index 00000000..09b2d039 --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/webview_environment/main.dart @@ -0,0 +1,2 @@ +export 'platform_webview_environment.dart'; +export 'webview_environment_settings.dart' show WebViewEnvironmentSettings; \ No newline at end of file diff --git a/flutter_inappwebview_platform_interface/lib/src/webview_environment/platform_webview_environment.dart b/flutter_inappwebview_platform_interface/lib/src/webview_environment/platform_webview_environment.dart new file mode 100644 index 00000000..0aea7510 --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/webview_environment/platform_webview_environment.dart @@ -0,0 +1,147 @@ +import 'package:flutter/foundation.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +import '../debug_logging_settings.dart'; +import '../inappwebview_platform.dart'; +import '../types/disposable.dart'; +import 'webview_environment_settings.dart'; + +/// Object specifying creation parameters for creating a [PlatformWebViewEnvironment]. +/// +/// Platform specific implementations can add additional fields by extending +/// this class. +@immutable +class PlatformWebViewEnvironmentCreationParams { + /// Used by the platform implementation to create a new [PlatformWebViewEnvironment]. + const PlatformWebViewEnvironmentCreationParams({this.settings}); + + ///{@macro flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.settings} + final WebViewEnvironmentSettings? settings; +} + +///Controls a WebView Environment used by WebView instances. +///Use [dispose] when not needed anymore to release references. +/// +///**Officially Supported Platforms/Implementations**: +///- Windows +abstract class PlatformWebViewEnvironment extends PlatformInterface + implements Disposable { + ///Debug settings used by [PlatformWebViewEnvironment]. + static DebugLoggingSettings debugLoggingSettings = DebugLoggingSettings( + maxLogMessageLength: 1000 + ); + + /// Creates a new [PlatformInAppWebViewController] + factory PlatformWebViewEnvironment( + PlatformWebViewEnvironmentCreationParams params) { + assert( + InAppWebViewPlatform.instance != null, + 'A platform implementation for `flutter_inappwebview` has not been set. Please ' + 'ensure that an implementation of `InAppWebViewPlatform` has been set to ' + '`InAppWebViewPlatform.instance` before use. For unit testing, ' + '`InAppWebViewPlatform.instance` can be set with your own test implementation.', + ); + final PlatformWebViewEnvironment webViewEnvironment = + InAppWebViewPlatform.instance! + .createPlatformWebViewEnvironment(params); + PlatformInterface.verify(webViewEnvironment, _token); + return webViewEnvironment; + } + + /// Creates a new [PlatformWebViewEnvironment] to access static methods. + factory PlatformWebViewEnvironment.static() { + assert( + InAppWebViewPlatform.instance != null, + 'A platform implementation for `flutter_inappwebview` has not been set. Please ' + 'ensure that an implementation of `InAppWebViewPlatform` has been set to ' + '`InAppWebViewPlatform.instance` before use. For unit testing, ' + '`InAppWebViewPlatform.instance` can be set with your own test implementation.', + ); + final PlatformWebViewEnvironment webViewEnvironment = + InAppWebViewPlatform.instance! + .createPlatformWebViewEnvironmentStatic(); + PlatformInterface.verify(webViewEnvironment, _token); + return webViewEnvironment; + } + + /// Used by the platform implementation to create a new [PlatformWebViewEnvironment]. + /// + /// Should only be used by platform implementations because they can't extend + /// a class that only contains a factory constructor. + @protected + PlatformWebViewEnvironment.implementation(this.params) + : super(token: _token); + + static final Object _token = Object(); + + /// The parameters used to initialize the [PlatformWebViewEnvironment]. + final PlatformWebViewEnvironmentCreationParams params; + + ///{@template flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.id} + /// WebView Environment ID. + ///{@endtemplate} + String get id => + throw UnimplementedError('id is not implemented on the current platform'); + + ///{@template flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.settings} + /// WebView Environment settings. + ///{@endtemplate} + WebViewEnvironmentSettings? get settings => params.settings; + + ///{@template flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.create} + ///Creates the [PlatformWebViewEnvironment] using [settings]. + /// + ///Check https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#createcorewebview2environmentwithoptions + ///for more info. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - CreateCoreWebView2EnvironmentWithOptions](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#createcorewebview2environmentwithoptions)) + ///{@endtemplate} + Future create( + {WebViewEnvironmentSettings? settings}) { + throw UnimplementedError( + 'create is not implemented on the current platform'); + } + + ///{@template flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.getAvailableVersion} + ///Get the browser version info including channel name if it is not the WebView2 Runtime. + /// + ///Channel names are Beta, Dev, and Canary. + ///If an override exists for the browserExecutableFolder or the channel preference, the override is used. + ///If an override is not specified, then the parameter value passed to [getAvailableVersion] is used. + ///Returns `null` if it fails to find an installed WebView2 runtime or non-stable Microsoft Edge installation. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - GetAvailableCoreWebView2BrowserVersionString](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#comparebrowserversions)) + ///{@endtemplate} + Future getAvailableVersion( + {String? browserExecutableFolder}) { + throw UnimplementedError( + 'getAvailableVersion is not implemented on the current platform'); + } + + ///{@template flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.compareBrowserVersions} + ///This method is for anyone want to compare version correctly to determine which version is newer, older or same. + /// + ///Use it to determine whether to use webview2 or certain feature based upon version. + ///Sets the value of result to `-1`, `0` or `1` if version1 is less than, equal or greater than version2 respectively. + ///Returns `null` if it fails to parse any of the version strings. + ///Directly use the version info obtained from [getAvailableVersion] with input, channel information is ignored. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - CompareBrowserVersions](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#comparebrowserversions)) + ///{@endtemplate} + Future compareBrowserVersions( + {required String version1, required String version2}) { + throw UnimplementedError( + 'compareBrowserVersions is not implemented on the current platform'); + } + + ///{@template flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.dispose} + ///Disposes the WebView Environment reference. + ///{@endtemplate} + Future dispose() { + throw UnimplementedError( + 'dispose is not implemented on the current platform'); + } +} \ No newline at end of file diff --git a/flutter_inappwebview_platform_interface/lib/src/webview_environment/webview_environment_settings.dart b/flutter_inappwebview_platform_interface/lib/src/webview_environment/webview_environment_settings.dart new file mode 100644 index 00000000..0a7ee483 --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/webview_environment/webview_environment_settings.dart @@ -0,0 +1,91 @@ +import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + +import 'platform_webview_environment.dart'; + +part 'webview_environment_settings.g.dart'; + +///This class represents all the [PlatformWebViewEnvironment] settings available. +/// +///The [browserExecutableFolder], [userDataFolder] and [additionalBrowserArguments] +///may be overridden by values either specified in environment variables or in the registry. +@SupportedPlatforms(platforms: [ + WindowsPlatform() +]) +@ExchangeableObject(copyMethod: true) +class WebViewEnvironmentSettings_ { + ///Use [browserExecutableFolder] to specify whether WebView2 controls use a fixed + ///or installed version of the WebView2 Runtime that exists on a user machine. + ///To use a fixed version of the WebView2 Runtime, pass the folder path that contains + ///the fixed version of the WebView2 Runtime to [browserExecutableFolder]. + ///BrowserExecutableFolder supports both relative (to the application's executable) and absolute files paths. + ///To create WebView2 controls that use the installed version of the WebView2 Runtime that exists on user machines, + ///pass a `null` or empty string to [browserExecutableFolder]. + ///In this scenario, the API tries to find a compatible version of the WebView2 Runtime + ///that is installed on the user machine (first at the machine level, and then per user) using the selected channel preference. + ///The path of fixed version of the WebView2 Runtime should not contain `\Edge\Application\`. + ///When such a path is used, the API fails with `HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)`. + /// + ///The default channel search order is the WebView2 Runtime, Beta, Dev, and Canary. + ///When an override `WEBVIEW2_RELEASE_CHANNEL_PREFERENCE` environment variable or + ///applicable `releaseChannelPreference` registry value is set to `1`, the channel search order is reversed. + @SupportedPlatforms(platforms: [ + WindowsPlatform(apiName: 'CreateCoreWebView2EnvironmentWithOptions.browserExecutableFolder', apiUrl: 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#createcorewebview2environmentwithoptions') + ]) + final String? browserExecutableFolder; + + ///You may specify the [userDataFolder] to change the default user data folder location for WebView2. + ///The path is either an absolute file path or a relative file path that is interpreted as relative + ///to the compiled code for the current process. + ///For UWP apps, the default user data folder is the app data folder for the package. + ///For non-UWP apps, the default user data (`{Executable File Name}.WebView2`) folder + ///is created in the same directory next to the compiled code for the app. + ///WebView2 creation fails if the compiled code is running in a directory in which the + ///process does not have permission to create a new directory. + ///The app is responsible to clean up the associated user data folder when it is done. + /// + ///**NOTE**: As a browser process may be shared among WebViews, + ///WebView creation fails with `HRESULT_FROM_WIN32(ERROR_INVALID_STATE)` if the specified + ///options does not match the options of the WebViews that are currently + ///running in the shared browser process. + @SupportedPlatforms(platforms: [ + WindowsPlatform(apiName: 'CreateCoreWebView2EnvironmentWithOptions.userDataFolder', apiUrl: 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#createcorewebview2environmentwithoptions') + ]) + final String? userDataFolder; + + ///If there are multiple switches, there should be a space in between them. + ///The one exception is if multiple features are being enabled/disabled for a single switch, + ///in which case the features should be comma-seperated. + ///Example: `"--disable-features=feature1,feature2 --some-other-switch --do-something"` + @SupportedPlatforms(platforms: [ + WindowsPlatform(apiName: 'ICoreWebView2EnvironmentOptions.put_AdditionalBrowserArguments', apiUrl: 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions?view=webview2-1.0.2210.55#put_additionalbrowserarguments') + ]) + final String? additionalBrowserArguments; + + ///This property is used to enable single sign on with Azure Active Directory (AAD) + ///and personal Microsoft Account (MSA) resources inside WebView. + @SupportedPlatforms(platforms: [ + WindowsPlatform(apiName: 'ICoreWebView2EnvironmentOptions.put_AllowSingleSignOnUsingOSPrimaryAccount', apiUrl: 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions?view=webview2-1.0.2210.55#put_allowsinglesignonusingosprimaryaccount') + ]) + final bool? allowSingleSignOnUsingOSPrimaryAccount; + + ///The default display language for WebView. + @SupportedPlatforms(platforms: [ + WindowsPlatform(apiName: 'ICoreWebView2EnvironmentOptions.put_Language', apiUrl: 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions?view=webview2-1.0.2210.55#put_language') + ]) + final String? language; + + ///Specifies the version of the WebView2 Runtime binaries required to be compatible with your app. + @SupportedPlatforms(platforms: [ + WindowsPlatform(apiName: 'ICoreWebView2EnvironmentOptions.put_TargetCompatibleBrowserVersion', apiUrl: 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions?view=webview2-1.0.2210.55#put_targetcompatiblebrowserversion') + ]) + final String? targetCompatibleBrowserVersion; + + WebViewEnvironmentSettings_({ + this.browserExecutableFolder, + this.userDataFolder, + this.additionalBrowserArguments, + this.allowSingleSignOnUsingOSPrimaryAccount, + this.language, + this.targetCompatibleBrowserVersion + }); +} \ No newline at end of file diff --git a/flutter_inappwebview_platform_interface/lib/src/webview_environment/webview_environment_settings.g.dart b/flutter_inappwebview_platform_interface/lib/src/webview_environment/webview_environment_settings.g.dart new file mode 100644 index 00000000..eb62b39f --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/webview_environment/webview_environment_settings.g.dart @@ -0,0 +1,140 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'webview_environment_settings.dart'; + +// ************************************************************************** +// ExchangeableObjectGenerator +// ************************************************************************** + +///This class represents all the [PlatformWebViewEnvironment] settings available. +/// +///The [browserExecutableFolder], [userDataFolder] and [additionalBrowserArguments] +///may be overridden by values either specified in environment variables or in the registry. +/// +///**Officially Supported Platforms/Implementations**: +///- Windows +class WebViewEnvironmentSettings { + ///If there are multiple switches, there should be a space in between them. + ///The one exception is if multiple features are being enabled/disabled for a single switch, + ///in which case the features should be comma-seperated. + ///Example: `"--disable-features=feature1,feature2 --some-other-switch --do-something"` + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - ICoreWebView2EnvironmentOptions.put_AdditionalBrowserArguments](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions?view=webview2-1.0.2210.55#put_additionalbrowserarguments)) + final String? additionalBrowserArguments; + + ///This property is used to enable single sign on with Azure Active Directory (AAD) + ///and personal Microsoft Account (MSA) resources inside WebView. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - ICoreWebView2EnvironmentOptions.put_AllowSingleSignOnUsingOSPrimaryAccount](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions?view=webview2-1.0.2210.55#put_allowsinglesignonusingosprimaryaccount)) + final bool? allowSingleSignOnUsingOSPrimaryAccount; + + ///Use [browserExecutableFolder] to specify whether WebView2 controls use a fixed + ///or installed version of the WebView2 Runtime that exists on a user machine. + ///To use a fixed version of the WebView2 Runtime, pass the folder path that contains + ///the fixed version of the WebView2 Runtime to [browserExecutableFolder]. + ///BrowserExecutableFolder supports both relative (to the application's executable) and absolute files paths. + ///To create WebView2 controls that use the installed version of the WebView2 Runtime that exists on user machines, + ///pass a `null` or empty string to [browserExecutableFolder]. + ///In this scenario, the API tries to find a compatible version of the WebView2 Runtime + ///that is installed on the user machine (first at the machine level, and then per user) using the selected channel preference. + ///The path of fixed version of the WebView2 Runtime should not contain `\Edge\Application\`. + ///When such a path is used, the API fails with `HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)`. + /// + ///The default channel search order is the WebView2 Runtime, Beta, Dev, and Canary. + ///When an override `WEBVIEW2_RELEASE_CHANNEL_PREFERENCE` environment variable or + ///applicable `releaseChannelPreference` registry value is set to `1`, the channel search order is reversed. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - CreateCoreWebView2EnvironmentWithOptions.browserExecutableFolder](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#createcorewebview2environmentwithoptions)) + final String? browserExecutableFolder; + + ///The default display language for WebView. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - ICoreWebView2EnvironmentOptions.put_Language](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions?view=webview2-1.0.2210.55#put_language)) + final String? language; + + ///Specifies the version of the WebView2 Runtime binaries required to be compatible with your app. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - ICoreWebView2EnvironmentOptions.put_TargetCompatibleBrowserVersion](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions?view=webview2-1.0.2210.55#put_targetcompatiblebrowserversion)) + final String? targetCompatibleBrowserVersion; + + ///You may specify the [userDataFolder] to change the default user data folder location for WebView2. + ///The path is either an absolute file path or a relative file path that is interpreted as relative + ///to the compiled code for the current process. + ///For UWP apps, the default user data folder is the app data folder for the package. + ///For non-UWP apps, the default user data (`{Executable File Name}.WebView2`) folder + ///is created in the same directory next to the compiled code for the app. + ///WebView2 creation fails if the compiled code is running in a directory in which the + ///process does not have permission to create a new directory. + ///The app is responsible to clean up the associated user data folder when it is done. + /// + ///**NOTE**: As a browser process may be shared among WebViews, + ///WebView creation fails with `HRESULT_FROM_WIN32(ERROR_INVALID_STATE)` if the specified + ///options does not match the options of the WebViews that are currently + ///running in the shared browser process. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - CreateCoreWebView2EnvironmentWithOptions.userDataFolder](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#createcorewebview2environmentwithoptions)) + final String? userDataFolder; + + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows + WebViewEnvironmentSettings( + {this.additionalBrowserArguments, + this.allowSingleSignOnUsingOSPrimaryAccount, + this.browserExecutableFolder, + this.language, + this.targetCompatibleBrowserVersion, + this.userDataFolder}); + + ///Gets a possible [WebViewEnvironmentSettings] instance from a [Map] value. + static WebViewEnvironmentSettings? fromMap(Map? map) { + if (map == null) { + return null; + } + final instance = WebViewEnvironmentSettings( + additionalBrowserArguments: map['additionalBrowserArguments'], + allowSingleSignOnUsingOSPrimaryAccount: + map['allowSingleSignOnUsingOSPrimaryAccount'], + browserExecutableFolder: map['browserExecutableFolder'], + language: map['language'], + targetCompatibleBrowserVersion: map['targetCompatibleBrowserVersion'], + userDataFolder: map['userDataFolder'], + ); + return instance; + } + + ///Converts instance to a map. + Map toMap() { + return { + "additionalBrowserArguments": additionalBrowserArguments, + "allowSingleSignOnUsingOSPrimaryAccount": + allowSingleSignOnUsingOSPrimaryAccount, + "browserExecutableFolder": browserExecutableFolder, + "language": language, + "targetCompatibleBrowserVersion": targetCompatibleBrowserVersion, + "userDataFolder": userDataFolder, + }; + } + + ///Converts instance to a map. + Map toJson() { + return toMap(); + } + + ///Returns a copy of WebViewEnvironmentSettings. + WebViewEnvironmentSettings copy() { + return WebViewEnvironmentSettings.fromMap(toMap()) ?? + WebViewEnvironmentSettings(); + } + + @override + String toString() { + return 'WebViewEnvironmentSettings{additionalBrowserArguments: $additionalBrowserArguments, allowSingleSignOnUsingOSPrimaryAccount: $allowSingleSignOnUsingOSPrimaryAccount, browserExecutableFolder: $browserExecutableFolder, language: $language, targetCompatibleBrowserVersion: $targetCompatibleBrowserVersion, userDataFolder: $userDataFolder}'; + } +} diff --git a/flutter_inappwebview_platform_interface/pubspec.yaml b/flutter_inappwebview_platform_interface/pubspec.yaml index bbb0f0a9..a3d59800 100644 --- a/flutter_inappwebview_platform_interface/pubspec.yaml +++ b/flutter_inappwebview_platform_interface/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_inappwebview_platform_interface description: A common platform interface for the flutter_inappwebview plugin. -version: 1.0.10 +version: 1.0.11 homepage: https://inappwebview.dev/ repository: https://github.com/pichillilorenzo/flutter_inappwebview/tree/master/flutter_inappwebview_platform_interface issue_tracker: https://github.com/pichillilorenzo/flutter_inappwebview/issues diff --git a/flutter_inappwebview_windows/.gitignore b/flutter_inappwebview_windows/.gitignore new file mode 100644 index 00000000..ac5aa989 --- /dev/null +++ b/flutter_inappwebview_windows/.gitignore @@ -0,0 +1,29 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +build/ diff --git a/flutter_inappwebview_windows/.metadata b/flutter_inappwebview_windows/.metadata new file mode 100644 index 00000000..f0767e5b --- /dev/null +++ b/flutter_inappwebview_windows/.metadata @@ -0,0 +1,30 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9" + channel: "stable" + +project_type: plugin + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 + base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 + - platform: windows + create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 + base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/flutter_inappwebview_windows/CHANGELOG.md b/flutter_inappwebview_windows/CHANGELOG.md new file mode 100644 index 00000000..41cc7d81 --- /dev/null +++ b/flutter_inappwebview_windows/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +* TODO: Describe initial release. diff --git a/flutter_inappwebview_windows/LICENSE b/flutter_inappwebview_windows/LICENSE new file mode 100644 index 00000000..ba75c69f --- /dev/null +++ b/flutter_inappwebview_windows/LICENSE @@ -0,0 +1 @@ +TODO: Add your license here. diff --git a/flutter_inappwebview_windows/README.md b/flutter_inappwebview_windows/README.md new file mode 100644 index 00000000..c410a93e --- /dev/null +++ b/flutter_inappwebview_windows/README.md @@ -0,0 +1,15 @@ +# flutter_inappwebview_windows + +A new Flutter project. + +## Getting Started + +This project is a starting point for a Flutter +[plug-in package](https://flutter.dev/developing-packages/), +a specialized package that includes platform-specific implementation code for +Android and/or iOS. + +For help getting started with Flutter development, view the +[online documentation](https://flutter.dev/docs), which offers tutorials, +samples, guidance on mobile development, and a full API reference. + diff --git a/flutter_inappwebview_windows/analysis_options.yaml b/flutter_inappwebview_windows/analysis_options.yaml new file mode 100644 index 00000000..84964c30 --- /dev/null +++ b/flutter_inappwebview_windows/analysis_options.yaml @@ -0,0 +1,15 @@ +include: package:flutter_lints/flutter.yaml + +linter: + rules: + constant_identifier_names: ignore + deprecated_member_use_from_same_package: ignore + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options +analyzer: + errors: + deprecated_member_use: ignore + deprecated_member_use_from_same_package: ignore + unnecessary_cast: ignore + unnecessary_import: ignore diff --git a/flutter_inappwebview_windows/example/.gitignore b/flutter_inappwebview_windows/example/.gitignore new file mode 100644 index 00000000..29a3a501 --- /dev/null +++ b/flutter_inappwebview_windows/example/.gitignore @@ -0,0 +1,43 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/flutter_inappwebview_windows/example/README.md b/flutter_inappwebview_windows/example/README.md new file mode 100644 index 00000000..a07c91fa --- /dev/null +++ b/flutter_inappwebview_windows/example/README.md @@ -0,0 +1,16 @@ +# flutter_inappwebview_windows_example + +Demonstrates how to use the flutter_inappwebview_windows plugin. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/flutter_inappwebview_windows/example/analysis_options.yaml b/flutter_inappwebview_windows/example/analysis_options.yaml new file mode 100644 index 00000000..0d290213 --- /dev/null +++ b/flutter_inappwebview_windows/example/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/flutter_inappwebview_windows/example/integration_test/plugin_integration_test.dart b/flutter_inappwebview_windows/example/integration_test/plugin_integration_test.dart new file mode 100644 index 00000000..e69de29b diff --git a/flutter_inappwebview_windows/example/lib/main.dart b/flutter_inappwebview_windows/example/lib/main.dart new file mode 100644 index 00000000..e69de29b diff --git a/flutter_inappwebview_windows/example/pubspec.lock b/flutter_inappwebview_windows/example/pubspec.lock new file mode 100644 index 00000000..847582b6 --- /dev/null +++ b/flutter_inappwebview_windows/example/pubspec.lock @@ -0,0 +1,282 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + collection: + dependency: transitive + description: + name: collection + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" + source: hosted + version: "1.18.0" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d + url: "https://pub.dev" + source: hosted + version: "1.0.6" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + file: + dependency: transitive + description: + name: file + sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + url: "https://pub.dev" + source: hosted + version: "6.1.4" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_driver: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + flutter_inappwebview_internal_annotations: + dependency: transitive + description: + name: flutter_inappwebview_internal_annotations + sha256: "5f80fd30e208ddded7dbbcd0d569e7995f9f63d45ea3f548d8dd4c0b473fb4c8" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + flutter_inappwebview_platform_interface: + dependency: transitive + description: + path: "../../flutter_inappwebview_platform_interface" + relative: true + source: path + version: "1.0.10" + flutter_inappwebview_windows: + dependency: "direct main" + description: + path: ".." + relative: true + source: path + version: "1.0.11" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 + url: "https://pub.dev" + source: hosted + version: "2.0.3" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + fuchsia_remote_debug_protocol: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + integration_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + lints: + dependency: transitive + description: + name: lints + sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + matcher: + dependency: transitive + description: + name: matcher + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + url: "https://pub.dev" + source: hosted + version: "0.12.16" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + url: "https://pub.dev" + source: hosted + version: "0.5.0" + meta: + dependency: transitive + description: + name: meta + sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e + url: "https://pub.dev" + source: hosted + version: "1.10.0" + path: + dependency: transitive + description: + name: path + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + url: "https://pub.dev" + source: hosted + version: "1.8.3" + platform: + dependency: transitive + description: + name: platform + sha256: ae68c7bfcd7383af3629daafb32fb4e8681c7154428da4febcff06200585f102 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: f4f88d4a900933e7267e2b353594774fc0d07fb072b47eedcd5b54e1ea3269f8 + url: "https://pub.dev" + source: hosted + version: "2.1.7" + process: + dependency: transitive + description: + name: process + sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" + url: "https://pub.dev" + source: hosted + version: "4.2.4" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" + source: hosted + version: "1.11.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" + source: hosted + version: "2.1.2" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + sync_http: + dependency: transitive + description: + name: sync_http + sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961" + url: "https://pub.dev" + source: hosted + version: "0.3.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + url: "https://pub.dev" + source: hosted + version: "0.6.1" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: c538be99af830f478718b51630ec1b6bee5e74e52c8a802d328d9e71d35d2583 + url: "https://pub.dev" + source: hosted + version: "11.10.0" + web: + dependency: transitive + description: + name: web + sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 + url: "https://pub.dev" + source: hosted + version: "0.3.0" + webdriver: + dependency: transitive + description: + name: webdriver + sha256: "3c923e918918feeb90c4c9fdf1fe39220fa4c0e8e2c0fffaded174498ef86c49" + url: "https://pub.dev" + source: hosted + version: "3.0.2" +sdks: + dart: ">=3.2.3 <4.0.0" + flutter: ">=3.0.0" diff --git a/flutter_inappwebview_windows/example/pubspec.yaml b/flutter_inappwebview_windows/example/pubspec.yaml new file mode 100644 index 00000000..fc697f2f --- /dev/null +++ b/flutter_inappwebview_windows/example/pubspec.yaml @@ -0,0 +1,85 @@ +name: flutter_inappwebview_windows_example +description: "Demonstrates how to use the flutter_inappwebview_windows plugin." +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +environment: + sdk: '>=3.2.3 <4.0.0' + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + flutter_inappwebview_windows: + # When depending on this package from a real application you should use: + # flutter_inappwebview_windows: ^x.y.z + # See https://dart.dev/tools/pub/dependencies#version-constraints + # The example app is bundled with the plugin so we use a path dependency on + # the parent directory to use the current plugin's version. + path: ../ + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.2 + +dev_dependencies: + integration_test: + sdk: flutter + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^2.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/flutter_inappwebview_windows/example/test/widget_test.dart b/flutter_inappwebview_windows/example/test/widget_test.dart new file mode 100644 index 00000000..e69de29b diff --git a/flutter_inappwebview_windows/example/windows/.gitignore b/flutter_inappwebview_windows/example/windows/.gitignore new file mode 100644 index 00000000..d492d0d9 --- /dev/null +++ b/flutter_inappwebview_windows/example/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/flutter_inappwebview_windows/example/windows/CMakeLists.txt b/flutter_inappwebview_windows/example/windows/CMakeLists.txt new file mode 100644 index 00000000..3241da61 --- /dev/null +++ b/flutter_inappwebview_windows/example/windows/CMakeLists.txt @@ -0,0 +1,110 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(flutter_inappwebview_windows_example LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "flutter_inappwebview_windows_example") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + +# Enable the test target. +set(include_flutter_inappwebview_windows_tests TRUE) + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/flutter_inappwebview_windows/example/windows/flutter/CMakeLists.txt b/flutter_inappwebview_windows/example/windows/flutter/CMakeLists.txt new file mode 100644 index 00000000..903f4899 --- /dev/null +++ b/flutter_inappwebview_windows/example/windows/flutter/CMakeLists.txt @@ -0,0 +1,109 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + ${FLUTTER_TARGET_PLATFORM} $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/flutter_inappwebview_windows/example/windows/flutter/generated_plugin_registrant.cc b/flutter_inappwebview_windows/example/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 00000000..3b4ee903 --- /dev/null +++ b/flutter_inappwebview_windows/example/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,14 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include + +void RegisterPlugins(flutter::PluginRegistry* registry) { + FlutterInappwebviewWindowsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FlutterInappwebviewWindowsPluginCApi")); +} diff --git a/flutter_inappwebview_windows/example/windows/flutter/generated_plugin_registrant.h b/flutter_inappwebview_windows/example/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 00000000..dc139d85 --- /dev/null +++ b/flutter_inappwebview_windows/example/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/flutter_inappwebview_windows/example/windows/flutter/generated_plugins.cmake b/flutter_inappwebview_windows/example/windows/flutter/generated_plugins.cmake new file mode 100644 index 00000000..61c79a21 --- /dev/null +++ b/flutter_inappwebview_windows/example/windows/flutter/generated_plugins.cmake @@ -0,0 +1,24 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + flutter_inappwebview_windows +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/flutter_inappwebview_windows/example/windows/runner/CMakeLists.txt b/flutter_inappwebview_windows/example/windows/runner/CMakeLists.txt new file mode 100644 index 00000000..394917c0 --- /dev/null +++ b/flutter_inappwebview_windows/example/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/flutter_inappwebview_windows/example/windows/runner/Runner.rc b/flutter_inappwebview_windows/example/windows/runner/Runner.rc new file mode 100644 index 00000000..1815f6d8 --- /dev/null +++ b/flutter_inappwebview_windows/example/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.pichillilorenzo" "\0" + VALUE "FileDescription", "flutter_inappwebview_windows_example" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "flutter_inappwebview_windows_example" "\0" + VALUE "LegalCopyright", "Copyright (C) 2023 com.pichillilorenzo. All rights reserved." "\0" + VALUE "OriginalFilename", "flutter_inappwebview_windows_example.exe" "\0" + VALUE "ProductName", "flutter_inappwebview_windows_example" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/flutter_inappwebview_windows/example/windows/runner/flutter_window.cpp b/flutter_inappwebview_windows/example/windows/runner/flutter_window.cpp new file mode 100644 index 00000000..955ee303 --- /dev/null +++ b/flutter_inappwebview_windows/example/windows/runner/flutter_window.cpp @@ -0,0 +1,71 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/flutter_inappwebview_windows/example/windows/runner/flutter_window.h b/flutter_inappwebview_windows/example/windows/runner/flutter_window.h new file mode 100644 index 00000000..6da0652f --- /dev/null +++ b/flutter_inappwebview_windows/example/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/flutter_inappwebview_windows/example/windows/runner/main.cpp b/flutter_inappwebview_windows/example/windows/runner/main.cpp new file mode 100644 index 00000000..52028bc4 --- /dev/null +++ b/flutter_inappwebview_windows/example/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"flutter_inappwebview_windows_example", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/flutter_inappwebview_windows/example/windows/runner/resource.h b/flutter_inappwebview_windows/example/windows/runner/resource.h new file mode 100644 index 00000000..66a65d1e --- /dev/null +++ b/flutter_inappwebview_windows/example/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/flutter_inappwebview_windows/example/windows/runner/resources/app_icon.ico b/flutter_inappwebview_windows/example/windows/runner/resources/app_icon.ico new file mode 100644 index 00000000..c04e20ca Binary files /dev/null and b/flutter_inappwebview_windows/example/windows/runner/resources/app_icon.ico differ diff --git a/flutter_inappwebview_windows/example/windows/runner/runner.exe.manifest b/flutter_inappwebview_windows/example/windows/runner/runner.exe.manifest new file mode 100644 index 00000000..a42ea768 --- /dev/null +++ b/flutter_inappwebview_windows/example/windows/runner/runner.exe.manifest @@ -0,0 +1,20 @@ + + + + + PerMonitorV2 + + + + + + + + + + + + + + + diff --git a/flutter_inappwebview_windows/example/windows/runner/utils.cpp b/flutter_inappwebview_windows/example/windows/runner/utils.cpp new file mode 100644 index 00000000..b2b08734 --- /dev/null +++ b/flutter_inappwebview_windows/example/windows/runner/utils.cpp @@ -0,0 +1,65 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length <= 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/flutter_inappwebview_windows/example/windows/runner/utils.h b/flutter_inappwebview_windows/example/windows/runner/utils.h new file mode 100644 index 00000000..3879d547 --- /dev/null +++ b/flutter_inappwebview_windows/example/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/flutter_inappwebview_windows/example/windows/runner/win32_window.cpp b/flutter_inappwebview_windows/example/windows/runner/win32_window.cpp new file mode 100644 index 00000000..60608d0f --- /dev/null +++ b/flutter_inappwebview_windows/example/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/flutter_inappwebview_windows/example/windows/runner/win32_window.h b/flutter_inappwebview_windows/example/windows/runner/win32_window.h new file mode 100644 index 00000000..e901dde6 --- /dev/null +++ b/flutter_inappwebview_windows/example/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/flutter_inappwebview_windows/lib/flutter_inappwebview_windows.dart b/flutter_inappwebview_windows/lib/flutter_inappwebview_windows.dart new file mode 100644 index 00000000..4cd071c6 --- /dev/null +++ b/flutter_inappwebview_windows/lib/flutter_inappwebview_windows.dart @@ -0,0 +1,3 @@ +library flutter_inappwebview_windows; + +export 'src/main.dart'; diff --git a/flutter_inappwebview_windows/lib/src/cookie_manager.dart b/flutter_inappwebview_windows/lib/src/cookie_manager.dart new file mode 100644 index 00000000..30474f7d --- /dev/null +++ b/flutter_inappwebview_windows/lib/src/cookie_manager.dart @@ -0,0 +1,232 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; + +import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; + +import 'webview_environment/webview_environment.dart'; + +/// Object specifying creation parameters for creating a [WindowsCookieManager]. +/// +/// When adding additional fields make sure they can be null or have a default +/// value to avoid breaking changes. See [PlatformCookieManagerCreationParams] for +/// more information. +@immutable +class WindowsCookieManagerCreationParams + extends PlatformCookieManagerCreationParams { + /// Creates a new [WindowsCookieManagerCreationParams] instance. + const WindowsCookieManagerCreationParams( + {this.webViewEnvironment}); + + /// Creates a [WindowsCookieManagerCreationParams] instance based on [PlatformCookieManagerCreationParams]. + factory WindowsCookieManagerCreationParams.fromPlatformCookieManagerCreationParams( + // Recommended placeholder to prevent being broken by platform interface. + // ignore: avoid_unused_constructor_parameters + PlatformCookieManagerCreationParams params) { + return WindowsCookieManagerCreationParams( + webViewEnvironment: params.webViewEnvironment as WindowsWebViewEnvironment?); + } + + @override + final WindowsWebViewEnvironment? webViewEnvironment; +} + +///{@macro flutter_inappwebview_platform_interface.PlatformCookieManager} +class WindowsCookieManager extends PlatformCookieManager + with ChannelController { + /// Creates a new [WindowsCookieManager]. + WindowsCookieManager(PlatformCookieManagerCreationParams params) + : super.implementation( + params is WindowsCookieManagerCreationParams + ? params + : WindowsCookieManagerCreationParams + .fromPlatformCookieManagerCreationParams(params), + ) { + channel = const MethodChannel( + 'com.pichillilorenzo/flutter_inappwebview_cookiemanager'); + handler = handleMethod; + initMethodCallHandler(); + } + + static WindowsCookieManager? _instance; + + ///Gets the [WindowsCookieManager] shared instance. + static WindowsCookieManager instance({WindowsWebViewEnvironment? webViewEnvironment}) { + if (webViewEnvironment == null) { + if (_instance == null) { + _instance = _init(); + } + return _instance!; + } else { + return WindowsCookieManager( + WindowsCookieManagerCreationParams(webViewEnvironment: webViewEnvironment) + ); + } + } + + static WindowsCookieManager _init() { + _instance = WindowsCookieManager(WindowsCookieManagerCreationParams()); + return _instance!; + } + + Future _handleMethod(MethodCall call) async {} + + @override + Future setCookie( + {required WebUri url, + required String name, + required String value, + String path = "/", + String? domain, + int? expiresDate, + int? maxAge, + bool? isSecure, + bool? isHttpOnly, + HTTPCookieSameSitePolicy? sameSite, + @Deprecated("Use webViewController instead") + PlatformInAppWebViewController? iosBelow11WebViewController, + PlatformInAppWebViewController? webViewController}) async { + assert(url.toString().isNotEmpty); + assert(name.isNotEmpty); + assert(value.isNotEmpty); + assert(path.isNotEmpty); + + Map args = {}; + args.putIfAbsent('url', () => url.toString()); + args.putIfAbsent('name', () => name); + args.putIfAbsent('value', () => value); + args.putIfAbsent('domain', () => domain); + args.putIfAbsent('path', () => path); + args.putIfAbsent('expiresDate', () => expiresDate?.toString()); + args.putIfAbsent('maxAge', () => maxAge); + args.putIfAbsent('isSecure', () => isSecure); + args.putIfAbsent('isHttpOnly', () => isHttpOnly); + args.putIfAbsent('sameSite', () => sameSite?.toNativeValue()); + args.putIfAbsent('webViewEnvironmentId', () => params.webViewEnvironment?.id); + + return await channel?.invokeMethod('setCookie', args) ?? false; + } + + @override + Future> getCookies( + {required WebUri url, + @Deprecated("Use webViewController instead") + PlatformInAppWebViewController? iosBelow11WebViewController, + PlatformInAppWebViewController? webViewController}) async { + assert(url.toString().isNotEmpty); + + List cookies = []; + + Map args = {}; + args.putIfAbsent('url', () => url.toString()); + args.putIfAbsent('webViewEnvironmentId', () => params.webViewEnvironment?.id); + List cookieListMap = + await channel?.invokeMethod('getCookies', args) ?? []; + cookieListMap = cookieListMap.cast>(); + + cookieListMap.forEach((cookieMap) { + cookies.add(Cookie( + name: cookieMap["name"], + value: cookieMap["value"], + expiresDate: cookieMap["expiresDate"], + isSessionOnly: cookieMap["isSessionOnly"], + domain: cookieMap["domain"], + sameSite: + HTTPCookieSameSitePolicy.fromNativeValue(cookieMap["sameSite"]), + isSecure: cookieMap["isSecure"], + isHttpOnly: cookieMap["isHttpOnly"], + path: cookieMap["path"])); + }); + return cookies; + } + + @override + Future getCookie( + {required WebUri url, + required String name, + @Deprecated("Use webViewController instead") + PlatformInAppWebViewController? iosBelow11WebViewController, + PlatformInAppWebViewController? webViewController}) async { + assert(url.toString().isNotEmpty); + assert(name.isNotEmpty); + + Map args = {}; + args.putIfAbsent('url', () => url.toString()); + args.putIfAbsent('webViewEnvironmentId', () => params.webViewEnvironment?.id); + List cookies = + await channel?.invokeMethod('getCookies', args) ?? []; + cookies = cookies.cast>(); + for (var i = 0; i < cookies.length; i++) { + cookies[i] = cookies[i].cast(); + if (cookies[i]["name"] == name) + return Cookie( + name: cookies[i]["name"], + value: cookies[i]["value"], + expiresDate: cookies[i]["expiresDate"], + isSessionOnly: cookies[i]["isSessionOnly"], + domain: cookies[i]["domain"], + sameSite: HTTPCookieSameSitePolicy.fromNativeValue( + cookies[i]["sameSite"]), + isSecure: cookies[i]["isSecure"], + isHttpOnly: cookies[i]["isHttpOnly"], + path: cookies[i]["path"]); + } + return null; + } + + @override + Future deleteCookie( + {required WebUri url, + required String name, + String path = "/", + String? domain, + @Deprecated("Use webViewController instead") + PlatformInAppWebViewController? iosBelow11WebViewController, + PlatformInAppWebViewController? webViewController}) async { + assert(url.toString().isNotEmpty); + assert(name.isNotEmpty); + + Map args = {}; + args.putIfAbsent('url', () => url.toString()); + args.putIfAbsent('name', () => name); + args.putIfAbsent('domain', () => domain); + args.putIfAbsent('path', () => path); + args.putIfAbsent('webViewEnvironmentId', () => params.webViewEnvironment?.id); + return await channel?.invokeMethod('deleteCookie', args) ?? false; + } + + @override + Future deleteCookies( + {required WebUri url, + String path = "/", + String? domain, + @Deprecated("Use webViewController instead") + PlatformInAppWebViewController? iosBelow11WebViewController, + PlatformInAppWebViewController? webViewController}) async { + assert(url.toString().isNotEmpty); + + Map args = {}; + args.putIfAbsent('url', () => url.toString()); + args.putIfAbsent('domain', () => domain); + args.putIfAbsent('path', () => path); + args.putIfAbsent('webViewEnvironmentId', () => params.webViewEnvironment?.id); + return await channel?.invokeMethod('deleteCookies', args) ?? false; + } + + @override + Future deleteAllCookies() async { + Map args = {}; + args.putIfAbsent('webViewEnvironmentId', () => params.webViewEnvironment?.id); + return await channel?.invokeMethod('deleteAllCookies', args) ?? false; + } + + @override + void dispose() { + // empty + } +} + +extension InternalCookieManager on WindowsCookieManager { + get handleMethod => _handleMethod; +} diff --git a/flutter_inappwebview_windows/lib/src/find_interaction/find_interaction_controller.dart b/flutter_inappwebview_windows/lib/src/find_interaction/find_interaction_controller.dart new file mode 100644 index 00000000..8868a3d6 --- /dev/null +++ b/flutter_inappwebview_windows/lib/src/find_interaction/find_interaction_controller.dart @@ -0,0 +1,124 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; + +/// Object specifying creation parameters for creating a [WindowsFindInteractionController]. +/// +/// When adding additional fields make sure they can be null or have a default +/// value to avoid breaking changes. See [PlatformFindInteractionControllerCreationParams] for +/// more information. +@immutable +class WindowsFindInteractionControllerCreationParams + extends PlatformFindInteractionControllerCreationParams { + /// Creates a new [WindowsFindInteractionControllerCreationParams] instance. + const WindowsFindInteractionControllerCreationParams( + {super.onFindResultReceived}); + + /// Creates a [WindowsFindInteractionControllerCreationParams] instance based on [PlatformFindInteractionControllerCreationParams]. + factory WindowsFindInteractionControllerCreationParams.fromPlatformFindInteractionControllerCreationParams( + // Recommended placeholder to prevent being broken by platform interface. + // ignore: avoid_unused_constructor_parameters + PlatformFindInteractionControllerCreationParams params) { + return WindowsFindInteractionControllerCreationParams( + onFindResultReceived: params.onFindResultReceived); + } +} + +///{@macro flutter_inappwebview_platform_interface.PlatformFindInteractionController} +class WindowsFindInteractionController extends PlatformFindInteractionController + with ChannelController { + /// Constructs a [WindowsFindInteractionController]. + WindowsFindInteractionController( + PlatformFindInteractionControllerCreationParams params) + : super.implementation( + params is WindowsFindInteractionControllerCreationParams + ? params + : WindowsFindInteractionControllerCreationParams + .fromPlatformFindInteractionControllerCreationParams(params), + ); + + _debugLog(String method, dynamic args) { + debugLog( + className: this.runtimeType.toString(), + debugLoggingSettings: + PlatformFindInteractionController.debugLoggingSettings, + method: method, + args: args); + } + + Future _handleMethod(MethodCall call) async { + _debugLog(call.method, call.arguments); + + switch (call.method) { + case "onFindResultReceived": + if (onFindResultReceived != null) { + int activeMatchOrdinal = call.arguments["activeMatchOrdinal"]; + int numberOfMatches = call.arguments["numberOfMatches"]; + bool isDoneCounting = call.arguments["isDoneCounting"]; + onFindResultReceived!( + this, activeMatchOrdinal, numberOfMatches, isDoneCounting); + } + break; + default: + throw UnimplementedError("Unimplemented ${call.method} method"); + } + return null; + } + + ///{@macro flutter_inappwebview_platform_interface.PlatformFindInteractionController.findAll} + Future findAll({String? find}) async { + Map args = {}; + args.putIfAbsent('find', () => find); + await channel?.invokeMethod('findAll', args); + } + + ///{@macro flutter_inappwebview_platform_interface.PlatformFindInteractionController.findNext} + Future findNext({bool forward = true}) async { + Map args = {}; + args.putIfAbsent('forward', () => forward); + await channel?.invokeMethod('findNext', args); + } + + ///{@macro flutter_inappwebview_platform_interface.PlatformFindInteractionController.clearMatches} + Future clearMatches() async { + Map args = {}; + await channel?.invokeMethod('clearMatches', args); + } + + ///{@macro flutter_inappwebview_platform_interface.PlatformFindInteractionController.setSearchText} + Future setSearchText(String? searchText) async { + Map args = {}; + args.putIfAbsent('searchText', () => searchText); + await channel?.invokeMethod('setSearchText', args); + } + + ///{@macro flutter_inappwebview_platform_interface.PlatformFindInteractionController.getSearchText} + Future getSearchText() async { + Map args = {}; + return await channel?.invokeMethod('getSearchText', args); + } + + ///{@macro flutter_inappwebview_platform_interface.PlatformFindInteractionController.getActiveFindSession} + Future getActiveFindSession() async { + Map args = {}; + Map? result = + (await channel?.invokeMethod('getActiveFindSession', args)) + ?.cast(); + return FindSession.fromMap(result); + } + + ///{@macro flutter_inappwebview_platform_interface.PlatformFindInteractionController.dispose} + @override + void dispose({bool isKeepAlive = false}) { + disposeChannel(removeMethodCallHandler: !isKeepAlive); + } +} + +extension InternalFindInteractionController on WindowsFindInteractionController { + void init(dynamic id) { + channel = MethodChannel( + 'com.pichillilorenzo/flutter_inappwebview_find_interaction_$id'); + handler = _handleMethod; + initMethodCallHandler(); + } +} diff --git a/flutter_inappwebview_windows/lib/src/find_interaction/main.dart b/flutter_inappwebview_windows/lib/src/find_interaction/main.dart new file mode 100644 index 00000000..a7adaacf --- /dev/null +++ b/flutter_inappwebview_windows/lib/src/find_interaction/main.dart @@ -0,0 +1,2 @@ +export 'find_interaction_controller.dart' + hide InternalFindInteractionController; diff --git a/flutter_inappwebview_windows/lib/src/http_auth_credentials_database.dart b/flutter_inappwebview_windows/lib/src/http_auth_credentials_database.dart new file mode 100644 index 00000000..90bc6e7e --- /dev/null +++ b/flutter_inappwebview_windows/lib/src/http_auth_credentials_database.dart @@ -0,0 +1,155 @@ +import 'dart:async'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; + +/// Object specifying creation parameters for creating a [WindowsHttpAuthCredentialDatabase]. +/// +/// When adding additional fields make sure they can be null or have a default +/// value to avoid breaking changes. See [PlatformHttpAuthCredentialDatabaseCreationParams] for +/// more information. +@immutable +class WindowsHttpAuthCredentialDatabaseCreationParams + extends PlatformHttpAuthCredentialDatabaseCreationParams { + /// Creates a new [WindowsHttpAuthCredentialDatabaseCreationParams] instance. + const WindowsHttpAuthCredentialDatabaseCreationParams( + // This parameter prevents breaking changes later. + // ignore: avoid_unused_constructor_parameters + PlatformHttpAuthCredentialDatabaseCreationParams params, + ) : super(); + + /// Creates a [WindowsHttpAuthCredentialDatabaseCreationParams] instance based on [PlatformHttpAuthCredentialDatabaseCreationParams]. + factory WindowsHttpAuthCredentialDatabaseCreationParams.fromPlatformHttpAuthCredentialDatabaseCreationParams( + PlatformHttpAuthCredentialDatabaseCreationParams params) { + return WindowsHttpAuthCredentialDatabaseCreationParams(params); + } +} + +///{@macro flutter_inappwebview_platform_interface.PlatformHttpAuthCredentialDatabase} +class WindowsHttpAuthCredentialDatabase extends PlatformHttpAuthCredentialDatabase + with ChannelController { + /// Creates a new [WindowsHttpAuthCredentialDatabase]. + WindowsHttpAuthCredentialDatabase( + PlatformHttpAuthCredentialDatabaseCreationParams params) + : super.implementation( + params is WindowsHttpAuthCredentialDatabaseCreationParams + ? params + : WindowsHttpAuthCredentialDatabaseCreationParams + .fromPlatformHttpAuthCredentialDatabaseCreationParams(params), + ) { + channel = const MethodChannel( + 'com.pichillilorenzo/flutter_inappwebview_credential_database'); + handler = handleMethod; + initMethodCallHandler(); + } + + static WindowsHttpAuthCredentialDatabase? _instance; + + ///Gets the database shared instance. + static WindowsHttpAuthCredentialDatabase instance() { + return (_instance != null) ? _instance! : _init(); + } + + static WindowsHttpAuthCredentialDatabase _init() { + _instance = WindowsHttpAuthCredentialDatabase( + WindowsHttpAuthCredentialDatabaseCreationParams( + const PlatformHttpAuthCredentialDatabaseCreationParams())); + return _instance!; + } + + Future _handleMethod(MethodCall call) async {} + + @override + Future> + getAllAuthCredentials() async { + Map args = {}; + List allCredentials = + await channel?.invokeMethod('getAllAuthCredentials', args) ?? []; + + List result = []; + + for (Map map in allCredentials) { + var element = URLProtectionSpaceHttpAuthCredentials.fromMap( + map.cast()); + if (element != null) { + result.add(element); + } + } + return result; + } + + @override + Future> getHttpAuthCredentials( + {required URLProtectionSpace protectionSpace}) async { + Map args = {}; + args.putIfAbsent("host", () => protectionSpace.host); + args.putIfAbsent("protocol", () => protectionSpace.protocol); + args.putIfAbsent("realm", () => protectionSpace.realm); + args.putIfAbsent("port", () => protectionSpace.port); + List credentialList = + await channel?.invokeMethod('getHttpAuthCredentials', args) ?? []; + List credentials = []; + for (Map map in credentialList) { + var credential = URLCredential.fromMap(map.cast()); + if (credential != null) { + credentials.add(credential); + } + } + return credentials; + } + + @override + Future setHttpAuthCredential( + {required URLProtectionSpace protectionSpace, + required URLCredential credential}) async { + Map args = {}; + args.putIfAbsent("host", () => protectionSpace.host); + args.putIfAbsent("protocol", () => protectionSpace.protocol); + args.putIfAbsent("realm", () => protectionSpace.realm); + args.putIfAbsent("port", () => protectionSpace.port); + args.putIfAbsent("username", () => credential.username); + args.putIfAbsent("password", () => credential.password); + await channel?.invokeMethod('setHttpAuthCredential', args); + } + + @override + Future removeHttpAuthCredential( + {required URLProtectionSpace protectionSpace, + required URLCredential credential}) async { + Map args = {}; + args.putIfAbsent("host", () => protectionSpace.host); + args.putIfAbsent("protocol", () => protectionSpace.protocol); + args.putIfAbsent("realm", () => protectionSpace.realm); + args.putIfAbsent("port", () => protectionSpace.port); + args.putIfAbsent("username", () => credential.username); + args.putIfAbsent("password", () => credential.password); + await channel?.invokeMethod('removeHttpAuthCredential', args); + } + + @override + Future removeHttpAuthCredentials( + {required URLProtectionSpace protectionSpace}) async { + Map args = {}; + args.putIfAbsent("host", () => protectionSpace.host); + args.putIfAbsent("protocol", () => protectionSpace.protocol); + args.putIfAbsent("realm", () => protectionSpace.realm); + args.putIfAbsent("port", () => protectionSpace.port); + await channel?.invokeMethod('removeHttpAuthCredentials', args); + } + + @override + Future clearAllAuthCredentials() async { + Map args = {}; + await channel?.invokeMethod('clearAllAuthCredentials', args); + } + + @override + void dispose() { + // empty + } +} + +extension InternalHttpAuthCredentialDatabase + on WindowsHttpAuthCredentialDatabase { + get handleMethod => _handleMethod; +} diff --git a/flutter_inappwebview_windows/lib/src/in_app_browser/in_app_browser.dart b/flutter_inappwebview_windows/lib/src/in_app_browser/in_app_browser.dart new file mode 100644 index 00000000..7920c613 --- /dev/null +++ b/flutter_inappwebview_windows/lib/src/in_app_browser/in_app_browser.dart @@ -0,0 +1,380 @@ +import 'dart:async'; +import 'dart:collection'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; + +import '../find_interaction/find_interaction_controller.dart'; +import '../in_app_webview/in_app_webview_controller.dart'; +import '../webview_environment/webview_environment.dart'; + +/// Object specifying creation parameters for creating a [WindowsInAppBrowser]. +/// +/// When adding additional fields make sure they can be null or have a default +/// value to avoid breaking changes. See [PlatformInAppBrowserCreationParams] for +/// more information. +class WindowsInAppBrowserCreationParams + extends PlatformInAppBrowserCreationParams { + /// Creates a new [WindowsInAppBrowserCreationParams] instance. + WindowsInAppBrowserCreationParams( + {super.contextMenu, + super.pullToRefreshController, + this.findInteractionController, + super.initialUserScripts, + super.windowId, + this.webViewEnvironment}); + + /// Creates a [WindowsInAppBrowserCreationParams] instance based on [PlatformInAppBrowserCreationParams]. + factory WindowsInAppBrowserCreationParams.fromPlatformInAppBrowserCreationParams( + // Recommended placeholder to prevent being broken by platform interface. + // ignore: avoid_unused_constructor_parameters + PlatformInAppBrowserCreationParams params) { + return WindowsInAppBrowserCreationParams( + contextMenu: params.contextMenu, + pullToRefreshController: params.pullToRefreshController, + findInteractionController: + params.findInteractionController as WindowsFindInteractionController?, + initialUserScripts: params.initialUserScripts, + windowId: params.windowId, + webViewEnvironment: params.webViewEnvironment as WindowsWebViewEnvironment?); + } + + @override + final WindowsFindInteractionController? findInteractionController; + + @override + final WindowsWebViewEnvironment? webViewEnvironment; +} + +///{@macro flutter_inappwebview_platform_interface.PlatformInAppBrowser} +class WindowsInAppBrowser extends PlatformInAppBrowser with ChannelController { + @override + final String id = IdGenerator.generate(); + + /// Constructs a [WindowsInAppBrowser]. + WindowsInAppBrowser(PlatformInAppBrowserCreationParams params) + : super.implementation( + params is WindowsInAppBrowserCreationParams + ? params + : WindowsInAppBrowserCreationParams + .fromPlatformInAppBrowserCreationParams(params), + ) { + _contextMenu = params.contextMenu; + } + + static final WindowsInAppBrowser _staticValue = + WindowsInAppBrowser(WindowsInAppBrowserCreationParams()); + + /// Provide static access. + factory WindowsInAppBrowser.static() { + return _staticValue; + } + + WindowsInAppBrowserCreationParams get _windowsParams => + params as WindowsInAppBrowserCreationParams; + + static const MethodChannel _staticChannel = + const MethodChannel('com.pichillilorenzo/flutter_inappbrowser'); + + ContextMenu? _contextMenu; + + @override + ContextMenu? get contextMenu => _contextMenu; + + Map _menuItems = HashMap(); + bool _isOpened = false; + WindowsInAppWebViewController? _webViewController; + + @override + WindowsInAppWebViewController? get webViewController { + return _isOpened ? _webViewController : null; + } + + _init() { + channel = MethodChannel('com.pichillilorenzo/flutter_inappbrowser_$id'); + handler = _handleMethod; + initMethodCallHandler(); + + _webViewController = WindowsInAppWebViewController.fromInAppBrowser( + WindowsInAppWebViewControllerCreationParams(id: id), + channel!, + this, + this.initialUserScripts); + _windowsParams.findInteractionController?.init(id); + } + + _debugLog(String method, dynamic args) { + debugLog( + className: this.runtimeType.toString(), + id: id, + debugLoggingSettings: PlatformInAppBrowser.debugLoggingSettings, + method: method, + args: args); + } + + Future _handleMethod(MethodCall call) async { + switch (call.method) { + case "onBrowserCreated": + _debugLog(call.method, call.arguments); + eventHandler?.onBrowserCreated(); + break; + case "onMenuItemClicked": + _debugLog(call.method, call.arguments); + int id = call.arguments["id"].toInt(); + if (this._menuItems[id] != null) { + if (this._menuItems[id]?.onClick != null) { + this._menuItems[id]?.onClick!(); + } + } + break; + case "onMainWindowWillClose": + _debugLog(call.method, call.arguments); + eventHandler?.onMainWindowWillClose(); + break; + case "onExit": + _debugLog(call.method, call.arguments); + _isOpened = false; + final onExit = eventHandler?.onExit; + dispose(); + onExit?.call(); + break; + default: + return _webViewController?.handleMethod(call); + } + } + + Map _prepareOpenRequest( + {@Deprecated('Use settings instead') InAppBrowserClassOptions? options, + InAppBrowserClassSettings? settings}) { + assert(!_isOpened, 'The browser is already opened.'); + _isOpened = true; + _init(); + + var initialSettings = settings?.toMap() ?? + options?.toMap() ?? + InAppBrowserClassSettings().toMap(); + + Map pullToRefreshSettings = + pullToRefreshController?.settings.toMap() ?? + pullToRefreshController?.options.toMap() ?? + PullToRefreshSettings(enabled: false).toMap(); + + List> menuItemList = []; + _menuItems.forEach((key, value) { + menuItemList.add(value.toMap()); + }); + + Map args = {}; + args.putIfAbsent('id', () => id); + args.putIfAbsent('settings', () => initialSettings); + args.putIfAbsent('contextMenu', () => contextMenu?.toMap() ?? {}); + args.putIfAbsent('windowId', () => windowId); + args.putIfAbsent('initialUserScripts', + () => initialUserScripts?.map((e) => e.toMap()).toList() ?? []); + args.putIfAbsent('pullToRefreshSettings', () => pullToRefreshSettings); + args.putIfAbsent('menuItems', () => menuItemList); + args.putIfAbsent('webViewEnvironmentId', () => _windowsParams.webViewEnvironment?.id); + return args; + } + + @override + Future openUrlRequest( + {required URLRequest urlRequest, + // ignore: deprecated_member_use_from_same_package + @Deprecated('Use settings instead') InAppBrowserClassOptions? options, + InAppBrowserClassSettings? settings}) async { + assert(urlRequest.url != null && urlRequest.url.toString().isNotEmpty); + + Map args = + _prepareOpenRequest(options: options, settings: settings); + args.putIfAbsent('urlRequest', () => urlRequest.toMap()); + await _staticChannel.invokeMethod('open', args); + } + + @override + Future openFile( + {required String assetFilePath, + // ignore: deprecated_member_use_from_same_package + @Deprecated('Use settings instead') InAppBrowserClassOptions? options, + InAppBrowserClassSettings? settings}) async { + assert(assetFilePath.isNotEmpty); + + Map args = + _prepareOpenRequest(options: options, settings: settings); + args.putIfAbsent('assetFilePath', () => assetFilePath); + await _staticChannel.invokeMethod('open', args); + } + + @override + Future openData( + {required String data, + String mimeType = "text/html", + String encoding = "utf8", + WebUri? baseUrl, + @Deprecated("Use historyUrl instead") Uri? androidHistoryUrl, + WebUri? historyUrl, + // ignore: deprecated_member_use_from_same_package + @Deprecated('Use settings instead') InAppBrowserClassOptions? options, + InAppBrowserClassSettings? settings}) async { + Map args = + _prepareOpenRequest(options: options, settings: settings); + args.putIfAbsent('data', () => data); + args.putIfAbsent('mimeType', () => mimeType); + args.putIfAbsent('encoding', () => encoding); + args.putIfAbsent('baseUrl', () => baseUrl?.toString() ?? "about:blank"); + args.putIfAbsent('historyUrl', + () => (historyUrl ?? androidHistoryUrl)?.toString() ?? "about:blank"); + await _staticChannel.invokeMethod('open', args); + } + + @override + Future openWithSystemBrowser({required WebUri url}) async { + assert(url.toString().isNotEmpty); + + Map args = {}; + args.putIfAbsent('url', () => url.toString()); + return await _staticChannel.invokeMethod('openWithSystemBrowser', args); + } + + @override + void addMenuItem(InAppBrowserMenuItem menuItem) { + _menuItems[menuItem.id] = menuItem; + } + + @override + void addMenuItems(List menuItems) { + menuItems.forEach((menuItem) { + _menuItems[menuItem.id] = menuItem; + }); + } + + @override + bool removeMenuItem(InAppBrowserMenuItem menuItem) { + return _menuItems.remove(menuItem.id) != null; + } + + @override + void removeMenuItems(List menuItems) { + for (final menuItem in menuItems) { + removeMenuItem(menuItem); + } + } + + @override + void removeAllMenuItem() { + _menuItems.clear(); + } + + @override + bool hasMenuItem(InAppBrowserMenuItem menuItem) { + return _menuItems.containsKey(menuItem.id); + } + + @override + Future show() async { + assert(_isOpened, 'The browser is not opened.'); + + Map args = {}; + await channel?.invokeMethod('show', args); + } + + @override + Future hide() async { + assert(_isOpened, 'The browser is not opened.'); + + Map args = {}; + await channel?.invokeMethod('hide', args); + } + + @override + Future close() async { + assert(_isOpened, 'The browser is not opened.'); + + Map args = {}; + await channel?.invokeMethod('close', args); + } + + @override + Future isHidden() async { + assert(_isOpened, 'The browser is not opened.'); + + Map args = {}; + return await channel?.invokeMethod('isHidden', args) ?? false; + } + + @override + @Deprecated('Use setSettings instead') + Future setOptions({required InAppBrowserClassOptions options}) async { + assert(_isOpened, 'The browser is not opened.'); + + Map args = {}; + args.putIfAbsent('settings', () => options.toMap()); + await channel?.invokeMethod('setSettings', args); + } + + @override + @Deprecated('Use getSettings instead') + Future getOptions() async { + assert(_isOpened, 'The browser is not opened.'); + Map args = {}; + + Map? options = + await channel?.invokeMethod('getSettings', args); + if (options != null) { + options = options.cast(); + return InAppBrowserClassOptions.fromMap(options as Map); + } + + return null; + } + + @override + Future setSettings( + {required InAppBrowserClassSettings settings}) async { + assert(_isOpened, 'The browser is not opened.'); + + Map args = {}; + args.putIfAbsent('settings', () => settings.toMap()); + await channel?.invokeMethod('setSettings', args); + } + + @override + Future getSettings() async { + assert(_isOpened, 'The browser is not opened.'); + + Map args = {}; + + Map? settings = + await channel?.invokeMethod('getSettings', args); + if (settings != null) { + settings = settings.cast(); + return InAppBrowserClassSettings.fromMap( + settings as Map); + } + + return null; + } + + @override + bool isOpened() { + return this._isOpened; + } + + @override + @mustCallSuper + void dispose() { + super.dispose(); + disposeChannel(); + _webViewController?.dispose(); + _webViewController = null; + pullToRefreshController?.dispose(); + findInteractionController?.dispose(); + } +} + +extension InternalInAppBrowser on WindowsInAppBrowser { + void setContextMenu(ContextMenu? contextMenu) { + _contextMenu = contextMenu; + } +} diff --git a/flutter_inappwebview_windows/lib/src/in_app_browser/main.dart b/flutter_inappwebview_windows/lib/src/in_app_browser/main.dart new file mode 100644 index 00000000..e11eb8b1 --- /dev/null +++ b/flutter_inappwebview_windows/lib/src/in_app_browser/main.dart @@ -0,0 +1 @@ +export 'in_app_browser.dart' hide InternalInAppBrowser; diff --git a/flutter_inappwebview_windows/lib/src/in_app_webview/_static_channel.dart b/flutter_inappwebview_windows/lib/src/in_app_webview/_static_channel.dart new file mode 100644 index 00000000..beb7de70 --- /dev/null +++ b/flutter_inappwebview_windows/lib/src/in_app_webview/_static_channel.dart @@ -0,0 +1,4 @@ +import 'package:flutter/services.dart'; + +const IN_APP_WEBVIEW_STATIC_CHANNEL = + MethodChannel('com.pichillilorenzo/flutter_inappwebview_manager'); diff --git a/flutter_inappwebview_windows/lib/src/in_app_webview/custom_platform_view.dart b/flutter_inappwebview_windows/lib/src/in_app_webview/custom_platform_view.dart new file mode 100644 index 00000000..8a302954 --- /dev/null +++ b/flutter_inappwebview_windows/lib/src/in_app_webview/custom_platform_view.dart @@ -0,0 +1,442 @@ +import 'package:flutter/services.dart'; +import 'dart:async'; +import 'dart:ui'; + +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; + +const Map _cursors = { + 'none': SystemMouseCursors.none, + 'basic': SystemMouseCursors.basic, + 'click': SystemMouseCursors.click, + 'forbidden': SystemMouseCursors.forbidden, + 'wait': SystemMouseCursors.wait, + 'progress': SystemMouseCursors.progress, + 'contextMenu': SystemMouseCursors.contextMenu, + 'help': SystemMouseCursors.help, + 'text': SystemMouseCursors.text, + 'verticalText': SystemMouseCursors.verticalText, + 'cell': SystemMouseCursors.cell, + 'precise': SystemMouseCursors.precise, + 'move': SystemMouseCursors.move, + 'grab': SystemMouseCursors.grab, + 'grabbing': SystemMouseCursors.grabbing, + 'noDrop': SystemMouseCursors.noDrop, + 'alias': SystemMouseCursors.alias, + 'copy': SystemMouseCursors.copy, + 'disappearing': SystemMouseCursors.disappearing, + 'allScroll': SystemMouseCursors.allScroll, + 'resizeLeftRight': SystemMouseCursors.resizeLeftRight, + 'resizeUpDown': SystemMouseCursors.resizeUpDown, + 'resizeUpLeftDownRight': SystemMouseCursors.resizeUpLeftDownRight, + 'resizeUpRightDownLeft': SystemMouseCursors.resizeUpRightDownLeft, + 'resizeUp': SystemMouseCursors.resizeUp, + 'resizeDown': SystemMouseCursors.resizeDown, + 'resizeLeft': SystemMouseCursors.resizeLeft, + 'resizeRight': SystemMouseCursors.resizeRight, + 'resizeUpLeft': SystemMouseCursors.resizeUpLeft, + 'resizeUpRight': SystemMouseCursors.resizeUpRight, + 'resizeDownLeft': SystemMouseCursors.resizeDownLeft, + 'resizeDownRight': SystemMouseCursors.resizeDownRight, + 'resizeColumn': SystemMouseCursors.resizeColumn, + 'resizeRow': SystemMouseCursors.resizeRow, + 'zoomIn': SystemMouseCursors.zoomIn, + 'zoomOut': SystemMouseCursors.zoomOut, +}; + +SystemMouseCursor _getCursorByName(String name) => + _cursors[name] ?? SystemMouseCursors.basic; + +/// Pointer button type +// Order must match InAppWebViewPointerEventKind (see in_app_webview.h) +enum PointerButton { none, primary, secondary, tertiary } + +/// Pointer Event kind +// Order must match InAppWebViewPointerEventKind (see in_app_webview.h) +enum InAppWebViewPointerEventKind { activate, down, enter, leave, up, update } + +/// Attempts to translate a button constant such as [kPrimaryMouseButton] +/// to a [PointerButton] +PointerButton _getButton(int value) { + switch (value) { + case kPrimaryMouseButton: + return PointerButton.primary; + case kSecondaryMouseButton: + return PointerButton.secondary; + case kTertiaryButton: + return PointerButton.tertiary; + default: + return PointerButton.none; + } +} + +const String _pluginChannelPrefix = 'com.pichillilorenzo/flutter_inappwebview'; +const MethodChannel _pluginChannel = MethodChannel(_pluginChannelPrefix); + +class CustomFlutterViewControllerValue { + const CustomFlutterViewControllerValue({ + required this.isInitialized, + }); + + final bool isInitialized; + + CustomFlutterViewControllerValue copyWith({ + bool? isInitialized, + }) { + return CustomFlutterViewControllerValue( + isInitialized: isInitialized ?? this.isInitialized, + ); + } + + CustomFlutterViewControllerValue.uninitialized() + : this( + isInitialized: false, + ); +} + +/// Controls a WebView and provides streams for various change events. +class CustomPlatformViewController + extends ValueNotifier { + Completer _creatingCompleter = Completer(); + int _textureId = 0; + bool _isDisposed = false; + + Future get ready => _creatingCompleter.future; + + late MethodChannel _methodChannel; + late EventChannel _eventChannel; + StreamSubscription? _eventStreamSubscription; + + final StreamController _cursorStreamController = + StreamController.broadcast(); + + /// A stream reflecting the current cursor style. + Stream get _cursor => _cursorStreamController.stream; + + CustomPlatformViewController() + : super(CustomFlutterViewControllerValue.uninitialized()); + + /// Initializes the underlying platform view. + Future initialize( + {Function(int id)? onPlatformViewCreated, dynamic arguments}) async { + if (_isDisposed) { + return; + } + _textureId = (await _pluginChannel.invokeMethod( + 'createInAppWebView', arguments))!; + + _methodChannel = + MethodChannel('com.pichillilorenzo/custom_platform_view_$_textureId'); + _eventChannel = + EventChannel('com.pichillilorenzo/custom_platform_view_${_textureId}_events'); + _eventStreamSubscription = + _eventChannel.receiveBroadcastStream().listen((event) { + final map = event as Map; + switch (map['type']) { + case 'cursorChanged': + _cursorStreamController.add(_getCursorByName(map['value'])); + break; + } + }); + + _methodChannel.setMethodCallHandler((call) { + throw MissingPluginException('Unknown method ${call.method}'); + }); + + value = value.copyWith(isInitialized: true); + + _creatingCompleter.complete(); + + onPlatformViewCreated?.call(_textureId); + } + + @override + Future dispose() async { + await _creatingCompleter.future; + if (!_isDisposed) { + _isDisposed = true; + await _eventStreamSubscription?.cancel(); + await _pluginChannel.invokeMethod('dispose', {"id": _textureId}); + } + super.dispose(); + } + + /// Limits the number of frames per second to the given value. + Future setFpsLimit([int? maxFps = 0]) async { + if (_isDisposed) { + return; + } + assert(value.isInitialized); + return _methodChannel.invokeMethod('setFpsLimit', maxFps); + } + + /// Sends a Pointer (Touch) update + Future _setPointerUpdate(InAppWebViewPointerEventKind kind, int pointer, + Offset position, double size, double pressure) async { + if (_isDisposed) { + return; + } + assert(value.isInitialized); + return _methodChannel.invokeMethod('setPointerUpdate', + [pointer, kind.index, position.dx, position.dy, size, pressure]); + } + + /// Moves the virtual cursor to [position]. + Future _setCursorPos(Offset position) async { + if (_isDisposed) { + return; + } + assert(value.isInitialized); + return _methodChannel + .invokeMethod('setCursorPos', [position.dx, position.dy]); + } + + /// Indicates whether the specified [button] is currently down. + Future _setPointerButtonState(PointerButton button, bool isDown) async { + if (_isDisposed) { + return; + } + assert(value.isInitialized); + return _methodChannel.invokeMethod('setPointerButton', + {'button': button.index, 'isDown': isDown}); + } + + /// Sets the horizontal and vertical scroll delta. + Future _setScrollDelta(double dx, double dy) async { + if (_isDisposed) { + return; + } + assert(value.isInitialized); + return _methodChannel.invokeMethod('setScrollDelta', [dx, dy]); + } + + /// Sets the surface size to the provided [size]. + Future _setSize(Size size, double scaleFactor) async { + if (_isDisposed) { + return; + } + assert(value.isInitialized); + return _methodChannel + .invokeMethod('setSize', [size.width, size.height, scaleFactor]); + } + + /// Sets the surface size to the provided [size]. + Future _setPosition(Offset position, double scaleFactor) async { + if (_isDisposed) { + return; + } + assert(value.isInitialized); + return _methodChannel + .invokeMethod('setPosition', [position.dx, position.dy, scaleFactor]); + } +} + +class CustomPlatformView extends StatefulWidget { + /// An optional scale factor. Defaults to [FlutterView.devicePixelRatio] for + /// rendering in native resolution. + /// Setting this to 1.0 will disable high-DPI support. + /// This should only be needed to mimic old behavior before high-DPI support + /// was available. + final double? scaleFactor; + + /// The [FilterQuality] used for scaling the texture's contents. + /// Defaults to [FilterQuality.none] as this renders in native resolution + /// unless specifying a [scaleFactor]. + final FilterQuality filterQuality; + + final dynamic creationParams; + + final Function(int id)? onPlatformViewCreated; + + const CustomPlatformView( + {this.creationParams, + this.onPlatformViewCreated, + this.scaleFactor, + this.filterQuality = FilterQuality.none}); + + @override + _CustomPlatformViewState createState() => _CustomPlatformViewState(); +} + +class _CustomPlatformViewState extends State { + final GlobalKey _key = GlobalKey(); + final _downButtons = {}; + + PointerDeviceKind _pointerKind = PointerDeviceKind.unknown; + + MouseCursor _cursor = SystemMouseCursors.basic; + + final _controller = CustomPlatformViewController(); + final _focusNode = FocusNode(); + + StreamSubscription? _cursorSubscription; + + @override + void initState() { + super.initState(); + + _controller.initialize( + onPlatformViewCreated: (id) { + widget.onPlatformViewCreated?.call(id); + setState(() {}); + }, + arguments: widget.creationParams); + + // Report initial surface size and widget position + WidgetsBinding.instance.addPostFrameCallback((_) { + _reportSurfaceSize(); + _reportWidgetPosition(); + }); + + _cursorSubscription = _controller._cursor.listen((cursor) { + setState(() { + _cursor = cursor; + }); + }); + } + + @override + Widget build(BuildContext context) { + return Focus( + autofocus: true, + focusNode: _focusNode, + canRequestFocus: true, + debugLabel: "flutter_inappwebview_windows_custom_platform_view", + onFocusChange: (focused) { + + }, + child: SizedBox.expand(key: _key, child: _buildInner()), + ); + } + + Widget _buildInner() { + return NotificationListener( + onNotification: (notification) { + _reportSurfaceSize(); + _reportWidgetPosition(); + return true; + }, + child: SizeChangedLayoutNotifier( + child: _controller.value.isInitialized + ? Listener( + onPointerHover: (ev) { + // ev.kind is for whatever reason not set to touch + // even on touch input + if (_pointerKind == PointerDeviceKind.touch) { + // Ignoring hover events on touch for now + return; + } + _controller._setCursorPos(ev.localPosition); + }, + onPointerDown: (ev) { + _reportSurfaceSize(); + _reportWidgetPosition(); + + if (!_focusNode.hasFocus) { + _focusNode.requestFocus(); + Future.delayed(const Duration(milliseconds: 50), () { + if (!_focusNode.hasFocus) { + _focusNode.requestFocus(); + } + }); + } + + _pointerKind = ev.kind; + if (ev.kind == PointerDeviceKind.touch) { + _controller._setPointerUpdate( + InAppWebViewPointerEventKind.down, + ev.pointer, + ev.localPosition, + ev.size, + ev.pressure); + return; + } + final button = _getButton(ev.buttons); + _downButtons[ev.pointer] = button; + _controller._setPointerButtonState(button, true); + }, + onPointerUp: (ev) { + _pointerKind = ev.kind; + if (ev.kind == PointerDeviceKind.touch) { + _controller._setPointerUpdate( + InAppWebViewPointerEventKind.up, + ev.pointer, + ev.localPosition, + ev.size, + ev.pressure); + return; + } + final button = _downButtons.remove(ev.pointer); + if (button != null) { + _controller._setPointerButtonState(button, false); + } + }, + onPointerCancel: (ev) { + _pointerKind = ev.kind; + final button = _downButtons.remove(ev.pointer); + if (button != null) { + _controller._setPointerButtonState(button, false); + } + }, + onPointerMove: (ev) { + _pointerKind = ev.kind; + if (ev.kind == PointerDeviceKind.touch) { + _controller._setPointerUpdate( + InAppWebViewPointerEventKind.update, + ev.pointer, + ev.localPosition, + ev.size, + ev.pressure); + } else { + _controller._setCursorPos(ev.localPosition); + } + }, + onPointerSignal: (signal) { + if (signal is PointerScrollEvent) { + _controller._setScrollDelta( + -signal.scrollDelta.dx, -signal.scrollDelta.dy); + } + }, + onPointerPanZoomUpdate: (ev) { + _controller._setScrollDelta( + ev.panDelta.dx, ev.panDelta.dy); + }, + child: MouseRegion( + cursor: _cursor, + child: Texture( + textureId: _controller._textureId, + filterQuality: widget.filterQuality, + )), + ) + : const SizedBox())); + } + + void _reportSurfaceSize() async { + final box = _key.currentContext?.findRenderObject() as RenderBox?; + if (box != null) { + await _controller.ready; + unawaited(_controller._setSize( + box.size, widget.scaleFactor ?? window.devicePixelRatio)); + + } + } + + void _reportWidgetPosition() async { + final box = _key.currentContext?.findRenderObject() as RenderBox?; + if (box != null) { + await _controller.ready; + final position = box.localToGlobal(Offset.zero); + unawaited(_controller._setPosition( + position, widget.scaleFactor ?? window.devicePixelRatio)); + } + } + + @override + void dispose() { + super.dispose(); + _cursorSubscription?.cancel(); + _controller.dispose(); + _focusNode.dispose(); + } +} diff --git a/flutter_inappwebview_windows/lib/src/in_app_webview/headless_in_app_webview.dart b/flutter_inappwebview_windows/lib/src/in_app_webview/headless_in_app_webview.dart new file mode 100644 index 00000000..1bede448 --- /dev/null +++ b/flutter_inappwebview_windows/lib/src/in_app_webview/headless_in_app_webview.dart @@ -0,0 +1,446 @@ +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; +import '../find_interaction/find_interaction_controller.dart'; +import '../webview_environment/webview_environment.dart'; +import 'in_app_webview_controller.dart'; + +/// Object specifying creation parameters for creating a [WindowsHeadlessInAppWebView]. +/// +/// When adding additional fields make sure they can be null or have a default +/// value to avoid breaking changes. See [PlatformHeadlessInAppWebViewCreationParams] for +/// more information. +@immutable +class WindowsHeadlessInAppWebViewCreationParams + extends PlatformHeadlessInAppWebViewCreationParams { + /// Creates a new [WindowsHeadlessInAppWebViewCreationParams] instance. + WindowsHeadlessInAppWebViewCreationParams( + {super.controllerFromPlatform, + super.initialSize, + this.webViewEnvironment, + super.windowId, + super.onWebViewCreated, + super.onLoadStart, + super.onLoadStop, + @Deprecated('Use onReceivedError instead') super.onLoadError, + super.onReceivedError, + @Deprecated("Use onReceivedHttpError instead") super.onLoadHttpError, + super.onReceivedHttpError, + super.onProgressChanged, + super.onConsoleMessage, + super.shouldOverrideUrlLoading, + super.onLoadResource, + super.onScrollChanged, + @Deprecated('Use onDownloadStartRequest instead') super.onDownloadStart, + super.onDownloadStartRequest, + @Deprecated('Use onLoadResourceWithCustomScheme instead') + super.onLoadResourceCustomScheme, + super.onLoadResourceWithCustomScheme, + super.onCreateWindow, + super.onCloseWindow, + super.onJsAlert, + super.onJsConfirm, + super.onJsPrompt, + super.onReceivedHttpAuthRequest, + super.onReceivedServerTrustAuthRequest, + super.onReceivedClientCertRequest, + @Deprecated('Use FindInteractionController.onFindResultReceived instead') + super.onFindResultReceived, + super.shouldInterceptAjaxRequest, + super.onAjaxReadyStateChange, + super.onAjaxProgress, + super.shouldInterceptFetchRequest, + super.onUpdateVisitedHistory, + @Deprecated("Use onPrintRequest instead") super.onPrint, + super.onPrintRequest, + super.onLongPressHitTestResult, + super.onEnterFullscreen, + super.onExitFullscreen, + super.onPageCommitVisible, + super.onTitleChanged, + super.onWindowFocus, + super.onWindowBlur, + super.onOverScrolled, + super.onZoomScaleChanged, + @Deprecated('Use onSafeBrowsingHit instead') + super.androidOnSafeBrowsingHit, + super.onSafeBrowsingHit, + @Deprecated('Use onPermissionRequest instead') + super.androidOnPermissionRequest, + super.onPermissionRequest, + @Deprecated('Use onGeolocationPermissionsShowPrompt instead') + super.androidOnGeolocationPermissionsShowPrompt, + super.onGeolocationPermissionsShowPrompt, + @Deprecated('Use onGeolocationPermissionsHidePrompt instead') + super.androidOnGeolocationPermissionsHidePrompt, + super.onGeolocationPermissionsHidePrompt, + @Deprecated('Use shouldInterceptRequest instead') + super.androidShouldInterceptRequest, + super.shouldInterceptRequest, + @Deprecated('Use onRenderProcessGone instead') + super.androidOnRenderProcessGone, + super.onRenderProcessGone, + @Deprecated('Use onRenderProcessResponsive instead') + super.androidOnRenderProcessResponsive, + super.onRenderProcessResponsive, + @Deprecated('Use onRenderProcessUnresponsive instead') + super.androidOnRenderProcessUnresponsive, + super.onRenderProcessUnresponsive, + @Deprecated('Use onFormResubmission instead') + super.androidOnFormResubmission, + super.onFormResubmission, + @Deprecated('Use onZoomScaleChanged instead') super.androidOnScaleChanged, + @Deprecated('Use onReceivedIcon instead') super.androidOnReceivedIcon, + super.onReceivedIcon, + @Deprecated('Use onReceivedTouchIconUrl instead') + super.androidOnReceivedTouchIconUrl, + super.onReceivedTouchIconUrl, + @Deprecated('Use onJsBeforeUnload instead') super.androidOnJsBeforeUnload, + super.onJsBeforeUnload, + @Deprecated('Use onReceivedLoginRequest instead') + super.androidOnReceivedLoginRequest, + super.onReceivedLoginRequest, + super.onPermissionRequestCanceled, + super.onRequestFocus, + @Deprecated('Use onWebContentProcessDidTerminate instead') + super.iosOnWebContentProcessDidTerminate, + super.onWebContentProcessDidTerminate, + @Deprecated( + 'Use onDidReceiveServerRedirectForProvisionalNavigation instead') + super.iosOnDidReceiveServerRedirectForProvisionalNavigation, + super.onDidReceiveServerRedirectForProvisionalNavigation, + @Deprecated('Use onNavigationResponse instead') + super.iosOnNavigationResponse, + super.onNavigationResponse, + @Deprecated('Use shouldAllowDeprecatedTLS instead') + super.iosShouldAllowDeprecatedTLS, + super.shouldAllowDeprecatedTLS, + super.onCameraCaptureStateChanged, + super.onMicrophoneCaptureStateChanged, + super.onContentSizeChanged, + super.initialUrlRequest, + super.initialFile, + super.initialData, + @Deprecated('Use initialSettings instead') super.initialOptions, + super.initialSettings, + super.contextMenu, + super.initialUserScripts, + super.pullToRefreshController, + this.findInteractionController}); + + /// Creates a [WindowsHeadlessInAppWebViewCreationParams] instance based on [PlatformHeadlessInAppWebViewCreationParams]. + WindowsHeadlessInAppWebViewCreationParams.fromPlatformHeadlessInAppWebViewCreationParams( + PlatformHeadlessInAppWebViewCreationParams params) + : this( + controllerFromPlatform: params.controllerFromPlatform, + webViewEnvironment: params.webViewEnvironment as WindowsWebViewEnvironment?, + initialSize: params.initialSize, + windowId: params.windowId, + onWebViewCreated: params.onWebViewCreated, + onLoadStart: params.onLoadStart, + onLoadStop: params.onLoadStop, + onLoadError: params.onLoadError, + onReceivedError: params.onReceivedError, + onLoadHttpError: params.onLoadHttpError, + onReceivedHttpError: params.onReceivedHttpError, + onProgressChanged: params.onProgressChanged, + onConsoleMessage: params.onConsoleMessage, + shouldOverrideUrlLoading: params.shouldOverrideUrlLoading, + onLoadResource: params.onLoadResource, + onScrollChanged: params.onScrollChanged, + onDownloadStart: params.onDownloadStart, + onDownloadStartRequest: params.onDownloadStartRequest, + onLoadResourceCustomScheme: params.onLoadResourceCustomScheme, + onLoadResourceWithCustomScheme: + params.onLoadResourceWithCustomScheme, + onCreateWindow: params.onCreateWindow, + onCloseWindow: params.onCloseWindow, + onJsAlert: params.onJsAlert, + onJsConfirm: params.onJsConfirm, + onJsPrompt: params.onJsPrompt, + onReceivedHttpAuthRequest: params.onReceivedHttpAuthRequest, + onReceivedServerTrustAuthRequest: + params.onReceivedServerTrustAuthRequest, + onReceivedClientCertRequest: params.onReceivedClientCertRequest, + onFindResultReceived: params.onFindResultReceived, + shouldInterceptAjaxRequest: params.shouldInterceptAjaxRequest, + onAjaxReadyStateChange: params.onAjaxReadyStateChange, + onAjaxProgress: params.onAjaxProgress, + shouldInterceptFetchRequest: params.shouldInterceptFetchRequest, + onUpdateVisitedHistory: params.onUpdateVisitedHistory, + onPrint: params.onPrint, + onPrintRequest: params.onPrintRequest, + onLongPressHitTestResult: params.onLongPressHitTestResult, + onEnterFullscreen: params.onEnterFullscreen, + onExitFullscreen: params.onExitFullscreen, + onPageCommitVisible: params.onPageCommitVisible, + onTitleChanged: params.onTitleChanged, + onWindowFocus: params.onWindowFocus, + onWindowBlur: params.onWindowBlur, + onOverScrolled: params.onOverScrolled, + onZoomScaleChanged: params.onZoomScaleChanged, + androidOnSafeBrowsingHit: params.androidOnSafeBrowsingHit, + onSafeBrowsingHit: params.onSafeBrowsingHit, + androidOnPermissionRequest: params.androidOnPermissionRequest, + onPermissionRequest: params.onPermissionRequest, + androidOnGeolocationPermissionsShowPrompt: + params.androidOnGeolocationPermissionsShowPrompt, + onGeolocationPermissionsShowPrompt: + params.onGeolocationPermissionsShowPrompt, + androidOnGeolocationPermissionsHidePrompt: + params.androidOnGeolocationPermissionsHidePrompt, + onGeolocationPermissionsHidePrompt: + params.onGeolocationPermissionsHidePrompt, + androidShouldInterceptRequest: params.androidShouldInterceptRequest, + shouldInterceptRequest: params.shouldInterceptRequest, + androidOnRenderProcessGone: params.androidOnRenderProcessGone, + onRenderProcessGone: params.onRenderProcessGone, + androidOnRenderProcessResponsive: + params.androidOnRenderProcessResponsive, + onRenderProcessResponsive: params.onRenderProcessResponsive, + androidOnRenderProcessUnresponsive: + params.androidOnRenderProcessUnresponsive, + onRenderProcessUnresponsive: params.onRenderProcessUnresponsive, + androidOnFormResubmission: params.androidOnFormResubmission, + onFormResubmission: params.onFormResubmission, + androidOnScaleChanged: params.androidOnScaleChanged, + androidOnReceivedIcon: params.androidOnReceivedIcon, + onReceivedIcon: params.onReceivedIcon, + androidOnReceivedTouchIconUrl: params.androidOnReceivedTouchIconUrl, + onReceivedTouchIconUrl: params.onReceivedTouchIconUrl, + androidOnJsBeforeUnload: params.androidOnJsBeforeUnload, + onJsBeforeUnload: params.onJsBeforeUnload, + androidOnReceivedLoginRequest: params.androidOnReceivedLoginRequest, + onReceivedLoginRequest: params.onReceivedLoginRequest, + onPermissionRequestCanceled: params.onPermissionRequestCanceled, + onRequestFocus: params.onRequestFocus, + iosOnWebContentProcessDidTerminate: + params.iosOnWebContentProcessDidTerminate, + onWebContentProcessDidTerminate: + params.onWebContentProcessDidTerminate, + iosOnDidReceiveServerRedirectForProvisionalNavigation: + params.iosOnDidReceiveServerRedirectForProvisionalNavigation, + onDidReceiveServerRedirectForProvisionalNavigation: + params.onDidReceiveServerRedirectForProvisionalNavigation, + iosOnNavigationResponse: params.iosOnNavigationResponse, + onNavigationResponse: params.onNavigationResponse, + iosShouldAllowDeprecatedTLS: params.iosShouldAllowDeprecatedTLS, + shouldAllowDeprecatedTLS: params.shouldAllowDeprecatedTLS, + onCameraCaptureStateChanged: params.onCameraCaptureStateChanged, + onMicrophoneCaptureStateChanged: + params.onMicrophoneCaptureStateChanged, + onContentSizeChanged: params.onContentSizeChanged, + initialUrlRequest: params.initialUrlRequest, + initialFile: params.initialFile, + initialData: params.initialData, + initialOptions: params.initialOptions, + initialSettings: params.initialSettings, + contextMenu: params.contextMenu, + initialUserScripts: params.initialUserScripts, + pullToRefreshController: params.pullToRefreshController, + findInteractionController: params.findInteractionController + as WindowsFindInteractionController?); + + @override + final WindowsFindInteractionController? findInteractionController; + + @override + final WindowsWebViewEnvironment? webViewEnvironment; +} + +///{@macro flutter_inappwebview_platform_interface.PlatformHeadlessInAppWebView} +class WindowsHeadlessInAppWebView extends PlatformHeadlessInAppWebView + with ChannelController { + @override + late final String id; + + bool _started = false; + bool _running = false; + + static const MethodChannel _sharedChannel = + const MethodChannel('com.pichillilorenzo/flutter_headless_inappwebview'); + + WindowsInAppWebViewController? _webViewController; + + /// Constructs a [WindowsHeadlessInAppWebView]. + WindowsHeadlessInAppWebView(PlatformHeadlessInAppWebViewCreationParams params) + : super.implementation( + params is WindowsHeadlessInAppWebViewCreationParams + ? params + : WindowsHeadlessInAppWebViewCreationParams + .fromPlatformHeadlessInAppWebViewCreationParams(params), + ) { + id = IdGenerator.generate(); + } + + @override + WindowsInAppWebViewController? get webViewController => _webViewController; + + dynamic _controllerFromPlatform; + + WindowsHeadlessInAppWebViewCreationParams get _windowsParams => + params as WindowsHeadlessInAppWebViewCreationParams; + + _init() { + _webViewController = WindowsInAppWebViewController( + WindowsInAppWebViewControllerCreationParams(id: id, webviewParams: params), + ); + _controllerFromPlatform = + params.controllerFromPlatform?.call(_webViewController!) ?? + _webViewController!; + _windowsParams.findInteractionController?.init(id); + channel = + MethodChannel('com.pichillilorenzo/flutter_headless_inappwebview_$id'); + handler = _handleMethod; + initMethodCallHandler(); + } + + Future _handleMethod(MethodCall call) async { + switch (call.method) { + case "onWebViewCreated": + if (params.onWebViewCreated != null && _webViewController != null) { + params.onWebViewCreated!(_controllerFromPlatform); + } + break; + default: + throw UnimplementedError("Unimplemented ${call.method} method"); + } + return null; + } + + Future run() async { + if (_started) { + return; + } + _started = true; + _init(); + + final initialSettings = params.initialSettings ?? InAppWebViewSettings(); + _inferInitialSettings(initialSettings); + + Map settingsMap = + (params.initialSettings != null ? initialSettings.toMap() : null) ?? + params.initialOptions?.toMap() ?? + initialSettings.toMap(); + + Map pullToRefreshSettings = + _windowsParams.pullToRefreshController?.params.settings.toMap() ?? + _windowsParams.pullToRefreshController?.params.options.toMap() ?? + PullToRefreshSettings(enabled: false).toMap(); + + Map args = {}; + args.putIfAbsent('id', () => id); + args.putIfAbsent( + 'params', + () => { + 'initialUrlRequest': params.initialUrlRequest?.toMap(), + 'initialFile': params.initialFile, + 'initialData': params.initialData?.toMap(), + 'initialSettings': settingsMap, + 'contextMenu': params.contextMenu?.toMap() ?? {}, + 'windowId': params.windowId, + 'initialUserScripts': + params.initialUserScripts?.map((e) => e.toMap()).toList() ?? + [], + 'pullToRefreshSettings': pullToRefreshSettings, + 'initialSize': params.initialSize.toMap(), + 'webViewEnvironmentId': params.webViewEnvironment?.id, + }); + try { + await _sharedChannel.invokeMethod('run', args); + _running = true; + } catch (e) { + _running = false; + _started = false; + throw e; + } + } + + void _inferInitialSettings(InAppWebViewSettings settings) { + if (params.shouldOverrideUrlLoading != null && + settings.useShouldOverrideUrlLoading == null) { + settings.useShouldOverrideUrlLoading = true; + } + if (params.onLoadResource != null && settings.useOnLoadResource == null) { + settings.useOnLoadResource = true; + } + if (params.onDownloadStartRequest != null && + settings.useOnDownloadStart == null) { + settings.useOnDownloadStart = true; + } + if (params.shouldInterceptAjaxRequest != null && + settings.useShouldInterceptAjaxRequest == null) { + settings.useShouldInterceptAjaxRequest = true; + } + if (params.shouldInterceptFetchRequest != null && + settings.useShouldInterceptFetchRequest == null) { + settings.useShouldInterceptFetchRequest = true; + } + if (params.shouldInterceptRequest != null && + settings.useShouldInterceptRequest == null) { + settings.useShouldInterceptRequest = true; + } + if (params.onRenderProcessGone != null && + settings.useOnRenderProcessGone == null) { + settings.useOnRenderProcessGone = true; + } + if (params.onNavigationResponse != null && + settings.useOnNavigationResponse == null) { + settings.useOnNavigationResponse = true; + } + } + + @override + bool isRunning() { + return _running; + } + + @override + Future setSize(Size size) async { + if (!_running) { + return; + } + + Map args = {}; + args.putIfAbsent('size', () => size.toMap()); + await channel?.invokeMethod('setSize', args); + } + + @override + Future getSize() async { + if (!_running) { + return null; + } + + Map args = {}; + Map sizeMap = + (await channel?.invokeMethod('getSize', args))?.cast(); + return MapSize.fromMap(sizeMap); + } + + @override + Future dispose() async { + if (!_running) { + return; + } + Map args = {}; + await channel?.invokeMethod('dispose', args); + disposeChannel(); + _started = false; + _running = false; + _webViewController?.dispose(); + _webViewController = null; + _controllerFromPlatform = null; + _windowsParams.pullToRefreshController?.dispose(); + _windowsParams.findInteractionController?.dispose(); + } +} + +extension InternalHeadlessInAppWebView on WindowsHeadlessInAppWebView { + Future internalDispose() async { + _started = false; + _running = false; + } +} diff --git a/flutter_inappwebview_windows/lib/src/in_app_webview/in_app_webview.dart b/flutter_inappwebview_windows/lib/src/in_app_webview/in_app_webview.dart new file mode 100644 index 00000000..61f44880 --- /dev/null +++ b/flutter_inappwebview_windows/lib/src/in_app_webview/in_app_webview.dart @@ -0,0 +1,411 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; +import '../webview_environment/webview_environment.dart'; +import 'headless_in_app_webview.dart'; + +import '../find_interaction/find_interaction_controller.dart'; +import 'in_app_webview_controller.dart'; + +import 'custom_platform_view.dart'; + +/// Object specifying creation parameters for creating a [PlatformInAppWebViewWidget]. +/// +/// Platform specific implementations can add additional fields by extending +/// this class. +class WindowsInAppWebViewWidgetCreationParams + extends PlatformInAppWebViewWidgetCreationParams { + WindowsInAppWebViewWidgetCreationParams( + {super.controllerFromPlatform, + super.key, + super.layoutDirection, + super.gestureRecognizers, + super.headlessWebView, + super.keepAlive, + super.preventGestureDelay, + super.windowId, + this.webViewEnvironment, + super.onWebViewCreated, + super.onLoadStart, + super.onLoadStop, + @Deprecated('Use onReceivedError instead') super.onLoadError, + super.onReceivedError, + @Deprecated("Use onReceivedHttpError instead") super.onLoadHttpError, + super.onReceivedHttpError, + super.onProgressChanged, + super.onConsoleMessage, + super.shouldOverrideUrlLoading, + super.onLoadResource, + super.onScrollChanged, + @Deprecated('Use onDownloadStartRequest instead') super.onDownloadStart, + super.onDownloadStartRequest, + @Deprecated('Use onLoadResourceWithCustomScheme instead') + super.onLoadResourceCustomScheme, + super.onLoadResourceWithCustomScheme, + super.onCreateWindow, + super.onCloseWindow, + super.onJsAlert, + super.onJsConfirm, + super.onJsPrompt, + super.onReceivedHttpAuthRequest, + super.onReceivedServerTrustAuthRequest, + super.onReceivedClientCertRequest, + @Deprecated('Use FindInteractionController.onFindResultReceived instead') + super.onFindResultReceived, + super.shouldInterceptAjaxRequest, + super.onAjaxReadyStateChange, + super.onAjaxProgress, + super.shouldInterceptFetchRequest, + super.onUpdateVisitedHistory, + @Deprecated("Use onPrintRequest instead") super.onPrint, + super.onPrintRequest, + super.onLongPressHitTestResult, + super.onEnterFullscreen, + super.onExitFullscreen, + super.onPageCommitVisible, + super.onTitleChanged, + super.onWindowFocus, + super.onWindowBlur, + super.onOverScrolled, + super.onZoomScaleChanged, + @Deprecated('Use onSafeBrowsingHit instead') + super.androidOnSafeBrowsingHit, + super.onSafeBrowsingHit, + @Deprecated('Use onPermissionRequest instead') + super.androidOnPermissionRequest, + super.onPermissionRequest, + @Deprecated('Use onGeolocationPermissionsShowPrompt instead') + super.androidOnGeolocationPermissionsShowPrompt, + super.onGeolocationPermissionsShowPrompt, + @Deprecated('Use onGeolocationPermissionsHidePrompt instead') + super.androidOnGeolocationPermissionsHidePrompt, + super.onGeolocationPermissionsHidePrompt, + @Deprecated('Use shouldInterceptRequest instead') + super.androidShouldInterceptRequest, + super.shouldInterceptRequest, + @Deprecated('Use onRenderProcessGone instead') + super.androidOnRenderProcessGone, + super.onRenderProcessGone, + @Deprecated('Use onRenderProcessResponsive instead') + super.androidOnRenderProcessResponsive, + super.onRenderProcessResponsive, + @Deprecated('Use onRenderProcessUnresponsive instead') + super.androidOnRenderProcessUnresponsive, + super.onRenderProcessUnresponsive, + @Deprecated('Use onFormResubmission instead') + super.androidOnFormResubmission, + super.onFormResubmission, + @Deprecated('Use onZoomScaleChanged instead') super.androidOnScaleChanged, + @Deprecated('Use onReceivedIcon instead') super.androidOnReceivedIcon, + super.onReceivedIcon, + @Deprecated('Use onReceivedTouchIconUrl instead') + super.androidOnReceivedTouchIconUrl, + super.onReceivedTouchIconUrl, + @Deprecated('Use onJsBeforeUnload instead') super.androidOnJsBeforeUnload, + super.onJsBeforeUnload, + @Deprecated('Use onReceivedLoginRequest instead') + super.androidOnReceivedLoginRequest, + super.onReceivedLoginRequest, + super.onPermissionRequestCanceled, + super.onRequestFocus, + @Deprecated('Use onWebContentProcessDidTerminate instead') + super.iosOnWebContentProcessDidTerminate, + super.onWebContentProcessDidTerminate, + @Deprecated( + 'Use onDidReceiveServerRedirectForProvisionalNavigation instead') + super.iosOnDidReceiveServerRedirectForProvisionalNavigation, + super.onDidReceiveServerRedirectForProvisionalNavigation, + @Deprecated('Use onNavigationResponse instead') + super.iosOnNavigationResponse, + super.onNavigationResponse, + @Deprecated('Use shouldAllowDeprecatedTLS instead') + super.iosShouldAllowDeprecatedTLS, + super.shouldAllowDeprecatedTLS, + super.onCameraCaptureStateChanged, + super.onMicrophoneCaptureStateChanged, + super.onContentSizeChanged, + super.initialUrlRequest, + super.initialFile, + super.initialData, + @Deprecated('Use initialSettings instead') super.initialOptions, + super.initialSettings, + super.contextMenu, + super.initialUserScripts, + super.pullToRefreshController, + this.findInteractionController}); + + /// Constructs a [WindowsInAppWebViewWidgetCreationParams] using a + /// [PlatformInAppWebViewWidgetCreationParams]. + WindowsInAppWebViewWidgetCreationParams.fromPlatformInAppWebViewWidgetCreationParams( + PlatformInAppWebViewWidgetCreationParams params) + : this( + controllerFromPlatform: params.controllerFromPlatform, + key: params.key, + layoutDirection: params.layoutDirection, + gestureRecognizers: params.gestureRecognizers, + headlessWebView: params.headlessWebView, + keepAlive: params.keepAlive, + preventGestureDelay: params.preventGestureDelay, + windowId: params.windowId, + webViewEnvironment: params.webViewEnvironment as WindowsWebViewEnvironment?, + onWebViewCreated: params.onWebViewCreated, + onLoadStart: params.onLoadStart, + onLoadStop: params.onLoadStop, + onLoadError: params.onLoadError, + onReceivedError: params.onReceivedError, + onLoadHttpError: params.onLoadHttpError, + onReceivedHttpError: params.onReceivedHttpError, + onProgressChanged: params.onProgressChanged, + onConsoleMessage: params.onConsoleMessage, + shouldOverrideUrlLoading: params.shouldOverrideUrlLoading, + onLoadResource: params.onLoadResource, + onScrollChanged: params.onScrollChanged, + onDownloadStart: params.onDownloadStart, + onDownloadStartRequest: params.onDownloadStartRequest, + onLoadResourceCustomScheme: params.onLoadResourceCustomScheme, + onLoadResourceWithCustomScheme: + params.onLoadResourceWithCustomScheme, + onCreateWindow: params.onCreateWindow, + onCloseWindow: params.onCloseWindow, + onJsAlert: params.onJsAlert, + onJsConfirm: params.onJsConfirm, + onJsPrompt: params.onJsPrompt, + onReceivedHttpAuthRequest: params.onReceivedHttpAuthRequest, + onReceivedServerTrustAuthRequest: + params.onReceivedServerTrustAuthRequest, + onReceivedClientCertRequest: params.onReceivedClientCertRequest, + onFindResultReceived: params.onFindResultReceived, + shouldInterceptAjaxRequest: params.shouldInterceptAjaxRequest, + onAjaxReadyStateChange: params.onAjaxReadyStateChange, + onAjaxProgress: params.onAjaxProgress, + shouldInterceptFetchRequest: params.shouldInterceptFetchRequest, + onUpdateVisitedHistory: params.onUpdateVisitedHistory, + onPrint: params.onPrint, + onPrintRequest: params.onPrintRequest, + onLongPressHitTestResult: params.onLongPressHitTestResult, + onEnterFullscreen: params.onEnterFullscreen, + onExitFullscreen: params.onExitFullscreen, + onPageCommitVisible: params.onPageCommitVisible, + onTitleChanged: params.onTitleChanged, + onWindowFocus: params.onWindowFocus, + onWindowBlur: params.onWindowBlur, + onOverScrolled: params.onOverScrolled, + onZoomScaleChanged: params.onZoomScaleChanged, + androidOnSafeBrowsingHit: params.androidOnSafeBrowsingHit, + onSafeBrowsingHit: params.onSafeBrowsingHit, + androidOnPermissionRequest: params.androidOnPermissionRequest, + onPermissionRequest: params.onPermissionRequest, + androidOnGeolocationPermissionsShowPrompt: + params.androidOnGeolocationPermissionsShowPrompt, + onGeolocationPermissionsShowPrompt: + params.onGeolocationPermissionsShowPrompt, + androidOnGeolocationPermissionsHidePrompt: + params.androidOnGeolocationPermissionsHidePrompt, + onGeolocationPermissionsHidePrompt: + params.onGeolocationPermissionsHidePrompt, + androidShouldInterceptRequest: params.androidShouldInterceptRequest, + shouldInterceptRequest: params.shouldInterceptRequest, + androidOnRenderProcessGone: params.androidOnRenderProcessGone, + onRenderProcessGone: params.onRenderProcessGone, + androidOnRenderProcessResponsive: + params.androidOnRenderProcessResponsive, + onRenderProcessResponsive: params.onRenderProcessResponsive, + androidOnRenderProcessUnresponsive: + params.androidOnRenderProcessUnresponsive, + onRenderProcessUnresponsive: params.onRenderProcessUnresponsive, + androidOnFormResubmission: params.androidOnFormResubmission, + onFormResubmission: params.onFormResubmission, + androidOnScaleChanged: params.androidOnScaleChanged, + androidOnReceivedIcon: params.androidOnReceivedIcon, + onReceivedIcon: params.onReceivedIcon, + androidOnReceivedTouchIconUrl: params.androidOnReceivedTouchIconUrl, + onReceivedTouchIconUrl: params.onReceivedTouchIconUrl, + androidOnJsBeforeUnload: params.androidOnJsBeforeUnload, + onJsBeforeUnload: params.onJsBeforeUnload, + androidOnReceivedLoginRequest: params.androidOnReceivedLoginRequest, + onReceivedLoginRequest: params.onReceivedLoginRequest, + onPermissionRequestCanceled: params.onPermissionRequestCanceled, + onRequestFocus: params.onRequestFocus, + iosOnWebContentProcessDidTerminate: + params.iosOnWebContentProcessDidTerminate, + onWebContentProcessDidTerminate: + params.onWebContentProcessDidTerminate, + iosOnDidReceiveServerRedirectForProvisionalNavigation: + params.iosOnDidReceiveServerRedirectForProvisionalNavigation, + onDidReceiveServerRedirectForProvisionalNavigation: + params.onDidReceiveServerRedirectForProvisionalNavigation, + iosOnNavigationResponse: params.iosOnNavigationResponse, + onNavigationResponse: params.onNavigationResponse, + iosShouldAllowDeprecatedTLS: params.iosShouldAllowDeprecatedTLS, + shouldAllowDeprecatedTLS: params.shouldAllowDeprecatedTLS, + onCameraCaptureStateChanged: params.onCameraCaptureStateChanged, + onMicrophoneCaptureStateChanged: + params.onMicrophoneCaptureStateChanged, + onContentSizeChanged: params.onContentSizeChanged, + initialUrlRequest: params.initialUrlRequest, + initialFile: params.initialFile, + initialData: params.initialData, + initialOptions: params.initialOptions, + initialSettings: params.initialSettings, + contextMenu: params.contextMenu, + initialUserScripts: params.initialUserScripts, + pullToRefreshController: params.pullToRefreshController, + findInteractionController: params.findInteractionController + as WindowsFindInteractionController?); + + @override + final WindowsFindInteractionController? findInteractionController; + + @override + final WindowsWebViewEnvironment? webViewEnvironment; +} + +///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewWidget} +class WindowsInAppWebViewWidget extends PlatformInAppWebViewWidget { + /// Constructs a [WindowsInAppWebViewWidget]. + /// + ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewWidget} + WindowsInAppWebViewWidget(PlatformInAppWebViewWidgetCreationParams params) + : super.implementation( + params is WindowsInAppWebViewWidgetCreationParams + ? params + : WindowsInAppWebViewWidgetCreationParams + .fromPlatformInAppWebViewWidgetCreationParams(params), + ); + + WindowsInAppWebViewWidgetCreationParams get _windowsParams => + params as WindowsInAppWebViewWidgetCreationParams; + + WindowsInAppWebViewController? _controller; + + WindowsHeadlessInAppWebView? get _windowsHeadlessInAppWebView => + params.headlessWebView as WindowsHeadlessInAppWebView?; + + @override + Widget build(BuildContext context) { + final initialSettings = params.initialSettings ?? InAppWebViewSettings(); + _inferInitialSettings(initialSettings); + + Map settingsMap = + (params.initialSettings != null ? initialSettings.toMap() : null) ?? + // ignore: deprecated_member_use_from_same_package + params.initialOptions?.toMap() ?? + initialSettings.toMap(); + + Map pullToRefreshSettings = + params.pullToRefreshController?.params.settings.toMap() ?? + // ignore: deprecated_member_use_from_same_package + params.pullToRefreshController?.params.options.toMap() ?? + PullToRefreshSettings(enabled: false).toMap(); + + if ((params.headlessWebView?.isRunning() ?? false) && + params.keepAlive != null) { + final headlessId = params.headlessWebView?.id; + if (headlessId != null) { + // force keep alive id to match headless webview id + params.keepAlive?.id = headlessId; + } + } + + return CustomPlatformView( + onPlatformViewCreated: _onPlatformViewCreated, + creationParams: { + 'initialUrlRequest': params.initialUrlRequest?.toMap(), + 'initialFile': params.initialFile, + 'initialData': params.initialData?.toMap(), + 'initialSettings': settingsMap, + 'contextMenu': params.contextMenu?.toMap() ?? {}, + 'windowId': params.windowId, + 'headlessWebViewId': params.headlessWebView?.isRunning() ?? false + ? params.headlessWebView?.id + : null, + 'initialUserScripts': + params.initialUserScripts?.map((e) => e.toMap()).toList() ?? [], + 'keepAliveId': params.keepAlive?.id, + 'webViewEnvironmentId': params.webViewEnvironment?.id, + }, + ); + } + + void _onPlatformViewCreated(int id) { + dynamic viewId = id; + if (params.headlessWebView?.isRunning() ?? false) { + viewId = params.headlessWebView?.id; + } + viewId = params.keepAlive?.id ?? viewId ?? id; + _windowsHeadlessInAppWebView?.internalDispose(); + _controller = WindowsInAppWebViewController( + PlatformInAppWebViewControllerCreationParams( + id: viewId, webviewParams: params)); + _windowsParams.findInteractionController?.init(viewId); + debugLog( + className: runtimeType.toString(), + id: viewId?.toString(), + debugLoggingSettings: + PlatformInAppWebViewController.debugLoggingSettings, + method: "onWebViewCreated", + args: []); + if (params.onWebViewCreated != null) { + params.onWebViewCreated!( + params.controllerFromPlatform?.call(_controller!) ?? _controller!); + } + } + + void _inferInitialSettings(InAppWebViewSettings settings) { + if (params.shouldOverrideUrlLoading != null && + settings.useShouldOverrideUrlLoading == null) { + settings.useShouldOverrideUrlLoading = true; + } + if (params.onLoadResource != null && settings.useOnLoadResource == null) { + settings.useOnLoadResource = true; + } + if (params.onDownloadStartRequest != null && + settings.useOnDownloadStart == null) { + settings.useOnDownloadStart = true; + } + if ((params.shouldInterceptAjaxRequest != null || + params.onAjaxProgress != null || + params.onAjaxReadyStateChange != null) && + settings.useShouldInterceptAjaxRequest == null) { + settings.useShouldInterceptAjaxRequest = true; + } + if (params.shouldInterceptFetchRequest != null && + settings.useShouldInterceptFetchRequest == null) { + settings.useShouldInterceptFetchRequest = true; + } + if (params.shouldInterceptRequest != null && + settings.useShouldInterceptRequest == null) { + settings.useShouldInterceptRequest = true; + } + if (params.onRenderProcessGone != null && + settings.useOnRenderProcessGone == null) { + settings.useOnRenderProcessGone = true; + } + if (params.onNavigationResponse != null && + settings.useOnNavigationResponse == null) { + settings.useOnNavigationResponse = true; + } + } + + @override + void dispose() { + dynamic viewId = _controller?.getViewId(); + debugLog( + className: runtimeType.toString(), + id: viewId?.toString(), + debugLoggingSettings: + PlatformInAppWebViewController.debugLoggingSettings, + method: "dispose", + args: []); + final isKeepAlive = params.keepAlive != null; + _controller?.dispose(isKeepAlive: isKeepAlive); + _controller = null; + params.findInteractionController?.dispose(isKeepAlive: isKeepAlive); + } + + @override + T controllerFromPlatform(PlatformInAppWebViewController controller) { + // unused + throw UnimplementedError(); + } +} diff --git a/flutter_inappwebview_windows/lib/src/in_app_webview/in_app_webview_controller.dart b/flutter_inappwebview_windows/lib/src/in_app_webview/in_app_webview_controller.dart new file mode 100644 index 00000000..37997164 --- /dev/null +++ b/flutter_inappwebview_windows/lib/src/in_app_webview/in_app_webview_controller.dart @@ -0,0 +1,2745 @@ +import 'dart:io'; +import 'dart:collection'; +import 'dart:convert'; +import 'dart:core'; +import 'dart:developer' as developer; +import 'dart:typed_data'; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; + +import '../web_message/main.dart'; + +import '../in_app_browser/in_app_browser.dart'; +import '../web_storage/web_storage.dart'; + +import 'headless_in_app_webview.dart'; +import '_static_channel.dart'; + +import '../print_job/main.dart'; + +///List of forbidden names for JavaScript handlers. +// ignore: non_constant_identifier_names +final _JAVASCRIPT_HANDLER_FORBIDDEN_NAMES = UnmodifiableListView([ + "onLoadResource", + "shouldInterceptAjaxRequest", + "onAjaxReadyStateChange", + "onAjaxProgress", + "shouldInterceptFetchRequest", + "onPrintRequest", + "onWindowFocus", + "onWindowBlur", + "callAsyncJavaScript", + "evaluateJavaScriptWithContentWorld" +]); + +/// Object specifying creation parameters for creating a [WindowsInAppWebViewController]. +/// +/// When adding additional fields make sure they can be null or have a default +/// value to avoid breaking changes. See [PlatformInAppWebViewControllerCreationParams] for +/// more information. +@immutable +class WindowsInAppWebViewControllerCreationParams + extends PlatformInAppWebViewControllerCreationParams { + /// Creates a new [WindowsInAppWebViewControllerCreationParams] instance. + const WindowsInAppWebViewControllerCreationParams( + {required super.id, super.webviewParams}); + + /// Creates a [WindowsInAppWebViewControllerCreationParams] instance based on [PlatformInAppWebViewControllerCreationParams]. + factory WindowsInAppWebViewControllerCreationParams.fromPlatformInAppWebViewControllerCreationParams( + // Recommended placeholder to prevent being broken by platform interface. + // ignore: avoid_unused_constructor_parameters + PlatformInAppWebViewControllerCreationParams params) { + return WindowsInAppWebViewControllerCreationParams( + id: params.id, webviewParams: params.webviewParams); + } +} + +///Controls a WebView, such as an [InAppWebView] widget instance, a [WindowsHeadlessInAppWebView] instance or [WindowsInAppBrowser] WebView instance. +/// +///If you are using the [InAppWebView] widget, an [InAppWebViewController] instance can be obtained by setting the [InAppWebView.onWebViewCreated] +///callback. Instead, if you are using an [WindowsInAppBrowser] instance, you can get it through the [WindowsInAppBrowser.webViewController] attribute. +class WindowsInAppWebViewController extends PlatformInAppWebViewController + with ChannelController { + static final MethodChannel _staticChannel = IN_APP_WEBVIEW_STATIC_CHANNEL; + + // List of properties to be saved and restored for keep alive feature + Map _javaScriptHandlersMap = + HashMap(); + Map> _userScripts = { + UserScriptInjectionTime.AT_DOCUMENT_START: [], + UserScriptInjectionTime.AT_DOCUMENT_END: [] + }; + Set _webMessageListenerObjNames = Set(); + Map _injectedScriptsFromURL = {}; + Set _webMessageChannels = Set(); + Set _webMessageListeners = Set(); + Map _devToolsProtocolEventListenerMap = HashMap(); + + // static map that contains the properties to be saved and restored for keep alive feature + static final Map + _keepAliveMap = {}; + + WindowsInAppBrowser? _inAppBrowser; + + PlatformInAppBrowserEvents? get _inAppBrowserEventHandler => + _inAppBrowser?.eventHandler; + + dynamic _controllerFromPlatform; + + @override + late WindowsWebStorage webStorage; + + WindowsInAppWebViewController( + PlatformInAppWebViewControllerCreationParams params) + : super.implementation(params is WindowsInAppWebViewControllerCreationParams + ? params + : WindowsInAppWebViewControllerCreationParams + .fromPlatformInAppWebViewControllerCreationParams(params)) { + channel = MethodChannel('com.pichillilorenzo/flutter_inappwebview_$id'); + handler = handleMethod; + initMethodCallHandler(); + + final initialUserScripts = webviewParams?.initialUserScripts; + if (initialUserScripts != null) { + for (final userScript in initialUserScripts) { + if (userScript.injectionTime == + UserScriptInjectionTime.AT_DOCUMENT_START) { + this + ._userScripts[UserScriptInjectionTime.AT_DOCUMENT_START] + ?.add(userScript); + } else { + this + ._userScripts[UserScriptInjectionTime.AT_DOCUMENT_END] + ?.add(userScript); + } + } + } + + this._init(params); + } + + static final WindowsInAppWebViewController _staticValue = + WindowsInAppWebViewController( + WindowsInAppWebViewControllerCreationParams(id: null)); + + factory WindowsInAppWebViewController.static() { + return _staticValue; + } + + WindowsInAppWebViewController.fromInAppBrowser( + PlatformInAppWebViewControllerCreationParams params, + MethodChannel channel, + WindowsInAppBrowser inAppBrowser, + UnmodifiableListView? initialUserScripts) + : super.implementation(params is WindowsInAppWebViewControllerCreationParams + ? params + : WindowsInAppWebViewControllerCreationParams + .fromPlatformInAppWebViewControllerCreationParams(params)) { + this.channel = channel; + this._inAppBrowser = inAppBrowser; + + if (initialUserScripts != null) { + for (final userScript in initialUserScripts) { + if (userScript.injectionTime == + UserScriptInjectionTime.AT_DOCUMENT_START) { + this + ._userScripts[UserScriptInjectionTime.AT_DOCUMENT_START] + ?.add(userScript); + } else { + this + ._userScripts[UserScriptInjectionTime.AT_DOCUMENT_END] + ?.add(userScript); + } + } + } + this._init(params); + } + + void _init(PlatformInAppWebViewControllerCreationParams params) { + _controllerFromPlatform = + params.webviewParams?.controllerFromPlatform?.call(this) ?? this; + + webStorage = WindowsWebStorage(WindowsWebStorageCreationParams( + localStorage: WindowsLocalStorage.defaultStorage(controller: this), + sessionStorage: WindowsSessionStorage.defaultStorage(controller: this))); + + if (params.webviewParams is PlatformInAppWebViewWidgetCreationParams) { + final keepAlive = + (params.webviewParams as PlatformInAppWebViewWidgetCreationParams) + .keepAlive; + if (keepAlive != null) { + InAppWebViewControllerKeepAliveProps? props = _keepAliveMap[keepAlive]; + if (props == null) { + // save controller properties to restore it later + _keepAliveMap[keepAlive] = InAppWebViewControllerKeepAliveProps( + injectedScriptsFromURL: _injectedScriptsFromURL, + javaScriptHandlersMap: _javaScriptHandlersMap, + userScripts: _userScripts, + webMessageListenerObjNames: _webMessageListenerObjNames, + webMessageChannels: _webMessageChannels, + webMessageListeners: _webMessageListeners, + devToolsProtocolEventListenerMap: _devToolsProtocolEventListenerMap + ); + } else { + // restore controller properties + _injectedScriptsFromURL = props.injectedScriptsFromURL; + _javaScriptHandlersMap = props.javaScriptHandlersMap; + _userScripts = props.userScripts; + _webMessageListenerObjNames = props.webMessageListenerObjNames; + _webMessageChannels = + props.webMessageChannels as Set; + _webMessageListeners = + props.webMessageListeners as Set; + _devToolsProtocolEventListenerMap = props.devToolsProtocolEventListenerMap; + } + } + } + } + + _debugLog(String method, dynamic args) { + debugLog( + className: this.runtimeType.toString(), + name: _inAppBrowser == null + ? "WebView" + : _inAppBrowser.runtimeType.toString(), + id: (getViewId() ?? _inAppBrowser?.id).toString(), + debugLoggingSettings: + PlatformInAppWebViewController.debugLoggingSettings, + method: method, + args: args); + } + + Future _handleMethod(MethodCall call) async { + if (PlatformInAppWebViewController.debugLoggingSettings.enabled && + call.method != "onCallJsHandler") { + _debugLog(call.method, call.arguments); + } + + switch (call.method) { + case "onLoadStart": + _injectedScriptsFromURL.clear(); + if ((webviewParams != null && webviewParams!.onLoadStart != null) || + _inAppBrowserEventHandler != null) { + String? url = call.arguments["url"]; + WebUri? uri = url != null ? WebUri(url) : null; + if (webviewParams != null && webviewParams!.onLoadStart != null) + webviewParams!.onLoadStart!(_controllerFromPlatform, uri); + else + _inAppBrowserEventHandler!.onLoadStart(uri); + } + break; + case "onLoadStop": + if ((webviewParams != null && webviewParams!.onLoadStop != null) || + _inAppBrowserEventHandler != null) { + String? url = call.arguments["url"]; + WebUri? uri = url != null ? WebUri(url) : null; + if (webviewParams != null && webviewParams!.onLoadStop != null) + webviewParams!.onLoadStop!(_controllerFromPlatform, uri); + else + _inAppBrowserEventHandler!.onLoadStop(uri); + } + break; + case "onReceivedError": + if ((webviewParams != null && + (webviewParams!.onReceivedError != null || + // ignore: deprecated_member_use_from_same_package + webviewParams!.onLoadError != null)) || + _inAppBrowserEventHandler != null) { + WebResourceRequest request = WebResourceRequest.fromMap( + call.arguments["request"].cast())!; + WebResourceError error = WebResourceError.fromMap( + call.arguments["error"].cast())!; + var isForMainFrame = request.isForMainFrame ?? false; + + if (webviewParams != null) { + if (webviewParams!.onReceivedError != null) + webviewParams!.onReceivedError!( + _controllerFromPlatform, request, error); + else if (isForMainFrame) { + // ignore: deprecated_member_use_from_same_package + webviewParams!.onLoadError!(_controllerFromPlatform, request.url, + error.type.toNativeValue() ?? -1, error.description); + } + } else { + if (isForMainFrame) { + _inAppBrowserEventHandler!.onLoadError(request.url, + error.type.toNativeValue() ?? -1, error.description); + } + _inAppBrowserEventHandler!.onReceivedError(request, error); + } + } + break; + case "onReceivedHttpError": + if ((webviewParams != null && + (webviewParams!.onReceivedHttpError != null || + // ignore: deprecated_member_use_from_same_package + webviewParams!.onLoadHttpError != null)) || + _inAppBrowserEventHandler != null) { + WebResourceRequest request = WebResourceRequest.fromMap( + call.arguments["request"].cast())!; + WebResourceResponse errorResponse = WebResourceResponse.fromMap( + call.arguments["errorResponse"].cast())!; + var isForMainFrame = request.isForMainFrame ?? false; + + if (webviewParams != null) { + if (webviewParams!.onReceivedHttpError != null) + webviewParams!.onReceivedHttpError!( + _controllerFromPlatform, request, errorResponse); + else if (isForMainFrame) { + // ignore: deprecated_member_use_from_same_package + webviewParams!.onLoadHttpError!( + _controllerFromPlatform, + request.url, + errorResponse.statusCode ?? -1, + errorResponse.reasonPhrase ?? ''); + } + } else { + if (isForMainFrame) { + _inAppBrowserEventHandler!.onLoadHttpError( + request.url, + errorResponse.statusCode ?? -1, + errorResponse.reasonPhrase ?? ''); + } + _inAppBrowserEventHandler! + .onReceivedHttpError(request, errorResponse); + } + } + break; + case "onProgressChanged": + if ((webviewParams != null && + webviewParams!.onProgressChanged != null) || + _inAppBrowserEventHandler != null) { + int progress = call.arguments["progress"]; + if (webviewParams != null && webviewParams!.onProgressChanged != null) + webviewParams!.onProgressChanged!( + _controllerFromPlatform, progress); + else + _inAppBrowserEventHandler!.onProgressChanged(progress); + } + break; + case "shouldOverrideUrlLoading": + if ((webviewParams != null && + webviewParams!.shouldOverrideUrlLoading != null) || + _inAppBrowserEventHandler != null) { + Map arguments = + call.arguments.cast(); + NavigationAction navigationAction = + NavigationAction.fromMap(arguments)!; + + if (webviewParams != null && + webviewParams!.shouldOverrideUrlLoading != null) + return (await webviewParams!.shouldOverrideUrlLoading!( + _controllerFromPlatform, navigationAction)) + ?.toNativeValue(); + return (await _inAppBrowserEventHandler! + .shouldOverrideUrlLoading(navigationAction)) + ?.toNativeValue(); + } + break; + case "onConsoleMessage": + if ((webviewParams != null && + webviewParams!.onConsoleMessage != null) || + _inAppBrowserEventHandler != null) { + Map arguments = + call.arguments.cast(); + ConsoleMessage consoleMessage = ConsoleMessage.fromMap(arguments)!; + if (webviewParams != null && webviewParams!.onConsoleMessage != null) + webviewParams!.onConsoleMessage!( + _controllerFromPlatform, consoleMessage); + else + _inAppBrowserEventHandler!.onConsoleMessage(consoleMessage); + } + break; + case "onScrollChanged": + if ((webviewParams != null && webviewParams!.onScrollChanged != null) || + _inAppBrowserEventHandler != null) { + int x = call.arguments["x"]; + int y = call.arguments["y"]; + if (webviewParams != null && webviewParams!.onScrollChanged != null) + webviewParams!.onScrollChanged!(_controllerFromPlatform, x, y); + else + _inAppBrowserEventHandler!.onScrollChanged(x, y); + } + break; + case "onDownloadStartRequest": + if ((webviewParams != null && + // ignore: deprecated_member_use_from_same_package + (webviewParams!.onDownloadStart != null || + webviewParams!.onDownloadStartRequest != null)) || + _inAppBrowserEventHandler != null) { + Map arguments = + call.arguments.cast(); + DownloadStartRequest downloadStartRequest = + DownloadStartRequest.fromMap(arguments)!; + + if (webviewParams != null) { + if (webviewParams!.onDownloadStartRequest != null) + webviewParams!.onDownloadStartRequest!( + _controllerFromPlatform, downloadStartRequest); + else { + // ignore: deprecated_member_use_from_same_package + webviewParams!.onDownloadStart!( + _controllerFromPlatform, downloadStartRequest.url); + } + } else { + // ignore: deprecated_member_use_from_same_package + _inAppBrowserEventHandler! + .onDownloadStart(downloadStartRequest.url); + _inAppBrowserEventHandler! + .onDownloadStartRequest(downloadStartRequest); + } + } + break; + case "onLoadResourceWithCustomScheme": + if ((webviewParams != null && + (webviewParams!.onLoadResourceWithCustomScheme != null || + // ignore: deprecated_member_use_from_same_package + webviewParams!.onLoadResourceCustomScheme != null)) || + _inAppBrowserEventHandler != null) { + Map requestMap = + call.arguments["request"].cast(); + WebResourceRequest request = WebResourceRequest.fromMap(requestMap)!; + + if (webviewParams != null) { + if (webviewParams!.onLoadResourceWithCustomScheme != null) + return (await webviewParams!.onLoadResourceWithCustomScheme!( + _controllerFromPlatform, request)) + ?.toMap(); + else { + return (await params + .webviewParams! + // ignore: deprecated_member_use_from_same_package + .onLoadResourceCustomScheme!( + _controllerFromPlatform, request.url)) + ?.toMap(); + } + } else { + return ((await _inAppBrowserEventHandler! + .onLoadResourceWithCustomScheme(request)) ?? + (await _inAppBrowserEventHandler! + .onLoadResourceCustomScheme(request.url))) + ?.toMap(); + } + } + break; + case "onCreateWindow": + if ((webviewParams != null && webviewParams!.onCreateWindow != null) || + _inAppBrowserEventHandler != null) { + Map arguments = + call.arguments.cast(); + CreateWindowAction createWindowAction = + CreateWindowAction.fromMap(arguments)!; + + if (webviewParams != null && webviewParams!.onCreateWindow != null) + return await webviewParams!.onCreateWindow!( + _controllerFromPlatform, createWindowAction); + else + return await _inAppBrowserEventHandler! + .onCreateWindow(createWindowAction); + } + break; + case "onCloseWindow": + if (webviewParams != null && webviewParams!.onCloseWindow != null) + webviewParams!.onCloseWindow!(_controllerFromPlatform); + else if (_inAppBrowserEventHandler != null) + _inAppBrowserEventHandler!.onCloseWindow(); + break; + case "onTitleChanged": + if ((webviewParams != null && webviewParams!.onTitleChanged != null) || + _inAppBrowserEventHandler != null) { + String? title = call.arguments["title"]; + if (webviewParams != null && webviewParams!.onTitleChanged != null) + webviewParams!.onTitleChanged!(_controllerFromPlatform, title); + else + _inAppBrowserEventHandler!.onTitleChanged(title); + } + break; + case "onGeolocationPermissionsShowPrompt": + if ((webviewParams != null && + (webviewParams!.onGeolocationPermissionsShowPrompt != null || + // ignore: deprecated_member_use_from_same_package + webviewParams!.androidOnGeolocationPermissionsShowPrompt != + null)) || + _inAppBrowserEventHandler != null) { + String origin = call.arguments["origin"]; + + if (webviewParams != null) { + if (webviewParams!.onGeolocationPermissionsShowPrompt != null) + return (await webviewParams!.onGeolocationPermissionsShowPrompt!( + _controllerFromPlatform, origin)) + ?.toMap(); + else { + return (await params + .webviewParams! + // ignore: deprecated_member_use_from_same_package + .androidOnGeolocationPermissionsShowPrompt!( + _controllerFromPlatform, origin)) + ?.toMap(); + } + } else { + return ((await _inAppBrowserEventHandler! + .onGeolocationPermissionsShowPrompt(origin)) ?? + (await _inAppBrowserEventHandler! + .androidOnGeolocationPermissionsShowPrompt(origin))) + ?.toMap(); + } + } + break; + case "onGeolocationPermissionsHidePrompt": + if (webviewParams != null && + (webviewParams!.onGeolocationPermissionsHidePrompt != null || + // ignore: deprecated_member_use_from_same_package + webviewParams!.androidOnGeolocationPermissionsHidePrompt != + null)) { + if (webviewParams!.onGeolocationPermissionsHidePrompt != null) + webviewParams! + .onGeolocationPermissionsHidePrompt!(_controllerFromPlatform); + else { + // ignore: deprecated_member_use_from_same_package + webviewParams!.androidOnGeolocationPermissionsHidePrompt!( + _controllerFromPlatform); + } + } else if (_inAppBrowserEventHandler != null) { + _inAppBrowserEventHandler!.onGeolocationPermissionsHidePrompt(); + // ignore: deprecated_member_use_from_same_package + _inAppBrowserEventHandler! + .androidOnGeolocationPermissionsHidePrompt(); + } + break; + case "shouldInterceptRequest": + if ((webviewParams != null && + (webviewParams!.shouldInterceptRequest != null || + // ignore: deprecated_member_use_from_same_package + webviewParams!.androidShouldInterceptRequest != null)) || + _inAppBrowserEventHandler != null) { + Map arguments = + call.arguments.cast(); + WebResourceRequest request = WebResourceRequest.fromMap(arguments)!; + + if (webviewParams != null) { + if (webviewParams!.shouldInterceptRequest != null) + return (await webviewParams!.shouldInterceptRequest!( + _controllerFromPlatform, request)) + ?.toMap(); + else { + // ignore: deprecated_member_use_from_same_package + return (await webviewParams!.androidShouldInterceptRequest!( + _controllerFromPlatform, request)) + ?.toMap(); + } + } else { + return ((await _inAppBrowserEventHandler! + .shouldInterceptRequest(request)) ?? + (await _inAppBrowserEventHandler! + .androidShouldInterceptRequest(request))) + ?.toMap(); + } + } + break; + case "onRenderProcessUnresponsive": + if ((webviewParams != null && + (webviewParams!.onRenderProcessUnresponsive != null || + // ignore: deprecated_member_use_from_same_package + webviewParams!.androidOnRenderProcessUnresponsive != + null)) || + _inAppBrowserEventHandler != null) { + String? url = call.arguments["url"]; + WebUri? uri = url != null ? WebUri(url) : null; + + if (webviewParams != null) { + if (webviewParams!.onRenderProcessUnresponsive != null) + return (await webviewParams!.onRenderProcessUnresponsive!( + _controllerFromPlatform, uri)) + ?.toNativeValue(); + else { + // ignore: deprecated_member_use_from_same_package + return (await webviewParams!.androidOnRenderProcessUnresponsive!( + _controllerFromPlatform, uri)) + ?.toNativeValue(); + } + } else { + return ((await _inAppBrowserEventHandler! + .onRenderProcessUnresponsive(uri)) ?? + (await _inAppBrowserEventHandler! + .androidOnRenderProcessUnresponsive(uri))) + ?.toNativeValue(); + } + } + break; + case "onRenderProcessResponsive": + if ((webviewParams != null && + (webviewParams!.onRenderProcessResponsive != null || + // ignore: deprecated_member_use_from_same_package + webviewParams!.androidOnRenderProcessResponsive != null)) || + _inAppBrowserEventHandler != null) { + String? url = call.arguments["url"]; + WebUri? uri = url != null ? WebUri(url) : null; + + if (webviewParams != null) { + if (webviewParams!.onRenderProcessResponsive != null) + return (await webviewParams!.onRenderProcessResponsive!( + _controllerFromPlatform, uri)) + ?.toNativeValue(); + else { + // ignore: deprecated_member_use_from_same_package + return (await webviewParams!.androidOnRenderProcessResponsive!( + _controllerFromPlatform, uri)) + ?.toNativeValue(); + } + } else { + return ((await _inAppBrowserEventHandler! + .onRenderProcessResponsive(uri)) ?? + (await _inAppBrowserEventHandler! + .androidOnRenderProcessResponsive(uri))) + ?.toNativeValue(); + } + } + break; + case "onRenderProcessGone": + if ((webviewParams != null && + (webviewParams!.onRenderProcessGone != null || + // ignore: deprecated_member_use_from_same_package + webviewParams!.androidOnRenderProcessGone != null)) || + _inAppBrowserEventHandler != null) { + Map arguments = + call.arguments.cast(); + RenderProcessGoneDetail detail = + RenderProcessGoneDetail.fromMap(arguments)!; + + if (webviewParams != null) { + if (webviewParams!.onRenderProcessGone != null) + webviewParams!.onRenderProcessGone!( + _controllerFromPlatform, detail); + else { + // ignore: deprecated_member_use_from_same_package + webviewParams!.androidOnRenderProcessGone!( + _controllerFromPlatform, detail); + } + } else if (_inAppBrowserEventHandler != null) { + _inAppBrowserEventHandler!.onRenderProcessGone(detail); + // ignore: deprecated_member_use_from_same_package + _inAppBrowserEventHandler!.androidOnRenderProcessGone(detail); + } + } + break; + case "onFormResubmission": + if ((webviewParams != null && + (webviewParams!.onFormResubmission != null || + // ignore: deprecated_member_use_from_same_package + webviewParams!.androidOnFormResubmission != null)) || + _inAppBrowserEventHandler != null) { + String? url = call.arguments["url"]; + WebUri? uri = url != null ? WebUri(url) : null; + + if (webviewParams != null) { + if (webviewParams!.onFormResubmission != null) + return (await webviewParams!.onFormResubmission!( + _controllerFromPlatform, uri)) + ?.toNativeValue(); + else { + // ignore: deprecated_member_use_from_same_package + return (await webviewParams!.androidOnFormResubmission!( + _controllerFromPlatform, uri)) + ?.toNativeValue(); + } + } else { + return ((await _inAppBrowserEventHandler! + .onFormResubmission(uri)) ?? + // ignore: deprecated_member_use_from_same_package + (await _inAppBrowserEventHandler! + .androidOnFormResubmission(uri))) + ?.toNativeValue(); + } + } + break; + case "onZoomScaleChanged": + if ((webviewParams != null && + // ignore: deprecated_member_use_from_same_package + (webviewParams!.androidOnScaleChanged != null || + webviewParams!.onZoomScaleChanged != null)) || + _inAppBrowserEventHandler != null) { + double oldScale = call.arguments["oldScale"]; + double newScale = call.arguments["newScale"]; + + if (webviewParams != null) { + if (webviewParams!.onZoomScaleChanged != null) + webviewParams!.onZoomScaleChanged!( + _controllerFromPlatform, oldScale, newScale); + else { + // ignore: deprecated_member_use_from_same_package + webviewParams!.androidOnScaleChanged!( + _controllerFromPlatform, oldScale, newScale); + } + } else { + _inAppBrowserEventHandler!.onZoomScaleChanged(oldScale, newScale); + // ignore: deprecated_member_use_from_same_package + _inAppBrowserEventHandler! + .androidOnScaleChanged(oldScale, newScale); + } + } + break; + case "onReceivedIcon": + if ((webviewParams != null && + (webviewParams!.onReceivedIcon != null || + // ignore: deprecated_member_use_from_same_package + webviewParams!.androidOnReceivedIcon != null)) || + _inAppBrowserEventHandler != null) { + Uint8List icon = + Uint8List.fromList(call.arguments["icon"].cast()); + + if (webviewParams != null) { + if (webviewParams!.onReceivedIcon != null) + webviewParams!.onReceivedIcon!(_controllerFromPlatform, icon); + else { + // ignore: deprecated_member_use_from_same_package + webviewParams!.androidOnReceivedIcon!( + _controllerFromPlatform, icon); + } + } else { + _inAppBrowserEventHandler!.onReceivedIcon(icon); + // ignore: deprecated_member_use_from_same_package + _inAppBrowserEventHandler!.androidOnReceivedIcon(icon); + } + } + break; + case "onReceivedTouchIconUrl": + if ((webviewParams != null && + (webviewParams!.onReceivedTouchIconUrl != null || + // ignore: deprecated_member_use_from_same_package + webviewParams!.androidOnReceivedTouchIconUrl != null)) || + _inAppBrowserEventHandler != null) { + String url = call.arguments["url"]; + bool precomposed = call.arguments["precomposed"]; + WebUri uri = WebUri(url); + + if (webviewParams != null) { + if (webviewParams!.onReceivedTouchIconUrl != null) + webviewParams!.onReceivedTouchIconUrl!( + _controllerFromPlatform, uri, precomposed); + else { + // ignore: deprecated_member_use_from_same_package + webviewParams!.androidOnReceivedTouchIconUrl!( + _controllerFromPlatform, uri, precomposed); + } + } else { + _inAppBrowserEventHandler!.onReceivedTouchIconUrl(uri, precomposed); + // ignore: deprecated_member_use_from_same_package + _inAppBrowserEventHandler! + .androidOnReceivedTouchIconUrl(uri, precomposed); + } + } + break; + case "onJsAlert": + if ((webviewParams != null && webviewParams!.onJsAlert != null) || + _inAppBrowserEventHandler != null) { + Map arguments = + call.arguments.cast(); + JsAlertRequest jsAlertRequest = JsAlertRequest.fromMap(arguments)!; + + if (webviewParams != null && webviewParams!.onJsAlert != null) + return (await webviewParams!.onJsAlert!( + _controllerFromPlatform, jsAlertRequest)) + ?.toMap(); + else + return (await _inAppBrowserEventHandler!.onJsAlert(jsAlertRequest)) + ?.toMap(); + } + break; + case "onJsConfirm": + if ((webviewParams != null && webviewParams!.onJsConfirm != null) || + _inAppBrowserEventHandler != null) { + Map arguments = + call.arguments.cast(); + JsConfirmRequest jsConfirmRequest = + JsConfirmRequest.fromMap(arguments)!; + + if (webviewParams != null && webviewParams!.onJsConfirm != null) + return (await webviewParams!.onJsConfirm!( + _controllerFromPlatform, jsConfirmRequest)) + ?.toMap(); + else + return (await _inAppBrowserEventHandler! + .onJsConfirm(jsConfirmRequest)) + ?.toMap(); + } + break; + case "onJsPrompt": + if ((webviewParams != null && webviewParams!.onJsPrompt != null) || + _inAppBrowserEventHandler != null) { + Map arguments = + call.arguments.cast(); + JsPromptRequest jsPromptRequest = JsPromptRequest.fromMap(arguments)!; + + if (webviewParams != null && webviewParams!.onJsPrompt != null) + return (await webviewParams!.onJsPrompt!( + _controllerFromPlatform, jsPromptRequest)) + ?.toMap(); + else + return (await _inAppBrowserEventHandler! + .onJsPrompt(jsPromptRequest)) + ?.toMap(); + } + break; + case "onJsBeforeUnload": + if ((webviewParams != null && + (webviewParams!.onJsBeforeUnload != null || + // ignore: deprecated_member_use_from_same_package + webviewParams!.androidOnJsBeforeUnload != null)) || + _inAppBrowserEventHandler != null) { + Map arguments = + call.arguments.cast(); + JsBeforeUnloadRequest jsBeforeUnloadRequest = + JsBeforeUnloadRequest.fromMap(arguments)!; + + if (webviewParams != null) { + if (webviewParams!.onJsBeforeUnload != null) + return (await webviewParams!.onJsBeforeUnload!( + _controllerFromPlatform, jsBeforeUnloadRequest)) + ?.toMap(); + else { + // ignore: deprecated_member_use_from_same_package + return (await webviewParams!.androidOnJsBeforeUnload!( + _controllerFromPlatform, jsBeforeUnloadRequest)) + ?.toMap(); + } + } else { + return ((await _inAppBrowserEventHandler! + .onJsBeforeUnload(jsBeforeUnloadRequest)) ?? + (await _inAppBrowserEventHandler! + .androidOnJsBeforeUnload(jsBeforeUnloadRequest))) + ?.toMap(); + } + } + break; + case "onSafeBrowsingHit": + if ((webviewParams != null && + (webviewParams!.onSafeBrowsingHit != null || + // ignore: deprecated_member_use_from_same_package + webviewParams!.androidOnSafeBrowsingHit != null)) || + _inAppBrowserEventHandler != null) { + String url = call.arguments["url"]; + SafeBrowsingThreat? threatType = + SafeBrowsingThreat.fromNativeValue(call.arguments["threatType"]); + WebUri uri = WebUri(url); + + if (webviewParams != null) { + if (webviewParams!.onSafeBrowsingHit != null) + return (await webviewParams!.onSafeBrowsingHit!( + _controllerFromPlatform, uri, threatType)) + ?.toMap(); + else { + // ignore: deprecated_member_use_from_same_package + return (await webviewParams!.androidOnSafeBrowsingHit!( + _controllerFromPlatform, uri, threatType)) + ?.toMap(); + } + } else { + return ((await _inAppBrowserEventHandler! + .onSafeBrowsingHit(uri, threatType)) ?? + (await _inAppBrowserEventHandler! + .androidOnSafeBrowsingHit(uri, threatType))) + ?.toMap(); + } + } + break; + case "onReceivedLoginRequest": + if ((webviewParams != null && + (webviewParams!.onReceivedLoginRequest != null || + // ignore: deprecated_member_use_from_same_package + webviewParams!.androidOnReceivedLoginRequest != null)) || + _inAppBrowserEventHandler != null) { + Map arguments = + call.arguments.cast(); + LoginRequest loginRequest = LoginRequest.fromMap(arguments)!; + + if (webviewParams != null) { + if (webviewParams!.onReceivedLoginRequest != null) + webviewParams!.onReceivedLoginRequest!( + _controllerFromPlatform, loginRequest); + else { + // ignore: deprecated_member_use_from_same_package + webviewParams!.androidOnReceivedLoginRequest!( + _controllerFromPlatform, loginRequest); + } + } else { + _inAppBrowserEventHandler!.onReceivedLoginRequest(loginRequest); + // ignore: deprecated_member_use_from_same_package + _inAppBrowserEventHandler! + .androidOnReceivedLoginRequest(loginRequest); + } + } + break; + case "onPermissionRequestCanceled": + if ((webviewParams != null && + webviewParams!.onPermissionRequestCanceled != null) || + _inAppBrowserEventHandler != null) { + Map arguments = + call.arguments.cast(); + PermissionRequest permissionRequest = + PermissionRequest.fromMap(arguments)!; + + if (webviewParams != null && + webviewParams!.onPermissionRequestCanceled != null) + webviewParams!.onPermissionRequestCanceled!( + _controllerFromPlatform, permissionRequest); + else + _inAppBrowserEventHandler! + .onPermissionRequestCanceled(permissionRequest); + } + break; + case "onRequestFocus": + if ((webviewParams != null && webviewParams!.onRequestFocus != null) || + _inAppBrowserEventHandler != null) { + if (webviewParams != null && webviewParams!.onRequestFocus != null) + webviewParams!.onRequestFocus!(_controllerFromPlatform); + else + _inAppBrowserEventHandler!.onRequestFocus(); + } + break; + case "onReceivedHttpAuthRequest": + if ((webviewParams != null && + webviewParams!.onReceivedHttpAuthRequest != null) || + _inAppBrowserEventHandler != null) { + Map arguments = + call.arguments.cast(); + HttpAuthenticationChallenge challenge = + HttpAuthenticationChallenge.fromMap(arguments)!; + + if (webviewParams != null && + webviewParams!.onReceivedHttpAuthRequest != null) + return (await webviewParams!.onReceivedHttpAuthRequest!( + _controllerFromPlatform, challenge)) + ?.toMap(); + else + return (await _inAppBrowserEventHandler! + .onReceivedHttpAuthRequest(challenge)) + ?.toMap(); + } + break; + case "onReceivedServerTrustAuthRequest": + if ((webviewParams != null && + webviewParams!.onReceivedServerTrustAuthRequest != null) || + _inAppBrowserEventHandler != null) { + Map arguments = + call.arguments.cast(); + ServerTrustChallenge challenge = + ServerTrustChallenge.fromMap(arguments)!; + + if (webviewParams != null && + webviewParams!.onReceivedServerTrustAuthRequest != null) + return (await webviewParams!.onReceivedServerTrustAuthRequest!( + _controllerFromPlatform, challenge)) + ?.toMap(); + else + return (await _inAppBrowserEventHandler! + .onReceivedServerTrustAuthRequest(challenge)) + ?.toMap(); + } + break; + case "onReceivedClientCertRequest": + if ((webviewParams != null && + webviewParams!.onReceivedClientCertRequest != null) || + _inAppBrowserEventHandler != null) { + Map arguments = + call.arguments.cast(); + ClientCertChallenge challenge = + ClientCertChallenge.fromMap(arguments)!; + + if (webviewParams != null && + webviewParams!.onReceivedClientCertRequest != null) + return (await webviewParams!.onReceivedClientCertRequest!( + _controllerFromPlatform, challenge)) + ?.toMap(); + else + return (await _inAppBrowserEventHandler! + .onReceivedClientCertRequest(challenge)) + ?.toMap(); + } + break; + case "onFindResultReceived": + if ((webviewParams != null && + (webviewParams!.onFindResultReceived != null || + (webviewParams!.findInteractionController != null && + webviewParams!.findInteractionController!.params + .onFindResultReceived != + null))) || + _inAppBrowserEventHandler != null) { + int activeMatchOrdinal = call.arguments["activeMatchOrdinal"]; + int numberOfMatches = call.arguments["numberOfMatches"]; + bool isDoneCounting = call.arguments["isDoneCounting"]; + if (webviewParams != null) { + if (webviewParams!.findInteractionController != null && + webviewParams!.findInteractionController!.params + .onFindResultReceived != + null) + webviewParams! + .findInteractionController!.params.onFindResultReceived!( + webviewParams!.findInteractionController!, + activeMatchOrdinal, + numberOfMatches, + isDoneCounting); + else + webviewParams!.onFindResultReceived!(_controllerFromPlatform, + activeMatchOrdinal, numberOfMatches, isDoneCounting); + } else { + if (_inAppBrowser!.findInteractionController != null && + _inAppBrowser! + .findInteractionController!.onFindResultReceived != + null) + _inAppBrowser!.findInteractionController!.onFindResultReceived!( + webviewParams!.findInteractionController!, + activeMatchOrdinal, + numberOfMatches, + isDoneCounting); + else + _inAppBrowserEventHandler!.onFindResultReceived( + activeMatchOrdinal, numberOfMatches, isDoneCounting); + } + } + break; + case "onPermissionRequest": + if ((webviewParams != null && + (webviewParams!.onPermissionRequest != null || + // ignore: deprecated_member_use_from_same_package + webviewParams!.androidOnPermissionRequest != null)) || + _inAppBrowserEventHandler != null) { + String origin = call.arguments["origin"]; + List resources = call.arguments["resources"].cast(); + + Map arguments = + call.arguments.cast(); + PermissionRequest permissionRequest = + PermissionRequest.fromMap(arguments)!; + + if (webviewParams != null) { + if (webviewParams!.onPermissionRequest != null) + return (await webviewParams!.onPermissionRequest!( + _controllerFromPlatform, permissionRequest)) + ?.toMap(); + else { + return (await webviewParams!.androidOnPermissionRequest!( + _controllerFromPlatform, origin, resources)) + ?.toMap(); + } + } else { + return (await _inAppBrowserEventHandler! + .onPermissionRequest(permissionRequest)) + ?.toMap() ?? + (await _inAppBrowserEventHandler! + .androidOnPermissionRequest(origin, resources)) + ?.toMap(); + } + } + break; + case "onUpdateVisitedHistory": + if ((webviewParams != null && + webviewParams!.onUpdateVisitedHistory != null) || + _inAppBrowserEventHandler != null) { + String? url = call.arguments["url"]; + bool? isReload = call.arguments["isReload"]; + WebUri? uri = url != null ? WebUri(url) : null; + if (webviewParams != null && + webviewParams!.onUpdateVisitedHistory != null) + webviewParams!.onUpdateVisitedHistory!( + _controllerFromPlatform, uri, isReload); + else + _inAppBrowserEventHandler!.onUpdateVisitedHistory(uri, isReload); + } + break; + case "onWebContentProcessDidTerminate": + if (webviewParams != null && + (webviewParams!.onWebContentProcessDidTerminate != null || + // ignore: deprecated_member_use_from_same_package + webviewParams!.iosOnWebContentProcessDidTerminate != null)) { + if (webviewParams!.onWebContentProcessDidTerminate != null) + webviewParams! + .onWebContentProcessDidTerminate!(_controllerFromPlatform); + else { + // ignore: deprecated_member_use_from_same_package + webviewParams! + .iosOnWebContentProcessDidTerminate!(_controllerFromPlatform); + } + } else if (_inAppBrowserEventHandler != null) { + _inAppBrowserEventHandler!.onWebContentProcessDidTerminate(); + // ignore: deprecated_member_use_from_same_package + _inAppBrowserEventHandler!.iosOnWebContentProcessDidTerminate(); + } + break; + case "onPageCommitVisible": + if ((webviewParams != null && + webviewParams!.onPageCommitVisible != null) || + _inAppBrowserEventHandler != null) { + String? url = call.arguments["url"]; + WebUri? uri = url != null ? WebUri(url) : null; + if (webviewParams != null && + webviewParams!.onPageCommitVisible != null) + webviewParams!.onPageCommitVisible!(_controllerFromPlatform, uri); + else + _inAppBrowserEventHandler!.onPageCommitVisible(uri); + } + break; + case "onDidReceiveServerRedirectForProvisionalNavigation": + if (webviewParams != null && + (webviewParams! + .onDidReceiveServerRedirectForProvisionalNavigation != + null || + params + .webviewParams! + // ignore: deprecated_member_use_from_same_package + .iosOnDidReceiveServerRedirectForProvisionalNavigation != + null)) { + if (webviewParams! + .onDidReceiveServerRedirectForProvisionalNavigation != + null) + webviewParams!.onDidReceiveServerRedirectForProvisionalNavigation!( + _controllerFromPlatform); + else { + params + .webviewParams! + // ignore: deprecated_member_use_from_same_package + .iosOnDidReceiveServerRedirectForProvisionalNavigation!( + _controllerFromPlatform); + } + } else if (_inAppBrowserEventHandler != null) { + _inAppBrowserEventHandler! + .onDidReceiveServerRedirectForProvisionalNavigation(); + _inAppBrowserEventHandler! + .iosOnDidReceiveServerRedirectForProvisionalNavigation(); + } + break; + case "onNavigationResponse": + if ((webviewParams != null && + (webviewParams!.onNavigationResponse != null || + // ignore: deprecated_member_use_from_same_package + webviewParams!.iosOnNavigationResponse != null)) || + _inAppBrowserEventHandler != null) { + Map arguments = + call.arguments.cast(); + // ignore: deprecated_member_use_from_same_package + IOSWKNavigationResponse iosOnNavigationResponse = + // ignore: deprecated_member_use_from_same_package + IOSWKNavigationResponse.fromMap(arguments)!; + + NavigationResponse navigationResponse = + NavigationResponse.fromMap(arguments)!; + + if (webviewParams != null) { + if (webviewParams!.onNavigationResponse != null) + return (await webviewParams!.onNavigationResponse!( + _controllerFromPlatform, navigationResponse)) + ?.toNativeValue(); + else { + // ignore: deprecated_member_use_from_same_package + return (await webviewParams!.iosOnNavigationResponse!( + _controllerFromPlatform, iosOnNavigationResponse)) + ?.toNativeValue(); + } + } else { + return (await _inAppBrowserEventHandler! + .onNavigationResponse(navigationResponse)) + ?.toNativeValue() ?? + (await _inAppBrowserEventHandler! + .iosOnNavigationResponse(iosOnNavigationResponse)) + ?.toNativeValue(); + } + } + break; + case "shouldAllowDeprecatedTLS": + if ((webviewParams != null && + (webviewParams!.shouldAllowDeprecatedTLS != null || + // ignore: deprecated_member_use_from_same_package + webviewParams!.iosShouldAllowDeprecatedTLS != null)) || + _inAppBrowserEventHandler != null) { + Map arguments = + call.arguments.cast(); + URLAuthenticationChallenge challenge = + URLAuthenticationChallenge.fromMap(arguments)!; + + if (webviewParams != null) { + if (webviewParams!.shouldAllowDeprecatedTLS != null) + return (await webviewParams!.shouldAllowDeprecatedTLS!( + _controllerFromPlatform, challenge)) + ?.toNativeValue(); + else { + // ignore: deprecated_member_use_from_same_package + return (await webviewParams!.iosShouldAllowDeprecatedTLS!( + _controllerFromPlatform, challenge)) + ?.toNativeValue(); + } + } else { + return (await _inAppBrowserEventHandler! + .shouldAllowDeprecatedTLS(challenge)) + ?.toNativeValue() ?? + // ignore: deprecated_member_use_from_same_package + (await _inAppBrowserEventHandler! + .iosShouldAllowDeprecatedTLS(challenge)) + ?.toNativeValue(); + } + } + break; + case "onLongPressHitTestResult": + if ((webviewParams != null && + webviewParams!.onLongPressHitTestResult != null) || + _inAppBrowserEventHandler != null) { + Map arguments = + call.arguments.cast(); + InAppWebViewHitTestResult hitTestResult = + InAppWebViewHitTestResult.fromMap(arguments)!; + + if (webviewParams != null && + webviewParams!.onLongPressHitTestResult != null) + webviewParams!.onLongPressHitTestResult!( + _controllerFromPlatform, hitTestResult); + else + _inAppBrowserEventHandler!.onLongPressHitTestResult(hitTestResult); + } + break; + case "onCreateContextMenu": + ContextMenu? contextMenu; + if (webviewParams != null && webviewParams!.contextMenu != null) { + contextMenu = webviewParams!.contextMenu; + } else if (_inAppBrowserEventHandler != null && + _inAppBrowser!.contextMenu != null) { + contextMenu = _inAppBrowser!.contextMenu; + } + + if (contextMenu != null && contextMenu.onCreateContextMenu != null) { + Map arguments = + call.arguments.cast(); + InAppWebViewHitTestResult hitTestResult = + InAppWebViewHitTestResult.fromMap(arguments)!; + + contextMenu.onCreateContextMenu!(hitTestResult); + } + break; + case "onHideContextMenu": + ContextMenu? contextMenu; + if (webviewParams != null && webviewParams!.contextMenu != null) { + contextMenu = webviewParams!.contextMenu; + } else if (_inAppBrowserEventHandler != null && + _inAppBrowser!.contextMenu != null) { + contextMenu = _inAppBrowser!.contextMenu; + } + + if (contextMenu != null && contextMenu.onHideContextMenu != null) { + contextMenu.onHideContextMenu!(); + } + break; + case "onContextMenuActionItemClicked": + ContextMenu? contextMenu; + if (webviewParams != null && webviewParams!.contextMenu != null) { + contextMenu = webviewParams!.contextMenu; + } else if (_inAppBrowserEventHandler != null && + _inAppBrowser!.contextMenu != null) { + contextMenu = _inAppBrowser!.contextMenu; + } + + if (contextMenu != null) { + int? androidId = call.arguments["androidId"]; + String? iosId = call.arguments["iosId"]; + dynamic id = call.arguments["id"]; + String title = call.arguments["title"]; + + ContextMenuItem menuItemClicked = ContextMenuItem( + id: id, + // ignore: deprecated_member_use_from_same_package + androidId: androidId, + // ignore: deprecated_member_use_from_same_package + iosId: iosId, + title: title, + action: null); + + for (var menuItem in contextMenu.menuItems) { + if (menuItem.id == id) { + menuItemClicked = menuItem; + if (menuItem.action != null) { + menuItem.action!(); + } + break; + } + } + + if (contextMenu.onContextMenuActionItemClicked != null) { + contextMenu.onContextMenuActionItemClicked!(menuItemClicked); + } + } + break; + case "onEnterFullscreen": + if (webviewParams != null && webviewParams!.onEnterFullscreen != null) + webviewParams!.onEnterFullscreen!(_controllerFromPlatform); + else if (_inAppBrowserEventHandler != null) + _inAppBrowserEventHandler!.onEnterFullscreen(); + break; + case "onExitFullscreen": + if (webviewParams != null && webviewParams!.onExitFullscreen != null) + webviewParams!.onExitFullscreen!(_controllerFromPlatform); + else if (_inAppBrowserEventHandler != null) + _inAppBrowserEventHandler!.onExitFullscreen(); + break; + case "onOverScrolled": + if ((webviewParams != null && webviewParams!.onOverScrolled != null) || + _inAppBrowserEventHandler != null) { + int x = call.arguments["x"]; + int y = call.arguments["y"]; + bool clampedX = call.arguments["clampedX"]; + bool clampedY = call.arguments["clampedY"]; + + if (webviewParams != null && webviewParams!.onOverScrolled != null) + webviewParams!.onOverScrolled!( + _controllerFromPlatform, x, y, clampedX, clampedY); + else + _inAppBrowserEventHandler!.onOverScrolled(x, y, clampedX, clampedY); + } + break; + case "onWindowFocus": + if (webviewParams != null && webviewParams!.onWindowFocus != null) + webviewParams!.onWindowFocus!(_controllerFromPlatform); + else if (_inAppBrowserEventHandler != null) + _inAppBrowserEventHandler!.onWindowFocus(); + break; + case "onWindowBlur": + if (webviewParams != null && webviewParams!.onWindowBlur != null) + webviewParams!.onWindowBlur!(_controllerFromPlatform); + else if (_inAppBrowserEventHandler != null) + _inAppBrowserEventHandler!.onWindowBlur(); + break; + case "onPrintRequest": + if ((webviewParams != null && + (webviewParams!.onPrintRequest != null || + // ignore: deprecated_member_use_from_same_package + webviewParams!.onPrint != null)) || + _inAppBrowserEventHandler != null) { + String? url = call.arguments["url"]; + String? printJobId = call.arguments["printJobId"]; + WebUri? uri = url != null ? WebUri(url) : null; + WindowsPrintJobController? printJob = printJobId != null + ? WindowsPrintJobController( + WindowsPrintJobControllerCreationParams(id: printJobId)) + : null; + + if (webviewParams != null) { + if (webviewParams!.onPrintRequest != null) + return await webviewParams!.onPrintRequest!( + _controllerFromPlatform, uri, printJob); + else { + // ignore: deprecated_member_use_from_same_package + webviewParams!.onPrint!(_controllerFromPlatform, uri); + return false; + } + } else { + // ignore: deprecated_member_use_from_same_package + _inAppBrowserEventHandler!.onPrint(uri); + return await _inAppBrowserEventHandler! + .onPrintRequest(uri, printJob); + } + } + break; + case "onInjectedScriptLoaded": + String id = call.arguments[0]; + var onLoadCallback = _injectedScriptsFromURL[id]?.onLoad; + if ((webviewParams != null || _inAppBrowserEventHandler != null) && + onLoadCallback != null) { + onLoadCallback(); + } + break; + case "onInjectedScriptError": + String id = call.arguments[0]; + var onErrorCallback = _injectedScriptsFromURL[id]?.onError; + if ((webviewParams != null || _inAppBrowserEventHandler != null) && + onErrorCallback != null) { + onErrorCallback(); + } + break; + case "onCameraCaptureStateChanged": + if ((webviewParams != null && + webviewParams!.onCameraCaptureStateChanged != null) || + _inAppBrowserEventHandler != null) { + var oldState = + MediaCaptureState.fromNativeValue(call.arguments["oldState"]); + var newState = + MediaCaptureState.fromNativeValue(call.arguments["newState"]); + + if (webviewParams != null && + webviewParams!.onCameraCaptureStateChanged != null) + webviewParams!.onCameraCaptureStateChanged!( + _controllerFromPlatform, oldState, newState); + else + _inAppBrowserEventHandler! + .onCameraCaptureStateChanged(oldState, newState); + } + break; + case "onMicrophoneCaptureStateChanged": + if ((webviewParams != null && + webviewParams!.onMicrophoneCaptureStateChanged != null) || + _inAppBrowserEventHandler != null) { + var oldState = + MediaCaptureState.fromNativeValue(call.arguments["oldState"]); + var newState = + MediaCaptureState.fromNativeValue(call.arguments["newState"]); + + if (webviewParams != null && + webviewParams!.onMicrophoneCaptureStateChanged != null) + webviewParams!.onMicrophoneCaptureStateChanged!( + _controllerFromPlatform, oldState, newState); + else + _inAppBrowserEventHandler! + .onMicrophoneCaptureStateChanged(oldState, newState); + } + break; + case "onContentSizeChanged": + if ((webviewParams != null && + webviewParams!.onContentSizeChanged != null) || + _inAppBrowserEventHandler != null) { + var oldContentSize = MapSize.fromMap( + call.arguments["oldContentSize"]?.cast())!; + var newContentSize = MapSize.fromMap( + call.arguments["newContentSize"]?.cast())!; + + if (webviewParams != null && + webviewParams!.onContentSizeChanged != null) + webviewParams!.onContentSizeChanged!( + _controllerFromPlatform, oldContentSize, newContentSize); + else + _inAppBrowserEventHandler! + .onContentSizeChanged(oldContentSize, newContentSize); + } + break; + case "onDevToolsProtocolEventReceived": + String eventName = call.arguments["eventName"]; + dynamic data = call.arguments["data"] != null ? jsonDecode(call.arguments["data"]) : null; + + if (this._devToolsProtocolEventListenerMap.containsKey(eventName)) { + this._devToolsProtocolEventListenerMap[eventName]!.call(data); + } + break; + case "onCallJsHandler": + String handlerName = call.arguments["handlerName"]; + // decode args to json + List args = jsonDecode(call.arguments["args"]); + + _debugLog(handlerName, args); + + switch (handlerName) { + case "onLoadResource": + if ((webviewParams != null && + webviewParams!.onLoadResource != null) || + _inAppBrowserEventHandler != null) { + Map arguments = args[0].cast(); + arguments["startTime"] = arguments["startTime"] is int + ? arguments["startTime"].toDouble() + : arguments["startTime"]; + arguments["duration"] = arguments["duration"] is int + ? arguments["duration"].toDouble() + : arguments["duration"]; + + var response = LoadedResource.fromMap(arguments)!; + + if (webviewParams != null && + webviewParams!.onLoadResource != null) + webviewParams!.onLoadResource!( + _controllerFromPlatform, response); + else + _inAppBrowserEventHandler!.onLoadResource(response); + } + return null; + case "shouldInterceptAjaxRequest": + if ((webviewParams != null && + webviewParams!.shouldInterceptAjaxRequest != null) || + _inAppBrowserEventHandler != null) { + Map arguments = args[0].cast(); + AjaxRequest request = AjaxRequest.fromMap(arguments)!; + + if (webviewParams != null && + webviewParams!.shouldInterceptAjaxRequest != null) + return jsonEncode( + await params.webviewParams!.shouldInterceptAjaxRequest!( + _controllerFromPlatform, request)); + else + return jsonEncode(await _inAppBrowserEventHandler! + .shouldInterceptAjaxRequest(request)); + } + return null; + case "onAjaxReadyStateChange": + if ((webviewParams != null && + webviewParams!.onAjaxReadyStateChange != null) || + _inAppBrowserEventHandler != null) { + Map arguments = args[0].cast(); + AjaxRequest request = AjaxRequest.fromMap(arguments)!; + + if (webviewParams != null && + webviewParams!.onAjaxReadyStateChange != null) + return (await webviewParams!.onAjaxReadyStateChange!( + _controllerFromPlatform, request)) + ?.toNativeValue(); + else + return (await _inAppBrowserEventHandler! + .onAjaxReadyStateChange(request)) + ?.toNativeValue(); + } + return null; + case "onAjaxProgress": + if ((webviewParams != null && + webviewParams!.onAjaxProgress != null) || + _inAppBrowserEventHandler != null) { + Map arguments = args[0].cast(); + AjaxRequest request = AjaxRequest.fromMap(arguments)!; + + if (webviewParams != null && + webviewParams!.onAjaxProgress != null) + return (await webviewParams!.onAjaxProgress!( + _controllerFromPlatform, request)) + ?.toNativeValue(); + else + return (await _inAppBrowserEventHandler! + .onAjaxProgress(request)) + ?.toNativeValue(); + } + return null; + case "shouldInterceptFetchRequest": + if ((webviewParams != null && + webviewParams!.shouldInterceptFetchRequest != null) || + _inAppBrowserEventHandler != null) { + Map arguments = args[0].cast(); + FetchRequest request = FetchRequest.fromMap(arguments)!; + + if (webviewParams != null && + webviewParams!.shouldInterceptFetchRequest != null) + return jsonEncode( + await webviewParams!.shouldInterceptFetchRequest!( + _controllerFromPlatform, request)); + else + return jsonEncode(await _inAppBrowserEventHandler! + .shouldInterceptFetchRequest(request)); + } + return null; + case "onWindowFocus": + if (webviewParams != null && webviewParams!.onWindowFocus != null) + webviewParams!.onWindowFocus!(_controllerFromPlatform); + else if (_inAppBrowserEventHandler != null) + _inAppBrowserEventHandler!.onWindowFocus(); + return null; + case "onWindowBlur": + if (webviewParams != null && webviewParams!.onWindowBlur != null) + webviewParams!.onWindowBlur!(_controllerFromPlatform); + else if (_inAppBrowserEventHandler != null) + _inAppBrowserEventHandler!.onWindowBlur(); + return null; + case "onInjectedScriptLoaded": + String id = args[0]; + var onLoadCallback = _injectedScriptsFromURL[id]?.onLoad; + if ((webviewParams != null || _inAppBrowserEventHandler != null) && + onLoadCallback != null) { + onLoadCallback(); + } + return null; + case "onInjectedScriptError": + String id = args[0]; + var onErrorCallback = _injectedScriptsFromURL[id]?.onError; + if ((webviewParams != null || _inAppBrowserEventHandler != null) && + onErrorCallback != null) { + onErrorCallback(); + } + return null; + } + + if (_javaScriptHandlersMap.containsKey(handlerName)) { + // convert result to json + try { + return jsonEncode(await _javaScriptHandlersMap[handlerName]!(args)); + } catch (error, stacktrace) { + developer.log(error.toString() + '\n' + stacktrace.toString(), + name: 'JavaScript Handler "$handlerName"'); + throw Exception(error.toString().replaceFirst('Exception: ', '')); + } + } + break; + default: + throw UnimplementedError("Unimplemented ${call.method} method"); + } + return null; + } + + @override + Future getUrl() async { + Map args = {}; + String? url = await channel?.invokeMethod('getUrl', args); + return url != null ? WebUri(url) : null; + } + + @override + Future getTitle() async { + Map args = {}; + return await channel?.invokeMethod('getTitle', args); + } + + @override + Future getProgress() async { + Map args = {}; + return await channel?.invokeMethod('getProgress', args); + } + + @override + Future getHtml() async { + String? html; + + InAppWebViewSettings? settings = await getSettings(); + if (settings != null && settings.javaScriptEnabled == true) { + html = await evaluateJavascript( + source: "window.document.getElementsByTagName('html')[0].outerHTML;"); + if (html != null && html.isNotEmpty) return html; + } + + var webviewUrl = await getUrl(); + if (webviewUrl == null) { + return html; + } + + if (webviewUrl.isScheme("file")) { + var assetPathSplit = webviewUrl.toString().split("/flutter_assets/"); + var assetPath = assetPathSplit[assetPathSplit.length - 1]; + try { + var bytes = await rootBundle.load(assetPath); + html = utf8.decode(bytes.buffer.asUint8List()); + } catch (e) {} + } else { + try { + HttpClient client = HttpClient(); + var htmlRequest = await client.getUrl(webviewUrl); + html = + await (await htmlRequest.close()).transform(Utf8Decoder()).join(); + } catch (e) { + developer.log(e.toString(), name: this.runtimeType.toString()); + } + } + + return html; + } + + @override + Future> getFavicons() async { + List favicons = []; + + var webviewUrl = await getUrl(); + + if (webviewUrl == null) { + return favicons; + } + + String? manifestUrl; + + var html = await getHtml(); + if (html == null || html.isEmpty) { + return favicons; + } + var assetPathBase; + + if (webviewUrl.isScheme("file")) { + var assetPathSplit = webviewUrl.toString().split("/flutter_assets/"); + assetPathBase = assetPathSplit[0] + "/flutter_assets/"; + } + + InAppWebViewSettings? settings = await getSettings(); + if (settings != null && settings.javaScriptEnabled == true) { + List> links = (await evaluateJavascript(source: """ +(function() { + var linkNodes = document.head.getElementsByTagName("link"); + var links = []; + for (var i = 0; i < linkNodes.length; i++) { + var linkNode = linkNodes[i]; + if (linkNode.rel === 'manifest') { + links.push( + { + rel: linkNode.rel, + href: linkNode.href, + sizes: null + } + ); + } else if (linkNode.rel != null && linkNode.rel.indexOf('icon') >= 0) { + links.push( + { + rel: linkNode.rel, + href: linkNode.href, + sizes: linkNode.sizes != null && linkNode.sizes.value != "" ? linkNode.sizes.value : null + } + ); + } + } + return links; +})(); +"""))?.cast>() ?? []; + for (var link in links) { + if (link["rel"] == "manifest") { + manifestUrl = link["href"]; + if (!_isUrlAbsolute(manifestUrl!)) { + if (manifestUrl.startsWith("/")) { + manifestUrl = manifestUrl.substring(1); + } + manifestUrl = ((assetPathBase == null) + ? webviewUrl.scheme + "://" + webviewUrl.host + "/" + : assetPathBase) + + manifestUrl; + } + continue; + } + favicons.addAll(_createFavicons(webviewUrl, assetPathBase, link["href"], + link["rel"], link["sizes"], false)); + } + } + + // try to get /favicon.ico + try { + HttpClient client = HttpClient(); + var faviconUrl = + webviewUrl.scheme + "://" + webviewUrl.host + "/favicon.ico"; + var faviconUri = WebUri(faviconUrl); + var headRequest = await client.headUrl(faviconUri); + var headResponse = await headRequest.close(); + if (headResponse.statusCode == 200) { + favicons.add(Favicon(url: faviconUri, rel: "shortcut icon")); + } + } catch (e) { + developer.log("/favicon.ico file not found: " + e.toString(), + name: runtimeType.toString()); + } + + // try to get the manifest file + HttpClientRequest? manifestRequest; + HttpClientResponse? manifestResponse; + bool manifestFound = false; + if (manifestUrl == null) { + manifestUrl = + webviewUrl.scheme + "://" + webviewUrl.host + "/manifest.json"; + } + try { + HttpClient client = HttpClient(); + manifestRequest = await client.getUrl(Uri.parse(manifestUrl)); + manifestResponse = await manifestRequest.close(); + manifestFound = manifestResponse.statusCode == 200 && + manifestResponse.headers.contentType?.mimeType == "application/json"; + } catch (e) { + developer.log("Manifest file not found: " + e.toString(), + name: this.runtimeType.toString()); + } + + if (manifestFound) { + try { + Map manifest = json + .decode(await manifestResponse!.transform(Utf8Decoder()).join()); + if (manifest.containsKey("icons")) { + for (Map icon in manifest["icons"]) { + favicons.addAll(_createFavicons(webviewUrl, assetPathBase, + icon["src"], icon["rel"], icon["sizes"], true)); + } + } + } catch (e) { + developer.log( + "Cannot get favicons from Manifest file. It might not have a valid format: " + + e.toString(), + error: e, + name: runtimeType.toString()); + } + } + + return favicons; + } + + bool _isUrlAbsolute(String url) { + return url.startsWith("http://") || url.startsWith("https://"); + } + + List _createFavicons(WebUri url, String? assetPathBase, + String urlIcon, String? rel, String? sizes, bool isManifest) { + List favicons = []; + + List urlSplit = urlIcon.split("/"); + if (!_isUrlAbsolute(urlIcon)) { + if (urlIcon.startsWith("/")) { + urlIcon = urlIcon.substring(1); + } + urlIcon = ((assetPathBase == null) + ? url.scheme + "://" + url.host + "/" + : assetPathBase) + + urlIcon; + } + if (isManifest) { + rel = (sizes != null) + ? urlSplit[urlSplit.length - 1] + .replaceFirst("-" + sizes, "") + .split(" ")[0] + .split(".")[0] + : null; + } + if (sizes != null && sizes.isNotEmpty && sizes != "any") { + List sizesSplit = sizes.split(" "); + for (String size in sizesSplit) { + int width = int.parse(size.split("x")[0]); + int height = int.parse(size.split("x")[1]); + favicons.add(Favicon( + url: WebUri(urlIcon), rel: rel, width: width, height: height)); + } + } else { + favicons.add( + Favicon(url: WebUri(urlIcon), rel: rel, width: null, height: null)); + } + + return favicons; + } + + @override + Future loadUrl( + {required URLRequest urlRequest, + @Deprecated('Use allowingReadAccessTo instead') + Uri? iosAllowingReadAccessTo, + WebUri? allowingReadAccessTo}) async { + assert(urlRequest.url != null && urlRequest.url.toString().isNotEmpty); + assert( + allowingReadAccessTo == null || allowingReadAccessTo.isScheme("file")); + assert(iosAllowingReadAccessTo == null || + iosAllowingReadAccessTo.isScheme("file")); + + Map args = {}; + args.putIfAbsent('urlRequest', () => urlRequest.toMap()); + args.putIfAbsent( + 'allowingReadAccessTo', + () => + allowingReadAccessTo?.toString() ?? + iosAllowingReadAccessTo?.toString()); + await channel?.invokeMethod('loadUrl', args); + } + + @override + Future postUrl( + {required WebUri url, required Uint8List postData}) async { + assert(url.toString().isNotEmpty); + Map args = {}; + args.putIfAbsent('url', () => url.toString()); + args.putIfAbsent('postData', () => postData); + await channel?.invokeMethod('postUrl', args); + } + + @override + Future loadData( + {required String data, + String mimeType = "text/html", + String encoding = "utf8", + WebUri? baseUrl, + @Deprecated('Use historyUrl instead') Uri? androidHistoryUrl, + WebUri? historyUrl, + @Deprecated('Use allowingReadAccessTo instead') + Uri? iosAllowingReadAccessTo, + WebUri? allowingReadAccessTo}) async { + assert( + allowingReadAccessTo == null || allowingReadAccessTo.isScheme("file")); + assert(iosAllowingReadAccessTo == null || + iosAllowingReadAccessTo.isScheme("file")); + + Map args = {}; + args.putIfAbsent('data', () => data); + args.putIfAbsent('mimeType', () => mimeType); + args.putIfAbsent('encoding', () => encoding); + args.putIfAbsent('baseUrl', () => baseUrl?.toString() ?? "about:blank"); + args.putIfAbsent( + 'historyUrl', + () => + historyUrl?.toString() ?? + androidHistoryUrl?.toString() ?? + "about:blank"); + args.putIfAbsent( + 'allowingReadAccessTo', + () => + allowingReadAccessTo?.toString() ?? + iosAllowingReadAccessTo?.toString()); + await channel?.invokeMethod('loadData', args); + } + + @override + Future loadFile({required String assetFilePath}) async { + assert(assetFilePath.isNotEmpty); + Map args = {}; + args.putIfAbsent('assetFilePath', () => assetFilePath); + await channel?.invokeMethod('loadFile', args); + } + + @override + Future reload() async { + Map args = {}; + await channel?.invokeMethod('reload', args); + } + + @override + Future goBack() async { + Map args = {}; + await channel?.invokeMethod('goBack', args); + } + + @override + Future canGoBack() async { + Map args = {}; + return await channel?.invokeMethod('canGoBack', args) ?? false; + } + + @override + Future goForward() async { + Map args = {}; + await channel?.invokeMethod('goForward', args); + } + + @override + Future canGoForward() async { + Map args = {}; + return await channel?.invokeMethod('canGoForward', args) ?? false; + } + + @override + Future goBackOrForward({required int steps}) async { + Map args = {}; + args.putIfAbsent('steps', () => steps); + await channel?.invokeMethod('goBackOrForward', args); + } + + @override + Future canGoBackOrForward({required int steps}) async { + Map args = {}; + args.putIfAbsent('steps', () => steps); + return await channel?.invokeMethod('canGoBackOrForward', args) ?? + false; + } + + @override + Future goTo({required WebHistoryItem historyItem}) async { + var steps = historyItem.offset; + if (steps != null) { + await goBackOrForward(steps: steps); + } + } + + @override + Future isLoading() async { + Map args = {}; + return await channel?.invokeMethod('isLoading', args) ?? false; + } + + @override + Future stopLoading() async { + Map args = {}; + await channel?.invokeMethod('stopLoading', args); + } + + @override + Future evaluateJavascript( + {required String source, ContentWorld? contentWorld}) async { + Map args = {}; + args.putIfAbsent('source', () => source); + args.putIfAbsent('contentWorld', () => contentWorld?.toMap()); + var data = await channel?.invokeMethod('evaluateJavascript', args); + if (data != null) { + try { + // try to json decode the data coming from JavaScript + // otherwise return it as it is. + data = json.decode(data); + } catch (e) {} + } + return data; + } + + @override + Future injectJavascriptFileFromUrl( + {required WebUri urlFile, + ScriptHtmlTagAttributes? scriptHtmlTagAttributes}) async { + assert(urlFile.toString().isNotEmpty); + var id = scriptHtmlTagAttributes?.id; + if (scriptHtmlTagAttributes != null && id != null) { + _injectedScriptsFromURL[id] = scriptHtmlTagAttributes; + } + Map args = {}; + args.putIfAbsent('urlFile', () => urlFile.toString()); + args.putIfAbsent( + 'scriptHtmlTagAttributes', () => scriptHtmlTagAttributes?.toMap()); + await channel?.invokeMethod('injectJavascriptFileFromUrl', args); + } + + @override + Future injectJavascriptFileFromAsset( + {required String assetFilePath}) async { + String source = await rootBundle.loadString(assetFilePath); + return await evaluateJavascript(source: source); + } + + @override + Future injectCSSCode({required String source}) async { + Map args = {}; + args.putIfAbsent('source', () => source); + await channel?.invokeMethod('injectCSSCode', args); + } + + @override + Future injectCSSFileFromUrl( + {required WebUri urlFile, + CSSLinkHtmlTagAttributes? cssLinkHtmlTagAttributes}) async { + assert(urlFile.toString().isNotEmpty); + Map args = {}; + args.putIfAbsent('urlFile', () => urlFile.toString()); + args.putIfAbsent( + 'cssLinkHtmlTagAttributes', () => cssLinkHtmlTagAttributes?.toMap()); + await channel?.invokeMethod('injectCSSFileFromUrl', args); + } + + @override + Future injectCSSFileFromAsset({required String assetFilePath}) async { + String source = await rootBundle.loadString(assetFilePath); + await injectCSSCode(source: source); + } + + @override + void addJavaScriptHandler( + {required String handlerName, + required JavaScriptHandlerCallback callback}) { + assert(!_JAVASCRIPT_HANDLER_FORBIDDEN_NAMES.contains(handlerName), + '"$handlerName" is a forbidden name!'); + this._javaScriptHandlersMap[handlerName] = (callback); + } + + @override + JavaScriptHandlerCallback? removeJavaScriptHandler( + {required String handlerName}) { + return this._javaScriptHandlersMap.remove(handlerName); + } + + @override + bool hasJavaScriptHandler({required String handlerName}) { + return this._javaScriptHandlersMap.containsKey(handlerName); + } + + @override + Future takeScreenshot( + {ScreenshotConfiguration? screenshotConfiguration}) async { + Map args = {}; + args.putIfAbsent( + 'screenshotConfiguration', () => screenshotConfiguration?.toMap()); + final base64 = await channel?.invokeMethod('takeScreenshot', args); + return base64 != null ? base64Decode(base64) : null; + } + + @override + @Deprecated('Use setSettings instead') + Future setOptions({required InAppWebViewGroupOptions options}) async { + InAppWebViewSettings settings = + InAppWebViewSettings.fromMap(options.toMap()) ?? InAppWebViewSettings(); + await setSettings(settings: settings); + } + + @override + @Deprecated('Use getSettings instead') + Future getOptions() async { + InAppWebViewSettings? settings = await getSettings(); + + Map? options = settings?.toMap(); + if (options != null) { + options = options.cast(); + return InAppWebViewGroupOptions.fromMap(options as Map); + } + + return null; + } + + @override + Future setSettings({required InAppWebViewSettings settings}) async { + Map args = {}; + + args.putIfAbsent('settings', () => settings.toMap()); + await channel?.invokeMethod('setSettings', args); + } + + @override + Future getSettings() async { + Map args = {}; + + Map? settings = + await channel?.invokeMethod('getSettings', args); + if (settings != null) { + settings = settings.cast(); + return InAppWebViewSettings.fromMap(settings as Map); + } + + return null; + } + + @override + Future getCopyBackForwardList() async { + Map args = {}; + Map? result = + (await channel?.invokeMethod('getCopyBackForwardList', args)) + ?.cast(); + return WebHistory.fromMap(result); + } + + @override + @Deprecated("Use InAppWebViewController.clearAllCache instead") + Future clearCache() async { + Map args = {}; + await channel?.invokeMethod('clearCache', args); + } + + @override + @Deprecated("Use FindInteractionController.findAll instead") + Future findAllAsync({required String find}) async { + Map args = {}; + args.putIfAbsent('find', () => find); + await channel?.invokeMethod('findAll', args); + } + + @override + @Deprecated("Use FindInteractionController.findNext instead") + Future findNext({required bool forward}) async { + Map args = {}; + args.putIfAbsent('forward', () => forward); + await channel?.invokeMethod('findNext', args); + } + + @override + @Deprecated("Use FindInteractionController.clearMatches instead") + Future clearMatches() async { + Map args = {}; + await channel?.invokeMethod('clearMatches', args); + } + + @override + @Deprecated("Use tRexRunnerHtml instead") + Future getTRexRunnerHtml() async { + return await tRexRunnerHtml; + } + + @override + @Deprecated("Use tRexRunnerCss instead") + Future getTRexRunnerCss() async { + return await tRexRunnerCss; + } + + @override + Future scrollTo( + {required int x, required int y, bool animated = false}) async { + Map args = {}; + args.putIfAbsent('x', () => x); + args.putIfAbsent('y', () => y); + args.putIfAbsent('animated', () => animated); + await channel?.invokeMethod('scrollTo', args); + } + + @override + Future scrollBy( + {required int x, required int y, bool animated = false}) async { + Map args = {}; + args.putIfAbsent('x', () => x); + args.putIfAbsent('y', () => y); + args.putIfAbsent('animated', () => animated); + await channel?.invokeMethod('scrollBy', args); + } + + @override + Future pauseTimers() async { + Map args = {}; + await channel?.invokeMethod('pauseTimers', args); + } + + @override + Future resumeTimers() async { + Map args = {}; + await channel?.invokeMethod('resumeTimers', args); + } + + @override + Future printCurrentPage( + {PrintJobSettings? settings}) async { + Map args = {}; + args.putIfAbsent("settings", () => settings?.toMap()); + String? jobId = + await channel?.invokeMethod('printCurrentPage', args); + if (jobId != null) { + return WindowsPrintJobController( + PlatformPrintJobControllerCreationParams(id: jobId)); + } + return null; + } + + @override + Future getContentHeight() async { + Map args = {}; + var height = await channel?.invokeMethod('getContentHeight', args); + if (height == null || height == 0) { + // try to use javascript + var scrollHeight = await evaluateJavascript( + source: "document.documentElement.scrollHeight;"); + if (scrollHeight != null && scrollHeight is num) { + height = scrollHeight.toInt(); + } + } + return height; + } + + @override + Future getContentWidth() async { + Map args = {}; + var height = await channel?.invokeMethod('getContentWidth', args); + if (height == null || height == 0) { + // try to use javascript + var scrollHeight = await evaluateJavascript( + source: "document.documentElement.scrollWidth;"); + if (scrollHeight != null && scrollHeight is num) { + height = scrollHeight.toInt(); + } + } + return height; + } + + @override + Future zoomBy( + {required double zoomFactor, + @Deprecated('Use animated instead') bool? iosAnimated, + bool animated = false}) async { + Map args = {}; + args.putIfAbsent('zoomFactor', () => zoomFactor); + args.putIfAbsent('animated', () => iosAnimated ?? animated); + return await channel?.invokeMethod('zoomBy', args); + } + + @override + Future getOriginalUrl() async { + Map args = {}; + String? url = await channel?.invokeMethod('getOriginalUrl', args); + return url != null ? WebUri(url) : null; + } + + @override + @Deprecated('Use getZoomScale instead') + Future getScale() async { + return await getZoomScale(); + } + + @override + Future getSelectedText() async { + Map args = {}; + return await channel?.invokeMethod('getSelectedText', args); + } + + @override + Future> getMetaTags() async { + List metaTags = []; + + List>? metaTagList = + (await evaluateJavascript(source: """ +(function() { + var metaTags = []; + var metaTagNodes = document.head.getElementsByTagName('meta'); + for (var i = 0; i < metaTagNodes.length; i++) { + var metaTagNode = metaTagNodes[i]; + + var otherAttributes = metaTagNode.getAttributeNames(); + var nameIndex = otherAttributes.indexOf("name"); + if (nameIndex !== -1) otherAttributes.splice(nameIndex, 1); + var contentIndex = otherAttributes.indexOf("content"); + if (contentIndex !== -1) otherAttributes.splice(contentIndex, 1); + + var attrs = []; + for (var j = 0; j < otherAttributes.length; j++) { + var otherAttribute = otherAttributes[j]; + attrs.push( + { + name: otherAttribute, + value: metaTagNode.getAttribute(otherAttribute) + } + ); + } + + metaTags.push( + { + name: metaTagNode.name, + content: metaTagNode.content, + attrs: attrs + } + ); + } + return metaTags; +})(); + """))?.cast>(); + + if (metaTagList == null) { + return metaTags; + } + + for (var metaTag in metaTagList) { + var attrs = []; + + for (var metaTagAttr in metaTag["attrs"]) { + attrs.add(MetaTagAttribute( + name: metaTagAttr["name"], value: metaTagAttr["value"])); + } + + metaTags.add(MetaTag( + name: metaTag["name"], content: metaTag["content"], attrs: attrs)); + } + + return metaTags; + } + + @override + Future getMetaThemeColor() async { + Color? themeColor; + + try { + Map args = {}; + themeColor = UtilColor.fromStringRepresentation( + await channel?.invokeMethod('getMetaThemeColor', args)); + return themeColor; + } catch (e) { + // not implemented + } + + // try using javascript + var metaTags = await getMetaTags(); + MetaTag? metaTagThemeColor; + + for (var metaTag in metaTags) { + if (metaTag.name == "theme-color") { + metaTagThemeColor = metaTag; + break; + } + } + + if (metaTagThemeColor == null) { + return null; + } + + var colorValue = metaTagThemeColor.content; + + themeColor = colorValue != null + ? UtilColor.fromStringRepresentation(colorValue) + : null; + + return themeColor; + } + + @override + Future getScrollX() async { + Map args = {}; + return await channel?.invokeMethod('getScrollX', args); + } + + @override + Future getScrollY() async { + Map args = {}; + return await channel?.invokeMethod('getScrollY', args); + } + + @override + Future getCertificate() async { + Map args = {}; + Map? sslCertificateMap = + (await channel?.invokeMethod('getCertificate', args)) + ?.cast(); + return SslCertificate.fromMap(sslCertificateMap); + } + + @override + Future addUserScript({required UserScript userScript}) async { + assert(webviewParams?.windowId == null); + + Map args = {}; + args.putIfAbsent('userScript', () => userScript.toMap()); + if (!(_userScripts[userScript.injectionTime]?.contains(userScript) ?? + false)) { + _userScripts[userScript.injectionTime]?.add(userScript); + await channel?.invokeMethod('addUserScript', args); + } + } + + @override + Future addUserScripts({required List userScripts}) async { + assert(webviewParams?.windowId == null); + + for (var i = 0; i < userScripts.length; i++) { + await addUserScript(userScript: userScripts[i]); + } + } + + @override + Future removeUserScript({required UserScript userScript}) async { + assert(webviewParams?.windowId == null); + + var index = _userScripts[userScript.injectionTime]?.indexOf(userScript); + if (index == null || index == -1) { + return false; + } + + _userScripts[userScript.injectionTime]?.remove(userScript); + Map args = {}; + args.putIfAbsent('userScript', () => userScript.toMap()); + args.putIfAbsent('index', () => index); + await channel?.invokeMethod('removeUserScript', args); + + return true; + } + + @override + Future removeUserScriptsByGroupName({required String groupName}) async { + assert(webviewParams?.windowId == null); + + final List userScriptsAtDocumentStart = List.from( + _userScripts[UserScriptInjectionTime.AT_DOCUMENT_START] ?? []); + for (final userScript in userScriptsAtDocumentStart) { + if (userScript.groupName == groupName) { + _userScripts[userScript.injectionTime]?.remove(userScript); + } + } + + final List userScriptsAtDocumentEnd = + List.from(_userScripts[UserScriptInjectionTime.AT_DOCUMENT_END] ?? []); + for (final userScript in userScriptsAtDocumentEnd) { + if (userScript.groupName == groupName) { + _userScripts[userScript.injectionTime]?.remove(userScript); + } + } + + Map args = {}; + args.putIfAbsent('groupName', () => groupName); + await channel?.invokeMethod('removeUserScriptsByGroupName', args); + } + + @override + Future removeUserScripts( + {required List userScripts}) async { + assert(webviewParams?.windowId == null); + + for (final userScript in userScripts) { + await removeUserScript(userScript: userScript); + } + } + + @override + Future removeAllUserScripts() async { + assert(webviewParams?.windowId == null); + + _userScripts[UserScriptInjectionTime.AT_DOCUMENT_START]?.clear(); + _userScripts[UserScriptInjectionTime.AT_DOCUMENT_END]?.clear(); + + Map args = {}; + await channel?.invokeMethod('removeAllUserScripts', args); + } + + @override + bool hasUserScript({required UserScript userScript}) { + return _userScripts[userScript.injectionTime]?.contains(userScript) ?? + false; + } + + @override + Future callAsyncJavaScript( + {required String functionBody, + Map arguments = const {}, + ContentWorld? contentWorld}) async { + Map args = {}; + args.putIfAbsent('functionBody', () => functionBody); + 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"]); + } + + @override + Future saveWebArchive( + {required String filePath, bool autoname = false}) async { + if (!autoname) { + assert( + filePath.endsWith("." + WebArchiveFormat.WEBARCHIVE.toNativeValue())); + } + + Map args = {}; + args.putIfAbsent("filePath", () => filePath); + args.putIfAbsent("autoname", () => autoname); + return await channel?.invokeMethod('saveWebArchive', args); + } + + @override + Future isSecureContext() async { + Map args = {}; + return await channel?.invokeMethod('isSecureContext', args) ?? false; + } + + @override + Future createWebMessageChannel() async { + Map args = {}; + Map? result = + (await channel?.invokeMethod('createWebMessageChannel', args)) + ?.cast(); + final webMessageChannel = WindowsWebMessageChannel.static().fromMap(result); + if (webMessageChannel != null) { + _webMessageChannels.add(webMessageChannel); + } + return webMessageChannel; + } + + @override + Future postWebMessage( + {required WebMessage message, WebUri? targetOrigin}) async { + if (targetOrigin == null) { + targetOrigin = WebUri(''); + } + Map args = {}; + args.putIfAbsent('message', () => message.toMap()); + args.putIfAbsent('targetOrigin', () => targetOrigin.toString()); + await channel?.invokeMethod('postWebMessage', args); + } + + @override + Future addWebMessageListener( + PlatformWebMessageListener webMessageListener) async { + assert(!_webMessageListeners.contains(webMessageListener), + "${webMessageListener} was already added."); + assert( + !_webMessageListenerObjNames + .contains(webMessageListener.params.jsObjectName), + "jsObjectName ${webMessageListener.params.jsObjectName} was already added."); + _webMessageListeners.add(webMessageListener as WindowsWebMessageListener); + _webMessageListenerObjNames.add(webMessageListener.params.jsObjectName); + + Map args = {}; + args.putIfAbsent('webMessageListener', () => webMessageListener.toMap()); + await channel?.invokeMethod('addWebMessageListener', args); + } + + @override + bool hasWebMessageListener(PlatformWebMessageListener webMessageListener) { + return _webMessageListeners.contains(webMessageListener) || + _webMessageListenerObjNames + .contains(webMessageListener.params.jsObjectName); + } + + @override + Future canScrollVertically() async { + Map args = {}; + return await channel?.invokeMethod('canScrollVertically', args) ?? + false; + } + + @override + Future canScrollHorizontally() async { + Map args = {}; + return await channel?.invokeMethod('canScrollHorizontally', args) ?? + false; + } + + @override + Future reloadFromOrigin() async { + Map args = {}; + await channel?.invokeMethod('reloadFromOrigin', args); + } + + @override + Future createPdf( + {@Deprecated("Use pdfConfiguration instead") + // ignore: deprecated_member_use_from_same_package + IOSWKPDFConfiguration? iosWKPdfConfiguration, + PDFConfiguration? pdfConfiguration}) async { + Map args = {}; + args.putIfAbsent('pdfConfiguration', + () => pdfConfiguration?.toMap() ?? iosWKPdfConfiguration?.toMap()); + return await channel?.invokeMethod('createPdf', args); + } + + @override + Future createWebArchiveData() async { + Map args = {}; + return await channel?.invokeMethod('createWebArchiveData', args); + } + + @override + Future hasOnlySecureContent() async { + Map args = {}; + return await channel?.invokeMethod('hasOnlySecureContent', args) ?? + false; + } + + @override + Future pauseAllMediaPlayback() async { + Map args = {}; + return await channel?.invokeMethod('pauseAllMediaPlayback', args); + } + + @override + Future setAllMediaPlaybackSuspended({required bool suspended}) async { + Map args = {}; + args.putIfAbsent("suspended", () => suspended); + return await channel?.invokeMethod('setAllMediaPlaybackSuspended', args); + } + + @override + Future closeAllMediaPresentations() async { + Map args = {}; + return await channel?.invokeMethod('closeAllMediaPresentations', args); + } + + @override + Future requestMediaPlaybackState() async { + Map args = {}; + return MediaPlaybackState.fromNativeValue( + await channel?.invokeMethod('requestMediaPlaybackState', args)); + } + + @override + Future isInFullscreen() async { + Map args = {}; + return await channel?.invokeMethod('isInFullscreen', args) ?? false; + } + + @override + Future getCameraCaptureState() async { + Map args = {}; + return MediaCaptureState.fromNativeValue( + await channel?.invokeMethod('getCameraCaptureState', args)); + } + + @override + Future setCameraCaptureState({required MediaCaptureState state}) async { + Map args = {}; + args.putIfAbsent('state', () => state.toNativeValue()); + await channel?.invokeMethod('setCameraCaptureState', args); + } + + @override + Future getMicrophoneCaptureState() async { + Map args = {}; + return MediaCaptureState.fromNativeValue( + await channel?.invokeMethod('getMicrophoneCaptureState', args)); + } + + @override + Future setMicrophoneCaptureState( + {required MediaCaptureState state}) async { + Map args = {}; + args.putIfAbsent('state', () => state.toNativeValue()); + await channel?.invokeMethod('setMicrophoneCaptureState', args); + } + + @override + Future loadSimulatedRequest( + {required URLRequest urlRequest, + required Uint8List data, + URLResponse? urlResponse}) async { + Map args = {}; + args.putIfAbsent('urlRequest', () => urlRequest.toMap()); + args.putIfAbsent('data', () => data); + args.putIfAbsent('urlResponse', () => urlResponse?.toMap()); + await channel?.invokeMethod('loadSimulatedRequest', args); + } + + @override + Future openDevTools() async { + Map args = {}; + await channel?.invokeMethod('openDevTools', args); + } + + @override + Future callDevToolsProtocolMethod({required String methodName, Map? parameters}) async { + Map args = {}; + args.putIfAbsent('methodName', () => methodName); + args.putIfAbsent('parametersAsJson', () => parameters != null ? jsonEncode(parameters) : null); + final result = await channel?.invokeMethod('callDevToolsProtocolMethod', args); + if (result != null) { + return jsonDecode(result); + } + return null; + } + + @override + Future addDevToolsProtocolEventListener({required String eventName, required Function(dynamic data) callback}) async { + Map args = {}; + args.putIfAbsent('eventName', () => eventName); + await channel?.invokeMethod('addDevToolsProtocolEventListener', args); + this._devToolsProtocolEventListenerMap[eventName] = callback; + } + + @override + Future removeDevToolsProtocolEventListener({required String eventName}) async { + Map args = {}; + args.putIfAbsent('eventName', () => eventName); + await channel?.invokeMethod('removeDevToolsProtocolEventListener', args); + this._devToolsProtocolEventListenerMap.remove(eventName); + } + + @override + Future getDefaultUserAgent() async { + Map args = {}; + return await _staticChannel.invokeMethod( + 'getDefaultUserAgent', args) ?? + ''; + } + + @override + Future handlesURLScheme(String urlScheme) async { + Map args = {}; + args.putIfAbsent('urlScheme', () => urlScheme); + return await _staticChannel.invokeMethod('handlesURLScheme', args); + } + + @override + Future disposeKeepAlive(InAppWebViewKeepAlive keepAlive) async { + Map args = {}; + args.putIfAbsent('keepAliveId', () => keepAlive.id); + await _staticChannel.invokeMethod('disposeKeepAlive', args); + _keepAliveMap[keepAlive] = null; + } + + @override + Future clearAllCache({bool includeDiskFiles = true}) async { + Map args = {}; + args.putIfAbsent('includeDiskFiles', () => includeDiskFiles); + await _staticChannel.invokeMethod('clearAllCache', args); + } + + @override + Future get tRexRunnerHtml async => await rootBundle.loadString( + 'packages/flutter_inappwebview/assets/t_rex_runner/t-rex.html'); + + @override + Future get tRexRunnerCss async => await rootBundle.loadString( + 'packages/flutter_inappwebview/assets/t_rex_runner/t-rex.css'); + + @override + dynamic getViewId() { + return id; + } + + @override + void dispose({bool isKeepAlive = false}) { + disposeChannel(removeMethodCallHandler: !isKeepAlive); + _inAppBrowser = null; + webStorage.dispose(); + if (!isKeepAlive) { + _controllerFromPlatform = null; + _javaScriptHandlersMap.clear(); + _userScripts.clear(); + _webMessageListenerObjNames.clear(); + _injectedScriptsFromURL.clear(); + for (final webMessageChannel in _webMessageChannels) { + webMessageChannel.dispose(); + } + _webMessageChannels.clear(); + for (final webMessageListener in _webMessageListeners) { + webMessageListener.dispose(); + } + _webMessageListeners.clear(); + _devToolsProtocolEventListenerMap.clear(); + } + } +} + +extension InternalInAppWebViewController on WindowsInAppWebViewController { + get handleMethod => _handleMethod; +} diff --git a/flutter_inappwebview_windows/lib/src/in_app_webview/main.dart b/flutter_inappwebview_windows/lib/src/in_app_webview/main.dart new file mode 100644 index 00000000..b83b0611 --- /dev/null +++ b/flutter_inappwebview_windows/lib/src/in_app_webview/main.dart @@ -0,0 +1,3 @@ +export 'in_app_webview_controller.dart' hide InternalInAppWebViewController; +export 'in_app_webview.dart'; +export 'headless_in_app_webview.dart' hide InternalHeadlessInAppWebView; diff --git a/flutter_inappwebview_windows/lib/src/inappwebview_platform.dart b/flutter_inappwebview_windows/lib/src/inappwebview_platform.dart new file mode 100644 index 00000000..3dbf4ab7 --- /dev/null +++ b/flutter_inappwebview_windows/lib/src/inappwebview_platform.dart @@ -0,0 +1,144 @@ +import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; + +import 'cookie_manager.dart'; +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'; +import 'webview_environment/webview_environment.dart'; +import 'web_storage/web_storage.dart'; + +/// Implementation of [InAppWebViewPlatform] using the WebKit API. +class WindowsInAppWebViewPlatform extends InAppWebViewPlatform { + /// Registers this class as the default instance of [InAppWebViewPlatform]. + static void registerWith() { + InAppWebViewPlatform.instance = WindowsInAppWebViewPlatform(); + } + + /// Creates a new [WindowsCookieManager]. + /// + /// This function should only be called by the app-facing package. + /// Look at using [CookieManager] in `flutter_inappwebview` instead. + @override + WindowsCookieManager createPlatformCookieManager( + PlatformCookieManagerCreationParams params, + ) { + return WindowsCookieManager(params); + } + + + /// Creates a new [WindowsInAppWebViewController]. + /// + /// This function should only be called by the app-facing package. + /// Look at using [InAppWebViewController] in `flutter_inappwebview` instead. + @override + WindowsInAppWebViewController createPlatformInAppWebViewController( + PlatformInAppWebViewControllerCreationParams params, + ) { + return WindowsInAppWebViewController(params); + } + + /// Creates a new empty [WindowsInAppWebViewController] to access static methods. + /// + /// This function should only be called by the app-facing package. + /// Look at using [InAppWebViewController] in `flutter_inappwebview` instead. + @override + WindowsInAppWebViewController createPlatformInAppWebViewControllerStatic() { + return WindowsInAppWebViewController.static(); + } + + /// Creates a new [WindowsInAppWebViewWidget]. + /// + /// This function should only be called by the app-facing package. + /// Look at using [InAppWebView] in `flutter_inappwebview` instead. + @override + WindowsInAppWebViewWidget createPlatformInAppWebViewWidget( + PlatformInAppWebViewWidgetCreationParams params, + ) { + return WindowsInAppWebViewWidget(params); + } + + /// Creates a new [WindowsInAppBrowser]. + /// + /// This function should only be called by the app-facing package. + /// Look at using [InAppBrowser] in `flutter_inappwebview` instead. + @override + WindowsInAppBrowser createPlatformInAppBrowser( + PlatformInAppBrowserCreationParams params, + ) { + return WindowsInAppBrowser(params); + } + + /// Creates a new empty [WindowsInAppBrowser] to access static methods. + /// + /// This function should only be called by the app-facing package. + /// Look at using [InAppBrowser] in `flutter_inappwebview` instead. + @override + 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); + } + + /// Creates a new [WindowsWebViewEnvironment]. + /// + /// This function should only be called by the app-facing package. + /// Look at using [WebViewEnvironment] in `flutter_inappwebview` instead. + @override + WindowsWebViewEnvironment createPlatformWebViewEnvironment( + PlatformWebViewEnvironmentCreationParams params, + ) { + return WindowsWebViewEnvironment(params); + } + + /// Creates a new empty [WindowsWebViewEnvironment] to access static methods. + /// + /// This function should only be called by the app-facing package. + /// Look at using [WebViewEnvironment] in `flutter_inappwebview` instead. + @override + WindowsWebViewEnvironment createPlatformWebViewEnvironmentStatic() { + return WindowsWebViewEnvironment.static(); + } + + /// Creates a new [WindowsWebStorage]. + /// + /// This function should only be called by the app-facing package. + /// Look at using [WebStorage] in `flutter_inappwebview` instead. + @override + WindowsWebStorage createPlatformWebStorage( + PlatformWebStorageCreationParams params, + ) { + return WindowsWebStorage(params); + } + + /// Creates a new [WindowsLocalStorage]. + /// + /// This function should only be called by the app-facing package. + /// Look at using [LocalStorage] in `flutter_inappwebview` instead. + @override + WindowsLocalStorage createPlatformLocalStorage( + PlatformLocalStorageCreationParams params, + ) { + return WindowsLocalStorage(params); + } + + /// Creates a new [WindowsSessionStorage]. + /// + /// This function should only be called by the app-facing package. + /// Look at using [SessionStorage] in `flutter_inappwebview` instead. + @override + WindowsSessionStorage createPlatformSessionStorage( + PlatformSessionStorageCreationParams params, + ) { + return WindowsSessionStorage(params); + } +} diff --git a/flutter_inappwebview_windows/lib/src/main.dart b/flutter_inappwebview_windows/lib/src/main.dart new file mode 100644 index 00000000..4fce8f8c --- /dev/null +++ b/flutter_inappwebview_windows/lib/src/main.dart @@ -0,0 +1,11 @@ +export 'inappwebview_platform.dart'; +export 'in_app_webview/main.dart'; +export 'in_app_browser/main.dart'; +export 'web_storage/main.dart'; +export 'cookie_manager.dart' hide InternalCookieManager; +export 'http_auth_credentials_database.dart' + hide InternalHttpAuthCredentialDatabase; +export 'web_message/main.dart'; +export 'print_job/main.dart'; +export 'find_interaction/main.dart'; +export 'webview_environment/main.dart'; diff --git a/flutter_inappwebview_windows/lib/src/platform_util.dart b/flutter_inappwebview_windows/lib/src/platform_util.dart new file mode 100644 index 00000000..27e96478 --- /dev/null +++ b/flutter_inappwebview_windows/lib/src/platform_util.dart @@ -0,0 +1,64 @@ +import 'package:flutter/services.dart'; + +///Platform native utilities +class PlatformUtil { + static PlatformUtil? _instance; + static const MethodChannel _channel = + MethodChannel('com.pichillilorenzo/flutter_inappwebview_platformutil'); + + PlatformUtil._(); + + ///Get [PlatformUtil] instance. + static PlatformUtil instance() { + return (_instance != null) ? _instance! : _init(); + } + + static PlatformUtil _init() { + _channel.setMethodCallHandler((call) async { + try { + return await _handleMethod(call); + } on Error catch (e) { + print(e); + print(e.stackTrace); + } + }); + _instance = PlatformUtil._(); + return _instance!; + } + + static Future _handleMethod(MethodCall call) async {} + + String? _cachedSystemVersion; + + ///Get current platform system version. + Future getSystemVersion() async { + if (_cachedSystemVersion != null) { + return _cachedSystemVersion!; + } + Map args = {}; + _cachedSystemVersion = + await _channel.invokeMethod('getSystemVersion', args); + return _cachedSystemVersion!; + } + + ///Format date. + Future formatDate( + {required DateTime date, + required String format, + String locale = "en_US", + String timezone = "UTC"}) async { + Map args = {}; + args.putIfAbsent('date', () => date.millisecondsSinceEpoch); + args.putIfAbsent('format', () => format); + args.putIfAbsent('locale', () => locale); + args.putIfAbsent('timezone', () => timezone); + return await _channel.invokeMethod('formatDate', args); + } + + ///Get cookie expiration date used by Web platform. + Future getWebCookieExpirationDate({required DateTime date}) async { + Map args = {}; + args.putIfAbsent('date', () => date.millisecondsSinceEpoch); + return await _channel.invokeMethod('getWebCookieExpirationDate', args); + } +} diff --git a/flutter_inappwebview_windows/lib/src/print_job/main.dart b/flutter_inappwebview_windows/lib/src/print_job/main.dart new file mode 100644 index 00000000..4e70ad94 --- /dev/null +++ b/flutter_inappwebview_windows/lib/src/print_job/main.dart @@ -0,0 +1 @@ +export 'print_job_controller.dart'; diff --git a/flutter_inappwebview_windows/lib/src/print_job/print_job_controller.dart b/flutter_inappwebview_windows/lib/src/print_job/print_job_controller.dart new file mode 100644 index 00000000..a1da365a --- /dev/null +++ b/flutter_inappwebview_windows/lib/src/print_job/print_job_controller.dart @@ -0,0 +1,73 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; + +/// Object specifying creation parameters for creating a [WindowsPrintJobController]. +/// +/// When adding additional fields make sure they can be null or have a default +/// value to avoid breaking changes. See [PlatformPrintJobControllerCreationParams] for +/// more information. +@immutable +class WindowsPrintJobControllerCreationParams + extends PlatformPrintJobControllerCreationParams { + /// Creates a new [WindowsPrintJobControllerCreationParams] instance. + const WindowsPrintJobControllerCreationParams( + {required super.id, super.onComplete}); + + /// Creates a [WindowsPrintJobControllerCreationParams] instance based on [PlatformPrintJobControllerCreationParams]. + factory WindowsPrintJobControllerCreationParams.fromPlatformPrintJobControllerCreationParams( + // Recommended placeholder to prevent being broken by platform interface. + // ignore: avoid_unused_constructor_parameters + PlatformPrintJobControllerCreationParams params) { + return WindowsPrintJobControllerCreationParams( + id: params.id, onComplete: params.onComplete); + } +} + +///{@macro flutter_inappwebview_platform_interface.PlatformPrintJobController} +class WindowsPrintJobController extends PlatformPrintJobController + with ChannelController { + /// Constructs a [WindowsPrintJobController]. + WindowsPrintJobController(PlatformPrintJobControllerCreationParams params) + : super.implementation( + params is WindowsPrintJobControllerCreationParams + ? params + : WindowsPrintJobControllerCreationParams + .fromPlatformPrintJobControllerCreationParams(params), + ) { + onComplete = params.onComplete; + channel = MethodChannel( + 'com.pichillilorenzo/flutter_inappwebview_printjobcontroller_${params.id}'); + handler = _handleMethod; + initMethodCallHandler(); + } + + Future _handleMethod(MethodCall call) async { + switch (call.method) { + case "onComplete": + bool completed = call.arguments["completed"]; + String? error = call.arguments["error"]; + if (onComplete != null) { + onComplete!(completed, error); + } + break; + default: + throw UnimplementedError("Unimplemented ${call.method} method"); + } + } + + @override + Future getInfo() async { + Map args = {}; + Map? infoMap = + (await channel?.invokeMethod('getInfo', args))?.cast(); + return PrintJobInfo.fromMap(infoMap); + } + + @override + Future dispose() async { + Map args = {}; + await channel?.invokeMethod('dispose', args); + disposeChannel(); + } +} diff --git a/flutter_inappwebview_windows/lib/src/web_message/main.dart b/flutter_inappwebview_windows/lib/src/web_message/main.dart new file mode 100644 index 00000000..d41e30c7 --- /dev/null +++ b/flutter_inappwebview_windows/lib/src/web_message/main.dart @@ -0,0 +1,3 @@ +export 'web_message_port.dart' hide InternalWebMessagePort; +export 'web_message_channel.dart' hide InternalWebMessageChannel; +export 'web_message_listener.dart'; diff --git a/flutter_inappwebview_windows/lib/src/web_message/web_message_channel.dart b/flutter_inappwebview_windows/lib/src/web_message/web_message_channel.dart new file mode 100644 index 00000000..2a47ab16 --- /dev/null +++ b/flutter_inappwebview_windows/lib/src/web_message/web_message_channel.dart @@ -0,0 +1,120 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; +import 'web_message_port.dart'; + +/// Object specifying creation parameters for creating a [WindowsWebMessageChannel]. +/// +/// When adding additional fields make sure they can be null or have a default +/// value to avoid breaking changes. See [PlatformWebMessageChannelCreationParams] for +/// more information. +@immutable +class WindowsWebMessageChannelCreationParams + extends PlatformWebMessageChannelCreationParams { + /// Creates a new [WindowsWebMessageChannelCreationParams] instance. + const WindowsWebMessageChannelCreationParams( + {required super.id, required super.port1, required super.port2}); + + /// Creates a [WindowsWebMessageChannelCreationParams] instance based on [PlatformWebMessageChannelCreationParams]. + factory WindowsWebMessageChannelCreationParams.fromPlatformWebMessageChannelCreationParams( + // Recommended placeholder to prevent being broken by platform interface. + // ignore: avoid_unused_constructor_parameters + PlatformWebMessageChannelCreationParams params) { + return WindowsWebMessageChannelCreationParams( + id: params.id, port1: params.port1, port2: params.port2); + } + + @override + String toString() { + return 'MacOSWebMessageChannelCreationParams{id: $id, port1: $port1, port2: $port2}'; + } +} + +///{@macro flutter_inappwebview_platform_interface.PlatformWebMessageChannel} +class WindowsWebMessageChannel extends PlatformWebMessageChannel + with ChannelController { + /// Constructs a [WindowsWebMessageChannel]. + WindowsWebMessageChannel(PlatformWebMessageChannelCreationParams params) + : super.implementation( + params is WindowsWebMessageChannelCreationParams + ? params + : WindowsWebMessageChannelCreationParams + .fromPlatformWebMessageChannelCreationParams(params), + ) { + channel = MethodChannel( + 'com.pichillilorenzo/flutter_inappwebview_web_message_channel_${params.id}'); + handler = _handleMethod; + initMethodCallHandler(); + } + + static final WindowsWebMessageChannel _staticValue = WindowsWebMessageChannel( + WindowsWebMessageChannelCreationParams( + id: '', + port1: + WindowsWebMessagePort(WindowsWebMessagePortCreationParams(index: 0)), + port2: WindowsWebMessagePort( + WindowsWebMessagePortCreationParams(index: 1)))); + + /// Provide static access. + factory WindowsWebMessageChannel.static() { + return _staticValue; + } + + WindowsWebMessagePort get _macosPort1 => port1 as WindowsWebMessagePort; + + WindowsWebMessagePort get _macosPort2 => port2 as WindowsWebMessagePort; + + static WindowsWebMessageChannel? _fromMap(Map? map) { + if (map == null) { + return null; + } + var webMessageChannel = WindowsWebMessageChannel( + WindowsWebMessageChannelCreationParams( + id: map["id"], + port1: WindowsWebMessagePort( + WindowsWebMessagePortCreationParams(index: 0)), + port2: WindowsWebMessagePort( + WindowsWebMessagePortCreationParams(index: 1)))); + webMessageChannel._macosPort1.webMessageChannel = webMessageChannel; + webMessageChannel._macosPort2.webMessageChannel = webMessageChannel; + return webMessageChannel; + } + + Future _handleMethod(MethodCall call) async { + switch (call.method) { + case "onMessage": + int index = call.arguments["index"]; + var port = index == 0 ? _macosPort1 : _macosPort2; + if (port.onMessage != null) { + WebMessage? message = call.arguments["message"] != null + ? WebMessage.fromMap( + call.arguments["message"].cast()) + : null; + port.onMessage!(message); + } + break; + default: + throw UnimplementedError("Unimplemented ${call.method} method"); + } + return null; + } + + @override + WindowsWebMessageChannel? fromMap(Map? map) { + return _fromMap(map); + } + + @override + void dispose() { + disposeChannel(); + } + + @override + String toString() { + return 'MacOSWebMessageChannel{id: $id, port1: $port1, port2: $port2}'; + } +} + +extension InternalWebMessageChannel on WindowsWebMessageChannel { + MethodChannel? get internalChannel => channel; +} diff --git a/flutter_inappwebview_windows/lib/src/web_message/web_message_listener.dart b/flutter_inappwebview_windows/lib/src/web_message/web_message_listener.dart new file mode 100644 index 00000000..22a9cd76 --- /dev/null +++ b/flutter_inappwebview_windows/lib/src/web_message/web_message_listener.dart @@ -0,0 +1,164 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; + +/// Object specifying creation parameters for creating a [WindowsWebMessageListener]. +/// +/// When adding additional fields make sure they can be null or have a default +/// value to avoid breaking changes. See [PlatformWebMessageListenerCreationParams] for +/// more information. +@immutable +class WindowsWebMessageListenerCreationParams + extends PlatformWebMessageListenerCreationParams { + /// Creates a new [WindowsWebMessageListenerCreationParams] instance. + const WindowsWebMessageListenerCreationParams( + {required this.allowedOriginRules, + required super.jsObjectName, + super.onPostMessage}); + + /// Creates a [WindowsWebMessageListenerCreationParams] instance based on [PlatformWebMessageListenerCreationParams]. + factory WindowsWebMessageListenerCreationParams.fromPlatformWebMessageListenerCreationParams( + // Recommended placeholder to prevent being broken by platform interface. + // ignore: avoid_unused_constructor_parameters + PlatformWebMessageListenerCreationParams params) { + return WindowsWebMessageListenerCreationParams( + allowedOriginRules: params.allowedOriginRules ?? Set.from(["*"]), + jsObjectName: params.jsObjectName, + onPostMessage: params.onPostMessage); + } + + @override + final Set allowedOriginRules; + + @override + String toString() { + return 'MacOSWebMessageListenerCreationParams{jsObjectName: $jsObjectName, allowedOriginRules: $allowedOriginRules, onPostMessage: $onPostMessage}'; + } +} + +///{@macro flutter_inappwebview_platform_interface.PlatformWebMessageListener} +class WindowsWebMessageListener extends PlatformWebMessageListener + with ChannelController { + /// Constructs a [WindowsWebMessageListener]. + WindowsWebMessageListener(PlatformWebMessageListenerCreationParams params) + : super.implementation( + params is WindowsWebMessageListenerCreationParams + ? params + : WindowsWebMessageListenerCreationParams + .fromPlatformWebMessageListenerCreationParams(params), + ) { + assert(!this._macosParams.allowedOriginRules.contains(""), + "allowedOriginRules cannot contain empty strings"); + channel = MethodChannel( + 'com.pichillilorenzo/flutter_inappwebview_web_message_listener_${_id}_${params.jsObjectName}'); + handler = _handleMethod; + initMethodCallHandler(); + } + + ///Message Listener ID used internally. + final String _id = IdGenerator.generate(); + + MacOSJavaScriptReplyProxy? _replyProxy; + + WindowsWebMessageListenerCreationParams get _macosParams => + params as WindowsWebMessageListenerCreationParams; + + Future _handleMethod(MethodCall call) async { + switch (call.method) { + case "onPostMessage": + if (_replyProxy == null) { + _replyProxy = MacOSJavaScriptReplyProxy( + PlatformJavaScriptReplyProxyCreationParams( + webMessageListener: this)); + } + if (onPostMessage != null) { + WebMessage? message = call.arguments["message"] != null + ? WebMessage.fromMap( + call.arguments["message"].cast()) + : null; + WebUri? sourceOrigin = call.arguments["sourceOrigin"] != null + ? WebUri(call.arguments["sourceOrigin"]) + : null; + bool isMainFrame = call.arguments["isMainFrame"]; + onPostMessage!(message, sourceOrigin, isMainFrame, _replyProxy!); + } + break; + default: + throw UnimplementedError("Unimplemented ${call.method} method"); + } + return null; + } + + @override + void dispose() { + disposeChannel(); + } + + @override + Map toMap() { + return { + "id": _id, + "jsObjectName": params.jsObjectName, + "allowedOriginRules": _macosParams.allowedOriginRules.toList(), + }; + } + + @override + Map toJson() { + return this.toMap(); + } + + @override + String toString() { + return 'MacOSWebMessageListener{id: ${_id}, jsObjectName: ${params.jsObjectName}, allowedOriginRules: ${params.allowedOriginRules}, replyProxy: $_replyProxy}'; + } +} + +/// Object specifying creation parameters for creating a [MacOSJavaScriptReplyProxy]. +/// +/// When adding additional fields make sure they can be null or have a default +/// value to avoid breaking changes. See [PlatformJavaScriptReplyProxyCreationParams] for +/// more information. +@immutable +class MacOSJavaScriptReplyProxyCreationParams + extends PlatformJavaScriptReplyProxyCreationParams { + /// Creates a new [MacOSJavaScriptReplyProxyCreationParams] instance. + const MacOSJavaScriptReplyProxyCreationParams( + {required super.webMessageListener}); + + /// Creates a [MacOSJavaScriptReplyProxyCreationParams] instance based on [PlatformJavaScriptReplyProxyCreationParams]. + factory MacOSJavaScriptReplyProxyCreationParams.fromPlatformJavaScriptReplyProxyCreationParams( + // Recommended placeholder to prevent being broken by platform interface. + // ignore: avoid_unused_constructor_parameters + PlatformJavaScriptReplyProxyCreationParams params) { + return MacOSJavaScriptReplyProxyCreationParams( + webMessageListener: params.webMessageListener); + } +} + +///{@macro flutter_inappwebview_platform_interface.JavaScriptReplyProxy} +class MacOSJavaScriptReplyProxy extends PlatformJavaScriptReplyProxy { + /// Constructs a [WindowsWebMessageListener]. + MacOSJavaScriptReplyProxy(PlatformJavaScriptReplyProxyCreationParams params) + : super.implementation( + params is MacOSJavaScriptReplyProxyCreationParams + ? params + : MacOSJavaScriptReplyProxyCreationParams + .fromPlatformJavaScriptReplyProxyCreationParams(params), + ); + + WindowsWebMessageListener get _macosWebMessageListener => + params.webMessageListener as WindowsWebMessageListener; + + @override + Future postMessage(WebMessage message) async { + Map args = {}; + args.putIfAbsent('message', () => message.toMap()); + await _macosWebMessageListener.channel?.invokeMethod('postMessage', args); + } + + @override + String toString() { + return 'MacOSJavaScriptReplyProxy{}'; + } +} diff --git a/flutter_inappwebview_windows/lib/src/web_message/web_message_port.dart b/flutter_inappwebview_windows/lib/src/web_message/web_message_port.dart new file mode 100644 index 00000000..f75c956e --- /dev/null +++ b/flutter_inappwebview_windows/lib/src/web_message/web_message_port.dart @@ -0,0 +1,95 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; + +import 'web_message_channel.dart'; + +/// Object specifying creation parameters for creating a [WindowsWebMessagePort]. +/// +/// When adding additional fields make sure they can be null or have a default +/// value to avoid breaking changes. See [PlatformWebMessagePortCreationParams] for +/// more information. +@immutable +class WindowsWebMessagePortCreationParams + extends PlatformWebMessagePortCreationParams { + /// Creates a new [WindowsWebMessagePortCreationParams] instance. + const WindowsWebMessagePortCreationParams({required super.index}); + + /// Creates a [WindowsWebMessagePortCreationParams] instance based on [PlatformWebMessagePortCreationParams]. + factory WindowsWebMessagePortCreationParams.fromPlatformWebMessagePortCreationParams( + // Recommended placeholder to prevent being broken by platform interface. + // ignore: avoid_unused_constructor_parameters + PlatformWebMessagePortCreationParams params) { + return WindowsWebMessagePortCreationParams(index: params.index); + } + + @override + String toString() { + return 'MacOSWebMessagePortCreationParams{index: $index}'; + } +} + +///{@macro flutter_inappwebview_platform_interface.PlatformWebMessagePort} +class WindowsWebMessagePort extends PlatformWebMessagePort { + WebMessageCallback? _onMessage; + late WindowsWebMessageChannel _webMessageChannel; + + /// Constructs a [WindowsWebMessagePort]. + WindowsWebMessagePort(PlatformWebMessagePortCreationParams params) + : super.implementation( + params is WindowsWebMessagePortCreationParams + ? params + : WindowsWebMessagePortCreationParams + .fromPlatformWebMessagePortCreationParams(params), + ); + + @override + Future setWebMessageCallback(WebMessageCallback? onMessage) async { + Map args = {}; + args.putIfAbsent('index', () => params.index); + await _webMessageChannel.internalChannel + ?.invokeMethod('setWebMessageCallback', args); + this._onMessage = onMessage; + } + + @override + Future postMessage(WebMessage message) async { + Map args = {}; + args.putIfAbsent('index', () => params.index); + args.putIfAbsent('message', () => message.toMap()); + await _webMessageChannel.internalChannel?.invokeMethod('postMessage', args); + } + + @override + Future close() async { + Map args = {}; + args.putIfAbsent('index', () => params.index); + await _webMessageChannel.internalChannel?.invokeMethod('close', args); + } + + @override + Map toMap() { + return { + "index": params.index, + "webMessageChannelId": this._webMessageChannel.params.id + }; + } + + @override + Map toJson() { + return toMap(); + } + + @override + String toString() { + return 'MacOSWebMessagePort{index: ${params.index}}'; + } +} + +extension InternalWebMessagePort on WindowsWebMessagePort { + WebMessageCallback? get onMessage => _onMessage; + void set onMessage(WebMessageCallback? value) => _onMessage = value; + + WindowsWebMessageChannel get webMessageChannel => _webMessageChannel; + void set webMessageChannel(WindowsWebMessageChannel value) => + _webMessageChannel = value; +} diff --git a/flutter_inappwebview_windows/lib/src/web_storage/main.dart b/flutter_inappwebview_windows/lib/src/web_storage/main.dart new file mode 100644 index 00000000..7265ae52 --- /dev/null +++ b/flutter_inappwebview_windows/lib/src/web_storage/main.dart @@ -0,0 +1,2 @@ +export 'web_storage.dart'; +export 'web_storage_manager.dart' hide InternalWebStorageManager; diff --git a/flutter_inappwebview_windows/lib/src/web_storage/web_storage.dart b/flutter_inappwebview_windows/lib/src/web_storage/web_storage.dart new file mode 100644 index 00000000..7fe18b95 --- /dev/null +++ b/flutter_inappwebview_windows/lib/src/web_storage/web_storage.dart @@ -0,0 +1,257 @@ +import 'dart:convert'; + +import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; + +import '../in_app_webview/in_app_webview_controller.dart'; + +/// Object specifying creation parameters for creating a [WindowsWebStorage]. +/// +/// When adding additional fields make sure they can be null or have a default +/// value to avoid breaking changes. See [PlatformWebStorageCreationParams] for +/// more information. +class WindowsWebStorageCreationParams extends PlatformWebStorageCreationParams { + /// Creates a new [WindowsWebStorageCreationParams] instance. + WindowsWebStorageCreationParams( + {required super.localStorage, required super.sessionStorage}); + + /// Creates a [WindowsWebStorageCreationParams] instance based on [PlatformWebStorageCreationParams]. + factory WindowsWebStorageCreationParams.fromPlatformWebStorageCreationParams( + // Recommended placeholder to prevent being broken by platform interface. + // ignore: avoid_unused_constructor_parameters + PlatformWebStorageCreationParams params) { + return WindowsWebStorageCreationParams( + localStorage: params.localStorage, + sessionStorage: params.sessionStorage); + } +} + +///{@macro flutter_inappwebview_platform_interface.PlatformWebStorage} +class WindowsWebStorage extends PlatformWebStorage { + /// Constructs a [WindowsWebStorage]. + WindowsWebStorage(PlatformWebStorageCreationParams params) + : super.implementation( + params is WindowsWebStorageCreationParams + ? params + : WindowsWebStorageCreationParams + .fromPlatformWebStorageCreationParams(params), + ); + + @override + PlatformLocalStorage get localStorage => params.localStorage; + + @override + PlatformSessionStorage get sessionStorage => params.sessionStorage; + + @override + void dispose() { + localStorage.dispose(); + sessionStorage.dispose(); + } +} + +/// Object specifying creation parameters for creating a [WindowsStorage]. +/// +/// When adding additional fields make sure they can be null or have a default +/// value to avoid breaking changes. See [PlatformStorageCreationParams] for +/// more information. +class WindowsStorageCreationParams extends PlatformStorageCreationParams { + /// Creates a new [WindowsStorageCreationParams] instance. + WindowsStorageCreationParams( + {required super.controller, required super.webStorageType}); + + /// Creates a [WindowsStorageCreationParams] instance based on [PlatformStorageCreationParams]. + factory WindowsStorageCreationParams.fromPlatformStorageCreationParams( + // Recommended placeholder to prevent being broken by platform interface. + // ignore: avoid_unused_constructor_parameters + PlatformStorageCreationParams params) { + return WindowsStorageCreationParams( + controller: params.controller, webStorageType: params.webStorageType); + } +} + +///{@macro flutter_inappwebview_platform_interface.PlatformStorage} +abstract class WindowsStorage implements PlatformStorage { + @override + WindowsInAppWebViewController? controller; + + @override + Future length() async { + var result = await controller?.evaluateJavascript(source: """ + window.$webStorageType.length; + """); + return result != null ? int.parse(json.decode(result)) : null; + } + + @override + Future setItem({required String key, required dynamic value}) async { + var encodedValue = json.encode(value); + await controller?.evaluateJavascript(source: """ + window.$webStorageType.setItem("$key", ${value is String ? encodedValue : "JSON.stringify($encodedValue)"}); + """); + } + + @override + Future getItem({required String key}) async { + var itemValue = await controller?.evaluateJavascript(source: """ + window.$webStorageType.getItem("$key"); + """); + + if (itemValue == null) { + return null; + } + + try { + return json.decode(itemValue); + } catch (e) {} + + return itemValue; + } + + @override + Future removeItem({required String key}) async { + await controller?.evaluateJavascript(source: """ + window.$webStorageType.removeItem("$key"); + """); + } + + @override + Future> getItems() async { + var webStorageItems = []; + + List>? items = + (await controller?.evaluateJavascript(source: """ +(function() { + var webStorageItems = []; + for(var i = 0; i < window.$webStorageType.length; i++){ + var key = window.$webStorageType.key(i); + webStorageItems.push( + { + key: key, + value: window.$webStorageType.getItem(key) + } + ); + } + return webStorageItems; +})(); + """))?.cast>(); + + if (items == null) { + return webStorageItems; + } + + for (var item in items) { + webStorageItems + .add(WebStorageItem(key: item["key"], value: item["value"])); + } + + return webStorageItems; + } + + @override + Future clear() async { + await controller?.evaluateJavascript(source: """ + window.$webStorageType.clear(); + """); + } + + @override + Future key({required int index}) async { + var result = await controller?.evaluateJavascript(source: """ + window.$webStorageType.key($index); + """); + return result != null ? json.decode(result) : null; + } + + @override + void dispose() { + controller = null; + } +} + +/// Object specifying creation parameters for creating a [WindowsLocalStorage]. +/// +/// When adding additional fields make sure they can be null or have a default +/// value to avoid breaking changes. See [PlatformLocalStorageCreationParams] for +/// more information. +class WindowsLocalStorageCreationParams + extends PlatformLocalStorageCreationParams { + /// Creates a new [WindowsLocalStorageCreationParams] instance. + WindowsLocalStorageCreationParams(super.params); + + /// Creates a [WindowsLocalStorageCreationParams] instance based on [PlatformLocalStorageCreationParams]. + factory WindowsLocalStorageCreationParams.fromPlatformLocalStorageCreationParams( + // Recommended placeholder to prevent being broken by platform interface. + // ignore: avoid_unused_constructor_parameters + PlatformLocalStorageCreationParams params) { + return WindowsLocalStorageCreationParams(params); + } +} + +///{@macro flutter_inappwebview_platform_interface.PlatformLocalStorage} +class WindowsLocalStorage extends PlatformLocalStorage with WindowsStorage { + /// Constructs a [WindowsLocalStorage]. + WindowsLocalStorage(PlatformLocalStorageCreationParams params) + : super.implementation( + params is WindowsLocalStorageCreationParams + ? params + : WindowsLocalStorageCreationParams + .fromPlatformLocalStorageCreationParams(params), + ); + + /// Default storage + factory WindowsLocalStorage.defaultStorage( + {required PlatformInAppWebViewController? controller}) { + return WindowsLocalStorage(WindowsLocalStorageCreationParams( + PlatformLocalStorageCreationParams(PlatformStorageCreationParams( + controller: controller, + webStorageType: WebStorageType.LOCAL_STORAGE)))); + } + + @override + WindowsInAppWebViewController? get controller => + params.controller as WindowsInAppWebViewController?; +} + +/// Object specifying creation parameters for creating a [WindowsSessionStorage]. +/// +/// When adding additional fields make sure they can be null or have a default +/// value to avoid breaking changes. See [PlatformSessionStorageCreationParams] for +/// more information. +class WindowsSessionStorageCreationParams + extends PlatformSessionStorageCreationParams { + /// Creates a new [WindowsSessionStorageCreationParams] instance. + WindowsSessionStorageCreationParams(super.params); + + /// Creates a [WindowsSessionStorageCreationParams] instance based on [PlatformSessionStorageCreationParams]. + factory WindowsSessionStorageCreationParams.fromPlatformSessionStorageCreationParams( + // Recommended placeholder to prevent being broken by platform interface. + // ignore: avoid_unused_constructor_parameters + PlatformSessionStorageCreationParams params) { + return WindowsSessionStorageCreationParams(params); + } +} + +///{@macro flutter_inappwebview_platform_interface.PlatformSessionStorage} +class WindowsSessionStorage extends PlatformSessionStorage with WindowsStorage { + /// Constructs a [WindowsSessionStorage]. + WindowsSessionStorage(PlatformSessionStorageCreationParams params) + : super.implementation( + params is WindowsSessionStorageCreationParams + ? params + : WindowsSessionStorageCreationParams + .fromPlatformSessionStorageCreationParams(params), + ); + + /// Default storage + factory WindowsSessionStorage.defaultStorage( + {required PlatformInAppWebViewController? controller}) { + return WindowsSessionStorage(WindowsSessionStorageCreationParams( + PlatformSessionStorageCreationParams(PlatformStorageCreationParams( + controller: controller, + webStorageType: WebStorageType.SESSION_STORAGE)))); + } + + @override + WindowsInAppWebViewController? get controller => + params.controller as WindowsInAppWebViewController?; +} diff --git a/flutter_inappwebview_windows/lib/src/web_storage/web_storage_manager.dart b/flutter_inappwebview_windows/lib/src/web_storage/web_storage_manager.dart new file mode 100644 index 00000000..cd9c24e6 --- /dev/null +++ b/flutter_inappwebview_windows/lib/src/web_storage/web_storage_manager.dart @@ -0,0 +1,134 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; + +/// Object specifying creation parameters for creating a [WindowsWebStorageManager]. +/// +/// When adding additional fields make sure they can be null or have a default +/// value to avoid breaking changes. See [PlatformWebStorageManagerCreationParams] for +/// more information. +@immutable +class WindowsWebStorageManagerCreationParams + extends PlatformWebStorageManagerCreationParams { + /// Creates a new [WindowsWebStorageManagerCreationParams] instance. + const WindowsWebStorageManagerCreationParams( + // This parameter prevents breaking changes later. + // ignore: avoid_unused_constructor_parameters + PlatformWebStorageManagerCreationParams params, + ) : super(); + + /// Creates a [WindowsWebStorageManagerCreationParams] instance based on [PlatformWebStorageManagerCreationParams]. + factory WindowsWebStorageManagerCreationParams.fromPlatformWebStorageManagerCreationParams( + PlatformWebStorageManagerCreationParams params) { + return WindowsWebStorageManagerCreationParams(params); + } +} + +///{@macro flutter_inappwebview_platform_interface.PlatformWebStorageManager} +class WindowsWebStorageManager extends PlatformWebStorageManager + with ChannelController { + /// Creates a new [WindowsWebStorageManager]. + WindowsWebStorageManager(PlatformWebStorageManagerCreationParams params) + : super.implementation( + params is WindowsWebStorageManagerCreationParams + ? params + : WindowsWebStorageManagerCreationParams + .fromPlatformWebStorageManagerCreationParams(params), + ) { + channel = const MethodChannel( + 'com.pichillilorenzo/flutter_inappwebview_webstoragemanager'); + handler = handleMethod; + initMethodCallHandler(); + } + + static WindowsWebStorageManager? _instance; + + ///Gets the WebStorage manager shared instance. + static WindowsWebStorageManager instance() { + return (_instance != null) ? _instance! : _init(); + } + + static WindowsWebStorageManager _init() { + _instance = WindowsWebStorageManager(WindowsWebStorageManagerCreationParams( + const PlatformWebStorageManagerCreationParams())); + return _instance!; + } + + Future _handleMethod(MethodCall call) async {} + + @override + Future> fetchDataRecords( + {required Set dataTypes}) async { + List recordList = []; + List dataTypesList = []; + for (var dataType in dataTypes) { + dataTypesList.add(dataType.toNativeValue()); + } + Map args = {}; + args.putIfAbsent("dataTypes", () => dataTypesList); + List> records = + (await channel?.invokeMethod('fetchDataRecords', args)) + ?.cast>() ?? + []; + for (var record in records) { + List dataTypesString = record["dataTypes"].cast(); + Set dataTypes = Set(); + for (var dataTypeValue in dataTypesString) { + var dataType = WebsiteDataType.fromNativeValue(dataTypeValue); + if (dataType != null) { + dataTypes.add(dataType); + } + } + recordList.add(WebsiteDataRecord( + displayName: record["displayName"], dataTypes: dataTypes)); + } + return recordList; + } + + @override + Future removeDataFor( + {required Set dataTypes, + required List dataRecords}) async { + List dataTypesList = []; + for (var dataType in dataTypes) { + dataTypesList.add(dataType.toNativeValue()); + } + + List> recordList = []; + for (var record in dataRecords) { + recordList.add(record.toMap()); + } + + Map args = {}; + args.putIfAbsent("dataTypes", () => dataTypesList); + args.putIfAbsent("recordList", () => recordList); + await channel?.invokeMethod('removeDataFor', args); + } + + @override + Future removeDataModifiedSince( + {required Set dataTypes, required DateTime date}) async { + List dataTypesList = []; + for (var dataType in dataTypes) { + dataTypesList.add(dataType.toNativeValue()); + } + + var timestamp = date.millisecondsSinceEpoch; + + Map args = {}; + args.putIfAbsent("dataTypes", () => dataTypesList); + args.putIfAbsent("timestamp", () => timestamp); + await channel?.invokeMethod('removeDataModifiedSince', args); + } + + @override + void dispose() { + // empty + } +} + +extension InternalWebStorageManager on WindowsWebStorageManager { + get handleMethod => _handleMethod; +} diff --git a/flutter_inappwebview_windows/lib/src/webview_environment/main.dart b/flutter_inappwebview_windows/lib/src/webview_environment/main.dart new file mode 100644 index 00000000..64d0520b --- /dev/null +++ b/flutter_inappwebview_windows/lib/src/webview_environment/main.dart @@ -0,0 +1 @@ +export 'webview_environment.dart' hide InternalWindowsWebViewEnvironment; \ No newline at end of file diff --git a/flutter_inappwebview_windows/lib/src/webview_environment/webview_environment.dart b/flutter_inappwebview_windows/lib/src/webview_environment/webview_environment.dart new file mode 100644 index 00000000..c2020b5c --- /dev/null +++ b/flutter_inappwebview_windows/lib/src/webview_environment/webview_environment.dart @@ -0,0 +1,121 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; + +/// Object specifying creation parameters for creating a [WindowsWebViewEnvironment]. +/// +/// Platform specific implementations can add additional fields by extending +/// this class. +@immutable +class WindowsWebViewEnvironmentCreationParams extends PlatformWebViewEnvironmentCreationParams { + /// Creates a new [WindowsInAppWebViewControllerCreationParams] instance. + const WindowsWebViewEnvironmentCreationParams({super.settings}); + + /// Creates a [WindowsInAppWebViewControllerCreationParams] instance based on [PlatformInAppWebViewControllerCreationParams]. + factory WindowsWebViewEnvironmentCreationParams.fromPlatformWebViewEnvironmentCreationParams( + // Recommended placeholder to prevent being broken by platform interface. + // ignore: avoid_unused_constructor_parameters + PlatformWebViewEnvironmentCreationParams params) { + return WindowsWebViewEnvironmentCreationParams( + settings: params.settings + ); + } +} + +///Controls a WebView Environment used by WebView instances. +/// +///**Officially Supported Platforms/Implementations**: +///- Windows +class WindowsWebViewEnvironment extends PlatformWebViewEnvironment + with ChannelController { + static final MethodChannel _staticChannel = MethodChannel('com.pichillilorenzo/flutter_webview_environment'); + + @override + final String id = IdGenerator.generate(); + + WindowsWebViewEnvironment( + PlatformWebViewEnvironmentCreationParams params) + : super.implementation(params is WindowsWebViewEnvironmentCreationParams + ? params + : WindowsWebViewEnvironmentCreationParams + .fromPlatformWebViewEnvironmentCreationParams(params)); + + static final WindowsWebViewEnvironment _staticValue = + WindowsWebViewEnvironment( + WindowsWebViewEnvironmentCreationParams()); + + factory WindowsWebViewEnvironment.static() { + return _staticValue; + } + + _debugLog(String method, dynamic args) { + debugLog( + className: this.runtimeType.toString(), + id: id, + debugLoggingSettings: + PlatformWebViewEnvironment.debugLoggingSettings, + method: method, + args: args); + } + + Future _handleMethod(MethodCall call) async { + if (PlatformWebViewEnvironment.debugLoggingSettings.enabled) { + _debugLog(call.method, call.arguments); + } + + switch (call.method) { + default: + throw UnimplementedError("Unimplemented ${call.method} method"); + } + return null; + } + + @override + Future create( + {WebViewEnvironmentSettings? settings}) async { + final env = WindowsWebViewEnvironment( + WindowsWebViewEnvironmentCreationParams(settings: settings) + ); + + Map args = {}; + args.putIfAbsent('id', () => env.id); + args.putIfAbsent('settings', () => env.settings?.toMap()); + await _staticChannel.invokeMethod( + 'create', args); + + env.channel = MethodChannel('com.pichillilorenzo/flutter_webview_environment_$id'); + env.handler = env.handleMethod; + env.initMethodCallHandler(); + return env; + } + + @override + Future getAvailableVersion( + {String? browserExecutableFolder}) async { + Map args = {}; + args.putIfAbsent('browserExecutableFolder', () => browserExecutableFolder); + return await _staticChannel.invokeMethod( + 'getAvailableVersion', args); + } + + @override + Future compareBrowserVersions( + {required String version1, required String version2}) async { + Map args = {}; + args.putIfAbsent('version1', () => version1); + args.putIfAbsent('version2', () => version2); + return await _staticChannel.invokeMethod( + 'compareBrowserVersions', args); + } + + @override + Future dispose() async { + Map args = {}; + await channel?.invokeMethod('dispose', args); + disposeChannel(); + } +} + +extension InternalWindowsWebViewEnvironment on WindowsWebViewEnvironment { + get handleMethod => _handleMethod; +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/pubspec.yaml b/flutter_inappwebview_windows/pubspec.yaml new file mode 100644 index 00000000..6741c0bb --- /dev/null +++ b/flutter_inappwebview_windows/pubspec.yaml @@ -0,0 +1,79 @@ +name: flutter_inappwebview_windows +description: Windows implementation of the flutter_inappwebview plugin. +version: 1.0.11 +homepage: https://inappwebview.dev/ +repository: https://github.com/pichillilorenzo/flutter_inappwebview/tree/master/flutter_inappwebview_windows +issue_tracker: https://github.com/pichillilorenzo/flutter_inappwebview/issues +topics: + - html + - webview + - webview-flutter + - inappwebview + - browser + +environment: + sdk: ">=2.17.0 <4.0.0" + flutter: ">=3.0.0" + +dependencies: + flutter: + sdk: flutter + flutter_inappwebview_platform_interface: #^1.0.10 + path: ../flutter_inappwebview_platform_interface + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + # This section identifies this Flutter project as a plugin project. + # The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.) + # which should be registered in the plugin registry. This is required for + # using method channels. + # The Android 'package' specifies package in which the registered class is. + # This is required for using method channels on Android. + # The 'ffiPlugin' specifies that native code should be built and bundled. + # This is required for using `dart:ffi`. + # All these are used by the tooling to maintain consistency when + # adding or updating assets for this project. + plugin: + platforms: + windows: + pluginClass: FlutterInappwebviewWindowsPluginCApi + dartPluginClass: WindowsInAppWebViewPlatform + + # To add assets to your plugin package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/assets-and-images/#from-packages + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # To add custom fonts to your plugin package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.dev/custom-fonts/#from-packages diff --git a/flutter_inappwebview_windows/test/flutter_inappwebview_windows_test.dart b/flutter_inappwebview_windows/test/flutter_inappwebview_windows_test.dart new file mode 100644 index 00000000..e69de29b diff --git a/flutter_inappwebview_windows/windows/.gitignore b/flutter_inappwebview_windows/windows/.gitignore new file mode 100644 index 00000000..d87ced66 --- /dev/null +++ b/flutter_inappwebview_windows/windows/.gitignore @@ -0,0 +1,19 @@ +flutter/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +packages/ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/CMakeLists.txt b/flutter_inappwebview_windows/windows/CMakeLists.txt new file mode 100644 index 00000000..9b6da7af --- /dev/null +++ b/flutter_inappwebview_windows/windows/CMakeLists.txt @@ -0,0 +1,233 @@ +# The Flutter tooling requires that developers have a version of Visual Studio +# installed that includes CMake 3.14 or later. You should not increase this +# version, as doing so will cause the plugin to fail to compile for some +# customers of the plugin. +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})") + +# Project-level configuration. +set(PROJECT_NAME "flutter_inappwebview_windows") +project(${PROJECT_NAME} LANGUAGES CXX) + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# This value is used when generating builds using this plugin, so it must +# not be changed +set(PLUGIN_NAME "flutter_inappwebview_windows_plugin") + +find_program(NUGET nuget) +if(NOT NUGET) + message(NOTICE "Nuget is not installed.") +endif() + +add_custom_target(${PROJECT_NAME}_DEPENDENCIES_DOWNLOAD ALL) +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} +) + +# Any new source files that you add to the plugin should be added here. +list(APPEND PLUGIN_SOURCES + "flutter_inappwebview_windows_plugin.cpp" + "flutter_inappwebview_windows_plugin.h" + "utils/log.h" + "utils/strconv.h" + "utils/map.h" + "utils/vector.h" + "utils/string.h" + "utils/util.h" + "utils/flutter.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" + "types/web_resource_error.cpp" + "types/web_resource_error.h" + "types/web_resource_request.cpp" + "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" + "types/content_world.cpp" + "types/content_world.h" + "types/user_script.cpp" + "types/user_script.h" + "types/plugin_script.cpp" + "types/plugin_script.h" + "types/size_2d.cpp" + "types/size_2d.h" + "types/rect.cpp" + "types/rect.h" + "types/callbacks_complete.h" + "types/screenshot_configuration.cpp" + "types/screenshot_configuration.h" + "custom_platform_view/custom_platform_view.cc" + "custom_platform_view/custom_platform_view.h" + "custom_platform_view/texture_bridge.cc" + "custom_platform_view/texture_bridge.h" + "custom_platform_view/graphics_context.cc" + "custom_platform_view/graphics_context.h" + "custom_platform_view/util/direct3d11.interop.cc" + "custom_platform_view/util/direct3d11.interop.h" + "custom_platform_view/util/rohelper.cc" + "custom_platform_view/util/rohelper.h" + "custom_platform_view/util/string_converter.cc" + "custom_platform_view/util/string_converter.h" + "custom_platform_view/util/swizzle.h" + "plugin_scripts_js/plugin_scripts_util.h" + "plugin_scripts_js/javascript_bridge_js.cpp" + "plugin_scripts_js/javascript_bridge_js.h" + "webview_environment/webview_environment_settings.cpp" + "webview_environment/webview_environment_settings.h" + "webview_environment/webview_environment.cpp" + "webview_environment/webview_environment.h" + "webview_environment/webview_environment_manager.cpp" + "webview_environment/webview_environment_manager.h" + "webview_environment/webview_environment_channel_delegate.cpp" + "webview_environment/webview_environment_channel_delegate.h" + "in_app_webview/user_content_controller.cpp" + "in_app_webview/user_content_controller.h" + "in_app_webview/in_app_webview_settings.cpp" + "in_app_webview/in_app_webview_settings.h" + "in_app_webview/in_app_webview.cpp" + "in_app_webview/in_app_webview.h" + "in_app_webview/in_app_webview_manager.cpp" + "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" + "in_app_browser/in_app_browser_manager.h" + "in_app_browser/in_app_browser.cpp" + "in_app_browser/in_app_browser.h" + "in_app_browser/in_app_browser_channel_delegate.cpp" + "in_app_browser/in_app_browser_channel_delegate.h" + "cookie_manager.cpp" + "cookie_manager.h" +) + +# Define the plugin library target. Its name must not be changed (see comment +# on PLUGIN_NAME above). +add_library(${PLUGIN_NAME} SHARED + "include/flutter_inappwebview_windows/flutter_inappwebview_windows_plugin_c_api.h" + "flutter_inappwebview_windows_plugin_c_api.cpp" + ${PLUGIN_SOURCES} +) + +if(NOT FLUTTER_WEBVIEW_WINDOWS_USE_TEXTURE_FALLBACK) + message(STATUS "Building with D3D texture support.") + target_compile_definitions("${PLUGIN_NAME}" PRIVATE + HAVE_FLUTTER_D3D_TEXTURE + ) + target_sources("${PLUGIN_NAME}" PRIVATE + "custom_platform_view/texture_bridge_gpu.cc" + "custom_platform_view/texture_bridge_gpu.h" + ) +else() + message(STATUS "Building with fallback PixelBuffer texture.") + target_sources("${PLUGIN_NAME}" PRIVATE + "custom_platform_view/texture_bridge_fallback.cc" + "custom_platform_view/texture_bridge_fallback.h" + "custom_platform_view/util/cpuid/cpuinfo.cc" + "custom_platform_view/util/cpuid/cpuinfo.h" + ) + # Enable AVX2 for pixel buffer conversions + if(MSVC) + target_compile_options(${PLUGIN_NAME} PRIVATE "/arch:AVX2") + endif() +endif() + +# Apply a standard set of build settings that are configured in the +# application-level CMakeLists.txt. This can be removed for plugins that want +# full control over build settings. +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 +# exported should be explicitly exported with the FLUTTER_PLUGIN_EXPORT macro. +set_target_properties(${PLUGIN_NAME} PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL) + +# Source include directories and library dependencies. Add any plugin-specific +# dependencies here. +target_include_directories(${PLUGIN_NAME} INTERFACE + "${CMAKE_CURRENT_SOURCE_DIR}/include") +target_link_libraries(${PLUGIN_NAME} PRIVATE flutter flutter_wrapper_plugin) + +# List of absolute paths to libraries that should be bundled with the plugin. +# This list could contain prebuilt libraries, or libraries created by an +# external build triggered from this build file. +set(flutter_inappwebview_windows_bundled_libraries + PARENT_SCOPE +) + +# === Tests === +# These unit tests can be run from a terminal after building the example, or +# from Visual Studio after opening the generated solution file. + +# Only enable test builds when building the example (which sets this variable) +# so that plugin clients aren't building the tests. +if (${include_${PROJECT_NAME}_tests}) +set(TEST_RUNNER "${PROJECT_NAME}_test") +enable_testing() + +# Add the Google Test dependency. +include(FetchContent) +FetchContent_Declare( + googletest + URL https://github.com/google/googletest/archive/release-1.11.0.zip +) +# Prevent overriding the parent project's compiler/linker settings +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +# Disable install commands for gtest so it doesn't end up in the bundle. +set(INSTALL_GTEST OFF CACHE BOOL "Disable installation of googletest" FORCE) +FetchContent_MakeAvailable(googletest) + +# The plugin's C API is not very useful for unit testing, so build the sources +# directly into the test binary rather than using the DLL. +add_executable(${TEST_RUNNER} + test/flutter_inappwebview_windows_plugin_test.cpp + ${PLUGIN_SOURCES} +) +apply_standard_settings(${TEST_RUNNER}) +target_include_directories(${TEST_RUNNER} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}") +target_link_libraries(${TEST_RUNNER} PRIVATE flutter_wrapper_plugin) +target_link_libraries(${TEST_RUNNER} PRIVATE gtest_main gmock) +# flutter_wrapper_plugin has link dependencies on the Flutter DLL. +add_custom_command(TARGET ${TEST_RUNNER} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${FLUTTER_LIBRARY}" $ +) + +# Enable automatic test discovery. +include(GoogleTest) +gtest_discover_tests(${TEST_RUNNER}) +endif() diff --git a/flutter_inappwebview_windows/windows/cookie_manager.cpp b/flutter_inappwebview_windows/windows/cookie_manager.cpp new file mode 100644 index 00000000..2fa0eb5d --- /dev/null +++ b/flutter_inappwebview_windows/windows/cookie_manager.cpp @@ -0,0 +1,349 @@ +#include +#include +#include +#include +#include + +#include "cookie_manager.h" +#include "types/callbacks_complete.h" +#include "utils/flutter.h" +#include "utils/log.h" + +namespace flutter_inappwebview_plugin +{ + using namespace Microsoft::WRL; + + CookieManager::CookieManager(const FlutterInappwebviewWindowsPlugin* plugin) + : plugin(plugin), ChannelDelegate(plugin->registrar->messenger(), CookieManager::METHOD_CHANNEL_NAME_PREFIX) + {} + + void CookieManager::HandleMethodCall(const flutter::MethodCall& method_call, + std::unique_ptr> result) + { + auto& arguments = std::get(*method_call.arguments()); + auto& methodName = method_call.method_name(); + + auto webViewEnvironmentId = get_optional_fl_map_value(arguments, "webViewEnvironmentId"); + + auto webViewEnvironment = webViewEnvironmentId.has_value() && map_contains(plugin->webViewEnvironmentManager->webViewEnvironments, webViewEnvironmentId.value()) + ? plugin->webViewEnvironmentManager->webViewEnvironments.at(webViewEnvironmentId.value()).get() : nullptr; + + auto result_ = std::shared_ptr>(std::move(result)); + auto callback = [this, result_, methodName, arguments](WebViewEnvironment* webViewEnvironment) + { + if (!webViewEnvironment) { + result_->Error("0", "Cannot obtain the WebViewEnvironment!"); + return; + } + + if (string_equals(methodName, "setCookie")) { + setCookie(webViewEnvironment, arguments, [result_](const bool& created) + { + result_->Success(created); + }); + } + else if (string_equals(methodName, "getCookie")) { + auto url = get_fl_map_value(arguments, "url"); + auto name = get_fl_map_value(arguments, "name"); + getCookie(webViewEnvironment, url, name, [result_](const flutter::EncodableValue& cookie) + { + result_->Success(cookie); + }); + } + else if (string_equals(methodName, "getCookies")) { + auto url = get_fl_map_value(arguments, "url"); + getCookies(webViewEnvironment, url, [result_](const flutter::EncodableList& cookies) + { + result_->Success(cookies); + }); + } + else if (string_equals(methodName, "deleteCookie")) { + auto url = get_fl_map_value(arguments, "url"); + auto name = get_fl_map_value(arguments, "name"); + auto path = get_fl_map_value(arguments, "path"); + auto domain = get_optional_fl_map_value(arguments, "domain"); + deleteCookie(webViewEnvironment, url, name, path, domain, [result_](const bool& deleted) + { + result_->Success(deleted); + }); + } + else if (string_equals(methodName, "deleteCookies")) { + auto url = get_fl_map_value(arguments, "url"); + auto path = get_fl_map_value(arguments, "path"); + auto domain = get_optional_fl_map_value(arguments, "domain"); + deleteCookies(webViewEnvironment, url, path, domain, [result_](const bool& deleted) + { + result_->Success(deleted); + }); + } + else if (string_equals(methodName, "deleteAllCookies")) { + deleteAllCookies(webViewEnvironment, [result_](const bool& deleted) + { + result_->Success(deleted); + }); + } + else { + result_->NotImplemented(); + } + }; + + if (webViewEnvironment) { + callback(webViewEnvironment); + } + else { + plugin->webViewEnvironmentManager->createOrGetDefaultWebViewEnvironment([callback](WebViewEnvironment* webViewEnvironment) + { + callback(webViewEnvironment); + }); + } + } + + void CookieManager::setCookie(WebViewEnvironment* webViewEnvironment, const flutter::EncodableMap& map, std::function completionHandler) const + { + if (!plugin || !plugin->webViewEnvironmentManager) { + if (completionHandler) { + completionHandler(false); + } + return; + } + + auto url = get_fl_map_value(map, "url"); + auto name = get_fl_map_value(map, "name"); + auto value = get_fl_map_value(map, "value"); + auto path = get_fl_map_value(map, "path"); + auto domain = get_optional_fl_map_value(map, "domain"); + auto expiresDate = get_optional_fl_map_value(map, "expiresDate"); + auto maxAge = get_optional_fl_map_value(map, "maxAge"); + auto isSecure = get_optional_fl_map_value(map, "isSecure"); + auto isHttpOnly = get_optional_fl_map_value(map, "isHttpOnly"); + auto sameSite = get_optional_fl_map_value(map, "sameSite"); + + nlohmann::json parameters = { + {"url", url}, + {"name", name}, + {"value", value}, + {"path", path} + }; + if (domain.has_value()) { + parameters["domain"] = domain.value(); + } + if (expiresDate.has_value()) { + parameters["expires"] = expiresDate.value() / 1000; + } + if (maxAge.has_value()) { + // time(NULL) represents the current unix timestamp in seconds + parameters["expires"] = time(NULL) + maxAge.value(); + } + if (isSecure.has_value()) { + parameters["secure"] = isSecure.value(); + } + if (isHttpOnly.has_value()) { + parameters["httpOnly"] = isHttpOnly.value(); + } + if (sameSite.has_value()) { + parameters["sameSite"] = sameSite.value(); + } + + auto hr = webViewEnvironment->getWebView()->CallDevToolsProtocolMethod(L"Network.setCookie", utf8_to_wide(parameters.dump()).c_str(), Callback( + [completionHandler](HRESULT errorCode, LPCWSTR returnObjectAsJson) + { + if (completionHandler) { + completionHandler(succeededOrLog(errorCode)); + } + return S_OK; + } + ).Get()); + + if (failedAndLog(hr) && completionHandler) { + completionHandler(false); + } + } + + void CookieManager::getCookie(WebViewEnvironment* webViewEnvironment, const std::string& url, const std::string& name, std::function completionHandler) const + { + if (!plugin || !plugin->webViewEnvironmentManager) { + if (completionHandler) { + completionHandler(make_fl_value()); + } + return; + } + + nlohmann::json parameters = { + {"urls", std::vector{url}} + }; + + auto hr = webViewEnvironment->getWebView()->CallDevToolsProtocolMethod(L"Network.getCookies", utf8_to_wide(parameters.dump()).c_str(), Callback( + [completionHandler, name](HRESULT errorCode, LPCWSTR returnObjectAsJson) + { + if (succeededOrLog(errorCode)) { + nlohmann::json json = nlohmann::json::parse(wide_to_utf8(returnObjectAsJson)); + auto jsonCookies = json["cookies"].get>(); + for (auto& jsonCookie : jsonCookies) { + auto cookieName = jsonCookie["name"].get(); + if (string_equals(name, cookieName)) { + completionHandler(flutter::EncodableMap{ + {"name", cookieName}, + {"value", jsonCookie["value"].get()}, + {"domain", jsonCookie["domain"].get()}, + {"path", jsonCookie["path"].get()}, + {"expiresDate", jsonCookie["expires"].get()}, + {"isHttpOnly", jsonCookie["httpOnly"].get()}, + {"isSecure", jsonCookie["secure"].get()}, + {"isSessionOnly", jsonCookie["session"].get()}, + {"sameSite", jsonCookie.contains("sameSite") ? jsonCookie["sameSite"].get() : make_fl_value()} + }); + return S_OK; + } + } + } + if (completionHandler) { + completionHandler(make_fl_value()); + } + return S_OK; + } + ).Get()); + + if (failedAndLog(hr) && completionHandler) { + completionHandler(make_fl_value()); + } + } + + void CookieManager::getCookies(WebViewEnvironment* webViewEnvironment, const std::string& url, std::function completionHandler) const + { + if (!plugin || !plugin->webViewEnvironmentManager) { + if (completionHandler) { + completionHandler({}); + } + return; + } + + nlohmann::json parameters = { + {"urls", std::vector{url}} + }; + + auto hr = webViewEnvironment->getWebView()->CallDevToolsProtocolMethod(L"Network.getCookies", utf8_to_wide(parameters.dump()).c_str(), Callback( + [completionHandler](HRESULT errorCode, LPCWSTR returnObjectAsJson) + { + std::vector cookies = {}; + if (succeededOrLog(errorCode)) { + nlohmann::json json = nlohmann::json::parse(wide_to_utf8(returnObjectAsJson)); + auto jsonCookies = json["cookies"].get>(); + for (auto& jsonCookie : jsonCookies) { + cookies.push_back(flutter::EncodableMap{ + {"name", jsonCookie["name"].get()}, + {"value", jsonCookie["value"].get()}, + {"domain", jsonCookie["domain"].get()}, + {"path", jsonCookie["path"].get()}, + {"expiresDate", jsonCookie["expires"].get()}, + {"isHttpOnly", jsonCookie["httpOnly"].get()}, + {"isSecure", jsonCookie["secure"].get()}, + {"isSessionOnly", jsonCookie["session"].get()}, + {"sameSite", jsonCookie.contains("sameSite") ? jsonCookie["sameSite"].get() : make_fl_value()} + }); + } + } + if (completionHandler) { + completionHandler(cookies); + } + return S_OK; + } + ).Get()); + + if (failedAndLog(hr) && completionHandler) { + completionHandler({}); + } + } + + void CookieManager::deleteCookie(WebViewEnvironment* webViewEnvironment, const std::string& url, const std::string& name, const std::string& path, const std::optional& domain, std::function completionHandler) const + { + if (!plugin || !plugin->webViewEnvironmentManager) { + if (completionHandler) { + completionHandler(false); + } + return; + } + + nlohmann::json parameters = { + {"url", url}, + {"name", name}, + {"path", path} + }; + if (domain.has_value()) { + parameters["domain"] = domain.value(); + } + + auto hr = webViewEnvironment->getWebView()->CallDevToolsProtocolMethod(L"Network.deleteCookies", utf8_to_wide(parameters.dump()).c_str(), Callback( + [completionHandler](HRESULT errorCode, LPCWSTR returnObjectAsJson) + { + if (completionHandler) { + completionHandler(succeededOrLog(errorCode)); + } + return S_OK; + } + ).Get()); + + if (failedAndLog(hr) && completionHandler) { + completionHandler(false); + } + } + + void CookieManager::deleteCookies(WebViewEnvironment* webViewEnvironment, const std::string& url, const std::string& path, const std::optional& domain, std::function completionHandler) const + { + if (!plugin || !plugin->webViewEnvironmentManager) { + if (completionHandler) { + completionHandler(false); + } + return; + } + + getCookies(webViewEnvironment, url, [this, webViewEnvironment, url, path, domain, completionHandler](const flutter::EncodableList& cookies) + { + auto callbacksComplete = std::make_shared>( + [completionHandler](const std::vector& values) + { + if (completionHandler) { + completionHandler(true); + } + }); + + for (auto& cookie : cookies) { + auto cookieMap = std::get(cookie); + auto name = get_fl_map_value(cookieMap, "name"); + deleteCookie(webViewEnvironment, url, name, path, domain, [callbacksComplete](const bool& deleted) + { + callbacksComplete->addValue(deleted); + }); + } + }); + } + + void CookieManager::deleteAllCookies(WebViewEnvironment* webViewEnvironment, std::function completionHandler) const + { + if (!plugin || !plugin->webViewEnvironmentManager) { + if (completionHandler) { + completionHandler(false); + } + return; + } + + auto hr = webViewEnvironment->getWebView()->CallDevToolsProtocolMethod(L"Network.clearBrowserCookies", L"{}", Callback( + [completionHandler](HRESULT errorCode, LPCWSTR returnObjectAsJson) + { + if (completionHandler) { + completionHandler(succeededOrLog(errorCode)); + } + return S_OK; + } + ).Get()); + + if (failedAndLog(hr) && completionHandler) { + completionHandler(false); + } + } + + CookieManager::~CookieManager() + { + debugLog("dealloc CookieManager"); + plugin = nullptr; + } +} diff --git a/flutter_inappwebview_windows/windows/cookie_manager.h b/flutter_inappwebview_windows/windows/cookie_manager.h new file mode 100644 index 00000000..313fd8a2 --- /dev/null +++ b/flutter_inappwebview_windows/windows/cookie_manager.h @@ -0,0 +1,38 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_COOKIE_MANAGER_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_COOKIE_MANAGER_H_ + +#include +#include +#include +#include + +#include "flutter_inappwebview_windows_plugin.h" +#include "types/channel_delegate.h" +#include "webview_environment/webview_environment_manager.h" + +namespace flutter_inappwebview_plugin +{ + class CookieManager : public ChannelDelegate + { + public: + static inline const std::string METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_cookiemanager"; + + const FlutterInappwebviewWindowsPlugin* plugin; + + CookieManager(const FlutterInappwebviewWindowsPlugin* plugin); + ~CookieManager(); + + void HandleMethodCall( + const flutter::MethodCall& method_call, + std::unique_ptr> result); + + void setCookie(WebViewEnvironment* webViewEnvironment, const flutter::EncodableMap& map, std::function completionHandler) const; + void getCookie(WebViewEnvironment* webViewEnvironment, const std::string& url, const std::string& name, std::function completionHandler) const; + void getCookies(WebViewEnvironment* webViewEnvironment, const std::string& url, std::function completionHandler) const; + void deleteCookie(WebViewEnvironment* webViewEnvironment, const std::string& url, const std::string& name, const std::string& path, const std::optional& domain, std::function completionHandler) const; + void deleteCookies(WebViewEnvironment* webViewEnvironment, const std::string& url, const std::string& path, const std::optional& domain, std::function completionHandler) const; + void deleteAllCookies(WebViewEnvironment* webViewEnvironment, std::function completionHandler) const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_COOKIE_MANAGER_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/custom_platform_view.cc b/flutter_inappwebview_windows/windows/custom_platform_view/custom_platform_view.cc new file mode 100644 index 00000000..c955f87d --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/custom_platform_view.cc @@ -0,0 +1,326 @@ +#include "../utils/log.h" +#include "custom_platform_view.h" + +#include +#include + +#ifdef HAVE_FLUTTER_D3D_TEXTURE +#include "texture_bridge_gpu.h" +#else +#include "texture_bridge_fallback.h" +#endif + +namespace flutter_inappwebview_plugin +{ + constexpr auto kErrorInvalidArgs = "invalidArguments"; + + constexpr auto kMethodSetSize = "setSize"; + constexpr auto kMethodSetPosition = "setPosition"; + constexpr auto kMethodSetCursorPos = "setCursorPos"; + constexpr auto kMethodSetPointerUpdate = "setPointerUpdate"; + constexpr auto kMethodSetPointerButton = "setPointerButton"; + constexpr auto kMethodSetScrollDelta = "setScrollDelta"; + constexpr auto kMethodSetFpsLimit = "setFpsLimit"; + + constexpr auto kEventType = "type"; + constexpr auto kEventValue = "value"; + + static const std::optional> GetPointFromArgs( + const flutter::EncodableValue* args) + { + const flutter::EncodableList* list = + std::get_if(args); + if (!list || list->size() != 2) { + return std::nullopt; + } + const auto x = std::get_if(&(*list)[0]); + const auto y = std::get_if(&(*list)[1]); + if (!x || !y) { + return std::nullopt; + } + return std::make_pair(*x, *y); + } + + static const std::optional> + GetPointAndScaleFactorFromArgs(const flutter::EncodableValue* args) + { + const flutter::EncodableList* list = + std::get_if(args); + if (!list || list->size() != 3) { + return std::nullopt; + } + const auto x = std::get_if(&(*list)[0]); + const auto y = std::get_if(&(*list)[1]); + const auto z = std::get_if(&(*list)[2]); + if (!x || !y || !z) { + return std::nullopt; + } + return std::make_tuple(*x, *y, *z); + } + + static const std::string& GetCursorName(const HCURSOR cursor) + { + // The cursor names correspond to the Flutter Engine names: + // in shell/platform/windows/flutter_window_win32.cc + static const std::string kDefaultCursorName = "basic"; + static const std::pair mappings[] = { + {"allScroll", IDC_SIZEALL}, + {kDefaultCursorName, IDC_ARROW}, + {"click", IDC_HAND}, + {"forbidden", IDC_NO}, + {"help", IDC_HELP}, + {"move", IDC_SIZEALL}, + {"none", nullptr}, + {"noDrop", IDC_NO}, + {"precise", IDC_CROSS}, + {"progress", IDC_APPSTARTING}, + {"text", IDC_IBEAM}, + {"resizeColumn", IDC_SIZEWE}, + {"resizeDown", IDC_SIZENS}, + {"resizeDownLeft", IDC_SIZENESW}, + {"resizeDownRight", IDC_SIZENWSE}, + {"resizeLeft", IDC_SIZEWE}, + {"resizeLeftRight", IDC_SIZEWE}, + {"resizeRight", IDC_SIZEWE}, + {"resizeRow", IDC_SIZENS}, + {"resizeUp", IDC_SIZENS}, + {"resizeUpDown", IDC_SIZENS}, + {"resizeUpLeft", IDC_SIZENWSE}, + {"resizeUpRight", IDC_SIZENESW}, + {"resizeUpLeftDownRight", IDC_SIZENWSE}, + {"resizeUpRightDownLeft", IDC_SIZENESW}, + {"wait", IDC_WAIT}, + }; + + static std::map cursors; + static bool initialized = false; + + if (!initialized) { + initialized = true; + for (const auto& pair : mappings) { + HCURSOR cursor_handle = LoadCursor(nullptr, pair.second); + if (cursor_handle) { + cursors[cursor_handle] = pair.first; + } + } + } + + const auto it = cursors.find(cursor); + if (it != cursors.end()) { + return it->second; + } + return kDefaultCursorName; + } + + CustomPlatformView::CustomPlatformView(flutter::BinaryMessenger* messenger, + flutter::TextureRegistrar* texture_registrar, + GraphicsContext* graphics_context, + HWND hwnd, + std::unique_ptr webView) + : hwnd_(hwnd), view(std::move(webView)), texture_registrar_(texture_registrar) + { +#ifdef HAVE_FLUTTER_D3D_TEXTURE + texture_bridge_ = + std::make_unique(graphics_context, view->surface()); + + flutter_texture_ = + std::make_unique(flutter::GpuSurfaceTexture( + kFlutterDesktopGpuSurfaceTypeDxgiSharedHandle, + [bridge = static_cast(texture_bridge_.get())]( + size_t width, + size_t height) -> const FlutterDesktopGpuSurfaceDescriptor* + { + return bridge->GetSurfaceDescriptor(width, height); + })); +#else + texture_bridge_ = std::make_unique( + graphics_context, webview_->surface()); + + flutter_texture_ = + std::make_unique(flutter::PixelBufferTexture( + [bridge = static_cast(texture_bridge_.get())]( + size_t width, size_t height) -> const FlutterDesktopPixelBuffer* + { + return bridge->CopyPixelBuffer(width, height); + })); +#endif + + texture_id_ = texture_registrar->RegisterTexture(flutter_texture_.get()); + texture_bridge_->SetOnFrameAvailable( + [this]() { texture_registrar_->MarkTextureFrameAvailable(texture_id_); }); + // texture_bridge_->SetOnSurfaceSizeChanged([this](Size size) { + // view->SetSurfaceSize(size.width, size.height); + //}); + + const auto method_channel_name = "com.pichillilorenzo/custom_platform_view_" + std::to_string(texture_id_); + method_channel_ = + std::make_unique>( + messenger, method_channel_name, + &flutter::StandardMethodCodec::GetInstance()); + method_channel_->SetMethodCallHandler([this](const auto& call, auto result) + { + HandleMethodCall(call, std::move(result)); + }); + + const auto event_channel_name = "com.pichillilorenzo/custom_platform_view_" + std::to_string(texture_id_) + "_events"; + event_channel_ = + std::make_unique>( + messenger, event_channel_name, + &flutter::StandardMethodCodec::GetInstance()); + + auto handler = std::make_unique< + flutter::StreamHandlerFunctions>( + [this](const flutter::EncodableValue* arguments, + std::unique_ptr>&& + events) + { + event_sink_ = std::move(events); + RegisterEventHandlers(); + return nullptr; + }, + [this](const flutter::EncodableValue* arguments) + { + event_sink_ = nullptr; + return nullptr; + }); + + event_channel_->SetStreamHandler(std::move(handler)); + } + + CustomPlatformView::~CustomPlatformView() + { + debugLog("dealloc CustomPlatformView"); + method_channel_->SetMethodCallHandler(nullptr); + texture_registrar_->UnregisterTexture(texture_id_); + } + + void CustomPlatformView::RegisterEventHandlers() + { + if (!view) { + return; + } + + view->onSurfaceSizeChanged([this](size_t width, size_t height) + { + texture_bridge_->NotifySurfaceSizeChanged(); + }); + + view->onCursorChanged([this](const HCURSOR cursor) + { + const auto& name = GetCursorName(cursor); + const auto event = flutter::EncodableValue( + flutter::EncodableMap { {flutter::EncodableValue(kEventType), + flutter::EncodableValue("cursorChanged")}, + { flutter::EncodableValue(kEventValue), name }}); + EmitEvent(event); + }); + } + + void CustomPlatformView::HandleMethodCall( + const flutter::MethodCall& method_call, + std::unique_ptr> result) + { + const auto& method_name = method_call.method_name(); + + // setCursorPos: [double x, double y] + if (method_name.compare(kMethodSetCursorPos) == 0) { + const auto point = GetPointFromArgs(method_call.arguments()); + if (point && view) { + view->setCursorPos(point->first, point->second); + return result->Success(); + } + return result->Error(kErrorInvalidArgs); + } + + // setPointerUpdate: + // [int pointer, int event, double x, double y, double size, double pressure] + if (method_name.compare(kMethodSetPointerUpdate) == 0) { + const flutter::EncodableList* list = + std::get_if(method_call.arguments()); + if (!list || list->size() != 6) { + return result->Error(kErrorInvalidArgs); + } + + const auto pointer = std::get_if(&(*list)[0]); + const auto event = std::get_if(&(*list)[1]); + const auto x = std::get_if(&(*list)[2]); + const auto y = std::get_if(&(*list)[3]); + const auto size = std::get_if(&(*list)[4]); + const auto pressure = std::get_if(&(*list)[5]); + + if (pointer && event && x && y && size && pressure && view) { + view->setPointerUpdate(*pointer, + static_cast(*event), + *x, *y, *size, *pressure); + return result->Success(); + } + return result->Error(kErrorInvalidArgs); + } + + // setScrollDelta: [double dx, double dy] + if (method_name.compare(kMethodSetScrollDelta) == 0) { + const auto delta = GetPointFromArgs(method_call.arguments()); + if (delta && view) { + view->setScrollDelta(delta->first, delta->second); + return result->Success(); + } + return result->Error(kErrorInvalidArgs); + } + + // setPointerButton: {"button": int, "isDown": bool} + if (method_name.compare(kMethodSetPointerButton) == 0) { + const auto& map = std::get(*method_call.arguments()); + + const auto button = map.find(flutter::EncodableValue("button")); + const auto isDown = map.find(flutter::EncodableValue("isDown")); + if (button != map.end() && isDown != map.end()) { + const auto buttonValue = std::get_if(&button->second); + const auto isDownValue = std::get_if(&isDown->second); + if (buttonValue && isDownValue && view) { + view->setPointerButtonState( + static_cast(*buttonValue), *isDownValue); + return result->Success(); + } + } + return result->Error(kErrorInvalidArgs); + } + + // setSize: [double width, double height, double scale_factor] + if (method_name.compare(kMethodSetSize) == 0) { + auto size = GetPointAndScaleFactorFromArgs(method_call.arguments()); + if (size && view) { + const auto [width, height, scale_factor] = size.value(); + + view->setSurfaceSize(static_cast(width), + static_cast(height), + static_cast(scale_factor)); + + texture_bridge_->Start(); + return result->Success(); + } + return result->Error(kErrorInvalidArgs); + } + else if (method_name.compare(kMethodSetPosition) == 0) { + auto position = GetPointAndScaleFactorFromArgs(method_call.arguments()); + if (position && view) { + const auto [x, y, scale_factor] = position.value(); + + view->setPosition(static_cast(x), + static_cast(y), + static_cast(scale_factor)); + + return result->Success(); + } + return result->Error(kErrorInvalidArgs); + } + else if (method_name.compare(kMethodSetFpsLimit) == 0) { + if (const auto value = std::get_if(method_call.arguments())) { + texture_bridge_->SetFpsLimit(*value == 0 ? std::nullopt + : std::make_optional(*value)); + return result->Success(); + } + } + + result->NotImplemented(); + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/custom_platform_view.h b/flutter_inappwebview_windows/windows/custom_platform_view/custom_platform_view.h new file mode 100644 index 00000000..6c4adfdb --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/custom_platform_view.h @@ -0,0 +1,58 @@ +#pragma once + +#include +#include +#include +#include + +#include + +#include "../in_app_webview/in_app_webview.h" +#include "graphics_context.h" +#include "texture_bridge.h" + +namespace flutter_inappwebview_plugin +{ + class CustomPlatformView { + public: + static inline const wchar_t* CLASS_NAME = L"CustomPlatformView"; + + const std::unique_ptr view; + + CustomPlatformView(flutter::BinaryMessenger* messenger, + flutter::TextureRegistrar* texture_registrar, + GraphicsContext* graphics_context, + HWND hwnd, + std::unique_ptr webView); + ~CustomPlatformView(); + + TextureBridge* texture_bridge() const { return texture_bridge_.get(); } + + int64_t texture_id() const { return texture_id_; } + private: + HWND hwnd_; + std::unique_ptr flutter_texture_; + std::unique_ptr texture_bridge_; + std::unique_ptr> event_sink_; + std::unique_ptr> + event_channel_; + std::unique_ptr> + method_channel_; + + flutter::TextureRegistrar* texture_registrar_; + int64_t texture_id_; + + void HandleMethodCall( + const flutter::MethodCall& method_call, + std::unique_ptr> result); + void RegisterEventHandlers(); + + template + void EmitEvent(const T& value) + { + if (event_sink_) { + event_sink_->Success(value); + } + } + }; +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/graphics_context.cc b/flutter_inappwebview_windows/windows/custom_platform_view/graphics_context.cc new file mode 100644 index 00000000..2bbc1e74 --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/graphics_context.cc @@ -0,0 +1,158 @@ +#include "graphics_context.h" + +#include "util/d3dutil.h" +#include "util/direct3d11.interop.h" + +namespace flutter_inappwebview_plugin +{ + GraphicsContext::GraphicsContext(rx::RoHelper* rohelper) : rohelper_(rohelper) + { + device_ = CreateD3DDevice(); + if (!device_) { + return; + } + + device_->GetImmediateContext(device_context_.put()); + if (FAILED(CreateDirect3D11DeviceFromDXGIDevice( + device_.try_as().get(), + (IInspectable**)device_winrt_.put()))) { + return; + } + + valid_ = true; + } + + winrt::com_ptr + GraphicsContext::CreateCompositor() + { + HSTRING className; + HSTRING_HEADER classNameHeader; + + if (FAILED(rohelper_->GetStringReference( + RuntimeClass_Windows_UI_Composition_Compositor, &className, + &classNameHeader))) { + return nullptr; + } + + winrt::com_ptr af; + if (FAILED(rohelper_->GetActivationFactory( + className, __uuidof(IActivationFactory), af.put_void()))) { + return nullptr; + } + + winrt::com_ptr compositor; + if (FAILED(af->ActivateInstance( + reinterpret_cast(compositor.put())))) { + return nullptr; + } + + return compositor; + } + + winrt::com_ptr + GraphicsContext::CreateGraphicsCaptureItemFromVisual( + ABI::Windows::UI::Composition::IVisual* visual) const + { + HSTRING className; + HSTRING_HEADER classNameHeader; + + if (FAILED(rohelper_->GetStringReference( + RuntimeClass_Windows_Graphics_Capture_GraphicsCaptureItem, &className, + &classNameHeader))) { + return nullptr; + } + + ABI::Windows::Graphics::Capture::IGraphicsCaptureItemStatics* + capture_item_statics; + if (FAILED(rohelper_->GetActivationFactory( + className, + __uuidof( + ABI::Windows::Graphics::Capture::IGraphicsCaptureItemStatics), + (void**)&capture_item_statics))) { + return nullptr; + } + + winrt::com_ptr + capture_item; + if (FAILED( + capture_item_statics->CreateFromVisual(visual, capture_item.put()))) { + return nullptr; + } + + return capture_item; + } + + winrt::com_ptr + GraphicsContext::CreateCaptureFramePool( + ABI::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice* device, + ABI::Windows::Graphics::DirectX::DirectXPixelFormat pixelFormat, + INT32 numberOfBuffers, ABI::Windows::Graphics::SizeInt32 size) const + { + HSTRING className; + HSTRING_HEADER classNameHeader; + + if (FAILED(rohelper_->GetStringReference( + RuntimeClass_Windows_Graphics_Capture_Direct3D11CaptureFramePool, + &className, &classNameHeader))) { + return nullptr; + } + + ABI::Windows::Graphics::Capture::IDirect3D11CaptureFramePoolStatics* + capture_frame_pool_statics; + if (FAILED(rohelper_->GetActivationFactory( + className, + __uuidof(ABI::Windows::Graphics::Capture:: + IDirect3D11CaptureFramePoolStatics), + (void**)&capture_frame_pool_statics))) { + return nullptr; + } + + winrt::com_ptr + capture_frame_pool; + + if (FAILED(capture_frame_pool_statics->Create(device, pixelFormat, + numberOfBuffers, size, + capture_frame_pool.put()))) { + return nullptr; + } + + return capture_frame_pool; + } + + winrt::com_ptr + GraphicsContext::CreateFreeThreadedCaptureFramePool( + ABI::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice* device, + ABI::Windows::Graphics::DirectX::DirectXPixelFormat pixelFormat, + INT32 numberOfBuffers, ABI::Windows::Graphics::SizeInt32 size) const + { + HSTRING className; + HSTRING_HEADER classNameHeader; + + if (FAILED(rohelper_->GetStringReference( + RuntimeClass_Windows_Graphics_Capture_Direct3D11CaptureFramePool, + &className, &classNameHeader))) { + return nullptr; + } + + ABI::Windows::Graphics::Capture::IDirect3D11CaptureFramePoolStatics2* + capture_frame_pool_statics; + if (FAILED(rohelper_->GetActivationFactory( + className, + __uuidof(ABI::Windows::Graphics::Capture:: + IDirect3D11CaptureFramePoolStatics2), + (void**)&capture_frame_pool_statics))) { + return nullptr; + } + + winrt::com_ptr + capture_frame_pool; + + if (FAILED(capture_frame_pool_statics->CreateFreeThreaded( + device, pixelFormat, numberOfBuffers, size, + capture_frame_pool.put()))) { + return nullptr; + } + + return capture_frame_pool; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/graphics_context.h b/flutter_inappwebview_windows/windows/custom_platform_view/graphics_context.h new file mode 100644 index 00000000..883bffe7 --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/graphics_context.h @@ -0,0 +1,54 @@ +#pragma once + +#include +#include +#include +#include + +#include "util/rohelper.h" + +namespace flutter_inappwebview_plugin +{ + class GraphicsContext { + public: + GraphicsContext(rx::RoHelper* rohelper); + + inline bool IsValid() const { return valid_; } + + ABI::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice* device() const + { + return device_winrt_.get(); + } + ID3D11Device* d3d_device() const { return device_.get(); } + ID3D11DeviceContext* d3d_device_context() const + { + return device_context_.get(); + } + + winrt::com_ptr CreateCompositor(); + + winrt::com_ptr + CreateGraphicsCaptureItemFromVisual( + ABI::Windows::UI::Composition::IVisual* visual) const; + + winrt::com_ptr + CreateCaptureFramePool( + ABI::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice* device, + ABI::Windows::Graphics::DirectX::DirectXPixelFormat pixelFormat, + INT32 numberOfBuffers, ABI::Windows::Graphics::SizeInt32 size) const; + + winrt::com_ptr + CreateFreeThreadedCaptureFramePool( + ABI::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice* device, + ABI::Windows::Graphics::DirectX::DirectXPixelFormat pixelFormat, + INT32 numberOfBuffers, ABI::Windows::Graphics::SizeInt32 size) const; + + private: + bool valid_ = false; + rx::RoHelper* rohelper_; + winrt::com_ptr + device_winrt_; + winrt::com_ptr device_{ nullptr }; + winrt::com_ptr device_context_{ nullptr }; + }; +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge.cc b/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge.cc new file mode 100644 index 00000000..658df19f --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge.cc @@ -0,0 +1,189 @@ +#include "texture_bridge.h" + +#include + +#include +#include +#include +#include + +#include "util/direct3d11.interop.h" + +namespace flutter_inappwebview_plugin +{ + const int kNumBuffers = 1; + + TextureBridge::TextureBridge(GraphicsContext* graphics_context, + ABI::Windows::UI::Composition::IVisual* visual) + : graphics_context_(graphics_context) + { + capture_item_ = + graphics_context_->CreateGraphicsCaptureItemFromVisual(visual); + assert(capture_item_); + + capture_item_->add_Closed( + Microsoft::WRL::Callback>( + [](ABI::Windows::Graphics::Capture::IGraphicsCaptureItem* item, + IInspectable* args) -> HRESULT + { + std::cerr << "Capture item was closed." << std::endl; + return S_OK; + }) + .Get(), + &on_closed_token_); + } + + TextureBridge::~TextureBridge() + { + const std::lock_guard lock(mutex_); + StopInternal(); + if (capture_item_) { + capture_item_->remove_Closed(on_closed_token_); + } + } + + bool TextureBridge::Start() + { + const std::lock_guard lock(mutex_); + if (is_running_ || !capture_item_) { + return false; + } + + ABI::Windows::Graphics::SizeInt32 size; + capture_item_->get_Size(&size); + + frame_pool_ = graphics_context_->CreateCaptureFramePool( + graphics_context_->device(), + static_cast( + kPixelFormat), + kNumBuffers, size); + assert(frame_pool_); + + frame_pool_->add_FrameArrived( + Microsoft::WRL::Callback>( + [this](ABI::Windows::Graphics::Capture::IDirect3D11CaptureFramePool* + pool, + IInspectable* args) -> HRESULT + { + OnFrameArrived(); + return S_OK; + }) + .Get(), + &on_frame_arrived_token_); + + if (FAILED(frame_pool_->CreateCaptureSession(capture_item_.get(), + capture_session_.put()))) { + std::cerr << "Creating capture session failed." << std::endl; + return false; + } + + if (SUCCEEDED(capture_session_->StartCapture())) { + is_running_ = true; + return true; + } + + return false; + } + + void TextureBridge::Stop() + { + const std::lock_guard lock(mutex_); + StopInternal(); + } + + void TextureBridge::StopInternal() + { + if (is_running_) { + is_running_ = false; + frame_pool_->remove_FrameArrived(on_frame_arrived_token_); + auto closable = + capture_session_.try_as(); + assert(closable); + closable->Close(); + capture_session_ = nullptr; + } + } + + void TextureBridge::OnFrameArrived() + { + const std::lock_guard lock(mutex_); + if (!is_running_) { + return; + } + + bool has_frame = false; + + winrt::com_ptr + frame; + auto hr = frame_pool_->TryGetNextFrame(frame.put()); + if (SUCCEEDED(hr) && frame) { + winrt::com_ptr< + ABI::Windows::Graphics::DirectX::Direct3D11::IDirect3DSurface> + frame_surface; + + if (SUCCEEDED(frame->get_Surface(frame_surface.put()))) { + last_frame_ = + TryGetDXGIInterfaceFromObject(frame_surface); + has_frame = !ShouldDropFrame(); + } + } + + if (needs_update_) { + ABI::Windows::Graphics::SizeInt32 size; + capture_item_->get_Size(&size); + frame_pool_->Recreate( + graphics_context_->device(), + static_cast( + kPixelFormat), + kNumBuffers, size); + needs_update_ = false; + } + + if (has_frame && frame_available_) { + frame_available_(); + } + } + + bool TextureBridge::ShouldDropFrame() + { + if (!frame_duration_.has_value()) { + return false; + } + auto now = std::chrono::high_resolution_clock::now(); + + bool should_drop_frame = false; + if (last_frame_timestamp_.has_value()) { + auto diff = std::chrono::duration_cast( + now - last_frame_timestamp_.value()); + should_drop_frame = diff < frame_duration_.value(); + } + + if (!should_drop_frame) { + last_frame_timestamp_ = now; + } + return should_drop_frame; + } + + void TextureBridge::NotifySurfaceSizeChanged() + { + const std::lock_guard lock(mutex_); + needs_update_ = true; + } + + void TextureBridge::SetFpsLimit(std::optional max_fps) + { + const std::lock_guard lock(mutex_); + auto value = max_fps.value_or(0); + if (value != 0) { + frame_duration_ = FrameDuration(1000.0 / value); + } + else { + frame_duration_.reset(); + last_frame_timestamp_.reset(); + } + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge.h b/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge.h new file mode 100644 index 00000000..122c6938 --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge.h @@ -0,0 +1,79 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include + +#include "graphics_context.h" + +namespace flutter_inappwebview_plugin +{ + typedef struct { + size_t width; + size_t height; + } Size; + + class TextureBridge { + public: + typedef std::function FrameAvailableCallback; + typedef std::function SurfaceSizeChangedCallback; + typedef std::chrono::duration FrameDuration; + + TextureBridge(GraphicsContext* graphics_context, + ABI::Windows::UI::Composition::IVisual* visual); + virtual ~TextureBridge(); + + bool Start(); + void Stop(); + + void SetOnFrameAvailable(FrameAvailableCallback callback) + { + frame_available_ = std::move(callback); + } + + void SetOnSurfaceSizeChanged(SurfaceSizeChangedCallback callback) + { + surface_size_changed_ = std::move(callback); + } + + void NotifySurfaceSizeChanged(); + void SetFpsLimit(std::optional max_fps); + + protected: + bool is_running_ = false; + + const GraphicsContext* graphics_context_; + std::mutex mutex_; + std::optional frame_duration_ = std::nullopt; + + FrameAvailableCallback frame_available_; + SurfaceSizeChangedCallback surface_size_changed_; + std::atomic needs_update_ = false; + winrt::com_ptr last_frame_; + std::optional + last_frame_timestamp_; + + winrt::com_ptr + capture_item_; + winrt::com_ptr + frame_pool_; + winrt::com_ptr + capture_session_; + + EventRegistrationToken on_closed_token_ = {}; + EventRegistrationToken on_frame_arrived_token_ = {}; + + virtual void StopInternal(); + void OnFrameArrived(); + bool ShouldDropFrame(); + + // corresponds to DXGI_FORMAT_B8G8R8A8_UNORM + static constexpr auto kPixelFormat = ABI::Windows::Graphics::DirectX:: + DirectXPixelFormat::DirectXPixelFormat_B8G8R8A8UIntNormalized; + }; +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge_fallback.cc b/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge_fallback.cc new file mode 100644 index 00000000..6aa5d58d --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge_fallback.cc @@ -0,0 +1,145 @@ +#include "texture_bridge_fallback.h" + +#include + +#include "util/direct3d11.interop.h" +#include "util/swizzle.h" + +namespace flutter_inappwebview_plugin +{ + TextureBridgeFallback::TextureBridgeFallback( + GraphicsContext* graphics_context, + ABI::Windows::UI::Composition::IVisual* visual) + : TextureBridge(graphics_context, visual) + {} + + TextureBridgeFallback::~TextureBridgeFallback() + { + const std::lock_guard lock(buffer_mutex_); + } + + void TextureBridgeFallback::ProcessFrame( + winrt::com_ptr src_texture) + { + D3D11_TEXTURE2D_DESC desc; + src_texture->GetDesc(&desc); + + const auto width = desc.Width; + const auto height = desc.Height; + + bool is_exact_size; + EnsureStagingTexture(width, height, is_exact_size); + + auto device_context = graphics_context_->d3d_device_context(); + auto staging_texture = staging_texture_.get(); + + if (is_exact_size) { + device_context->CopyResource(staging_texture, src_texture.get()); + } + else { + D3D11_BOX client_box; + client_box.top = 0; + client_box.left = 0; + client_box.right = width; + client_box.bottom = height; + client_box.front = 0; + client_box.back = 1; + device_context->CopySubresourceRegion(staging_texture, 0, 0, 0, 0, + src_texture.get(), 0, &client_box); + } + + D3D11_MAPPED_SUBRESOURCE mappedResource; + if (!SUCCEEDED(device_context->Map(staging_texture, 0, D3D11_MAP_READ, 0, + &mappedResource))) { + return; + } + + { + const std::lock_guard lock(buffer_mutex_); + if (!pixel_buffer_ || pixel_buffer_->width != width || + pixel_buffer_->height != height) { + if (!pixel_buffer_) { + pixel_buffer_ = std::make_unique(); + pixel_buffer_->release_context = &buffer_mutex_; + // Gets invoked after the FlutterDesktopPixelBuffer's + // backing buffer has been uploaded. + pixel_buffer_->release_callback = [](void* opaque) + { + auto mutex = reinterpret_cast(opaque); + // Gets locked just before |CopyPixelBuffer| returns. + mutex->unlock(); + }; + } + pixel_buffer_->width = width; + pixel_buffer_->height = height; + const auto size = width * height * 4; + backing_pixel_buffer_.reset(new uint8_t[size]); + pixel_buffer_->buffer = backing_pixel_buffer_.get(); + } + + const auto src_pitch_in_pixels = mappedResource.RowPitch / 4; + RGBA_to_BGRA(reinterpret_cast(backing_pixel_buffer_.get()), + static_cast(mappedResource.pData), height, + src_pitch_in_pixels, width); + } + + device_context->Unmap(staging_texture, 0); + } + + void TextureBridgeFallback::EnsureStagingTexture(uint32_t width, + uint32_t height, + bool& is_exact_size) + { + // Only recreate an existing texture if it's too small. + if (!staging_texture_ || staging_texture_size_.width < width || + staging_texture_size_.height < height) { + D3D11_TEXTURE2D_DESC dstDesc = {}; + dstDesc.ArraySize = 1; + dstDesc.MipLevels = 1; + dstDesc.BindFlags = 0; + dstDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + dstDesc.Format = static_cast(kPixelFormat); + dstDesc.Width = width; + dstDesc.Height = height; + dstDesc.MiscFlags = 0; + dstDesc.SampleDesc.Count = 1; + dstDesc.SampleDesc.Quality = 0; + dstDesc.Usage = D3D11_USAGE_STAGING; + + staging_texture_ = nullptr; + if (!SUCCEEDED(graphics_context_->d3d_device()->CreateTexture2D( + &dstDesc, nullptr, staging_texture_.put()))) { + std::cerr << "Creating dst texture failed" << std::endl; + return; + } + + staging_texture_size_ = { width, height }; + } + + is_exact_size = staging_texture_size_.width == width && + staging_texture_size_.height == height; + } + + const FlutterDesktopPixelBuffer* TextureBridgeFallback::CopyPixelBuffer( + size_t width, size_t height) + { + const std::lock_guard lock(mutex_); + + if (!is_running_) { + return nullptr; + } + + if (last_frame_) { + ProcessFrame(last_frame_); + } + + auto buffer = pixel_buffer_.get(); + // Only lock the mutex if the buffer is not null + // (to ensure the release callback gets called) + if (buffer) { + // Gets unlocked in the FlutterDesktopPixelBuffer's release callback. + buffer_mutex_.lock(); + } + return buffer; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge_fallback.h b/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge_fallback.h new file mode 100644 index 00000000..ad07b77c --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge_fallback.h @@ -0,0 +1,30 @@ +#pragma once + +#include + +#include + +#include "texture_bridge.h" + +namespace flutter_inappwebview_plugin +{ + class TextureBridgeFallback : public TextureBridge { + public: + TextureBridgeFallback(GraphicsContext* graphics_context, + ABI::Windows::UI::Composition::IVisual* visual); + ~TextureBridgeFallback() override; + + const FlutterDesktopPixelBuffer* CopyPixelBuffer(size_t width, size_t height); + + private: + Size staging_texture_size_ = { 0, 0 }; + winrt::com_ptr staging_texture_{ nullptr }; + std::mutex buffer_mutex_; + std::unique_ptr backing_pixel_buffer_; + std::unique_ptr pixel_buffer_; + + void ProcessFrame(winrt::com_ptr src_texture); + void EnsureStagingTexture(uint32_t width, uint32_t height, + bool& is_exact_size); + }; +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge_gpu.cc b/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge_gpu.cc new file mode 100644 index 00000000..c3bdee26 --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge_gpu.cc @@ -0,0 +1,108 @@ +#include "texture_bridge_gpu.h" + +#include + +#include "util/direct3d11.interop.h" + +namespace flutter_inappwebview_plugin +{ + TextureBridgeGpu::TextureBridgeGpu( + GraphicsContext* graphics_context, + ABI::Windows::UI::Composition::IVisual* visual) + : TextureBridge(graphics_context, visual) + { + surface_descriptor_.struct_size = sizeof(FlutterDesktopGpuSurfaceDescriptor); + surface_descriptor_.format = + kFlutterDesktopPixelFormatNone; // no format required for DXGI surfaces + } + + void TextureBridgeGpu::ProcessFrame( + winrt::com_ptr src_texture) + { + D3D11_TEXTURE2D_DESC desc; + src_texture->GetDesc(&desc); + + const auto width = desc.Width; + const auto height = desc.Height; + + EnsureSurface(width, height); + + auto device_context = graphics_context_->d3d_device_context(); + + device_context->CopyResource(surface_.get(), src_texture.get()); + device_context->Flush(); + } + + void TextureBridgeGpu::EnsureSurface(uint32_t width, uint32_t height) + { + if (!surface_ || surface_size_.width != width || + surface_size_.height != height) { + D3D11_TEXTURE2D_DESC dstDesc = {}; + dstDesc.ArraySize = 1; + dstDesc.MipLevels = 1; + dstDesc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE; + dstDesc.CPUAccessFlags = 0; + dstDesc.Format = static_cast(kPixelFormat); + dstDesc.Width = width; + dstDesc.Height = height; + dstDesc.MiscFlags = D3D11_RESOURCE_MISC_SHARED; + dstDesc.SampleDesc.Count = 1; + dstDesc.SampleDesc.Quality = 0; + dstDesc.Usage = D3D11_USAGE_DEFAULT; + + surface_ = nullptr; + if (!SUCCEEDED(graphics_context_->d3d_device()->CreateTexture2D( + &dstDesc, nullptr, surface_.put()))) { + std::cerr << "Creating intermediate texture failed" << std::endl; + return; + } + + HANDLE shared_handle; + surface_.try_as(dxgi_surface_); + assert(dxgi_surface_); + dxgi_surface_->GetSharedHandle(&shared_handle); + + surface_descriptor_.handle = shared_handle; + surface_descriptor_.width = surface_descriptor_.visible_width = width; + surface_descriptor_.height = surface_descriptor_.visible_height = height; + surface_descriptor_.release_context = surface_.get(); + surface_descriptor_.release_callback = [](void* release_context) + { + auto texture = reinterpret_cast(release_context); + texture->Release(); + }; + + surface_size_ = { width, height }; + } + } + + const FlutterDesktopGpuSurfaceDescriptor* + TextureBridgeGpu::GetSurfaceDescriptor(size_t width, size_t height) + { + const std::lock_guard lock(mutex_); + + if (!is_running_) { + return nullptr; + } + + if (last_frame_) { + ProcessFrame(last_frame_); + } + + if (surface_) { + // Gets released in the SurfaceDescriptor's release callback. + surface_->AddRef(); + } + + return &surface_descriptor_; + } + + void TextureBridgeGpu::StopInternal() + { + TextureBridge::StopInternal(); + + // For some reason, the destination surface needs to be recreated upon + // resuming. Force |EnsureSurface| to create a new one by resetting it here. + surface_ = nullptr; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge_gpu.h b/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge_gpu.h new file mode 100644 index 00000000..8b29a321 --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge_gpu.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +#include "texture_bridge.h" + +namespace flutter_inappwebview_plugin +{ + class TextureBridgeGpu : public TextureBridge { + public: + TextureBridgeGpu(GraphicsContext* graphics_context, + ABI::Windows::UI::Composition::IVisual* visual); + + const FlutterDesktopGpuSurfaceDescriptor* GetSurfaceDescriptor(size_t width, + size_t height); + + protected: + void StopInternal() override; + + private: + FlutterDesktopGpuSurfaceDescriptor surface_descriptor_ = {}; + Size surface_size_ = { 0, 0 }; + winrt::com_ptr surface_{ nullptr }; + winrt::com_ptr dxgi_surface_; + + void ProcessFrame(winrt::com_ptr src_texture); + void EnsureSurface(uint32_t width, uint32_t height); + }; +} diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/util/composition.desktop.interop.h b/flutter_inappwebview_windows/windows/custom_platform_view/util/composition.desktop.interop.h new file mode 100644 index 00000000..9b9eb0d3 --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/util/composition.desktop.interop.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +#include + +namespace util { + + winrt::com_ptr TryCreateDesktopWindowTarget( + const winrt::com_ptr& + compositor, + HWND window) + { + namespace abi = ABI::Windows::UI::Composition::Desktop; + auto interop = compositor.try_as(); + + winrt::com_ptr target; + interop->CreateDesktopWindowTarget(window, true, target.put()); + return target; + } + +} // namespace util diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/cpuinfo.cc b/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/cpuinfo.cc new file mode 100644 index 00000000..acee3437 --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/cpuinfo.cc @@ -0,0 +1,80 @@ +#include "cpuinfo.h" + +#include "detail/cpuinfo_impl.h" + +#if defined(_MSC_VER) && (defined(__x86_64__) || defined(_M_X64)) +#include "detail/init_msvc_x86.h" +#else +#include "detail/init_unknown.hpp" +#endif + +namespace cpuid { + +cpuinfo::cpuinfo() : impl_(new impl) { init_cpuinfo(*impl_); } + +cpuinfo::~cpuinfo() {} + +// x86 member functions +bool cpuinfo::has_fpu() const { return impl_->m_has_fpu; } + +bool cpuinfo::has_mmx() const { return impl_->m_has_mmx; } + +bool cpuinfo::has_sse() const { return impl_->m_has_sse; } + +bool cpuinfo::has_sse2() const { return impl_->m_has_sse2; } + +bool cpuinfo::has_sse3() const { return impl_->m_has_sse3; } + +bool cpuinfo::has_ssse3() const { return impl_->m_has_ssse3; } + +bool cpuinfo::has_sse4_1() const { return impl_->m_has_sse4_1; } + +bool cpuinfo::has_sse4_2() const { return impl_->m_has_sse4_2; } + +bool cpuinfo::has_pclmulqdq() const { return impl_->m_has_pclmulqdq; } + +bool cpuinfo::has_avx() const { return impl_->m_has_avx; } + +bool cpuinfo::has_avx2() const { return impl_->m_has_avx2; } + +bool cpuinfo::has_avx512_f() const { return impl_->m_has_avx512_f; } + +bool cpuinfo::has_avx512_dq() const { return impl_->m_has_avx512_dq; } + +bool cpuinfo::has_avx512_ifma() const { return impl_->m_has_avx512_ifma; } + +bool cpuinfo::has_avx512_pf() const { return impl_->m_has_avx512_pf; } + +bool cpuinfo::has_avx512_er() const { return impl_->m_has_avx512_er; } + +bool cpuinfo::has_avx512_cd() const { return impl_->m_has_avx512_cd; } + +bool cpuinfo::has_avx512_bw() const { return impl_->m_has_avx512_bw; } + +bool cpuinfo::has_avx512_vl() const { return impl_->m_has_avx512_vl; } + +bool cpuinfo::has_avx512_vbmi() const { return impl_->m_has_avx512_vbmi; } + +bool cpuinfo::has_avx512_vbmi2() const { return impl_->m_has_avx512_vbmi2; } + +bool cpuinfo::has_avx512_vnni() const { return impl_->m_has_avx512_vnni; } + +bool cpuinfo::has_avx512_bitalg() const { return impl_->m_has_avx512_bitalg; } + +bool cpuinfo::has_avx512_vpopcntdq() const { + return impl_->m_has_avx512_vpopcntdq; +} + +bool cpuinfo::has_avx512_4vnniw() const { return impl_->m_has_avx512_4vnniw; } + +bool cpuinfo::has_avx512_4fmaps() const { return impl_->m_has_avx512_4fmaps; } + +bool cpuinfo::has_avx512_vp2intersect() const { + return impl_->m_has_avx512_vp2intersect; +} + +bool cpuinfo::has_f16c() const { return impl_->m_has_f16c; } + +// ARM member functions +bool cpuinfo::has_neon() const { return impl_->m_has_neon; } +} // namespace cpuid diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/cpuinfo.h b/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/cpuinfo.h new file mode 100644 index 00000000..0cee3598 --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/cpuinfo.h @@ -0,0 +1,105 @@ +#pragma once + +#include + +namespace cpuid { + +class cpuinfo { + public: + struct impl; + + cpuinfo(); + ~cpuinfo(); + + // Has X87 FPU + bool has_fpu() const; + + // Return true if the CPU supports MMX + bool has_mmx() const; + + // Return true if the CPU supports SSE + bool has_sse() const; + + // Return true if the CPU supports SSE2 + bool has_sse2() const; + + // Return true if the CPU supports SSE3 + bool has_sse3() const; + + // Return true if the CPU supports SSSE3 + bool has_ssse3() const; + + // Return true if the CPU supports SSE 4.1 + bool has_sse4_1() const; + + // Return true if the CPU supports SSE 4.2 + bool has_sse4_2() const; + + // Return true if the CPU supports pclmulqdq + bool has_pclmulqdq() const; + + // Return true if the CPU supports AVX + bool has_avx() const; + + // Return true if the CPU supports AVX2 + bool has_avx2() const; + + // Return true if the CPU supports AVX512F + bool has_avx512_f() const; + + // Return true if the CPU supports AVX512DQ + bool has_avx512_dq() const; + + // Return true if the CPU supports AVX512_IFMA + bool has_avx512_ifma() const; + + // Return true if the CPU supports AVX512PF + bool has_avx512_pf() const; + + // Return true if the CPU supports AVX512ER + bool has_avx512_er() const; + + // Return true if the CPU supports AVX512CD + bool has_avx512_cd() const; + + // Return true if the CPU supports AVX512BW + bool has_avx512_bw() const; + + // Return true if the CPU supports AVX512VL + bool has_avx512_vl() const; + + // Return true if the CPU supports AVX512_VBMI + bool has_avx512_vbmi() const; + + // Return true if the CPU supports AVX512_VBMI2 + bool has_avx512_vbmi2() const; + + // Return true if the CPU supports AVX512_VNNI + bool has_avx512_vnni() const; + + // Return true if the CPU supports AVX512_BITALG + bool has_avx512_bitalg() const; + + // Return true if the CPU supports AVX512_VPOPCNTDQ + bool has_avx512_vpopcntdq() const; + + // Return true if the CPU supports AVX512_4VNNIW + bool has_avx512_4vnniw() const; + + // Return true if the CPU supports AVX512_4FMAPS + bool has_avx512_4fmaps() const; + + // Return true if the CPU supports AVX512_VP2INTERSECT + bool has_avx512_vp2intersect() const; + + // Return true if the CPU supports F16C + bool has_f16c() const; + + // Return true if the CPU supports NEON + bool has_neon() const; + + private: + // Private implementation + std::unique_ptr impl_; +}; +} // namespace cpuid diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/detail/cpuinfo_impl.h b/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/detail/cpuinfo_impl.h new file mode 100644 index 00000000..1d2ee69f --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/detail/cpuinfo_impl.h @@ -0,0 +1,69 @@ +#pragma once + +#include "../cpuinfo.h" + +namespace cpuid { + +struct cpuinfo::impl { + impl() + : m_has_fpu(false), + m_has_mmx(false), + m_has_sse(false), + m_has_sse2(false), + m_has_sse3(false), + m_has_ssse3(false), + m_has_sse4_1(false), + m_has_sse4_2(false), + m_has_pclmulqdq(false), + m_has_avx(false), + m_has_avx2(false), + m_has_avx512_f(false), + m_has_avx512_dq(false), + m_has_avx512_ifma(false), + m_has_avx512_pf(false), + m_has_avx512_er(false), + m_has_avx512_cd(false), + m_has_avx512_bw(false), + m_has_avx512_vl(false), + m_has_avx512_vbmi(false), + m_has_avx512_vbmi2(false), + m_has_avx512_vnni(false), + m_has_avx512_bitalg(false), + m_has_avx512_vpopcntdq(false), + m_has_avx512_4vnniw(false), + m_has_avx512_4fmaps(false), + m_has_avx512_vp2intersect(false), + m_has_f16c(false), + m_has_neon(false) {} + + bool m_has_fpu; + bool m_has_mmx; + bool m_has_sse; + bool m_has_sse2; + bool m_has_sse3; + bool m_has_ssse3; + bool m_has_sse4_1; + bool m_has_sse4_2; + bool m_has_pclmulqdq; + bool m_has_avx; + bool m_has_avx2; + bool m_has_avx512_f; + bool m_has_avx512_dq; + bool m_has_avx512_ifma; + bool m_has_avx512_pf; + bool m_has_avx512_er; + bool m_has_avx512_cd; + bool m_has_avx512_bw; + bool m_has_avx512_vl; + bool m_has_avx512_vbmi; + bool m_has_avx512_vbmi2; + bool m_has_avx512_vnni; + bool m_has_avx512_bitalg; + bool m_has_avx512_vpopcntdq; + bool m_has_avx512_4vnniw; + bool m_has_avx512_4fmaps; + bool m_has_avx512_vp2intersect; + bool m_has_f16c; + bool m_has_neon; +}; +} // namespace cpuid diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/detail/extract_x86_flags.h b/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/detail/extract_x86_flags.h new file mode 100644 index 00000000..5bea5866 --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/detail/extract_x86_flags.h @@ -0,0 +1,43 @@ +#pragma once + +#include + +#include "cpuinfo_impl.h" + +namespace cpuid { + +void extract_x86_flags(cpuinfo::impl& info, uint32_t ecx, uint32_t edx) { + info.m_has_fpu = (edx & (1 << 0)) != 0; + info.m_has_mmx = (edx & (1 << 23)) != 0; + info.m_has_sse = (edx & (1 << 25)) != 0; + info.m_has_sse2 = (edx & (1 << 26)) != 0; + info.m_has_sse3 = (ecx & (1 << 0)) != 0; + info.m_has_ssse3 = (ecx & (1 << 9)) != 0; + info.m_has_sse4_1 = (ecx & (1 << 19)) != 0; + info.m_has_sse4_2 = (ecx & (1 << 20)) != 0; + info.m_has_pclmulqdq = (ecx & (1 << 1)) != 0; + info.m_has_avx = (ecx & (1 << 28)) != 0; + info.m_has_f16c = (ecx & (1 << 29)) != 0; +} + +void extract_x86_extended_flags(cpuinfo::impl& info, uint32_t ebx, uint32_t ecx, + uint32_t edx) { + info.m_has_avx2 = (ebx & (1 << 5)) != 0; + info.m_has_avx512_f = (ebx & (1 << 16)) != 0; + info.m_has_avx512_dq = (ebx & (1 << 17)) != 0; + info.m_has_avx512_ifma = (ebx & (1 << 21)) != 0; + info.m_has_avx512_pf = (ebx & (1 << 26)) != 0; + info.m_has_avx512_er = (ebx & (1 << 27)) != 0; + info.m_has_avx512_cd = (ebx & (1 << 28)) != 0; + info.m_has_avx512_bw = (ebx & (1 << 30)) != 0; + info.m_has_avx512_vl = (ebx & (1 << 31)) != 0; + info.m_has_avx512_vbmi = (ecx & (1 << 1)) != 0; + info.m_has_avx512_vbmi2 = (ecx & (1 << 6)) != 0; + info.m_has_avx512_vnni = (ecx & (1 << 11)) != 0; + info.m_has_avx512_bitalg = (ecx & (1 << 12)) != 0; + info.m_has_avx512_vpopcntdq = (ecx & (1 << 14)) != 0; + info.m_has_avx512_4vnniw = (edx & (1 << 2)) != 0; + info.m_has_avx512_4fmaps = (edx & (1 << 3)) != 0; + info.m_has_avx512_vp2intersect = (edx & (1 << 8)) != 0; +} +} // namespace cpuid diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/detail/init_msvc_x86.h b/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/detail/init_msvc_x86.h new file mode 100644 index 00000000..697270b1 --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/detail/init_msvc_x86.h @@ -0,0 +1,36 @@ +#pragma once + +#include + +#include "cpuinfo_impl.h" +#include "extract_x86_flags.h" + +namespace cpuid { + +void init_cpuinfo(cpuinfo::impl& info) { + int registers[4]; + + // The register information per input can be extracted from here: + // http://en.wikipedia.org/wiki/CPUID + // + // CPUID should be called with EAX=0 first, as this will return the + // maximum supported EAX input value for future calls + __cpuid(registers, 0); + uint32_t maximum_eax = registers[0]; + + // Set registers for basic flag extraction, eax=1 + // All CPUs should support index=1 + if (maximum_eax >= 1U) { + __cpuid(registers, 1); + extract_x86_flags(info, registers[2], registers[3]); + } + + // Set registers for extended flags extraction, eax=7 and ecx=0 + // This operation is not supported on older CPUs, so it should be skipped + // to avoid incorrect results + if (maximum_eax >= 7U) { + __cpuidex(registers, 7, 0); + extract_x86_extended_flags(info, registers[1], registers[2], registers[3]); + } +} +} // namespace cpuid diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/detail/init_unknown.h b/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/detail/init_unknown.h new file mode 100644 index 00000000..3b52ef2b --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/detail/init_unknown.h @@ -0,0 +1,9 @@ + +#pragma once + +#include "cpuinfo_impl.h" + +namespace cpuid { + +void init_cpuinfo(cpuinfo::impl& info) { (void)info; } +} // namespace cpuid diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/util/d3dutil.h b/flutter_inappwebview_windows/windows/custom_platform_view/util/d3dutil.h new file mode 100644 index 00000000..a32bc157 --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/util/d3dutil.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include + +inline auto CreateD3DDevice(D3D_DRIVER_TYPE const type, + winrt::com_ptr& device) { + WINRT_ASSERT(!device); + + UINT flags = + D3D11_CREATE_DEVICE_BGRA_SUPPORT | D3D11_CREATE_DEVICE_VIDEO_SUPPORT; + + //#ifdef _DEBUG + // flags |= D3D11_CREATE_DEVICE_DEBUG; + //#endif + + return D3D11CreateDevice(nullptr, type, nullptr, flags, nullptr, 0, + D3D11_SDK_VERSION, device.put(), nullptr, nullptr); +} + +inline auto CreateD3DDevice() { + winrt::com_ptr device; + HRESULT hr = CreateD3DDevice(D3D_DRIVER_TYPE_HARDWARE, device); + + if (DXGI_ERROR_UNSUPPORTED == hr) { + CreateD3DDevice(D3D_DRIVER_TYPE_WARP, device); + } + + return device; +} diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/util/direct3d11.interop.cc b/flutter_inappwebview_windows/windows/custom_platform_view/util/direct3d11.interop.cc new file mode 100644 index 00000000..96a74ba9 --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/util/direct3d11.interop.cc @@ -0,0 +1,47 @@ +#include "direct3d11.interop.h" + +namespace flutter_inappwebview_plugin +{ + + namespace { + + typedef HRESULT(WINAPI* CreateDirect3D11DeviceFromDXGIDeviceFn)(IDXGIDevice*, + LPVOID*); + + struct D3DFuncs { + CreateDirect3D11DeviceFromDXGIDeviceFn CreateDirect3D11DeviceFromDXGIDevice = + nullptr; + + D3DFuncs() + { + auto handle = GetModuleHandle(L"d3d11.dll"); + if (!handle) { + return; + } + + CreateDirect3D11DeviceFromDXGIDevice = + reinterpret_cast( + GetProcAddress(handle, "CreateDirect3D11DeviceFromDXGIDevice")); + } + + static const D3DFuncs& instance() + { + static D3DFuncs funcs; + return funcs; + } + }; + + } // namespace + + HRESULT CreateDirect3D11DeviceFromDXGIDevice(IDXGIDevice* dxgiDevice, + IInspectable** graphicsDevice) + { + auto ptr = D3DFuncs::instance().CreateDirect3D11DeviceFromDXGIDevice; + if (ptr) { + return ptr(dxgiDevice, reinterpret_cast(graphicsDevice)); + } + + return E_NOTIMPL; + } + +} // namespace util diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/util/direct3d11.interop.h b/flutter_inappwebview_windows/windows/custom_platform_view/util/direct3d11.interop.h new file mode 100644 index 00000000..4434815d --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/util/direct3d11.interop.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include + +#include "dxgi.h" + +namespace Windows { + namespace Graphics { + namespace DirectX { + namespace Direct3D11 { + struct __declspec(uuid("A9B3D012-3DF2-4EE3-B8D1-8695F457D3C1")) + IDirect3DDxgiInterfaceAccess : ::IUnknown { + virtual HRESULT __stdcall GetInterface(GUID const& id, void** object) = 0; + }; + + } // namespace Direct3D11 + } // namespace DirectX + } // namespace Graphics +} // namespace Windows + +namespace flutter_inappwebview_plugin +{ + + HRESULT CreateDirect3D11DeviceFromDXGIDevice(IDXGIDevice* dxgiDevice, + IInspectable** graphicsDevice); + + template + auto GetDXGIInterfaceFromObject( + winrt::Windows::Foundation::IInspectable const& object) + { + auto access = object.as< + Windows::Graphics::DirectX::Direct3D11::IDirect3DDxgiInterfaceAccess>(); + winrt::com_ptr result; + winrt::check_hresult( + access->GetInterface(winrt::guid_of(), result.put_void())); + return result; + } + + template + auto TryGetDXGIInterfaceFromObject(const winrt::com_ptr& object) + { + auto access = object.try_as< + Windows::Graphics::DirectX::Direct3D11::IDirect3DDxgiInterfaceAccess>(); + winrt::com_ptr result; + access->GetInterface(winrt::guid_of(), result.put_void()); + return result; + } + +} // namespace util diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/util/rohelper.cc b/flutter_inappwebview_windows/windows/custom_platform_view/util/rohelper.cc new file mode 100644 index 00000000..6fa6ab0b --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/util/rohelper.cc @@ -0,0 +1,244 @@ +// Based on ANGLE's RoHelper (CompositorNativeWindow11.{cpp,h}) +// - https://github.com/google/angle/blob/main/src/libANGLE/renderer/d3d/d3d11/converged/CompositorNativeWindow11.h +// - https://github.com/google/angle/blob/main/src/libANGLE/renderer/d3d/d3d11/converged/CompositorNativeWindow11.cpp +// - https://gist.github.com/clarkezone/43e984fb9bdcd2cfcd9a4f41c208a02f +// +// Copyright 2018 The ANGLE Project Authors. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// +// Neither the name of TransGaming Inc., Google Inc., 3DLabs Inc. +// Ltd., nor the names of their contributors may be used to endorse +// or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#include "rohelper.h" + +#include +#include + +namespace rx { +template +bool AssignProcAddress(HMODULE comBaseModule, const char* name, T*& outProc) { + outProc = reinterpret_cast(GetProcAddress(comBaseModule, name)); + return *outProc != nullptr; +} + +RoHelper::RoHelper(RO_INIT_TYPE init_type) + : mFpWindowsCreateStringReference(nullptr), + mFpGetActivationFactory(nullptr), + mFpWindowsCompareStringOrdinal(nullptr), + mFpCreateDispatcherQueueController(nullptr), + mFpWindowsDeleteString(nullptr), + mFpRoInitialize(nullptr), + mFpRoUninitialize(nullptr), + mWinRtAvailable(false), + mComBaseModule(nullptr), + mCoreMessagingModule(nullptr) { +#ifdef WINUWP + mFpWindowsCreateStringReference = &::WindowsCreateStringReference; + mFpRoInitialize = &::RoInitialize; + mFpRoUninitialize = &::RoUninitialize; + mFpWindowsDeleteString = &::WindowsDeleteString; + mFpGetActivationFactory = &::RoGetActivationFactory; + mFpWindowsCompareStringOrdinal = &::WindowsCompareStringOrdinal; + mFpCreateDispatcherQueueController = &::CreateDispatcherQueueController; + mWinRtAvailable = true; +#else + + mComBaseModule = LoadLibraryA("ComBase.dll"); + + if (mComBaseModule == nullptr) { + return; + } + + if (!AssignProcAddress(mComBaseModule, "WindowsCreateStringReference", + mFpWindowsCreateStringReference)) { + return; + } + + if (!AssignProcAddress(mComBaseModule, "RoGetActivationFactory", + mFpGetActivationFactory)) { + return; + } + + if (!AssignProcAddress(mComBaseModule, "WindowsCompareStringOrdinal", + mFpWindowsCompareStringOrdinal)) { + return; + } + + if (!AssignProcAddress(mComBaseModule, "WindowsDeleteString", + mFpWindowsDeleteString)) { + return; + } + + if (!AssignProcAddress(mComBaseModule, "RoInitialize", mFpRoInitialize)) { + return; + } + + if (!AssignProcAddress(mComBaseModule, "RoUninitialize", mFpRoUninitialize)) { + return; + } + + mCoreMessagingModule = LoadLibraryA("coremessaging.dll"); + + if (mCoreMessagingModule == nullptr) { + return; + } + + if (!AssignProcAddress(mCoreMessagingModule, + "CreateDispatcherQueueController", + mFpCreateDispatcherQueueController)) { + return; + } + + auto result = RoInitialize(init_type); + + if (SUCCEEDED(result) || result == S_FALSE || result == RPC_E_CHANGED_MODE) { + mWinRtAvailable = true; + } +#endif +} + +RoHelper::~RoHelper() { +#ifndef WINUWP + if (mWinRtAvailable) { + RoUninitialize(); + } + + if (mCoreMessagingModule != nullptr) { + FreeLibrary(mCoreMessagingModule); + mCoreMessagingModule = nullptr; + } + + if (mComBaseModule != nullptr) { + FreeLibrary(mComBaseModule); + mComBaseModule = nullptr; + } +#endif +} + +bool RoHelper::WinRtAvailable() const { return mWinRtAvailable; } + +bool RoHelper::SupportedWindowsRelease() { + if (!mWinRtAvailable) { + return false; + } + + HSTRING className, contractName; + HSTRING_HEADER classNameHeader, contractNameHeader; + boolean isSupported = false; + + HRESULT hr = GetStringReference( + RuntimeClass_Windows_Foundation_Metadata_ApiInformation, &className, + &classNameHeader); + + if (FAILED(hr)) { + return !!isSupported; + } + + Microsoft::WRL::ComPtr< + ABI::Windows::Foundation::Metadata::IApiInformationStatics> + api; + + hr = GetActivationFactory( + className, + __uuidof(ABI::Windows::Foundation::Metadata::IApiInformationStatics), + &api); + + if (FAILED(hr)) { + return !!isSupported; + } + + hr = GetStringReference(L"Windows.Foundation.UniversalApiContract", + &contractName, &contractNameHeader); + if (FAILED(hr)) { + return !!isSupported; + } + + api->IsApiContractPresentByMajor(contractName, 6, &isSupported); + + return !!isSupported; +} + +HRESULT RoHelper::GetStringReference(PCWSTR source, HSTRING* act, + HSTRING_HEADER* header) { + if (!mWinRtAvailable) { + return E_FAIL; + } + + const wchar_t* str = static_cast(source); + + unsigned int length; + HRESULT hr = SizeTToUInt32(::wcslen(str), &length); + if (FAILED(hr)) { + return hr; + } + + return mFpWindowsCreateStringReference(source, length, header, act); +} + +HRESULT RoHelper::GetActivationFactory(const HSTRING act, + const IID& interfaceId, void** fac) { + if (!mWinRtAvailable) { + return E_FAIL; + } + auto hr = mFpGetActivationFactory(act, interfaceId, fac); + return hr; +} + +HRESULT RoHelper::WindowsCompareStringOrdinal(HSTRING one, HSTRING two, + int* result) { + if (!mWinRtAvailable) { + return E_FAIL; + } + return mFpWindowsCompareStringOrdinal(one, two, result); +} + +HRESULT RoHelper::CreateDispatcherQueueController( + DispatcherQueueOptions options, + ABI::Windows::System::IDispatcherQueueController** + dispatcherQueueController) { + if (!mWinRtAvailable) { + return E_FAIL; + } + return mFpCreateDispatcherQueueController(options, dispatcherQueueController); +} + +HRESULT RoHelper::WindowsDeleteString(HSTRING one) { + if (!mWinRtAvailable) { + return E_FAIL; + } + return mFpWindowsDeleteString(one); +} + +HRESULT RoHelper::RoInitialize(RO_INIT_TYPE type) { + return mFpRoInitialize(type); +} + +void RoHelper::RoUninitialize() { mFpRoUninitialize(); } +} // namespace rx diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/util/rohelper.h b/flutter_inappwebview_windows/windows/custom_platform_view/util/rohelper.h new file mode 100644 index 00000000..12e7e6cc --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/util/rohelper.h @@ -0,0 +1,97 @@ +// Based on ANGLE's RoHelper (CompositorNativeWindow11.{cpp,h}) +// - https://github.com/google/angle/blob/main/src/libANGLE/renderer/d3d/d3d11/converged/CompositorNativeWindow11.h +// - https://github.com/google/angle/blob/main/src/libANGLE/renderer/d3d/d3d11/converged/CompositorNativeWindow11.cpp +// - https://gist.github.com/clarkezone/43e984fb9bdcd2cfcd9a4f41c208a02f +// +// Copyright 2018 The ANGLE Project Authors. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// +// Neither the name of TransGaming Inc., Google Inc., 3DLabs Inc. +// Ltd., nor the names of their contributors may be used to endorse +// or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include +#include +#include + +namespace rx { +class RoHelper { + public: + RoHelper(RO_INIT_TYPE init_type); + ~RoHelper(); + bool WinRtAvailable() const; + bool SupportedWindowsRelease(); + HRESULT GetStringReference(PCWSTR source, HSTRING* act, + HSTRING_HEADER* header); + HRESULT GetActivationFactory(const HSTRING act, const IID& interfaceId, + void** fac); + HRESULT WindowsCompareStringOrdinal(HSTRING one, HSTRING two, int* result); + HRESULT CreateDispatcherQueueController( + DispatcherQueueOptions options, + ABI::Windows::System::IDispatcherQueueController** + dispatcherQueueController); + HRESULT WindowsDeleteString(HSTRING one); + HRESULT RoInitialize(RO_INIT_TYPE type); + void RoUninitialize(); + + private: + using WindowsCreateStringReference_ = HRESULT __stdcall(PCWSTR, UINT32, + HSTRING_HEADER*, + HSTRING*); + + using GetActivationFactory_ = HRESULT __stdcall(HSTRING, REFIID, void**); + + using WindowsCompareStringOrginal_ = HRESULT __stdcall(HSTRING, HSTRING, + int*); + + using WindowsDeleteString_ = HRESULT __stdcall(HSTRING); + + using CreateDispatcherQueueController_ = + HRESULT __stdcall(DispatcherQueueOptions, + ABI::Windows::System::IDispatcherQueueController**); + + using RoInitialize_ = HRESULT __stdcall(RO_INIT_TYPE); + using RoUninitialize_ = void __stdcall(); + + WindowsCreateStringReference_* mFpWindowsCreateStringReference; + GetActivationFactory_* mFpGetActivationFactory; + WindowsCompareStringOrginal_* mFpWindowsCompareStringOrdinal; + CreateDispatcherQueueController_* mFpCreateDispatcherQueueController; + WindowsDeleteString_* mFpWindowsDeleteString; + RoInitialize_* mFpRoInitialize; + RoUninitialize_* mFpRoUninitialize; + + bool mWinRtAvailable; + + HMODULE mComBaseModule; + HMODULE mCoreMessagingModule; +}; +} // namespace rx diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/util/string_converter.cc b/flutter_inappwebview_windows/windows/custom_platform_view/util/string_converter.cc new file mode 100644 index 00000000..7b2d8861 --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/util/string_converter.cc @@ -0,0 +1,54 @@ +#include "string_converter.h" + +#include + +namespace util { +std::string Utf8FromUtf16(std::wstring_view utf16_string) { + if (utf16_string.empty()) { + return std::string(); + } + + auto src_length = static_cast(utf16_string.size()); + int target_length = + ::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string.data(), + src_length, nullptr, 0, nullptr, nullptr); + + std::string utf8_string; + if (target_length <= 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string.data(), src_length, + utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} + +std::wstring Utf16FromUtf8(std::string_view utf8_string) { + if (utf8_string.empty()) { + return std::wstring(); + } + + auto src_length = static_cast(utf8_string.size()); + int target_length = + ::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, utf8_string.data(), + src_length, nullptr, 0); + + std::wstring utf16_string; + if (target_length <= 0 || target_length > utf16_string.max_size()) { + return utf16_string; + } + utf16_string.resize(target_length); + int converted_length = + ::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, utf8_string.data(), + src_length, utf16_string.data(), target_length); + if (converted_length == 0) { + return std::wstring(); + } + return utf16_string; +} + +} // namespace util diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/util/string_converter.h b/flutter_inappwebview_windows/windows/custom_platform_view/util/string_converter.h new file mode 100644 index 00000000..0a71dbe0 --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/util/string_converter.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +namespace util { +std::string Utf8FromUtf16(std::wstring_view utf16_string); +std::wstring Utf16FromUtf8(std::string_view utf8_string); +} // namespace util diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/util/swizzle.h b/flutter_inappwebview_windows/windows/custom_platform_view/util/swizzle.h new file mode 100644 index 00000000..67f5f7c6 --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/util/swizzle.h @@ -0,0 +1,192 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/* + * see skia/src/opts/SkSwizzler_opts.h + */ + +#pragma once + +#include "cpuid/cpuinfo.h" + +/** + * SK_CPU_SSE_LEVEL + * + * If defined, SK_CPU_SSE_LEVEL should be set to the highest supported level. + * On non-intel CPU this should be undefined. + */ +#define SK_CPU_SSE_LEVEL_SSE1 10 +#define SK_CPU_SSE_LEVEL_SSE2 20 +#define SK_CPU_SSE_LEVEL_SSE3 30 +#define SK_CPU_SSE_LEVEL_SSSE3 31 +#define SK_CPU_SSE_LEVEL_SSE41 41 +#define SK_CPU_SSE_LEVEL_SSE42 42 +#define SK_CPU_SSE_LEVEL_AVX 51 +#define SK_CPU_SSE_LEVEL_AVX2 52 +#define SK_CPU_SSE_LEVEL_SKX 60 + +// Are we in GCC/Clang? +#ifndef SK_CPU_SSE_LEVEL +// These checks must be done in descending order to ensure we set the highest +// available SSE level. +#if defined(__AVX512F__) && defined(__AVX512DQ__) && defined(__AVX512CD__) && \ + defined(__AVX512BW__) && defined(__AVX512VL__) +#define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_SKX +#elif defined(__AVX2__) +#define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_AVX2 +#elif defined(__AVX__) +#define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_AVX +#elif defined(__SSE4_2__) +#define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_SSE42 +#elif defined(__SSE4_1__) +#define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_SSE41 +#elif defined(__SSSE3__) +#define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_SSSE3 +#elif defined(__SSE3__) +#define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_SSE3 +#elif defined(__SSE2__) +#define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_SSE2 +#endif +#endif + +// Are we in VisualStudio? +#ifndef SK_CPU_SSE_LEVEL +// These checks must be done in descending order to ensure we set the highest +// available SSE level. 64-bit intel guarantees at least SSE2 support. +#if defined(__AVX512F__) && defined(__AVX512DQ__) && defined(__AVX512CD__) && \ + defined(__AVX512BW__) && defined(__AVX512VL__) +#define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_SKX +#elif defined(__AVX2__) +#define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_AVX2 +#elif defined(__AVX__) +#define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_AVX +#elif defined(_M_X64) || defined(_M_AMD64) +#define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_SSE2 +#elif defined(_M_IX86_FP) +#if _M_IX86_FP >= 2 +#define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_SSE2 +#elif _M_IX86_FP == 1 +#define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_SSE1 +#endif +#endif +#endif + +inline void RGBA_to_BGRA_portable(uint32_t* dst, const uint32_t* src, + int height, int src_stride, int dst_stride) { + auto width = std::min(src_stride, dst_stride); + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + uint8_t a = (src[x] >> 24) & 0xFF, b = (src[x] >> 16) & 0xFF, + g = (src[x] >> 8) & 0xFF, r = (src[x] >> 0) & 0xFF; + dst[x] = (uint32_t)a << 24 | (uint32_t)r << 16 | (uint32_t)g << 8 | + (uint32_t)b << 0; + } + + src += src_stride; + dst += dst_stride; + } +} + +#if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SKX + +inline void RGBA_to_BGRA_SKX(uint32_t* dst, const uint32_t* src, int height, + int src_stride, int dst_stride) { + const uint8_t mask[64] = {2, 1, 0, 3, 6, 5, 4, 7, 10, 9, 8, 11, 14, + 13, 12, 15, 2, 1, 0, 3, 6, 5, 4, 7, 10, 9, + 8, 11, 14, 13, 12, 15, 2, 1, 0, 3, 6, 5, 4, + 7, 10, 9, 8, 11, 14, 13, 12, 15, 2, 1, 0, 3, + 6, 5, 4, 7, 10, 9, 8, 11, 14, 13, 12, 15}; + const __m512i swapRB = _mm512_loadu_si512(mask); + + auto width = std::min(src_stride, dst_stride); + + for (int y = 0; y < height; y++) { + auto cw = width; + auto rptr = src; + auto dptr = dst; + while (cw >= 16) { + __m512i rgba = _mm512_loadu_si512((const __m512i*)rptr); + __m512i bgra = _mm512_shuffle_epi8(rgba, swapRB); + _mm512_storeu_si512((__m512i*)dptr, bgra); + + rptr += 16; + dptr += 16; + cw -= 16; + } + + for (auto x = 0; x < cw; x++) { + uint8_t a = (rptr[x] >> 24) & 0xFF, b = (rptr[x] >> 16) & 0xFF, + g = (rptr[x] >> 8) & 0xFF, r = (rptr[x] >> 0) & 0xFF; + dptr[x] = (uint32_t)a << 24 | (uint32_t)r << 16 | (uint32_t)g << 8 | + (uint32_t)b << 0; + } + + src += src_stride; + dst += dst_stride; + } +} + +#endif + +#if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_AVX2 + +inline void RGBA_to_BGRA_AVX2(uint32_t* dst, const uint32_t* src, int height, + int src_stride, int dst_stride) { + const __m256i swapRB = + _mm256_setr_epi8(2, 1, 0, 3, 6, 5, 4, 7, 10, 9, 8, 11, 14, 13, 12, 15, 2, + 1, 0, 3, 6, 5, 4, 7, 10, 9, 8, 11, 14, 13, 12, 15); + + auto width = std::min(src_stride, dst_stride); + + for (int y = 0; y < height; y++) { + auto cw = width; + auto rptr = src; + auto dptr = dst; + while (cw >= 8) { + __m256i rgba = _mm256_loadu_si256((const __m256i*)rptr); + __m256i bgra = _mm256_shuffle_epi8(rgba, swapRB); + _mm256_storeu_si256((__m256i*)dptr, bgra); + + rptr += 8; + dptr += 8; + cw -= 8; + } + + for (auto x = 0; x < cw; x++) { + uint8_t a = (rptr[x] >> 24) & 0xFF, b = (rptr[x] >> 16) & 0xFF, + g = (rptr[x] >> 8) & 0xFF, r = (rptr[x] >> 0) & 0xFF; + dptr[x] = (uint32_t)a << 24 | (uint32_t)r << 16 | (uint32_t)g << 8 | + (uint32_t)b << 0; + } + + src += src_stride; + dst += dst_stride; + } +} + +#endif + +inline void RGBA_to_BGRA(uint32_t* dst, const uint32_t* src, int height, + int src_stride, int dst_stride) { + static cpuid::cpuinfo info; + +#if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SKX + if (info.has_avx512_f() && info.has_avx512_dq() && info.has_avx512_cd() && + info.has_avx512_bw() && info.has_avx512_vl()) { + return RGBA_to_BGRA_SKX(dst, src, height, src_stride, dst_stride); + } +#endif + +#if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_AVX2 + if (info.has_avx2()) { + return RGBA_to_BGRA_AVX2(dst, src, height, src_stride, dst_stride); + } +#endif + + RGBA_to_BGRA_portable(dst, src, height, src_stride, dst_stride); +} diff --git a/flutter_inappwebview_windows/windows/flutter_inappwebview_windows_plugin.cpp b/flutter_inappwebview_windows/windows/flutter_inappwebview_windows_plugin.cpp new file mode 100644 index 00000000..a616a89b --- /dev/null +++ b/flutter_inappwebview_windows/windows/flutter_inappwebview_windows_plugin.cpp @@ -0,0 +1,37 @@ +#include "flutter_inappwebview_windows_plugin.h" + +#include + +#include "cookie_manager.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" +#include "webview_environment/webview_environment_manager.h" + +#pragma comment(lib, "Shlwapi.lib") +#pragma comment(lib, "dxgi.lib") +#pragma comment(lib, "d3d11.lib") + +namespace flutter_inappwebview_plugin +{ + // static + void FlutterInappwebviewWindowsPlugin::RegisterWithRegistrar( + flutter::PluginRegistrarWindows* registrar) + { + auto plugin = std::make_unique(registrar); + registrar->AddPlugin(std::move(plugin)); + } + + FlutterInappwebviewWindowsPlugin::FlutterInappwebviewWindowsPlugin(flutter::PluginRegistrarWindows* registrar) + : registrar(registrar) + { + webViewEnvironmentManager = std::make_unique(this); + inAppWebViewManager = std::make_unique(this); + inAppBrowserManager = std::make_unique(this); + headlessInAppWebViewManager = std::make_unique(this); + cookieManager = std::make_unique(this); + } + + FlutterInappwebviewWindowsPlugin::~FlutterInappwebviewWindowsPlugin() + {} +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/flutter_inappwebview_windows_plugin.h b/flutter_inappwebview_windows/windows/flutter_inappwebview_windows_plugin.h new file mode 100644 index 00000000..9d0bd403 --- /dev/null +++ b/flutter_inappwebview_windows/windows/flutter_inappwebview_windows_plugin.h @@ -0,0 +1,35 @@ +#ifndef FLUTTER_PLUGIN_FLUTTER_INAPPWEBVIEW_PLUGIN_PLUGIN_H_ +#define FLUTTER_PLUGIN_FLUTTER_INAPPWEBVIEW_PLUGIN_PLUGIN_H_ + +#include +#include + +namespace flutter_inappwebview_plugin +{ + class WebViewEnvironmentManager; + class InAppWebViewManager; + class InAppBrowserManager; + class HeadlessInAppWebViewManager; + class CookieManager; + + class FlutterInappwebviewWindowsPlugin : public flutter::Plugin { + public: + flutter::PluginRegistrarWindows* registrar; + std::unique_ptr webViewEnvironmentManager; + std::unique_ptr inAppWebViewManager; + std::unique_ptr inAppBrowserManager; + std::unique_ptr headlessInAppWebViewManager; + std::unique_ptr cookieManager; + + static void RegisterWithRegistrar(flutter::PluginRegistrarWindows* registrar); + + FlutterInappwebviewWindowsPlugin(flutter::PluginRegistrarWindows* registrar); + + virtual ~FlutterInappwebviewWindowsPlugin(); + + // Disallow copy and assign. + FlutterInappwebviewWindowsPlugin(const FlutterInappwebviewWindowsPlugin&) = delete; + FlutterInappwebviewWindowsPlugin& operator=(const FlutterInappwebviewWindowsPlugin&) = delete; + }; +} +#endif // FLUTTER_PLUGIN_FLUTTER_INAPPWEBVIEW_PLUGIN_PLUGIN_H_ diff --git a/flutter_inappwebview_windows/windows/flutter_inappwebview_windows_plugin_c_api.cpp b/flutter_inappwebview_windows/windows/flutter_inappwebview_windows_plugin_c_api.cpp new file mode 100644 index 00000000..9fc6f673 --- /dev/null +++ b/flutter_inappwebview_windows/windows/flutter_inappwebview_windows_plugin_c_api.cpp @@ -0,0 +1,13 @@ +#include "include/flutter_inappwebview_windows/flutter_inappwebview_windows_plugin_c_api.h" + +#include + +#include "flutter_inappwebview_windows_plugin.h" + +void FlutterInappwebviewWindowsPluginCApiRegisterWithRegistrar( + FlutterDesktopPluginRegistrarRef registrar) +{ + flutter_inappwebview_plugin::FlutterInappwebviewWindowsPlugin::RegisterWithRegistrar( + flutter::PluginRegistrarManager::GetInstance() + ->GetRegistrar(registrar)); +} diff --git a/flutter_inappwebview_windows/windows/headless_in_app_webview/headless_in_app_webview.cpp b/flutter_inappwebview_windows/windows/headless_in_app_webview/headless_in_app_webview.cpp new file mode 100644 index 00000000..fc2ff2c1 --- /dev/null +++ b/flutter_inappwebview_windows/windows/headless_in_app_webview/headless_in_app_webview.cpp @@ -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 webView) + : plugin(plugin), id(params.id), + webView(std::move(webView)), + channelDelegate(std::make_unique(this, plugin->registrar->messenger())) + { + prepare(params); + ShowWindow(parentWindow, SW_HIDE); + } + + void HeadlessInAppWebView::prepare(const HeadlessInAppWebViewCreationParams& params) + { + if (!webView) { + return; + } + } + + void HeadlessInAppWebView::setSize(const std::shared_ptr size) const + { + if (!webView) { + return; + } + RECT rect = { + 0, 0, (LONG)size->width, (LONG)size->height + }; + webView->webViewController->put_Bounds(rect); + } + + std::shared_ptr HeadlessInAppWebView::getSize() const + { + if (!webView) { + return std::make_shared(-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((double)width, (double)height); + } + + HeadlessInAppWebView::~HeadlessInAppWebView() + { + debugLog("dealloc HeadlessInAppWebView"); + HWND parentWindow = nullptr; + if (webView && webView->webViewController) { + webView->webViewController->get_ParentWindow(&parentWindow); + } + webView = nullptr; + if (parentWindow) { + DestroyWindow(parentWindow); + } + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/headless_in_app_webview/headless_in_app_webview.h b/flutter_inappwebview_windows/windows/headless_in_app_webview/headless_in_app_webview.h new file mode 100644 index 00000000..0e419602 --- /dev/null +++ b/flutter_inappwebview_windows/windows/headless_in_app_webview/headless_in_app_webview.h @@ -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 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 webView; + std::unique_ptr channelDelegate; + + HeadlessInAppWebView(const FlutterInappwebviewWindowsPlugin* plugin, const HeadlessInAppWebViewCreationParams& params, const HWND parentWindow, std::unique_ptr webView); + ~HeadlessInAppWebView(); + + void prepare(const HeadlessInAppWebViewCreationParams& params); + void setSize(const std::shared_ptr size) const; + std::shared_ptr getSize() const; + }; +} +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_HEADLESS_IN_APP_WEBVIEW_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/headless_in_app_webview/headless_in_app_webview_manager.cpp b/flutter_inappwebview_windows/windows/headless_in_app_webview/headless_in_app_webview_manager.cpp new file mode 100644 index 00000000..ff235315 --- /dev/null +++ b/flutter_inappwebview_windows/windows/headless_in_app_webview/headless_in_app_webview_manager.cpp @@ -0,0 +1,142 @@ +#include +#include +#include +#include +#include + +#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/map.h" +#include "../utils/string.h" +#include "../utils/vector.h" +#include "../webview_environment/webview_environment_manager.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& method_call, + std::unique_ptr> result) + { + auto* arguments = std::get_if(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> result) + { + auto result_ = std::shared_ptr>(std::move(result)); + + auto id = get_fl_map_value(*arguments, "id"); + auto params = get_fl_map_value(*arguments, "params"); + + auto initialSize = std::make_shared(get_fl_map_value(params, "initialSize")); + + auto settingsMap = get_fl_map_value(params, "initialSettings"); + auto urlRequestMap = get_optional_fl_map_value(params, "initialUrlRequest"); + auto initialFile = get_optional_fl_map_value(params, "initialFile"); + auto initialDataMap = get_optional_fl_map_value(params, "initialData"); + auto initialUserScriptList = get_optional_fl_map_value(params, "initialUserScripts"); + auto webViewEnvironmentId = get_optional_fl_map_value(params, "webViewEnvironmentId"); + + 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); + + auto webViewEnvironment = webViewEnvironmentId.has_value() && map_contains(plugin->webViewEnvironmentManager->webViewEnvironments, webViewEnvironmentId.value()) + ? plugin->webViewEnvironmentManager->webViewEnvironments.at(webViewEnvironmentId.value()).get() : nullptr; + + InAppWebView::createInAppWebViewEnv(hwnd, false, webViewEnvironment, + [=](wil::com_ptr webViewEnv, + wil::com_ptr webViewController, + wil::com_ptr webViewCompositionController) + { + if (webViewEnv && webViewController) { + auto initialSettings = std::make_unique(settingsMap); + std::optional>> initialUserScripts = initialUserScriptList.has_value() ? + functional_map(initialUserScriptList.value(), [](const flutter::EncodableValue& map) { return std::make_shared(std::get(map)); }) : + std::optional>>{}; + + InAppWebViewCreationParams params = { + id, + std::move(initialSettings), + initialUserScripts + }; + + auto inAppWebView = std::make_unique(plugin, params, hwnd, + std::move(webViewEnv), std::move(webViewController), nullptr + ); + + HeadlessInAppWebViewCreationParams headlessParams = { + id, + std::move(initialSize) + }; + + auto headlessInAppWebView = std::make_unique(plugin, + headlessParams, + hwnd, + std::move(inAppWebView)); + + headlessInAppWebView->webView->initChannel(std::nullopt, std::nullopt); + + if (headlessInAppWebView->channelDelegate) { + headlessInAppWebView->channelDelegate->onWebViewCreated(); + } + + std::optional> urlRequest = urlRequestMap.has_value() ? std::make_shared(urlRequestMap.value()) : std::optional>{}; + 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(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; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/headless_in_app_webview/headless_in_app_webview_manager.h b/flutter_inappwebview_windows/windows/headless_in_app_webview/headless_in_app_webview_manager.h new file mode 100644 index 00000000..1e52c51f --- /dev/null +++ b/flutter_inappwebview_windows/windows/headless_in_app_webview/headless_in_app_webview_manager.h @@ -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 +#include +#include +#include +#include + +#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> webViews; + + HeadlessInAppWebViewManager(const FlutterInappwebviewWindowsPlugin* plugin); + ~HeadlessInAppWebViewManager(); + + void HandleMethodCall( + const flutter::MethodCall& method_call, + std::unique_ptr> result); + + void run(const flutter::EncodableMap* arguments, std::unique_ptr> result); + private: + WNDCLASS windowClass_ = {}; + }; +} +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_HEADLESS_IN_APP_WEBVIEW_MANAGER_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/headless_in_app_webview/headless_webview_channel_delegate.cpp b/flutter_inappwebview_windows/windows/headless_in_app_webview/headless_webview_channel_delegate.cpp new file mode 100644 index 00000000..5aadab56 --- /dev/null +++ b/flutter_inappwebview_windows/windows/headless_in_app_webview/headless_webview_channel_delegate.cpp @@ -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& method_call, + std::unique_ptr> result) + { + if (!webView) { + result->Success(); + return; + } + + // auto& arguments = std::get(*method_call.arguments()); + auto& methodName = method_call.method_name(); + + if (string_equals(methodName, "dispose")) { + if (webView->plugin && webView->plugin->headlessInAppWebViewManager) { + std::map>& 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; + } +} diff --git a/flutter_inappwebview_windows/windows/headless_in_app_webview/headless_webview_channel_delegate.h b/flutter_inappwebview_windows/windows/headless_in_app_webview/headless_webview_channel_delegate.h new file mode 100644 index 00000000..15d3a8ed --- /dev/null +++ b/flutter_inappwebview_windows/windows/headless_in_app_webview/headless_webview_channel_delegate.h @@ -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 + +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& method_call, + std::unique_ptr> result); + + void onWebViewCreated() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_HEADLESS_WEBVIEW_CHANNEL_DELEGATE_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser.cpp b/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser.cpp new file mode 100644 index 00000000..71f5bd39 --- /dev/null +++ b/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser.cpp @@ -0,0 +1,254 @@ +#include + +#include "../utils/log.h" +#include "../utils/strconv.h" +#include "../webview_environment/webview_environment_manager.h" +#include "in_app_browser.h" +#include "in_app_browser_manager.h" + +namespace flutter_inappwebview_plugin +{ + InAppBrowser::InAppBrowser(const FlutterInappwebviewWindowsPlugin* plugin, const InAppBrowserCreationParams& params) + : plugin(plugin), + m_hInstance(GetModuleHandle(nullptr)), + id(params.id), + settings(params.initialSettings), + channelDelegate(std::make_unique(id, plugin->registrar->messenger())) + { + + WNDCLASS wndClass = {}; + wndClass.lpszClassName = InAppBrowser::CLASS_NAME; + wndClass.hInstance = m_hInstance; + wndClass.hIcon = LoadIcon(NULL, IDI_WINLOGO); + wndClass.hCursor = LoadCursor(NULL, IDC_ARROW); + wndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); + wndClass.lpfnWndProc = InAppBrowser::WndProc; + + RegisterClass(&wndClass); + + auto parentWindow = plugin->registrar->GetView()->GetNativeWindow(); + RECT bounds; + GetWindowRect(parentWindow, &bounds); + + auto x = CW_USEDEFAULT; + auto y = CW_USEDEFAULT; + auto width = bounds.right - bounds.left; + auto height = bounds.bottom - bounds.top; + + if (settings->windowFrame) { + x = (int)settings->windowFrame->x; + y = (int)settings->windowFrame->y; + width = (int)settings->windowFrame->width; + height = (int)settings->windowFrame->height; + } + + m_hWnd = CreateWindowEx( + WS_EX_LAYERED, // Optional window styles. + wndClass.lpszClassName, // Window class + + settings->toolbarTopFixedTitle.empty() ? L"" : utf8_to_wide(settings->toolbarTopFixedTitle).c_str(), // Window text + + settings->windowType == InAppBrowserWindowType::window ? WS_OVERLAPPEDWINDOW : (WS_CHILDWINDOW | WS_OVERLAPPEDWINDOW), // Window style + + // Position + x, y, + // Size + width, height, + + settings->windowType == InAppBrowserWindowType::window ? nullptr : parentWindow, // Parent window + nullptr, // Menu + wndClass.hInstance, // Instance handle + this // Additional application data + ); + + SetLayeredWindowAttributes(m_hWnd, 0, (BYTE)(255 * settings->windowAlphaValue), LWA_ALPHA); + + ShowWindow(m_hWnd, settings->hidden ? SW_HIDE : SW_SHOW); + + InAppWebViewCreationParams webViewParams = { + id, + params.initialWebViewSettings, + params.initialUserScripts + }; + + auto webViewEnvironment = params.webViewEnvironmentId.has_value() && map_contains(plugin->webViewEnvironmentManager->webViewEnvironments, params.webViewEnvironmentId.value()) + ? plugin->webViewEnvironmentManager->webViewEnvironments.at(params.webViewEnvironmentId.value()).get() : nullptr; + + InAppWebView::createInAppWebViewEnv(m_hWnd, false, webViewEnvironment, + [this, params, webViewParams](wil::com_ptr webViewEnv, wil::com_ptr webViewController, wil::com_ptr webViewCompositionController) -> void + { + if (webViewEnv && webViewController) { + webView = std::make_unique(this, this->plugin, webViewParams, m_hWnd, std::move(webViewEnv), std::move(webViewController), nullptr); + webView->initChannel(std::nullopt, InAppBrowser::METHOD_CHANNEL_NAME_PREFIX + id); + + if (channelDelegate) { + channelDelegate->onBrowserCreated(); + } + + if (params.urlRequest.has_value()) { + webView->loadUrl(params.urlRequest.value()); + } + else if (params.assetFilePath.has_value()) { + webView->loadFile(params.assetFilePath.value()); + } + else if (params.data.has_value()) { + webView->loadData(params.data.value()); + } + } + else { + std::cerr << "Cannot create the InAppWebView instance!" << std::endl; + close(); + } + }); + } + + void InAppBrowser::close() const + { + DestroyWindow(m_hWnd); + } + + void InAppBrowser::show() const + { + ShowWindow(m_hWnd, SW_SHOW); + } + + void InAppBrowser::hide() const + { + ShowWindow(m_hWnd, SW_HIDE); + } + + bool InAppBrowser::isHidden() const + { + return !IsWindowVisible(m_hWnd); + } + + void InAppBrowser::setSettings(const std::shared_ptr newSettings, const flutter::EncodableMap& newSettingsMap) + { + if (webView) { + webView->setSettings(std::make_shared(newSettingsMap), newSettingsMap); + } + + if (fl_map_contains_not_null(newSettingsMap, "hidden") && settings->hidden != newSettings->hidden) { + newSettings->hidden ? hide() : show(); + } + + if (fl_map_contains_not_null(newSettingsMap, "toolbarTopFixedTitle") && !string_equals(settings->toolbarTopFixedTitle, newSettings->toolbarTopFixedTitle) && !newSettings->toolbarTopFixedTitle.empty()) { + SetWindowText(m_hWnd, utf8_to_wide(newSettings->toolbarTopFixedTitle).c_str()); + } + + if (fl_map_contains_not_null(newSettingsMap, "windowAlphaValue") && settings->windowAlphaValue != newSettings->windowAlphaValue) { + SetLayeredWindowAttributes(m_hWnd, 0, (BYTE)(255 * newSettings->windowAlphaValue), LWA_ALPHA); + } + + if (fl_map_contains_not_null(newSettingsMap, "windowFrame")) { + auto x = (int)newSettings->windowFrame->x; + auto y = (int)newSettings->windowFrame->y; + auto width = (int)newSettings->windowFrame->width; + auto height = (int)newSettings->windowFrame->height; + MoveWindow(m_hWnd, x, y, width, height, true); + } + + settings = newSettings; + } + + flutter::EncodableValue InAppBrowser::getSettings() const + { + if (!settings || !webView) { + return make_fl_value(); + } + + auto encodableMap = settings->getRealSettings(this); + encodableMap.merge(std::get(webView->getSettings())); + return encodableMap; + } + + void InAppBrowser::didChangeTitle(const std::optional& title) const + { + if (title.has_value() && settings->toolbarTopFixedTitle.empty()) { + SetWindowText(m_hWnd, utf8_to_wide(title.value()).c_str()); + } + } + + LRESULT CALLBACK InAppBrowser::WndProc( + HWND window, + UINT message, + WPARAM wparam, + LPARAM lparam + ) noexcept + { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, reinterpret_cast(window_struct->lpCreateParams)); + } + else if (InAppBrowser* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); + } + + LRESULT InAppBrowser::MessageHandler( + HWND hwnd, + UINT message, + WPARAM wparam, + LPARAM lparam + ) noexcept + { + switch (message) { + case WM_DESTROY: { + // might receive multiple WM_DESTROY messages. + if (!destroyed_) { + destroyed_ = true; + + webView.reset(); + + if (channelDelegate) { + channelDelegate->onExit(); + } + + if (plugin && plugin->inAppBrowserManager) { + plugin->inAppBrowserManager->browsers.erase(id); + } + } + return 0; + } + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + return 0; + } + case WM_SIZE: { + RECT bounds; + GetClientRect(hwnd, &bounds); + if (webView) { + webView->webViewController->put_Bounds(bounds); + } + return 0; + } + case WM_ACTIVATE: { + return 0; + } + } + + return DefWindowProc(hwnd, message, wparam, lparam); + } + + InAppBrowser* InAppBrowser::GetThisFromHandle(HWND const window) noexcept + { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); + } + + InAppBrowser::~InAppBrowser() + { + debugLog("dealloc InAppBrowser"); + webView.reset(); + SetWindowLongPtr(m_hWnd, GWLP_USERDATA, 0); + plugin = nullptr; + } + +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser.h b/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser.h new file mode 100644 index 00000000..3c1181c0 --- /dev/null +++ b/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser.h @@ -0,0 +1,71 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_BROWSER_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_BROWSER_H_ + +#include +#include +#include + +#include "../flutter_inappwebview_windows_plugin.h" +#include "../in_app_webview/in_app_webview.h" +#include "../in_app_webview/in_app_webview_settings.h" +#include "../types/url_request.h" +#include "in_app_browser_channel_delegate.h" +#include "in_app_browser_settings.h" + +namespace flutter_inappwebview_plugin +{ + struct InAppBrowserCreationParams + { + const std::string id; + const std::optional> urlRequest; + const std::optional assetFilePath; + const std::optional data; + const std::shared_ptr initialSettings; + const std::shared_ptr initialWebViewSettings; + const std::optional>> initialUserScripts; + const std::optional webViewEnvironmentId; + }; + + class InAppBrowser { + public: + static inline const std::string METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappbrowser_"; + static inline const wchar_t* CLASS_NAME = L"InAppBrowser"; + + static LRESULT CALLBACK WndProc(HWND window, + UINT message, + WPARAM wparam, + LPARAM lparam) noexcept; + + const FlutterInappwebviewWindowsPlugin* plugin; + const std::string id; + std::unique_ptr webView; + std::unique_ptr channelDelegate; + std::shared_ptr settings; + + InAppBrowser(const FlutterInappwebviewWindowsPlugin* plugin, const InAppBrowserCreationParams& params); + ~InAppBrowser(); + + void close() const; + void show() const; + void hide() const; + bool isHidden() const; + void setSettings(const std::shared_ptr newSettings, const flutter::EncodableMap& newSettingsMap); + flutter::EncodableValue getSettings() const; + + void didChangeTitle(const std::optional& title) const; + HWND getHWND() const + { + return m_hWnd; + } + private: + const HINSTANCE m_hInstance; + HWND m_hWnd; + bool destroyed_ = false; + static InAppBrowser* GetThisFromHandle(HWND window) noexcept; + LRESULT MessageHandler(HWND window, + UINT message, + WPARAM wparam, + LPARAM lparam) noexcept; + }; +} +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_BROWSER_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser_channel_delegate.cpp b/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser_channel_delegate.cpp new file mode 100644 index 00000000..34aa966e --- /dev/null +++ b/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser_channel_delegate.cpp @@ -0,0 +1,37 @@ +#include "../utils/log.h" +#include "in_app_browser.h" +#include "in_app_browser_channel_delegate.h" + +namespace flutter_inappwebview_plugin +{ + InAppBrowserChannelDelegate::InAppBrowserChannelDelegate(const std::string& id, flutter::BinaryMessenger* messenger) + : ChannelDelegate(messenger, InAppBrowser::METHOD_CHANNEL_NAME_PREFIX + id) + {} + + void InAppBrowserChannelDelegate::HandleMethodCall(const flutter::MethodCall& method_call, + std::unique_ptr> result) + { + result->NotImplemented(); + } + + void InAppBrowserChannelDelegate::onBrowserCreated() const + { + if (!channel) { + return; + } + channel->InvokeMethod("onBrowserCreated", nullptr); + } + + void InAppBrowserChannelDelegate::onExit() const + { + if (!channel) { + return; + } + channel->InvokeMethod("onExit", nullptr); + } + + InAppBrowserChannelDelegate::~InAppBrowserChannelDelegate() + { + debugLog("dealloc InAppBrowserChannelDelegate"); + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser_channel_delegate.h b/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser_channel_delegate.h new file mode 100644 index 00000000..d0032cff --- /dev/null +++ b/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser_channel_delegate.h @@ -0,0 +1,26 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_BROWSER_CHANNEL_DELEGATE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_BROWSER_CHANNEL_DELEGATE_H_ + +#include +#include + +#include "../types/channel_delegate.h" + +namespace flutter_inappwebview_plugin +{ + class InAppBrowserChannelDelegate : public ChannelDelegate + { + public: + InAppBrowserChannelDelegate(const std::string& id, flutter::BinaryMessenger* messenger); + ~InAppBrowserChannelDelegate(); + + void HandleMethodCall( + const flutter::MethodCall& method_call, + std::unique_ptr> result); + + void onBrowserCreated() const; + void onExit() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_BROWSER_CHANNEL_DELEGATE_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser_manager.cpp b/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser_manager.cpp new file mode 100644 index 00000000..46130067 --- /dev/null +++ b/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser_manager.cpp @@ -0,0 +1,88 @@ +#include +#include +#include + +#include "../in_app_webview/in_app_webview_settings.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 "in_app_browser_manager.h" +#include "in_app_browser_settings.h" + +namespace flutter_inappwebview_plugin +{ + InAppBrowserManager::InAppBrowserManager(const FlutterInappwebviewWindowsPlugin* plugin) + : plugin(plugin), ChannelDelegate(plugin->registrar->messenger(), InAppBrowserManager::METHOD_CHANNEL_NAME) + {} + + void InAppBrowserManager::HandleMethodCall(const flutter::MethodCall& method_call, + std::unique_ptr> result) + { + auto* arguments = std::get_if(method_call.arguments()); + auto& methodName = method_call.method_name(); + + if (string_equals(methodName, "open")) { + createInAppBrowser(arguments); + result->Success(true); + } + else if (string_equals(methodName, "openWithSystemBrowser")) { + auto url = get_fl_map_value(*arguments, "url"); + + int status = static_cast(reinterpret_cast( + ShellExecute(nullptr, TEXT("open"), utf8_to_wide(url).c_str(), + nullptr, nullptr, SW_SHOWNORMAL))); + + // Anything >32 indicates success. + // https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shellexecutea#return-value + if (status <= 32) { + std::cerr << "Failed to open " << url << ": ShellExecute error code " << status << std::endl; + } + } + else { + result->NotImplemented(); + } + } + + void InAppBrowserManager::createInAppBrowser(const flutter::EncodableMap* arguments) + { + auto id = get_fl_map_value(*arguments, "id"); + auto urlRequestMap = get_optional_fl_map_value(*arguments, "urlRequest"); + auto assetFilePath = get_optional_fl_map_value(*arguments, "assetFilePath"); + auto data = get_optional_fl_map_value(*arguments, "data"); + auto initialUserScriptList = get_optional_fl_map_value(*arguments, "initialUserScripts"); + auto webViewEnvironmentId = get_optional_fl_map_value(*arguments, "webViewEnvironmentId"); + + std::optional> urlRequest = urlRequestMap.has_value() ? std::make_shared(urlRequestMap.value()) : std::optional>{}; + + auto settingsMap = get_fl_map_value(*arguments, "settings"); + auto initialSettings = std::make_unique(settingsMap); + auto initialWebViewSettings = std::make_unique(settingsMap); + std::optional>> initialUserScripts = initialUserScriptList.has_value() ? + functional_map(initialUserScriptList.value(), [](const flutter::EncodableValue& map) { return std::make_shared(std::get(map)); }) : + std::optional>>{}; + + InAppBrowserCreationParams params = { + id, + urlRequest, + assetFilePath, + data, + std::move(initialSettings), + std::move(initialWebViewSettings), + initialUserScripts, + webViewEnvironmentId + }; + + auto inAppBrowser = std::make_unique(plugin, params); + browsers.insert({ id, std::move(inAppBrowser) }); + } + + InAppBrowserManager::~InAppBrowserManager() + { + debugLog("dealloc InAppBrowserManager"); + browsers.clear(); + plugin = nullptr; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser_manager.h b/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser_manager.h new file mode 100644 index 00000000..c8b53a27 --- /dev/null +++ b/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser_manager.h @@ -0,0 +1,33 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_BROWSER_MANAGER_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_BROWSER_MANAGER_H_ + +#include +#include +#include +#include + +#include "../flutter_inappwebview_windows_plugin.h" +#include "../types/channel_delegate.h" +#include "in_app_browser.h" + +namespace flutter_inappwebview_plugin +{ + class InAppBrowserManager : public ChannelDelegate + { + public: + static inline const std::string METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappbrowser"; + + const FlutterInappwebviewWindowsPlugin* plugin; + std::map> browsers; + + InAppBrowserManager(const FlutterInappwebviewWindowsPlugin* plugin); + ~InAppBrowserManager(); + + void HandleMethodCall( + const flutter::MethodCall& method_call, + std::unique_ptr> result); + + void createInAppBrowser(const flutter::EncodableMap* arguments); + }; +} +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_BROWSER_MANAGER_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser_settings.cpp b/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser_settings.cpp new file mode 100644 index 00000000..dea7f862 --- /dev/null +++ b/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser_settings.cpp @@ -0,0 +1,76 @@ +#include "../utils/flutter.h" +#include "../utils/log.h" +#include "../utils/string.h" +#include "in_app_browser_settings.h" + +#include "in_app_browser.h" + +namespace flutter_inappwebview_plugin +{ + namespace + { + InAppBrowserWindowType inAppBrowserWindowTypeFromString(const std::string& s) + { + if (string_equals(s, "CHILD")) { + return InAppBrowserWindowType::child; + } + return InAppBrowserWindowType::window; + } + + std::string inAppBrowserWindowTypeToString(const InAppBrowserWindowType& t) + { + switch (t) { + case InAppBrowserWindowType::child: + return "CHILD"; + default: + return "WINDOW"; + } + } + } + + InAppBrowserSettings::InAppBrowserSettings() {}; + + InAppBrowserSettings::InAppBrowserSettings(const flutter::EncodableMap& encodableMap) + { + hidden = get_fl_map_value(encodableMap, "hidden", hidden); + windowType = inAppBrowserWindowTypeFromString(get_fl_map_value(encodableMap, "windowType", inAppBrowserWindowTypeToString(InAppBrowserWindowType::window))); + toolbarTopFixedTitle = get_fl_map_value(encodableMap, "toolbarTopFixedTitle", toolbarTopFixedTitle); + windowAlphaValue = get_fl_map_value(encodableMap, "windowAlphaValue", windowAlphaValue); + auto windowFrameMap = get_optional_fl_map_value(encodableMap, "windowFrame"); + if (windowFrameMap.has_value()) { + windowFrame = std::make_shared(windowFrameMap.value()); + } + } + + flutter::EncodableMap InAppBrowserSettings::toEncodableMap() const + { + return flutter::EncodableMap{ + {"hidden", hidden}, + {"windowType", inAppBrowserWindowTypeToString(windowType)}, + {"toolbarTopFixedTitle", toolbarTopFixedTitle}, + {"windowAlphaValue", windowAlphaValue}, + {"windowFrame", windowFrame ? windowFrame->toEncodableMap() : make_fl_value()}, + }; + } + + flutter::EncodableMap InAppBrowserSettings::getRealSettings(const InAppBrowser* inAppBrowser) const + { + auto settingsMap = toEncodableMap(); + settingsMap["hidden"] = inAppBrowser->isHidden(); + + BYTE alphaValue = 0; + GetLayeredWindowAttributes(inAppBrowser->getHWND(), nullptr, &alphaValue, nullptr); + settingsMap["windowAlphaValue"] = (double)alphaValue; + + RECT position; + GetWindowRect(inAppBrowser->getHWND(), &position); + settingsMap["windowFrame"] = std::make_unique(position.left, position.top, position.right - position.left, position.bottom - position.top)->toEncodableMap(); + + return settingsMap; + } + + InAppBrowserSettings::~InAppBrowserSettings() + { + debugLog("dealloc InAppBrowserSettings"); + }; +} diff --git a/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser_settings.h b/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser_settings.h new file mode 100644 index 00000000..1243ca2b --- /dev/null +++ b/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser_settings.h @@ -0,0 +1,36 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_BROWSER_SETTINGS_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_BROWSER_SETTINGS_H_ + +#include +#include +#include + +#include "../types/rect.h" + +namespace flutter_inappwebview_plugin +{ + class InAppBrowser; + + enum InAppBrowserWindowType { + window, + child + }; + + class InAppBrowserSettings + { + public: + bool hidden = false; + InAppBrowserWindowType windowType = window; + std::string toolbarTopFixedTitle; + double windowAlphaValue = 1.0; + std::shared_ptr windowFrame; + + InAppBrowserSettings(); + InAppBrowserSettings(const flutter::EncodableMap& encodableMap); + ~InAppBrowserSettings(); + + flutter::EncodableMap toEncodableMap() const; + flutter::EncodableMap getRealSettings(const InAppBrowser* inAppBrowser) const; + }; +} +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_BROWSER_SETTINGS_H_ \ No newline at end of file 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 new file mode 100644 index 00000000..abfae2b1 --- /dev/null +++ b/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview.cpp @@ -0,0 +1,1398 @@ +#include +#include +#include +#include +#include +#include + +#include "../custom_platform_view/util/composition.desktop.interop.h" +#include "../plugin_scripts_js/javascript_bridge_js.h" +#include "../types/web_resource_error.h" +#include "../types/web_resource_request.h" +#include "../utils/log.h" +#include "../utils/map.h" +#include "../utils/strconv.h" +#include "../utils/string.h" +#include "../webview_environment/webview_environment_manager.h" +#include "in_app_webview.h" +#include "in_app_webview_manager.h" + +namespace flutter_inappwebview_plugin +{ + using namespace Microsoft::WRL; + + InAppWebView::InAppWebView(const FlutterInappwebviewWindowsPlugin* plugin, const InAppWebViewCreationParams& params, const HWND parentWindow, wil::com_ptr webViewEnv, + wil::com_ptr webViewController, + wil::com_ptr webViewCompositionController) + : plugin(plugin), id(params.id), + webViewEnv(std::move(webViewEnv)), webViewController(std::move(webViewController)), webViewCompositionController(std::move(webViewCompositionController)), + settings(params.initialSettings), userContentController(std::make_unique(this)) + { + if (failedAndLog(this->webViewController->get_CoreWebView2(webView.put()))) { + std::cerr << "Cannot create CoreWebView2." << std::endl; + } + + if (this->webViewCompositionController) { + if (!createSurface(parentWindow, plugin->inAppWebViewManager->compositor())) { + std::cerr << "Cannot create InAppWebView surface." << std::endl; + } + registerSurfaceEventHandlers(); + } + else { + this->webViewController->put_IsVisible(true); + // Resize WebView to fit the bounds of the parent window + RECT bounds; + GetClientRect(parentWindow, &bounds); + this->webViewController->put_Bounds(bounds); + } + + prepare(params); + } + + InAppWebView::InAppWebView(InAppBrowser* inAppBrowser, const FlutterInappwebviewWindowsPlugin* plugin, const InAppWebViewCreationParams& params, const HWND parentWindow, wil::com_ptr webViewEnv, + wil::com_ptr webViewController, + wil::com_ptr webViewCompositionController) + : InAppWebView(plugin, params, parentWindow, std::move(webViewEnv), std::move(webViewController), std::move(webViewCompositionController)) + { + this->inAppBrowser = inAppBrowser; + } + + void InAppWebView::createInAppWebViewEnv(const HWND parentWindow, const bool& willBeSurface, WebViewEnvironment* webViewEnvironment, std::function webViewEnv, + wil::com_ptr webViewController, + wil::com_ptr webViewCompositionController)> completionHandler) + { + auto callback = [parentWindow, willBeSurface, completionHandler](HRESULT result, wil::com_ptr env) -> HRESULT + { + if (failedAndLog(result) || !env) { + completionHandler(nullptr, nullptr, nullptr); + return E_FAIL; + } + + wil::com_ptr webViewEnv3; + if (willBeSurface && succeededOrLog(env->QueryInterface(IID_PPV_ARGS(&webViewEnv3)))) { + failedLog(webViewEnv3->CreateCoreWebView2CompositionController(parentWindow, Callback( + [completionHandler, env](HRESULT result, wil::com_ptr compositionController) -> HRESULT + { + wil::com_ptr webViewController = compositionController.try_query(); + + if (failedAndLog(result) || !webViewController) { + completionHandler(nullptr, nullptr, nullptr); + return E_FAIL; + } + + ICoreWebView2Controller3* webViewController3; + if (succeededOrLog(webViewController->QueryInterface(IID_PPV_ARGS(&webViewController3)))) { + webViewController3->put_BoundsMode(COREWEBVIEW2_BOUNDS_MODE_USE_RAW_PIXELS); + webViewController3->put_ShouldDetectMonitorScaleChanges(FALSE); + webViewController3->put_RasterizationScale(1.0); + } + + completionHandler(std::move(env), std::move(webViewController), std::move(compositionController)); + return S_OK; + } + ).Get())); + } + else { + failedLog(env->CreateCoreWebView2Controller(parentWindow, Callback( + [completionHandler, env](HRESULT result, wil::com_ptr controller) -> HRESULT + { + if (failedAndLog(result) || !controller) { + completionHandler(nullptr, nullptr, nullptr); + return E_FAIL; + } + + completionHandler(std::move(env), std::move(controller), nullptr); + return S_OK; + }).Get())); + } + return S_OK; + }; + + HRESULT hr; + if (webViewEnvironment && webViewEnvironment->getEnvironment()) { + hr = callback(S_OK, webViewEnvironment->getEnvironment()); + } + else { + hr = CreateCoreWebView2EnvironmentWithOptions( + nullptr, nullptr, nullptr, + Callback(callback).Get()); + } + + if (failedAndLog(hr)) { + completionHandler(nullptr, nullptr, nullptr); + } + } + + void InAppWebView::initChannel(const std::optional> viewId, const std::optional channelName) + { + if (viewId.has_value()) { + id = viewId.value(); + } + channelDelegate = channelName.has_value() ? std::make_unique(this, plugin->registrar->messenger(), channelName.value()) : + std::make_unique(this, plugin->registrar->messenger()); + } + + void InAppWebView::prepare(const InAppWebViewCreationParams& params) + { + if (!webView) { + return; + } + + wil::com_ptr webView2Settings; + auto hrWebView2Settings = webView->get_Settings(&webView2Settings); + if (succeededOrLog(hrWebView2Settings)) { + webView2Settings->put_IsScriptEnabled(settings->javaScriptEnabled); + webView2Settings->put_IsZoomControlEnabled(settings->supportZoom); + webView2Settings->put_AreDevToolsEnabled(settings->isInspectable); + webView2Settings->put_AreDefaultContextMenusEnabled(!settings->disableContextMenu); + + wil::com_ptr webView2Settings2; + if (succeededOrLog(webView2Settings->QueryInterface(IID_PPV_ARGS(&webView2Settings2)))) { + if (!settings->userAgent.empty()) { + webView2Settings2->put_UserAgent(utf8_to_wide(settings->userAgent).c_str()); + } + } + } + + wil::com_ptr webViewController2; + if (succeededOrLog(webViewController->QueryInterface(IID_PPV_ARGS(&webViewController2)))) { + if (!settings->transparentBackground) { + webViewController2->put_DefaultBackgroundColor({ 0, 255, 255, 255 }); + } + } + + // required to make Runtime events work + failedLog(webView->CallDevToolsProtocolMethod(L"Runtime.enable", L"{}", Callback( + [this](HRESULT errorCode, LPCWSTR returnObjectAsJson) + { + failedLog(errorCode); + return S_OK; + } + ).Get())); + + // required to make Page events work and to add User Scripts + failedLog(webView->CallDevToolsProtocolMethod(L"Page.enable", L"{}", Callback( + [this](HRESULT errorCode, LPCWSTR returnObjectAsJson) + { + failedLog(errorCode); + return S_OK; + } + ).Get())); + + failedLog(webView->CallDevToolsProtocolMethod(L"Page.getFrameTree", L"{}", Callback( + [this](HRESULT errorCode, LPCWSTR returnObjectAsJson) + { + if (succeededOrLog(errorCode)) { + auto treeJson = nlohmann::json::parse(wide_to_utf8(returnObjectAsJson)); + pageFrameId_ = treeJson["frameTree"]["frame"]["id"].get(); + } + return S_OK; + } + ).Get())); + + if (userContentController) { + userContentController->addPluginScript(std::move(createJavaScriptBridgePluginScript())); + if (params.initialUserScripts.has_value()) { + userContentController->addUserOnlyScripts(params.initialUserScripts.value()); + } + } + + registerEventHandlers(); + } + + void InAppWebView::registerEventHandlers() + { + if (!webView) { + return; + } + + failedLog(webView->add_NavigationStarting( + Callback( + [this](ICoreWebView2* sender, ICoreWebView2NavigationStartingEventArgs* args) + { + isLoading_ = true; + + if (!channelDelegate) { + args->put_Cancel(false); + return S_OK; + } + + wil::unique_cotaskmem_string uri = nullptr; + std::optional url = SUCCEEDED(args->get_Uri(&uri)) ? wide_to_utf8(uri.get()) : std::optional{}; + + wil::unique_cotaskmem_string requestMethod = nullptr; + wil::com_ptr requestHeaders = nullptr; + std::optional> headers = std::optional>{}; + if (SUCCEEDED(args->get_RequestHeaders(&requestHeaders))) { + headers = std::make_optional>({}); + wil::com_ptr 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(name.get()), wide_to_utf8(value.get()) }); + } + + BOOL hasNext = FALSE; + iterator->MoveNext(&hasNext); + } + + requestHeaders->GetHeader(L"Flutter-InAppWebView-Request-Method", &requestMethod); + requestHeaders->RemoveHeader(L"Flutter-InAppWebView-Request-Method"); + } + + std::optional method = requestMethod ? wide_to_utf8(requestMethod.get()) : std::optional{}; + + BOOL isUserInitiated; + if (FAILED(args->get_IsUserInitiated(&isUserInitiated))) { + isUserInitiated = FALSE; + } + + BOOL isRedirect; + if (FAILED(args->get_IsRedirected(&isRedirect))) { + isRedirect = FALSE; + } + + std::optional navigationType = std::nullopt; + wil::com_ptr args3; + if (SUCCEEDED(args->QueryInterface(IID_PPV_ARGS(&args3)))) { + COREWEBVIEW2_NAVIGATION_KIND navigationKind; + if (SUCCEEDED(args3->get_NavigationKind(&navigationKind))) { + switch (navigationKind) { + case COREWEBVIEW2_NAVIGATION_KIND_RELOAD: + navigationType = NavigationActionType::reload; + break; + case COREWEBVIEW2_NAVIGATION_KIND_BACK_OR_FORWARD: + navigationType = NavigationActionType::backForward; + break; + case COREWEBVIEW2_NAVIGATION_KIND_NEW_DOCUMENT: + if (isUserInitiated && !isRedirect) { + navigationType = NavigationActionType::linkActivated; + } + else { + navigationType = NavigationActionType::other; + } + break; + default: + navigationType = NavigationActionType::other; + } + } + } + + auto urlRequest = std::make_shared(url, method, headers, std::nullopt); + auto navigationAction = std::make_shared( + urlRequest, + true, + isRedirect, + navigationType + ); + + lastNavigationAction_ = navigationAction; + + UINT64 navigationId; + if (SUCCEEDED(args->get_NavigationId(&navigationId))) { + navigationActions_.insert({ navigationId, navigationAction }); + } + + 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. + + auto callback = std::make_unique(); + callback->nonNullSuccess = [this, urlRequest](const NavigationActionPolicy actionPolicy) + { + callShouldOverrideUrlLoading_ = false; + if (actionPolicy == NavigationActionPolicy::allow) { + loadUrl(urlRequest); + } + return false; + }; + auto defaultBehaviour = [this, urlRequest](const std::optional 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; + } + ).Get(), nullptr)); + + failedLog(webView->add_NavigationCompleted( + Callback( + [this](ICoreWebView2* sender, ICoreWebView2NavigationCompletedEventArgs* args) + { + isLoading_ = false; + + evaluateJavascript(PLATFORM_READY_JS_SOURCE, ContentWorld::page(), nullptr); + + std::shared_ptr navigationAction; + UINT64 navigationId; + if (SUCCEEDED(args->get_NavigationId(&navigationId))) { + navigationAction = map_at_or_null(navigationActions_, navigationId); + if (navigationAction) { + navigationActions_.erase(navigationId); + } + } + + COREWEBVIEW2_WEB_ERROR_STATUS webErrorType = COREWEBVIEW2_WEB_ERROR_STATUS_UNKNOWN; + args->get_WebErrorStatus(&webErrorType); + + BOOL isSuccess; + args->get_IsSuccess(&isSuccess); + + if (channelDelegate) { + wil::unique_cotaskmem_string uri; + std::optional url = SUCCEEDED(webView->get_Source(&uri)) ? wide_to_utf8(uri.get()) : std::optional{}; + if (isSuccess) { + channelDelegate->onLoadStop(url); + } + else if (!InAppWebView::isSslError(webErrorType) && navigationAction) { + auto webResourceRequest = std::make_unique(url, navigationAction->request->method, navigationAction->request->headers, navigationAction->isForMainFrame); + int httpStatusCode = 0; + wil::com_ptr args2; + if (SUCCEEDED(args->QueryInterface(IID_PPV_ARGS(&args2))) && SUCCEEDED(args2->get_HttpStatusCode(&httpStatusCode)) && httpStatusCode >= 400) { + auto webResourceResponse = std::make_unique(httpStatusCode); + channelDelegate->onReceivedHttpError(std::move(webResourceRequest), std::move(webResourceResponse)); + } + else if (httpStatusCode < 400) { + auto webResourceError = std::make_unique(WebErrorStatusDescription[webErrorType], webErrorType); + channelDelegate->onReceivedError(std::move(webResourceRequest), std::move(webResourceError)); + } + } + } + + return S_OK; + } + ).Get(), nullptr)); + + failedLog(webView->add_DocumentTitleChanged(Callback( + [this](ICoreWebView2* sender, IUnknown* args) + { + if (channelDelegate) { + wil::unique_cotaskmem_string title; + sender->get_DocumentTitle(&title); + channelDelegate->onTitleChanged(title.is_valid() ? wide_to_utf8(title.get()) : std::optional{}); + } + return S_OK; + } + ).Get(), nullptr)); + + failedLog(webView->add_HistoryChanged(Callback( + [this](ICoreWebView2* sender, IUnknown* args) + { + if (channelDelegate) { + std::optional isReload = std::nullopt; + if (lastNavigationAction_ && lastNavigationAction_->navigationType.has_value()) { + isReload = lastNavigationAction_->navigationType.value() == NavigationActionType::reload; + } + channelDelegate->onUpdateVisitedHistory(getUrl(), isReload); + } + return S_OK; + } + ).Get(), nullptr)); + + failedLog(webView->add_WebMessageReceived(Callback( + [this](ICoreWebView2* sender, ICoreWebView2WebMessageReceivedEventArgs* args) + { + if (!channelDelegate) { + return S_OK; + } + + wil::unique_cotaskmem_string json; + if (succeededOrLog(args->get_WebMessageAsJson(&json))) { + auto message = nlohmann::json::parse(wide_to_utf8(json.get())); + + if (message.is_object() && message.contains("name") && message.at("name").is_string() && message.contains("body") && message.at("body").is_object()) { + auto name = message.at("name").get(); + auto body = message.at("body").get(); + + if (name.compare("callHandler") == 0 && body.contains("handlerName") && body.at("handlerName").is_string()) { + auto handlerName = body.at("handlerName").get(); + auto callHandlerID = body.at("_callHandlerID").is_number_integer() ? body.at("_callHandlerID").get() : 0; + std::string handlerArgs = body.at("args").is_string() ? body.at("args").get() : ""; + + auto callback = std::make_unique(); + callback->defaultBehaviour = [this, callHandlerID](const std::optional response) + { + std::string json = "null"; + if (response.has_value() && !response.value()->IsNull()) { + json = std::get(*(response.value())); + } + + evaluateJavascript("if (window." + JAVASCRIPT_BRIDGE_NAME + "[" + std::to_string(callHandlerID) + "] != null) { \ + window." + JAVASCRIPT_BRIDGE_NAME + "[" + std::to_string(callHandlerID) + "].resolve(" + json + "); \ + delete window." + JAVASCRIPT_BRIDGE_NAME + "[" + std::to_string(callHandlerID) + "]; \ + }", ContentWorld::page(), nullptr); + }; + callback->error = [this, callHandlerID](const std::string& error_code, const std::string& error_message, const flutter::EncodableValue* error_details) + { + auto errorMessage = error_code + ", " + error_message; + debugLog(errorMessage); + + evaluateJavascript("if (window." + JAVASCRIPT_BRIDGE_NAME + "[" + std::to_string(callHandlerID) + "] != null) { \ + window." + JAVASCRIPT_BRIDGE_NAME + "[" + std::to_string(callHandlerID) + "].reject(new Error('" + replace_all_copy(errorMessage, "\'", "\\'") + "')); \ + delete window." + JAVASCRIPT_BRIDGE_NAME + "[" + std::to_string(callHandlerID) + "]; \ + }", ContentWorld::page(), nullptr); + }; + channelDelegate->onCallJsHandler(handlerName, handlerArgs, std::move(callback)); + } + } + } + + return S_OK; + } + ).Get(), nullptr)); + + wil::com_ptr consoleMessageReceiver; + if (succeededOrLog(webView->GetDevToolsProtocolEventReceiver(L"Runtime.consoleAPICalled", &consoleMessageReceiver))) { + failedLog(consoleMessageReceiver->add_DevToolsProtocolEventReceived( + Callback( + [this]( + ICoreWebView2* sender, + ICoreWebView2DevToolsProtocolEventReceivedEventArgs* args) -> HRESULT + { + + if (!channelDelegate) { + return S_OK; + } + + wil::unique_cotaskmem_string json; + if (succeededOrLog(args->get_ParameterObjectAsJson(&json))) { + auto consoleMessageJson = nlohmann::json::parse(wide_to_utf8(json.get())); + + auto level = consoleMessageJson.at("type").get(); + int64_t messageLevel = 1; + if (string_equals(level, "log")) { + messageLevel = 1; + } + else if (string_equals(level, "debug")) { + messageLevel = 0; + } + else if (string_equals(level, "error")) { + messageLevel = 3; + } + else if (string_equals(level, "info")) { + messageLevel = 1; + } + else if (string_equals(level, "warn")) { + messageLevel = 2; + } + + auto consoleArgs = consoleMessageJson.at("args").get>(); + auto message = join(functional_map(consoleArgs, [](const nlohmann::json& json) { return json.contains("value") ? json.at("value").dump() : (json.contains("description") ? json.at("description").dump() : json.dump()); }), std::string{ " " }); + channelDelegate->onConsoleMessage(message, messageLevel); + } + + return S_OK; + }) + .Get(), nullptr)); + } + + if (userContentController) { + userContentController->registerEventHandlers(); + } + } + + void InAppWebView::registerSurfaceEventHandlers() + { + if (!webViewCompositionController) { + return; + } + + failedLog(webViewCompositionController->add_CursorChanged( + Callback( + [this](ICoreWebView2CompositionController* sender, + IUnknown* args) -> HRESULT + { + HCURSOR cursor; + if (cursorChangedCallback_ && + sender->get_Cursor(&cursor) == S_OK) { + cursorChangedCallback_(cursor); + } + return S_OK; + }) + .Get(), nullptr)); + } + + std::optional InAppWebView::getUrl() const + { + wil::unique_cotaskmem_string uri; + return webView && succeededOrLog(webView->get_Source(&uri)) ? wide_to_utf8(uri.get()) : std::optional{}; + } + + std::optional InAppWebView::getTitle() const + { + wil::unique_cotaskmem_string title; + return webView && succeededOrLog(webView->get_DocumentTitle(&title)) ? wide_to_utf8(title.get()) : std::optional{}; + } + + void InAppWebView::loadUrl(const std::shared_ptr urlRequest) const + { + if (!webView || !urlRequest->url.has_value()) { + return; + } + + std::wstring url = utf8_to_wide(urlRequest->url.value()); + + wil::com_ptr webViewEnv2; + wil::com_ptr webView2; + if (SUCCEEDED(webViewEnv->QueryInterface(IID_PPV_ARGS(&webViewEnv2))) && SUCCEEDED(webView->QueryInterface(IID_PPV_ARGS(&webView2)))) { + wil::com_ptr webResourceRequest; + std::wstring method = urlRequest->method.has_value() ? utf8_to_wide(urlRequest->method.value()) : L"GET"; + + wil::com_ptr postDataStream = nullptr; + if (urlRequest->body.has_value()) { + auto postData = std::string(urlRequest->body->begin(), urlRequest->body->end()); + postDataStream = SHCreateMemStream( + reinterpret_cast(postData.data()), static_cast(postData.length())); + } + if (succeededOrLog(webViewEnv2->CreateWebResourceRequest( + url.c_str(), + method.c_str(), + postDataStream.get(), + L"", + &webResourceRequest + ))) { + wil::com_ptr requestHeaders; + if (SUCCEEDED(webResourceRequest->get_Headers(&requestHeaders))) { + if (method.compare(L"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(utf8_to_wide(key).c_str(), utf8_to_wide(val).c_str()); + } + } + } + failedLog(webView2->NavigateWithWebResourceRequest(webResourceRequest.get())); + } + return; + } + failedLog(webView->Navigate(url.c_str())); + } + + + void InAppWebView::loadFile(const std::string& assetFilePath) const + { + if (!webView) { + return; + } + + WCHAR* buf = new WCHAR[32768]; + GetModuleFileName(NULL, buf, 32768); + std::filesystem::path exeAbsPath = std::wstring(buf); + delete[] buf; + + std::filesystem::path flutterAssetPath("data/flutter_assets/" + assetFilePath); + auto absAssetFilePath = exeAbsPath.parent_path() / flutterAssetPath; + + if (!std::filesystem::exists(absAssetFilePath)) { + debugLog(absAssetFilePath.native() + L" asset file cannot be found!"); + return; + } + failedLog(webView->Navigate(absAssetFilePath.c_str())); + } + + void InAppWebView::loadData(const std::string& data) const + { + if (!webView) { + return; + } + + failedLog(webView->NavigateToString(utf8_to_wide(data).c_str())); + } + + void InAppWebView::reload() const + { + if (!webView) { + return; + } + + failedLog(webView->Reload()); + } + + void InAppWebView::goBack() + { + if (!webView) { + return; + } + + callShouldOverrideUrlLoading_ = false; + failedLog(webView->GoBack()); + } + + bool InAppWebView::canGoBack() const + { + BOOL canGoBack_; + return webView && succeededOrLog(webView->get_CanGoBack(&canGoBack_)) ? canGoBack_ : false; + } + + void InAppWebView::goForward() + { + if (!webView) { + return; + } + + callShouldOverrideUrlLoading_ = false; + failedLog(webView->GoForward()); + } + + bool InAppWebView::canGoForward() const + { + BOOL canGoForward_; + return webView && succeededOrLog(webView->get_CanGoForward(&canGoForward_)) ? canGoForward_ : false; + } + + void InAppWebView::goBackOrForward(const int64_t& steps) + { + getCopyBackForwardList( + [this, steps](std::unique_ptr webHistory) + { + if (webHistory && webHistory->currentIndex.has_value() && webHistory->list.has_value()) { + auto currentIndex = webHistory->currentIndex.value(); + auto items = &webHistory->list.value(); + auto nextIndex = currentIndex + steps; + int64_t size = items->size(); + if (nextIndex >= 0 && nextIndex < size) { + auto entryId = items->at(nextIndex)->entryId; + std::cout << "entryId: " + std::to_string(entryId.value()) << "\n"; + if (entryId.has_value()) { + auto oldCallShouldOverrideUrlLoading_ = callShouldOverrideUrlLoading_; + callShouldOverrideUrlLoading_ = false; + if (failedAndLog(webView->CallDevToolsProtocolMethod(L"Page.navigateToHistoryEntry", utf8_to_wide("{\"entryId\": " + std::to_string(entryId.value()) + "}").c_str(), Callback( + [this](HRESULT errorCode, LPCWSTR returnObjectAsJson) + { + failedLog(errorCode); + return S_OK; + } + ).Get()))) { + callShouldOverrideUrlLoading_ = oldCallShouldOverrideUrlLoading_; + } + } + } + } + } + ); + } + + void InAppWebView::canGoBackOrForward(const int64_t& steps, std::function completionHandler) const + { + getCopyBackForwardList( + [steps, completionHandler](std::unique_ptr webHistory) + { + auto canGoBackOrForward_ = false; + if (webHistory && webHistory->currentIndex.has_value() && webHistory->list.has_value()) { + auto currentIndex = webHistory->currentIndex.value(); + auto items = &webHistory->list.value(); + auto nextIndex = currentIndex + steps; + int64_t size = items->size(); + canGoBackOrForward_ = nextIndex >= 0 && nextIndex < size; + } + + if (completionHandler) { + completionHandler(canGoBackOrForward_); + } + } + ); + } + + void InAppWebView::stopLoading() const + { + if (!webView) { + return; + } + + failedLog(webView->Stop()); + } + + void InAppWebView::getCopyBackForwardList(const std::function)> completionHandler) const + { + if (!webView) { + if (completionHandler) { + completionHandler(std::make_unique(std::nullopt, std::nullopt)); + } + return; + } + + failedLog(webView->CallDevToolsProtocolMethod(L"Page.getNavigationHistory", L"{}", Callback( + [completionHandler](HRESULT errorCode, LPCWSTR returnObjectAsJson) + { + if (!completionHandler) { + return S_OK; + } + + if (errorCode == S_OK) { + auto historyJson = nlohmann::json::parse(wide_to_utf8(returnObjectAsJson)); + + int64_t currentIndex = historyJson.at("currentIndex").is_number_unsigned() ? historyJson.at("currentIndex").get() : 0; + std::vector 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( + entry.at("id").is_number_integer() ? entry.at("id").get() : std::optional{}, + 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(errorCode); + completionHandler(std::make_unique(std::nullopt, std::nullopt)); + } + + return S_OK; + } + ).Get())); + } + + void InAppWebView::evaluateJavascript(const std::string& source, const std::shared_ptr contentWorld, const std::function completionHandler) const + { + if (!webView || !userContentController) { + if (completionHandler) { + completionHandler("null"); + } + return; + } + + userContentController->createContentWorld(contentWorld, + [=](const int& contextId) + { + nlohmann::json parameters = { + {"expression", source} + }; + + if (contextId >= 0) { + parameters["contextId"] = contextId; + } + + auto hr = webView->CallDevToolsProtocolMethod(L"Runtime.evaluate", utf8_to_wide(parameters.dump()).c_str(), Callback( + [this, completionHandler](HRESULT errorCode, LPCWSTR returnObjectAsJson) + { + nlohmann::json result; + if (succeededOrLog(errorCode)) { + 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() : result["value"].dump()); + result = nlohmann::json{}; + debugLog(exceptionDetails.dump()); + if (channelDelegate) { + channelDelegate->onConsoleMessage(errorMessage, 3); + } + } + } + if (completionHandler) { + 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, const std::function completionHandler) const + { + if (!webView || !userContentController) { + if (completionHandler) { + completionHandler("null"); + } + return; + } + + userContentController->createContentWorld(contentWorld, + [=](const int& contextId) + { + std::vector functionArgumentNamesList; + std::vector 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( + [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() : 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; + } + ).Get()); + + if (failedAndLog(hr) && completionHandler) { + completionHandler("null"); + } + }); + } + + void InAppWebView::addUserScript(const std::shared_ptr userScript) const + { + if (!userContentController) { + return; + } + + userContentController->addUserOnlyScript(userScript); + } + + void InAppWebView::removeUserScript(const int64_t index, const std::shared_ptr userScript) const + { + if (!userContentController) { + return; + } + + userContentController->removeUserOnlyScriptAt(index, userScript->injectionTime); + } + + void InAppWebView::removeUserScriptsByGroupName(const std::string& groupName) const + { + if (!userContentController) { + return; + } + + userContentController->removeUserOnlyScriptsByGroupName(groupName); + } + + void InAppWebView::removeAllUserScripts() const + { + if (!userContentController) { + return; + } + + userContentController->removeAllUserOnlyScripts(); + } + + void InAppWebView::takeScreenshot(const std::optional> screenshotConfiguration, const std::function)> completionHandler) const + { + if (!webView) { + if (completionHandler) { + completionHandler(std::nullopt); + } + return; + } + + nlohmann::json parameters = { + {"captureBeyondViewport", true} + }; + if (screenshotConfiguration.has_value()) { + auto& scp = screenshotConfiguration.value(); + parameters["format"] = to_lowercase_copy(CompressFormatToString(scp->compressFormat)); + if (scp->compressFormat == CompressFormat::jpeg) { + parameters["quality"] = scp->quality; + } + if (scp->rect.has_value()) { + auto& rect = scp->rect.value(); + parameters["clip"] = { + {"x", rect->x}, + {"y", rect->y}, + {"width", rect->width}, + {"height", rect->height}, + {"scale", scaleFactor_} + }; + } + } + + auto hr = webView->CallDevToolsProtocolMethod(L"Page.captureScreenshot", utf8_to_wide(parameters.dump()).c_str(), Callback( + [this, completionHandler](HRESULT errorCode, LPCWSTR returnObjectAsJson) + { + std::optional result = std::nullopt; + if (succeededOrLog(errorCode)) { + nlohmann::json json = nlohmann::json::parse(wide_to_utf8(returnObjectAsJson)); + result = json["data"].get(); + } + if (completionHandler) { + completionHandler(result); + } + return S_OK; + } + ).Get()); + if (failedAndLog(hr) && completionHandler) { + completionHandler(std::nullopt); + } + } + + void InAppWebView::setSettings(const std::shared_ptr newSettings, const flutter::EncodableMap& newSettingsMap) + { + wil::com_ptr webView2Settings; + if (succeededOrLog(webView->get_Settings(&webView2Settings))) { + if (fl_map_contains_not_null(newSettingsMap, "javaScriptEnabled") && settings->javaScriptEnabled != newSettings->javaScriptEnabled) { + webView2Settings->put_IsScriptEnabled(newSettings->javaScriptEnabled); + } + + if (fl_map_contains_not_null(newSettingsMap, "supportZoom") && settings->supportZoom != newSettings->supportZoom) { + webView2Settings->put_IsZoomControlEnabled(newSettings->supportZoom); + } + + if (fl_map_contains_not_null(newSettingsMap, "isInspectable") && settings->isInspectable != newSettings->isInspectable) { + webView2Settings->put_AreDevToolsEnabled(newSettings->isInspectable); + } + + if (fl_map_contains_not_null(newSettingsMap, "disableContextMenu") && settings->disableContextMenu != newSettings->disableContextMenu) { + webView2Settings->put_AreDefaultContextMenusEnabled(!newSettings->disableContextMenu); + } + + wil::com_ptr webView2Settings2; + if (succeededOrLog(webView2Settings->QueryInterface(IID_PPV_ARGS(&webView2Settings2)))) { + if (fl_map_contains_not_null(newSettingsMap, "userAgent") && !string_equals(settings->userAgent, newSettings->userAgent)) { + webView2Settings2->put_UserAgent(utf8_to_wide(newSettings->userAgent).c_str()); + } + } + } + + wil::com_ptr webViewController2; + if (succeededOrLog(webViewController->QueryInterface(IID_PPV_ARGS(&webViewController2)))) { + if (fl_map_contains_not_null(newSettingsMap, "transparentBackground") && settings->transparentBackground != newSettings->transparentBackground) { + BYTE alpha = newSettings->transparentBackground ? 0 : 255; + webViewController2->put_DefaultBackgroundColor({ alpha, 255, 255, 255 }); + } + } + + settings = newSettings; + } + + flutter::EncodableValue InAppWebView::getSettings() const + { + if (!settings || !webView) { + return make_fl_value(); + } + + wil::com_ptr webView2Settings; + if (succeededOrLog(webView->get_Settings(&webView2Settings))) { + return settings->getRealSettings(webView2Settings.get()); + } + return settings->toEncodableMap(); + } + + void InAppWebView::openDevTools() const + { + if (!webView) { + return; + } + + failedLog(webView->OpenDevToolsWindow()); + } + + void InAppWebView::callDevToolsProtocolMethod(const std::string& methodName, const std::optional& parametersAsJson, const std::function&)> completionHandler) const + { + if (!webView) { + if (completionHandler) { + completionHandler(S_OK, std::nullopt); + } + return; + } + + auto hr = webView->CallDevToolsProtocolMethod( + utf8_to_wide(methodName).c_str(), + !parametersAsJson.has_value() || parametersAsJson.value().empty() ? L"{}" : utf8_to_wide(parametersAsJson.value()).c_str(), + Callback( + [completionHandler](HRESULT errorCode, LPCWSTR returnObjectAsJson) + { + failedLog(errorCode); + if (completionHandler) { + completionHandler(errorCode, wide_to_utf8(returnObjectAsJson)); + } + return S_OK; + } + ).Get()); + + if (failedAndLog(hr) && completionHandler) { + completionHandler(hr, std::nullopt); + } + } + + void InAppWebView::addDevToolsProtocolEventListener(const std::string& eventName) + { + if (map_contains(devToolsProtocolEventListener_, eventName)) { + return; + } + + wil::com_ptr eventReceiver; + if (succeededOrLog(webView->GetDevToolsProtocolEventReceiver(utf8_to_wide(eventName).c_str(), &eventReceiver))) { + EventRegistrationToken token = {}; + auto hr = eventReceiver->add_DevToolsProtocolEventReceived( + Callback( + [this, eventName]( + ICoreWebView2* sender, + ICoreWebView2DevToolsProtocolEventReceivedEventArgs* args) -> HRESULT + { + if (!channelDelegate) { + return S_OK; + } + + wil::unique_cotaskmem_string json; + failedLog(args->get_ParameterObjectAsJson(&json)); + channelDelegate->onDevToolsProtocolEventReceived(eventName, wide_to_utf8(json.get())); + + return S_OK; + }) + .Get(), &token); + if (succeededOrLog(hr)) { + devToolsProtocolEventListener_.insert({ eventName, std::make_pair(std::move(eventReceiver), token) }); + } + } + } + + void InAppWebView::removeDevToolsProtocolEventListener(const std::string& eventName) + { + if (map_contains(devToolsProtocolEventListener_, eventName)) { + auto eventReceiver = devToolsProtocolEventListener_.at(eventName).first; + auto token = devToolsProtocolEventListener_.at(eventName).second; + eventReceiver->remove_DevToolsProtocolEventReceived(token); + devToolsProtocolEventListener_.erase(eventName); + } + } + + // flutter_view + void InAppWebView::setSurfaceSize(size_t width, size_t height, float scale_factor) + { + if (!webViewController) { + return; + } + + if (surface_ && width > 0 && height > 0) { + scaleFactor_ = scale_factor; + auto scaled_width = width * scale_factor; + auto scaled_height = height * scale_factor; + + RECT bounds; + bounds.left = 0; + bounds.top = 0; + bounds.right = static_cast(scaled_width); + bounds.bottom = static_cast(scaled_height); + + surface_->put_Size({ scaled_width, scaled_height }); + + wil::com_ptr webViewController3; + if (SUCCEEDED(webViewController->QueryInterface(IID_PPV_ARGS(&webViewController3)))) { + webViewController3->put_RasterizationScale(scale_factor); + } + + if (webViewController->put_Bounds(bounds) != S_OK) { + std::cerr << "Setting webview bounds failed." << std::endl; + } + + if (surfaceSizeChangedCallback_) { + surfaceSizeChangedCallback_(width, height); + } + } + } + + + void InAppWebView::setPosition(size_t x, size_t y, float scale_factor) + { + if (!webViewController || !plugin || !plugin->registrar) { + return; + } + + if (x >= 0 && y >= 0) { + scaleFactor_ = scale_factor; + auto scaled_x = static_cast(x * scale_factor); + auto scaled_y = static_cast(y * scale_factor); + + auto titleBarHeight = ((GetSystemMetrics(SM_CYCAPTION) + GetSystemMetrics(SM_CYFRAME)) * scale_factor) + GetSystemMetrics(SM_CXPADDEDBORDER); + auto borderWidth = (GetSystemMetrics(SM_CXBORDER) + GetSystemMetrics(SM_CXPADDEDBORDER)) * scale_factor; + + RECT flutterWindowRect; + HWND flutterWindowHWnd = plugin->registrar->GetView()->GetNativeWindow(); + GetWindowRect(flutterWindowHWnd, &flutterWindowRect); + + HWND webViewHWnd; + if (succeededOrLog(webViewController->get_ParentWindow(&webViewHWnd))) { + ::SetWindowPos(webViewHWnd, + nullptr, + static_cast(flutterWindowRect.left + scaled_x - borderWidth), + static_cast(flutterWindowRect.top + scaled_y - titleBarHeight), + 0, 0, + SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE); + } + } + } + + + void InAppWebView::setCursorPos(double x, double y) + { + if (!webViewCompositionController) { + return; + } + + POINT point; + point.x = static_cast(x * scaleFactor_); + point.y = static_cast(y * scaleFactor_); + lastCursorPos_ = point; + + // https://docs.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2?view=webview2-1.0.774.44 + webViewCompositionController->SendMouseInput( + COREWEBVIEW2_MOUSE_EVENT_KIND::COREWEBVIEW2_MOUSE_EVENT_KIND_MOVE, + virtualKeys_.state(), 0, point); + } + + void InAppWebView::setPointerUpdate(int32_t pointer, + InAppWebViewPointerEventKind eventKind, double x, + double y, double size, double pressure) + { + if (!webViewEnv || !webViewCompositionController) { + return; + } + + COREWEBVIEW2_POINTER_EVENT_KIND event = + COREWEBVIEW2_POINTER_EVENT_KIND_UPDATE; + UINT32 pointerFlags = POINTER_FLAG_NONE; + switch (eventKind) { + case InAppWebViewPointerEventKind::Activate: + event = COREWEBVIEW2_POINTER_EVENT_KIND_ACTIVATE; + break; + case InAppWebViewPointerEventKind::Down: + event = COREWEBVIEW2_POINTER_EVENT_KIND_DOWN; + pointerFlags = + POINTER_FLAG_DOWN | POINTER_FLAG_INRANGE | POINTER_FLAG_INCONTACT; + break; + case InAppWebViewPointerEventKind::Enter: + event = COREWEBVIEW2_POINTER_EVENT_KIND_ENTER; + break; + case InAppWebViewPointerEventKind::Leave: + event = COREWEBVIEW2_POINTER_EVENT_KIND_LEAVE; + break; + case InAppWebViewPointerEventKind::Up: + event = COREWEBVIEW2_POINTER_EVENT_KIND_UP; + pointerFlags = POINTER_FLAG_UP; + break; + case InAppWebViewPointerEventKind::Update: + event = COREWEBVIEW2_POINTER_EVENT_KIND_UPDATE; + pointerFlags = + POINTER_FLAG_UPDATE | POINTER_FLAG_INRANGE | POINTER_FLAG_INCONTACT; + break; + } + + POINT point; + point.x = static_cast(x * scaleFactor_); + point.y = static_cast(y * scaleFactor_); + + RECT rect; + rect.left = point.x - 2; + rect.right = point.x + 2; + rect.top = point.y - 2; + rect.bottom = point.y + 2; + + wil::com_ptr webViewEnv3; + if (SUCCEEDED(webViewEnv->QueryInterface(IID_PPV_ARGS(&webViewEnv3)))) { + wil::com_ptr pInfo; + if (SUCCEEDED(webViewEnv3->CreateCoreWebView2PointerInfo(&pInfo))) { + if (pInfo) { + pInfo->put_PointerId(pointer); + pInfo->put_PointerKind(PT_TOUCH); + pInfo->put_PointerFlags(pointerFlags); + pInfo->put_TouchFlags(TOUCH_FLAG_NONE); + pInfo->put_TouchMask(TOUCH_MASK_CONTACTAREA | TOUCH_MASK_PRESSURE); + pInfo->put_TouchPressure( + std::clamp((UINT32)(pressure == 0.0 ? 1024 : 1024 * pressure), + (UINT32)0, (UINT32)1024)); + pInfo->put_PixelLocationRaw(point); + pInfo->put_TouchContactRaw(rect); + webViewCompositionController->SendPointerInput(event, pInfo.get()); + } + } + } + } + + void InAppWebView::setPointerButtonState(InAppWebViewPointerButton button, bool is_down) + { + if (!webViewCompositionController) { + return; + } + + COREWEBVIEW2_MOUSE_EVENT_KIND kind; + switch (button) { + case InAppWebViewPointerButton::Primary: + virtualKeys_.setIsLeftButtonDown(is_down); + kind = is_down ? COREWEBVIEW2_MOUSE_EVENT_KIND_LEFT_BUTTON_DOWN + : COREWEBVIEW2_MOUSE_EVENT_KIND_LEFT_BUTTON_UP; + break; + case InAppWebViewPointerButton::Secondary: + virtualKeys_.setIsRightButtonDown(is_down); + kind = is_down ? COREWEBVIEW2_MOUSE_EVENT_KIND_RIGHT_BUTTON_DOWN + : COREWEBVIEW2_MOUSE_EVENT_KIND_RIGHT_BUTTON_UP; + break; + case InAppWebViewPointerButton::Tertiary: + virtualKeys_.setIsMiddleButtonDown(is_down); + kind = is_down ? COREWEBVIEW2_MOUSE_EVENT_KIND_MIDDLE_BUTTON_DOWN + : COREWEBVIEW2_MOUSE_EVENT_KIND_MIDDLE_BUTTON_UP; + break; + default: + kind = static_cast(0); + } + + webViewCompositionController->SendMouseInput(kind, virtualKeys_.state(), 0, + lastCursorPos_); + } + + void InAppWebView::sendScroll(double delta, bool horizontal) + { + if (!webViewCompositionController) { + return; + } + + // delta * 6 gives me a multiple of WHEEL_DELTA (120) + constexpr auto kScrollMultiplier = 6; + + auto offset = static_cast(delta * kScrollMultiplier); + + if (horizontal) { + webViewCompositionController->SendMouseInput( + COREWEBVIEW2_MOUSE_EVENT_KIND_HORIZONTAL_WHEEL, virtualKeys_.state(), + offset, lastCursorPos_); + } + else { + webViewCompositionController->SendMouseInput(COREWEBVIEW2_MOUSE_EVENT_KIND_WHEEL, + virtualKeys_.state(), offset, + lastCursorPos_); + } + } + + void InAppWebView::setScrollDelta(double delta_x, double delta_y) + { + if (!webViewCompositionController) { + return; + } + + if (delta_x != 0.0) { + sendScroll(delta_x, true); + } + if (delta_y != 0.0) { + sendScroll(delta_y, false); + } + } + + bool InAppWebView::createSurface(const HWND parentWindow, + winrt::com_ptr compositor) + { + if (!webViewCompositionController || !webViewController) { + return false; + } + + winrt::com_ptr root; + if (FAILED(compositor->CreateContainerVisual(root.put()))) { + return false; + } + surface_ = root.try_as(); + assert(surface_); + + // initial size. doesn't matter as we resize the surface anyway. + surface_->put_Size({ 1280, 720 }); + surface_->put_IsVisible(true); + + winrt::com_ptr webview_visual; + compositor->CreateContainerVisual( + reinterpret_cast( + webview_visual.put())); + + auto webview_visual2 = + webview_visual.try_as(); + if (webview_visual2) { + webview_visual2->put_RelativeSizeAdjustment({ 1.0f, 1.0f }); + } + + winrt::com_ptr children; + root->get_Children(children.put()); + children->InsertAtTop(webview_visual.get()); + webViewCompositionController->put_RootVisualTarget(webview_visual2.get()); + + webViewController->put_IsVisible(true); + + return true; + } + + bool InAppWebView::isSslError(const COREWEBVIEW2_WEB_ERROR_STATUS& webErrorStatus) + { + return webErrorStatus >= COREWEBVIEW2_WEB_ERROR_STATUS_CERTIFICATE_COMMON_NAME_IS_INCORRECT && webErrorStatus <= COREWEBVIEW2_WEB_ERROR_STATUS_CERTIFICATE_IS_INVALID; + } + + InAppWebView::~InAppWebView() + { + debugLog("dealloc InAppWebView"); + userContentController = nullptr; + if (webView) { + failedLog(webView->Stop()); + } + HWND parentWindow = nullptr; + if (webViewCompositionController && webViewController && succeededOrLog(webViewController->get_ParentWindow(&parentWindow))) { + // if it's an InAppWebView (so webViewCompositionController will be not a nullptr!), + // then destroy the Window created with it + DestroyWindow(parentWindow); + } + if (webViewController) { + failedLog(webViewController->Close()); + } + navigationActions_.clear(); + inAppBrowser = nullptr; + plugin = nullptr; + } +} 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 new file mode 100644 index 00000000..6f92eea9 --- /dev/null +++ b/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview.h @@ -0,0 +1,200 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_WEBVIEW_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_WEBVIEW_H_ + +#include +#include +#include +#include +#include +#include + +#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/screenshot_configuration.h" +#include "../types/url_request.h" +#include "../types/web_history.h" +#include "../webview_environment/webview_environment.h" +#include "in_app_webview_settings.h" +#include "user_content_controller.h" +#include "webview_channel_delegate.h" + +namespace flutter_inappwebview_plugin +{ + class InAppBrowser; + + using namespace Microsoft::WRL; + + // custom_platform_view + enum class InAppWebViewPointerButton { None, Primary, Secondary, Tertiary }; + enum class InAppWebViewPointerEventKind { Activate, Down, Enter, Leave, Up, Update }; + typedef std::function + SurfaceSizeChangedCallback; + typedef std::function CursorChangedCallback; + struct VirtualKeyState { + public: + inline void setIsLeftButtonDown(bool is_down) + { + set(COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS:: + COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_LEFT_BUTTON, + is_down); + } + + inline void setIsRightButtonDown(bool is_down) + { + set(COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS:: + COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_RIGHT_BUTTON, + is_down); + } + + inline void setIsMiddleButtonDown(bool is_down) + { + set(COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS:: + COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_MIDDLE_BUTTON, + is_down); + } + + inline COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS state() const { return state_; } + + private: + COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS state_ = + COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS:: + COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_NONE; + + inline void set(COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS key, bool flag) + { + if (flag) { + state_ |= key; + } + else { + state_ &= ~key; + } + } + }; + + 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 id; + const std::shared_ptr initialSettings; + const std::optional>> initialUserScripts; + }; + + class InAppWebView + { + public: + static inline const std::string METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_"; + + const FlutterInappwebviewWindowsPlugin* plugin; + std::variant id; + wil::com_ptr webViewEnv; + wil::com_ptr webViewController; + wil::com_ptr webViewCompositionController; + wil::com_ptr webView; + std::unique_ptr channelDelegate; + std::shared_ptr settings; + InAppBrowser* inAppBrowser = nullptr; + std::unique_ptr userContentController; + + InAppWebView(const FlutterInappwebviewWindowsPlugin* plugin, const InAppWebViewCreationParams& params, const HWND parentWindow, + wil::com_ptr webViewEnv, + wil::com_ptr webViewController, + wil::com_ptr webViewCompositionController); + InAppWebView(InAppBrowser* inAppBrowser, const FlutterInappwebviewWindowsPlugin* plugin, const InAppWebViewCreationParams& params, const HWND parentWindow, + wil::com_ptr webViewEnv, + wil::com_ptr webViewController, + wil::com_ptr webViewCompositionController); + ~InAppWebView(); + + static void createInAppWebViewEnv(const HWND parentWindow, const bool& willBeSurface, WebViewEnvironment* webViewEnvironment, std::function webViewEnv, + wil::com_ptr webViewController, + wil::com_ptr webViewCompositionController)> completionHandler); + + // custom_platform_view + ABI::Windows::UI::Composition::IVisual* const surface() + { + return surface_.get(); + } + void setSurfaceSize(size_t width, size_t height, float scale_factor); + void setPosition(size_t x, size_t y, float scale_factor); + void setCursorPos(double x, double y); + void setPointerUpdate(int32_t pointer, InAppWebViewPointerEventKind eventKind, + double x, double y, double size, double pressure); + void setPointerButtonState(InAppWebViewPointerButton button, bool isDown); + void sendScroll(double offset, bool horizontal); + void setScrollDelta(double delta_x, double delta_y); + void onSurfaceSizeChanged(SurfaceSizeChangedCallback callback) + { + surfaceSizeChangedCallback_ = std::move(callback); + } + void onCursorChanged(CursorChangedCallback callback) + { + cursorChangedCallback_ = std::move(callback); + } + bool createSurface(const HWND parentWindow, + winrt::com_ptr compositor); + + void initChannel(const std::optional> viewId, const std::optional channelName); + void prepare(const InAppWebViewCreationParams& params); + std::optional getUrl() const; + std::optional getTitle() const; + void loadUrl(const std::shared_ptr urlRequest) const; + void loadFile(const std::string& assetFilePath) const; + void loadData(const std::string& data) const; + void reload() const; + void goBack(); + bool canGoBack() const; + void goForward(); + bool canGoForward() const; + void goBackOrForward(const int64_t& steps); + void canGoBackOrForward(const int64_t& steps, std::function completionHandler) const; + bool isLoading() const + { + return isLoading_; + } + void stopLoading() const; + void evaluateJavascript(const std::string& source, const std::shared_ptr contentWorld, const std::function completionHandler) const; + void callAsyncJavaScript(const std::string& functionBody, const std::string& argumentsAsJson, const std::shared_ptr contentWorld, const std::function completionHandler) const; + void getCopyBackForwardList(const std::function)> completionHandler) const; + void addUserScript(const std::shared_ptr userScript) const; + void removeUserScript(const int64_t index, const std::shared_ptr userScript) const; + void removeUserScriptsByGroupName(const std::string& groupName) const; + void removeAllUserScripts() const; + void takeScreenshot(const std::optional> screenshotConfiguration, const std::function)> completionHandler) const; + void setSettings(const std::shared_ptr newSettings, const flutter::EncodableMap& newSettingsMap); + flutter::EncodableValue getSettings() const; + void openDevTools() const; + void callDevToolsProtocolMethod(const std::string& methodName, const std::optional& parametersAsJson, const std::function&)> completionHandler) const; + void addDevToolsProtocolEventListener(const std::string& eventName); + void removeDevToolsProtocolEventListener(const std::string& eventName); + + std::string pageFrameId() const + { + return pageFrameId_; + } + + static bool isSslError(const COREWEBVIEW2_WEB_ERROR_STATUS& webErrorStatus); + private: + // custom_platform_view + winrt::com_ptr surface_; + SurfaceSizeChangedCallback surfaceSizeChangedCallback_; + CursorChangedCallback cursorChangedCallback_; + float scaleFactor_ = 1.0; + POINT lastCursorPos_ = { 0, 0 }; + VirtualKeyState virtualKeys_; + + bool callShouldOverrideUrlLoading_ = true; + std::map> navigationActions_ = {}; + std::shared_ptr lastNavigationAction_; + bool isLoading_ = false; + std::string pageFrameId_; + std::map, EventRegistrationToken>> devToolsProtocolEventListener_ = {}; + + void registerEventHandlers(); + void registerSurfaceEventHandlers(); + }; +} +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_WEBVIEW_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview_manager.cpp b/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview_manager.cpp new file mode 100644 index 00000000..173e85a6 --- /dev/null +++ b/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview_manager.cpp @@ -0,0 +1,190 @@ +#include +#include +#include +#include +#include + +#include "../in_app_webview/in_app_webview_settings.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 "../webview_environment/webview_environment_manager.h" +#include "in_app_webview_manager.h" + +namespace flutter_inappwebview_plugin +{ + InAppWebViewManager::InAppWebViewManager(const FlutterInappwebviewWindowsPlugin* plugin) + : plugin(plugin), + ChannelDelegate(plugin->registrar->messenger(), InAppWebViewManager::METHOD_CHANNEL_NAME), + rohelper_(std::make_unique(RO_INIT_SINGLETHREADED)) + { + if (rohelper_->WinRtAvailable()) { + DispatcherQueueOptions options{ sizeof(DispatcherQueueOptions), + DQTYPE_THREAD_CURRENT, DQTAT_COM_STA }; + + if (FAILED(rohelper_->CreateDispatcherQueueController( + options, dispatcher_queue_controller_.put()))) { + std::cerr << "Creating DispatcherQueueController failed." << std::endl; + return; + } + + if (!isGraphicsCaptureSessionSupported()) { + std::cerr << "Windows::Graphics::Capture::GraphicsCaptureSession is not " + "supported." + << std::endl; + return; + } + + graphics_context_ = std::make_unique(rohelper_.get()); + compositor_ = graphics_context_->CreateCompositor(); + valid_ = graphics_context_->IsValid(); + } + + windowClass_.lpszClassName = CustomPlatformView::CLASS_NAME; + windowClass_.lpfnWndProc = &DefWindowProc; + + RegisterClass(&windowClass_); + } + + void InAppWebViewManager::HandleMethodCall(const flutter::MethodCall& method_call, + std::unique_ptr> result) + { + auto* arguments = std::get_if(method_call.arguments()); + auto& methodName = method_call.method_name(); + + if (string_equals(methodName, "createInAppWebView")) { + if (isSupported()) { + createInAppWebView(arguments, std::move(result)); + } + else { + result->Error("0", "Creating an InAppWebView instance is not supported! Graphics Context is not valid!"); + } + } + else if (string_equals(methodName, "dispose")) { + auto id = get_fl_map_value(*arguments, "id"); + if (map_contains(webViews, (uint64_t)id)) { + webViews.erase(id); + } + result->Success(); + } + else { + result->NotImplemented(); + } + } + + void InAppWebViewManager::createInAppWebView(const flutter::EncodableMap* arguments, std::unique_ptr> result) + { + auto result_ = std::shared_ptr>(std::move(result)); + + auto settingsMap = get_fl_map_value(*arguments, "initialSettings"); + auto urlRequestMap = get_optional_fl_map_value(*arguments, "initialUrlRequest"); + auto initialFile = get_optional_fl_map_value(*arguments, "initialFile"); + auto initialDataMap = get_optional_fl_map_value(*arguments, "initialData"); + auto initialUserScriptList = get_optional_fl_map_value(*arguments, "initialUserScripts"); + auto webViewEnvironmentId = get_optional_fl_map_value(*arguments, "webViewEnvironmentId"); + + RECT bounds; + GetClientRect(plugin->registrar->GetView()->GetNativeWindow(), &bounds); + + auto hwnd = CreateWindowEx(0, windowClass_.lpszClassName, L"", 0, 0, + 0, bounds.right - bounds.left, bounds.bottom - bounds.top, + plugin->registrar->GetView()->GetNativeWindow(), + nullptr, + windowClass_.hInstance, nullptr); + + auto webViewEnvironment = webViewEnvironmentId.has_value() && map_contains(plugin->webViewEnvironmentManager->webViewEnvironments, webViewEnvironmentId.value()) + ? plugin->webViewEnvironmentManager->webViewEnvironments.at(webViewEnvironmentId.value()).get() : nullptr; + + InAppWebView::createInAppWebViewEnv(hwnd, true, webViewEnvironment, + [=](wil::com_ptr webViewEnv, + wil::com_ptr webViewController, + wil::com_ptr webViewCompositionController) + { + if (webViewEnv && webViewController && webViewCompositionController) { + auto initialSettings = std::make_unique(settingsMap); + std::optional>> initialUserScripts = initialUserScriptList.has_value() ? + functional_map(initialUserScriptList.value(), [](const flutter::EncodableValue& map) { return std::make_shared(std::get(map)); }) : + std::optional>>{}; + + InAppWebViewCreationParams params = { + "", + std::move(initialSettings), + initialUserScripts + }; + + auto inAppWebView = std::make_unique(plugin, params, hwnd, + std::move(webViewEnv), std::move(webViewController), std::move(webViewCompositionController) + ); + + std::optional> urlRequest = urlRequestMap.has_value() ? std::make_shared(urlRequestMap.value()) : std::optional>{}; + if (urlRequest.has_value()) { + inAppWebView->loadUrl(urlRequest.value()); + } + else if (initialFile.has_value()) { + inAppWebView->loadFile(initialFile.value()); + } + else if (initialDataMap.has_value()) { + inAppWebView->loadData(get_fl_map_value(initialDataMap.value(), "data")); + } + + auto customPlatformView = std::make_unique(plugin->registrar->messenger(), + plugin->registrar->texture_registrar(), + graphics_context(), + hwnd, + std::move(inAppWebView)); + + auto textureId = customPlatformView->texture_id(); + + customPlatformView->view->initChannel(textureId, std::nullopt); + + webViews.insert({ textureId, std::move(customPlatformView) }); + + result_->Success(textureId); + } + else { + result_->Error("0", "Cannot create the InAppWebView instance!"); + } + } + ); + } + + bool InAppWebViewManager::isGraphicsCaptureSessionSupported() + { + HSTRING className; + HSTRING_HEADER classNameHeader; + + if (FAILED(rohelper_->GetStringReference( + RuntimeClass_Windows_Graphics_Capture_GraphicsCaptureSession, + &className, &classNameHeader))) { + return false; + } + + ABI::Windows::Graphics::Capture::IGraphicsCaptureSessionStatics* + capture_session_statics; + if (FAILED(rohelper_->GetActivationFactory( + className, + __uuidof( + ABI::Windows::Graphics::Capture::IGraphicsCaptureSessionStatics), + (void**)&capture_session_statics))) { + return false; + } + + boolean is_supported = false; + if (FAILED(capture_session_statics->IsSupported(&is_supported))) { + return false; + } + + return !!is_supported; + } + + InAppWebViewManager::~InAppWebViewManager() + { + debugLog("dealloc InAppWebViewManager"); + webViews.clear(); + UnregisterClass(windowClass_.lpszClassName, nullptr); + plugin = nullptr; + } +} diff --git a/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview_manager.h b/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview_manager.h new file mode 100644 index 00000000..8630ceeb --- /dev/null +++ b/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview_manager.h @@ -0,0 +1,58 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_WEBVIEW_MANAGER_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_WEBVIEW_MANAGER_H_ + +#include +#include +#include +#include +#include +#include + +#include "../custom_platform_view/custom_platform_view.h" +#include "../custom_platform_view/graphics_context.h" +#include "../custom_platform_view/util/rohelper.h" +#include "../flutter_inappwebview_windows_plugin.h" +#include "../types/channel_delegate.h" +#include "windows.ui.composition.h" + +namespace flutter_inappwebview_plugin +{ + class InAppWebViewManager : public ChannelDelegate + { + public: + static inline const std::string METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview"; + + const FlutterInappwebviewWindowsPlugin* plugin; + std::map> webViews; + + bool isSupported() const { return valid_; } + bool isGraphicsCaptureSessionSupported(); + GraphicsContext* graphics_context() const + { + return graphics_context_.get(); + }; + rx::RoHelper* rohelper() const { return rohelper_.get(); } + winrt::com_ptr compositor() const + { + return compositor_; + } + + InAppWebViewManager(const FlutterInappwebviewWindowsPlugin* plugin); + ~InAppWebViewManager(); + + void HandleMethodCall( + const flutter::MethodCall& method_call, + std::unique_ptr> result); + + void createInAppWebView(const flutter::EncodableMap* arguments, std::unique_ptr> result); + private: + std::unique_ptr rohelper_; + winrt::com_ptr + dispatcher_queue_controller_; + std::unique_ptr graphics_context_; + winrt::com_ptr compositor_; + WNDCLASS windowClass_ = {}; + bool valid_ = false; + }; +} +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_WEBVIEW_MANAGER_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview_settings.cpp b/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview_settings.cpp new file mode 100644 index 00000000..cf63c982 --- /dev/null +++ b/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview_settings.cpp @@ -0,0 +1,79 @@ +#include "../utils/flutter.h" +#include "../utils/log.h" +#include "in_app_webview_settings.h" + +#include + +namespace flutter_inappwebview_plugin +{ + using namespace Microsoft::WRL; + + InAppWebViewSettings::InAppWebViewSettings() {}; + + InAppWebViewSettings::InAppWebViewSettings(const flutter::EncodableMap& encodableMap) + { + useShouldOverrideUrlLoading = get_fl_map_value(encodableMap, "useShouldOverrideUrlLoading", useShouldOverrideUrlLoading); + useOnLoadResource = get_fl_map_value(encodableMap, "useOnLoadResource", useOnLoadResource); + useOnDownloadStart = get_fl_map_value(encodableMap, "useOnDownloadStart", useOnDownloadStart); + userAgent = get_fl_map_value(encodableMap, "userAgent", userAgent); + javaScriptEnabled = get_fl_map_value(encodableMap, "javaScriptEnabled", javaScriptEnabled); + resourceCustomSchemes = get_fl_map_value(encodableMap, "resourceCustomSchemes", resourceCustomSchemes); + transparentBackground = get_fl_map_value(encodableMap, "transparentBackground", transparentBackground); + supportZoom = get_fl_map_value(encodableMap, "supportZoom", supportZoom); + isInspectable = get_fl_map_value(encodableMap, "isInspectable", isInspectable); + disableContextMenu = get_fl_map_value(encodableMap, "disableContextMenu", disableContextMenu); + } + + flutter::EncodableMap InAppWebViewSettings::toEncodableMap() const + { + return flutter::EncodableMap{ + {"useShouldOverrideUrlLoading", useShouldOverrideUrlLoading}, + {"useOnLoadResource", useOnLoadResource}, + {"useOnDownloadStart", useOnDownloadStart}, + {"userAgent", userAgent}, + {"javaScriptEnabled", javaScriptEnabled}, + {"resourceCustomSchemes", make_fl_value(resourceCustomSchemes)}, + {"transparentBackground", transparentBackground}, + {"supportZoom", supportZoom}, + {"isInspectable", isInspectable}, + {"disableContextMenu", disableContextMenu}, + }; + } + + flutter::EncodableMap InAppWebViewSettings::getRealSettings(ICoreWebView2Settings* settings) const + { + auto settingsMap = toEncodableMap(); + if (settings) { + BOOL realJavaScriptEnabled; + if (SUCCEEDED(settings->get_IsScriptEnabled(&realJavaScriptEnabled))) { + settingsMap["javaScriptEnabled"] = (bool)realJavaScriptEnabled; + } + BOOL realSupportZoom; + if (SUCCEEDED(settings->get_IsZoomControlEnabled(&realSupportZoom))) { + settingsMap["supportZoom"] = (bool)realSupportZoom; + } + BOOL realIsInspectable; + if (SUCCEEDED(settings->get_AreDevToolsEnabled(&realIsInspectable))) { + settingsMap["isInspectable"] = (bool)realIsInspectable; + } + BOOL areDefaultContextMenusEnabled; + if (SUCCEEDED(settings->get_AreDefaultContextMenusEnabled(&areDefaultContextMenusEnabled))) { + settingsMap["disableContextMenu"] = !(bool)areDefaultContextMenusEnabled; + } + + wil::com_ptr settings2; + if (SUCCEEDED(settings->QueryInterface(IID_PPV_ARGS(&settings2)))) { + wil::unique_cotaskmem_string realUserAgent; + if (SUCCEEDED(settings2->get_UserAgent(&realUserAgent))) { + settingsMap["userAgent"] = wide_to_utf8(realUserAgent.get()); + } + } + } + return settingsMap; + } + + InAppWebViewSettings::~InAppWebViewSettings() + { + debugLog("dealloc InAppWebViewSettings"); + } +} diff --git a/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview_settings.h b/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview_settings.h new file mode 100644 index 00000000..417951df --- /dev/null +++ b/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview_settings.h @@ -0,0 +1,32 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_WEBVIEW_SETTINGS_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_WEBVIEW_SETTINGS_H_ + +#include +#include +#include + +namespace flutter_inappwebview_plugin +{ + class InAppWebViewSettings + { + public: + bool useShouldOverrideUrlLoading = false; + bool useOnLoadResource = false; + bool useOnDownloadStart = false; + std::string userAgent; + bool javaScriptEnabled = true; + std::vector resourceCustomSchemes; + bool transparentBackground = false; + bool supportZoom = true; + bool isInspectable = true; + bool disableContextMenu = false; + + InAppWebViewSettings(); + InAppWebViewSettings(const flutter::EncodableMap& encodableMap); + ~InAppWebViewSettings(); + + flutter::EncodableMap toEncodableMap() const; + flutter::EncodableMap getRealSettings(ICoreWebView2Settings* settings) const; + }; +} +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_WEBVIEW_SETTINGS_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/in_app_webview/user_content_controller.cpp b/flutter_inappwebview_windows/windows/in_app_webview/user_content_controller.cpp new file mode 100644 index 00000000..9cbe2761 --- /dev/null +++ b/flutter_inappwebview_windows/windows/in_app_webview/user_content_controller.cpp @@ -0,0 +1,433 @@ +#include +#include + +#include "../utils/log.h" +#include "../utils/string.h" +#include "../utils/vector.h" +#include "in_app_webview.h" +#include "user_content_controller.h" + +namespace flutter_inappwebview_plugin +{ + using namespace Microsoft::WRL; + + UserContentController::UserContentController(InAppWebView* webView) + : webView_(webView) + {} + + void UserContentController::registerEventHandlers() + { + if (!webView_ || !(webView_->webView)) { + return; + } + + wil::com_ptr executionContextCreated; + if (succeededOrLog(webView_->webView->GetDevToolsProtocolEventReceiver(L"Runtime.executionContextCreated", &executionContextCreated))) { + auto hr = executionContextCreated->add_DevToolsProtocolEventReceived( + Callback( + [this]( + ICoreWebView2* sender, + ICoreWebView2DevToolsProtocolEventReceivedEventArgs* args) -> HRESULT + { + wil::unique_cotaskmem_string json; + if (succeededOrLog(args->get_ParameterObjectAsJson(&json))) { + nlohmann::json context = nlohmann::json::parse(wide_to_utf8(json.get()))["context"]; + auto id = context["id"].get(); + auto name = context["name"].get(); + nlohmann::json auxData = context["auxData"]; + auto isDefault = auxData["isDefault"].get(); + auto frameId = auxData["frameId"].get(); + if (string_equals(webView_->pageFrameId(), frameId)) { + if (isDefault) { + contentWorlds_.insert_or_assign(ContentWorld::page()->name, id); + } + else { + contentWorlds_.insert_or_assign(name, id); + addPluginScriptsIfRequired(std::make_shared(name)); + } + } + } + + return S_OK; + }) + .Get(), nullptr); + + failedLog(hr); + } + + /* + wil::com_ptr executionContextDestroyed; + if (succeededOrLog(webView_->webView->GetDevToolsProtocolEventReceiver(L"Runtime.executionContextDestroyed ", &executionContextDestroyed))) { + failedLog(executionContextDestroyed->add_DevToolsProtocolEventReceived( + Callback( + [this]( + ICoreWebView2* sender, + ICoreWebView2DevToolsProtocolEventReceivedEventArgs* args) -> HRESULT + { + wil::unique_cotaskmem_string json; + if (succeededOrLog(args->get_ParameterObjectAsJson(&json))) { + debugLog("executionContextDestroyed: " + wide_to_utf8(json.get())); + } + + return S_OK; + }) + .Get(), nullptr)); + } + */ + } + + std::vector> UserContentController::getUserOnlyScriptsAt(const UserScriptInjectionTime& injectionTime) const + { + return userOnlyScripts_.at(injectionTime); + } + + void UserContentController::addUserOnlyScript(std::shared_ptr userScript) + { + if (!userScript) { + return; + } + + addPluginScriptsIfRequired(userScript->contentWorld); + addScriptToWebView(userScript, nullptr); + userOnlyScripts_.at(userScript->injectionTime).push_back(std::move(userScript)); + } + + void UserContentController::addUserOnlyScripts(std::vector> userScripts) + { + for (auto& userScript : userScripts) { + addUserOnlyScript(std::move(userScript)); + } + } + + void UserContentController::removeUserOnlyScript(std::shared_ptr userScript) + { + if (!userScript) { + return; + } + + if (webView_) { + removeScriptFromWebView(userScript, nullptr); + } + + vector_remove_erase(userOnlyScripts_.at(userScript->injectionTime), std::move(userScript)); + } + + void UserContentController::removeUserOnlyScriptAt(const int64_t& index, const UserScriptInjectionTime& injectionTime) + { + auto& vec = userOnlyScripts_.at(injectionTime); + int64_t size = vec.size(); + if (index >= size) { + return; + } + + auto& userScript = vec.at(index); + if (userScript) { + removeScriptFromWebView(userScript, nullptr); + vec.erase(vec.begin() + index); + } + } + + void UserContentController::removeAllUserOnlyScripts() + { + auto& userScriptsAtStart = userOnlyScripts_.at(UserScriptInjectionTime::atDocumentStart); + auto& userScriptsAtEnd = userOnlyScripts_.at(UserScriptInjectionTime::atDocumentEnd); + + if (webView_) { + for (auto& userScript : userScriptsAtStart) { + removeScriptFromWebView(userScript, nullptr); + } + for (auto& userScript : userScriptsAtEnd) { + removeScriptFromWebView(userScript, nullptr); + } + } + + userScriptsAtStart.clear(); + userScriptsAtEnd.clear(); + } + + void UserContentController::removeUserOnlyScriptsByGroupName(const std::string& groupName) + { + std::vector> userScriptsAtStart = userOnlyScripts_.at(UserScriptInjectionTime::atDocumentStart); + std::vector> userScriptsAtEnd = userOnlyScripts_.at(UserScriptInjectionTime::atDocumentEnd); + + for (auto& userScript : userScriptsAtStart) { + if (string_equals(groupName, userScript->groupName)) { + removeUserOnlyScript(userScript); + } + } + + for (auto& userScript : userScriptsAtEnd) { + if (string_equals(groupName, userScript->groupName)) { + removeUserOnlyScript(userScript); + } + } + } + + std::vector> UserContentController::getPluginScriptsAt(const UserScriptInjectionTime& injectionTime) const + { + return pluginScripts_.at(injectionTime); + } + + bool UserContentController::containsUserOnlyScript(std::shared_ptr userScript) const + { + if (!userScript) { + return false; + } + + return vector_contains(userOnlyScripts_.at(userScript->injectionTime), std::move(userScript)); + } + + bool UserContentController::containsUserOnlyScriptByGroupName(const std::string& groupName) const + { + return vector_contains_if(userOnlyScripts_.at(UserScriptInjectionTime::atDocumentStart), [groupName](const std::shared_ptr userScript) { return string_equals(groupName, userScript->groupName); }) || + vector_contains_if(userOnlyScripts_.at(UserScriptInjectionTime::atDocumentEnd), [groupName](const std::shared_ptr userScript) { return string_equals(groupName, userScript->groupName); }); + } + + void UserContentController::addPluginScript(std::shared_ptr pluginScript) + { + if (!pluginScript) { + return; + } + + addScriptToWebView(pluginScript, nullptr); + pluginScripts_.at(pluginScript->injectionTime).push_back(std::move(pluginScript)); + } + + void UserContentController::addPluginScripts(std::vector> pluginScripts) + { + for (auto& pluginScript : pluginScripts) { + addPluginScript(std::move(pluginScript)); + } + } + + void UserContentController::removePluginScript(std::shared_ptr pluginScript) + { + if (!pluginScript) { + return; + } + + if (webView_) { + removeScriptFromWebView(pluginScript, nullptr); + } + + vector_remove_erase(pluginScripts_.at(pluginScript->injectionTime), std::move(pluginScript)); + } + + void UserContentController::removeAllPluginScripts() + { + auto& pluginScriptsAtStart = pluginScripts_.at(UserScriptInjectionTime::atDocumentStart); + auto& pluginScriptsAtEnd = pluginScripts_.at(UserScriptInjectionTime::atDocumentEnd); + + if (webView_) { + for (auto& pluginScript : pluginScriptsAtStart) { + removeScriptFromWebView(pluginScript, nullptr); + } + for (auto& pluginScript : pluginScriptsAtEnd) { + removeScriptFromWebView(pluginScript, nullptr); + } + } + + pluginScriptsAtStart.clear(); + pluginScriptsAtEnd.clear(); + } + + bool UserContentController::containsPluginScript(std::shared_ptr pluginScript) const + { + if (!pluginScript) { + return false; + } + + auto injectionTime = pluginScript->injectionTime; + return vector_contains(pluginScripts_.at(injectionTime), std::move(pluginScript)); + } + + + bool UserContentController::containsPluginScript(std::shared_ptr pluginScript, const std::shared_ptr contentWorld) const + { + if (!pluginScript || !map_contains(pluginScriptsInContentWorlds_, contentWorld->name)) { + return false; + } + + return vector_contains(pluginScriptsInContentWorlds_.at(contentWorld->name), std::move(pluginScript)); + } + + bool UserContentController::containsPluginScriptByGroupName(const std::string& groupName) const + { + return vector_contains_if(pluginScripts_.at(UserScriptInjectionTime::atDocumentStart), [groupName](const std::shared_ptr pluginScript) { return string_equals(groupName, pluginScript->groupName); }) || + vector_contains_if(pluginScripts_.at(UserScriptInjectionTime::atDocumentEnd), [groupName](const std::shared_ptr pluginScript) { return string_equals(groupName, pluginScript->groupName); }); + } + + void UserContentController::removePluginScriptsByGroupName(const std::string& groupName) + { + std::vector> pluginScriptsAtStart = pluginScripts_.at(UserScriptInjectionTime::atDocumentStart); + std::vector> pluginScriptsAtEnd = pluginScripts_.at(UserScriptInjectionTime::atDocumentEnd); + + for (auto& pluginScript : pluginScriptsAtStart) { + if (string_equals(groupName, pluginScript->groupName)) { + removePluginScript(pluginScript); + } + } + + for (auto& pluginScript : pluginScriptsAtEnd) { + if (string_equals(groupName, pluginScript->groupName)) { + removePluginScript(pluginScript); + } + } + } + + + std::vector> UserContentController::getPluginScriptsRequiredInAllContentWorlds() const + { + std::vector> res; + + std::vector> pluginScriptsAtStart = pluginScripts_.at(UserScriptInjectionTime::atDocumentStart); + std::vector> pluginScriptsAtEnd = pluginScripts_.at(UserScriptInjectionTime::atDocumentEnd); + + for (auto& pluginScript : pluginScriptsAtStart) { + if (!pluginScript->contentWorld && pluginScript->isRequiredInAllContentWorlds()) { + res.push_back(pluginScript); + } + } + + for (auto& pluginScript : pluginScriptsAtEnd) { + if (!pluginScript->contentWorld && pluginScript->isRequiredInAllContentWorlds()) { + res.push_back(pluginScript); + } + } + + return res; + } + + void UserContentController::createContentWorld(const std::shared_ptr contentWorld, const std::function completionHandler) + { + if (!webView_ || !(webView_->webView) || ContentWorld::isPage(contentWorld)) { + if (completionHandler) { + completionHandler(-1); + } + return; + } + + auto& worldName = contentWorld->name; + if (!map_contains(contentWorlds_, worldName)) { + nlohmann::json parameters = { + {"frameId", webView_->pageFrameId()}, + {"worldName", worldName} + }; + auto hr = webView_->webView->CallDevToolsProtocolMethod(L"Page.createIsolatedWorld", utf8_to_wide(parameters.dump()).c_str(), Callback( + [this, completionHandler, worldName](HRESULT errorCode, LPCWSTR returnObjectAsJson) + { + if (succeededOrLog(errorCode) && completionHandler) { + auto id = nlohmann::json::parse(wide_to_utf8(returnObjectAsJson))["executionContextId"].get(); + addPluginScriptsIfRequired(std::make_shared(worldName)); + completionHandler(id); + } + return S_OK; + } + ).Get()); + if (failedAndLog(hr) && completionHandler) { + completionHandler(-1); + } + } + else if (completionHandler) { + completionHandler(contentWorlds_.at(worldName)); + } + } + + void UserContentController::addScriptToWebView(std::shared_ptr userScript, const std::function completionHandler) const + { + if (!webView_ || !(webView_->webView)) { + if (completionHandler) { + completionHandler(userScript->id); + } + } + + std::string source = userScript->source; + if (userScript->injectionTime == UserScriptInjectionTime::atDocumentEnd) { + source = replace_all_copy(USER_SCRIPTS_AT_DOCUMENT_END_WRAPPER_JS_SOURCE, VAR_PLACEHOLDER_VALUE, source); + std::ostringstream address; + address << std::addressof(userScript); + replace_all(source, VAR_PLACEHOLDER_MEMORY_ADDRESS_VALUE, address.str()); + } + + nlohmann::json parameters = { + {"source", source} + }; + + if (userScript->contentWorld && !ContentWorld::isPage(userScript->contentWorld)) { + parameters["worldName"] = userScript->contentWorld->name; + } + + auto hr = webView_->webView->CallDevToolsProtocolMethod(L"Page.addScriptToEvaluateOnNewDocument", utf8_to_wide(parameters.dump()).c_str(), Callback( + [userScript, completionHandler](HRESULT errorCode, LPCWSTR returnObjectAsJson) + { + if (succeededOrLog(errorCode)) { + nlohmann::json json = nlohmann::json::parse(wide_to_utf8(returnObjectAsJson)); + userScript->id = json["identifier"].get(); + } + if (completionHandler) { + completionHandler(userScript->id); + } + return S_OK; + } + ).Get()); + + if (failedAndLog(hr) && completionHandler) { + completionHandler(userScript->id); + } + } + + void UserContentController::removeScriptFromWebView(std::shared_ptr userScript, const std::function completionHandler) const + { + if (!webView_ || !(webView_->webView)) { + if (completionHandler) { + completionHandler(); + } + return; + } + + nlohmann::json parameters = { + {"identifier", userScript->id} + }; + + auto hr = webView_->webView->CallDevToolsProtocolMethod(L"Page.removeScriptToEvaluateOnNewDocument", utf8_to_wide(parameters.dump()).c_str(), Callback( + [userScript, completionHandler](HRESULT errorCode, LPCWSTR returnObjectAsJson) + { + failedLog(errorCode); + if (completionHandler) { + completionHandler(); + } + return S_OK; + } + ).Get()); + + if (failedAndLog(hr) && completionHandler) { + completionHandler(); + } + } + + void UserContentController::addPluginScriptsIfRequired(const std::shared_ptr contentWorld) + { + if (contentWorld && !ContentWorld::isPage(contentWorld)) { + std::vector> pluginScriptsRequiredInAllContentWorlds = getPluginScriptsRequiredInAllContentWorlds(); + for (auto& pluginScript : pluginScriptsRequiredInAllContentWorlds) { + if (!containsPluginScript(pluginScript, contentWorld)) { + if (!map_contains(pluginScriptsInContentWorlds_, contentWorld->name)) { + pluginScriptsInContentWorlds_.insert({ contentWorld->name, {} }); + } + pluginScriptsInContentWorlds_.at(contentWorld->name).push_back(pluginScript); + addPluginScript(pluginScript->copyAndSet(contentWorld)); + } + } + } + } + + UserContentController::~UserContentController() + { + debugLog("dealloc UserContentController"); + removeAllUserOnlyScripts(); + removeAllPluginScripts(); + contentWorlds_.clear(); + pluginScriptsInContentWorlds_.clear(); + webView_ = nullptr; + } +} diff --git a/flutter_inappwebview_windows/windows/in_app_webview/user_content_controller.h b/flutter_inappwebview_windows/windows/in_app_webview/user_content_controller.h new file mode 100644 index 00000000..a6592913 --- /dev/null +++ b/flutter_inappwebview_windows/windows/in_app_webview/user_content_controller.h @@ -0,0 +1,78 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_USER_CONTENT_CONTROLLER_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_USER_CONTENT_CONTROLLER_H_ + +#include +#include +#include + +#include "../plugin_scripts_js/javascript_bridge_js.h" +#include "../plugin_scripts_js/plugin_scripts_util.h" +#include "../types/content_world.h" +#include "../types/plugin_script.h" +#include "../types/user_script.h" + +namespace flutter_inappwebview_plugin +{ + class InAppWebView; + + const std::string USER_SCRIPTS_AT_DOCUMENT_END_WRAPPER_JS_SOURCE = "window.addEventListener('load', () => { \ + if (window." + JAVASCRIPT_BRIDGE_NAME + " != null && (window." + JAVASCRIPT_BRIDGE_NAME + "._userScript" + VAR_PLACEHOLDER_MEMORY_ADDRESS_VALUE + "AtDocumentEndLoaded == null || !window." + JAVASCRIPT_BRIDGE_NAME + "._userScript" + VAR_PLACEHOLDER_MEMORY_ADDRESS_VALUE + "AtDocumentEndLoaded)) { \ + window." + JAVASCRIPT_BRIDGE_NAME + "._userScript" + VAR_PLACEHOLDER_MEMORY_ADDRESS_VALUE + "AtDocumentEndLoaded = true; \ + " + VAR_PLACEHOLDER_VALUE + " \ + } \ + });"; + + class UserContentController + { + public: + UserContentController(InAppWebView* webView); + ~UserContentController(); + + std::vector> getUserOnlyScriptsAt(const UserScriptInjectionTime& injectionTime) const; + void addUserOnlyScript(std::shared_ptr userScript); + void addUserOnlyScripts(std::vector> userScripts); + void removeUserOnlyScript(std::shared_ptr userScript); + void removeUserOnlyScriptAt(const int64_t& index, const UserScriptInjectionTime& injectionTime); + void removeAllUserOnlyScripts(); + bool containsUserOnlyScript(std::shared_ptr userScript) const; + bool containsUserOnlyScriptByGroupName(const std::string& groupName) const; + void removeUserOnlyScriptsByGroupName(const std::string& groupName); + + std::vector> getPluginScriptsAt(const UserScriptInjectionTime& injectionTime) const; + void addPluginScript(std::shared_ptr pluginScript); + void addPluginScripts(std::vector> pluginScripts); + void removePluginScript(std::shared_ptr pluginScript); + void removeAllPluginScripts(); + bool containsPluginScript(std::shared_ptr pluginScript) const; + bool containsPluginScript(std::shared_ptr pluginScript, const std::shared_ptr contentWorld) const; + bool containsPluginScriptByGroupName(const std::string& groupName) const; + void removePluginScriptsByGroupName(const std::string& groupName); + std::vector> getPluginScriptsRequiredInAllContentWorlds() const; + + void registerEventHandlers(); + void createContentWorld(const std::shared_ptr contentWorld, const std::function completionHandler); + private: + InAppWebView* webView_; + + // used to track Content World names -> Execution Context ID + std::map contentWorlds_; + // used only to track plugin script to inject inside new Content Worlds + std::map>> pluginScriptsInContentWorlds_; + + std::map>> pluginScripts_ = { + {UserScriptInjectionTime::atDocumentStart, {}}, + {UserScriptInjectionTime::atDocumentEnd, {}} + }; + + std::map>> userOnlyScripts_ = { + {UserScriptInjectionTime::atDocumentStart, {}}, + {UserScriptInjectionTime::atDocumentEnd, {}} + }; + + void addScriptToWebView(std::shared_ptr userScript, const std::function completionHandler) const; + void removeScriptFromWebView(std::shared_ptr userScript, const std::function completionHandler) const; + + void addPluginScriptsIfRequired(const std::shared_ptr contentWorld); + }; +} +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_USER_CONTENT_CONTROLLER_H_ \ No newline at end of file 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 new file mode 100644 index 00000000..a3ffc806 --- /dev/null +++ b/flutter_inappwebview_windows/windows/in_app_webview/webview_channel_delegate.cpp @@ -0,0 +1,373 @@ +#include "../in_app_browser/in_app_browser.h" +#include "../types/base_callback_result.h" +#include "../types/content_world.h" +#include "../utils/flutter.h" +#include "../utils/log.h" +#include "../utils/strconv.h" +#include "../utils/string.h" +#include "in_app_webview.h" +#include "webview_channel_delegate.h" + +namespace flutter_inappwebview_plugin +{ + WebViewChannelDelegate::WebViewChannelDelegate(InAppWebView* webView, flutter::BinaryMessenger* messenger) + : webView(webView), ChannelDelegate(messenger, InAppWebView::METHOD_CHANNEL_NAME_PREFIX + variant_to_string(webView->id)) + {} + + WebViewChannelDelegate::WebViewChannelDelegate(InAppWebView* webView, flutter::BinaryMessenger* messenger, const std::string& name) + : webView(webView), ChannelDelegate(messenger, name) + {} + + WebViewChannelDelegate::ShouldOverrideUrlLoadingCallback::ShouldOverrideUrlLoadingCallback() + { + decodeResult = [](const flutter::EncodableValue* value) + { + if (!value || value->IsNull()) { + return NavigationActionPolicy::cancel; + } + auto navigationPolicy = std::get(*value); + return static_cast(navigationPolicy); + }; + } + + WebViewChannelDelegate::CallJsHandlerCallback::CallJsHandlerCallback() + { + decodeResult = [](const flutter::EncodableValue* value) + { + return value; + }; + } + + void WebViewChannelDelegate::HandleMethodCall(const flutter::MethodCall& method_call, + std::unique_ptr> result) + { + if (!webView) { + result->Success(); + return; + } + + auto& arguments = std::get(*method_call.arguments()); + auto& methodName = method_call.method_name(); + + if (string_equals(methodName, "getUrl")) { + result->Success(make_fl_value(webView->getUrl())); + } + else if (string_equals(methodName, "getTitle")) { + result->Success(make_fl_value(webView->getUrl())); + } + else if (string_equals(methodName, "loadUrl")) { + auto urlRequest = std::make_unique(get_fl_map_value(arguments, "urlRequest")); + webView->loadUrl(std::move(urlRequest)); + result->Success(true); + } + else if (string_equals(methodName, "loadFile")) { + auto assetFilePath = get_fl_map_value(arguments, "assetFilePath"); + webView->loadFile(assetFilePath); + result->Success(true); + } + else if (string_equals(methodName, "loadData")) { + auto data = get_fl_map_value(arguments, "data"); + webView->loadData(data); + result->Success(true); + } + else if (string_equals(methodName, "reload")) { + webView->reload(); + result->Success(true); + } + else if (string_equals(methodName, "goBack")) { + webView->goBack(); + result->Success(true); + } + else if (string_equals(methodName, "canGoBack")) { + result->Success(webView->canGoBack()); + } + else if (string_equals(methodName, "goForward")) { + webView->goForward(); + result->Success(true); + } + else if (string_equals(methodName, "canGoForward")) { + result->Success(webView->canGoForward()); + } + else if (string_equals(methodName, "goBackOrForward")) { + auto steps = get_fl_map_value(arguments, "steps"); + webView->goBackOrForward(steps); + result->Success(true); + } + else if (string_equals(methodName, "canGoBackOrForward")) { + auto result_ = std::shared_ptr>(std::move(result)); + + auto steps = get_fl_map_value(arguments, "steps"); + webView->canGoBackOrForward(steps, [result_ = std::move(result_)](const bool& value) + { + result_->Success(value); + }); + } + else if (string_equals(methodName, "isLoading")) { + result->Success(webView->isLoading()); + } + else if (string_equals(methodName, "stopLoading")) { + webView->stopLoading(); + result->Success(true); + } + else if (string_equals(methodName, "evaluateJavascript")) { + auto result_ = std::shared_ptr>(std::move(result)); + + auto source = get_fl_map_value(arguments, "source"); + auto contentWorldMap = get_optional_fl_map_value(arguments, "contentWorld"); + std::shared_ptr contentWorld = contentWorldMap.has_value() ? std::make_shared(contentWorldMap.value()) : ContentWorld::page(); + webView->evaluateJavascript(source, std::move(contentWorld), [result_ = std::move(result_)](const std::string& value) + { + result_->Success(value); + }); + } + else if (string_equals(methodName, "callAsyncJavaScript")) { + auto result_ = std::shared_ptr>(std::move(result)); + + auto functionBody = get_fl_map_value(arguments, "functionBody"); + auto argumentsAsJson = get_fl_map_value(arguments, "arguments"); + auto contentWorldMap = get_optional_fl_map_value(arguments, "contentWorld"); + std::shared_ptr contentWorld = contentWorldMap.has_value() ? std::make_shared(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>(std::move(result)); + webView->getCopyBackForwardList([result_ = std::move(result_)](const std::unique_ptr value) + { + result_->Success(value->toEncodableMap()); + }); + } + else if (string_equals(methodName, "addUserScript")) { + auto userScript = std::make_unique(get_fl_map_value(arguments, "userScript")); + webView->addUserScript(std::move(userScript)); + result->Success(true); + } + else if (string_equals(methodName, "removeUserScript")) { + auto index = get_fl_map_value(arguments, "index"); + auto userScript = std::make_unique(get_fl_map_value(arguments, "userScript")); + webView->removeUserScript(index, std::move(userScript)); + result->Success(true); + } + else if (string_equals(methodName, "removeUserScriptsByGroupName")) { + auto groupName = get_fl_map_value(arguments, "groupName"); + webView->removeUserScriptsByGroupName(groupName); + result->Success(true); + } + else if (string_equals(methodName, "removeAllUserScripts")) { + webView->removeAllUserScripts(); + result->Success(true); + } + else if (string_equals(methodName, "takeScreenshot")) { + auto result_ = std::shared_ptr>(std::move(result)); + auto screenshotConfigurationMap = get_optional_fl_map_value(arguments, "screenshotConfiguration"); + std::optional> screenshotConfiguration = + screenshotConfigurationMap.has_value() ? std::make_unique(screenshotConfigurationMap.value()) : std::optional>{}; + webView->takeScreenshot(std::move(screenshotConfiguration), [result_ = std::move(result_)](const std::optional data) + { + result_->Success(make_fl_value(data)); + }); + } + else if (string_equals(methodName, "setSettings")) { + if (webView->inAppBrowser) { + auto settingsMap = get_fl_map_value(arguments, "settings"); + auto settings = std::make_unique(settingsMap); + webView->inAppBrowser->setSettings(std::move(settings), settingsMap); + } + else { + auto settingsMap = get_fl_map_value(arguments, "settings"); + auto settings = std::make_unique(settingsMap); + webView->setSettings(std::move(settings), settingsMap); + } + result->Success(true); + } + else if (string_equals(methodName, "getSettings")) { + if (webView->inAppBrowser) { + result->Success(webView->inAppBrowser->getSettings()); + } + else { + result->Success(webView->getSettings()); + } + } + else if (string_equals(methodName, "openDevTools")) { + webView->openDevTools(); + result->Success(true); + } + else if (string_equals(methodName, "callDevToolsProtocolMethod")) { + auto result_ = std::shared_ptr>(std::move(result)); + auto cdpMethodName = get_fl_map_value(arguments, "methodName"); + auto parametersAsJson = get_optional_fl_map_value(arguments, "parametersAsJson"); + webView->callDevToolsProtocolMethod(cdpMethodName, parametersAsJson, [result_ = std::move(result_)](const HRESULT& errorCode, const std::optional& data) + { + if (SUCCEEDED(errorCode)) { + result_->Success(make_fl_value(data)); + } + else { + result_->Error(std::to_string(errorCode), getHRMessage(errorCode)); + } + }); + } + else if (string_equals(methodName, "addDevToolsProtocolEventListener")) { + auto eventName = get_fl_map_value(arguments, "eventName"); + webView->addDevToolsProtocolEventListener(eventName); + result->Success(true); + } + else if (string_equals(methodName, "removeDevToolsProtocolEventListener")) { + auto eventName = get_fl_map_value(arguments, "eventName"); + webView->removeDevToolsProtocolEventListener(eventName); + result->Success(true); + } + // for inAppBrowser + else if (webView->inAppBrowser && string_equals(methodName, "show")) { + webView->inAppBrowser->show(); + result->Success(true); + } + else if (webView->inAppBrowser && string_equals(methodName, "hide")) { + webView->inAppBrowser->hide(); + result->Success(true); + } + else if (webView->inAppBrowser && string_equals(methodName, "close")) { + webView->inAppBrowser->close(); + result->Success(true); + } + else { + result->NotImplemented(); + } + } + + void WebViewChannelDelegate::onLoadStart(const std::optional& url) const + { + if (!channel) { + return; + } + + auto arguments = std::make_unique(flutter::EncodableMap{ + {"url", make_fl_value(url)}, + }); + channel->InvokeMethod("onLoadStart", std::move(arguments)); + } + + void WebViewChannelDelegate::onLoadStop(const std::optional& url) const + { + if (!channel) { + return; + } + + auto arguments = std::make_unique(flutter::EncodableMap{ + {"url", make_fl_value(url)}, + }); + channel->InvokeMethod("onLoadStop", std::move(arguments)); + } + + void WebViewChannelDelegate::shouldOverrideUrlLoading(std::shared_ptr navigationAction, std::unique_ptr callback) const + { + if (!channel) { + return; + } + + auto arguments = std::make_unique(navigationAction->toEncodableMap()); + channel->InvokeMethod("shouldOverrideUrlLoading", std::move(arguments), std::move(callback)); + } + + void WebViewChannelDelegate::onReceivedError(std::shared_ptr request, std::shared_ptr error) const + { + if (!channel) { + return; + } + + auto arguments = std::make_unique(flutter::EncodableMap{ + {"request", request->toEncodableMap()}, + {"error", error->toEncodableMap()}, + }); + channel->InvokeMethod("onReceivedError", std::move(arguments)); + } + + void WebViewChannelDelegate::onReceivedHttpError(std::shared_ptr request, std::shared_ptr errorResponse) const + { + if (!channel) { + return; + } + + auto arguments = std::make_unique(flutter::EncodableMap{ + {"request", request->toEncodableMap()}, + {"errorResponse", errorResponse->toEncodableMap()}, + }); + channel->InvokeMethod("onReceivedHttpError", std::move(arguments)); + } + + void WebViewChannelDelegate::onTitleChanged(const std::optional& title) const + { + if (!channel) { + return; + } + + auto arguments = std::make_unique(flutter::EncodableMap{ + {"title", make_fl_value(title)} + }); + channel->InvokeMethod("onTitleChanged", std::move(arguments)); + + if (webView && webView->inAppBrowser) { + webView->inAppBrowser->didChangeTitle(title); + } + } + + void WebViewChannelDelegate::onUpdateVisitedHistory(const std::optional& url, const std::optional& isReload) const + { + if (!channel) { + return; + } + + auto arguments = std::make_unique(flutter::EncodableMap{ + {"url", make_fl_value(url)}, + {"isReload", make_fl_value(isReload)} + }); + channel->InvokeMethod("onUpdateVisitedHistory", std::move(arguments)); + } + + void WebViewChannelDelegate::onCallJsHandler(const std::string& handlerName, const std::string& args, std::unique_ptr callback) const + { + if (!channel) { + return; + } + + auto arguments = std::make_unique(flutter::EncodableMap{ + {"handlerName", handlerName}, + {"args", args} + }); + channel->InvokeMethod("onCallJsHandler", std::move(arguments), std::move(callback)); + } + + void WebViewChannelDelegate::onConsoleMessage(const std::string& message, const int64_t& messageLevel) const + { + if (!channel) { + return; + } + + auto arguments = std::make_unique(flutter::EncodableMap{ + {"message", message}, + {"messageLevel", messageLevel} + }); + channel->InvokeMethod("onConsoleMessage", std::move(arguments)); + } + + + void WebViewChannelDelegate::onDevToolsProtocolEventReceived(const std::string& eventName, const std::string& data) const + { + if (!channel) { + return; + } + + auto arguments = std::make_unique(flutter::EncodableMap{ + {"eventName", eventName}, + {"data", data} + }); + channel->InvokeMethod("onDevToolsProtocolEventReceived", std::move(arguments)); + } + + WebViewChannelDelegate::~WebViewChannelDelegate() + { + debugLog("dealloc WebViewChannelDelegate"); + webView = nullptr; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/in_app_webview/webview_channel_delegate.h b/flutter_inappwebview_windows/windows/in_app_webview/webview_channel_delegate.h new file mode 100644 index 00000000..b27cbb9e --- /dev/null +++ b/flutter_inappwebview_windows/windows/in_app_webview/webview_channel_delegate.h @@ -0,0 +1,58 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_WEBVIEW_CHANNEL_DELEGATE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_WEBVIEW_CHANNEL_DELEGATE_H_ + +#include +#include + +#include "../types/base_callback_result.h" +#include "../types/channel_delegate.h" +#include "../types/navigation_action.h" +#include "../types/web_resource_error.h" +#include "../types/web_resource_request.h" +#include "../types/web_resource_response.h" + +namespace flutter_inappwebview_plugin +{ + class InAppWebView; + + enum NavigationActionPolicy { cancel = 0, allow = 1 }; + + class WebViewChannelDelegate : public ChannelDelegate + { + public: + InAppWebView* webView; + + class ShouldOverrideUrlLoadingCallback : public BaseCallbackResult { + public: + ShouldOverrideUrlLoadingCallback(); + ~ShouldOverrideUrlLoadingCallback() = default; + }; + + class CallJsHandlerCallback : public BaseCallbackResult { + public: + CallJsHandlerCallback(); + ~CallJsHandlerCallback() = default; + }; + + WebViewChannelDelegate(InAppWebView* webView, flutter::BinaryMessenger* messenger); + WebViewChannelDelegate(InAppWebView* webView, flutter::BinaryMessenger* messenger, const std::string& name); + ~WebViewChannelDelegate(); + + void HandleMethodCall( + const flutter::MethodCall& method_call, + std::unique_ptr> result); + + void onLoadStart(const std::optional& url) const; + void onLoadStop(const std::optional& url) const; + void shouldOverrideUrlLoading(std::shared_ptr navigationAction, std::unique_ptr callback) const; + void onReceivedError(std::shared_ptr request, std::shared_ptr error) const; + void onReceivedHttpError(std::shared_ptr request, std::shared_ptr error) const; + void onTitleChanged(const std::optional& title) const; + void onUpdateVisitedHistory(const std::optional& url, const std::optional& isReload) const; + void onCallJsHandler(const std::string& handlerName, const std::string& args, std::unique_ptr callback) const; + void onConsoleMessage(const std::string& message, const int64_t& messageLevel) const; + void onDevToolsProtocolEventReceived(const std::string& eventName, const std::string& data) const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_WEBVIEW_CHANNEL_DELEGATE_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/include/flutter_inappwebview_windows/flutter_inappwebview_windows_plugin_c_api.h b/flutter_inappwebview_windows/windows/include/flutter_inappwebview_windows/flutter_inappwebview_windows_plugin_c_api.h new file mode 100644 index 00000000..6a318ec0 --- /dev/null +++ b/flutter_inappwebview_windows/windows/include/flutter_inappwebview_windows/flutter_inappwebview_windows_plugin_c_api.h @@ -0,0 +1,23 @@ +#ifndef FLUTTER_PLUGIN_FLUTTER_INAPPWEBVIEW_PLUGIN_PLUGIN_C_API_H_ +#define FLUTTER_PLUGIN_FLUTTER_INAPPWEBVIEW_PLUGIN_PLUGIN_C_API_H_ + +#include + +#ifdef FLUTTER_PLUGIN_IMPL +#define FLUTTER_PLUGIN_EXPORT __declspec(dllexport) +#else +#define FLUTTER_PLUGIN_EXPORT __declspec(dllimport) +#endif + +#if defined(__cplusplus) +extern "C" { +#endif + + FLUTTER_PLUGIN_EXPORT void FlutterInappwebviewWindowsPluginCApiRegisterWithRegistrar( + FlutterDesktopPluginRegistrarRef registrar); + +#if defined(__cplusplus) +} // extern "C" +#endif + +#endif // FLUTTER_PLUGIN_FLUTTER_INAPPWEBVIEW_PLUGIN_PLUGIN_C_API_H_ diff --git a/flutter_inappwebview_windows/windows/plugin_scripts_js/javascript_bridge_js.cpp b/flutter_inappwebview_windows/windows/plugin_scripts_js/javascript_bridge_js.cpp new file mode 100644 index 00000000..dcb743bf --- /dev/null +++ b/flutter_inappwebview_windows/windows/plugin_scripts_js/javascript_bridge_js.cpp @@ -0,0 +1,19 @@ +#include + +#include "javascript_bridge_js.h" + +namespace flutter_inappwebview_plugin +{ + std::unique_ptr createJavaScriptBridgePluginScript() + { + const std::vector allowedOriginRules = { "*" }; + return std::make_unique( + JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT_GROUP_NAME, + JAVASCRIPT_BRIDGE_JS_SOURCE, + UserScriptInjectionTime::atDocumentStart, + allowedOriginRules, + nullptr, + true + ); + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/plugin_scripts_js/javascript_bridge_js.h b/flutter_inappwebview_windows/windows/plugin_scripts_js/javascript_bridge_js.h new file mode 100644 index 00000000..a10c832c --- /dev/null +++ b/flutter_inappwebview_windows/windows/plugin_scripts_js/javascript_bridge_js.h @@ -0,0 +1,31 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_JAVASCRIPT_BRIDGE_JS_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_JAVASCRIPT_BRIDGE_JS_H_ + +#include +#include + +#include "../types/plugin_script.h" + +namespace flutter_inappwebview_plugin +{ + const std::string JAVASCRIPT_BRIDGE_NAME = "flutter_inappwebview"; + const std::string JAVASCRIPT_BRIDGE_JS_SOURCE = "window." + JAVASCRIPT_BRIDGE_NAME + " = {}; \ + window." + JAVASCRIPT_BRIDGE_NAME + ".callHandler = function() { \ + var _callHandlerID = setTimeout(function() {}); \ + window.chrome.webview.postMessage({ 'name': 'callHandler', 'body': {'handlerName': arguments[0], '_callHandlerID' : _callHandlerID, 'args' : JSON.stringify(Array.prototype.slice.call(arguments, 1))} }); \ + return new Promise(function(resolve, reject) { \ + window." + JAVASCRIPT_BRIDGE_NAME + "[_callHandlerID] = { resolve: resolve, reject : reject };\ + });\ + };"; + const std::string JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT"; + const std::string PLATFORM_READY_JS_SOURCE = "(function() { \ + if ((window.top == null || window.top === window) && window." + JAVASCRIPT_BRIDGE_NAME + " != null && window." + JAVASCRIPT_BRIDGE_NAME + "._platformReady == null) { \ + window.dispatchEvent(new Event('flutterInAppWebViewPlatformReady')); \ + window." + JAVASCRIPT_BRIDGE_NAME + "._platformReady = true; \ + } \ + })();"; + + std::unique_ptr createJavaScriptBridgePluginScript(); +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_JAVASCRIPT_BRIDGE_JS_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/plugin_scripts_js/plugin_scripts_util.h b/flutter_inappwebview_windows/windows/plugin_scripts_js/plugin_scripts_util.h new file mode 100644 index 00000000..239783c3 --- /dev/null +++ b/flutter_inappwebview_windows/windows/plugin_scripts_js/plugin_scripts_util.h @@ -0,0 +1,15 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_PLUGIN_SCRIPTS_UTIL_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_PLUGIN_SCRIPTS_UTIL_H_ + +#include + +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_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/test/flutter_inappwebview_windows_plugin_test.cpp b/flutter_inappwebview_windows/windows/test/flutter_inappwebview_windows_plugin_test.cpp new file mode 100644 index 00000000..f2504fd6 --- /dev/null +++ b/flutter_inappwebview_windows/windows/test/flutter_inappwebview_windows_plugin_test.cpp @@ -0,0 +1,43 @@ +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "flutter_inappwebview_windows_plugin.h" + +namespace flutter_inappwebview_windows { +namespace test { + +namespace { + +using flutter::EncodableMap; +using flutter::EncodableValue; +using flutter::MethodCall; +using flutter::MethodResultFunctions; + +} // namespace + +TEST(FlutterInappwebviewWindowsPlugin, GetPlatformVersion) { + FlutterInappwebviewWindowsPlugin plugin; + // Save the reply value from the success callback. + std::string result_string; + plugin.HandleMethodCall( + MethodCall("getPlatformVersion", std::make_unique()), + std::make_unique>( + [&result_string](const EncodableValue* result) { + result_string = std::get(*result); + }, + nullptr, nullptr)); + + // Since the exact string varies by host, just ensure that it's a string + // with the expected format. + EXPECT_TRUE(result_string.rfind("Windows ", 0) == 0); +} + +} // namespace test +} // namespace flutter_inappwebview_windows diff --git a/flutter_inappwebview_windows/windows/types/base_callback_result.h b/flutter_inappwebview_windows/windows/types/base_callback_result.h new file mode 100644 index 00000000..d156e1e6 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/base_callback_result.h @@ -0,0 +1,57 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_BASE_CALLBACK_RESULT_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_BASE_CALLBACK_RESULT_H_ + +#include +#include +#include + +namespace flutter_inappwebview_plugin +{ + template + class BaseCallbackResult : public flutter::MethodResultFunctions + { + public: + flutter::ResultHandlerError error; + flutter::ResultHandlerNotImplemented notImplemented; + std::function nonNullSuccess = [](const T result) { return true; }; + std::function nullSuccess = []() { return true; }; + std::function result)> defaultBehaviour = [](const std::optional result) {}; + std::function(const flutter::EncodableValue* result)> decodeResult = [](const flutter::EncodableValue* result) { return std::nullopt; }; + + BaseCallbackResult() : + MethodResultFunctions( + [this](const flutter::EncodableValue* val) + { + std::optional 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() {}; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_BASE_CALLBACK_RESULT_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/callbacks_complete.h b/flutter_inappwebview_windows/windows/types/callbacks_complete.h new file mode 100644 index 00000000..c2ba5704 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/callbacks_complete.h @@ -0,0 +1,39 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_CALLBACKS_COMPLETE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_CALLBACKS_COMPLETE_H_ + +#include +#include +#include + +namespace flutter_inappwebview_plugin +{ + template + class CallbacksComplete + { + public: + std::function&)> onComplete; + + CallbacksComplete(const std::function&)> onComplete) + : onComplete(onComplete) + {} + + ~CallbacksComplete() + { + if (onComplete) { + onComplete(values_); + } + } + + void addValue(const T& value) + { + const std::lock_guard lock(mutex_); + values_.push_back(value); + } + + private: + std::vector values_; + std::mutex mutex_; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_CALLBACKS_COMPLETE_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/channel_delegate.cpp b/flutter_inappwebview_windows/windows/types/channel_delegate.cpp new file mode 100644 index 00000000..983a2366 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/channel_delegate.cpp @@ -0,0 +1,35 @@ +#include +#include + +#include "../utils/util.h" +#include "channel_delegate.h" + +namespace flutter_inappwebview_plugin +{ + ChannelDelegate::ChannelDelegate(flutter::BinaryMessenger* messenger, const std::string& name) : messenger(messenger) + { + channel = std::make_shared>( + this->messenger, name, + &flutter::StandardMethodCodec::GetInstance() + ); + channel->SetMethodCallHandler( + [this](const auto& call, auto result) + { + this->HandleMethodCall(call, std::move(result)); + }); + } + + void ChannelDelegate::HandleMethodCall( + const flutter::MethodCall& method_call, + std::unique_ptr> result) + {} + + ChannelDelegate::~ChannelDelegate() + { + messenger = nullptr; + if (channel != nullptr) { + channel->SetMethodCallHandler(nullptr); + } + channel.reset(); + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/channel_delegate.h b/flutter_inappwebview_windows/windows/types/channel_delegate.h new file mode 100644 index 00000000..a412b7d1 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/channel_delegate.h @@ -0,0 +1,25 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_CHANNEL_DELEGATE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_CHANNEL_DELEGATE_H_ + +#include + +namespace flutter_inappwebview_plugin +{ + class ChannelDelegate + { + using FlutterMethodChannel = std::shared_ptr>; + + public: + FlutterMethodChannel channel; + flutter::BinaryMessenger* messenger; + + ChannelDelegate(flutter::BinaryMessenger* messenger, const std::string& name); + virtual ~ChannelDelegate(); + + virtual void HandleMethodCall( + const flutter::MethodCall& method_call, + std::unique_ptr> result); + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_CHANNEL_DELEGATE_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/content_world.cpp b/flutter_inappwebview_windows/windows/types/content_world.cpp new file mode 100644 index 00000000..a95e773c --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/content_world.cpp @@ -0,0 +1,60 @@ +#include "content_world.h" + +namespace flutter_inappwebview_plugin +{ + namespace { + const std::shared_ptr ContentWorldPage = std::make_shared("page"); + const std::shared_ptr ContentWorldDefaultClient = std::make_shared("defaultClient"); + } + + ContentWorld::ContentWorld(const std::string& name) + : name(name) + {} + + ContentWorld::ContentWorld(const flutter::EncodableMap& map) + : name(get_fl_map_value(map, "name")) + {} + + bool ContentWorld::isSame(const ContentWorld& contentWorld) const + { + return name == contentWorld.name; + } + + bool ContentWorld::isSame(const std::shared_ptr contentWorld) const + { + return contentWorld && name == contentWorld->name; + } + + const std::shared_ptr ContentWorld::page() + { + return ContentWorldPage; + } + + const std::shared_ptr ContentWorld::defaultClient() + { + return ContentWorldDefaultClient; + } + + bool ContentWorld::isPage(const ContentWorld& contentWorld) + { + return contentWorld.isSame(*ContentWorld::page()); + } + + bool ContentWorld::isPage(const std::shared_ptr contentWorld) + { + return ContentWorld::page()->isSame(contentWorld); + } + + bool ContentWorld::isDefaultClient(const ContentWorld& contentWorld) + { + return contentWorld.isSame(*ContentWorld::defaultClient()); + } + + bool ContentWorld::isDefaultClient(const std::shared_ptr contentWorld) + { + return ContentWorld::defaultClient()->isSame(contentWorld); + } + + ContentWorld::~ContentWorld() + {} +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/content_world.h b/flutter_inappwebview_windows/windows/types/content_world.h new file mode 100644 index 00000000..46622289 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/content_world.h @@ -0,0 +1,33 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_CONTENT_WORLD_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_CONTENT_WORLD_H_ + +#include +#include + +#include "../utils/flutter.h" + +namespace flutter_inappwebview_plugin +{ + class ContentWorld + { + public: + + const std::string name; + + ContentWorld(const std::string& name); + ContentWorld(const flutter::EncodableMap& map); + ~ContentWorld(); + + bool isSame(const ContentWorld& contentWorld) const; + bool isSame(const std::shared_ptr contentWorld) const; + + const static std::shared_ptr page(); + const static std::shared_ptr defaultClient(); + + static bool isPage(const ContentWorld& contentWorld); + static bool isPage(const std::shared_ptr contentWorld); + static bool isDefaultClient(const ContentWorld& contentWorld); + static bool isDefaultClient(const std::shared_ptr contentWorld); + }; +} +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_CONTENT_WORLD_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/navigation_action.cpp b/flutter_inappwebview_windows/windows/types/navigation_action.cpp new file mode 100644 index 00000000..543553e7 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/navigation_action.cpp @@ -0,0 +1,21 @@ +#include "../utils/flutter.h" +#include "navigation_action.h" + +namespace flutter_inappwebview_plugin +{ + NavigationAction::NavigationAction(std::shared_ptr request, const bool& isForMainFrame, + const std::optional& isRedirect, const std::optional& navigationType) + : request(std::move(request)), isForMainFrame(isForMainFrame), + isRedirect(isRedirect), navigationType(navigationType) + {} + + flutter::EncodableMap NavigationAction::toEncodableMap() const + { + return flutter::EncodableMap{ + {"request", request->toEncodableMap()}, + {"isForMainFrame", isForMainFrame}, + {"isRedirect", make_fl_value(isRedirect)}, + {"navigationType", make_fl_value(navigationType)} + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/navigation_action.h b/flutter_inappwebview_windows/windows/types/navigation_action.h new file mode 100644 index 00000000..886640be --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/navigation_action.h @@ -0,0 +1,33 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_NAVIGATION_ACTION_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_NAVIGATION_ACTION_H_ + +#include +#include + +#include "url_request.h" + +namespace flutter_inappwebview_plugin +{ + enum NavigationActionType { + linkActivated = 0, + backForward, + reload, + other + }; + + class NavigationAction + { + public: + const std::shared_ptr request; + const bool isForMainFrame; + const std::optional isRedirect; + const std::optional navigationType; + + NavigationAction(std::shared_ptr request, const bool& isForMainFrame, const std::optional& isRedirect, const std::optional& navigationType); + ~NavigationAction() = default; + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_NAVIGATION_ACTION_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/plugin_script.cpp b/flutter_inappwebview_windows/windows/types/plugin_script.cpp new file mode 100644 index 00000000..cc37467a --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/plugin_script.cpp @@ -0,0 +1,30 @@ +#include "plugin_script.h" + +namespace flutter_inappwebview_plugin +{ + PluginScript::PluginScript( + const std::optional& groupName, + const std::string& source, + const UserScriptInjectionTime& injectionTime, + const std::vector& allowedOriginRules, + std::shared_ptr contentWorld, + const bool& requiredInAllContentWorlds + ) : UserScript(groupName, source, injectionTime, allowedOriginRules, std::move(contentWorld)), + requiredInAllContentWorlds_(requiredInAllContentWorlds) + {} + + + std::shared_ptr PluginScript::copyAndSet(const std::shared_ptr cw) const + { + return std::make_unique( + this->groupName, + this->source, + this->injectionTime, + this->allowedOriginRules, + cw, + this->requiredInAllContentWorlds_ + ); + } + + PluginScript::~PluginScript() {} +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/plugin_script.h b/flutter_inappwebview_windows/windows/types/plugin_script.h new file mode 100644 index 00000000..e2fae03f --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/plugin_script.h @@ -0,0 +1,33 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_PLUGIN_SCRIPT_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_PLUGIN_SCRIPT_H_ + +#include "user_script.h" + +namespace flutter_inappwebview_plugin +{ + class PluginScript : public UserScript + { + public: + + PluginScript( + const std::optional& groupName, + const std::string& source, + const UserScriptInjectionTime& injectionTime, + const std::vector& allowedOriginRules, + std::shared_ptr contentWorld, + const bool& requiredInAllContentWorlds + ); + ~PluginScript(); + + bool isRequiredInAllContentWorlds() const + { + return requiredInAllContentWorlds_; + } + + std::shared_ptr copyAndSet(const std::shared_ptr cw) const; + + private: + bool requiredInAllContentWorlds_; + }; +} +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_PLUGIN_SCRIPT_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/rect.cpp b/flutter_inappwebview_windows/windows/types/rect.cpp new file mode 100644 index 00000000..a0a41ab0 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/rect.cpp @@ -0,0 +1,25 @@ +#include "rect.h" + +namespace flutter_inappwebview_plugin +{ + Rect::Rect(const double& x, const double& y, const double& width, const double& height) + : x(x), y(y), width(width), height(height) + {} + + Rect::Rect(const flutter::EncodableMap& map) + : x(get_fl_map_value(map, "x")), + y(get_fl_map_value(map, "y")), + width(get_fl_map_value(map, "width")), + height(get_fl_map_value(map, "height")) + {} + + flutter::EncodableMap Rect::toEncodableMap() const + { + return flutter::EncodableMap{ + {"x", x}, + {"y", y}, + {"width", width}, + {"height", height} + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/rect.h b/flutter_inappwebview_windows/windows/types/rect.h new file mode 100644 index 00000000..57c94eeb --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/rect.h @@ -0,0 +1,36 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_RECT_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_RECT_H_ + +#include +#include + +#include "../utils/flutter.h" + +namespace flutter_inappwebview_plugin +{ + class Rect + { + public: + const double x; + const double y; + const double width; + const double height; + + Rect(const double& x, const double& y, const double& width, const double& height); + Rect(const flutter::EncodableMap& map); + ~Rect() = default; + + bool Rect::operator==(const Rect& other) + { + return x == other.x && y == other.y && width == other.width && height == other.height; + } + bool Rect::operator!=(const Rect& other) + { + return !(*this == other); + } + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_RECT_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/screenshot_configuration.cpp b/flutter_inappwebview_windows/windows/types/screenshot_configuration.cpp new file mode 100644 index 00000000..5e0f027e --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/screenshot_configuration.cpp @@ -0,0 +1,48 @@ +#include "../utils/flutter.h" +#include "../utils/map.h" +#include "screenshot_configuration.h" + +namespace flutter_inappwebview_plugin +{ + CompressFormat CompressFormatFromString(const std::string& compressFormat) + { + if (string_equals(compressFormat, "PNG")) { + return CompressFormat::png; + } + else if (string_equals(compressFormat, "JPEG")) { + return CompressFormat::jpeg; + } + else if (string_equals(compressFormat, "WEBP")) { + return CompressFormat::webp; + } + return CompressFormat::png; + } + + std::string CompressFormatToString(const CompressFormat& compressFormat) + { + switch (compressFormat) { + case CompressFormat::jpeg: + return "JPEG"; + case CompressFormat::webp: + return "WEBP"; + case CompressFormat::png: + default: + return "PNG"; + } + } + + ScreenshotConfiguration::ScreenshotConfiguration( + const CompressFormat& compressFormat, + const int64_t& quality, + const std::optional> rect + ) : compressFormat(compressFormat), quality(quality), rect(rect) + {} + + ScreenshotConfiguration::ScreenshotConfiguration(const flutter::EncodableMap& map) + : compressFormat(CompressFormatFromString(get_fl_map_value(map, "compressFormat"))), + quality(get_fl_map_value(map, "quality")), + rect(fl_map_contains_not_null(map, "rect") ? std::make_shared(get_fl_map_value(map, "rect")) : std::optional>{}) + {} + + ScreenshotConfiguration::~ScreenshotConfiguration() {} +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/screenshot_configuration.h b/flutter_inappwebview_windows/windows/types/screenshot_configuration.h new file mode 100644 index 00000000..f05ee7f0 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/screenshot_configuration.h @@ -0,0 +1,38 @@ +#ifndef FLUTTER_INAPPWEBVIEW_SCREENSHOT_CONFIGURATION_H_ +#define FLUTTER_INAPPWEBVIEW_SCREENSHOT_CONFIGURATION_H_ + +#include +#include +#include + +#include "../types/rect.h" +#include "../utils/string.h" + +namespace flutter_inappwebview_plugin +{ + enum CompressFormat { + png, + jpeg, + webp + }; + + CompressFormat CompressFormatFromString(const std::string& compressFormat); + std::string CompressFormatToString(const CompressFormat& compressFormat); + + class ScreenshotConfiguration + { + public: + const CompressFormat compressFormat; + const int64_t quality; + const std::optional> rect; + + ScreenshotConfiguration( + const CompressFormat& compressFormat, + const int64_t& quality, + const std::optional> rect + ); + ScreenshotConfiguration(const flutter::EncodableMap& map); + ~ScreenshotConfiguration(); + }; +} +#endif //FLUTTER_INAPPWEBVIEW_SCREENSHOT_CONFIGURATION_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/size_2d.cpp b/flutter_inappwebview_windows/windows/types/size_2d.cpp new file mode 100644 index 00000000..e1239026 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/size_2d.cpp @@ -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(map, "width")), + height(get_fl_map_value(map, "height")) + {} + + flutter::EncodableMap Size2D::toEncodableMap() const + { + return flutter::EncodableMap{ + {"width", width}, + {"height", height} + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/size_2d.h b/flutter_inappwebview_windows/windows/types/size_2d.h new file mode 100644 index 00000000..e2cc28f6 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/size_2d.h @@ -0,0 +1,34 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_SIZE_2D_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_SIZE_2D_H_ + +#include +#include + +#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; + + bool Size2D::operator==(const Size2D& other) + { + return width == other.width && height == other.height; + } + bool Size2D::operator!=(const Size2D& other) + { + return !(*this == other); + } + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_SIZE_2D_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/url_request.cpp b/flutter_inappwebview_windows/windows/types/url_request.cpp new file mode 100644 index 00000000..42340dd0 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/url_request.cpp @@ -0,0 +1,27 @@ +#include "../utils/flutter.h" +#include "url_request.h" + +namespace flutter_inappwebview_plugin +{ + URLRequest::URLRequest(const std::optional& url, const std::optional& method, + const std::optional>& headers, const std::optional>& body) + : url(url), method(method), headers(headers), body(body) + {} + + URLRequest::URLRequest(const flutter::EncodableMap& map) + : url(get_optional_fl_map_value(map, "url")), + method(get_optional_fl_map_value(map, "method")), + headers(get_optional_fl_map_value>(map, "headers")), + body(get_optional_fl_map_value>(map, "body")) + {} + + flutter::EncodableMap URLRequest::toEncodableMap() const + { + return flutter::EncodableMap{ + {"url", make_fl_value(url)}, + {"method", make_fl_value(method)}, + {"headers", make_fl_value(headers)}, + {"body", make_fl_value(body)} + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/url_request.h b/flutter_inappwebview_windows/windows/types/url_request.h new file mode 100644 index 00000000..37373d1c --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/url_request.h @@ -0,0 +1,28 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_URL_REQUEST_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_URL_REQUEST_H_ + +#include +#include + +#include "../utils/flutter.h" + +namespace flutter_inappwebview_plugin +{ + class URLRequest + { + public: + const std::optional url; + const std::optional method; + const std::optional> headers; + const std::optional> body; + + URLRequest(const std::optional& url, const std::optional& method, + const std::optional>& headers, const std::optional>& body); + URLRequest(const flutter::EncodableMap& map); + ~URLRequest() = default; + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_URL_REQUEST_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/user_script.cpp b/flutter_inappwebview_windows/windows/types/user_script.cpp new file mode 100644 index 00000000..6a783fe0 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/user_script.cpp @@ -0,0 +1,25 @@ +#include "../utils/map.h" +#include "user_script.h" + +namespace flutter_inappwebview_plugin +{ + UserScript::UserScript( + const std::optional& groupName, + const std::string& source, + const UserScriptInjectionTime& injectionTime, + const std::vector& allowedOriginRules, + std::shared_ptr contentWorld + ) : groupName(groupName), source(source), injectionTime(injectionTime), allowedOriginRules(allowedOriginRules), contentWorld(std::move(contentWorld)) + {} + + UserScript::UserScript(const flutter::EncodableMap& map) + : groupName(get_optional_fl_map_value(map, "groupName")), + source(get_fl_map_value(map, "source")), + injectionTime(static_cast(get_fl_map_value(map, "injectionTime"))), + allowedOriginRules(functional_map(get_fl_map_value(map, "allowedOriginRules"), [](const flutter::EncodableValue& m) { return std::get(m); })), + contentWorld(std::make_shared(get_fl_map_value(map, "contentWorld"))) + {} + + UserScript::~UserScript() + {} +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/user_script.h b/flutter_inappwebview_windows/windows/types/user_script.h new file mode 100644 index 00000000..473661f0 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/user_script.h @@ -0,0 +1,40 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_USER_SCRIPT_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_USER_SCRIPT_H_ + +#include +#include +#include +#include + +#include "../utils/flutter.h" +#include "content_world.h" + +namespace flutter_inappwebview_plugin +{ + enum UserScriptInjectionTime { + atDocumentStart = 0, + atDocumentEnd + }; + + class UserScript + { + public: + std::string id; + const std::optional groupName; + const std::string source; + const UserScriptInjectionTime injectionTime; + const std::vector allowedOriginRules; + const std::shared_ptr contentWorld; + + UserScript( + const std::optional& groupName, + const std::string& source, + const UserScriptInjectionTime& injectionTime, + const std::vector& allowedOriginRules, + std::shared_ptr contentWorld + ); + UserScript(const flutter::EncodableMap& map); + ~UserScript(); + }; +} +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_USER_SCRIPT_H_ \ No newline at end of file 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..aa89d9a2 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/web_history.cpp @@ -0,0 +1,22 @@ +#include "../utils/vector.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..11350eeb --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/web_history_item.cpp @@ -0,0 +1,31 @@ +#include "web_history_item.h" + +namespace flutter_inappwebview_plugin +{ + WebHistoryItem::WebHistoryItem(const std::optional& entryId, const std::optional& index, const std::optional& offset, + const std::optional& originalUrl, const std::optional& title, + const std::optional& url) + : entryId(entryId), index(index), offset(offset), originalUrl(originalUrl), title(title), url(url) + {} + + WebHistoryItem::WebHistoryItem(const flutter::EncodableMap& map) + : entryId(get_optional_fl_map_value(map, "entryId")), + 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{ + {"entryId", make_fl_value(entryId)}, + {"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..e58181e3 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/web_history_item.h @@ -0,0 +1,31 @@ +#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 entryId; + const std::optional index; + const std::optional offset; + const std::optional originalUrl; + const std::optional title; + const std::optional url; + + WebHistoryItem(const std::optional& entryId, 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/types/web_resource_error.cpp b/flutter_inappwebview_windows/windows/types/web_resource_error.cpp new file mode 100644 index 00000000..5059a3dc --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/web_resource_error.cpp @@ -0,0 +1,22 @@ +#include "../utils/flutter.h" +#include "web_resource_error.h" + +namespace flutter_inappwebview_plugin +{ + WebResourceError::WebResourceError(const std::string& description, const int64_t type) + : description(description), type(type) + {} + + WebResourceError::WebResourceError(const flutter::EncodableMap& map) + : description(get_fl_map_value(map, "description")), + type(get_fl_map_value(map, "type")) + {} + + flutter::EncodableMap WebResourceError::toEncodableMap() const + { + return flutter::EncodableMap{ + {"description", make_fl_value(description)}, + {"type", make_fl_value(type)} + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/web_resource_error.h b/flutter_inappwebview_windows/windows/types/web_resource_error.h new file mode 100644 index 00000000..f5a0a5aa --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/web_resource_error.h @@ -0,0 +1,46 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_RESOURCE_ERROR_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_RESOURCE_ERROR_H_ + +#include +#include + +namespace flutter_inappwebview_plugin +{ + static const std::string WebErrorStatusDescription[] = + { + "Indicates that an unknown error occurred.", + "Indicates that the SSL certificate common name does not match the web address.", + "Indicates that the SSL certificate has expired.", + "Indicates that the SSL client certificate contains errors.", + "Indicates that the SSL certificate has been revoked.", + "Indicates that the SSL certificate is not valid.", + "Indicates that the host is unreachable.", + "Indicates that the connection has timed out.", + "Indicates that the server returned an invalid or unrecognized response.", + "Indicates that the connection was stopped.", + "Indicates that the connection was reset.", + "Indicates that the Internet connection has been lost.", + "Indicates that a connection to the destination was not established.", + "Indicates that the provided host name was not able to be resolved.", + "Indicates that the operation was canceled.", + "Indicates that the request redirect failed.", + "Indicates that an unexpected error occurred.", + "Indicates that user is prompted with a login, waiting on user action.", + "Indicates that user lacks proper authentication credentials for a proxy server.", + }; + + class WebResourceError + { + public: + const std::string description; + const int64_t type; + + WebResourceError(const std::string& description, const int64_t type); + WebResourceError(const flutter::EncodableMap& map); + ~WebResourceError() = default; + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_RESOURCE_ERROR_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/web_resource_request.cpp b/flutter_inappwebview_windows/windows/types/web_resource_request.cpp new file mode 100644 index 00000000..b7074207 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/web_resource_request.cpp @@ -0,0 +1,27 @@ +#include "../utils/flutter.h" +#include "web_resource_request.h" + +namespace flutter_inappwebview_plugin +{ + WebResourceRequest::WebResourceRequest(const std::optional& url, const std::optional& method, + const std::optional>& headers, const std::optional& isForMainFrame) + : url(url), method(method), headers(headers), isForMainFrame(isForMainFrame) + {} + + WebResourceRequest::WebResourceRequest(const flutter::EncodableMap& map) + : url(get_optional_fl_map_value(map, "url")), + method(get_optional_fl_map_value(map, "method")), + headers(get_optional_fl_map_value>(map, "headers")), + isForMainFrame(get_optional_fl_map_value(map, "isForMainFrame")) + {} + + flutter::EncodableMap WebResourceRequest::toEncodableMap() const + { + return flutter::EncodableMap{ + {"url", make_fl_value(url)}, + {"method", make_fl_value(method)}, + {"headers", make_fl_value(headers)}, + {"isForMainFrame", make_fl_value(isForMainFrame)} + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/web_resource_request.h b/flutter_inappwebview_windows/windows/types/web_resource_request.h new file mode 100644 index 00000000..ea9bb587 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/web_resource_request.h @@ -0,0 +1,26 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_RESOURCE_REQUEST_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_RESOURCE_REQUEST_H_ + +#include +#include + +namespace flutter_inappwebview_plugin +{ + class WebResourceRequest + { + public: + const std::optional url; + const std::optional method; + const std::optional> headers; + const std::optional isForMainFrame; + + WebResourceRequest(const std::optional& url, const std::optional& method, + const std::optional>& headers, const std::optional& isForMainFrame); + WebResourceRequest(const flutter::EncodableMap& map); + ~WebResourceRequest() = default; + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_RESOURCE_REQUEST_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/web_resource_response.cpp b/flutter_inappwebview_windows/windows/types/web_resource_response.cpp new file mode 100644 index 00000000..2e84cd9e --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/web_resource_response.cpp @@ -0,0 +1,20 @@ +#include "../utils/flutter.h" +#include "web_resource_response.h" + +namespace flutter_inappwebview_plugin +{ + WebResourceResponse::WebResourceResponse(const std::optional& statusCode) + : statusCode(statusCode) + {} + + WebResourceResponse::WebResourceResponse(const flutter::EncodableMap& map) + : statusCode(get_optional_fl_map_value(map, "statusCode")) + {} + + flutter::EncodableMap WebResourceResponse::toEncodableMap() const + { + return flutter::EncodableMap{ + {"statusCode", make_fl_value(statusCode)} + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/web_resource_response.h b/flutter_inappwebview_windows/windows/types/web_resource_response.h new file mode 100644 index 00000000..c428e0ad --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/web_resource_response.h @@ -0,0 +1,22 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_RESOURCE_RESPONSE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_RESOURCE_RESPONSE_H_ + +#include +#include + +namespace flutter_inappwebview_plugin +{ + class WebResourceResponse + { + public: + const std::optional statusCode; + + WebResourceResponse(const std::optional& statusCode); + WebResourceResponse(const flutter::EncodableMap& map); + ~WebResourceResponse() = default; + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_WEB_RESOURCE_RESPONSE_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/utils/flutter.h b/flutter_inappwebview_windows/windows/utils/flutter.h new file mode 100644 index 00000000..45c1390c --- /dev/null +++ b/flutter_inappwebview_windows/windows/utils/flutter.h @@ -0,0 +1,197 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_FLUTTER_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_FLUTTER_H_ + +#include + +#include "map.h" +#include "util.h" +#include "vector.h" + +namespace flutter_inappwebview_plugin +{ + static inline flutter::EncodableValue make_fl_value() + { + return flutter::EncodableValue(); + } + + template + static inline flutter::EncodableValue make_fl_value(const T& val) + { + return flutter::EncodableValue(val); + } + + template + static inline flutter::EncodableValue make_fl_value(const T* val) + { + return val == nullptr ? make_fl_value() : flutter::EncodableValue(val); + } + + template + static inline flutter::EncodableValue make_fl_value(const std::vector& vec) + { + auto encodableList = flutter::EncodableList{}; + for (auto const& val : vec) { + encodableList.push_back(make_fl_value(val)); + } + return encodableList; + } + + template + static inline flutter::EncodableValue make_fl_value(const std::map& map) + { + auto encodableMap = flutter::EncodableMap{}; + for (auto const& [key, val] : map) { + encodableMap.insert({ make_fl_value(key), make_fl_value(val) }); + } + return encodableMap; + } + + template + static inline flutter::EncodableValue make_fl_value(const std::optional& optional) + { + return optional.has_value() ? make_fl_value(optional.value()) : make_fl_value(); + } + + template + static inline flutter::EncodableValue make_fl_value(const std::optional>& optional) + { + if (!optional.has_value()) { + return make_fl_value(); + } + auto& vecValue = optional.value(); + auto encodableList = flutter::EncodableList{}; + for (auto const& val : vecValue) { + encodableList.push_back(make_fl_value(val)); + } + return encodableList; + } + + template + static inline flutter::EncodableValue make_fl_value(const std::optional>& optional) + { + if (!optional.has_value()) { + return make_fl_value(); + } + auto& mapValue = optional.value(); + auto encodableMap = flutter::EncodableMap{}; + for (auto const& [key, val] : mapValue) { + encodableMap.insert({ make_fl_value(key), make_fl_value(val) }); + } + return encodableMap; + } + + static inline bool fl_map_contains(const flutter::EncodableMap& map, const char* key) + { + return map_contains(map, make_fl_value(key)); + } + + static inline bool fl_map_contains_not_null(const flutter::EncodableMap& map, const char* key) + { + return fl_map_contains(map, key) && !map.at(make_fl_value(key)).IsNull(); + } + + template::value && !std::is_same::value), bool>::type* = nullptr> + static inline T get_fl_map_value(const flutter::EncodableMap& map, const char* key) + { + return std::get(map.at(make_fl_value(key))); + } + + template::value, bool>::type* = nullptr> + static inline int64_t get_fl_map_value(const flutter::EncodableMap& map, const char* key) + { + return map.at(make_fl_value(key)).LongValue(); + } + + template::value, bool>::type* = nullptr> + static inline int64_t get_fl_map_value(const flutter::EncodableMap& map, const char* key) + { + return map.at(make_fl_value(key)).LongValue(); + } + + template::value && !is_vector::value && !std::is_same::value && !std::is_same::value) || + std::is_same::value || std::is_same::value), int>::type* = nullptr> + static inline std::optional get_optional_fl_map_value(const flutter::EncodableMap& map, const char* key) + { + if (fl_map_contains_not_null(map, key)) { + auto fl_key = make_fl_value(key); + return make_pointer_optional(std::get_if(&map.at(fl_key))); + } + return std::nullopt; + } + + template::value, bool>::type* = nullptr> + static inline std::optional get_optional_fl_map_value(const flutter::EncodableMap& map, const char* key) + { + if (fl_map_contains_not_null(map, key)) { + auto fl_key = make_fl_value(key); + return std::make_optional(map.at(fl_key).LongValue()); + } + return std::nullopt; + } + + template::value, bool>::type* = nullptr> + static inline std::optional get_optional_fl_map_value(const flutter::EncodableMap& map, const char* key) + { + if (fl_map_contains_not_null(map, key)) { + auto fl_key = make_fl_value(key); + return std::make_optional(map.at(fl_key).LongValue()); + } + return std::nullopt; + } + + template + static inline T get_fl_map_value(const flutter::EncodableMap& map, const char* key, const T& defaultValue) + { + auto optional = get_optional_fl_map_value(map, key); + return !optional.has_value() ? defaultValue : optional.value(); + } + + template::value && !std::is_same::value)>::type* = nullptr> + static inline std::optional get_optional_fl_map_value(const flutter::EncodableMap& map, const char* key) + { + using K = typename T::key_type; + using V = typename T::mapped_type; + + auto flMap = std::get_if(&map.at(make_fl_value(key))); + if (flMap) { + T mapValue = {}; + for (auto itr = flMap->begin(); itr != flMap->end(); itr++) { + mapValue.insert({ std::get(itr->first), std::get(itr->second) }); + } + return make_pointer_optional(&mapValue); + } + return std::nullopt; + } + + template + static inline std::map get_fl_map_value(const flutter::EncodableMap& map, const char* key, const std::map& defaultValue) + { + auto optional = get_optional_fl_map_value>(map, key); + return !optional.has_value() ? defaultValue : optional.value(); + } + + template::value && !std::is_same::value), bool>::type* = nullptr> + static inline std::optional get_optional_fl_map_value(const flutter::EncodableMap& map, const char* key) + { + using V = typename T::value_type; + + auto flList = std::get_if(&map.at(make_fl_value(key))); + if (flList) { + T vecValue(flList->size()); + for (auto itr = flList->begin(); itr != flList->end(); itr++) { + vecValue.push_back(std::get(*itr)); + } + return make_pointer_optional(&vecValue); + } + return std::nullopt; + } + + template + static inline std::vector get_fl_map_value(const flutter::EncodableMap& map, const char* key, const std::vector& defaultValue) + { + auto optional = get_optional_fl_map_value>(map, key); + return !optional.has_value() ? defaultValue : optional.value(); + } +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_FLUTTER_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/utils/log.h b/flutter_inappwebview_windows/windows/utils/log.h new file mode 100644 index 00000000..e61dcb61 --- /dev/null +++ b/flutter_inappwebview_windows/windows/utils/log.h @@ -0,0 +1,103 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_LOG_UTIL_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_LOG_UTIL_H_ + +#include +#include +#include +#include + +#include "strconv.h" +#include "string.h" + +namespace flutter_inappwebview_plugin +{ + template + static inline void debugLog(const std::basic_string& msg, const bool& isError = false, const std::string& filename = "", const int& line = 0) + { +#ifndef NDEBUG + std::basic_string debugMsg = msg; + if (!filename.empty() && line > 0) { + auto filenameSplit = split(filename, std::string{ "\\flutter_inappwebview_windows\\" }); + std::string reduceFilenamePath = filenameSplit.size() > 0 ? "flutter_inappwebview_windows\\" + filenameSplit.back() : filename; + debugMsg = reduceFilenamePath + "(" + std::to_string(line) + "): " + debugMsg; + } + if (isError) { + std::cerr << debugMsg << std::endl; + } + else { + std::cout << debugMsg << std::endl; + } + OutputDebugString(utf8_to_wide("\n" + debugMsg + "\n").c_str()); +#endif + } + + static inline void debugLog(const char* msg, const bool& isError = false, const std::string& filename = "", const int& line = 0) + { + debugLog(std::string(msg), isError, filename, line); + } + + static inline void debugLog(const std::wstring& msg, const bool& isError = false, const std::string& filename = "", const int& line = 0) + { + debugLog(wide_to_utf8(msg), isError, filename, line); + } + + static inline void debugLog(const bool& value, const bool& isError = false, const std::string& filename = "", const int& line = 0) + { + debugLog(value ? "true" : "false", isError, filename, line); + } + + template< + typename T, + typename = typename std::enable_if::value, T>::type + > + static inline void debugLog(const T& value, const bool& isError = false, const std::string& filename = "", const int& line = 0) + { + debugLog(std::to_string(value), isError); + } + + static inline std::string getHRMessage(const HRESULT& error) + { + return wide_to_utf8(_com_error(error).ErrorMessage()); + } + + static inline void debugLog(const HRESULT& hr, const std::string& filename = "", const int& line = 0) + { + auto isError = hr != S_OK; + 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) + { + if (SUCCEEDED(hr)) { + return true; + } + debugLog(hr, filename, line); + return false; + } + + static inline bool failedAndLog(const HRESULT& hr, const std::string& filename = "", const int& line = 0) + { + if (FAILED(hr)) { + debugLog(hr, filename, line); + return true; + } + return false; + } + + static inline void failedLog(const HRESULT& hr, const std::string& filename = "", const int& line = 0) + { + if (FAILED(hr)) { + debugLog(hr, filename, line); + } + } +} + +#ifndef NDEBUG +#define debugLog(value) debugLog(value, false, __FILE__, __LINE__) +#define succeededOrLog(value) succeededOrLog(value, __FILE__, __LINE__) +#define failedAndLog(value) failedAndLog(value, __FILE__, __LINE__) +#define failedLog(value) failedLog(value, __FILE__, __LINE__) +#endif + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_LOG_UTIL_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/utils/map.h b/flutter_inappwebview_windows/windows/utils/map.h new file mode 100644 index 00000000..c46d4a17 --- /dev/null +++ b/flutter_inappwebview_windows/windows/utils/map.h @@ -0,0 +1,37 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_MAP_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_MAP_H_ + +#include +#include +#include +#include + +namespace flutter_inappwebview_plugin +{ + template + struct is_mappish_impl : std::false_type { }; + + template + struct is_mappish_impl()[std::declval()])>> + : std::true_type { }; + + template + struct is_mappish : is_mappish_impl::type { }; + + template + static inline bool map_contains(const std::map& map, const K& key) + { + return map.find(key) != map.end(); + } + + template + static inline T map_at_or_null(const std::map& map, const K& key) + { + auto itr = map.find(key); + return itr != map.end() ? itr->second : nullptr; + } +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_MAP_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/utils/strconv.h b/flutter_inappwebview_windows/windows/utils/strconv.h new file mode 100644 index 00000000..0f1483ff --- /dev/null +++ b/flutter_inappwebview_windows/windows/utils/strconv.h @@ -0,0 +1,558 @@ +// from https://github.com/javacommons/strconv + +/* strconv.h v1.8.10 */ +/* Last Modified: 2021/08/30 21:53 */ + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2019-2021 JavaCommons +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +----------------------------------------------------------------------------- + */ + +#ifndef STRCONV_H +#define STRCONV_H + +#include +#include +#include +#include +#include + +namespace flutter_inappwebview_plugin +{ +#if __cplusplus >= 201103L && !defined(STRCONV_CPP98) + static inline std::wstring cp_to_wide(const std::string& s, UINT codepage) + { + int in_length = (int)s.length(); + int out_length = MultiByteToWideChar(codepage, 0, s.c_str(), in_length, 0, 0); + std::wstring result(out_length, L'\0'); + if (out_length) + MultiByteToWideChar(codepage, 0, s.c_str(), in_length, &result[0], out_length); + return result; + } + static inline std::string wide_to_cp(const std::wstring& s, UINT codepage) + { + int in_length = (int)s.length(); + int out_length = WideCharToMultiByte(codepage, 0, s.c_str(), in_length, 0, 0, 0, 0); + std::string result(out_length, '\0'); + if (out_length) + WideCharToMultiByte(codepage, 0, s.c_str(), in_length, &result[0], out_length, 0, 0); + return result; + } +#else /* __cplusplus < 201103L */ + static inline std::wstring cp_to_wide(const std::string& s, UINT codepage) + { + int in_length = (int)s.length(); + int out_length = MultiByteToWideChar(codepage, 0, s.c_str(), in_length, 0, 0); + std::vector buffer(out_length); + if (out_length) + MultiByteToWideChar(codepage, 0, s.c_str(), in_length, &buffer[0], out_length); + std::wstring result(buffer.begin(), buffer.end()); + return result; + } + static inline std::string wide_to_cp(const std::wstring& s, UINT codepage) + { + int in_length = (int)s.length(); + int out_length = WideCharToMultiByte(codepage, 0, s.c_str(), in_length, 0, 0, 0, 0); + std::vector buffer(out_length); + if (out_length) + WideCharToMultiByte(codepage, 0, s.c_str(), in_length, &buffer[0], out_length, 0, 0); + std::string result(buffer.begin(), buffer.end()); + return result; + } +#endif + + static inline std::string cp_to_utf8(const std::string& s, UINT codepage) + { + if (codepage == CP_UTF8) + return s; + std::wstring wide = cp_to_wide(s, codepage); + return wide_to_cp(wide, CP_UTF8); + } + static inline std::string utf8_to_cp(const std::string& s, UINT codepage) + { + if (codepage == CP_UTF8) + return s; + std::wstring wide = cp_to_wide(s, CP_UTF8); + return wide_to_cp(wide, codepage); + } + + static inline std::wstring ansi_to_wide(const std::string& s) + { + return cp_to_wide(s, CP_ACP); + } + static inline std::string wide_to_ansi(const std::wstring& s) + { + return wide_to_cp(s, CP_ACP); + } + + static inline std::wstring sjis_to_wide(const std::string& s) + { + return cp_to_wide(s, 932); + } + static inline std::string wide_to_sjis(const std::wstring& s) + { + return wide_to_cp(s, 932); + } + + static inline std::wstring utf8_to_wide(const std::string& s) + { + return cp_to_wide(s, CP_UTF8); + } + static inline std::string wide_to_utf8(const std::wstring& s) + { + return wide_to_cp(s, CP_UTF8); + } + + static inline std::string ansi_to_utf8(const std::string& s) + { + return cp_to_utf8(s, CP_ACP); + } + static inline std::string utf8_to_ansi(const std::string& s) + { + return utf8_to_cp(s, CP_ACP); + } + + static inline std::string sjis_to_utf8(const std::string& s) + { + return cp_to_utf8(s, 932); + } + static inline std::string utf8_to_sjis(const std::string& s) + { + return utf8_to_cp(s, 932); + } + +#ifdef __cpp_char8_t + static inline std::u8string utf8_to_char8(const std::string& s) + { + return std::u8string(s.begin(), s.end()); + } + static inline std::string char8_to_utf8(const std::u8string& s) + { + return std::string(s.begin(), s.end()); + } + + static inline std::wstring char8_to_wide(const std::u8string& s) + { + return cp_to_wide(char8_to_utf8(s), CP_UTF8); + } + static inline std::u8string wide_to_char8(const std::wstring& s) + { + return utf8_to_char8(wide_to_cp(s, CP_UTF8)); + } + + static inline std::u8string cp_to_char8(const std::string& s, UINT codepage) + { + return utf8_to_char8(cp_to_utf8(s, codepage)); + } + static inline std::string char8_to_cp(const std::u8string& s, UINT codepage) + { + return utf8_to_cp(char8_to_utf8(s), codepage); + } + + static inline std::u8string ansi_to_char8(const std::string& s) + { + return cp_to_char8(s, CP_ACP); + } + static inline std::string char8_to_ansi(const std::u8string& s) + { + return char8_to_cp(s, CP_ACP); + } + + static inline std::u8string sjis_to_char8(const std::string& s) + { + return cp_to_char8(s, 932); + } + static inline std::string char8_to_sjis(const std::u8string& s) + { + return char8_to_cp(s, 932); + } +#endif + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable : 4996) +#endif + + static inline std::wstring vformat(const wchar_t* format, va_list args) + { + int len = _vsnwprintf(0, 0, format, args); + if (len < 0) + return L""; + std::vector buffer(len + 1); + len = _vsnwprintf(&buffer[0], len, format, args); + if (len < 0) + return L""; + buffer[len] = L'\0'; + return &buffer[0]; + } + static inline std::string vformat(const char* format, va_list args) + { + int len = _vsnprintf(0, 0, format, args); + if (len < 0) + return ""; + std::vector buffer(len + 1); + len = _vsnprintf(&buffer[0], len, format, args); + if (len < 0) + return ""; + buffer[len] = '\0'; + return &buffer[0]; + } +#ifdef __cpp_char8_t + static inline std::u8string vformat(const char8_t* format, va_list args) + { + int len = _vsnprintf(0, 0, (const char*)format, args); + if (len < 0) + return u8""; + std::vector buffer(len + 1); + len = _vsnprintf(&buffer[0], len, (const char*)format, args); + if (len < 0) + return u8""; + buffer[len] = '\0'; + return (char8_t*)&buffer[0]; + } +#endif + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + + static inline std::wstring format(const wchar_t* format, ...) + { + va_list args; + va_start(args, format); + std::wstring s = vformat(format, args); + va_end(args); + return s; + } + static inline std::string format(const char* format, ...) + { + va_list args; + va_start(args, format); + std::string s = vformat(format, args); + va_end(args); + return s; + } +#ifdef __cpp_char8_t + static inline std::u8string format(const char8_t* format, ...) + { + va_list args; + va_start(args, format); + std::u8string s = vformat(format, args); + va_end(args); + return s; + } +#endif + + static inline void format(std::ostream& ostrm, const wchar_t* format, ...) + { + va_list args; + va_start(args, format); + std::wstring s = vformat(format, args); + va_end(args); + ostrm << wide_to_utf8(s) << std::flush; + } + static inline void format(std::ostream& ostrm, const char* format, ...) + { + va_list args; + va_start(args, format); + std::string s = vformat(format, args); + va_end(args); + ostrm << s << std::flush; + } +#ifdef __cpp_char8_t + static inline void format(std::ostream& ostrm, const char8_t* format, ...) + { + va_list args; + va_start(args, format); + std::u8string s = vformat(format, args); + va_end(args); + ostrm << char8_to_utf8(s) << std::flush; + } +#endif + + static inline std::string formatA(const wchar_t* format, ...) + { + va_list args; + va_start(args, format); + std::wstring s = vformat(format, args); + va_end(args); + return wide_to_ansi(s); + } + static inline std::string formatA(const char* format, ...) + { + va_list args; + va_start(args, format); + std::string s = vformat(format, args); + va_end(args); + return utf8_to_ansi(s); + } +#ifdef __cpp_char8_t + static inline std::string formatA(const char8_t* format, ...) + { + va_list args; + va_start(args, format); + std::u8string s = vformat(format, args); + va_end(args); + return char8_to_ansi(s); + } +#endif + + static inline void formatA(std::ostream& ostrm, const wchar_t* format, ...) + { + va_list args; + va_start(args, format); + std::wstring s = vformat(format, args); + va_end(args); + ostrm << wide_to_ansi(s) << std::flush; + } + static inline void formatA(std::ostream& ostrm, const char* format, ...) + { + va_list args; + va_start(args, format); + std::string s = vformat(format, args); + va_end(args); + ostrm << utf8_to_ansi(s) << std::flush; + } +#ifdef __cpp_char8_t + static inline void formatA(std::ostream& ostrm, const char8_t* format, ...) + { + va_list args; + va_start(args, format); + std::u8string s = vformat(format, args); + va_end(args); + ostrm << char8_to_ansi(s) << std::flush; + } +#endif + + static inline void dbgmsg(const wchar_t* title, const wchar_t* format, ...) + { + va_list args; + va_start(args, format); + std::wstring s = vformat(format, args); + va_end(args); + MessageBoxW(0, s.c_str(), title, MB_OK); + } + static inline void dbgmsg(const char* title, const char* format, ...) + { + va_list args; + va_start(args, format); + std::string s = vformat(format, args); + va_end(args); + MessageBoxW(0, utf8_to_wide(s).c_str(), utf8_to_wide(title).c_str(), MB_OK); + } +#ifdef __cpp_char8_t + static inline void dbgmsg(const char8_t* title, const char8_t* format, ...) + { + va_list args; + va_start(args, format); + std::u8string s = vformat(format, args); + va_end(args); + MessageBoxW(0, char8_to_wide(s).c_str(), char8_to_wide(title).c_str(), MB_OK); + } +#endif + + static inline HANDLE handle_for_ostream(std::ostream& ostrm) + { + if (&ostrm == &std::cout) { + return GetStdHandle(STD_OUTPUT_HANDLE); + } + else if (&ostrm == &std::cerr) { + return GetStdHandle(STD_ERROR_HANDLE); + } + return INVALID_HANDLE_VALUE; + } + static inline void dbgout(std::ostream& ostrm, const wchar_t* format, ...) + { + va_list args; + va_start(args, format); + std::wstring ws = vformat(format, args); + va_end(args); + HANDLE h = handle_for_ostream(ostrm); + if (h == INVALID_HANDLE_VALUE) { + return; + } + DWORD dwNumberOfCharsWrite; + if (GetFileType(h) != FILE_TYPE_CHAR) { + std::string s = wide_to_cp(ws, GetConsoleOutputCP()); + WriteFile(h, s.c_str(), (DWORD)s.size(), &dwNumberOfCharsWrite, NULL); + } + else { + WriteConsoleW(h, + ws.c_str(), + (DWORD)ws.size(), + &dwNumberOfCharsWrite, + NULL); + } + } + static inline void dbgout(std::ostream& ostrm, const char* format, ...) + { + va_list args; + va_start(args, format); + std::string s = vformat(format, args); + va_end(args); + HANDLE h = handle_for_ostream(ostrm); + if (h == INVALID_HANDLE_VALUE) { + return; + } + DWORD dwNumberOfCharsWrite; + if (GetFileType(h) != FILE_TYPE_CHAR) { + s = utf8_to_cp(s, GetConsoleOutputCP()); + WriteFile(h, s.c_str(), (DWORD)s.size(), &dwNumberOfCharsWrite, NULL); + } + else { + std::wstring ws = utf8_to_wide(s); + WriteConsoleW(h, + ws.c_str(), + (DWORD)ws.size(), + &dwNumberOfCharsWrite, + NULL); + } + } +#ifdef __cpp_char8_t + static inline void dbgout(std::ostream& ostrm, const char8_t* format, ...) + { + va_list args; + va_start(args, format); + std::u8string s = vformat(format, args); + va_end(args); + HANDLE h = handle_for_ostream(ostrm); + if (h == INVALID_HANDLE_VALUE) { + return; + } + DWORD dwNumberOfCharsWrite; + if (GetFileType(h) != FILE_TYPE_CHAR) { + std::string str = char8_to_cp(s, GetConsoleOutputCP()); + WriteFile(h, (const char*)str.c_str(), (DWORD)str.size(), &dwNumberOfCharsWrite, NULL); + } + else { + std::wstring ws = char8_to_wide(s); + WriteConsoleW(h, + ws.c_str(), + (DWORD)ws.size(), + &dwNumberOfCharsWrite, + NULL); + } + } +#endif + + class unicode_ostream + { + private: + std::ostream* m_ostrm; + UINT m_target_cp; + bool is_ascii(const std::string& s) + { + for (std::size_t i = 0; i < s.size(); i++) { + unsigned char c = (unsigned char)s[i]; + if (c > 0x7f) + return false; + } + return true; + } + + public: + unicode_ostream(std::ostream& ostrm, UINT target_cp = CP_ACP) : m_ostrm(&ostrm), m_target_cp(target_cp) {} + std::ostream& stream() { return *m_ostrm; } + void stream(std::ostream& ostrm) { m_ostrm = &ostrm; } + UINT target_cp() { return m_target_cp; } + void target_cp(UINT cp) { m_target_cp = cp; } + template + unicode_ostream& operator<<(const T& x) + { + std::ostringstream oss; + oss << x; + std::string output = oss.str(); + if (is_ascii(output)) { + (*m_ostrm) << x; + } + else { + (*m_ostrm) << utf8_to_cp(output, m_target_cp); + } + return *this; + } + unicode_ostream& operator<<(const std::wstring& x) + { + (*m_ostrm) << wide_to_cp(x, m_target_cp); + return *this; + } + unicode_ostream& operator<<(const wchar_t* x) + { + (*m_ostrm) << wide_to_cp(x, m_target_cp); + return *this; + } + unicode_ostream& operator<<(const std::string& x) + { + (*m_ostrm) << utf8_to_cp(x, m_target_cp); + return *this; + } + unicode_ostream& operator<<(const char* x) + { + (*m_ostrm) << utf8_to_cp(x, m_target_cp); + return *this; + } +#ifdef __cpp_char8_t + unicode_ostream& operator<<(const std::u8string& x) + { + (*m_ostrm) << char8_to_cp(x, m_target_cp); + return *this; + } + unicode_ostream& operator<<(const char8_t* x) + { + (*m_ostrm) << char8_to_cp(x, m_target_cp); + return *this; + } +#endif + unicode_ostream& operator<<(std::ostream& (*pf)(std::ostream&)) // For manipulators... + { + (*m_ostrm) << pf; + return *this; + } + unicode_ostream& operator<<(std::basic_ios& (*pf)(std::basic_ios&)) // For manipulators... + { + (*m_ostrm) << pf; + return *this; + } + }; +} + +#define U8(X) ((const char *)u8##X) +#define WIDE(X) (L##X) + +#endif /* STRCONV_H */ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/utils/string.h b/flutter_inappwebview_windows/windows/utils/string.h new file mode 100644 index 00000000..ee912db8 --- /dev/null +++ b/flutter_inappwebview_windows/windows/utils/string.h @@ -0,0 +1,181 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_STRING_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_STRING_H_ + +#include +#include +#include +#include +#include + +#include "strconv.h" + +namespace flutter_inappwebview_plugin +{ + template + struct is_string + : std::false_type + {}; + + // Partial specialization - parameters used to qualify the specialization + template + struct is_string> + : std::true_type + {}; + + template + using is_basic_string = is_string>; + + template + static inline bool string_equals(const std::basic_string& s1, const std::basic_string& s2) + { + return s1.compare(s2) == 0; + } + + template + static inline bool string_equals(const std::basic_string& s1, const char* s2) + { + return s1.compare(s2) == 0; + } + + template + static inline bool string_equals(const char* s1, const std::basic_string& s2) + { + return s2.compare(s1) == 0; + } + + static inline bool string_equals(const std::string& s1, const std::wstring& s2) + { + return string_equals(s1, wide_to_utf8(s2)); + } + + static inline bool string_equals(const std::wstring& s1, const std::string& s2) + { + return string_equals(wide_to_utf8(s1), s2); + } + + template + static inline bool string_equals(const std::optional>& s1, const std::basic_string& s2) + { + return s1.has_value() ? string_equals(s1.value(), s2) : false; + } + + template + static inline bool string_equals(const std::basic_string& s1, const std::optional>& s2) + { + return s2.has_value() ? string_equals(s1, s2.value()) : false; + } + + template + static inline bool string_equals(const std::optional>& s1, const std::optional>& s2) + { + return s1.has_value() && s2.has_value() ? string_equals(s1.value(), s2.value()) : true; + } + + static inline void replace_all(std::string& source, const std::string& from, const std::string& to) + { + std::string newString; + newString.reserve(source.length()); // avoids a few memory allocations + + std::string::size_type lastPos = 0; + std::string::size_type findPos; + + while (std::string::npos != (findPos = source.find(from, lastPos))) { + newString.append(source, lastPos, findPos - lastPos); + newString += to; + lastPos = findPos + from.length(); + } + + // Care for the rest after last occurrence + newString += source.substr(lastPos); + + source.swap(newString); + } + + static inline std::string replace_all_copy(const std::string& source, const std::string& from, const std::string& to) + { + std::string newString; + newString.reserve(source.length()); // avoids a few memory allocations + + std::string::size_type lastPos = 0; + std::string::size_type findPos; + + while (std::string::npos != (findPos = source.find(from, lastPos))) { + newString.append(source, lastPos, findPos - lastPos); + newString += to; + lastPos = findPos + from.length(); + } + + // Care for the rest after last occurrence + newString += source.substr(lastPos); + + return newString; + } + + template + static inline std::basic_string join(const std::vector>& vec, const std::basic_string& delim) + { + return vec.empty() ? std::basic_string{ "" } : /* leave early if there are no items in the list */ + std::accumulate( /* otherwise, accumulate */ + ++vec.begin(), vec.end(), /* the range 2nd to after-last */ + *vec.begin(), /* and start accumulating with the first item */ + [delim](auto& a, auto& b) { return a + delim + b; }); + } + + template + static inline std::basic_string join(const std::vector>& vec, const char* delim) + { + return join(vec, std::basic_string{ delim }); + } + + template + static inline std::vector> split(const std::basic_string& s, std::basic_string delimiter) + { + size_t pos_start = 0, pos_end, delim_len = delimiter.length(); + std::basic_string token; + std::vector> res; + + while ((pos_end = s.find(delimiter, pos_start)) != std::basic_string::npos) { + token = s.substr(pos_start, pos_end - pos_start); + pos_start = pos_end + delim_len; + res.push_back(token); + } + + res.push_back(s.substr(pos_start)); + return res; + } + + template + void to_lowercase(const std::basic_string& s) + { + std::transform(s.begin(), s.end(), s.begin(), + [](const T v) { return static_cast(std::tolower(v)); }); + } + + template + std::basic_string to_lowercase_copy(const std::basic_string& s) + { + std::basic_string s2 = s; + std::transform(s2.begin(), s2.end(), s2.begin(), + [](const T v) { return static_cast(std::tolower(v)); }); + return s2; + } + + template + void to_uppercase(const std::basic_string& s) + { + std::transform(s.begin(), s.end(), s.begin(), + [](const T v) { return static_cast(std::toupper(v)); }); + return s2; + } + + template + std::basic_string to_uppercase_copy(const std::basic_string& s) + { + std::basic_string s2 = s; + std::transform(s2.begin(), s2.end(), s2.begin(), + [](const T v) { return static_cast(std::toupper(v)); }); + return s2; + } +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_STRING_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 new file mode 100644 index 00000000..bbac292d --- /dev/null +++ b/flutter_inappwebview_windows/windows/utils/util.h @@ -0,0 +1,38 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_H_ + +#include +#include +#include +#include + +namespace flutter_inappwebview_plugin +{ + template + static inline std::optional make_pointer_optional(const T* value) + { + return value == nullptr ? std::nullopt : std::make_optional(*value); + } + + static inline std::string variant_to_string(const std::variant& var) + { + return std::visit([](auto&& arg) + { + using T = std::decay_t; + if constexpr (std::is_same_v) + return arg; + else if constexpr (std::is_arithmetic_v) + return std::to_string(arg); + else + static_assert(always_false_v, "non-exhaustive visitor!"); + }, var); + } + + static inline float get_current_scale_factor(HWND hwnd) + { + auto dpi = GetDpiForWindow(hwnd); + return dpi > 0 ? dpi / 96.0f : 1.0f; + } +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/utils/vector.h b/flutter_inappwebview_windows/windows/utils/vector.h new file mode 100644 index 00000000..4a70e8a4 --- /dev/null +++ b/flutter_inappwebview_windows/windows/utils/vector.h @@ -0,0 +1,99 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_VECTOR_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_VECTOR_H_ + +#include +#include +#include +#include +#include +#include + +namespace flutter_inappwebview_plugin +{ + template + struct is_vector_impl : std::false_type { }; + + template + struct is_vector_impl>::value> + > : std::true_type { }; + + template + struct is_vector_impl::value_type>::iterator>::value> + > : std::true_type { }; + + template + struct is_vector : is_vector_impl::type { }; + + template + static inline void vector_remove(std::vector& vec, const T& el) + { + std::remove(vec.begin(), vec.end(), el); + } + + template + static inline void vector_remove_if(std::vector& vec, UnaryPredicate&& predicate) + { + std::remove_if(vec.begin(), vec.end(), std::forward(predicate)); + } + + template + static inline void vector_remove_erase(std::vector& vec, const T& el) + { + vec.erase(std::remove(vec.begin(), vec.end(), el), vec.end()); + } + + template + static inline void vector_remove_erase_if(std::vector& vec, UnaryPredicate&& predicate) + { + vec.erase(std::remove_if(vec.begin(), vec.end(), std::forward(predicate)), vec.end()); + } + + template + static inline bool vector_contains(const std::vector& vec, const T& value) + { + return std::find(vec.begin(), vec.end(), value) != vec.end(); + } + + template + static inline bool vector_contains_if(const std::vector& vec, UnaryPredicate&& predicate) + { + return std::find_if(vec.begin(), vec.end(), std::forward(predicate)) != vec.end(); + } + + 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_VECTOR_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/webview_environment/webview_environment.cpp b/flutter_inappwebview_windows/windows/webview_environment/webview_environment.cpp new file mode 100644 index 00000000..e8ae3933 --- /dev/null +++ b/flutter_inappwebview_windows/windows/webview_environment/webview_environment.cpp @@ -0,0 +1,94 @@ +#include +#include + +#include "../utils/log.h" +#include "webview_environment.h" + +#include "webview_environment_manager.h" + +namespace flutter_inappwebview_plugin +{ + using namespace Microsoft::WRL; + + WebViewEnvironment::WebViewEnvironment(const FlutterInappwebviewWindowsPlugin* plugin, const std::string& id) + : plugin(plugin), id(id), + channelDelegate(std::make_unique(this, plugin->registrar->messenger())) + {} + + void WebViewEnvironment::create(const std::unique_ptr settings, const std::function completionHandler) + { + if (!plugin) { + if (completionHandler) { + completionHandler(E_FAIL); + } + return; + } + + auto hwnd = plugin->webViewEnvironmentManager->getHWND(); + if (!hwnd) { + if (completionHandler) { + completionHandler(E_FAIL); + } + return; + } + + auto options = Make(); + if (settings) { + if (settings->additionalBrowserArguments.has_value()) { + options->put_AdditionalBrowserArguments(utf8_to_wide(settings->additionalBrowserArguments.value()).c_str()); + } + if (settings->allowSingleSignOnUsingOSPrimaryAccount.has_value()) { + options->put_AllowSingleSignOnUsingOSPrimaryAccount(settings->allowSingleSignOnUsingOSPrimaryAccount.value()); + } + if (settings->language.has_value()) { + options->put_Language(utf8_to_wide(settings->language.value()).c_str()); + } + if (settings->targetCompatibleBrowserVersion.has_value()) { + options->put_TargetCompatibleBrowserVersion(utf8_to_wide(settings->targetCompatibleBrowserVersion.value()).c_str()); + } + } + + auto hr = CreateCoreWebView2EnvironmentWithOptions( + settings && settings->browserExecutableFolder.has_value() ? utf8_to_wide(settings->browserExecutableFolder.value()).c_str() : nullptr, + settings && settings->userDataFolder.has_value() ? utf8_to_wide(settings->userDataFolder.value()).c_str() : nullptr, + options.Get(), + Callback( + [this, hwnd, completionHandler](HRESULT result, wil::com_ptr environment) -> HRESULT + { + if (succeededOrLog(result)) { + environment_ = std::move(environment); + + auto hr = environment_->CreateCoreWebView2Controller(hwnd, Callback( + [this, completionHandler](HRESULT result, wil::com_ptr controller) -> HRESULT + { + if (succeededOrLog(result)) { + webViewController_ = std::move(controller); + webViewController_->get_CoreWebView2(&webView_); + webViewController_->put_IsVisible(false); + } + if (completionHandler) { + completionHandler(result); + } + return S_OK; + }).Get()); + + if (failedAndLog(hr) && completionHandler) { + completionHandler(hr); + } + } + else if (completionHandler) { + completionHandler(result); + } + return S_OK; + }).Get()); + + if (failedAndLog(hr) && completionHandler) { + completionHandler(hr); + } + } + + WebViewEnvironment::~WebViewEnvironment() + { + debugLog("dealloc WebViewEnvironment"); + } +} diff --git a/flutter_inappwebview_windows/windows/webview_environment/webview_environment.h b/flutter_inappwebview_windows/windows/webview_environment/webview_environment.h new file mode 100644 index 00000000..615442d4 --- /dev/null +++ b/flutter_inappwebview_windows/windows/webview_environment/webview_environment.h @@ -0,0 +1,48 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_WEBVIEW_ENVIRONMENT_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_WEBVIEW_ENVIRONMENT_H_ + +#include +#include +#include + +#include "../flutter_inappwebview_windows_plugin.h" +#include "webview_environment_channel_delegate.h" +#include "webview_environment_settings.h" + +namespace flutter_inappwebview_plugin +{ + class WebViewEnvironment + { + public: + static inline const wchar_t* CLASS_NAME = L"WebViewEnvironment"; + static inline const std::string METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_webview_environment_"; + + const FlutterInappwebviewWindowsPlugin* plugin; + std::string id; + + std::unique_ptr channelDelegate; + + WebViewEnvironment(const FlutterInappwebviewWindowsPlugin* plugin, const std::string& id); + ~WebViewEnvironment(); + + void create(const std::unique_ptr settings, const std::function completionHandler); + wil::com_ptr getEnvironment() + { + return environment_; + } + wil::com_ptr getWebViewController() + { + return webViewController_; + } + wil::com_ptr getWebView() + { + return webView_; + } + private: + wil::com_ptr environment_; + wil::com_ptr webViewController_; + wil::com_ptr webView_; + WNDCLASS windowClass_ = {}; + }; +} +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_WEBVIEW_ENVIRONMENT_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/webview_environment/webview_environment_channel_delegate.cpp b/flutter_inappwebview_windows/windows/webview_environment/webview_environment_channel_delegate.cpp new file mode 100644 index 00000000..d5b9e4d2 --- /dev/null +++ b/flutter_inappwebview_windows/windows/webview_environment/webview_environment_channel_delegate.cpp @@ -0,0 +1,47 @@ +#include "../utils/flutter.h" +#include "../utils/log.h" +#include "../utils/strconv.h" +#include "../utils/string.h" +#include "webview_environment.h" +#include "webview_environment_channel_delegate.h" + +#include "webview_environment_manager.h" + +namespace flutter_inappwebview_plugin +{ + WebViewEnvironmentChannelDelegate::WebViewEnvironmentChannelDelegate(WebViewEnvironment* webViewEnv, flutter::BinaryMessenger* messenger) + : webViewEnvironment(webViewEnv), ChannelDelegate(messenger, WebViewEnvironment::METHOD_CHANNEL_NAME_PREFIX + webViewEnv->id) + {} + + void WebViewEnvironmentChannelDelegate::HandleMethodCall(const flutter::MethodCall& method_call, + std::unique_ptr> result) + { + if (!webViewEnvironment) { + result->Success(); + return; + } + + // auto& arguments = std::get(*method_call.arguments()); + auto& methodName = method_call.method_name(); + + if (string_equals(methodName, "dispose")) { + if (webViewEnvironment->plugin && webViewEnvironment->plugin->webViewEnvironmentManager) { + std::map>& webViewEnvironments = webViewEnvironment->plugin->webViewEnvironmentManager->webViewEnvironments; + auto& id = webViewEnvironment->id; + if (map_contains(webViewEnvironments, id)) { + webViewEnvironments.erase(id); + } + } + result->Success(); + } + else { + result->NotImplemented(); + } + } + + WebViewEnvironmentChannelDelegate::~WebViewEnvironmentChannelDelegate() + { + debugLog("dealloc WebViewEnvironmentChannelDelegate"); + webViewEnvironment = nullptr; + } +} diff --git a/flutter_inappwebview_windows/windows/webview_environment/webview_environment_channel_delegate.h b/flutter_inappwebview_windows/windows/webview_environment/webview_environment_channel_delegate.h new file mode 100644 index 00000000..b98e67ab --- /dev/null +++ b/flutter_inappwebview_windows/windows/webview_environment/webview_environment_channel_delegate.h @@ -0,0 +1,25 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_WEBVIEW_ENVIRONMENT_CHANNEL_DELEGATE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_WEBVIEW_ENVIRONMENT_CHANNEL_DELEGATE_H_ + +#include "../types/channel_delegate.h" +#include + +namespace flutter_inappwebview_plugin +{ + class WebViewEnvironment; + + class WebViewEnvironmentChannelDelegate : public ChannelDelegate + { + public: + WebViewEnvironment* webViewEnvironment; + + WebViewEnvironmentChannelDelegate(WebViewEnvironment* webViewEnv, flutter::BinaryMessenger* messenger); + ~WebViewEnvironmentChannelDelegate(); + + void HandleMethodCall( + const flutter::MethodCall& method_call, + std::unique_ptr> result); + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_WEBVIEW_ENVIRONMENT_CHANNEL_DELEGATE_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/webview_environment/webview_environment_manager.cpp b/flutter_inappwebview_windows/windows/webview_environment/webview_environment_manager.cpp new file mode 100644 index 00000000..bae69c1a --- /dev/null +++ b/flutter_inappwebview_windows/windows/webview_environment/webview_environment_manager.cpp @@ -0,0 +1,129 @@ +#include +#include +#include + +#include "../in_app_webview/in_app_webview_settings.h" +#include "../utils/flutter.h" +#include "../utils/log.h" +#include "../utils/string.h" +#include "../utils/vector.h" +#include "webview_environment_manager.h" + +namespace flutter_inappwebview_plugin +{ + WebViewEnvironmentManager::WebViewEnvironmentManager(const FlutterInappwebviewWindowsPlugin* plugin) + : plugin(plugin), + ChannelDelegate(plugin->registrar->messenger(), WebViewEnvironmentManager::METHOD_CHANNEL_NAME) + { + windowClass_.lpszClassName = WebViewEnvironmentManager::CLASS_NAME; + windowClass_.lpfnWndProc = &DefWindowProc; + + RegisterClass(&windowClass_); + + hwnd_ = CreateWindowEx(0, windowClass_.lpszClassName, L"", 0, 0, + 0, 0, 0, + plugin->registrar->GetView()->GetNativeWindow(), + nullptr, + windowClass_.hInstance, nullptr); + } + + void WebViewEnvironmentManager::HandleMethodCall(const flutter::MethodCall& method_call, + std::unique_ptr> result) + { + auto* arguments = std::get_if(method_call.arguments()); + auto& methodName = method_call.method_name(); + + if (string_equals(methodName, "create")) { + auto id = get_fl_map_value(*arguments, "id"); + auto settingsMap = get_optional_fl_map_value(*arguments, "settings"); + auto settings = settingsMap.has_value() ? std::make_unique(settingsMap.value()) : nullptr; + createWebViewEnvironment(id, std::move(settings), std::move(result)); + } + else if (string_equals(methodName, "getAvailableVersion")) { + auto browserExecutableFolder = get_optional_fl_map_value(*arguments, "browserExecutableFolder"); + result->Success(make_fl_value(getAvailableVersion(browserExecutableFolder))); + } + else if (string_equals(methodName, "compareBrowserVersions")) { + auto version1 = get_fl_map_value(*arguments, "version1"); + auto version2 = get_fl_map_value(*arguments, "version2"); + result->Success(make_fl_value(compareBrowserVersions(version1, version2))); + } + else { + result->NotImplemented(); + } + } + + void WebViewEnvironmentManager::createWebViewEnvironment(const std::string& id, std::unique_ptr settings, std::unique_ptr> result) + { + auto result_ = std::shared_ptr>(std::move(result)); + + auto webViewEnvironment = std::make_unique(plugin, id); + webViewEnvironment->create(std::move(settings), + [this, id, result_](HRESULT errorCode) + { + if (succeededOrLog(errorCode)) { + result_->Success(true); + } + else { + result_->Error("0", "Cannot create WebViewEnvironment: " + getHRMessage(errorCode)); + if (map_contains(webViewEnvironments, id)) { + webViewEnvironments.erase(id); + } + } + }); + webViewEnvironments.insert({ id, std::move(webViewEnvironment) }); + } + + void WebViewEnvironmentManager::createOrGetDefaultWebViewEnvironment(const std::function completionHandler) + { + if (defaultEnvironment_) { + if (completionHandler) { + completionHandler(defaultEnvironment_.get()); + } + return; + } + + defaultEnvironment_ = std::make_unique(plugin, "-1"); + defaultEnvironment_->create(nullptr, [this, completionHandler](HRESULT errorCode) + { + if (succeededOrLog(errorCode)) { + if (completionHandler) { + completionHandler(defaultEnvironment_.get()); + } + } + else if (completionHandler) { + defaultEnvironment_ = nullptr; + completionHandler(nullptr); + } + }); + } + + std::optional WebViewEnvironmentManager::getAvailableVersion(std::optional browserExecutableFolder) + { + wil::unique_cotaskmem_string versionInfo; + if (succeededOrLog(GetAvailableCoreWebView2BrowserVersionString(browserExecutableFolder.has_value() ? utf8_to_wide(browserExecutableFolder.value()).c_str() : nullptr, &versionInfo))) { + return wide_to_utf8(versionInfo.get()); + } + return std::nullopt; + } + + std::optional WebViewEnvironmentManager::compareBrowserVersions(std::string version1, std::string version2) + { + int result = 0; + if (succeededOrLog(CompareBrowserVersions(utf8_to_wide(version1).c_str(), utf8_to_wide(version2).c_str(), &result))) { + return result; + } + return std::nullopt; + } + + WebViewEnvironmentManager::~WebViewEnvironmentManager() + { + debugLog("dealloc WebViewEnvironmentManager"); + webViewEnvironments.clear(); + plugin = nullptr; + defaultEnvironment_ = nullptr; + if (hwnd_) { + DestroyWindow(hwnd_); + } + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/webview_environment/webview_environment_manager.h b/flutter_inappwebview_windows/windows/webview_environment/webview_environment_manager.h new file mode 100644 index 00000000..6bfcafcb --- /dev/null +++ b/flutter_inappwebview_windows/windows/webview_environment/webview_environment_manager.h @@ -0,0 +1,46 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_WEBVIEW_ENVIRONMENT_MANAGER_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_WEBVIEW_ENVIRONMENT_MANAGER_H_ + +#include +#include +#include +#include + +#include "../flutter_inappwebview_windows_plugin.h" +#include "../types/channel_delegate.h" +#include "webview_environment.h" + +namespace flutter_inappwebview_plugin +{ + class WebViewEnvironmentManager : public ChannelDelegate + { + public: + static inline const wchar_t* CLASS_NAME = L"WebViewEnvironmentManager"; + static inline const std::string METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_webview_environment"; + + const FlutterInappwebviewWindowsPlugin* plugin; + std::map> webViewEnvironments; + + WebViewEnvironmentManager(const FlutterInappwebviewWindowsPlugin* plugin); + ~WebViewEnvironmentManager(); + + void HandleMethodCall( + const flutter::MethodCall& method_call, + std::unique_ptr> result); + + void createWebViewEnvironment(const std::string& id, std::unique_ptr settings, std::unique_ptr> result); + void createOrGetDefaultWebViewEnvironment(const std::function completionHandler); + HWND getHWND() + { + return hwnd_; + } + + static std::optional getAvailableVersion(std::optional browserExecutableFolder); + static std::optional compareBrowserVersions(std::string version1, std::string version2); + private: + std::unique_ptr defaultEnvironment_; + WNDCLASS windowClass_ = {}; + HWND hwnd_ = nullptr; + }; +} +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_WEBVIEW_ENVIRONMENT_MANAGER_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/webview_environment/webview_environment_settings.cpp b/flutter_inappwebview_windows/windows/webview_environment/webview_environment_settings.cpp new file mode 100644 index 00000000..24091a7d --- /dev/null +++ b/flutter_inappwebview_windows/windows/webview_environment/webview_environment_settings.cpp @@ -0,0 +1,27 @@ +#include "../utils/flutter.h" +#include "webview_environment_settings.h" + +namespace flutter_inappwebview_plugin +{ + WebViewEnvironmentSettings::WebViewEnvironmentSettings(const flutter::EncodableMap& map) + : browserExecutableFolder(get_optional_fl_map_value(map, "browserExecutableFolder")), + userDataFolder(get_optional_fl_map_value(map, "userDataFolder")), + additionalBrowserArguments(get_optional_fl_map_value(map, "additionalBrowserArguments")), + allowSingleSignOnUsingOSPrimaryAccount(get_optional_fl_map_value(map, "allowSingleSignOnUsingOSPrimaryAccount")), + language(get_optional_fl_map_value(map, "language")), + targetCompatibleBrowserVersion(get_optional_fl_map_value(map, "targetCompatibleBrowserVersion")) + {} + + flutter::EncodableMap WebViewEnvironmentSettings::toEncodableMap() const + { + return flutter::EncodableMap{ + {"browserExecutableFolder", make_fl_value(browserExecutableFolder)}, + {"userDataFolder", make_fl_value(userDataFolder)}, + {"additionalBrowserArguments", make_fl_value(additionalBrowserArguments)}, + {"allowSingleSignOnUsingOSPrimaryAccount", make_fl_value(allowSingleSignOnUsingOSPrimaryAccount)}, + {"language", make_fl_value(language)}, + {"targetCompatibleBrowserVersion", make_fl_value(targetCompatibleBrowserVersion)} + }; + } + +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/webview_environment/webview_environment_settings.h b/flutter_inappwebview_windows/windows/webview_environment/webview_environment_settings.h new file mode 100644 index 00000000..12d1388c --- /dev/null +++ b/flutter_inappwebview_windows/windows/webview_environment/webview_environment_settings.h @@ -0,0 +1,29 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_WEBVIEW_ENVIRONMENT_CREATION_PARAMS_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_WEBVIEW_ENVIRONMENT_CREATION_PARAMS_H_ + +#include +#include +#include + +#include "../flutter_inappwebview_windows_plugin.h" + +namespace flutter_inappwebview_plugin +{ + class WebViewEnvironmentSettings + { + public: + const std::optional browserExecutableFolder; + const std::optional userDataFolder; + const std::optional additionalBrowserArguments; + const std::optional allowSingleSignOnUsingOSPrimaryAccount; + const std::optional language; + const std::optional targetCompatibleBrowserVersion; + + WebViewEnvironmentSettings() = default; + WebViewEnvironmentSettings(const flutter::EncodableMap& map); + ~WebViewEnvironmentSettings() = default; + + flutter::EncodableMap toEncodableMap() const; + }; +} +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_WEBVIEW_ENVIRONMENT_CREATION_PARAMS_H_ \ No newline at end of file