Added WebMessageChannel and WebMessageListener features, Updated Android Gradle distributionUrl version to 5.6.4, AndroidInAppWebViewController.getCurrentWebViewPackage is available now starting from Android API 21+, Attempt to fix #665, fix #579, Fixed wrong mapping of NavigationAction class on Android for androidHasGesture and androidIsRedirect properties

This commit is contained in:
Lorenzo Pichilli 2021-03-11 22:42:18 +01:00
parent 54b31ca885
commit a40831087f
43 changed files with 1501 additions and 786 deletions

View File

@ -1,738 +0,0 @@
<component name="libraryTable">
<library name="Dart Packages" type="DartPackagesLibraryType">
<properties>
<option name="packageNameToDirsMap">
<entry key="_fe_analyzer_shared">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/_fe_analyzer_shared-12.0.0/lib" />
</list>
</value>
</entry>
<entry key="analyzer">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/analyzer-0.40.6/lib" />
</list>
</value>
</entry>
<entry key="archive">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/archive-3.0.0/lib" />
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/archive-2.0.13/lib" />
</list>
</value>
</entry>
<entry key="args">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/args-1.6.0/lib" />
</list>
</value>
</entry>
<entry key="async">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/async-2.5.0/lib" />
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/async-2.5.0-nullsafety.3/lib" />
</list>
</value>
</entry>
<entry key="boolean_selector">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/boolean_selector-2.1.0/lib" />
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/boolean_selector-2.1.0-nullsafety.3/lib" />
</list>
</value>
</entry>
<entry key="characters">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/characters-1.1.0/lib" />
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/characters-1.1.0-nullsafety.5/lib" />
</list>
</value>
</entry>
<entry key="charcode">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/charcode-1.2.0/lib" />
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/charcode-1.2.0-nullsafety.3/lib" />
</list>
</value>
</entry>
<entry key="cli_util">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/cli_util-0.2.0/lib" />
</list>
</value>
</entry>
<entry key="clock">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/clock-1.1.0/lib" />
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/clock-1.1.0-nullsafety.3/lib" />
</list>
</value>
</entry>
<entry key="collection">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/collection-1.15.0/lib" />
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/collection-1.15.0-nullsafety.5/lib" />
</list>
</value>
</entry>
<entry key="convert">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/convert-2.1.1/lib" />
</list>
</value>
</entry>
<entry key="coverage">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/coverage-0.14.2/lib" />
</list>
</value>
</entry>
<entry key="crypto">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/crypto-3.0.0/lib" />
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/crypto-2.1.5/lib" />
</list>
</value>
</entry>
<entry key="cupertino_icons">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/cupertino_icons-1.0.2/lib" />
</list>
</value>
</entry>
<entry key="fake_async">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/fake_async-1.2.0/lib" />
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/fake_async-1.2.0-nullsafety.3/lib" />
</list>
</value>
</entry>
<entry key="ffi">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/ffi-1.0.0/lib" />
</list>
</value>
</entry>
<entry key="file">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/file-6.1.0/lib" />
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/file-6.0.0-nullsafety.4/lib" />
</list>
</value>
</entry>
<entry key="flutter">
<value>
<list>
<option value="$USER_HOME$/flutter/packages/flutter/lib" />
</list>
</value>
</entry>
<entry key="flutter_downloader">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.5.2/lib" />
</list>
</value>
</entry>
<entry key="flutter_driver">
<value>
<list>
<option value="$USER_HOME$/flutter/packages/flutter_driver/lib" />
</list>
</value>
</entry>
<entry key="flutter_test">
<value>
<list>
<option value="$USER_HOME$/flutter/packages/flutter_test/lib" />
</list>
</value>
</entry>
<entry key="fuchsia_remote_debug_protocol">
<value>
<list>
<option value="$USER_HOME$/flutter/packages/fuchsia_remote_debug_protocol/lib" />
</list>
</value>
</entry>
<entry key="glob">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/glob-1.2.0/lib" />
</list>
</value>
</entry>
<entry key="http_multi_server">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/http_multi_server-2.2.0/lib" />
</list>
</value>
</entry>
<entry key="http_parser">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/http_parser-3.1.4/lib" />
</list>
</value>
</entry>
<entry key="integration_test">
<value>
<list>
<option value="$USER_HOME$/flutter/packages/integration_test/lib" />
</list>
</value>
</entry>
<entry key="io">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/io-0.3.4/lib" />
</list>
</value>
</entry>
<entry key="js">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/js-0.6.3-nullsafety.3/lib" />
</list>
</value>
</entry>
<entry key="json_rpc_2">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/json_rpc_2-2.2.2/lib" />
</list>
</value>
</entry>
<entry key="logging">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/logging-0.11.4/lib" />
</list>
</value>
</entry>
<entry key="matcher">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/matcher-0.12.10/lib" />
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/matcher-0.12.10-nullsafety.3/lib" />
</list>
</value>
</entry>
<entry key="meta">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/meta-1.3.0/lib" />
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/meta-1.3.0-nullsafety.6/lib" />
</list>
</value>
</entry>
<entry key="mime">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/mime-0.9.7/lib" />
</list>
</value>
</entry>
<entry key="mockito">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/mockito-4.1.1/lib" />
</list>
</value>
</entry>
<entry key="node_interop">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/node_interop-1.2.1/lib" />
</list>
</value>
</entry>
<entry key="node_io">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/node_io-1.1.1/lib" />
</list>
</value>
</entry>
<entry key="node_preamble">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/node_preamble-1.4.13/lib" />
</list>
</value>
</entry>
<entry key="package_config">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/package_config-1.9.3/lib" />
</list>
</value>
</entry>
<entry key="path">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/path-1.8.0/lib" />
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/path-1.8.0-nullsafety.3/lib" />
</list>
</value>
</entry>
<entry key="path_provider">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-2.0.0-nullsafety/lib" />
</list>
</value>
</entry>
<entry key="path_provider_linux">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_linux-0.2.0-nullsafety/lib" />
</list>
</value>
</entry>
<entry key="path_provider_macos">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_macos-0.0.5-nullsafety/lib" />
</list>
</value>
</entry>
<entry key="path_provider_platform_interface">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_platform_interface-2.0.0-nullsafety/lib" />
</list>
</value>
</entry>
<entry key="path_provider_windows">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_windows-0.1.0-nullsafety.3/lib" />
</list>
</value>
</entry>
<entry key="pedantic">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/pedantic-1.10.0/lib" />
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/pedantic-1.10.0-nullsafety.3/lib" />
</list>
</value>
</entry>
<entry key="permission_handler">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.1.0+2/lib" />
</list>
</value>
</entry>
<entry key="permission_handler_platform_interface">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler_platform_interface-2.0.2/lib" />
</list>
</value>
</entry>
<entry key="platform">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/platform-3.0.0/lib" />
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/platform-3.0.0-nullsafety.4/lib" />
</list>
</value>
</entry>
<entry key="plugin_platform_interface">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/plugin_platform_interface-1.1.0-nullsafety.1/lib" />
</list>
</value>
</entry>
<entry key="pool">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/pool-1.5.0-nullsafety.3/lib" />
</list>
</value>
</entry>
<entry key="process">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/process-4.1.0/lib" />
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/process-4.0.0-nullsafety.4/lib" />
</list>
</value>
</entry>
<entry key="pub_semver">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/pub_semver-1.4.4/lib" />
</list>
</value>
</entry>
<entry key="shelf">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/shelf-0.7.5/lib" />
</list>
</value>
</entry>
<entry key="shelf_packages_handler">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/shelf_packages_handler-2.0.0/lib" />
</list>
</value>
</entry>
<entry key="shelf_static">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/shelf_static-0.2.9+1/lib" />
</list>
</value>
</entry>
<entry key="shelf_web_socket">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/shelf_web_socket-0.2.3/lib" />
</list>
</value>
</entry>
<entry key="sky_engine">
<value>
<list>
<option value="$USER_HOME$/flutter/bin/cache/pkg/sky_engine/lib" />
</list>
</value>
</entry>
<entry key="source_map_stack_trace">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/source_map_stack_trace-2.1.0-nullsafety.4/lib" />
</list>
</value>
</entry>
<entry key="source_maps">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/source_maps-0.10.10-nullsafety.3/lib" />
</list>
</value>
</entry>
<entry key="source_span">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/source_span-1.8.1/lib" />
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/source_span-1.8.0-nullsafety.4/lib" />
</list>
</value>
</entry>
<entry key="stack_trace">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/stack_trace-1.10.0/lib" />
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/stack_trace-1.10.0-nullsafety.6/lib" />
</list>
</value>
</entry>
<entry key="stream_channel">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/stream_channel-2.1.0/lib" />
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/stream_channel-2.1.0-nullsafety.3/lib" />
</list>
</value>
</entry>
<entry key="string_scanner">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/string_scanner-1.1.0/lib" />
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/string_scanner-1.1.0-nullsafety.3/lib" />
</list>
</value>
</entry>
<entry key="sync_http">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/sync_http-0.3.0/lib" />
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/sync_http-0.2.0/lib" />
</list>
</value>
</entry>
<entry key="term_glyph">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/term_glyph-1.2.0/lib" />
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/term_glyph-1.2.0-nullsafety.3/lib" />
</list>
</value>
</entry>
<entry key="test">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/test-1.16.0-nullsafety.9/lib" />
</list>
</value>
</entry>
<entry key="test_api">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/test_api-0.2.19/lib" />
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/test_api-0.2.19-nullsafety.6/lib" />
</list>
</value>
</entry>
<entry key="test_core">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/test_core-0.3.12-nullsafety.9/lib" />
</list>
</value>
</entry>
<entry key="typed_data">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/typed_data-1.3.0/lib" />
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/typed_data-1.3.0-nullsafety.5/lib" />
</list>
</value>
</entry>
<entry key="url_launcher">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher-6.0.0-nullsafety.6/lib" />
</list>
</value>
</entry>
<entry key="url_launcher_linux">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_linux-0.1.0-nullsafety.3/lib" />
</list>
</value>
</entry>
<entry key="url_launcher_macos">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_macos-0.1.0-nullsafety.2/lib" />
</list>
</value>
</entry>
<entry key="url_launcher_platform_interface">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_platform_interface-2.0.0-nullsafety.1/lib" />
</list>
</value>
</entry>
<entry key="url_launcher_windows">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_windows-0.1.0-nullsafety.2/lib" />
</list>
</value>
</entry>
<entry key="vector_math">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/vector_math-2.1.0/lib" />
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/vector_math-2.1.0-nullsafety.5/lib" />
</list>
</value>
</entry>
<entry key="vm_service">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/vm_service-6.0.1-nullsafety.1/lib" />
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/vm_service-5.5.0/lib" />
</list>
</value>
</entry>
<entry key="watcher">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/watcher-0.9.7+15/lib" />
</list>
</value>
</entry>
<entry key="web_socket_channel">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/web_socket_channel-1.2.0/lib" />
</list>
</value>
</entry>
<entry key="webdriver">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/webdriver-3.0.0/lib" />
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/webdriver-2.1.2/lib" />
</list>
</value>
</entry>
<entry key="webkit_inspection_protocol">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/webkit_inspection_protocol-0.7.4/lib" />
</list>
</value>
</entry>
<entry key="win32">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/win32-2.0.0/lib" />
</list>
</value>
</entry>
<entry key="xdg_directories">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/xdg_directories-0.2.0-nullsafety.1/lib" />
</list>
</value>
</entry>
<entry key="yaml">
<value>
<list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/yaml-2.2.1/lib" />
</list>
</value>
</entry>
</option>
</properties>
<CLASSES>
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/_fe_analyzer_shared-12.0.0/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/analyzer-0.40.6/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/archive-2.0.13/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/archive-3.0.0/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/args-1.6.0/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/async-2.5.0-nullsafety.3/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/async-2.5.0/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/boolean_selector-2.1.0-nullsafety.3/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/boolean_selector-2.1.0/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/characters-1.1.0-nullsafety.5/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/characters-1.1.0/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/charcode-1.2.0-nullsafety.3/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/charcode-1.2.0/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/cli_util-0.2.0/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/clock-1.1.0-nullsafety.3/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/clock-1.1.0/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/collection-1.15.0-nullsafety.5/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/collection-1.15.0/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/convert-2.1.1/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/coverage-0.14.2/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/crypto-2.1.5/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/crypto-3.0.0/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/cupertino_icons-1.0.2/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/fake_async-1.2.0-nullsafety.3/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/fake_async-1.2.0/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/ffi-1.0.0/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/file-6.0.0-nullsafety.4/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/file-6.1.0/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.5.2/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/glob-1.2.0/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/http_multi_server-2.2.0/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/http_parser-3.1.4/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/io-0.3.4/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/js-0.6.3-nullsafety.3/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/json_rpc_2-2.2.2/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/logging-0.11.4/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/matcher-0.12.10-nullsafety.3/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/matcher-0.12.10/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/meta-1.3.0-nullsafety.6/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/meta-1.3.0/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/mime-0.9.7/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/mockito-4.1.1/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/node_interop-1.2.1/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/node_io-1.1.1/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/node_preamble-1.4.13/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/package_config-1.9.3/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/path-1.8.0-nullsafety.3/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/path-1.8.0/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-2.0.0-nullsafety/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_linux-0.2.0-nullsafety/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_macos-0.0.5-nullsafety/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_platform_interface-2.0.0-nullsafety/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_windows-0.1.0-nullsafety.3/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/pedantic-1.10.0-nullsafety.3/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/pedantic-1.10.0/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.1.0+2/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler_platform_interface-2.0.2/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/platform-3.0.0-nullsafety.4/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/platform-3.0.0/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/plugin_platform_interface-1.1.0-nullsafety.1/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/pool-1.5.0-nullsafety.3/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/process-4.0.0-nullsafety.4/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/process-4.1.0/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/pub_semver-1.4.4/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/shelf-0.7.5/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/shelf_packages_handler-2.0.0/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/shelf_static-0.2.9+1/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/shelf_web_socket-0.2.3/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/source_map_stack_trace-2.1.0-nullsafety.4/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/source_maps-0.10.10-nullsafety.3/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/source_span-1.8.0-nullsafety.4/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/source_span-1.8.1/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/stack_trace-1.10.0-nullsafety.6/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/stack_trace-1.10.0/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/stream_channel-2.1.0-nullsafety.3/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/stream_channel-2.1.0/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/string_scanner-1.1.0-nullsafety.3/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/string_scanner-1.1.0/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/sync_http-0.2.0/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/sync_http-0.3.0/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/term_glyph-1.2.0-nullsafety.3/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/term_glyph-1.2.0/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/test-1.16.0-nullsafety.9/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/test_api-0.2.19-nullsafety.6/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/test_api-0.2.19/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/test_core-0.3.12-nullsafety.9/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/typed_data-1.3.0-nullsafety.5/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/typed_data-1.3.0/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher-6.0.0-nullsafety.6/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_linux-0.1.0-nullsafety.3/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_macos-0.1.0-nullsafety.2/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_platform_interface-2.0.0-nullsafety.1/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_windows-0.1.0-nullsafety.2/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/vector_math-2.1.0-nullsafety.5/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/vector_math-2.1.0/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/vm_service-5.5.0/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/vm_service-6.0.1-nullsafety.1/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/watcher-0.9.7+15/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/web_socket_channel-1.2.0/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/webdriver-2.1.2/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/webdriver-3.0.0/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/webkit_inspection_protocol-0.7.4/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/win32-2.0.0/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/xdg_directories-0.2.0-nullsafety.1/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/yaml-2.2.1/lib" />
<root url="file://$USER_HOME$/flutter/bin/cache/pkg/sky_engine/lib" />
<root url="file://$USER_HOME$/flutter/packages/flutter/lib" />
<root url="file://$USER_HOME$/flutter/packages/flutter_driver/lib" />
<root url="file://$USER_HOME$/flutter/packages/flutter_test/lib" />
<root url="file://$USER_HOME$/flutter/packages/fuchsia_remote_debug_protocol/lib" />
<root url="file://$USER_HOME$/flutter/packages/integration_test/lib" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@ -1,6 +1,8 @@
<component name="libraryTable">
<library name="Flutter Plugins">
<CLASSES />
<CLASSES>
<root url="file://$PROJECT_DIR$" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>

View File

@ -1,3 +1,12 @@
## 5.2.0
- Added `WebMessageChannel` and `WebMessageListener` features
- `AndroidInAppWebViewController.getCurrentWebViewPackage` is available now starting from Android API 21+.
- Updated Android Gradle distributionUrl version to `5.6.4`
- Attempt to fix "InAppBrowserActivity.onCreate NullPointerException - Attempt to invoke virtual method 'java.lang.String android.os.Bundle.getString(java.lang.String)' on a null object reference" [#665](https://github.com/pichillilorenzo/flutter_inappwebview/issues/665)
- Fixed "[iOS] Application crashes when processing onCreateWindow" [#579](https://github.com/pichillilorenzo/flutter_inappwebview/issues/579)
- Fixed wrong mapping of `NavigationAction` class on Android for `androidHasGesture` and `androidIsRedirect` properties
## 5.1.0+4
- Fixed "IOS scrolling crash the application" [#707](https://github.com/pichillilorenzo/flutter_inappwebview/issues/707)

View File

@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip

View File

@ -2,8 +2,13 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.pichillilorenzo.flutter_inappwebview">
<application>
<activity android:theme="@style/AppTheme" android:name="com.pichillilorenzo.flutter_inappwebview.in_app_browser.InAppBrowserActivity" android:configChanges="orientation|screenSize"></activity>
<activity android:theme="@style/ThemeTransparent" android:name="com.pichillilorenzo.flutter_inappwebview.chrome_custom_tabs.ChromeCustomTabsActivity" android:configChanges="orientation|screenSize"></activity>
<activity
android:theme="@style/AppTheme"
android:name="com.pichillilorenzo.flutter_inappwebview.in_app_browser.InAppBrowserActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density" />
<activity
android:theme="@style/ThemeTransparent"
android:name="com.pichillilorenzo.flutter_inappwebview.chrome_custom_tabs.ChromeCustomTabsActivity" />
<receiver android:name="com.pichillilorenzo.flutter_inappwebview.chrome_custom_tabs.ActionBroadcastReceiver" />
<meta-data
android:name="io.flutter.embedded_views_preview"

View File

@ -1,10 +1,14 @@
package com.pichillilorenzo.flutter_inappwebview;
import android.net.Uri;
import android.os.Build;
import android.util.Log;
import android.webkit.ValueCallback;
import android.webkit.WebView;
import androidx.annotation.NonNull;
import androidx.webkit.WebMessageCompat;
import androidx.webkit.WebMessagePortCompat;
import androidx.webkit.WebViewCompat;
import androidx.webkit.WebViewFeature;
@ -16,9 +20,13 @@ import com.pichillilorenzo.flutter_inappwebview.types.ContentWorld;
import com.pichillilorenzo.flutter_inappwebview.types.SslCertificateExt;
import com.pichillilorenzo.flutter_inappwebview.types.URLRequest;
import com.pichillilorenzo.flutter_inappwebview.types.UserScript;
import com.pichillilorenzo.flutter_inappwebview.types.WebMessageChannel;
import com.pichillilorenzo.flutter_inappwebview.types.WebMessageListener;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import io.flutter.plugin.common.MethodCall;
@ -505,6 +513,56 @@ public class InAppWebViewMethodHandler implements MethodChannel.MethodCallHandle
result.success(false);
}
break;
case "createWebMessageChannel":
if (webView != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
WebViewFeature.isFeatureSupported(WebViewFeature.CREATE_WEB_MESSAGE_CHANNEL)) {
result.success(webView.createCompatWebMessageChannel().toMap());
} else {
result.success(null);
}
break;
case "postWebMessage":
if (webView != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
WebViewFeature.isFeatureSupported(WebViewFeature.POST_WEB_MESSAGE)) {
Map<String, Object> message = (Map<String, Object>) call.argument("message");
String targetOrigin = (String) call.argument("targetOrigin");
List<WebMessagePortCompat> ports = new ArrayList<>();
List<Map<String, Object>> portsMap = (List<Map<String, Object>>) message.get("ports");
if (portsMap != null) {
for (Map<String, Object> portMap : portsMap) {
String webMessageChannelId = (String) portMap.get("webMessageChannelId");
Integer index = (Integer) portMap.get("index");
WebMessageChannel webMessageChannel = webView.webMessageChannels.get(webMessageChannelId);
if (webMessageChannel != null) {
ports.add(webMessageChannel.ports.get(index));
}
}
}
WebMessageCompat webMessage = new WebMessageCompat((String) message.get("data"), ports.toArray(new WebMessagePortCompat[0]));
try {
WebViewCompat.postWebMessage(webView, webMessage, Uri.parse(targetOrigin));
result.success(true);
} catch (Exception e) {
result.error(LOG_TAG, e.getMessage(), null);
}
} else {
result.success(true);
}
break;
case "addWebMessageListener":
if (webView != null && WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)) {
Map<String, Object> webMessageListenerMap = (Map<String, Object>) call.argument("webMessageListener");
WebMessageListener webMessageListener = WebMessageListener.fromMap(webMessageListenerMap);
try {
webView.addWebMessageListener(webMessageListener);
result.success(true);
} catch (Exception e) {
result.error(LOG_TAG, e.getMessage(), null);
}
} else {
result.success(true);
}
break;
default:
result.notImplemented();
}

View File

@ -1,6 +1,7 @@
package com.pichillilorenzo.flutter_inappwebview;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.webkit.ValueCallback;
import android.webkit.WebSettings;
@ -9,6 +10,7 @@ import android.webkit.WebView;
import androidx.webkit.WebViewCompat;
import androidx.webkit.WebViewFeature;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -66,8 +68,20 @@ public class InAppWebViewStatic implements MethodChannel.MethodCallHandler {
break;
case "getCurrentWebViewPackage":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
result.success(
convertWebViewPackageToMap(WebViewCompat.getCurrentWebViewPackage(Shared.activity)));
result.success(convertWebViewPackageToMap(WebViewCompat.getCurrentWebViewPackage(Shared.activity)));
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
//with Android Lollipop (API 21) they started to update the WebView
//as a separate APK with the PlayStore and they added the
//getLoadedPackageInfo() method to the WebViewFactory class and this
//should handle the Android 7.0 behaviour changes too
try {
Class webViewFactory = Class.forName("android.webkit.WebViewFactory");
Method method = webViewFactory.getMethod("getLoadedPackageInfo");
PackageInfo pInfo = (PackageInfo) method.invoke(null);
result.success(convertWebViewPackageToMap(pInfo));
} catch (Exception e) {
result.success(null);
}
} else {
result.success(null);
}

View File

@ -65,11 +65,8 @@ public class InAppBrowserActivity extends AppCompatActivity implements InAppBrow
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
return;
}
Bundle b = getIntent().getExtras();
assert b != null;
id = b.getString("id");
windowId = b.getInt("windowId");
@ -77,7 +74,7 @@ public class InAppBrowserActivity extends AppCompatActivity implements InAppBrow
setContentView(R.layout.activity_web_view);
Map<String, Object> pullToRefreshInitialOptions = (Map<String, Object>) b.getSerializable("pullToRefreshInitialOptions");
Map<String, Object> pullToRefreshInitialOptions = (Map<String, Object>) b.getSerializable("pullToRefreshInitialOptions");
MethodChannel pullToRefreshLayoutChannel = new MethodChannel(Shared.messenger, "com.pichillilorenzo/flutter_inappwebview_pull_to_refresh_" + id);
PullToRefreshOptions pullToRefreshOptions = new PullToRefreshOptions();
pullToRefreshOptions.parse(pullToRefreshInitialOptions);

View File

@ -32,6 +32,8 @@ import android.os.Bundle;
import android.webkit.MimeTypeMap;
import android.util.Log;
import androidx.annotation.NonNull;
import com.pichillilorenzo.flutter_inappwebview.Shared;
import java.io.Serializable;
@ -58,7 +60,7 @@ public class InAppBrowserManager implements MethodChannel.MethodCallHandler {
}
@Override
public void onMethodCall(final MethodCall call, final Result result) {
public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
final Activity activity = Shared.activity;
switch (call.method) {

View File

@ -9,7 +9,7 @@ import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
@ -41,30 +41,28 @@ import android.webkit.WebBackForwardList;
import android.webkit.WebHistoryItem;
import android.webkit.WebSettings;
import android.webkit.WebStorage;
import android.webkit.WebView;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.webkit.JavaScriptReplyProxy;
import androidx.webkit.WebMessageCompat;
import androidx.webkit.WebViewCompat;
import androidx.webkit.WebViewFeature;
import com.pichillilorenzo.flutter_inappwebview.types.PluginScript;
import com.pichillilorenzo.flutter_inappwebview.types.PreferredContentModeOptionType;
import com.pichillilorenzo.flutter_inappwebview.types.UserContentController;
import com.pichillilorenzo.flutter_inappwebview.JavaScriptBridgeInterface;
import com.pichillilorenzo.flutter_inappwebview.R;
import com.pichillilorenzo.flutter_inappwebview.Shared;
import com.pichillilorenzo.flutter_inappwebview.Util;
import com.pichillilorenzo.flutter_inappwebview.content_blocker.ContentBlocker;
import com.pichillilorenzo.flutter_inappwebview.content_blocker.ContentBlockerAction;
import com.pichillilorenzo.flutter_inappwebview.content_blocker.ContentBlockerHandler;
import com.pichillilorenzo.flutter_inappwebview.content_blocker.ContentBlockerTrigger;
import com.pichillilorenzo.flutter_inappwebview.types.ContentWorld;
import com.pichillilorenzo.flutter_inappwebview.in_app_browser.InAppBrowserDelegate;
import com.pichillilorenzo.flutter_inappwebview.JavaScriptBridgeInterface;
import com.pichillilorenzo.flutter_inappwebview.R;
import com.pichillilorenzo.flutter_inappwebview.Shared;
import com.pichillilorenzo.flutter_inappwebview.types.URLRequest;
import com.pichillilorenzo.flutter_inappwebview.types.UserScript;
import com.pichillilorenzo.flutter_inappwebview.Util;
import com.pichillilorenzo.flutter_inappwebview.plugin_scripts_js.ConsoleLogJS;
import com.pichillilorenzo.flutter_inappwebview.plugin_scripts_js.InterceptAjaxRequestJS;
import com.pichillilorenzo.flutter_inappwebview.plugin_scripts_js.InterceptFetchRequestJS;
@ -75,19 +73,25 @@ import com.pichillilorenzo.flutter_inappwebview.plugin_scripts_js.OnWindowFocusE
import com.pichillilorenzo.flutter_inappwebview.plugin_scripts_js.PluginScriptsUtil;
import com.pichillilorenzo.flutter_inappwebview.plugin_scripts_js.PrintJS;
import com.pichillilorenzo.flutter_inappwebview.plugin_scripts_js.PromisePolyfillJS;
import com.pichillilorenzo.flutter_inappwebview.types.ContentWorld;
import com.pichillilorenzo.flutter_inappwebview.types.PluginScript;
import com.pichillilorenzo.flutter_inappwebview.types.PreferredContentModeOptionType;
import com.pichillilorenzo.flutter_inappwebview.types.URLRequest;
import com.pichillilorenzo.flutter_inappwebview.types.UserContentController;
import com.pichillilorenzo.flutter_inappwebview.types.UserScript;
import com.pichillilorenzo.flutter_inappwebview.types.WebMessageChannel;
import com.pichillilorenzo.flutter_inappwebview.types.WebMessageListener;
import org.json.JSONObject;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.regex.Pattern;
@ -140,6 +144,8 @@ final public class InAppWebView extends InputAwareWebView {
public Map<String, ValueCallback<String>> callAsyncJavaScriptCallbacks = new HashMap<>();
public Map<String, ValueCallback<String>> evaluateJavaScriptContentWorldCallbacks = new HashMap<>();
public Map<String, WebMessageChannel> webMessageChannels = new HashMap<>();
public InAppWebView(Context context) {
super(context);
}
@ -1587,6 +1593,30 @@ final public class InAppWebView extends InputAwareWebView {
});
}
@TargetApi(Build.VERSION_CODES.M)
public WebMessageChannel createCompatWebMessageChannel() {
String id = UUID.randomUUID().toString();
WebMessageChannel webMessageChannel = new WebMessageChannel(id, this);
webMessageChannels.put(id, webMessageChannel);
return webMessageChannel;
}
public void addWebMessageListener(@NonNull WebMessageListener webMessageListener) {
WebViewCompat.addWebMessageListener(this, webMessageListener.jsObjectName, webMessageListener.allowedOriginRules, webMessageListener.listener);
}
public void disposeWebMessageChannels() {
for (WebMessageChannel webMessageChannel : webMessageChannels.values()) {
webMessageChannel.dispose();
}
webMessageChannels.clear();
}
// @Override
// protected void onWindowVisibilityChanged(int visibility) {
// if (visibility != View.GONE) super.onWindowVisibilityChanged(View.VISIBLE);
// }
@Override
public void dispose() {
if (windowId != null) {
@ -1594,6 +1624,7 @@ final public class InAppWebView extends InputAwareWebView {
}
headlessHandler.removeCallbacksAndMessages(null);
mHandler.removeCallbacksAndMessages(null);
disposeWebMessageChannels();
removeAllViews();
if (checkContextMenuShouldBeClosedTask != null)
removeCallbacks(checkContextMenuShouldBeClosedTask);

View File

@ -190,7 +190,7 @@ public class InAppWebViewClient extends WebViewClient {
public void onPageStarted(WebView view, String url, Bitmap favicon) {
final InAppWebView webView = (InAppWebView) view;
webView.isLoading = true;
webView.disposeWebMessageChannels();
webView.userContentController.resetContentWorlds();
loadCustomJavaScriptOnPageStarted(webView);

View File

@ -20,8 +20,8 @@ public class NavigationAction {
Map<String, Object> navigationActionMap = new HashMap<>();
navigationActionMap.put("request", request.toMap());
navigationActionMap.put("isForMainFrame", isForMainFrame);
navigationActionMap.put("hasGesture", hasGesture);
navigationActionMap.put("isRedirect", isRedirect);
navigationActionMap.put("androidHasGesture", hasGesture);
navigationActionMap.put("androidIsRedirect", isRedirect);
return navigationActionMap;
}

View File

@ -0,0 +1,133 @@
package com.pichillilorenzo.flutter_inappwebview.types;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.webkit.WebMessageCompat;
import androidx.webkit.WebMessagePortCompat;
import androidx.webkit.WebViewCompat;
import androidx.webkit.WebViewFeature;
import com.pichillilorenzo.flutter_inappwebview.Shared;
import com.pichillilorenzo.flutter_inappwebview.in_app_webview.InAppWebView;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
public class WebMessageChannel implements MethodChannel.MethodCallHandler {
static final String LOG_TAG = "WebMessageChannel";
public String id;
public MethodChannel channel;
public final List<WebMessagePortCompat> ports;
private InAppWebView webView;
public WebMessageChannel(@NonNull String id, @NonNull InAppWebView webView) {
this.id = id;
this.channel = new MethodChannel(Shared.messenger, "com.pichillilorenzo/flutter_inappwebview_web_message_channel_" + id);
this.channel.setMethodCallHandler(this);
this.ports = new ArrayList<>(Arrays.asList(WebViewCompat.createWebMessageChannel(webView)));
this.webView = webView;
}
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
switch (call.method) {
case "setWebMessageCallback":
if (webView != null && ports.size() > 0 &&
WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_PORT_SET_MESSAGE_CALLBACK)) {
final Integer index = (Integer) call.argument("index");
final WebMessagePortCompat webMessagePort = ports.get(index);
try {
webMessagePort.setWebMessageCallback(new WebMessagePortCompat.WebMessageCallbackCompat() {
@Override
public void onMessage(@NonNull WebMessagePortCompat port, @Nullable WebMessageCompat message) {
super.onMessage(port, message);
Map<String, Object> obj = new HashMap<>();
obj.put("index", index);
obj.put("message", message != null ? message.getData() : null);
channel.invokeMethod("onMessage", obj);
}
});
result.success(true);
} catch (Exception e) {
result.error(LOG_TAG, e.getMessage(), null);
}
} else {
result.success(true);
}
break;
case "postMessage":
if (webView != null && ports.size() > 0 &&
WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_PORT_POST_MESSAGE)) {
final Integer index = (Integer) call.argument("index");
WebMessagePortCompat port = ports.get(index);
Map<String, Object> message = (Map<String, Object>) call.argument("message");
List<WebMessagePortCompat> webMessagePorts = new ArrayList<>();
List<Map<String, Object>> portsMap = (List<Map<String, Object>>) message.get("ports");
if (portsMap != null) {
for (Map<String, Object> portMap : portsMap) {
String webMessageChannelId = (String) portMap.get("webMessageChannelId");
Integer portIndex = (Integer) portMap.get("index");
WebMessageChannel webMessageChannel = webView.webMessageChannels.get(webMessageChannelId);
if (webMessageChannel != null) {
webMessagePorts.add(webMessageChannel.ports.get(portIndex));
}
}
}
WebMessageCompat webMessage = new WebMessageCompat((String) message.get("data"), webMessagePorts.toArray(new WebMessagePortCompat[0]));
try {
port.postMessage(webMessage);
result.success(true);
} catch (Exception e) {
result.error(LOG_TAG, e.getMessage(), null);
}
} else {
result.success(true);
}
break;
case "close":
if (webView != null && ports.size() > 0 &&
WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_PORT_CLOSE)) {
Integer index = (Integer) call.argument("index");
WebMessagePortCompat port = ports.get(index);
try {
port.close();
result.success(true);
} catch (Exception e) {
result.error(LOG_TAG, e.getMessage(), null);
}
} else {
result.success(true);
}
break;
default:
result.notImplemented();
}
}
public Map<String, Object> toMap() {
Map<String, Object> webMessageChannelMap = new HashMap<>();
webMessageChannelMap.put("id", id);
return webMessageChannelMap;
}
public void dispose() {
if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_PORT_CLOSE)) {
for (WebMessagePortCompat port : ports) {
try {
port.close();
} catch (Exception ignored) {}
}
}
this.channel.setMethodCallHandler(null);
this.ports.clear();
this.webView = null;
}
}

View File

@ -0,0 +1,84 @@
package com.pichillilorenzo.flutter_inappwebview.types;
import android.net.Uri;
import android.webkit.WebView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.webkit.JavaScriptReplyProxy;
import androidx.webkit.WebMessageCompat;
import androidx.webkit.WebViewCompat;
import androidx.webkit.WebViewFeature;
import com.pichillilorenzo.flutter_inappwebview.Shared;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
public class WebMessageListener implements MethodChannel.MethodCallHandler {
static final String LOG_TAG = "WebMessageListener";
public String jsObjectName;
public Set<String> allowedOriginRules;
public WebViewCompat.WebMessageListener listener;
public JavaScriptReplyProxy replyProxy;
public MethodChannel channel;
public WebMessageListener(@NonNull String jsObjectName, @NonNull Set<String> allowedOriginRules) {
this.jsObjectName = jsObjectName;
this.allowedOriginRules = allowedOriginRules;
this.channel = new MethodChannel(Shared.messenger, "com.pichillilorenzo/flutter_inappwebview_web_message_listener_" + this.jsObjectName);
this.channel.setMethodCallHandler(this);
this.listener = new WebViewCompat.WebMessageListener() {
@Override
public void onPostMessage(@NonNull WebView view, @NonNull WebMessageCompat message, @NonNull Uri sourceOrigin, boolean isMainFrame, @NonNull JavaScriptReplyProxy javaScriptReplyProxy) {
replyProxy = javaScriptReplyProxy;
Map<String, Object> obj = new HashMap<>();
obj.put("message", message.getData());
obj.put("sourceOrigin", sourceOrigin.toString().equals("null") ? null : sourceOrigin.toString());
obj.put("isMainFrame", isMainFrame);
channel.invokeMethod("onPostMessage", obj);
}
};
}
@Nullable
public static WebMessageListener fromMap(@Nullable Map<String, Object> map) {
if (map == null) {
return null;
}
String jsObjectName = (String) map.get("jsObjectName");
assert jsObjectName != null;
List<String> allowedOriginRuleList = (List<String>) map.get("allowedOriginRules");
assert allowedOriginRuleList != null;
Set<String> allowedOriginRules = new HashSet<>(allowedOriginRuleList);
return new WebMessageListener(jsObjectName, allowedOriginRules);
}
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
switch (call.method) {
case "postMessage":
if (replyProxy != null && WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)) {
String message = (String) call.argument("message");
replyProxy.postMessage(message);
}
result.success(true);
break;
default:
result.notImplemented();
}
}
public void dispose() {
this.channel.setMethodCallHandler(null);
this.listener = null;
this.replyProxy = null;
}
}

View File

@ -1 +1 @@
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.5.2/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":[]},{"name":"integration_test","path":"/Users/lorenzopichilli/flutter/packages/integration_test/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-2.0.0-nullsafety/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.1.0+2/","dependencies":[]},{"name":"url_launcher","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher-6.0.0-nullsafety.6/","dependencies":[]}],"android":[{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.5.2/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":[]},{"name":"integration_test","path":"/Users/lorenzopichilli/flutter/packages/integration_test/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-2.0.0-nullsafety/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.1.0+2/","dependencies":[]},{"name":"url_launcher","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher-6.0.0-nullsafety.6/","dependencies":[]}],"macos":[{"name":"path_provider_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_macos-0.0.5-nullsafety/","dependencies":[]},{"name":"url_launcher_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_macos-0.1.0-nullsafety.2/","dependencies":[]}],"linux":[{"name":"path_provider_linux","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_linux-0.2.0-nullsafety/","dependencies":[]},{"name":"url_launcher_linux","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_linux-0.1.0-nullsafety.3/","dependencies":[]}],"windows":[{"name":"path_provider_windows","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_windows-0.1.0-nullsafety.3/","dependencies":[]},{"name":"url_launcher_windows","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_windows-0.1.0-nullsafety.2/","dependencies":[]}],"web":[]},"dependencyGraph":[{"name":"flutter_downloader","dependencies":[]},{"name":"flutter_inappwebview","dependencies":[]},{"name":"integration_test","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_macos","path_provider_linux","path_provider_windows"]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_macos","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"permission_handler","dependencies":[]},{"name":"url_launcher","dependencies":["url_launcher_linux","url_launcher_macos","url_launcher_windows"]},{"name":"url_launcher_linux","dependencies":[]},{"name":"url_launcher_macos","dependencies":[]},{"name":"url_launcher_windows","dependencies":[]}],"date_created":"2021-03-08 17:17:50.745041","version":"2.1.0-10.0.pre"}
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.5.2/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":[]},{"name":"integration_test","path":"/Users/lorenzopichilli/flutter/packages/integration_test/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-2.0.0-nullsafety/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.1.0+2/","dependencies":[]},{"name":"url_launcher","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher-6.0.0-nullsafety.6/","dependencies":[]}],"android":[{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.5.2/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":[]},{"name":"integration_test","path":"/Users/lorenzopichilli/flutter/packages/integration_test/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-2.0.0-nullsafety/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.1.0+2/","dependencies":[]},{"name":"url_launcher","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher-6.0.0-nullsafety.6/","dependencies":[]}],"macos":[{"name":"path_provider_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_macos-0.0.5-nullsafety/","dependencies":[]},{"name":"url_launcher_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_macos-0.1.0-nullsafety.2/","dependencies":[]}],"linux":[{"name":"path_provider_linux","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_linux-0.2.0-nullsafety/","dependencies":[]},{"name":"url_launcher_linux","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_linux-0.1.0-nullsafety.3/","dependencies":[]}],"windows":[{"name":"path_provider_windows","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_windows-0.1.0-nullsafety.3/","dependencies":[]},{"name":"url_launcher_windows","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_windows-0.1.0-nullsafety.2/","dependencies":[]}],"web":[]},"dependencyGraph":[{"name":"flutter_downloader","dependencies":[]},{"name":"flutter_inappwebview","dependencies":[]},{"name":"integration_test","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_macos","path_provider_linux","path_provider_windows"]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_macos","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"permission_handler","dependencies":[]},{"name":"url_launcher","dependencies":["url_launcher_linux","url_launcher_macos","url_launcher_windows"]},{"name":"url_launcher_linux","dependencies":[]},{"name":"url_launcher_macos","dependencies":[]},{"name":"url_launcher_windows","dependencies":[]}],"date_created":"2021-03-11 22:30:27.356562","version":"2.1.0-10.0.pre"}

View File

@ -2,13 +2,12 @@
# This is a generated file; do not edit or check into version control.
export "FLUTTER_ROOT=/Users/lorenzopichilli/flutter"
export "FLUTTER_APPLICATION_PATH=/Users/lorenzopichilli/Desktop/flutter_inappwebview/example"
export "FLUTTER_TARGET=/Users/lorenzopichilli/Desktop/flutter_inappwebview/example/lib/main.dart"
export "FLUTTER_TARGET=lib/main.dart"
export "FLUTTER_BUILD_DIR=build"
export "SYMROOT=${SOURCE_ROOT}/../build/ios"
export "FLUTTER_BUILD_NAME=1.0.0"
export "FLUTTER_BUILD_NUMBER=1"
export "DART_DEFINES=Zmx1dHRlci5pbnNwZWN0b3Iuc3RydWN0dXJlZEVycm9ycz10cnVl,RkxVVFRFUl9XRUJfQVVUT19ERVRFQ1Q9dHJ1ZQ=="
export "DART_OBFUSCATION=false"
export "TRACK_WIDGET_CREATION=true"
export "TRACK_WIDGET_CREATION=false"
export "TREE_SHAKE_ICONS=false"
export "PACKAGE_CONFIG=/Users/lorenzopichilli/Desktop/flutter_inappwebview/example/.dart_tool/package_config.json"

View File

@ -17,7 +17,7 @@ Future main() async {
WidgetsFlutterBinding.ensureInitialized();
// await Permission.camera.request();
// await Permission.microphone.request();
//await Permission.storage.request();
// await Permission.storage.request();
if (Platform.isAndroid) {
await AndroidInAppWebViewController.setWebContentsDebuggingEnabled(true);

View File

@ -80,6 +80,5 @@
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Dart SDK" level="project" />
<orderEntry type="library" name="Flutter Plugins" level="project" />
<orderEntry type="library" name="Dart Packages" level="project" />
</component>
</module>

View File

@ -75,6 +75,7 @@ public class InAppBrowserWebViewController: UIViewController, InAppBrowserDelega
pullToRefreshControl.prepare()
prepareWebView()
webView.windowCreated = true
progressBar = UIProgressView(progressViewStyle: .bar)

View File

@ -82,6 +82,7 @@ public class FlutterWebViewController: NSObject, FlutterPlatformView {
webView!.options = options
webView!.prepare()
webView!.windowCreated = true
if windowId == nil {
if #available(iOS 11.0, *) {

View File

@ -12,10 +12,13 @@ import WebKit
public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavigationDelegate, WKScriptMessageHandler, UIGestureRecognizerDelegate, PullToRefreshDelegate {
var windowId: Int64?
var windowCreated = false
var inAppBrowserDelegate: InAppBrowserDelegate?
var channel: FlutterMethodChannel?
var options: InAppWebViewOptions?
var pullToRefreshControl: PullToRefreshControl?
var webMessageChannels: [String:WebMessageChannel] = [:]
var webMessageListeners: [WebMessageListener] = []
static var sslCertificatesMap: [String: SslCertificate] = [:] // [URL host name : SslCertificate]
static var credentialsProposed: [URLCredential] = []
@ -450,6 +453,10 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
}
configuration.userContentController.removeScriptMessageHandler(forName: "onCallAsyncJavaScriptResultBelowIOS14Received")
configuration.userContentController.add(self, name: "onCallAsyncJavaScriptResultBelowIOS14Received")
configuration.userContentController.removeScriptMessageHandler(forName: "onWebMessagePortMessageReceived")
configuration.userContentController.add(self, name: "onWebMessagePortMessageReceived")
configuration.userContentController.removeScriptMessageHandler(forName: "onWebMessageListenerPostMessageReceived")
configuration.userContentController.add(self, name: "onWebMessageListenerPostMessageReceived")
configuration.userContentController.addUserOnlyScripts(initialUserScripts)
configuration.userContentController.sync(scriptMessageHandler: self)
}
@ -1418,6 +1425,11 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
if windowId != nil, !windowCreated {
decisionHandler(.cancel)
return
}
if navigationAction.request.url != nil {
if let useShouldOverrideUrlLoading = options?.useShouldOverrideUrlLoading, useShouldOverrideUrlLoading {
@ -1513,6 +1525,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
}
public func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
disposeWebMessageChannels()
initializeWindowIdJS()
if #available(iOS 14.0, *) {
@ -1561,6 +1574,11 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
public func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
if windowId != nil, !windowCreated {
completionHandler(.cancelAuthenticationChallenge, nil)
return
}
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPBasic ||
challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodDefault ||
challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPDigest ||
@ -1573,6 +1591,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
onReceivedHttpAuthRequest(challenge: challenge, result: {(result) -> Void in
if result is FlutterError {
print((result as! FlutterError).message ?? "")
completionHandler(.performDefaultHandling, nil)
}
else if (result as? NSObject) == FlutterMethodNotImplemented {
completionHandler(.performDefaultHandling, nil)
@ -1642,6 +1661,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
onReceivedServerTrustAuthRequest(challenge: challenge, result: {(result) -> Void in
if result is FlutterError {
print((result as! FlutterError).message ?? "")
completionHandler(.performDefaultHandling, nil)
}
else if (result as? NSObject) == FlutterMethodNotImplemented {
completionHandler(.performDefaultHandling, nil)
@ -1677,6 +1697,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
onReceivedClientCertRequest(challenge: challenge, result: {(result) -> Void in
if result is FlutterError {
print((result as! FlutterError).message ?? "")
completionHandler(.performDefaultHandling, nil)
}
else if (result as? NSObject) == FlutterMethodNotImplemented {
completionHandler(.performDefaultHandling, nil)
@ -1801,6 +1822,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
onJsAlert(frame: frame, message: message, result: {(result) -> Void in
if result is FlutterError {
print((result as! FlutterError).message ?? "")
completionHandler()
}
else if (result as? NSObject) == FlutterMethodNotImplemented {
self.createAlertDialog(message: message, responseMessage: nil, confirmButtonTitle: nil, completionHandler: completionHandler)
@ -1862,6 +1884,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
onJsConfirm(frame: frame, message: message, result: {(result) -> Void in
if result is FlutterError {
print((result as! FlutterError).message ?? "")
completionHandler(false)
}
else if (result as? NSObject) == FlutterMethodNotImplemented {
self.createConfirmDialog(message: message, responseMessage: nil, confirmButtonTitle: nil, cancelButtonTitle: nil, completionHandler: completionHandler)
@ -1937,6 +1960,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
onJsPrompt(frame: frame, message: message, defaultValue: defaultValue, result: {(result) -> Void in
if result is FlutterError {
print((result as! FlutterError).message ?? "")
completionHandler(nil)
}
else if (result as? NSObject) == FlutterMethodNotImplemented {
self.createPromptDialog(message: message, defaultValue: defaultValue, responseMessage: nil, confirmButtonTitle: nil, cancelButtonTitle: nil, value: nil, completionHandler: completionHandler)
@ -2068,9 +2092,15 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
public func webView(_ webView: WKWebView,
authenticationChallenge challenge: URLAuthenticationChallenge,
shouldAllowDeprecatedTLS decisionHandler: @escaping (Bool) -> Void) {
if windowId != nil, !windowCreated {
decisionHandler(false)
return
}
shouldAllowDeprecatedTLS(challenge: challenge, result: {(result) -> Void in
if result is FlutterError {
print((result as! FlutterError).message ?? "")
decisionHandler(false)
}
else if (result as? NSObject) == FlutterMethodNotImplemented {
decisionHandler(false)
@ -2511,6 +2541,45 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) {
])
callAsyncJavaScriptBelowIOS14Results.removeValue(forKey: resultUuid)
}
} else if message.name == "onWebMessagePortMessageReceived" {
let body = message.body as! [String: Any?]
let webMessageChannelId = body["webMessageChannelId"] as! String
let index = body["index"] as! Int64
let webMessage = body["message"] as? String
if let webMessageChannel = webMessageChannels[webMessageChannelId] {
webMessageChannel.onMessage(index: index, message: webMessage)
}
} else if message.name == "onWebMessageListenerPostMessageReceived" {
let body = message.body as! [String: Any?]
let jsObjectName = body["jsObjectName"] as! String
let messageData = body["message"] as? String
if let webMessageListener = webMessageListeners.first(where: ({($0.jsObjectName == jsObjectName)})) {
let isMainFrame = message.frameInfo.isMainFrame
var scheme: String? = nil
var host: String? = nil
var port: Int? = nil
if #available(iOS 9.0, *) {
let sourceOrigin = message.frameInfo.securityOrigin
scheme = sourceOrigin.protocol
host = sourceOrigin.host
port = sourceOrigin.port
} else if let url = message.frameInfo.request.url {
scheme = url.scheme
host = url.host
port = url.port
}
if !webMessageListener.isOriginAllowed(scheme: scheme, host: host, port: port) {
return
}
var sourceOrigin: URL? = nil
if let scheme = scheme, !scheme.isEmpty, let host = host, !host.isEmpty {
sourceOrigin = URL(string: "\(scheme)://\(host)\(port != nil && port != 0 ? ":" + String(port!) : "")")
}
webMessageListener.onPostMessage(message: messageData, sourceOrigin: sourceOrigin, isMainFrame: isMainFrame)
}
}
}
@ -2685,15 +2754,74 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) {
}
}
public func createWebMessageChannel(completionHandler: ((WebMessageChannel) -> Void)? = nil) -> WebMessageChannel {
let id = NSUUID().uuidString
let webMessageChannel = WebMessageChannel(id: id)
webMessageChannel.initJsInstance(webView: self, completionHandler: completionHandler)
webMessageChannels[id] = webMessageChannel
return webMessageChannel
}
public func postWebMessage(message: WebMessage, targetOrigin: String, completionHandler: ((Any?) -> Void)? = nil) throws {
var portsString = "null"
if let ports = message.ports {
var portArrayString: [String] = []
for port in ports {
if port.isStarted {
throw NSError(domain: "Port is already started", code: 0)
}
if port.isClosed || port.isTransferred {
throw NSError(domain: "Port is already closed or transferred", code: 0)
}
port.isTransferred = true
portArrayString.append("\(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)['\(port.webMessageChannel!.id)'].\(port.name)")
}
portsString = "[" + portArrayString.joined(separator: ", ") + "]"
}
let data = message.data?.replacingOccurrences(of: "\'", with: "\\'") ?? "null"
let url = URL(string: targetOrigin)?.absoluteString ?? "*"
let source = """
(function() {
window.postMessage('\(data)', '\(url)', \(portsString));
})();
"""
evaluateJavascript(source: source, completionHandler: completionHandler)
message.dispose()
}
public func addWebMessageListener(webMessageListener: WebMessageListener) throws {
if webMessageListeners.map({ ($0.jsObjectName) }).contains(webMessageListener.jsObjectName) {
throw NSError(domain: "jsObjectName \(webMessageListener.jsObjectName) was already added.", code: 0)
}
try webMessageListener.assertOriginRulesValid()
webMessageListener.initJsInstance(webView: self)
webMessageListeners.append(webMessageListener)
}
public func disposeWebMessageChannels() {
for webMessageChannel in webMessageChannels.values {
webMessageChannel.dispose()
}
webMessageChannels.removeAll()
}
public func dispose() {
if isPausedTimers, let completionHandler = isPausedTimersCompletionHandler {
isPausedTimersCompletionHandler = nil
completionHandler()
}
stopLoading()
disposeWebMessageChannels()
for webMessageListener in webMessageListeners {
webMessageListener.dispose()
}
webMessageListeners.removeAll()
if windowId == nil {
configuration.userContentController.removeAllPluginScriptMessageHandlers()
configuration.userContentController.removeScriptMessageHandler(forName: "onCallAsyncJavaScriptResultBelowIOS14Received")
configuration.userContentController.removeScriptMessageHandler(forName: "onWebMessagePortMessageReceived")
configuration.userContentController.removeScriptMessageHandler(forName: "onWebMessageListenerPostMessageReceived")
configuration.userContentController.removeAllUserScripts()
if #available(iOS 11.0, *) {
configuration.userContentController.removeAllContentRuleLists()

View File

@ -474,6 +474,57 @@ public class InAppWebViewMethodHandler: FlutterMethodCallDelegate {
result(false)
}
break
case "createWebMessageChannel":
if let webView = webView {
let _ = webView.createWebMessageChannel { (webMessageChannel) in
result(webMessageChannel.toMap())
}
} else {
result(nil)
}
break
case "postWebMessage":
if let webView = webView {
let message = arguments!["message"] as! [String: Any?]
let targetOrigin = arguments!["targetOrigin"] as! String
var ports: [WebMessagePort] = []
let portsMap = message["ports"] as? [[String: Any?]]
if let portsMap = portsMap {
for portMap in portsMap {
let webMessageChannelId = portMap["webMessageChannelId"] as! String
let index = portMap["index"] as! Int
if let webMessageChannel = webView.webMessageChannels[webMessageChannelId] {
ports.append(webMessageChannel.ports[index])
}
}
}
let webMessage = WebMessage(data: message["data"] as? String, ports: ports)
do {
try webView.postWebMessage(message: webMessage, targetOrigin: targetOrigin) { (_) in
result(true)
}
} catch let error as NSError {
result(FlutterError(code: "InAppWebViewMethodHandler", message: error.domain, details: nil))
}
} else {
result(false)
}
break
case "addWebMessageListener":
if let webView = webView {
let webMessageListenerMap = arguments!["webMessageListener"] as! [String: Any?]
let webMessageListener = WebMessageListener.fromMap(map: webMessageListenerMap)!
do {
try webView.addWebMessageListener(webMessageListener: webMessageListener)
result(false)
} catch let error as NSError {
result(FlutterError(code: "InAppWebViewMethodHandler", message: error.domain, details: nil))
}
} else {
result(false)
}
break
default:
result(FlutterMethodNotImplemented)
break

View File

@ -20,6 +20,7 @@ let JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT = PluginScript(
let JAVASCRIPT_BRIDGE_JS_SOURCE = """
window.\(JAVASCRIPT_BRIDGE_NAME) = {};
\(WEB_MESSAGE_CHANNELS_VARIABLE_NAME) = {};
window.\(JAVASCRIPT_BRIDGE_NAME).callHandler = function() {
var _windowId = \(WINDOW_ID_VARIABLE_JS_SOURCE);
var _callHandlerID = setTimeout(function(){});
@ -28,6 +29,7 @@ window.\(JAVASCRIPT_BRIDGE_NAME).callHandler = function() {
window.\(JAVASCRIPT_BRIDGE_NAME)[_callHandlerID] = resolve;
});
};
\(WEB_MESSAGE_LISTENER_JS_SOURCE)
"""
let PLATFORM_READY_JS_SOURCE = "window.dispatchEvent(new Event('flutterInAppWebViewPlatformReady'));";

View File

@ -0,0 +1,10 @@
//
// WebMessageChannelJS.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 10/03/21.
//
import Foundation
let WEB_MESSAGE_CHANNELS_VARIABLE_NAME = "window.\(JAVASCRIPT_BRIDGE_NAME)._webMessageChannels"

View File

@ -0,0 +1,112 @@
//
// WebMessageListenerJS.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 10/03/21.
//
import Foundation
let WEB_MESSAGE_LISTENER_JS_SOURCE = """
function FlutterInAppWebViewWebMessageListener(jsObjectName) {
this.jsObjectName = jsObjectName;
this.listeners = [];
this.onmessage = null;
}
FlutterInAppWebViewWebMessageListener.prototype.postMessage = function(message) {
window.webkit.messageHandlers['onWebMessageListenerPostMessageReceived'].postMessage({jsObjectName: this.jsObjectName, message: message});
};
FlutterInAppWebViewWebMessageListener.prototype.addEventListener = function(type, listener) {
if (listener == null) {
return;
}
this.listeners.push(listener);
};
FlutterInAppWebViewWebMessageListener.prototype.removeEventListener = function(type, listener) {
if (listener == null) {
return;
}
var index = this.listeners.indexOf(listener);
if (index >= 0) {
this.listeners.splice(index, 1);
}
};
window.\(JAVASCRIPT_BRIDGE_NAME)._normalizeIPv6 = function(ip_string) {
// replace ipv4 address if any
var ipv4 = ip_string.match(/(.*:)([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$)/);
if (ipv4) {
var ip_string = ipv4[1];
ipv4 = ipv4[2].match(/[0-9]+/g);
for (var i = 0;i < 4;i ++) {
var byte = parseInt(ipv4[i],10);
ipv4[i] = ("0" + byte.toString(16)).substr(-2);
}
ip_string += ipv4[0] + ipv4[1] + ':' + ipv4[2] + ipv4[3];
}
// take care of leading and trailing ::
ip_string = ip_string.replace(/^:|:$/g, '');
var ipv6 = ip_string.split(':');
for (var i = 0; i < ipv6.length; i ++) {
var hex = ipv6[i];
if (hex != "") {
// normalize leading zeros
ipv6[i] = ("0000" + hex).substr(-4);
}
else {
// normalize grouped zeros ::
hex = [];
for (var j = ipv6.length; j <= 8; j ++) {
hex.push('0000');
}
ipv6[i] = hex.join(':');
}
}
return ipv6.join(':');
}
window.\(JAVASCRIPT_BRIDGE_NAME)._isOriginAllowed = function(allowedOriginRules, scheme, host, port) {
for (var rule of allowedOriginRules) {
if (rule === "*") {
return true;
}
if (scheme == null || scheme === "") {
continue;
}
if ((scheme == null || scheme === "") && (host == null || host === "") && (port === 0 || port === "" || port == null)) {
continue;
}
var rulePort = rule.port == null || rule.port === 0 ? (rule.scheme == "https" ? 443 : 80) : rule.port;
var currentPort = port === 0 || port === "" || port == null ? (scheme == "https" ? 443 : 80) : port;
var IPv6 = null;
if (rule.host != null && rule.host[0] === "[") {
try {
IPv6 = normalizeIPv6(rule.host.substring(1, rule.host.length - 1));
} catch {}
}
var hostIPv6 = null;
try {
hostIPv6 = normalizeIPv6(host);
} catch {}
var schemeAllowed = scheme == rule.scheme;
var hostAllowed = rule.host == null ||
rule.host === "" ||
host === rule.host ||
(rule.host[0] === "*" && host != null && host.indexOf(rule.host.split("*")[1]) >= 0) ||
(hostIPv6 != null && IPv6 != null && hostIPv6 === IPv6);
var portAllowed = rulePort === currentPort
if (schemeAllowed && hostAllowed && portAllowed) {
return true;
}
}
return false
}
"""

View File

@ -0,0 +1,27 @@
//
// WebMessage.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 10/03/21.
//
import Foundation
public class WebMessage : NSObject {
var data: String?
var ports: [WebMessagePort]?
public init(data: String?, ports: [WebMessagePort]?) {
super.init()
self.data = data
self.ports = ports
}
public func dispose() {
ports?.removeAll()
}
deinit {
print("WebMessage - dealloc")
}
}

View File

@ -0,0 +1,150 @@
//
// WebMessageChannel.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 10/03/21.
//
import Foundation
public class WebMessageChannel : FlutterMethodCallDelegate {
var id: String
var channel: FlutterMethodChannel?
var webView: InAppWebView?
var ports: [WebMessagePort] = []
public init(id: String) {
self.id = id
super.init()
self.channel = FlutterMethodChannel(name: "com.pichillilorenzo/flutter_inappwebview_web_message_channel_" + id,
binaryMessenger: SwiftFlutterPlugin.instance!.registrar!.messenger())
self.channel?.setMethodCallHandler(self.handle)
self.ports = [
WebMessagePort(name: "port1", webMessageChannel: self),
WebMessagePort(name: "port2", webMessageChannel: self)
]
}
public func initJsInstance(webView: InAppWebView, completionHandler: ((WebMessageChannel) -> Void)? = nil) {
self.webView = webView
if let webView = self.webView {
webView.evaluateJavascript(source: """
(function() {
\(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(id)"] = new MessageChannel();
})();
""") { (_) in
completionHandler?(self)
}
} else {
completionHandler?(self)
}
}
public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
let arguments = call.arguments as? NSDictionary
switch call.method {
case "setWebMessageCallback":
if let _ = webView, ports.count > 0 {
let index = arguments!["index"] as! Int
let port = ports[index]
do {
try port.setWebMessageCallback { (_) in
result(true)
}
} catch let error as NSError {
result(FlutterError(code: "WebMessageChannel", message: error.domain, details: nil))
}
} else {
result(true)
}
break
case "postMessage":
if let webView = webView, ports.count > 0 {
let index = arguments!["index"] as! Int
let port = ports[index]
let message = arguments!["message"] as! [String: Any?]
var webMessagePorts: [WebMessagePort] = []
let portsMap = message["ports"] as? [[String: Any?]]
if let portsMap = portsMap {
for portMap in portsMap {
let webMessageChannelId = portMap["webMessageChannelId"] as! String
let index = portMap["index"] as! Int
if let webMessageChannel = webView.webMessageChannels[webMessageChannelId] {
webMessagePorts.append(webMessageChannel.ports[index])
}
}
}
let webMessage = WebMessage(data: message["data"] as? String, ports: webMessagePorts)
do {
try port.postMessage(message: webMessage) { (_) in
result(true)
}
} catch let error as NSError {
result(FlutterError(code: "WebMessageChannel", message: error.domain, details: nil))
}
} else {
result(true)
}
break
case "close":
if let _ = webView, ports.count > 0 {
let index = arguments!["index"] as! Int
let port = ports[index]
do {
try port.close { (_) in
result(true)
}
} catch let error as NSError {
result(FlutterError(code: "WebMessageChannel", message: error.domain, details: nil))
}
} else {
result(true)
}
break
default:
result(FlutterMethodNotImplemented)
break
}
}
public func onMessage(index: Int64, message: String?) {
let arguments: [String:Any?] = [
"index": index,
"message": message
]
channel?.invokeMethod("onMessage", arguments: arguments)
}
public func toMap () -> [String:Any?] {
return [
"id": id
]
}
public func dispose() {
channel?.setMethodCallHandler(nil)
for port in ports {
port.dispose()
}
ports.removeAll()
webView?.evaluateJavascript(source: """
(function() {
var webMessageChannel = \(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(id)"];
if (webMessageChannel != null) {
webMessageChannel.port1.close();
webMessageChannel.port2.close();
delete \(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(id)"];
}
})();
""")
channel = nil
webView = nil
}
deinit {
print("WebMessageChannel - dealloc")
}
}

View File

@ -0,0 +1,226 @@
//
// WebMessageListener.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 10/03/21.
//
import Foundation
import WebKit
public class WebMessageListener : FlutterMethodCallDelegate {
var jsObjectName: String
var allowedOriginRules: Set<String>
var channel: FlutterMethodChannel?
var webView: InAppWebView?
public init(jsObjectName: String, allowedOriginRules: Set<String>) {
self.jsObjectName = jsObjectName
self.allowedOriginRules = allowedOriginRules
super.init()
self.channel = FlutterMethodChannel(name: "com.pichillilorenzo/flutter_inappwebview_web_message_listener_" + self.jsObjectName,
binaryMessenger: SwiftFlutterPlugin.instance!.registrar!.messenger())
self.channel?.setMethodCallHandler(self.handle)
}
public func assertOriginRulesValid() throws {
for (index, originRule) in allowedOriginRules.enumerated() {
if originRule.isEmpty {
throw NSError(domain: "allowedOriginRules[\(index)] is empty", code: 0)
}
if originRule == "*" {
continue
}
if let url = URL(string: originRule) {
guard let scheme = url.scheme else {
throw NSError(domain: "allowedOriginRules \(originRule) is invalid", code: 0)
}
if scheme == "http" || scheme == "https", url.host == nil || url.host!.isEmpty {
throw NSError(domain: "allowedOriginRules \(originRule) is invalid", code: 0)
}
if scheme != "http", scheme != "https", url.host != nil || url.port != nil {
throw NSError(domain: "allowedOriginRules \(originRule) is invalid", code: 0)
}
if url.host == nil || url.host!.isEmpty, url.port != nil {
throw NSError(domain: "allowedOriginRules \(originRule) is invalid", code: 0)
}
if !url.path.isEmpty {
throw NSError(domain: "allowedOriginRules \(originRule) is invalid", code: 0)
}
if let hostname = url.host {
if let firstIndex = hostname.firstIndex(of: "*") {
let distance = hostname.distance(from: hostname.startIndex, to: firstIndex)
if distance != 0 || (distance == 0 && hostname.prefix(2) != "*.") {
throw NSError(domain: "allowedOriginRules \(originRule) is invalid", code: 0)
}
}
if hostname.hasPrefix("[") {
if !hostname.hasSuffix("]") {
throw NSError(domain: "allowedOriginRules \(originRule) is invalid", code: 0)
}
let fromIndex = hostname.index(hostname.startIndex, offsetBy: 1)
let toIndex = hostname.index(hostname.startIndex, offsetBy: hostname.count - 1)
let indexRange = Range<String.Index>(uncheckedBounds: (lower: fromIndex, upper: toIndex))
let ipv6 = String(hostname[indexRange])
if !Util.isIPv6(address: ipv6) {
throw NSError(domain: "allowedOriginRules \(originRule) is invalid", code: 0)
}
}
}
} else {
throw NSError(domain: "allowedOriginRules \(originRule) is invalid", code: 0)
}
}
}
public func initJsInstance(webView: InAppWebView) {
self.webView = webView
if let webView = self.webView {
let jsObjectNameEscaped = jsObjectName.replacingOccurrences(of: "\'", with: "\\'")
let allowedOriginRulesString = allowedOriginRules.map { (allowedOriginRule) -> String in
if allowedOriginRule == "*" {
return "'*'"
}
let rule = URL(string: allowedOriginRule)!
return """
{scheme: '\(rule.scheme!)', host: '\(rule.host?.replacingOccurrences(of: "\'", with: "\\'") ?? "null")', port: \(rule.port != nil ? String(rule.port!) : "null")}
"""
}.joined(separator: ", ")
let source = """
(function() {
var allowedOriginRules = [\(allowedOriginRulesString)];
var isPageBlank = window.location.href === "about:blank";
var scheme = !isPageBlank ? window.location.protocol.replace(":", "") : null;
var host = !isPageBlank ? window.location.hostname : null;
var port = !isPageBlank ? window.location.port : null;
if (window.\(JAVASCRIPT_BRIDGE_NAME)._isOriginAllowed(allowedOriginRules, scheme, host, port)) {
window['\(jsObjectNameEscaped)'] = new FlutterInAppWebViewWebMessageListener('\(jsObjectNameEscaped)');
}
})();
"""
webView.configuration.userContentController.addPluginScript(PluginScript(
groupName: "WebMessageListener-" + jsObjectName,
source: source,
injectionTime: .atDocumentStart,
forMainFrameOnly: false,
requiredInAllContentWorlds: false,
messageHandlerNames: ["onWebMessageListenerPostMessageReceived"]
))
webView.configuration.userContentController.sync(scriptMessageHandler: webView)
}
}
public static func fromMap(map: [String:Any?]?) -> WebMessageListener? {
guard let map = map else {
return nil
}
return WebMessageListener(
jsObjectName: map["jsObjectName"] as! String,
allowedOriginRules: Set(map["allowedOriginRules"] as! [String])
)
}
public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
let arguments = call.arguments as? NSDictionary
switch call.method {
case "postMessage":
if let webView = webView {
let jsObjectNameEscaped = jsObjectName.replacingOccurrences(of: "\'", with: "\\'")
let messageEscaped = (arguments!["message"] as! String).replacingOccurrences(of: "\'", with: "\\'")
let source = """
(function() {
var webMessageListener = window['\(jsObjectNameEscaped)'];
if (webMessageListener != null) {
var event = {data: '\(messageEscaped)'};
if (webMessageListener.onmessage != null) {
webMessageListener.onmessage(event);
} else {
for (var listener of webMessageListener.listeners) {
listener(event);
}
}
}
})();
"""
webView.evaluateJavascript(source: source) { (_) in
result(true)
}
} else {
result(true)
}
break
default:
result(FlutterMethodNotImplemented)
break
}
}
public func isOriginAllowed(scheme: String?, host: String?, port: Int?) -> Bool {
for allowedOriginRule in allowedOriginRules {
if allowedOriginRule == "*" {
return true
}
if scheme == nil || scheme!.isEmpty {
continue
}
if scheme == nil || scheme!.isEmpty, host == nil || host!.isEmpty, port == nil || port == 0 {
continue
}
if let rule = URL(string: allowedOriginRule) {
let rulePort = rule.port == nil || rule.port == 0 ? (rule.scheme == "https" ? 443 : 80) : rule.port!
let currentPort = port == nil || port == 0 ? (scheme == "https" ? 443 : 80) : port!
var IPv6: String? = nil
if let hostname = rule.host, hostname.hasPrefix("[") {
let fromIndex = hostname.index(hostname.startIndex, offsetBy: 1)
let toIndex = hostname.index(hostname.startIndex, offsetBy: hostname.count - 1)
let indexRange = Range<String.Index>(uncheckedBounds: (lower: fromIndex, upper: toIndex))
do {
IPv6 = try Util.normalizeIPv6(address: String(hostname[indexRange]))
} catch {}
}
var hostIPv6: String? = nil
if let host = host, Util.isIPv6(address: host) {
do {
hostIPv6 = try Util.normalizeIPv6(address: host)
} catch {}
}
let schemeAllowed = scheme != nil && !scheme!.isEmpty && scheme == rule.scheme
let hostAllowed = rule.host == nil ||
rule.host!.isEmpty ||
host == rule.host ||
(rule.host!.hasPrefix("*") && host != nil && host!.hasSuffix(rule.host!.split(separator: "*", omittingEmptySubsequences: false)[1])) ||
(hostIPv6 != nil && IPv6 != nil && hostIPv6 == IPv6)
let portAllowed = rulePort == currentPort
if schemeAllowed, hostAllowed, portAllowed {
return true
}
}
}
return false
}
public func onPostMessage(message: String?, sourceOrigin: URL?, isMainFrame: Bool) {
let arguments: [String:Any?] = [
"message": message,
"sourceOrigin": sourceOrigin?.absoluteString,
"isMainFrame": isMainFrame
]
channel?.invokeMethod("onPostMessage", arguments: arguments)
}
public func dispose() {
channel?.setMethodCallHandler(nil)
channel = nil
webView = nil
}
deinit {
print("WebMessageListener - dealloc")
}
}

View File

@ -0,0 +1,122 @@
//
// WebMessagePort.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 10/03/21.
//
import Foundation
public class WebMessagePort : NSObject {
var name: String
var webMessageChannel: WebMessageChannel?
var isClosed = false
var isTransferred = false
var isStarted = false
public init(name: String, webMessageChannel: WebMessageChannel) {
self.name = name
super.init()
self.webMessageChannel = webMessageChannel
}
public func setWebMessageCallback(completionHandler: ((Any?) -> Void)? = nil) throws {
if isClosed || isTransferred {
throw NSError(domain: "Port is already closed or transferred", code: 0)
}
self.isStarted = true
if let webMessageChannel = webMessageChannel, let webView = webMessageChannel.webView {
let index = name == "port1" ? 0 : 1
webView.evaluateJavascript(source: """
(function() {
var webMessageChannel = \(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(webMessageChannel.id)"];
if (webMessageChannel != null) {
webMessageChannel.\(self.name).onmessage = function (event) {
window.webkit.messageHandlers["onWebMessagePortMessageReceived"].postMessage({
"webMessageChannelId": "\(webMessageChannel.id)",
"index": \(String(index)),
"message": event.data
});
}
}
})();
""") { (_) in
completionHandler?(nil)
}
} else {
completionHandler?(nil)
}
}
public func postMessage(message: WebMessage, completionHandler: ((Any?) -> Void)? = nil) throws {
if isClosed || isTransferred {
throw NSError(domain: "Port is already closed or transferred", code: 0)
}
if let webMessageChannel = webMessageChannel, let webView = webMessageChannel.webView {
var portsString = "null"
if let ports = message.ports {
var portArrayString: [String] = []
for port in ports {
if port == self {
throw NSError(domain: "Source port cannot be transferred", code: 0)
}
if port.isStarted {
throw NSError(domain: "Port is already started", code: 0)
}
if port.isClosed || port.isTransferred {
throw NSError(domain: "Port is already closed or transferred", code: 0)
}
port.isTransferred = true
portArrayString.append("\(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)['\(port.webMessageChannel!.id)'].\(port.name)")
}
portsString = "[" + portArrayString.joined(separator: ", ") + "]"
}
let data = message.data?.replacingOccurrences(of: "\'", with: "\\'") ?? "null"
let source = """
(function() {
var webMessageChannel = \(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(webMessageChannel.id)"];
if (webMessageChannel != null) {
webMessageChannel.\(self.name).postMessage('\(data)', \(portsString));
}
})();
"""
webView.evaluateJavascript(source: source) { (_) in
completionHandler?(nil)
}
} else {
completionHandler?(nil)
}
message.dispose()
}
public func close(completionHandler: ((Any?) -> Void)? = nil) throws {
if isTransferred {
throw NSError(domain: "Port is already transferred", code: 0)
}
isClosed = true
if let webMessageChannel = webMessageChannel, let webView = webMessageChannel.webView {
let source = """
(function() {
var webMessageChannel = \(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(webMessageChannel.id)"];
if (webMessageChannel != null) {
webMessageChannel.\(self.name).close();
}
})();
"""
webView.evaluateJavascript(source: source) { (_) in
completionHandler?(nil)
}
} else {
completionHandler?(nil)
}
}
public func dispose() {
isClosed = true
webMessageChannel = nil
}
deinit {
print("WebMessagePort - dealloc")
}
}

View File

@ -149,4 +149,78 @@ public class Util {
return "NORMAL"
}
}
public static func isIPv4(address: String) -> Bool {
var sin = sockaddr_in()
return address.withCString({ cstring in inet_pton(AF_INET, cstring, &sin.sin_addr) }) == 1
}
public static func isIPv6(address: String) -> Bool {
var sin6 = sockaddr_in6()
return address.withCString({ cstring in inet_pton(AF_INET6, cstring, &sin6.sin6_addr) }) == 1
}
public static func isIpAddress(address: String) -> Bool {
return Util.isIPv6(address: address) || Util.isIPv4(address: address)
}
public static func normalizeIPv6(address: String) throws -> String {
if !Util.isIPv6(address: address) {
throw NSError(domain: "Invalid address: \(address)", code: 0)
}
var ipString = address
// replace ipv4 address if any
let ipv4Regex = try! NSRegularExpression(pattern: "(.*:)([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$)")
if let match = ipv4Regex.firstMatch(in: address, options: [], range: NSRange(location: 0, length: address.utf16.count)) {
if let ipv6PartRange = Range(match.range(at: 1), in: address) {
ipString = String(address[ipv6PartRange])
}
if let ipv4Range = Range(match.range(at: 2), in: address) {
let ipv4 = address[ipv4Range]
let ipv4Splitted = ipv4.split(separator: ".")
var ipv4Converted = Array(repeating: "0000", count: 4)
for i in 0...3 {
let byte = Int(ipv4Splitted[i])!
let hex = ("0" + String(byte, radix: 16))
var offset = hex.count - 3
offset = offset < 0 ? 0 : offset
let fromIndex = hex.index(hex.startIndex, offsetBy: offset)
let toIndex = hex.index(hex.startIndex, offsetBy: hex.count - 1)
let indexRange = Range<String.Index>(uncheckedBounds: (lower: fromIndex, upper: toIndex))
ipv4Converted[i] = String(hex[indexRange])
}
ipString += ipv4Converted[0] + ipv4Converted[1] + ":" + ipv4Converted[2] + ipv4Converted[3]
}
}
// take care of leading and trailing ::
let regex = try! NSRegularExpression(pattern: "^:|:$")
ipString = regex.stringByReplacingMatches(in: ipString, options: [], range: NSRange(location: 0, length: ipString.count), withTemplate: "")
let ipv6 = ipString.split(separator: ":", omittingEmptySubsequences: false)
var fullIPv6 = Array(repeating: "0000", count: ipv6.count)
for (i, hex) in ipv6.enumerated() {
if !hex.isEmpty {
// normalize leading zeros
let hexString = String("0000" + hex)
var offset = hexString.count - 5
offset = offset < 0 ? 0 : offset
let fromIndex = hexString.index(hexString.startIndex, offsetBy: offset)
let toIndex = hexString.index(hexString.startIndex, offsetBy: hexString.count - 1)
let indexRange = Range<String.Index>(uncheckedBounds: (lower: fromIndex, upper: toIndex))
fullIPv6[i] = String(hexString[indexRange])
} else {
// normalize grouped zeros ::
var zeros: [String] = []
for j in ipv6.count...8 {
zeros.append("0000")
}
fullIPv6[i] = zeros.joined(separator: ":")
}
}
return fullIPv6.joined(separator: ":")
}
}

View File

@ -45,7 +45,7 @@ class ChromeSafariBrowser {
const MethodChannel('com.pichillilorenzo/flutter_chromesafaribrowser');
ChromeSafariBrowser() {
id = ViewIdGenerator.generateId();
id = IdGenerator.generate();
this._channel =
MethodChannel('com.pichillilorenzo/flutter_chromesafaribrowser_$id');
this._channel.setMethodCallHandler(handleMethod);

View File

@ -66,7 +66,7 @@ class InAppBrowser {
///
InAppBrowser({this.windowId, this.initialUserScripts}) {
id = ViewIdGenerator.generateId();
id = IdGenerator.generate();
this._channel =
MethodChannel('com.pichillilorenzo/flutter_inappbrowser_$id');
this._channel.setMethodCallHandler(handleMethod);

View File

@ -176,7 +176,7 @@ class AndroidInAppWebViewController {
///has loaded WebView will be killed.
///The next time the app starts and loads WebView it will use the new WebView package instead.
///
///**NOTE**: available only on Android 26+.
///**NOTE**: available only on Android 21+.
///
///**Official Android API**: https://developer.android.com/reference/androidx/webkit/WebViewCompat#getCurrentWebViewPackage(android.content.Context)
static Future<AndroidWebViewPackageInfo?> getCurrentWebViewPackage() async {

View File

@ -91,7 +91,7 @@ class HeadlessInAppWebView implements WebView {
this.contextMenu,
this.initialUserScripts,
this.pullToRefreshController}) {
id = ViewIdGenerator.generateId();
id = IdGenerator.generate();
webViewController = new InAppWebViewController(id, this);
}

View File

@ -18,6 +18,8 @@ import '../types.dart';
import '../in_app_browser/in_app_browser.dart';
import '../web_storage/web_storage.dart';
import '../util.dart';
import '../web_message/web_message_channel.dart';
import '../web_message/web_message_listener.dart';
import 'headless_in_app_webview.dart';
import 'in_app_webview.dart';
@ -51,6 +53,7 @@ class InAppWebViewController {
Map<String, JavaScriptHandlerCallback> javaScriptHandlersMap =
HashMap<String, JavaScriptHandlerCallback>();
List<UserScript> _userScripts = [];
Set<String> _webMessageListenerObjNames = Set();
// ignore: unused_field
dynamic _id;
@ -1455,7 +1458,7 @@ class InAppWebViewController {
void addJavaScriptHandler(
{required String handlerName,
required JavaScriptHandlerCallback callback}) {
assert(!_JAVASCRIPT_HANDLER_FORBIDDEN_NAMES.contains(handlerName));
assert(!_JAVASCRIPT_HANDLER_FORBIDDEN_NAMES.contains(handlerName), '"$handlerName" is a forbidden name!');
this.javaScriptHandlersMap[handlerName] = (callback);
}
@ -1467,7 +1470,7 @@ class InAppWebViewController {
return this.javaScriptHandlersMap.remove(handlerName);
}
///Takes a screenshot (in PNG format) of the WebView's visible viewport and returns a [Uint8List]. Returns `null` if it wasn't be able to take it.
///Takes a screenshot of the WebView's visible viewport and returns a [Uint8List]. Returns `null` if it wasn't be able to take it.
///
///[screenshotConfiguration] represents the configuration data to use when generating an image from a web views contents.
///
@ -2087,6 +2090,32 @@ class InAppWebViewController {
return await _channel.invokeMethod('isSecureContext', args);
}
Future<WebMessageChannel?> createWebMessageChannel() async {
Map<String, dynamic> args = <String, dynamic>{};
Map<String, dynamic>? result = (await _channel.invokeMethod('createWebMessageChannel', args))
?.cast<String, dynamic>();
return WebMessageChannel.fromMap(result);
}
Future<void> postWebMessage({required WebMessage message, Uri? targetOrigin}) async {
if (targetOrigin == null) {
targetOrigin = Uri.parse("");
}
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('message', () => message.toMap());
args.putIfAbsent('targetOrigin', () => targetOrigin.toString());
await _channel.invokeMethod('postWebMessage', args);
}
Future<void> addWebMessageListener(WebMessageListener webMessageListener) async {
assert(!_webMessageListenerObjNames.contains(webMessageListener.jsObjectName), "jsObjectName ${webMessageListener.jsObjectName} was already added.");
_webMessageListenerObjNames.add(webMessageListener.jsObjectName);
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('webMessageListener', () => webMessageListener.toMap());
await _channel.invokeMethod('addWebMessageListener', args);
}
///Gets the default user agent.
///
///**Official Android API**: https://developer.android.com/reference/android/webkit/WebSettings#getDefaultUserAgent(android.content.Context)

View File

@ -15,3 +15,4 @@ export 'content_blocker.dart';
export 'http_auth_credentials_database.dart';
export 'context_menu.dart';
export 'pull_to_refresh/main.dart';
export 'web_message/main.dart';

View File

@ -6147,8 +6147,10 @@ class IOSURLResponse {
///The name of the text encoding provided by the responses originating source.
String? textEncodingName;
///All HTTP header fields of the response.
Map<String, String>? headers;
///The responses HTTP status code.
int? statusCode;
IOSURLResponse(

View File

@ -3,7 +3,7 @@ import 'dart:typed_data';
import 'package:flutter/material.dart';
class ViewIdGenerator {
class IdGenerator {
static int _count = 0;
/// Math.Random()-based RNG. All platforms, fast, not cryptographically strong. Optional Seed passable.
@ -31,7 +31,7 @@ class ViewIdGenerator {
return b;
}
static String generateId() {
static String generate() {
_count++;
return _count.toString() + cryptoRNG().map((e) => e.toString()).join('');
}

View File

@ -0,0 +1,2 @@
export 'web_message_channel.dart';
export 'web_message_listener.dart';

View File

@ -0,0 +1,115 @@
import 'package:flutter/services.dart';
class WebMessageChannel {
String id;
WebMessagePort port1;
WebMessagePort port2;
late MethodChannel _channel;
WebMessageChannel({required this.id, required this.port1, required this.port2}) {
this._channel = MethodChannel(
'com.pichillilorenzo/flutter_inappwebview_web_message_channel_$id');
this._channel.setMethodCallHandler(handleMethod);
}
static WebMessageChannel? fromMap(Map<String, dynamic>? map) {
if (map == null) {
return null;
}
var webMessageChannel = WebMessageChannel(
id: map["id"],
port1: WebMessagePort(index: 0),
port2: WebMessagePort(index: 1)
);
webMessageChannel.port1._webMessageChannel = webMessageChannel;
webMessageChannel.port2._webMessageChannel = webMessageChannel;
return webMessageChannel;
}
Future<dynamic> handleMethod(MethodCall call) async {
switch (call.method) {
case "onMessage":
int index = call.arguments["index"];
var port = index == 0 ? this.port1 : this.port2;
if (port._onMessage != null) {
String? message = call.arguments["message"];
port._onMessage!(message);
}
break;
default:
throw UnimplementedError("Unimplemented ${call.method} method");
}
return null;
}
}
class WebMessagePort {
late final int _index;
Function(String? message)? _onMessage;
late WebMessageChannel _webMessageChannel;
WebMessagePort({required int index}) {
this._index = index;
}
Future<void> setWebMessageCallback(Function(String? message)? onMessage) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('index', () => this._index);
await _webMessageChannel._channel.invokeMethod('setWebMessageCallback', args);
this._onMessage = onMessage;
}
Future<void> postMessage(WebMessage message) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('index', () => this._index);
args.putIfAbsent('message', () => message.toMap());
await _webMessageChannel._channel.invokeMethod('postMessage', args);
}
Future<void> close() async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('index', () => this._index);
await _webMessageChannel._channel.invokeMethod('close', args);
}
Map<String, dynamic> toMap() {
return {
"index": this._index,
"webMessageChannelId": this._webMessageChannel.id
};
}
Map<String, dynamic> toJson() {
return this.toMap();
}
@override
String toString() {
return toMap().toString();
}
}
class WebMessage {
String? data;
List<WebMessagePort>? ports;
WebMessage({this.data, this.ports});
Map<String, dynamic> toMap() {
return {
"data": this.data,
"ports": this.ports?.map((e) => e.toMap()).toList(),
};
}
Map<String, dynamic> toJson() {
return this.toMap();
}
@override
String toString() {
return toMap().toString();
}
}

View File

@ -0,0 +1,67 @@
import 'package:flutter/services.dart';
class WebMessageListener {
String jsObjectName;
late Set<String> allowedOriginRules;
JavaScriptReplyProxy? _replyProxy;
Function(String? message, Uri? sourceOrigin, bool isMainFrame, JavaScriptReplyProxy replyProxy)? onPostMessage;
late MethodChannel _channel;
WebMessageListener({required this.jsObjectName, Set<String>? allowedOriginRules, this.onPostMessage}) {
this.allowedOriginRules = allowedOriginRules != null ? allowedOriginRules : Set.from(["*"]);
assert(!this.allowedOriginRules.contains(""), "allowedOriginRules cannot contain empty strings");
this._channel = MethodChannel(
'com.pichillilorenzo/flutter_inappwebview_web_message_listener_$jsObjectName');
this._channel.setMethodCallHandler(handleMethod);
}
Future<dynamic> handleMethod(MethodCall call) async {
switch (call.method) {
case "onPostMessage":
if (_replyProxy == null) {
_replyProxy = new JavaScriptReplyProxy(this);
}
if (onPostMessage != null) {
String? message = call.arguments["message"];
Uri? sourceOrigin = call.arguments["sourceOrigin"] != null ? Uri.parse(call.arguments["sourceOrigin"]) : null;
bool isMainFrame = call.arguments["isMainFrame"];
onPostMessage!(message, sourceOrigin, isMainFrame, _replyProxy!);
}
break;
default:
throw UnimplementedError("Unimplemented ${call.method} method");
}
return null;
}
Map<String, dynamic> toMap() {
return {
"jsObjectName": jsObjectName,
"allowedOriginRules": allowedOriginRules.toList(),
};
}
Map<String, dynamic> toJson() {
return this.toMap();
}
@override
String toString() {
return toMap().toString();
}
}
class JavaScriptReplyProxy {
late WebMessageListener _webMessageListener;
JavaScriptReplyProxy(WebMessageListener webMessageListener) {
this._webMessageListener = webMessageListener;
}
Future<void> postMessage(String message) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('message', () => message);
await _webMessageListener._channel.invokeMethod('postMessage', args);
}
}

View File

@ -10,10 +10,10 @@ const appAuthBasic = express()
const fs = require('fs')
const path = require('path')
var options = {
key: fs.readFileSync('server-key.pem'),
cert: fs.readFileSync('server-crt.pem'),
ca: fs.readFileSync('ca-crt.pem'),
var options = {
key: fs.readFileSync('server-key.pem'),
cert: fs.readFileSync('server-crt.pem'),
ca: fs.readFileSync('ca-crt.pem'),
requestCert: true,
rejectUnauthorized: false
};

View File

@ -1,6 +1,6 @@
name: flutter_inappwebview
description: A Flutter plugin that allows you to add an inline webview, to use an headless webview, and to open an in-app browser window.
version: 5.1.0+4
version: 5.2.0
homepage: https://github.com/pichillilorenzo/flutter_inappwebview
environment: