diff --git a/packages/webview_flutter_lwe/CHANGELOG.md b/packages/webview_flutter_lwe/CHANGELOG.md index 50066e348..dcfdf3161 100644 --- a/packages/webview_flutter_lwe/CHANGELOG.md +++ b/packages/webview_flutter_lwe/CHANGELOG.md @@ -1,3 +1,11 @@ +## 0.3.6 +* Update minimum Flutter and Dart version to 3.24 and 3.5. +* Update the example main app. +* Update webview_flutter to 4.10.0. +* Update webview_flutter_platform_interface to 2.10.0. +* Add removeJavaScriptChannel method call. +* Fix deadlock issue caused by calling setBackgroundColor() after the WebViewController is created. + ## 0.3.5 * Fix an issue where platform channel isn't called on the main thread. diff --git a/packages/webview_flutter_lwe/README.md b/packages/webview_flutter_lwe/README.md index 29b66df3f..c715facd5 100644 --- a/packages/webview_flutter_lwe/README.md +++ b/packages/webview_flutter_lwe/README.md @@ -20,8 +20,8 @@ This package is not an _endorsed_ implementation of `webview_flutter`. Therefore ```yaml dependencies: - webview_flutter: ^4.4.2 - webview_flutter_lwe: ^0.3.5 + webview_flutter: ^4.10.0 + webview_flutter_lwe: ^0.3.6 ``` ## Example diff --git a/packages/webview_flutter_lwe/example/lib/main.dart b/packages/webview_flutter_lwe/example/lib/main.dart index 5c20f15b5..a3507b23f 100644 --- a/packages/webview_flutter_lwe/example/lib/main.dart +++ b/packages/webview_flutter_lwe/example/lib/main.dart @@ -7,8 +7,8 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; -import 'dart:typed_data'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:path_provider/path_provider.dart'; import 'package:webview_flutter/webview_flutter.dart'; @@ -108,13 +108,21 @@ class WebViewExample extends StatefulWidget { } class _WebViewExampleState extends State { - final WebViewController _controller = WebViewController(); + late final WebViewController _controller; @override void initState() { super.initState(); - _controller + // #docregion platform_features + late final PlatformWebViewControllerCreationParams params; + params = const PlatformWebViewControllerCreationParams(); + + final WebViewController controller = + WebViewController.fromPlatformCreationParams(params); + // #enddocregion platform_features + + controller ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setBackgroundColor(const Color(0x00000000)) ..setNavigationDelegate( @@ -138,7 +146,6 @@ Page resource error: '''); }, onNavigationRequest: (NavigationRequest request) { - debugPrint('On onNavigationReques'); if (request.url.startsWith('https://www.youtube.com/')) { debugPrint('blocking navigation to ${request.url}'); return NavigationDecision.prevent; @@ -146,6 +153,18 @@ Page resource error: debugPrint('allowing navigation to ${request.url}'); return NavigationDecision.navigate; }, + // Note: onHttpError is not supproted by LweWebview. + // onHttpError: (HttpResponseError error) { + // debugPrint('Error occurred on page: ${error.response?.statusCode}'); + // }, + // Note: onUrlChange is not supproted by LweWebview. + // onUrlChange: (UrlChange change) { + // debugPrint('url change to ${change.url}'); + // }, + // Note: onHttpAuthRequest is not supproted by LweWebview. + // onHttpAuthRequest: (HttpAuthRequest request) { + // openDialog(request); + // }, ), ) ..addJavaScriptChannel( @@ -157,6 +176,13 @@ Page resource error: }, ) ..loadRequest(Uri.parse('https://flutter.dev')); + + // setBackgroundColor is not currently supported on macOS. + if (kIsWeb || !Platform.isMacOS) { + controller.setBackgroundColor(const Color(0x80000000)); + } + + _controller = controller; } @override @@ -189,6 +215,62 @@ Page resource error: child: const Icon(Icons.favorite), ); } + + Future openDialog(HttpAuthRequest httpRequest) async { + final TextEditingController usernameTextController = + TextEditingController(); + final TextEditingController passwordTextController = + TextEditingController(); + + return showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return AlertDialog( + title: Text('${httpRequest.host}: ${httpRequest.realm ?? '-'}'), + content: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextField( + decoration: const InputDecoration(labelText: 'Username'), + autofocus: true, + controller: usernameTextController, + ), + TextField( + decoration: const InputDecoration(labelText: 'Password'), + controller: passwordTextController, + ), + ], + ), + ), + actions: [ + // Explicitly cancel the request on iOS as the OS does not emit new + // requests when a previous request is pending. + TextButton( + onPressed: () { + httpRequest.onCancel(); + Navigator.of(context).pop(); + }, + child: const Text('Cancel'), + ), + TextButton( + onPressed: () { + httpRequest.onProceed( + WebViewCredential( + user: usernameTextController.text, + password: passwordTextController.text, + ), + ); + Navigator.of(context).pop(); + }, + child: const Text('Authenticate'), + ), + ], + ); + }, + ); + } } enum MenuOptions { @@ -206,6 +288,7 @@ enum MenuOptions { transparentBackground, setCookie, logExample, + basicAuthentication, } class SampleMenu extends StatelessWidget { @@ -251,6 +334,8 @@ class SampleMenu extends StatelessWidget { _onSetCookie(); case MenuOptions.logExample: _onLogExample(); + case MenuOptions.basicAuthentication: + _promptForUrl(context); } }, itemBuilder: (BuildContext context) => >[ @@ -311,6 +396,10 @@ class SampleMenu extends StatelessWidget { value: MenuOptions.logExample, child: Text('Log example'), ), + const PopupMenuItem( + value: MenuOptions.basicAuthentication, + child: Text('Basic Authentication Example'), + ), ], ); } @@ -465,6 +554,38 @@ class SampleMenu extends StatelessWidget { return webViewController.loadHtmlString(kLogExamplePage); } + + Future _promptForUrl(BuildContext context) { + final TextEditingController urlTextController = TextEditingController(); + + return showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Input URL to visit'), + content: TextField( + decoration: const InputDecoration(labelText: 'URL'), + autofocus: true, + controller: urlTextController, + ), + actions: [ + TextButton( + onPressed: () { + if (urlTextController.text.isNotEmpty) { + final Uri? uri = Uri.tryParse(urlTextController.text); + if (uri != null && uri.scheme.isNotEmpty) { + webViewController.loadRequest(uri); + Navigator.pop(context); + } + } + }, + child: const Text('Visit'), + ), + ], + ); + }, + ); + } } class NavigationControls extends StatelessWidget { diff --git a/packages/webview_flutter_lwe/example/pubspec.yaml b/packages/webview_flutter_lwe/example/pubspec.yaml index 058680a4e..f4690ad55 100644 --- a/packages/webview_flutter_lwe/example/pubspec.yaml +++ b/packages/webview_flutter_lwe/example/pubspec.yaml @@ -3,8 +3,8 @@ description: Demonstrates how to use the webview_flutter_lwe plugin. publish_to: "none" environment: - sdk: ">=3.1.0 <4.0.0" - flutter: ">=3.13.0" + sdk: ^3.5.0 + flutter: ">=3.24.0" dependencies: flutter: @@ -12,7 +12,7 @@ dependencies: path_provider: ^2.0.7 path_provider_tizen: path: ../../path_provider/ - webview_flutter: ^4.4.2 + webview_flutter: ^4.10.0 webview_flutter_lwe: path: ../ diff --git a/packages/webview_flutter_lwe/lib/src/lwe_webview.dart b/packages/webview_flutter_lwe/lib/src/lwe_webview.dart index fe3747a00..cdf139652 100644 --- a/packages/webview_flutter_lwe/lib/src/lwe_webview.dart +++ b/packages/webview_flutter_lwe/lib/src/lwe_webview.dart @@ -169,12 +169,35 @@ class LweWebView { /// Adds a new JavaScript channel to the set of enabled channels. Future addJavaScriptChannel( JavaScriptChannelParams javaScriptChannelParams) { + // When JavaScript channel with the same name exists make sure to remove it + // before registering the new channel. + if (_javaScriptChannelParams.containsKey(javaScriptChannelParams.name)) { + _invokeChannelMethod( + 'removeJavaScriptChannel', javaScriptChannelParams.name); + } + _javaScriptChannelParams[javaScriptChannelParams.name] = javaScriptChannelParams; + return _invokeChannelMethod( 'addJavaScriptChannel', javaScriptChannelParams.name); } + /// Removes the JavaScript channel with the matching name from the set of + /// enabled channels. + Future removeJavaScriptChannel(String javaScriptChannelName) async { + final JavaScriptChannelParams? params = + _javaScriptChannelParams[javaScriptChannelName]; + + if (params == null) { + return; + } + + _javaScriptChannelParams.remove(javaScriptChannelName); + return _invokeChannelMethod( + 'removeJavaScriptChannel', javaScriptChannelName); + } + /// Runs the given JavaScript in the context of the current page. Future runJavaScript(String javaScript) => _invokeChannelMethod('runJavaScript', javaScript); diff --git a/packages/webview_flutter_lwe/lib/src/lwe_webview_controller.dart b/packages/webview_flutter_lwe/lib/src/lwe_webview_controller.dart index dd0074034..cbda3d7df 100644 --- a/packages/webview_flutter_lwe/lib/src/lwe_webview_controller.dart +++ b/packages/webview_flutter_lwe/lib/src/lwe_webview_controller.dart @@ -62,14 +62,6 @@ class LweWebViewController extends PlatformWebViewController { 'LoadRequestParams#uri is required to have a scheme.'); } - if (params.headers.isNotEmpty) { - throw ArgumentError('LoadRequestParams#headers is not supported.'); - } - - if (params.body != null) { - throw ArgumentError('LoadRequestParams#body is not supported.'); - } - switch (params.method) { case LoadRequestMethod.get: return _webview.loadRequest(params.uri.toString()); @@ -132,6 +124,13 @@ class LweWebViewController extends PlatformWebViewController { @override Future getScrollPosition() => _webview.getScrollPosition(); + @override + Future enableZoom(bool enabled) { + throw UnimplementedError( + 'This version of `LweWebViewController` currently has no ' + 'implementation of `enableZoom`.'); + } + @override Future setBackgroundColor(Color color) => _webview.setBackgroundColor(color); @@ -148,6 +147,10 @@ class LweWebViewController extends PlatformWebViewController { JavaScriptChannelParams javaScriptChannelParams) => _webview.addJavaScriptChannel(javaScriptChannelParams); + @override + Future removeJavaScriptChannel(String javaScriptChannelName) => + _webview.removeJavaScriptChannel(javaScriptChannelName); + @override Future runJavaScript(String javaScript) => _webview.runJavaScript(javaScript); @@ -160,6 +163,15 @@ class LweWebViewController extends PlatformWebViewController { Future setUserAgent(String? userAgent) => _webview.setUserAgent(userAgent); + @override + Future setOnScrollPositionChange( + void Function(ScrollPositionChange scrollPositionChange)? + onScrollPositionChange) async { + throw UnimplementedError( + 'This version of `LweWebViewController` currently has no ' + 'implementation of `setOnScrollPositionChange`.'); + } + @override Future getUserAgent() => _webview.getUserAgent(); @@ -186,6 +198,33 @@ class LweWebViewController extends PlatformWebViewController { 'This version of `LweWebViewController` currently has no ' 'implementation of `setOnConsoleMessage`.'); } + + @override + Future setOnJavaScriptAlertDialog( + Future Function(JavaScriptAlertDialogRequest request) + onJavaScriptAlertDialog) async { + throw UnimplementedError( + 'This version of `LweWebViewController` currently has no ' + 'implementation of `setOnJavaScriptAlertDialog`.'); + } + + @override + Future setOnJavaScriptConfirmDialog( + Future Function(JavaScriptConfirmDialogRequest request) + onJavaScriptConfirmDialog) async { + throw UnimplementedError( + 'This version of `LweWebViewController` currently has no ' + 'implementation of `setOnJavaScriptConfirmDialog`.'); + } + + @override + Future setOnJavaScriptTextInputDialog( + Future Function(JavaScriptTextInputDialogRequest request) + onJavaScriptTextInputDialog) async { + throw UnimplementedError( + 'This version of `LweWebViewController` currently has no ' + 'implementation of `setOnJavaScriptTextInputDialog`.'); + } } /// An implementation of [PlatformWebViewWidget] with the Lightweight Web Engine. @@ -328,6 +367,7 @@ class LweNavigationDelegate extends PlatformNavigationDelegate { _navigationDelegateChannel.setMethodCallHandler((MethodCall call) async { final Map arguments = (call.arguments as Map).cast(); + switch (call.method) { case 'navigationRequest': return _handleNavigation(arguments['url']! as String, @@ -416,6 +456,15 @@ class LweNavigationDelegate extends PlatformNavigationDelegate { _onPageFinished = onPageFinished; } + @override + Future setOnHttpError( + HttpResponseErrorCallback onHttpError, + ) async { + throw UnimplementedError( + 'This version of `LweNavigationDelegate` currently has no ' + 'implementation for `setOnHttpError`'); + } + @override Future setOnProgress( ProgressCallback onProgress, @@ -432,9 +481,17 @@ class LweNavigationDelegate extends PlatformNavigationDelegate { @override Future setOnUrlChange(UrlChangeCallback onUrlChange) async { - // The current version of LWE does not provide any functionality related to a 'url change'. throw UnimplementedError( 'This version of `LweNavigationDelegate` currently has no ' - 'implementation of `setOnConsoleMessage`.'); + 'implementation for `setOnUrlChange`'); + } + + @override + Future setOnHttpAuthRequest( + HttpAuthRequestCallback onHttpAuthRequest, + ) async { + throw UnimplementedError( + 'This version of `LweNavigationDelegate` currently has no ' + 'implementation for `setOnHttpAuthRequest`'); } } diff --git a/packages/webview_flutter_lwe/pubspec.yaml b/packages/webview_flutter_lwe/pubspec.yaml index b61fb6a29..b6d3704be 100644 --- a/packages/webview_flutter_lwe/pubspec.yaml +++ b/packages/webview_flutter_lwe/pubspec.yaml @@ -2,11 +2,11 @@ name: webview_flutter_lwe description: Tizen implementation of the webview_flutter plugin backed by Lightweight Web Engine. homepage: https://github.com/flutter-tizen/plugins repository: https://github.com/flutter-tizen/plugins/tree/master/packages/webview_flutter_lwe -version: 0.3.5 +version: 0.3.6 environment: - sdk: ">=3.1.0 <4.0.0" - flutter: ">=3.13.0" + sdk: ^3.5.0 + flutter: ">=3.24.0" flutter: plugin: @@ -20,8 +20,8 @@ dependencies: flutter: sdk: flutter flutter_tizen: ^0.2.1 - webview_flutter: ^4.4.2 - webview_flutter_platform_interface: ^2.6.0 + webview_flutter: ^4.10.0 + webview_flutter_platform_interface: ^2.10.0 topics: - html diff --git a/packages/webview_flutter_lwe/tizen/src/message_dispatcher.cc b/packages/webview_flutter_lwe/tizen/src/message_dispatcher.cc index 2d29d05b7..c6bc04c88 100644 --- a/packages/webview_flutter_lwe/tizen/src/message_dispatcher.cc +++ b/packages/webview_flutter_lwe/tizen/src/message_dispatcher.cc @@ -10,11 +10,22 @@ MessageDispatcher::MessageDispatcher() { ecore_init(); } MessageDispatcher::~MessageDispatcher() { ecore_shutdown(); } void MessageDispatcher::dispatchTaskOnMainThread(std::function&& fn) { - ecore_main_loop_thread_safe_call_sync( - [](void* data) -> void* { - auto fn = static_cast*>(data); - if (fn) (*fn)(); - return nullptr; + struct Param { + std::function fn; + }; + Param* p = new Param({std::move(fn)}); + + ecore_main_loop_thread_safe_call_async( + [](void* data) -> void { + ecore_timer_add( + 0.0, + [](void* data) -> Eina_Bool { + auto* p = static_cast(data); + p->fn(); + delete p; + return ECORE_CALLBACK_CANCEL; + }, + data); }, - &fn); + p); } diff --git a/packages/webview_flutter_lwe/tizen/src/webview.cc b/packages/webview_flutter_lwe/tizen/src/webview.cc index 118f82bbd..7989a7ac6 100644 --- a/packages/webview_flutter_lwe/tizen/src/webview.cc +++ b/packages/webview_flutter_lwe/tizen/src/webview.cc @@ -153,7 +153,7 @@ WebView::WebView(flutter::PluginRegistrar* registrar, int view_id, flutter::EncodableMap args = { {flutter::EncodableValue("url"), flutter::EncodableValue(url)}}; - dispatcher_->dispatchTaskOnMainThread([this, &args]() { + dispatcher_->dispatchTaskOnMainThread([this, args]() { navigation_delegate_channel_->InvokeMethod( "onPageStarted", std::make_unique(args)); }); @@ -163,7 +163,7 @@ WebView::WebView(flutter::PluginRegistrar* registrar, int view_id, flutter::EncodableMap args = { {flutter::EncodableValue("url"), flutter::EncodableValue(url)}}; - dispatcher_->dispatchTaskOnMainThread([this, &args]() { + dispatcher_->dispatchTaskOnMainThread([this, args]() { navigation_delegate_channel_->InvokeMethod( "onPageFinished", std::make_unique(args)); @@ -174,7 +174,7 @@ WebView::WebView(flutter::PluginRegistrar* registrar, int view_id, flutter::EncodableMap args = {{flutter::EncodableValue("progress"), flutter::EncodableValue(progress)}}; - dispatcher_->dispatchTaskOnMainThread([this, &args]() { + dispatcher_->dispatchTaskOnMainThread([this, args]() { navigation_delegate_channel_->InvokeMethod( "onProgress", std::make_unique(args)); }); @@ -189,8 +189,7 @@ WebView::WebView(flutter::PluginRegistrar* registrar, int view_id, {flutter::EncodableValue("failingUrl"), flutter::EncodableValue(error.GetUrl())}, }; - - dispatcher_->dispatchTaskOnMainThread([this, &args]() { + dispatcher_->dispatchTaskOnMainThread([this, args]() { navigation_delegate_channel_->InvokeMethod( "onWebResourceError", std::make_unique(args)); @@ -207,7 +206,7 @@ WebView::WebView(flutter::PluginRegistrar* registrar, int view_id, flutter::EncodableValue(true)}, }; - dispatcher_->dispatchTaskOnMainThread([this, &args, url]() { + dispatcher_->dispatchTaskOnMainThread([this, args, url]() { auto result = std::make_unique(url, this); navigation_delegate_channel_->InvokeMethod( "navigationRequest", @@ -231,7 +230,8 @@ void WebView::RegisterJavaScriptChannelName(const std::string& name) { {flutter::EncodableValue("channel"), flutter::EncodableValue(name)}, {flutter::EncodableValue("message"), flutter::EncodableValue(message)}, }; - dispatcher_->dispatchTaskOnMainThread([this, &args]() { + + dispatcher_->dispatchTaskOnMainThread([this, args]() { webview_channel_->InvokeMethod( "javaScriptChannelMessage", std::make_unique(args)); @@ -241,6 +241,10 @@ void WebView::RegisterJavaScriptChannelName(const std::string& name) { webview_instance_->AddJavaScriptInterface(name, "postMessage", on_message); } +void WebView::UnregisterJavaScriptChannelName(const std::string& name) { + webview_instance_->RemoveJavascriptInterface(name, "postMessage"); +} + WebView::~WebView() { Dispose(); } std::string WebView::GetWebViewChannelName() { @@ -583,6 +587,14 @@ void WebView::InitWebView() { #endif } +template +void WebView::SetBackgroundColor(const T& color) { + LWE::Settings settings = webview_instance_->GetSettings(); + settings.SetBaseBackgroundColor(color >> 16 & 0xff, color >> 8 & 0xff, + color & 0xff, color >> 24 & 0xff); + webview_instance_->SetSettings(settings); +} + void WebView::HandleWebViewMethodCall(const FlMethodCall& method_call, std::unique_ptr result) { if (!webview_instance_) { @@ -655,6 +667,12 @@ void WebView::HandleWebViewMethodCall(const FlMethodCall& method_call, RegisterJavaScriptChannelName(*channel); } result->Success(); + } else if (method_name == "removeJavaScriptChannel") { + const auto* channel = std::get_if(arguments); + if (channel) { + UnregisterJavaScriptChannelName(*channel); + } + result->Success(); } else if (method_name == "clearCache") { webview_instance_->ClearCache(); result->Success(); @@ -725,13 +743,15 @@ void WebView::HandleWebViewMethodCall(const FlMethodCall& method_call, result->Error("Invalid argument", "The argument must be a string."); } } else if (method_name == "backgroundColor") { - const auto* color = std::get_if(arguments); - if (color) { - LWE::Settings settings = webview_instance_->GetSettings(); - settings.SetBaseBackgroundColor(*color >> 16 & 0xff, *color >> 8 & 0xff, - *color & 0xff, *color >> 24 & 0xff); - webview_instance_->SetSettings(settings); + if (std::holds_alternative(*arguments)) { + SetBackgroundColor(std::get(*arguments)); result->Success(); + } else if (std::holds_alternative(*arguments)) { + SetBackgroundColor(std::get(*arguments)); + result->Success(); + } else { + result->Error("Invalid argument", + "The argument must be a int32_t or int64_t."); } } else if (method_name == "setUserAgent") { const auto* user_agent = std::get_if(arguments); diff --git a/packages/webview_flutter_lwe/tizen/src/webview.h b/packages/webview_flutter_lwe/tizen/src/webview.h index 0f2064c87..eae0a6eab 100644 --- a/packages/webview_flutter_lwe/tizen/src/webview.h +++ b/packages/webview_flutter_lwe/tizen/src/webview.h @@ -61,7 +61,11 @@ class WebView : public PlatformView { void HandleCookieMethodCall(const FlMethodCall& method_call, std::unique_ptr result); + template + void SetBackgroundColor(const T& color); + void RegisterJavaScriptChannelName(const std::string& name); + void UnregisterJavaScriptChannelName(const std::string& name); std::string GetWebViewChannelName(); std::string GetNavigationDelegateChannelName();