diff --git a/packages/webview_flutter/CHANGELOG.md b/packages/webview_flutter/CHANGELOG.md index 4bbc7bd7d..929fad45a 100644 --- a/packages/webview_flutter/CHANGELOG.md +++ b/packages/webview_flutter/CHANGELOG.md @@ -1,3 +1,13 @@ +## 0.9.0 + +* Update webivew_flutter to 4.4.2. +* Update webview_flutter_platform_interface to 2.6.0. +* Adds pub topics to package metadata. +* Adds support to retrieve the user agent. See `TizenWebViewController.getUserAgent`. +* Adds support to register a callback to receive JavaScript console messages. See `TizenWebViewController.setOnConsoleMessage`. +* Apply PlatformView API change. +* Fix bug on playing youtube on TV. + ## 0.8.0 * Update webivew_flutter to 4.2.3. diff --git a/packages/webview_flutter/README.md b/packages/webview_flutter/README.md index c4f668d34..a75600ea1 100644 --- a/packages/webview_flutter/README.md +++ b/packages/webview_flutter/README.md @@ -22,8 +22,8 @@ This package is not an _endorsed_ implementation of `webview_flutter`. Therefore ```yaml dependencies: - webview_flutter: ^4.2.3 - webview_flutter_tizen: ^0.8.0 + webview_flutter: ^4.4.2 + webview_flutter_tizen: ^0.9.0 ``` ## Example @@ -59,4 +59,20 @@ class _WebViewExampleState extends State { ## Supported devices -This plugin is only supported on Tizen TV devices running Tizen 5.5 or later. +This plugin is only supported on Tizen TV devices running Tizen 5.5 or later. + +## Note + +To play Youtube, make app's background color to transparent. + +```diff +--- a/packages/webview_flutter/example/lib/main.dart ++++ b/packages/webview_flutter/example/lib/main.dart + @override + Widget build(BuildContext context) { + return Scaffold( +- backgroundColor: Colors.green, ++ backgroundColor: Colors.transparent, + appBar: AppBar( + title: const Text('Flutter WebView example'), +``` diff --git a/packages/webview_flutter/example/integration_test/webview_flutter_test.dart b/packages/webview_flutter/example/integration_test/webview_flutter_test.dart index f538e3eff..8322d7adb 100644 --- a/packages/webview_flutter/example/integration_test/webview_flutter_test.dart +++ b/packages/webview_flutter/example/integration_test/webview_flutter_test.dart @@ -10,7 +10,6 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; @@ -176,7 +175,7 @@ Future main() async { await pageFinished.future; - final String customUserAgent = await _getUserAgent(controller); + final String? customUserAgent = await controller.getUserAgent(); expect(customUserAgent, 'Custom_User_Agent1'); }); @@ -477,23 +476,6 @@ Future main() async { }); } -/// Returns the value used for the HTTP User-Agent: request header in subsequent HTTP requests. -Future _getUserAgent(WebViewController controller) async { - return _runJavascriptReturningResult(controller, 'navigator.userAgent;'); -} - -Future _runJavascriptReturningResult( - WebViewController controller, - String js, -) async { - if (defaultTargetPlatform == TargetPlatform.iOS || - defaultTargetPlatform == TargetPlatform.linux) { - return await controller.runJavaScriptReturningResult(js) as String; - } - return jsonDecode(await controller.runJavaScriptReturningResult(js) as String) - as String; -} - class ResizableWebView extends StatefulWidget { const ResizableWebView({ super.key, diff --git a/packages/webview_flutter/example/lib/main.dart b/packages/webview_flutter/example/lib/main.dart index 8265578d8..cdd059f42 100644 --- a/packages/webview_flutter/example/lib/main.dart +++ b/packages/webview_flutter/example/lib/main.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// ignore_for_file: public_member_api_docs, avoid_print +// ignore_for_file: public_member_api_docs import 'dart:async'; import 'dart:convert'; @@ -70,6 +70,40 @@ const String kTransparentBackgroundPage = ''' '''; +const String kLogExamplePage = ''' + + + +Load file or HTML string example + + + +

Local demo page

+

+ This page is used to test the forwarding of console logs to Dart. +

+ + + +
+ + + + + +
+ + + +'''; + class WebViewExample extends StatefulWidget { const WebViewExample({super.key}); @@ -152,7 +186,7 @@ Page resource error: return FloatingActionButton( onPressed: () async { final String? url = await _controller.currentUrl(); - if (context.mounted) { + if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Favorited $url')), ); @@ -177,6 +211,7 @@ enum MenuOptions { loadHtmlString, transparentBackground, setCookie, + logExample, } class SampleMenu extends StatelessWidget { @@ -233,6 +268,9 @@ class SampleMenu extends StatelessWidget { case MenuOptions.setCookie: _onSetCookie(); break; + case MenuOptions.logExample: + _onLogExample(); + break; } }, itemBuilder: (BuildContext context) => >[ @@ -289,6 +327,10 @@ class SampleMenu extends StatelessWidget { value: MenuOptions.setCookie, child: Text('Set cookie'), ), + const PopupMenuItem( + value: MenuOptions.logExample, + child: Text('Log example'), + ), ], ); } @@ -433,6 +475,16 @@ class SampleMenu extends StatelessWidget { return indexFile.path; } + + Future _onLogExample() { + webViewController + .setOnConsoleMessage((JavaScriptConsoleMessage consoleMessage) { + debugPrint( + '== JS == ${consoleMessage.level.name}: ${consoleMessage.message}'); + }); + + return webViewController.loadHtmlString(kLogExamplePage); + } } class NavigationControls extends StatelessWidget { diff --git a/packages/webview_flutter/example/pubspec.yaml b/packages/webview_flutter/example/pubspec.yaml index c59c02c68..e06c60cb6 100644 --- a/packages/webview_flutter/example/pubspec.yaml +++ b/packages/webview_flutter/example/pubspec.yaml @@ -12,7 +12,7 @@ dependencies: path_provider: ^2.0.7 path_provider_tizen: path: ../../path_provider/ - webview_flutter: ^4.2.3 + webview_flutter: ^4.4.2 webview_flutter_tizen: path: ../ diff --git a/packages/webview_flutter/lib/src/tizen_webview.dart b/packages/webview_flutter/lib/src/tizen_webview.dart index ca2a68f4f..1ed3051de 100644 --- a/packages/webview_flutter/lib/src/tizen_webview.dart +++ b/packages/webview_flutter/lib/src/tizen_webview.dart @@ -211,5 +211,11 @@ class TizenWebView { /// Sets the value used for the HTTP `User-Agent:` request header. Future setUserAgent(String? userAgent) => - _invokeChannelMethod('userAgent', userAgent); + _invokeChannelMethod('setUserAgent', userAgent); + + /// Gets the HTTP 'User-Agent:' request header. + Future getUserAgent() async { + final String? result = await _invokeChannelMethod('getUserAgent'); + return result; + } } diff --git a/packages/webview_flutter/lib/src/tizen_webview_controller.dart b/packages/webview_flutter/lib/src/tizen_webview_controller.dart index de79235f2..88a47bb9c 100644 --- a/packages/webview_flutter/lib/src/tizen_webview_controller.dart +++ b/packages/webview_flutter/lib/src/tizen_webview_controller.dart @@ -16,6 +16,10 @@ import 'tizen_webview.dart'; const String kTizenNavigationDelegateChannelName = 'plugins.flutter.io/tizen_webview_navigation_delegate_'; +/// The channel name of [TizenWebViewController]. +const String kTizenWebViewControllerChannelName = + 'plugins.flutter.io/tizen_webview_controller_'; + /// An implementation of [PlatformWebViewController] using the Tizen WebView API. class TizenWebViewController extends PlatformWebViewController { /// Constructs a [TizenWebViewController]. @@ -26,12 +30,60 @@ class TizenWebViewController extends PlatformWebViewController { final TizenWebView _webview; late TizenNavigationDelegate _tizenNavigationDelegate; + void Function(JavaScriptConsoleMessage consoleMessage)? _onConsoleLogCallback; + + late final MethodChannel _webviewControllerChannel; + + /// Called when [TizenView] is created. + void createWebviewControllerChannel(int viewId) { + _webviewControllerChannel = + MethodChannel(kTizenWebViewControllerChannelName + viewId.toString()); + _webviewControllerChannel.setMethodCallHandler((MethodCall call) async { + final Map arguments = + (call.arguments as Map).cast(); + switch (call.method) { + case 'onConsoleMessage': + JavaScriptLogLevel level = JavaScriptLogLevel.log; + switch (arguments['level']! as String) { + case 'error': + level = JavaScriptLogLevel.error; + break; + case 'warning': + level = JavaScriptLogLevel.warning; + break; + case 'debug': + level = JavaScriptLogLevel.debug; + break; + case 'info': + level = JavaScriptLogLevel.info; + break; + case 'log': + level = JavaScriptLogLevel.log; + break; + } + + if (_onConsoleLogCallback != null) { + _onConsoleLogCallback!(JavaScriptConsoleMessage( + level: level, + message: arguments['message']! as String, + )); + } + return null; + } + + throw MissingPluginException( + '${call.method} was invoked but has no handler', + ); + }); + } + /// Called when [TizenView] is created. void onCreate(int viewId) { _webview.onCreate(viewId); if (_webview.hasNavigationDelegate) { _tizenNavigationDelegate.createNavigationDelegateChannel(viewId); } + createWebviewControllerChannel(viewId); } @override @@ -173,6 +225,20 @@ class TizenWebViewController extends PlatformWebViewController { 'This version of `TizenWebViewController` currently has no ' 'implementation.'); } + + /// Sets a callback that notifies the host application of any log messages + /// written to the JavaScript console. + @override + Future setOnConsoleMessage( + void Function(JavaScriptConsoleMessage consoleMessage) + onConsoleMessage) async { + _onConsoleLogCallback = onConsoleMessage; + } + + @override + Future getUserAgent() { + return _webview.getUserAgent(); + } } /// An implementation of [PlatformWebViewWidget] with the Tizen WebView API. diff --git a/packages/webview_flutter/pubspec.yaml b/packages/webview_flutter/pubspec.yaml index d3becad4e..b22c51928 100644 --- a/packages/webview_flutter/pubspec.yaml +++ b/packages/webview_flutter/pubspec.yaml @@ -2,7 +2,7 @@ name: webview_flutter_tizen description: Tizen implementation of the webview_flutter plugin. homepage: https://github.com/flutter-tizen/plugins repository: https://github.com/flutter-tizen/plugins/tree/master/packages/webview_flutter -version: 0.8.0 +version: 0.9.0 environment: sdk: ">=2.18.0 <4.0.0" @@ -20,5 +20,11 @@ dependencies: flutter: sdk: flutter flutter_tizen: ^0.2.1 - webview_flutter: ^4.2.3 - webview_flutter_platform_interface: ^2.3.0 + webview_flutter: ^4.4.2 + webview_flutter_platform_interface: ^2.6.0 + +topics: + - html + - webview + - webview-flutter + diff --git a/packages/webview_flutter/tizen/src/ewk_internal_api_binding.cc b/packages/webview_flutter/tizen/src/ewk_internal_api_binding.cc index 01be2c187..e09a5692d 100644 --- a/packages/webview_flutter/tizen/src/ewk_internal_api_binding.cc +++ b/packages/webview_flutter/tizen/src/ewk_internal_api_binding.cc @@ -35,6 +35,8 @@ bool EwkInternalApiBinding::Initialize() { dlsym(handle_, "ewk_view_ime_window_set")); view.KeyEventsEnabledSet = reinterpret_cast( dlsym(handle_, "ewk_view_key_events_enabled_set")); + view.SupportVideoHoleSet = reinterpret_cast( + dlsym(handle_, "ewk_view_set_support_video_hole")); // ewk_main main.SetArguments = reinterpret_cast( @@ -57,8 +59,8 @@ bool EwkInternalApiBinding::Initialize() { return view.SetBackgroundColor && view.FeedTouchEvent && view.SendKeyEvent && view.OffscreenRenderingEnabledSet && view.ImeWindowSet && - view.KeyEventsEnabledSet && main.SetArguments && - settings.ImePanelEnabledSet && console_message.LevelGet && - console_message.TextGet && console_message.LineGet && - console_message.SourceGet; + view.KeyEventsEnabledSet && view.SupportVideoHoleSet && + main.SetArguments && settings.ImePanelEnabledSet && + console_message.LevelGet && console_message.TextGet && + console_message.LineGet && console_message.SourceGet; } diff --git a/packages/webview_flutter/tizen/src/ewk_internal_api_binding.h b/packages/webview_flutter/tizen/src/ewk_internal_api_binding.h index 499560fb7..c259a9a0a 100644 --- a/packages/webview_flutter/tizen/src/ewk_internal_api_binding.h +++ b/packages/webview_flutter/tizen/src/ewk_internal_api_binding.h @@ -36,6 +36,10 @@ typedef void (*EwkViewOffscreenRenderingEnabledSetFnPtr)(Evas_Object* obj, typedef void (*EwkViewImeWindowSetFnPtr)(Evas_Object* obj, void* window); typedef Eina_Bool (*EwkViewKeyEventsEnabledSetFnPtr)(Evas_Object* obj, Eina_Bool enabled); +typedef Eina_Bool (*EwkViewSupportVideoHoleSetFnPtr)(Evas_Object* obj, + void* window, + Eina_Bool enabled, + Eina_Bool boo); typedef struct { EwkViewBgColorSetFnPtr SetBackgroundColor = nullptr; @@ -45,6 +49,7 @@ typedef struct { nullptr; EwkViewImeWindowSetFnPtr ImeWindowSet = nullptr; EwkViewKeyEventsEnabledSetFnPtr KeyEventsEnabledSet = nullptr; + EwkViewSupportVideoHoleSetFnPtr SupportVideoHoleSet = nullptr; } EwkViewProcTable; typedef void (*EwkSetArgumentsFnPtr)(int argc, char** argv); diff --git a/packages/webview_flutter/tizen/src/webview.cc b/packages/webview_flutter/tizen/src/webview.cc index abd15aa13..8fe79c97b 100644 --- a/packages/webview_flutter/tizen/src/webview.cc +++ b/packages/webview_flutter/tizen/src/webview.cc @@ -10,8 +10,6 @@ #include #include -#include - #include "buffer_pool.h" #include "ewk_internal_api_binding.h" #include "log.h" @@ -22,9 +20,29 @@ namespace { constexpr size_t kBufferPoolSize = 5; constexpr char kEwkInstance[] = "ewk_instance"; constexpr char kTizenWebViewChannelName[] = "plugins.flutter.io/tizen_webview_"; +constexpr char kTizenWebViewControllerChannelName[] = + "plugins.flutter.io/tizen_webview_controller_"; constexpr char kTizenNavigationDelegateChannelName[] = "plugins.flutter.io/tizen_webview_navigation_delegate_"; +std::string ConvertLogLevelToString(Ewk_Console_Message_Level level) { + switch (level) { + case EWK_CONSOLE_MESSAGE_LEVEL_NULL: + case EWK_CONSOLE_MESSAGE_LEVEL_LOG: + return "log"; + case EWK_CONSOLE_MESSAGE_LEVEL_WARNING: + return "warning"; + case EWK_CONSOLE_MESSAGE_LEVEL_ERROR: + return "error"; + case EWK_CONSOLE_MESSAGE_LEVEL_DEBUG: + return "debug"; + case EWK_CONSOLE_MESSAGE_LEVEL_INFO: + return "info"; + default: + return "log"; + } +} + class NavigationRequestResult : public FlMethodResult { public: NavigationRequestResult(WebView* webview) : webview_(webview) {} @@ -108,6 +126,10 @@ WebView::WebView(flutter::PluginRegistrar* registrar, int view_id, webview->HandleWebViewMethodCall(call, std::move(result)); }); + webview_controller_channel_ = std::make_unique( + GetPluginRegistrar()->messenger(), GetWebViewControllerChannelName(), + &flutter::StandardMethodCodec::GetInstance()); + navigation_delegate_channel_ = std::make_unique( GetPluginRegistrar()->messenger(), GetNavigationDelegateChannelName(), &flutter::StandardMethodCodec::GetInstance()); @@ -140,6 +162,11 @@ std::string WebView::GetWebViewChannelName() { return std::string(kTizenWebViewChannelName) + std::to_string(GetViewId()); } +std::string WebView::GetWebViewControllerChannelName() { + return std::string(kTizenWebViewControllerChannelName) + + std::to_string(GetViewId()); +} + std::string WebView::GetNavigationDelegateChannelName() { return std::string(kTizenNavigationDelegateChannelName) + std::to_string(GetViewId()); @@ -171,6 +198,14 @@ void WebView::Dispose() { } } +void WebView::Offset(double left, double top) { + left_ = left; + top_ = top; + + evas_object_move(webview_instance_, static_cast(left_), + static_cast(top_)); +} + void WebView::Resize(double width, double height) { width_ = width; height_ = height; @@ -203,8 +238,8 @@ void WebView::Touch(int type, int button, double x, double y, double dx, Eina_List* points = 0; Ewk_Touch_Point* point = new Ewk_Touch_Point; point->id = 0; - point->x = x; - point->y = y; + point->x = x + left_; + point->y = y + top_; point->state = state; points = eina_list_append(points, point); @@ -285,6 +320,11 @@ void WebView::InitWebView() { EwkInternalApiBinding::GetInstance().view.KeyEventsEnabledSet( webview_instance_, true); +#ifdef TV_PROFILE + EwkInternalApiBinding::GetInstance().view.SupportVideoHoleSet( + webview_instance_, window_, true, false); +#endif + evas_object_smart_callback_add(webview_instance_, "offscreen,frame,rendered", &WebView::OnFrameRendered, this); evas_object_smart_callback_add(webview_instance_, "load,started", @@ -506,12 +546,15 @@ void WebView::HandleWebViewMethodCall(const FlMethodCall& method_call, *color & 0xff, *color >> 24 & 0xff); result->Success(); } - } else if (method_name == "userAgent") { + } else if (method_name == "setUserAgent") { const auto* userAgent = std::get_if(arguments); if (userAgent) { ewk_view_user_agent_set(webview_instance_, userAgent->c_str()); } result->Success(); + } else if (method_name == "getUserAgent") { + result->Success(flutter::EncodableValue( + std::string(ewk_view_user_agent_get(webview_instance_)))); } else { result->NotImplemented(); } @@ -631,19 +674,18 @@ void WebView::OnConsoleMessage(void* data, Evas_Object* obj, void* event_info) { Ewk_Console_Message* message = static_cast(event_info); Ewk_Console_Message_Level log_level = EwkInternalApiBinding::GetInstance().console_message.LevelGet(message); - std::string source = - EwkInternalApiBinding::GetInstance().console_message.SourceGet(message); - int32_t line = - EwkInternalApiBinding::GetInstance().console_message.LineGet(message); std::string text = EwkInternalApiBinding::GetInstance().console_message.TextGet(message); - std::ostream& stream = - log_level == EWK_CONSOLE_MESSAGE_LEVEL_ERROR ? std::cerr : std::cout; - stream << "WebView: "; - if (!source.empty() && line > 0) { - stream << source << "(" << line << ") > "; + WebView* webview = static_cast(data); + if (webview->webview_controller_channel_) { + flutter::EncodableMap args = { + {flutter::EncodableValue("level"), + flutter::EncodableValue(ConvertLogLevelToString(log_level))}, + {flutter::EncodableValue("message"), flutter::EncodableValue(text)}, + }; + webview->webview_controller_channel_->InvokeMethod( + "onConsoleMessage", std::make_unique(args)); } - stream << text << std::endl; } void WebView::OnNavigationPolicy(void* data, Evas_Object* obj, diff --git a/packages/webview_flutter/tizen/src/webview.h b/packages/webview_flutter/tizen/src/webview.h index a3eb8ebc8..76eaa25bc 100644 --- a/packages/webview_flutter/tizen/src/webview.h +++ b/packages/webview_flutter/tizen/src/webview.h @@ -35,6 +35,7 @@ class WebView : public PlatformView { virtual void Dispose() override; + virtual void Offset(double left, double top) override; virtual void Resize(double width, double height) override; virtual void Touch(int type, int button, double x, double y, double dx, double dy) override; @@ -63,6 +64,7 @@ class WebView : public PlatformView { void RegisterJavaScriptChannelName(const std::string& name); std::string GetWebViewChannelName(); + std::string GetWebViewControllerChannelName(); std::string GetNavigationDelegateChannelName(); void InitWebView(); @@ -84,12 +86,15 @@ class WebView : public PlatformView { flutter::TextureRegistrar* texture_registrar_; double width_ = 0.0; double height_ = 0.0; + double left_ = 0.0; + double top_ = 0.0; void* window_ = nullptr; BufferUnit* working_surface_ = nullptr; BufferUnit* candidate_surface_ = nullptr; BufferUnit* rendered_surface_ = nullptr; bool has_navigation_delegate_ = false; std::unique_ptr webview_channel_; + std::unique_ptr webview_controller_channel_; std::unique_ptr navigation_delegate_channel_; std::unique_ptr texture_variant_; std::mutex mutex_;