From 643a41a7aec6944ef5cb78081e5620e741dd9be2 Mon Sep 17 00:00:00 2001 From: JunsuChoi Date: Thu, 23 Mar 2023 13:32:21 +0900 Subject: [PATCH 1/9] [webview_flutter_lwe] Update webview_flutter_lwe to v0.2.0 --- packages/webview_flutter_lwe/CHANGELOG.md | 5 + packages/webview_flutter_lwe/README.md | 23 +- .../webview_flutter_test.dart | 922 +++++++++++------- .../webview_flutter_lwe/example/lib/main.dart | 538 +++++----- .../webview_flutter_lwe/example/pubspec.yaml | 6 +- .../lib/src/tizen_webview.dart | 199 ++++ .../lib/src/tizen_webview_controller.dart | 377 +++++++ .../lib/src/tizen_webview_cookie_manager.dart | 44 + .../lib/src/tizen_webview_platform.dart | 45 + .../lib/webview_flutter_lwe.dart | 94 +- packages/webview_flutter_lwe/pubspec.yaml | 11 +- .../webview_flutter_lwe/tizen/src/webview.cc | 223 ++--- .../webview_flutter_lwe/tizen/src/webview.h | 24 +- 13 files changed, 1619 insertions(+), 892 deletions(-) create mode 100644 packages/webview_flutter_lwe/lib/src/tizen_webview.dart create mode 100644 packages/webview_flutter_lwe/lib/src/tizen_webview_controller.dart create mode 100644 packages/webview_flutter_lwe/lib/src/tizen_webview_cookie_manager.dart create mode 100644 packages/webview_flutter_lwe/lib/src/tizen_webview_platform.dart diff --git a/packages/webview_flutter_lwe/CHANGELOG.md b/packages/webview_flutter_lwe/CHANGELOG.md index c46a87856..17458954e 100644 --- a/packages/webview_flutter_lwe/CHANGELOG.md +++ b/packages/webview_flutter_lwe/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.2.0 + +* Update webivew_flutter to 4.0.2. +* Update webview_flutter_platform_interface to 2.0.1. + ## 0.1.1 * Use only error type names defined in `web_resource_error.dart`. diff --git a/packages/webview_flutter_lwe/README.md b/packages/webview_flutter_lwe/README.md index 240474d5b..918637efd 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: ^3.0.4 - webview_flutter_lwe: ^0.1.1 + webview_flutter: ^4.0.2 + webview_flutter_lwe: ^0.2.0 ``` ## Example @@ -30,16 +30,27 @@ dependencies: import 'package:webview_flutter/webview_flutter.dart'; class WebViewExample extends StatefulWidget { - const WebViewExample({Key? key}) : super(key: key); + const WebViewExample({super.key}); @override - WebViewExampleState createState() => WebViewExampleState(); + State createState() => _WebViewExampleState(); } -class WebViewExampleState extends State { +class _WebViewExampleState extends State { + final WebViewController _controller = WebViewController(); + + @override + void initState() { + super.initState(); + + _controller.loadRequest(Uri.parse('https://flutter.dev')); + } + @override Widget build(BuildContext context) { - return WebView(initialUrl: 'https://flutter.dev'); + return Scaffold( + body: WebViewWidget(controller: _controller), + ); } } ``` diff --git a/packages/webview_flutter_lwe/example/integration_test/webview_flutter_test.dart b/packages/webview_flutter_lwe/example/integration_test/webview_flutter_test.dart index c3710f944..d3b43bf44 100644 --- a/packages/webview_flutter_lwe/example/integration_test/webview_flutter_test.dart +++ b/packages/webview_flutter_lwe/example/integration_test/webview_flutter_test.dart @@ -9,8 +9,13 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +// TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) +// ignore: unnecessary_import +import 'dart:typed_data'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:webview_flutter/webview_flutter.dart'; @@ -36,124 +41,95 @@ Future main() async { final String prefixUrl = 'http://${server.address.address}:${server.port}'; final String primaryUrl = '$prefixUrl/hello.txt'; final String secondaryUrl = '$prefixUrl/secondary.txt'; + final String headersUrl = '$prefixUrl/headers'; - testWidgets('initialUrl', (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - final Completer pageFinishedCompleter = Completer(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: primaryUrl, - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - onPageFinished: pageFinishedCompleter.complete, - ), - ), - ); + testWidgets('loadRequest', (WidgetTester tester) async { + final Completer pageFinished = Completer(); + + final WebViewController controller = WebViewController() + ..setNavigationDelegate( + NavigationDelegate(onPageFinished: (_) => pageFinished.complete()), + ) + ..loadRequest(Uri.parse(primaryUrl)); - final WebViewController controller = await controllerCompleter.future; - await pageFinishedCompleter.future; + await tester.pumpWidget(WebViewWidget(controller: controller)); + + await pageFinished.future; final String? currentUrl = await controller.currentUrl(); expect(currentUrl, primaryUrl); }); - testWidgets('loadUrl', (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - final StreamController pageLoads = StreamController(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: primaryUrl, - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - onPageFinished: (String url) { - pageLoads.add(url); - }, - ), - ), - ); - final WebViewController controller = await controllerCompleter.future; + testWidgets('runJavaScriptReturningResult', (WidgetTester tester) async { + final Completer pageFinished = Completer(); + + final WebViewController controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate( + NavigationDelegate(onPageFinished: (_) => pageFinished.complete()), + ) + ..loadRequest(Uri.parse(primaryUrl)); + + await tester.pumpWidget(WebViewWidget(controller: controller)); + + await pageFinished.future; - await controller.loadUrl(secondaryUrl); await expectLater( - pageLoads.stream.firstWhere((String url) => url == secondaryUrl), - completion(secondaryUrl), + controller.runJavaScriptReturningResult('1 + 1'), + completion(2), ); }); - testWidgets('evaluateJavascript', (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: primaryUrl, - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - ), - ), - ); - final WebViewController controller = await controllerCompleter.future; - // ignore: deprecated_member_use - final String result = await controller.evaluateJavascript('1 + 1'); - expect(result, equals('2')); + testWidgets('loadRequest with headers', (WidgetTester tester) async { + final Map headers = { + 'test_header': 'flutter_test_header' + }; + + final StreamController pageLoads = StreamController(); + + final WebViewController controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate( + NavigationDelegate(onPageFinished: (String url) => pageLoads.add(url)), + ); + + await tester.pumpWidget(WebViewWidget(controller: controller)); + + controller.loadRequest(Uri.parse(headersUrl), headers: headers); + + await pageLoads.stream.firstWhere((String url) => url == headersUrl); + + final String content = await controller.runJavaScriptReturningResult( + 'document.documentElement.innerText', + ) as String; + expect(content.contains('flutter_test_header'), isTrue); }); testWidgets('JavascriptChannel', (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - final Completer pageStarted = Completer(); - final Completer pageLoaded = Completer(); + final Completer pageFinished = Completer(); + final WebViewController controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate( + NavigationDelegate(onPageFinished: (_) => pageFinished.complete()), + ); + final Completer channelCompleter = Completer(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - // This is the data URL for: '' - initialUrl: - 'data:text/html;charset=utf-8;base64,PCFET0NUWVBFIGh0bWw+', - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - javascriptChannels: { - JavascriptChannel( - name: 'Echo', - onMessageReceived: (JavascriptMessage message) { - channelCompleter.complete(message.message); - }, - ), - }, - onPageStarted: (String url) { - pageStarted.complete(null); - }, - onPageFinished: (String url) { - pageLoaded.complete(null); - }, - ), - ), + await controller.addJavaScriptChannel( + 'Echo', + onMessageReceived: (JavaScriptMessage message) { + channelCompleter.complete(message.message); + }, ); - final WebViewController controller = await controllerCompleter.future; - await pageStarted.future; - await pageLoaded.future; - expect(channelCompleter.isCompleted, isFalse); - await controller.runJavascript('Echo.postMessage("hello");'); + await controller.loadHtmlString( + 'data:text/html;charset=utf-8;base64,PCFET0NUWVBFIGh0bWw+', + ); + + await tester.pumpWidget(WebViewWidget(controller: controller)); + await pageFinished.future; + + await controller.runJavaScript('Echo.postMessage("hello");'); await expectLater(channelCompleter.future, completion('hello')); }); @@ -164,7 +140,7 @@ Future main() async { bool resizeButtonTapped = false; await tester.pumpWidget(ResizableWebView( - onResize: (_) { + onResize: () { if (resizeButtonTapped) { buttonTapResizeCompleter.complete(); } else { @@ -173,6 +149,7 @@ Future main() async { }, onPageFinished: () => onPageFinished.complete(), )); + await onPageFinished.future; // Wait for a potential call to resize after page is loaded. await initialResizeCompleter.future.timeout( @@ -181,47 +158,276 @@ Future main() async { ); resizeButtonTapped = true; + await tester.tap(find.byKey(const ValueKey('resizeButton'))); await tester.pumpAndSettle(); - expect(buttonTapResizeCompleter.future, completes); + + await expectLater(buttonTapResizeCompleter.future, completes); }); testWidgets('set custom userAgent', (WidgetTester tester) async { - final Completer controllerCompleter1 = - Completer(); - final GlobalKey globalKey = GlobalKey(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: globalKey, - initialUrl: 'about:blank', - javascriptMode: JavascriptMode.unrestricted, - userAgent: 'Custom_User_Agent1', - onWebViewCreated: (WebViewController controller) { - controllerCompleter1.complete(controller); - }, - ), - ), - ); - final WebViewController controller1 = await controllerCompleter1.future; - final String customUserAgent1 = await _getUserAgent(controller1); - expect(customUserAgent1, 'Custom_User_Agent1'); - // rebuild the WebView with a different user agent. - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: globalKey, - initialUrl: 'about:blank', - javascriptMode: JavascriptMode.unrestricted, - userAgent: 'Custom_User_Agent2', - ), - ), - ); + final Completer pageFinished = Completer(); + + final WebViewController controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate(NavigationDelegate( + onPageFinished: (_) => pageFinished.complete(), + )) + ..setUserAgent('Custom_User_Agent1') + ..loadRequest(Uri.parse('about:blank')); + + await tester.pumpWidget(WebViewWidget(controller: controller)); + + await pageFinished.future; + + final String customUserAgent = await _getUserAgent(controller); + expect(customUserAgent, 'Custom_User_Agent1'); + }); + + group('Video playback policy', () { + late String videoTestBase64; + setUpAll(() async { + final ByteData videoData = + await rootBundle.load('assets/sample_video.mp4'); + final String base64VideoData = + base64Encode(Uint8List.view(videoData.buffer)); + final String videoTest = ''' + + Video auto play + + + + + + + '''; + videoTestBase64 = base64Encode(const Utf8Encoder().convert(videoTest)); + }); + + testWidgets('Auto media playback', (WidgetTester tester) async { + Completer pageLoaded = Completer(); + + late PlatformWebViewControllerCreationParams params; + params = const PlatformWebViewControllerCreationParams(); + + WebViewController controller = + WebViewController.fromPlatformCreationParams(params) + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate( + NavigationDelegate(onPageFinished: (_) => pageLoaded.complete()), + ); + + await controller.loadRequest( + Uri.parse('data:text/html;charset=utf-8;base64,$videoTestBase64'), + ); + + await tester.pumpWidget(WebViewWidget(controller: controller)); + + await pageLoaded.future; + + bool isPaused = + await controller.runJavaScriptReturningResult('isPaused();') as bool; + expect(isPaused, false); + + pageLoaded = Completer(); + controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate( + NavigationDelegate(onPageFinished: (_) => pageLoaded.complete()), + ) + ..loadRequest( + Uri.parse('data:text/html;charset=utf-8;base64,$videoTestBase64'), + ); + + await tester.pumpWidget(WebViewWidget(controller: controller)); + + await pageLoaded.future; + + isPaused = + await controller.runJavaScriptReturningResult('isPaused();') as bool; + expect(isPaused, true); + }); + + testWidgets('Video plays inline', (WidgetTester tester) async { + final Completer pageLoaded = Completer(); + final Completer videoPlaying = Completer(); + + late PlatformWebViewControllerCreationParams params; + params = const PlatformWebViewControllerCreationParams(); + final WebViewController controller = + WebViewController.fromPlatformCreationParams(params) + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate( + NavigationDelegate(onPageFinished: (_) => pageLoaded.complete()), + ) + ..addJavaScriptChannel( + 'VideoTestTime', + onMessageReceived: (JavaScriptMessage message) { + final double currentTime = double.parse(message.message); + // Let it play for at least 1 second to make sure the related video's properties are set. + if (currentTime > 1 && !videoPlaying.isCompleted) { + videoPlaying.complete(null); + } + }, + ); + + await controller.loadRequest( + Uri.parse('data:text/html;charset=utf-8;base64,$videoTestBase64'), + ); + + await tester.pumpWidget(WebViewWidget(controller: controller)); + await tester.pumpAndSettle(); + + await pageLoaded.future; + + // Makes sure we get the correct event that indicates the video is actually playing. + await videoPlaying.future; + + final bool fullScreen = await controller + .runJavaScriptReturningResult('isFullScreen();') as bool; + expect(fullScreen, false); + }); + + // allowsInlineMediaPlayback is a noop on Android, so it is skipped. + testWidgets( + 'Video plays full screen when allowsInlineMediaPlayback is false', + (WidgetTester tester) async { + final Completer pageLoaded = Completer(); + final Completer videoPlaying = Completer(); + + final WebViewController controller = + WebViewController.fromPlatformCreationParams( + const PlatformWebViewControllerCreationParams()) + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate( + NavigationDelegate(onPageFinished: (_) => pageLoaded.complete()), + ) + ..addJavaScriptChannel( + 'VideoTestTime', + onMessageReceived: (JavaScriptMessage message) { + final double currentTime = double.parse(message.message); + // Let it play for at least 1 second to make sure the related video's properties are set. + if (currentTime > 1 && !videoPlaying.isCompleted) { + videoPlaying.complete(null); + } + }, + ) + ..loadRequest( + Uri.parse( + 'data:text/html;charset=utf-8;base64,$videoTestBase64', + ), + ); + + await tester.pumpWidget(WebViewWidget(controller: controller)); + await tester.pumpAndSettle(); + + await pageLoaded.future; + + // Makes sure we get the correct event that indicates the video is actually playing. + await videoPlaying.future; + + final bool fullScreen = await controller + .runJavaScriptReturningResult('isFullScreen();') as bool; + expect(fullScreen, true); + }, skip: Platform.isAndroid); + }); + + group('Audio playback policy', () { + late String audioTestBase64; + setUpAll(() async { + final ByteData audioData = + await rootBundle.load('assets/sample_audio.ogg'); + final String base64AudioData = + base64Encode(Uint8List.view(audioData.buffer)); + final String audioTest = ''' + + Audio auto play + + + + + + + '''; + audioTestBase64 = base64Encode(const Utf8Encoder().convert(audioTest)); + }); + + testWidgets('Auto media playback', (WidgetTester tester) async { + Completer pageLoaded = Completer(); + + late PlatformWebViewControllerCreationParams params; + params = const PlatformWebViewControllerCreationParams(); + + WebViewController controller = + WebViewController.fromPlatformCreationParams(params) + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate( + NavigationDelegate(onPageFinished: (_) => pageLoaded.complete()), + ); + + await controller.loadRequest( + Uri.parse('data:text/html;charset=utf-8;base64,$audioTestBase64'), + ); + + await tester.pumpWidget(WebViewWidget(controller: controller)); + await tester.pumpAndSettle(); + + await pageLoaded.future; + + bool isPaused = + await controller.runJavaScriptReturningResult('isPaused();') as bool; + expect(isPaused, false); + + pageLoaded = Completer(); + controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate( + NavigationDelegate(onPageFinished: (_) => pageLoaded.complete()), + ) + ..loadRequest( + Uri.parse('data:text/html;charset=utf-8;base64,$audioTestBase64'), + ); + + await tester.pumpWidget(WebViewWidget(controller: controller)); + await tester.pumpAndSettle(); - final String customUserAgent2 = await _getUserAgent(controller1); - expect(customUserAgent2, 'Custom_User_Agent2'); + await pageLoaded.future; + + isPaused = + await controller.runJavaScriptReturningResult('isPaused();') as bool; + expect(isPaused, true); + }); }); testWidgets('getTitle', (WidgetTester tester) async { @@ -235,39 +441,26 @@ Future main() async { '''; final String getTitleTestBase64 = base64Encode(const Utf8Encoder().convert(getTitleTest)); - final Completer pageStarted = Completer(); final Completer pageLoaded = Completer(); - final Completer controllerCompleter = - Completer(); - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - initialUrl: 'data:text/html;charset=utf-8;base64,$getTitleTestBase64', - javascriptMode: JavascriptMode.unrestricted, - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - onPageStarted: (String url) { - pageStarted.complete(null); - }, - onPageFinished: (String url) { - pageLoaded.complete(null); - }, - ), - ), - ); - final WebViewController controller = await controllerCompleter.future; - await pageStarted.future; + final WebViewController controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate(NavigationDelegate( + onPageFinished: (_) => pageLoaded.complete(), + )) + ..loadRequest( + Uri.parse('data:text/html;charset=utf-8;base64,$getTitleTestBase64'), + ); + + await tester.pumpWidget(WebViewWidget(controller: controller)); + await pageLoaded.future; // On at least iOS, it does not appear to be guaranteed that the native // code has the title when the page load completes. Execute some JavaScript // before checking the title to ensure that the page has been fully parsed // and processed. - await controller.runJavascript('1;'); + await controller.runJavaScript('1;'); final String? title = await controller.getTitle(); expect(title, 'Some title'); @@ -300,32 +493,22 @@ Future main() async { base64Encode(const Utf8Encoder().convert(scrollTestPage)); final Completer pageLoaded = Completer(); - final Completer controllerCompleter = - Completer(); - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - initialUrl: - 'data:text/html;charset=utf-8;base64,$scrollTestPageBase64', - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - onPageFinished: (String url) { - pageLoaded.complete(null); - }, - ), - ), - ); + final WebViewController controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate(NavigationDelegate( + onPageFinished: (_) => pageLoaded.complete(), + )) + ..loadRequest(Uri.parse( + 'data:text/html;charset=utf-8;base64,$scrollTestPageBase64', + )); + + await tester.pumpWidget(WebViewWidget(controller: controller)); - final WebViewController controller = await controllerCompleter.future; await pageLoaded.future; await tester.pumpAndSettle(const Duration(seconds: 3)); - int scrollPosX = await controller.getScrollX(); - int scrollPosY = await controller.getScrollY(); + Offset scrollPos = await controller.getScrollPosition(); // Check scrollTo() const int X_SCROLL = 123; @@ -333,21 +516,19 @@ Future main() async { // Get the initial position; this ensures that scrollTo is actually // changing something, but also gives the native view's scroll position // time to settle. - expect(scrollPosX, isNot(X_SCROLL)); - expect(scrollPosX, isNot(Y_SCROLL)); + expect(scrollPos.dx, isNot(X_SCROLL)); + expect(scrollPos.dy, isNot(Y_SCROLL)); await controller.scrollTo(X_SCROLL, Y_SCROLL); - scrollPosX = await controller.getScrollX(); - scrollPosY = await controller.getScrollY(); - expect(scrollPosX, X_SCROLL); - expect(scrollPosY, Y_SCROLL); + scrollPos = await controller.getScrollPosition(); + expect(scrollPos.dx, X_SCROLL); + expect(scrollPos.dy, Y_SCROLL); // Check scrollBy() (on top of scrollTo()) await controller.scrollBy(X_SCROLL, Y_SCROLL); - scrollPosX = await controller.getScrollX(); - scrollPosY = await controller.getScrollY(); - expect(scrollPosX, X_SCROLL * 2); - expect(scrollPosY, Y_SCROLL * 2); + scrollPos = await controller.getScrollPosition(); + expect(scrollPos.dx, X_SCROLL * 2); + expect(scrollPos.dy, Y_SCROLL * 2); }); }); @@ -357,35 +538,29 @@ Future main() async { '${base64Encode(const Utf8Encoder().convert(blankPage))}'; testWidgets('can allow requests', (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - final StreamController pageLoads = - StreamController.broadcast(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: blankPageEncoded, - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - navigationDelegate: (NavigationRequest request) { - return (request.url.contains('youtube.com')) - ? NavigationDecision.prevent - : NavigationDecision.navigate; - }, - onPageFinished: (String url) => pageLoads.add(url), - ), - ), - ); + Completer pageLoaded = Completer(); + + final WebViewController controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate(NavigationDelegate( + onPageFinished: (_) => pageLoaded.complete(), + onNavigationRequest: (NavigationRequest navigationRequest) { + return (navigationRequest.url.contains('youtube.com')) + ? NavigationDecision.prevent + : NavigationDecision.navigate; + }, + )); + + await tester.pumpWidget(WebViewWidget(controller: controller)); + + controller.loadRequest(Uri.parse(blankPageEncoded)); + + await pageLoaded.future; // Wait for initial page load. - await pageLoads.stream.first; // Wait for initial page load. - final WebViewController controller = await controllerCompleter.future; - await controller.runJavascript('location.href = "$secondaryUrl"'); + pageLoaded = Completer(); + await controller.runJavaScript('location.href = "$secondaryUrl"'); + await pageLoaded.future; // Wait for the next page load. - await pageLoads.stream.first; // Wait for the next page load. final String? currentUrl = await controller.currentUrl(); expect(currentUrl, secondaryUrl); }); @@ -394,30 +569,18 @@ Future main() async { final Completer errorCompleter = Completer(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: 'https://www.notawebsite..com', - onWebResourceError: (WebResourceError error) { - errorCompleter.complete(error); - }, - ), - ), - ); + final WebViewController controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate( + NavigationDelegate(onWebResourceError: (WebResourceError error) { + errorCompleter.complete(error); + })) + ..loadRequest(Uri.parse('https://www.notawebsite..com')); + + await tester.pumpWidget(WebViewWidget(controller: controller)); final WebResourceError error = await errorCompleter.future; expect(error, isNotNull); - - if (Platform.isIOS) { - expect(error.domain, isNotNull); - expect(error.failingUrl, isNull); - } else if (Platform.isAndroid) { - expect(error.errorType, isNotNull); - expect(error.failingUrl?.startsWith('https://www.notawebsite..com'), - isTrue); - } }); testWidgets('onWebResourceError is not called with valid url', @@ -426,129 +589,123 @@ Future main() async { Completer(); final Completer pageFinishCompleter = Completer(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: - 'data:text/html;charset=utf-8;base64,PCFET0NUWVBFIGh0bWw+', - onWebResourceError: (WebResourceError error) { - errorCompleter.complete(error); - }, - onPageFinished: (_) => pageFinishCompleter.complete(), - ), - ), - ); + final WebViewController controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate(NavigationDelegate( + onPageFinished: (_) => pageFinishCompleter.complete(), + onWebResourceError: (WebResourceError error) { + errorCompleter.complete(error); + }, + )) + ..loadRequest( + Uri.parse('data:text/html;charset=utf-8;base64,PCFET0NUWVBFIGh0bWw+'), + ); + + await tester.pumpWidget(WebViewWidget(controller: controller)); expect(errorCompleter.future, doesNotComplete); await pageFinishCompleter.future; }); testWidgets('can block requests', (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - final StreamController pageLoads = - StreamController.broadcast(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: blankPageEncoded, - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - navigationDelegate: (NavigationRequest request) { - return (request.url.contains('youtube.com')) + Completer pageLoaded = Completer(); + + final WebViewController controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate(NavigationDelegate( + onPageFinished: (_) => pageLoaded.complete(), + onNavigationRequest: (NavigationRequest navigationRequest) { + return (navigationRequest.url.contains('youtube.com')) ? NavigationDecision.prevent : NavigationDecision.navigate; - }, - onPageFinished: (String url) => pageLoads.add(url), - ), - ), - ); + })); + + await tester.pumpWidget(WebViewWidget(controller: controller)); + + controller.loadRequest(Uri.parse(blankPageEncoded)); + + await pageLoaded.future; // Wait for initial page load. - await pageLoads.stream.first; // Wait for initial page load. - final WebViewController controller = await controllerCompleter.future; + pageLoaded = Completer(); await controller - .runJavascript('location.href = "https://www.youtube.com/"'); + .runJavaScript('location.href = "https://www.youtube.com/"'); // There should never be any second page load, since our new URL is // blocked. Still wait for a potential page change for some time in order // to give the test a chance to fail. - await pageLoads.stream.first + await pageLoaded.future .timeout(const Duration(milliseconds: 500), onTimeout: () => ''); final String? currentUrl = await controller.currentUrl(); expect(currentUrl, isNot(contains('youtube.com'))); }); testWidgets('supports asynchronous decisions', (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - final StreamController pageLoads = - StreamController.broadcast(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: blankPageEncoded, - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - navigationDelegate: (NavigationRequest request) async { + Completer pageLoaded = Completer(); + + final WebViewController controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate(NavigationDelegate( + onPageFinished: (_) => pageLoaded.complete(), + onNavigationRequest: (NavigationRequest navigationRequest) async { NavigationDecision decision = NavigationDecision.prevent; decision = await Future.delayed( const Duration(milliseconds: 10), () => NavigationDecision.navigate); return decision; - }, - onPageFinished: (String url) => pageLoads.add(url), - ), - ), - ); + })); - await pageLoads.stream.first; // Wait for initial page load. - final WebViewController controller = await controllerCompleter.future; - await controller.runJavascript('location.href = "$secondaryUrl"'); + await tester.pumpWidget(WebViewWidget(controller: controller)); + + controller.loadRequest(Uri.parse(blankPageEncoded)); + + await pageLoaded.future; // Wait for initial page load. + + pageLoaded = Completer(); + await controller.runJavaScript('location.href = "$secondaryUrl"'); + await pageLoaded.future; // Wait for second page to load. - await pageLoads.stream.first; // Wait for second page to load. final String? currentUrl = await controller.currentUrl(); expect(currentUrl, secondaryUrl); }); }); + testWidgets('target _blank opens in same window', + (WidgetTester tester) async { + final Completer pageLoaded = Completer(); + + final WebViewController controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate(NavigationDelegate( + onPageFinished: (_) => pageLoaded.complete(), + )); + + await tester.pumpWidget(WebViewWidget(controller: controller)); + + await controller.runJavaScript('window.open("$primaryUrl", "_blank")'); + await pageLoaded.future; + final String? currentUrl = await controller.currentUrl(); + expect(currentUrl, primaryUrl); + }); + testWidgets( 'can open new window and go back', (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); Completer pageLoaded = Completer(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - onPageFinished: (String url) { - pageLoaded.complete(); - }, - initialUrl: primaryUrl, - ), - ), - ); - final WebViewController controller = await controllerCompleter.future; + + final WebViewController controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate(NavigationDelegate( + onPageFinished: (_) => pageLoaded.complete(), + )) + ..loadRequest(Uri.parse(primaryUrl)); + + await tester.pumpWidget(WebViewWidget(controller: controller)); + expect(controller.currentUrl(), completion(primaryUrl)); await pageLoaded.future; pageLoaded = Completer(); - await controller.runJavascript('window.open("$secondaryUrl")'); + await controller.runJavaScript('window.open("$secondaryUrl")'); await pageLoaded.future; pageLoaded = Completer(); expect(controller.currentUrl(), completion(secondaryUrl)); @@ -559,6 +716,70 @@ Future main() async { await expectLater(controller.currentUrl(), completion(primaryUrl)); }, ); + + testWidgets( + 'clearLocalStorage', + (WidgetTester tester) async { + Completer pageLoadCompleter = Completer(); + + final WebViewController controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate(NavigationDelegate( + onPageFinished: (_) => pageLoadCompleter.complete(), + )) + ..loadRequest(Uri.parse(primaryUrl)); + + await tester.pumpWidget(WebViewWidget(controller: controller)); + + await pageLoadCompleter.future; + pageLoadCompleter = Completer(); + + await controller.runJavaScript('localStorage.setItem("myCat", "Tom");'); + final String myCatItem = await controller.runJavaScriptReturningResult( + 'localStorage.getItem("myCat");', + ) as String; + expect(myCatItem, _webViewString('Tom')); + + await controller.clearLocalStorage(); + + // Reload page to have changes take effect. + await controller.reload(); + await pageLoadCompleter.future; + + late final String? nullItem; + try { + nullItem = await controller.runJavaScriptReturningResult( + 'localStorage.getItem("myCat");', + ) as String; + } catch (exception) { + if (defaultTargetPlatform == TargetPlatform.iOS && + exception is ArgumentError && + (exception.message as String).contains( + 'Result of JavaScript execution returned a `null` value.')) { + nullItem = ''; + } + } + expect(nullItem, _webViewNull()); + }, + ); +} + +// JavaScript `null` evaluate to different string values on Android and iOS. +// This utility method returns the string boolean value of the current platform. +String _webViewNull() { + if (defaultTargetPlatform == TargetPlatform.iOS) { + return ''; + } + return 'null'; +} + +// JavaScript String evaluate to different string values on Android and iOS. +// This utility method returns the string boolean value of the current platform. +String _webViewString(String value) { + if (defaultTargetPlatform == TargetPlatform.iOS) { + return value; + } + return '"$value"'; } /// Returns the value used for the HTTP User-Agent: request header in subsequent HTTP requests. @@ -567,16 +788,24 @@ Future _getUserAgent(WebViewController controller) async { } Future _runJavascriptReturningResult( - WebViewController controller, String js) async { - return controller.runJavascriptReturningResult(js); + WebViewController controller, + String js, +) async { + if (defaultTargetPlatform == TargetPlatform.iOS) { + return await controller.runJavaScriptReturningResult(js) as String; + } + return jsonDecode(await controller.runJavaScriptReturningResult(js) as String) + as String; } class ResizableWebView extends StatefulWidget { - const ResizableWebView( - {Key? key, required this.onResize, required this.onPageFinished}) - : super(key: key); + const ResizableWebView({ + super.key, + required this.onResize, + required this.onPageFinished, + }); - final JavascriptMessageHandler onResize; + final VoidCallback onResize; final VoidCallback onPageFinished; @override @@ -584,6 +813,23 @@ class ResizableWebView extends StatefulWidget { } class ResizableWebViewState extends State { + late final WebViewController controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate(NavigationDelegate( + onPageFinished: (_) => widget.onPageFinished(), + )) + ..addJavaScriptChannel( + 'Resize', + onMessageReceived: (_) { + widget.onResize(); + }, + ) + ..loadRequest( + Uri.parse( + 'data:text/html;charset=utf-8;base64,${base64Encode(const Utf8Encoder().convert(resizePage))}', + ), + ); + double webViewWidth = 200; double webViewHeight = 200; @@ -606,28 +852,14 @@ class ResizableWebViewState extends State { @override Widget build(BuildContext context) { - final String resizeTestBase64 = - base64Encode(const Utf8Encoder().convert(resizePage)); return Directionality( textDirection: TextDirection.ltr, child: Column( children: [ SizedBox( - width: webViewWidth, - height: webViewHeight, - child: WebView( - initialUrl: - 'data:text/html;charset=utf-8;base64,$resizeTestBase64', - javascriptChannels: { - JavascriptChannel( - name: 'Resize', - onMessageReceived: widget.onResize, - ), - }, - onPageFinished: (_) => widget.onPageFinished(), - javascriptMode: JavascriptMode.unrestricted, - ), - ), + width: webViewWidth, + height: webViewHeight, + child: WebViewWidget(controller: controller)), TextButton( key: const Key('resizeButton'), onPressed: () { diff --git a/packages/webview_flutter_lwe/example/lib/main.dart b/packages/webview_flutter_lwe/example/lib/main.dart index 9a586088e..37418136b 100644 --- a/packages/webview_flutter_lwe/example/lib/main.dart +++ b/packages/webview_flutter_lwe/example/lib/main.dart @@ -71,17 +71,63 @@ const String kTransparentBackgroundPage = ''' '''; class WebViewExample extends StatefulWidget { - const WebViewExample({Key? key, this.cookieManager}) : super(key: key); - - final CookieManager? cookieManager; + const WebViewExample({super.key}); @override State createState() => _WebViewExampleState(); } class _WebViewExampleState extends State { - final Completer _controller = - Completer(); + final WebViewController _controller = WebViewController(); + + @override + void initState() { + super.initState(); + + _controller + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setBackgroundColor(const Color(0x00000000)) + ..setNavigationDelegate( + NavigationDelegate( + onProgress: (int progress) { + debugPrint('WebView is loading (progress : $progress%)'); + }, + onPageStarted: (String url) { + debugPrint('Page started loading: $url'); + }, + onPageFinished: (String url) { + debugPrint('Page finished loading: $url'); + }, + onWebResourceError: (WebResourceError error) { + debugPrint(''' +Page resource error: + code: ${error.errorCode} + description: ${error.description} + errorType: ${error.errorType} + isForMainFrame: ${error.isForMainFrame} + '''); + }, + onNavigationRequest: (NavigationRequest request) { + debugPrint('On onNavigationReques'); + if (request.url.startsWith('https://www.youtube.com/')) { + debugPrint('blocking navigation to ${request.url}'); + return NavigationDecision.prevent; + } + debugPrint('allowing navigation to ${request.url}'); + return NavigationDecision.navigate; + }, + ), + ) + ..addJavaScriptChannel( + 'Toaster', + onMessageReceived: (JavaScriptMessage message) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(message.message)), + ); + }, + ) + ..loadRequest(Uri.parse('https://flutter.dev')); + } @override Widget build(BuildContext context) { @@ -91,79 +137,27 @@ class _WebViewExampleState extends State { title: const Text('Flutter WebView example'), // This drop down menu demonstrates that Flutter widgets can be shown over the web view. actions: [ - NavigationControls(_controller.future), - SampleMenu(_controller.future, widget.cookieManager), + NavigationControls(webViewController: _controller), + SampleMenu(webViewController: _controller), ], ), - body: WebView( - initialUrl: 'https://flutter.dev', - javascriptMode: JavascriptMode.unrestricted, - onWebViewCreated: (WebViewController webViewController) { - _controller.complete(webViewController); - }, - onProgress: (int progress) { - print('WebView is loading (progress : $progress%)'); - }, - javascriptChannels: { - _toasterJavascriptChannel(context), - }, - navigationDelegate: (NavigationRequest request) { - if (request.url.startsWith('https://www.youtube.com/')) { - print('blocking navigation to $request}'); - return NavigationDecision.prevent; - } - print('allowing navigation to $request'); - return NavigationDecision.navigate; - }, - onPageStarted: (String url) { - print('Page started loading: $url'); - }, - onPageFinished: (String url) { - print('Page finished loading: $url'); - }, - gestureNavigationEnabled: true, - backgroundColor: const Color(0x00000000), - ), + body: WebViewWidget(controller: _controller), floatingActionButton: favoriteButton(), ); } - JavascriptChannel _toasterJavascriptChannel(BuildContext context) { - return JavascriptChannel( - name: 'Toaster', - onMessageReceived: (JavascriptMessage message) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(message.message)), - ); - }); - } - Widget favoriteButton() { - return FutureBuilder( - future: _controller.future, - builder: (BuildContext context, - AsyncSnapshot controller) { - return FloatingActionButton( - onPressed: () async { - String? url; - if (controller.hasData) { - url = await controller.data!.currentUrl(); - } - if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - controller.hasData - ? 'Favorited $url' - : 'Unable to favorite', - ), - ), - ); - } - }, - child: const Icon(Icons.favorite), + return FloatingActionButton( + onPressed: () async { + final String? url = await _controller.currentUrl(); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Favorited $url')), ); - }); + } + }, + child: const Icon(Icons.favorite), + ); } } @@ -184,137 +178,130 @@ enum MenuOptions { } class SampleMenu extends StatelessWidget { - SampleMenu(this.controller, CookieManager? cookieManager, {Key? key}) - : cookieManager = cookieManager ?? CookieManager(), - super(key: key); + SampleMenu({ + super.key, + required this.webViewController, + }); - final Future controller; - late final CookieManager cookieManager; + final WebViewController webViewController; + late final WebViewCookieManager cookieManager = WebViewCookieManager(); @override Widget build(BuildContext context) { - return FutureBuilder( - future: controller, - builder: - (BuildContext context, AsyncSnapshot controller) { - return PopupMenuButton( - key: const ValueKey('ShowPopupMenu'), - onSelected: (MenuOptions value) { - switch (value) { - case MenuOptions.showUserAgent: - _onShowUserAgent(controller.data!, context); - break; - case MenuOptions.listCookies: - _onListCookies(controller.data!, context); - break; - case MenuOptions.clearCookies: - _onClearCookies(context); - break; - case MenuOptions.addToCache: - _onAddToCache(controller.data!, context); - break; - case MenuOptions.listCache: - _onListCache(controller.data!, context); - break; - case MenuOptions.clearCache: - _onClearCache(controller.data!, context); - break; - case MenuOptions.navigationDelegate: - _onNavigationDelegateExample(controller.data!, context); - break; - case MenuOptions.doPostRequest: - _onDoPostRequest(controller.data!, context); - break; - case MenuOptions.loadLocalFile: - _onLoadLocalFileExample(controller.data!, context); - break; - case MenuOptions.loadFlutterAsset: - _onLoadFlutterAssetExample(controller.data!, context); - break; - case MenuOptions.loadHtmlString: - _onLoadHtmlStringExample(controller.data!, context); - break; - case MenuOptions.transparentBackground: - _onTransparentBackground(controller.data!, context); - break; - case MenuOptions.setCookie: - _onSetCookie(controller.data!, context); - break; - } - }, - itemBuilder: (BuildContext context) => >[ - PopupMenuItem( - value: MenuOptions.showUserAgent, - enabled: controller.hasData, - child: const Text('Show user agent'), - ), - const PopupMenuItem( - value: MenuOptions.listCookies, - child: Text('List cookies'), - ), - const PopupMenuItem( - value: MenuOptions.clearCookies, - child: Text('Clear cookies'), - ), - const PopupMenuItem( - value: MenuOptions.addToCache, - child: Text('Add to cache'), - ), - const PopupMenuItem( - value: MenuOptions.listCache, - child: Text('List cache'), - ), - const PopupMenuItem( - value: MenuOptions.clearCache, - child: Text('Clear cache'), - ), - const PopupMenuItem( - value: MenuOptions.navigationDelegate, - child: Text('Navigation Delegate example'), - ), - const PopupMenuItem( - value: MenuOptions.doPostRequest, - child: Text('Post Request'), - ), - const PopupMenuItem( - value: MenuOptions.loadHtmlString, - child: Text('Load HTML string'), - ), - const PopupMenuItem( - value: MenuOptions.loadLocalFile, - child: Text('Load local file'), - ), - const PopupMenuItem( - value: MenuOptions.loadFlutterAsset, - child: Text('Load Flutter Asset'), - ), - const PopupMenuItem( - key: ValueKey('ShowTransparentBackgroundExample'), - value: MenuOptions.transparentBackground, - child: Text('Transparent background example'), - ), - const PopupMenuItem( - value: MenuOptions.setCookie, - child: Text('Set cookie'), - ), - ], - ); + return PopupMenuButton( + key: const ValueKey('ShowPopupMenu'), + onSelected: (MenuOptions value) { + switch (value) { + case MenuOptions.showUserAgent: + _onShowUserAgent(); + break; + case MenuOptions.listCookies: + _onListCookies(context); + break; + case MenuOptions.clearCookies: + _onClearCookies(context); + break; + case MenuOptions.addToCache: + _onAddToCache(context); + break; + case MenuOptions.listCache: + _onListCache(); + break; + case MenuOptions.clearCache: + _onClearCache(context); + break; + case MenuOptions.navigationDelegate: + _onNavigationDelegateExample(); + break; + case MenuOptions.doPostRequest: + _onDoPostRequest(); + break; + case MenuOptions.loadLocalFile: + _onLoadLocalFileExample(); + break; + case MenuOptions.loadFlutterAsset: + _onLoadFlutterAssetExample(); + break; + case MenuOptions.loadHtmlString: + _onLoadHtmlStringExample(); + break; + case MenuOptions.transparentBackground: + _onTransparentBackground(); + break; + case MenuOptions.setCookie: + _onSetCookie(); + break; + } }, + itemBuilder: (BuildContext context) => >[ + const PopupMenuItem( + value: MenuOptions.showUserAgent, + child: Text('Show user agent'), + ), + const PopupMenuItem( + value: MenuOptions.listCookies, + child: Text('List cookies'), + ), + const PopupMenuItem( + value: MenuOptions.clearCookies, + child: Text('Clear cookies'), + ), + const PopupMenuItem( + value: MenuOptions.addToCache, + child: Text('Add to cache'), + ), + const PopupMenuItem( + value: MenuOptions.listCache, + child: Text('List cache'), + ), + const PopupMenuItem( + value: MenuOptions.clearCache, + child: Text('Clear cache'), + ), + const PopupMenuItem( + value: MenuOptions.navigationDelegate, + child: Text('Navigation Delegate example'), + ), + const PopupMenuItem( + value: MenuOptions.doPostRequest, + child: Text('Post Request'), + ), + const PopupMenuItem( + value: MenuOptions.loadHtmlString, + child: Text('Load HTML string'), + ), + const PopupMenuItem( + value: MenuOptions.loadLocalFile, + child: Text('Load local file'), + ), + const PopupMenuItem( + value: MenuOptions.loadFlutterAsset, + child: Text('Load Flutter Asset'), + ), + const PopupMenuItem( + key: ValueKey('ShowTransparentBackgroundExample'), + value: MenuOptions.transparentBackground, + child: Text('Transparent background example'), + ), + const PopupMenuItem( + value: MenuOptions.setCookie, + child: Text('Set cookie'), + ), + ], ); } - Future _onShowUserAgent( - WebViewController controller, BuildContext context) async { + Future _onShowUserAgent() { // Send a message with the user agent string to the Toaster JavaScript channel we registered // with the WebView. - await controller.runJavascript( - 'Toaster.postMessage("User Agent: " + navigator.userAgent);'); + return webViewController.runJavaScript( + 'Toaster.postMessage("User Agent: " + navigator.userAgent);', + ); } - Future _onListCookies( - WebViewController controller, BuildContext context) async { - final String cookies = - await controller.runJavascriptReturningResult('document.cookie'); + Future _onListCookies(BuildContext context) async { + final String cookies = await webViewController + .runJavaScriptReturningResult('document.cookie') as String; if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Column( @@ -329,10 +316,10 @@ class SampleMenu extends StatelessWidget { } } - Future _onAddToCache( - WebViewController controller, BuildContext context) async { - await controller.runJavascript( - 'caches.open("test_caches_entry"); localStorage["test_localStorage"] = "dummy_entry";'); + Future _onAddToCache(BuildContext context) async { + await webViewController.runJavaScript( + 'caches.open("test_caches_entry"); localStorage["test_localStorage"] = "dummy_entry";', + ); if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar( content: Text('Added a test entry to cache.'), @@ -340,17 +327,17 @@ class SampleMenu extends StatelessWidget { } } - Future _onListCache( - WebViewController controller, BuildContext context) async { - await controller.runJavascript('caches.keys()' + Future _onListCache() { + return webViewController.runJavaScript('caches.keys()' // ignore: missing_whitespace_between_adjacent_strings '.then((cacheKeys) => JSON.stringify({"cacheKeys" : cacheKeys, "localStorage" : localStorage}))' '.then((caches) => Toaster.postMessage(caches))'); } - Future _onClearCache( - WebViewController controller, BuildContext context) async { - await controller.clearCache(); + Future _onClearCache(BuildContext context) async { + await webViewController.clearCache(); + // This is unimplemented in webview_flutter_tizen. + // await webViewController.clearLocalStorage(); if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar( content: Text('Cache cleared.'), @@ -371,53 +358,53 @@ class SampleMenu extends StatelessWidget { } } - Future _onNavigationDelegateExample( - WebViewController controller, BuildContext context) async { - final String contentBase64 = - base64Encode(const Utf8Encoder().convert(kNavigationExamplePage)); - await controller.loadUrl('data:text/html;base64,$contentBase64'); + Future _onNavigationDelegateExample() { + final String contentBase64 = base64Encode( + const Utf8Encoder().convert(kNavigationExamplePage), + ); + return webViewController.loadRequest( + Uri.parse('data:text/html;base64,$contentBase64'), + ); } - Future _onSetCookie( - WebViewController controller, BuildContext context) async { + Future _onSetCookie() async { await cookieManager.setCookie( const WebViewCookie( - name: 'foo', value: 'bar', domain: 'httpbin.org', path: '/anything'), + name: 'foo', + value: 'bar', + domain: 'httpbin.org', + path: '/anything', + ), ); - await controller.loadUrl('https://httpbin.org/anything'); + await webViewController.loadRequest(Uri.parse( + 'https://httpbin.org/anything', + )); } - Future _onDoPostRequest( - WebViewController controller, BuildContext context) async { - final WebViewRequest request = WebViewRequest( - uri: Uri.parse('https://httpbin.org/post'), - method: WebViewRequestMethod.post, + Future _onDoPostRequest() { + return webViewController.loadRequest( + Uri.parse('https://httpbin.org/post'), + method: LoadRequestMethod.post, headers: {'foo': 'bar', 'Content-Type': 'text/plain'}, body: Uint8List.fromList('Test Body'.codeUnits), ); - await controller.loadRequest(request); } - Future _onLoadLocalFileExample( - WebViewController controller, BuildContext context) async { + Future _onLoadLocalFileExample() async { final String pathToIndex = await _prepareLocalFile(); - - await controller.loadFile(pathToIndex); + await webViewController.loadFile(pathToIndex); } - Future _onLoadFlutterAssetExample( - WebViewController controller, BuildContext context) async { - await controller.loadFlutterAsset('assets/www/index.html'); + Future _onLoadFlutterAssetExample() { + return webViewController.loadFlutterAsset('assets/www/index.html'); } - Future _onLoadHtmlStringExample( - WebViewController controller, BuildContext context) async { - await controller.loadHtmlString(kLocalExamplePage); + Future _onLoadHtmlStringExample() { + return webViewController.loadHtmlString(kLocalExamplePage); } - Future _onTransparentBackground( - WebViewController controller, BuildContext context) async { - await controller.loadHtmlString(kTransparentBackgroundPage); + Future _onTransparentBackground() { + return webViewController.loadHtmlString(kTransparentBackgroundPage); } Widget _getCookieList(String cookies) { @@ -447,70 +434,47 @@ class SampleMenu extends StatelessWidget { } class NavigationControls extends StatelessWidget { - const NavigationControls(this._webViewControllerFuture, {Key? key}) - : assert(_webViewControllerFuture != null), - super(key: key); + const NavigationControls({super.key, required this.webViewController}); - final Future _webViewControllerFuture; + final WebViewController webViewController; @override Widget build(BuildContext context) { - return FutureBuilder( - future: _webViewControllerFuture, - builder: - (BuildContext context, AsyncSnapshot snapshot) { - final bool webViewReady = - snapshot.connectionState == ConnectionState.done; - final WebViewController? controller = snapshot.data; - return Row( - children: [ - IconButton( - icon: const Icon(Icons.arrow_back_ios), - onPressed: !webViewReady - ? null - : () async { - if (await controller!.canGoBack()) { - await controller.goBack(); - } else { - if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('No back history item')), - ); - } - return; - } - }, - ), - IconButton( - icon: const Icon(Icons.arrow_forward_ios), - onPressed: !webViewReady - ? null - : () async { - if (await controller!.canGoForward()) { - await controller.goForward(); - } else { - if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('No forward history item')), - ); - } - return; - } - }, - ), - IconButton( - icon: const Icon(Icons.replay), - onPressed: !webViewReady - ? null - : () { - controller!.reload(); - }, - ), - ], - ); - }, + return Row( + children: [ + IconButton( + icon: const Icon(Icons.arrow_back_ios), + onPressed: () async { + if (await webViewController.canGoBack()) { + await webViewController.goBack(); + } else { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('No back history item')), + ); + } + } + }, + ), + IconButton( + icon: const Icon(Icons.arrow_forward_ios), + onPressed: () async { + if (await webViewController.canGoForward()) { + await webViewController.goForward(); + } else { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('No forward history item')), + ); + } + } + }, + ), + IconButton( + icon: const Icon(Icons.replay), + onPressed: () => webViewController.reload(), + ), + ], ); } } diff --git a/packages/webview_flutter_lwe/example/pubspec.yaml b/packages/webview_flutter_lwe/example/pubspec.yaml index e2cc16af9..3cb7265ad 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: ">=2.14.0 <3.0.0" - flutter: ">=2.8.0" + sdk: ">=2.17.0 <3.0.0" + flutter: ">=3.0.0" dependencies: flutter: @@ -12,7 +12,7 @@ dependencies: path_provider: ^2.0.7 path_provider_tizen: path: ../../path_provider/ - webview_flutter: ^3.0.4 + webview_flutter: ^4.0.2 webview_flutter_lwe: path: ../ diff --git a/packages/webview_flutter_lwe/lib/src/tizen_webview.dart b/packages/webview_flutter_lwe/lib/src/tizen_webview.dart new file mode 100644 index 000000000..9ccf01c36 --- /dev/null +++ b/packages/webview_flutter_lwe/lib/src/tizen_webview.dart @@ -0,0 +1,199 @@ +// Copyright 2023 Samsung Electronics Co., Ltd. All rights reserved. +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:flutter/services.dart'; +import 'package:flutter_tizen/widgets.dart'; +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; + +/// The channel name of [TizenWebView]. +const String kTizenWebViewChannelName = 'plugins.flutter.io/tizen_webview_'; + +/// A Tizen webview that displays web pages. +class TizenWebView { + /// Whether the [TizenNavigationDelegate] is set by the [PlatformWebViewController]. + /// + /// Defaults to false. + bool hasNavigationDelegate = false; + + late final MethodChannel _tizenWebViewChannel; + bool _isCreated = false; + + final Map _javaScriptChannelParams = + {}; + final Map _pendingMethodCalls = {}; + + Future _onMethodCall(MethodCall call) async { + switch (call.method) { + case 'javaScriptChannelMessage': + final Map arguments = + (call.arguments as Map).cast(); + final String channel = arguments['channel']! as String; + final String message = arguments['message']! as String; + if (_javaScriptChannelParams.containsKey(channel)) { + _javaScriptChannelParams[channel] + ?.onMessageReceived(JavaScriptMessage(message: message)); + } + + return true; + } + + throw MissingPluginException( + '${call.method} was invoked but has no handler', + ); + } + + Future _invokeChannelMethod(String method, [dynamic arguments]) async { + if (!_isCreated) { + _pendingMethodCalls[method] = arguments; + return null; + } + + return _tizenWebViewChannel.invokeMethod(method, arguments); + } + + /// Called when [TizenView] is created. Invokes the requested method call before [TizenWebView] is created. + void onCreate(int viewId) { + _isCreated = true; + _tizenWebViewChannel = + MethodChannel(kTizenWebViewChannelName + viewId.toString()); + _tizenWebViewChannel.setMethodCallHandler(_onMethodCall); + + _callPendingMethodCalls(); + } + + /// Applies the requested settings before [TizenView] is created. + void _callPendingMethodCalls() { + if (hasNavigationDelegate) { + _invokeChannelMethod( + 'hasNavigationDelegate', hasNavigationDelegate); + } + + _pendingMethodCalls.forEach((String method, dynamic arguments) { + _tizenWebViewChannel.invokeMethod(method, arguments); + }); + _pendingMethodCalls.clear(); + } + + /// Loads the file located on the specified [absoluteFilePath]. + Future loadFile(String absoluteFilePath) { + return _invokeChannelMethod('loadFile', absoluteFilePath); + } + + /// Loads the Flutter asset specified in the pubspec.yaml file. + Future loadFlutterAsset(String key) { + return _invokeChannelMethod('loadFlutterAsset', key); + } + + /// Loads the supplied HTML string. + Future loadHtmlString( + String html, { + String? baseUrl, + }) { + return _invokeChannelMethod('loadHtmlString', { + 'html': html, + 'baseUrl': baseUrl, + }); + } + + /// Makes a specific HTTP request ands loads the response in the webview. + Future loadRequest(String uri) { + return _invokeChannelMethod( + 'loadRequest', {'url': uri}); + } + + /// Accessor to the current URL that the WebView is displaying. + Future currentUrl() => _invokeChannelMethod('currentUrl'); + + /// Checks whether there's a back history item. + Future canGoBack() async { + return await _invokeChannelMethod('canGoBack') ?? false; + } + + /// Checks whether there's a forward history item. + Future canGoForward() async { + return await _invokeChannelMethod('canGoForward') ?? false; + } + + /// Goes back in the history of this WebView. + Future goBack() => _invokeChannelMethod('goBack'); + + /// Goes forward in the history of this WebView. + Future goForward() => _invokeChannelMethod('goForward'); + + /// Reloads the current URL. + Future reload() => _invokeChannelMethod('reload'); + + /// Clears all caches used by the [WebView]. + Future clearCache() => _invokeChannelMethod('clearCache'); + + /// Sets the JavaScript execution mode to be used by the webview. + Future setJavaScriptMode(int javaScriptMode) => + _invokeChannelMethod('javaScriptMode', javaScriptMode); + + /// Returns the title of the currently loaded page. + Future getTitle() => _invokeChannelMethod('getTitle'); + + /// Sets the scrolled position of this view. + Future scrollTo(int x, int y) => + _invokeChannelMethod('scrollTo', { + 'x': x, + 'y': y, + }); + + /// Moves the scrolled position of this view. + Future scrollBy(int x, int y) => + _invokeChannelMethod('scrollBy', { + 'x': x, + 'y': y, + }); + + /// Returns the scroll position of this view set by [scrollTo]. + Future getScrollPosition() async { + final Map? position = + (await _invokeChannelMethod>('getScrollPosition')) + ?.cast(); + if (position == null) { + return Offset.zero; + } + return Offset(position['x']! as double, position['y']! as double); + } + + /// Sets the background color of this WebView. + Future setBackgroundColor(Color color) => + _invokeChannelMethod('backgroundColor', color.value); + + /// Adds a new JavaScript channel to the set of enabled channels. + Future addJavaScriptChannel( + JavaScriptChannelParams javaScriptChannelParams) { + _javaScriptChannelParams[javaScriptChannelParams.name] = + javaScriptChannelParams; + return _invokeChannelMethod( + 'addJavaScriptChannel', javaScriptChannelParams.name); + } + + /// Runs the given JavaScript in the context of the current page. + Future runJavaScript(String javaScript) => + _invokeChannelMethod('runJavaScript', javaScript); + + /// Runs the given JavaScript in the context of the current page, and returns the result. + Future runJavaScriptReturningResult(String javaScript) async { + final String? result = await _invokeChannelMethod( + 'runJavaScriptReturningResult', javaScript); + if (result == null) { + return ''; + } else if (result == 'true') { + return true; + } else if (result == 'false') { + return false; + } + return num.tryParse(result) ?? result; + } + + /// Sets the value used for the HTTP `User-Agent:` request header. + Future setUserAgent(String? userAgent) => + _invokeChannelMethod('userAgent', userAgent); +} diff --git a/packages/webview_flutter_lwe/lib/src/tizen_webview_controller.dart b/packages/webview_flutter_lwe/lib/src/tizen_webview_controller.dart new file mode 100644 index 000000000..707c2dbc1 --- /dev/null +++ b/packages/webview_flutter_lwe/lib/src/tizen_webview_controller.dart @@ -0,0 +1,377 @@ +// Copyright 2023 Samsung Electronics Co., Ltd. All rights reserved. +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_tizen/widgets.dart'; +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; + +import 'tizen_webview.dart'; + +/// The channel name of [TizenNavigationDelegate]. +const String kTizenNavigationDelegateChannelName = + 'plugins.flutter.io/tizen_webview_navigation_delegate_'; + +/// An implementation of [PlatformWebViewController] the Tizen WebView API. +class TizenWebViewController extends PlatformWebViewController { + /// Constructs a [TizenWebViewController]. + TizenWebViewController(super.params) + : _webview = TizenWebView(), + super.implementation(); + + final TizenWebView _webview; + late TizenNavigationDelegate _tizenNavigationDelegate; + + /// Called when [TizenView] is created. + void onCreate(int viewId) { + if (_webview.hasNavigationDelegate) { + _tizenNavigationDelegate.onCreate(viewId); + } + _webview.onCreate(viewId); + } + + @override + Future loadFile(String absoluteFilePath) { + assert(absoluteFilePath != null); + return _webview.loadFile(absoluteFilePath); + } + + @override + Future loadFlutterAsset(String key) { + assert(key.isNotEmpty); + return _webview.loadFlutterAsset(key); + } + + @override + Future loadHtmlString( + String html, { + String? baseUrl, + }) { + assert(html != null); + return _webview.loadHtmlString(html, baseUrl: baseUrl); + } + + @override + Future loadRequest(LoadRequestParams params) { + if (!params.uri.hasScheme) { + throw ArgumentError( + 'LoadRequestParams#uri is required to have a scheme.'); + } + + switch (params.method) { + case LoadRequestMethod.get: + return _webview.loadRequest(params.uri.toString()); + case LoadRequestMethod.post: + break; + } + // The enum comes from a different package, which could get a new value at + // any time, so a fallback case is necessary. Since there is no reasonable + // default behavior, throw to alert the client that they need an updated + // version. This is deliberately outside the switch rather than a `default` + // so that the linter will flag the switch as needing an update. + // ignore: dead_code + throw UnimplementedError( + 'This version of `TizenWebViewController` currently has no ' + 'implementation for HTTP method ${params.method.serialize()} in ' + 'loadRequest.'); + } + + @override + Future currentUrl() => _webview.currentUrl(); + + @override + Future canGoBack() => _webview.canGoBack(); + + @override + Future canGoForward() => _webview.canGoForward(); + + @override + Future goBack() => _webview.goBack(); + + @override + Future goForward() => _webview.goForward(); + + @override + Future reload() => _webview.reload(); + + @override + Future clearCache() => _webview.clearCache(); + + @override + Future clearLocalStorage() { + throw UnimplementedError( + 'This version of `TizenWebViewController` currently has no ' + 'implementation.'); + } + + @override + Future setJavaScriptMode(JavaScriptMode javaScriptMode) => + _webview.setJavaScriptMode(javaScriptMode.index); + + @override + Future getTitle() => _webview.getTitle(); + + @override + Future scrollTo(int x, int y) => _webview.scrollTo(x, y); + + @override + Future scrollBy(int x, int y) => _webview.scrollBy(x, y); + + @override + Future getScrollPosition() => _webview.getScrollPosition(); + + @override + Future setBackgroundColor(Color color) => + _webview.setBackgroundColor(color); + + @override + Future setPlatformNavigationDelegate( + covariant TizenNavigationDelegate handler) async { + _tizenNavigationDelegate = handler; + _webview.hasNavigationDelegate = true; + } + + @override + Future addJavaScriptChannel( + JavaScriptChannelParams javaScriptChannelParams) => + _webview.addJavaScriptChannel(javaScriptChannelParams); + + @override + Future runJavaScript(String javaScript) => + _webview.runJavaScript(javaScript); + + @override + Future runJavaScriptReturningResult(String javaScript) => + _webview.runJavaScriptReturningResult(javaScript); + + @override + Future setUserAgent(String? userAgent) => + _webview.setUserAgent(userAgent); +} + +/// An implementation of [PlatformWebViewWidget] with the Tizen WebView API. +class TizenWebViewWidget extends PlatformWebViewWidget { + /// Constructs a [TizenWebViewWidget]. + TizenWebViewWidget(super.params) : super.implementation(); + + @override + Widget build(BuildContext context) { + return TizenView( + key: params.key, + viewType: 'plugins.flutter.io/webview', + onPlatformViewCreated: (int id) { + final TizenWebViewController controller = + params.controller as TizenWebViewController; + controller.onCreate(id); + }, + layoutDirection: params.layoutDirection, + gestureRecognizers: params.gestureRecognizers, + ); + } +} + +/// Error returned in `WebView.onWebResourceError` when a web resource loading error has occurred. +@immutable +class TizenWebResourceError extends WebResourceError { + /// Creates a new [TizenWebResourceError]. + TizenWebResourceError._({ + required super.errorCode, + required super.description, + super.isForMainFrame, + this.failingUrl, + }) : super(errorType: _errorCodeToErrorType(errorCode)); + + /// Unknown error. + static const int unknown = 0; + + /// Failed to file I/O. + static const int failedFileIO = 3; + + /// Cannot connect to Network. + static const int cantConnect = 4; + + /// Fail to look up host from DNS. + static const int cantHostLookup = 5; + + /// Fail to SSL/TLS handshake. + static const int failedSslHandshake = 6; + + /// Connection timeout. + static const int requestTimeout = 8; + + /// Too many redirects. + static const int tooManyRedirect = 9; + + /// Too many requests during this load. + static const int tooManyRequests = 10; + + /// Malformed url. + static const int badUrl = 11; + + /// Unsupported scheme + static const int unsupportedScheme = 12; + + /// User authentication failed on server. + static const int authenticationFailed = 13; + + /// Gets the URL for which the failing resource request was made. + final String? failingUrl; + + static WebResourceErrorType? _errorCodeToErrorType(int errorCode) { + switch (errorCode) { + case unknown: + return WebResourceErrorType.unknown; + case failedFileIO: + return WebResourceErrorType.file; + case cantConnect: + return WebResourceErrorType.connect; + case cantHostLookup: + return WebResourceErrorType.hostLookup; + case failedSslHandshake: + return WebResourceErrorType.failedSslHandshake; + case requestTimeout: + return WebResourceErrorType.timeout; + case tooManyRedirect: + return WebResourceErrorType.redirectLoop; + case tooManyRequests: + return WebResourceErrorType.tooManyRequests; + case badUrl: + return WebResourceErrorType.badUrl; + case unsupportedScheme: + return WebResourceErrorType.unsupportedScheme; + case authenticationFailed: + return WebResourceErrorType.authentication; + } + + throw ArgumentError( + 'Could not find a WebResourceErrorType for errorCode: $errorCode', + ); + } +} + +/// A place to register callback methods responsible to handle navigation events +/// triggered by the [TizenWebView]. +class TizenNavigationDelegate extends PlatformNavigationDelegate { + /// Creates a new [TizenNavigationDelegate]. + TizenNavigationDelegate(super.params) : super.implementation(); + + late final MethodChannel _navigationDelegateChannel; + PageEventCallback? _onPageFinished; + PageEventCallback? _onPageStarted; + ProgressCallback? _onProgress; + WebResourceErrorCallback? _onWebResourceError; + NavigationRequestCallback? _onNavigationRequest; + + /// Called when [TizenView] is created. + void onCreate(int viewId) { + _navigationDelegateChannel = + MethodChannel(kTizenNavigationDelegateChannelName + viewId.toString()); + _navigationDelegateChannel.setMethodCallHandler((MethodCall call) async { + final Map arguments = + (call.arguments as Map).cast(); + switch (call.method) { + case 'navigationRequest': + return _handleNavigation(arguments['url']! as String, + isForMainFrame: arguments['isForMainFrame']! as bool); + case 'onPageFinished': + if (_onPageFinished != null) { + _onPageFinished!(arguments['url']! as String); + } + return null; + case 'onProgress': + if (_onProgress != null) { + _onProgress!(arguments['progress']! as int); + } + return null; + case 'onPageStarted': + if (_onPageStarted != null) { + _onPageStarted!(arguments['url']! as String); + } + return null; + case 'onWebResourceError': + if (_onWebResourceError != null) { + _onWebResourceError!(TizenWebResourceError._( + errorCode: arguments['errorCode']! as int, + description: arguments['description']! as String, + failingUrl: arguments['failingUrl']! as String, + isForMainFrame: true, + )); + } + return null; + } + + throw MissingPluginException( + '${call.method} was invoked but has no handler', + ); + }); + } + + Future _handleNavigation( + String url, { + required bool isForMainFrame, + }) async { + final NavigationRequestCallback? onNavigationRequest = _onNavigationRequest; + + if (onNavigationRequest == null) { + return true; + } + + final FutureOr returnValue = + onNavigationRequest(NavigationRequest( + url: url, + isMainFrame: isForMainFrame, + )); + + if (returnValue is NavigationDecision && + returnValue == NavigationDecision.navigate) { + return true; + } else if (returnValue is Future) { + return returnValue.then((NavigationDecision shouldLoadUrl) { + if (shouldLoadUrl == NavigationDecision.navigate) { + return true; + } + return false; + }); + } + return false; + } + + @override + Future setOnNavigationRequest( + NavigationRequestCallback onNavigationRequest, + ) async { + _onNavigationRequest = onNavigationRequest; + } + + @override + Future setOnPageStarted( + PageEventCallback onPageStarted, + ) async { + _onPageStarted = onPageStarted; + } + + @override + Future setOnPageFinished( + PageEventCallback onPageFinished, + ) async { + _onPageFinished = onPageFinished; + } + + @override + Future setOnProgress( + ProgressCallback onProgress, + ) async { + _onProgress = onProgress; + } + + @override + Future setOnWebResourceError( + WebResourceErrorCallback onWebResourceError, + ) async { + _onWebResourceError = onWebResourceError; + } +} diff --git a/packages/webview_flutter_lwe/lib/src/tizen_webview_cookie_manager.dart b/packages/webview_flutter_lwe/lib/src/tizen_webview_cookie_manager.dart new file mode 100644 index 000000000..e5ac2afb2 --- /dev/null +++ b/packages/webview_flutter_lwe/lib/src/tizen_webview_cookie_manager.dart @@ -0,0 +1,44 @@ +// Copyright 2023 Samsung Electronics Co., Ltd. All rights reserved. +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/services.dart'; + +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; + +/// Handles all cookie operations for the Tizen platform. +class TizenWebViewCookieManager extends PlatformWebViewCookieManager { + /// Creates a new [TizenWebViewCookieManager]. + TizenWebViewCookieManager(super.params) : super.implementation(); + + static const MethodChannel _cookieManagerChannel = + MethodChannel('plugins.flutter.io/tizen_cookie_manager'); + + @override + Future clearCookies() async { + return await _cookieManagerChannel.invokeMethod('clearCookies') ?? + false; + } + + @override + Future setCookie(WebViewCookie cookie) async { + if (!_isValidPath(cookie.path)) { + throw ArgumentError( + 'The path property for the provided cookie was not given a legal value.'); + } + throw UnimplementedError( + 'This version of `TizenWebViewCookieManager` currently has no ' + 'implementation for setCookie method.'); + } + + bool _isValidPath(String path) { + // Permitted ranges based on RFC6265bis: https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-02#section-4.1.1 + for (final int char in path.codeUnits) { + if ((char < 0x20 || char > 0x3A) && (char < 0x3C || char > 0x7E)) { + return false; + } + } + return true; + } +} diff --git a/packages/webview_flutter_lwe/lib/src/tizen_webview_platform.dart b/packages/webview_flutter_lwe/lib/src/tizen_webview_platform.dart new file mode 100644 index 000000000..9cb852fa1 --- /dev/null +++ b/packages/webview_flutter_lwe/lib/src/tizen_webview_platform.dart @@ -0,0 +1,45 @@ +// Copyright 2023 Samsung Electronics Co., Ltd. All rights reserved. +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; + +import 'tizen_webview_controller.dart'; +import 'tizen_webview_cookie_manager.dart'; + +/// An implementation of [WebViewPlatform] using the Tizen WebView API. +class LweWebViewPlatform extends WebViewPlatform { + @override + PlatformWebViewController createPlatformWebViewController( + PlatformWebViewControllerCreationParams params, + ) { + return TizenWebViewController(params); + } + + @override + PlatformNavigationDelegate createPlatformNavigationDelegate( + PlatformNavigationDelegateCreationParams params, + ) { + return TizenNavigationDelegate(params); + } + + @override + PlatformWebViewWidget createPlatformWebViewWidget( + PlatformWebViewWidgetCreationParams params, + ) { + return TizenWebViewWidget(params); + } + + @override + PlatformWebViewCookieManager createPlatformCookieManager( + PlatformWebViewCookieManagerCreationParams params, + ) { + return TizenWebViewCookieManager(params); + } + + /// Gets called when the plugin is registered. + static void register() { + WebViewPlatform.instance = LweWebViewPlatform(); + } +} diff --git a/packages/webview_flutter_lwe/lib/webview_flutter_lwe.dart b/packages/webview_flutter_lwe/lib/webview_flutter_lwe.dart index 95858ec0b..b2f7b7dcb 100644 --- a/packages/webview_flutter_lwe/lib/webview_flutter_lwe.dart +++ b/packages/webview_flutter_lwe/lib/webview_flutter_lwe.dart @@ -1,94 +1,10 @@ -// Copyright 2021 Samsung Electronics Co., Ltd. All rights reserved. +// Copyright 2023 Samsung Electronics Co., Ltd. All rights reserved. // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:async'; +library webview_flutter_tizen; -import 'package:flutter/foundation.dart'; -import 'package:flutter/gestures.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter/widgets.dart'; -import 'package:flutter_tizen/widgets.dart'; -import 'package:webview_flutter/webview_flutter.dart'; -import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; - -/// Builds a Tizen webview. -/// -/// This is used as the default implementation for [WebView.platform] on Tizen. It uses a method channel to -/// communicate with the platform code. -class LweWebView implements WebViewPlatform { - /// Sets a tizen [WebViewPlatform]. - static void register() { - WebView.platform = LweWebView(); - WebViewCookieManagerPlatform.instance = WebViewTizenCookieManager(); - } - - @override - Widget build({ - required BuildContext context, - required CreationParams creationParams, - required WebViewPlatformCallbacksHandler webViewPlatformCallbacksHandler, - required JavascriptChannelRegistry javascriptChannelRegistry, - WebViewPlatformCreatedCallback? onWebViewPlatformCreated, - Set>? gestureRecognizers, - }) { - assert(webViewPlatformCallbacksHandler != null); - return GestureDetector( - onLongPress: () {}, - excludeFromSemantics: true, - child: TizenView( - viewType: 'plugins.flutter.io/webview', - onPlatformViewCreated: (int id) { - if (onWebViewPlatformCreated == null) { - return; - } - onWebViewPlatformCreated(MethodChannelWebViewPlatform( - id, - webViewPlatformCallbacksHandler, - javascriptChannelRegistry, - )); - }, - gestureRecognizers: gestureRecognizers, - layoutDirection: Directionality.maybeOf(context) ?? TextDirection.rtl, - creationParams: - MethodChannelWebViewPlatform.creationParamsToMap(creationParams), - creationParamsCodec: const StandardMessageCodec(), - ), - ); - } - - @override - Future clearCookies() { - if (WebViewCookieManagerPlatform.instance == null) { - throw Exception( - 'Could not clear cookies as no implementation for WebViewCookieManagerPlatform has been registered.'); - } - return WebViewCookieManagerPlatform.instance!.clearCookies(); - } -} - -/// Handles all cookie operations for the current platform. -class WebViewTizenCookieManager extends WebViewCookieManagerPlatform { - @override - Future clearCookies() => MethodChannelWebViewPlatform.clearCookies(); - - @override - Future setCookie(WebViewCookie cookie) async { - if (!_isValidPath(cookie.path)) { - throw ArgumentError( - 'The path property for the provided cookie was not given a legal value.'); - } - return MethodChannelWebViewPlatform.setCookie(cookie); - } - - bool _isValidPath(String path) { - // Permitted ranges based on RFC6265bis: https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-02#section-4.1.1 - for (final int char in path.codeUnits) { - if ((char < 0x20 || char > 0x3A) && (char < 0x3C || char > 0x7E)) { - return false; - } - } - return true; - } -} +export 'src/tizen_webview_controller.dart'; +export 'src/tizen_webview_cookie_manager.dart'; +export 'src/tizen_webview_platform.dart'; diff --git a/packages/webview_flutter_lwe/pubspec.yaml b/packages/webview_flutter_lwe/pubspec.yaml index b8273f132..0bee91338 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.1.1 +version: 0.2.0 environment: sdk: ">=2.17.0 <3.0.0" - flutter: ">=2.8.0" + flutter: ">=3.0.0" flutter: plugin: @@ -14,11 +14,12 @@ flutter: tizen: pluginClass: WebviewFlutterLwePlugin fileName: webview_flutter_lwe_plugin.h - dartPluginClass: LweWebView + dartPluginClass: LweWebViewPlatform dependencies: flutter: sdk: flutter flutter_tizen: ^0.2.0 - webview_flutter: ^3.0.4 - webview_flutter_platform_interface: ^1.8.0 + webview_flutter: ^4.0.2 + webview_flutter_platform_interface: ^2.0.1 + \ No newline at end of file diff --git a/packages/webview_flutter_lwe/tizen/src/webview.cc b/packages/webview_flutter_lwe/tizen/src/webview.cc index fd4124dee..74770686a 100644 --- a/packages/webview_flutter_lwe/tizen/src/webview.cc +++ b/packages/webview_flutter_lwe/tizen/src/webview.cc @@ -20,6 +20,9 @@ #include "webview_factory.h" static constexpr size_t kBufferPoolSize = 5; +constexpr char kTizenWebViewChannelName[] = "plugins.flutter.io/tizen_webview_"; +constexpr char kTizenNavigationDelegateChannelName[] = + "plugins.flutter.io/tizen_webview_navigation_delegate_"; extern "C" size_t LWE_EXPORT createWebViewInstance( unsigned x, unsigned y, unsigned width, unsigned height, @@ -30,8 +33,7 @@ extern "C" size_t LWE_EXPORT createWebViewInstance( const std::function& flushCb, bool useSWBackend); -class NavigationRequestResult - : public flutter::MethodResult { +class NavigationRequestResult : public FlMethodResult { public: NavigationRequestResult(std::string url, WebView* webview) : url_(url), webview_(webview) {} @@ -176,83 +178,47 @@ WebView::WebView(flutter::PluginRegistrar* registrar, int view_id, InitWebView(); - channel_ = std::make_unique>( - GetPluginRegistrar()->messenger(), GetChannelName(), + tizen_webview_channel_ = std::make_unique( + GetPluginRegistrar()->messenger(), GetTizenWebViewChannelName(), &flutter::StandardMethodCodec::GetInstance()); - channel_->SetMethodCallHandler( + tizen_webview_channel_->SetMethodCallHandler( [webview = this](const auto& call, auto result) { - webview->HandleMethodCall(call, std::move(result)); + webview->HandleTizenWebViewMethodCall(call, std::move(result)); }); - auto cookie_channel = - std::make_unique>( - GetPluginRegistrar()->messenger(), - "plugins.flutter.io/cookie_manager", - &flutter::StandardMethodCodec::GetInstance()); + navigation_delegate_channel_ = std::make_unique( + GetPluginRegistrar()->messenger(), GetNavigationDelegateChannelName(), + &flutter::StandardMethodCodec::GetInstance()); + + auto cookie_channel = std::make_unique( + GetPluginRegistrar()->messenger(), + "plugins.flutter.io/tizen_cookie_manager", + &flutter::StandardMethodCodec::GetInstance()); cookie_channel->SetMethodCallHandler( [webview = this](const auto& call, auto result) { webview->HandleCookieMethodCall(call, std::move(result)); }); - std::string url; - if (!GetValueFromEncodableMap(¶ms, "initialUrl", &url)) { - url = "about:blank"; - } - - int32_t color; - if (GetValueFromEncodableMap(¶ms, "backgroundColor", &color)) { - LWE::Settings settings = webview_instance_->GetSettings(); - settings.SetBaseBackgroundColor(color >> 16 & 0xff, color >> 8 & 0xff, - color & 0xff, color >> 24 & 0xff); - webview_instance_->SetSettings(settings); - } - - flutter::EncodableMap settings; - if (GetValueFromEncodableMap(¶ms, "settings", &settings)) { - ApplySettings(settings); - } - - flutter::EncodableList names; - if (GetValueFromEncodableMap(¶ms, "javascriptChannelNames", &names)) { - for (flutter::EncodableValue name : names) { - if (std::holds_alternative(name)) { - RegisterJavaScriptChannelName(std::get(name)); - } - } - } - - // TODO: Implement autoMediaPlaybackPolicy. - - std::string user_agent; - if (GetValueFromEncodableMap(¶ms, "userAgent", &user_agent)) { - LWE::Settings settings = webview_instance_->GetSettings(); - settings.SetUserAgentString(user_agent); - webview_instance_->SetSettings(settings); - } - webview_instance_->RegisterOnPageStartedHandler( [this](LWE::WebContainer* container, const std::string& url) { flutter::EncodableMap args = { {flutter::EncodableValue("url"), flutter::EncodableValue(url)}}; - channel_->InvokeMethod("onPageStarted", - std::make_unique(args)); + navigation_delegate_channel_->InvokeMethod( + "onPageStarted", std::make_unique(args)); }); webview_instance_->RegisterOnPageLoadedHandler( [this](LWE::WebContainer* container, const std::string& url) { flutter::EncodableMap args = { {flutter::EncodableValue("url"), flutter::EncodableValue(url)}}; - channel_->InvokeMethod("onPageFinished", - std::make_unique(args)); + navigation_delegate_channel_->InvokeMethod( + "onPageFinished", std::make_unique(args)); }); webview_instance_->RegisterOnProgressChangedHandler( [this](LWE::WebContainer* container, int progress) { - if (!has_progress_tracking_) { - return; - } flutter::EncodableMap args = {{flutter::EncodableValue("progress"), flutter::EncodableValue(progress)}}; - channel_->InvokeMethod("onProgress", - std::make_unique(args)); + navigation_delegate_channel_->InvokeMethod( + "onProgress", std::make_unique(args)); }); webview_instance_->RegisterOnReceivedErrorHandler( [this](LWE::WebContainer* container, LWE::ResourceError error) { @@ -266,8 +232,9 @@ WebView::WebView(flutter::PluginRegistrar* registrar, int view_id, {flutter::EncodableValue("failingUrl"), flutter::EncodableValue(error.GetUrl())}, }; - channel_->InvokeMethod("onWebResourceError", - std::make_unique(args)); + navigation_delegate_channel_->InvokeMethod( + "onWebResourceError", + std::make_unique(args)); }); webview_instance_->RegisterShouldOverrideUrlLoadingHandler( [this](LWE::WebContainer* view, const std::string& url) -> bool { @@ -280,48 +247,11 @@ WebView::WebView(flutter::PluginRegistrar* registrar, int view_id, flutter::EncodableValue(true)}, }; auto result = std::make_unique(url, this); - channel_->InvokeMethod("navigationRequest", - std::make_unique(args), - std::move(result)); + navigation_delegate_channel_->InvokeMethod( + "navigationRequest", + std::make_unique(args), std::move(result)); return true; }); - - webview_instance_->LoadURL(url); -} - -void WebView::ApplySettings(const flutter::EncodableMap& settings) { - for (const auto& [key, value] : settings) { - if (std::holds_alternative(key)) { - std::string string_key = std::get(key); - if (string_key == "jsMode") { - // NOTE: Not supported by LWE on Tizen. - } else if (string_key == "hasNavigationDelegate") { - if (std::holds_alternative(value)) { - has_navigation_delegate_ = std::get(value); - } - } else if (string_key == "hasProgressTracking") { - if (std::holds_alternative(value)) { - has_progress_tracking_ = std::get(value); - } - } else if (string_key == "debuggingEnabled") { - // NOTE: Not supported by LWE on Tizen. - } else if (string_key == "gestureNavigationEnabled") { - // NOTE: Not supported by LWE on Tizen. - } else if (string_key == "allowsInlineMediaPlayback") { - // no-op inline media playback is always allowed on Tizen. - } else if (string_key == "userAgent") { - if (std::holds_alternative(value)) { - LWE::Settings settings = webview_instance_->GetSettings(); - settings.SetUserAgentString(std::get(value)); - webview_instance_->SetSettings(settings); - } - } else if (string_key == "zoomEnabled") { - // NOTE: Not supported by LWE on Tizen. - } else { - LOG_WARN("Unknown settings key: %s", string_key.c_str()); - } - } - } } /** @@ -332,16 +262,14 @@ void WebView::ApplySettings(const flutter::EncodableMap& settings) { * message over a method channel to the Dart code. */ void WebView::RegisterJavaScriptChannelName(const std::string& name) { - LOG_DEBUG("Register a JavaScript channel: %s", name.c_str()); - auto on_message = [this, name](const std::string& message) -> std::string { - LOG_DEBUG("JavaScript channel message: %s", message.c_str()); flutter::EncodableMap args = { {flutter::EncodableValue("channel"), flutter::EncodableValue(name)}, {flutter::EncodableValue("message"), flutter::EncodableValue(message)}, }; - channel_->InvokeMethod("javascriptChannelMessage", - std::make_unique(args)); + tizen_webview_channel_->InvokeMethod( + "javaScriptChannelMessage", + std::make_unique(args)); return "success"; }; webview_instance_->AddJavaScriptInterface(name, "postMessage", on_message); @@ -349,8 +277,13 @@ void WebView::RegisterJavaScriptChannelName(const std::string& name) { WebView::~WebView() { Dispose(); } -std::string WebView::GetChannelName() { - return "plugins.flutter.io/webview_" + std::to_string(GetViewId()); +std::string WebView::GetTizenWebViewChannelName() { + return std::string(kTizenWebViewChannelName) + std::to_string(GetViewId()); +} + +std::string WebView::GetNavigationDelegateChannelName() { + return std::string(kTizenNavigationDelegateChannelName) + + std::to_string(GetViewId()); } void WebView::Dispose() { @@ -684,9 +617,8 @@ void WebView::InitWebView() { #endif } -void WebView::HandleMethodCall( - const flutter::MethodCall& method_call, - std::unique_ptr> result) { +void WebView::HandleTizenWebViewMethodCall( + const FlMethodCall& method_call, std::unique_ptr result) { if (!webview_instance_) { result->Error("Invalid operation", "The webview instance has not been initialized."); @@ -696,9 +628,16 @@ void WebView::HandleMethodCall( const std::string& method_name = method_call.method_name(); const flutter::EncodableValue* arguments = method_call.arguments(); - LOG_DEBUG("Handle a method call: %s", method_name.c_str()); - - if (method_name == "loadUrl") { + if (method_name == "javaScriptMode") { + // NOTE: Not supported by LWE on Tizen. + result->Success(); + } else if (method_name == "hasNavigationDelegate") { + const auto* has_navigation_delegate = std::get_if(arguments); + if (has_navigation_delegate) { + has_navigation_delegate_ = *has_navigation_delegate; + } + result->Success(); + } else if (method_name == "loadRequest") { std::string url; if (GetValueFromEncodableMap(arguments, "url", &url)) { webview_instance_->LoadURL(url); @@ -706,12 +645,6 @@ void WebView::HandleMethodCall( } else { result->Error("Invalid argument", "No url provided."); } - } else if (method_name == "updateSettings") { - const auto* settings = std::get_if(arguments); - if (settings) { - ApplySettings(*settings); - } - result->Success(); } else if (method_name == "canGoBack") { result->Success(flutter::EncodableValue(webview_instance_->CanGoBack())); } else if (method_name == "canGoForward") { @@ -727,15 +660,14 @@ void WebView::HandleMethodCall( result->Success(); } else if (method_name == "currentUrl") { result->Success(flutter::EncodableValue(webview_instance_->GetURL())); - } else if (method_name == "evaluateJavascript" || - method_name == "runJavascriptReturningResult" || - method_name == "runJavascript") { + } else if (method_name == "evaluateJavaScript" || + method_name == "runJavaScriptReturningResult" || + method_name == "runJavaScript") { const auto* javascript = std::get_if(arguments); if (javascript) { - bool should_return = method_name != "runJavascript"; + bool should_return = method_name != "runJavaScript"; auto on_result = [result = result.release(), should_return](std::string value) { - LOG_DEBUG("JavaScript evaluation result: %s", value.c_str()); if (result) { if (should_return) { result->Success(flutter::EncodableValue(value)); @@ -749,25 +681,10 @@ void WebView::HandleMethodCall( } else { result->Error("Invalid argument", "The argument must be a string."); } - } else if (method_name == "addJavascriptChannels") { - const auto* channels = std::get_if(arguments); - if (channels) { - for (flutter::EncodableValue channel : *channels) { - if (std::holds_alternative(channel)) { - RegisterJavaScriptChannelName(std::get(channel)); - } - } - } - result->Success(); - } else if (method_name == "removeJavascriptChannels") { - const auto* channels = std::get_if(arguments); - if (channels) { - for (flutter::EncodableValue channel : *channels) { - if (std::holds_alternative(channel)) { - webview_instance_->RemoveJavascriptInterface( - std::get(channel), "postMessage"); - } - } + } else if (method_name == "addJavaScriptChannel") { + const auto* channel = std::get_if(arguments); + if (channel) { + RegisterJavaScriptChannelName(*channel); } result->Success(); } else if (method_name == "clearCache") { @@ -834,8 +751,23 @@ void WebView::HandleMethodCall( } else { result->Error("Invalid argument", "The argument must be a string."); } - } else if (method_name == "loadRequest") { - result->NotImplemented(); + } 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); + result->Success(); + } + } else if (method_name == "userAgent") { + const auto* userAgent = std::get_if(arguments); + if (userAgent) { + LWE::Settings settings = webview_instance_->GetSettings(); + settings.SetUserAgentString(*userAgent); + webview_instance_->SetSettings(settings); + } + result->Success(); } else if (method_name == "setCookie") { result->NotImplemented(); } else { @@ -843,9 +775,8 @@ void WebView::HandleMethodCall( } } -void WebView::HandleCookieMethodCall( - const flutter::MethodCall& method_call, - std::unique_ptr> result) { +void WebView::HandleCookieMethodCall(const FlMethodCall& method_call, + std::unique_ptr result) { if (!webview_instance_) { result->Error("Invalid operation", "The webview instance has not been initialized."); diff --git a/packages/webview_flutter_lwe/tizen/src/webview.h b/packages/webview_flutter_lwe/tizen/src/webview.h index f8f8b21e9..179f0bb8f 100644 --- a/packages/webview_flutter_lwe/tizen/src/webview.h +++ b/packages/webview_flutter_lwe/tizen/src/webview.h @@ -17,6 +17,10 @@ #include #include +typedef flutter::MethodCall FlMethodCall; +typedef flutter::MethodResult FlMethodResult; +typedef flutter::MethodChannel FlMethodChannel; + namespace LWE { class WebContainer; } @@ -50,16 +54,14 @@ class WebView : public PlatformView { size_t height); private: - void HandleMethodCall( - const flutter::MethodCall& method_call, - std::unique_ptr> result); - void HandleCookieMethodCall( - const flutter::MethodCall& method_call, - std::unique_ptr> result); - - void ApplySettings(const flutter::EncodableMap& settings); + void HandleTizenWebViewMethodCall(const FlMethodCall& method_call, + std::unique_ptr result); + void HandleCookieMethodCall(const FlMethodCall& method_call, + std::unique_ptr result); + void RegisterJavaScriptChannelName(const std::string& name); - std::string GetChannelName(); + std::string GetTizenWebViewChannelName(); + std::string GetNavigationDelegateChannelName(); void InitWebView(); @@ -72,8 +74,8 @@ class WebView : public PlatformView { BufferUnit* rendered_surface_ = nullptr; bool is_mouse_lbutton_down_ = false; bool has_navigation_delegate_ = false; - bool has_progress_tracking_ = false; - std::unique_ptr> channel_; + std::unique_ptr tizen_webview_channel_; + std::unique_ptr navigation_delegate_channel_; std::unique_ptr texture_variant_; std::mutex mutex_; std::unique_ptr tbm_pool_; From b25033e13a6c84272c0abbb5c1d1502d2d5b3f5c Mon Sep 17 00:00:00 2001 From: JunsuChoi Date: Fri, 24 Mar 2023 18:10:40 +0900 Subject: [PATCH 2/9] [webview_flutter_lwe] Modify based on review --- .../lib/src/tizen_webview_controller.dart | 2 +- packages/webview_flutter/tizen/src/webview.cc | 12 ++-- .../webview_flutter_lwe/example/lib/main.dart | 2 +- .../{tizen_webview.dart => lwe_webview.dart} | 24 +++---- ...oller.dart => lwe_webview_controller.dart} | 62 +++++++++---------- ...r.dart => lwe_webview_cookie_manager.dart} | 12 ++-- ...latform.dart => lwe_webview_platform.dart} | 14 ++--- .../lib/webview_flutter_lwe.dart | 8 +-- packages/webview_flutter_lwe/pubspec.yaml | 2 +- .../webview_flutter_lwe/tizen/src/webview.cc | 40 ++++++------ .../webview_flutter_lwe/tizen/src/webview.h | 8 +-- 11 files changed, 95 insertions(+), 91 deletions(-) rename packages/webview_flutter_lwe/lib/src/{tizen_webview.dart => lwe_webview.dart} (90%) rename packages/webview_flutter_lwe/lib/src/{tizen_webview_controller.dart => lwe_webview_controller.dart} (85%) rename packages/webview_flutter_lwe/lib/src/{tizen_webview_cookie_manager.dart => lwe_webview_cookie_manager.dart} (76%) rename packages/webview_flutter_lwe/lib/src/{tizen_webview_platform.dart => lwe_webview_platform.dart} (76%) diff --git a/packages/webview_flutter/lib/src/tizen_webview_controller.dart b/packages/webview_flutter/lib/src/tizen_webview_controller.dart index 707c2dbc1..389177398 100644 --- a/packages/webview_flutter/lib/src/tizen_webview_controller.dart +++ b/packages/webview_flutter/lib/src/tizen_webview_controller.dart @@ -16,7 +16,7 @@ import 'tizen_webview.dart'; const String kTizenNavigationDelegateChannelName = 'plugins.flutter.io/tizen_webview_navigation_delegate_'; -/// An implementation of [PlatformWebViewController] the Tizen WebView API. +/// An implementation of [PlatformWebViewController] using the Tizen WebView API. class TizenWebViewController extends PlatformWebViewController { /// Constructs a [TizenWebViewController]. TizenWebViewController(super.params) diff --git a/packages/webview_flutter/tizen/src/webview.cc b/packages/webview_flutter/tizen/src/webview.cc index 5ae7d252e..5e17cd89c 100644 --- a/packages/webview_flutter/tizen/src/webview.cc +++ b/packages/webview_flutter/tizen/src/webview.cc @@ -133,11 +133,11 @@ WebView::WebView(flutter::PluginRegistrar* registrar, int view_id, InitWebView(); - tizen_webview_channel_ = std::make_unique( - GetPluginRegistrar()->messenger(), GetTizenWebViewChannelName(), + webview_channel_ = std::make_unique( + GetPluginRegistrar()->messenger(), GetWebViewChannelName(), &flutter::StandardMethodCodec::GetInstance()); - tizen_webview_channel_->SetMethodCallHandler( + webview_channel_->SetMethodCallHandler( [webview = this](const auto& call, auto result) { webview->HandleTizenWebViewMethodCall(call, std::move(result)); }); @@ -170,7 +170,7 @@ void WebView::RegisterJavaScriptChannelName(const std::string& name) { WebView::~WebView() { Dispose(); } -std::string WebView::GetTizenWebViewChannelName() { +std::string WebView::GetWebViewChannelName() { return std::string(kTizenWebViewChannelName) + std::to_string(GetViewId()); } @@ -668,7 +668,7 @@ void WebView::OnJavaScriptMessage(Evas_Object* obj, if (obj) { WebView* webview = static_cast(evas_object_data_get(obj, kEwkInstance)); - if (webview->tizen_webview_channel_) { + if (webview->webview_channel_) { std::string channel_name(message.name); std::string message_body(static_cast(message.body)); @@ -678,7 +678,7 @@ void WebView::OnJavaScriptMessage(Evas_Object* obj, {flutter::EncodableValue("message"), flutter::EncodableValue(message_body)}, }; - webview->tizen_webview_channel_->InvokeMethod( + webview->webview_channel_->InvokeMethod( "javaScriptChannelMessage", std::make_unique(args)); } diff --git a/packages/webview_flutter_lwe/example/lib/main.dart b/packages/webview_flutter_lwe/example/lib/main.dart index 37418136b..d4c5f846b 100644 --- a/packages/webview_flutter_lwe/example/lib/main.dart +++ b/packages/webview_flutter_lwe/example/lib/main.dart @@ -336,7 +336,7 @@ class SampleMenu extends StatelessWidget { Future _onClearCache(BuildContext context) async { await webViewController.clearCache(); - // This is unimplemented in webview_flutter_tizen. + // This is unimplemented in webview_flutter_lwe. // await webViewController.clearLocalStorage(); if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar( diff --git a/packages/webview_flutter_lwe/lib/src/tizen_webview.dart b/packages/webview_flutter_lwe/lib/src/lwe_webview.dart similarity index 90% rename from packages/webview_flutter_lwe/lib/src/tizen_webview.dart rename to packages/webview_flutter_lwe/lib/src/lwe_webview.dart index 9ccf01c36..a64050678 100644 --- a/packages/webview_flutter_lwe/lib/src/tizen_webview.dart +++ b/packages/webview_flutter_lwe/lib/src/lwe_webview.dart @@ -9,17 +9,17 @@ import 'package:flutter/services.dart'; import 'package:flutter_tizen/widgets.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; -/// The channel name of [TizenWebView]. -const String kTizenWebViewChannelName = 'plugins.flutter.io/tizen_webview_'; +/// The channel name of [LweWebView]. +const String kLweWebViewChannelName = 'plugins.flutter.io/lwe_webview_'; -/// A Tizen webview that displays web pages. -class TizenWebView { - /// Whether the [TizenNavigationDelegate] is set by the [PlatformWebViewController]. +/// A lwe webview that displays web pages. +class LweWebView { + /// Whether the [LweNavigationDelegate] is set by the [PlatformWebViewController]. /// /// Defaults to false. bool hasNavigationDelegate = false; - late final MethodChannel _tizenWebViewChannel; + late final MethodChannel _lweWebViewChannel; bool _isCreated = false; final Map _javaScriptChannelParams = @@ -52,15 +52,15 @@ class TizenWebView { return null; } - return _tizenWebViewChannel.invokeMethod(method, arguments); + return _lweWebViewChannel.invokeMethod(method, arguments); } - /// Called when [TizenView] is created. Invokes the requested method call before [TizenWebView] is created. + /// Called when [TizenView] is created. Invokes the requested method call before [LweWebView] is created. void onCreate(int viewId) { _isCreated = true; - _tizenWebViewChannel = - MethodChannel(kTizenWebViewChannelName + viewId.toString()); - _tizenWebViewChannel.setMethodCallHandler(_onMethodCall); + _lweWebViewChannel = + MethodChannel(kLweWebViewChannelName + viewId.toString()); + _lweWebViewChannel.setMethodCallHandler(_onMethodCall); _callPendingMethodCalls(); } @@ -73,7 +73,7 @@ class TizenWebView { } _pendingMethodCalls.forEach((String method, dynamic arguments) { - _tizenWebViewChannel.invokeMethod(method, arguments); + _lweWebViewChannel.invokeMethod(method, arguments); }); _pendingMethodCalls.clear(); } diff --git a/packages/webview_flutter_lwe/lib/src/tizen_webview_controller.dart b/packages/webview_flutter_lwe/lib/src/lwe_webview_controller.dart similarity index 85% rename from packages/webview_flutter_lwe/lib/src/tizen_webview_controller.dart rename to packages/webview_flutter_lwe/lib/src/lwe_webview_controller.dart index 707c2dbc1..a3ecf3e33 100644 --- a/packages/webview_flutter_lwe/lib/src/tizen_webview_controller.dart +++ b/packages/webview_flutter_lwe/lib/src/lwe_webview_controller.dart @@ -10,26 +10,26 @@ import 'package:flutter/services.dart'; import 'package:flutter_tizen/widgets.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; -import 'tizen_webview.dart'; +import 'lwe_webview.dart'; -/// The channel name of [TizenNavigationDelegate]. -const String kTizenNavigationDelegateChannelName = - 'plugins.flutter.io/tizen_webview_navigation_delegate_'; +/// The channel name of [LweNavigationDelegate]. +const String kLweNavigationDelegateChannelName = + 'plugins.flutter.io/lwe_webview_navigation_delegate_'; -/// An implementation of [PlatformWebViewController] the Tizen WebView API. -class TizenWebViewController extends PlatformWebViewController { - /// Constructs a [TizenWebViewController]. - TizenWebViewController(super.params) - : _webview = TizenWebView(), +/// An implementation of [PlatformWebViewController] using the Lightweight Web Engine API. +class LweWebViewController extends PlatformWebViewController { + /// Constructs a [LweWebViewController]. + LweWebViewController(super.params) + : _webview = LweWebView(), super.implementation(); - final TizenWebView _webview; - late TizenNavigationDelegate _tizenNavigationDelegate; + final LweWebView _webview; + late LweNavigationDelegate _lweNavigationDelegate; /// Called when [TizenView] is created. void onCreate(int viewId) { if (_webview.hasNavigationDelegate) { - _tizenNavigationDelegate.onCreate(viewId); + _lweNavigationDelegate.onCreate(viewId); } _webview.onCreate(viewId); } @@ -75,7 +75,7 @@ class TizenWebViewController extends PlatformWebViewController { // so that the linter will flag the switch as needing an update. // ignore: dead_code throw UnimplementedError( - 'This version of `TizenWebViewController` currently has no ' + 'This version of `LweWebViewController` currently has no ' 'implementation for HTTP method ${params.method.serialize()} in ' 'loadRequest.'); } @@ -104,7 +104,7 @@ class TizenWebViewController extends PlatformWebViewController { @override Future clearLocalStorage() { throw UnimplementedError( - 'This version of `TizenWebViewController` currently has no ' + 'This version of `LweWebViewController` currently has no ' 'implementation.'); } @@ -130,8 +130,8 @@ class TizenWebViewController extends PlatformWebViewController { @override Future setPlatformNavigationDelegate( - covariant TizenNavigationDelegate handler) async { - _tizenNavigationDelegate = handler; + covariant LweNavigationDelegate handler) async { + _lweNavigationDelegate = handler; _webview.hasNavigationDelegate = true; } @@ -153,10 +153,10 @@ class TizenWebViewController extends PlatformWebViewController { _webview.setUserAgent(userAgent); } -/// An implementation of [PlatformWebViewWidget] with the Tizen WebView API. -class TizenWebViewWidget extends PlatformWebViewWidget { - /// Constructs a [TizenWebViewWidget]. - TizenWebViewWidget(super.params) : super.implementation(); +/// An implementation of [PlatformWebViewWidget] with the Lightweight Web Engine API. +class LweWebViewWidget extends PlatformWebViewWidget { + /// Constructs a [LweWebViewWidget]. + LweWebViewWidget(super.params) : super.implementation(); @override Widget build(BuildContext context) { @@ -164,8 +164,8 @@ class TizenWebViewWidget extends PlatformWebViewWidget { key: params.key, viewType: 'plugins.flutter.io/webview', onPlatformViewCreated: (int id) { - final TizenWebViewController controller = - params.controller as TizenWebViewController; + final LweWebViewController controller = + params.controller as LweWebViewController; controller.onCreate(id); }, layoutDirection: params.layoutDirection, @@ -176,9 +176,9 @@ class TizenWebViewWidget extends PlatformWebViewWidget { /// Error returned in `WebView.onWebResourceError` when a web resource loading error has occurred. @immutable -class TizenWebResourceError extends WebResourceError { - /// Creates a new [TizenWebResourceError]. - TizenWebResourceError._({ +class LweWebResourceError extends WebResourceError { + /// Creates a new [LweWebResourceError]. + LweWebResourceError._({ required super.errorCode, required super.description, super.isForMainFrame, @@ -254,10 +254,10 @@ class TizenWebResourceError extends WebResourceError { } /// A place to register callback methods responsible to handle navigation events -/// triggered by the [TizenWebView]. -class TizenNavigationDelegate extends PlatformNavigationDelegate { - /// Creates a new [TizenNavigationDelegate]. - TizenNavigationDelegate(super.params) : super.implementation(); +/// triggered by the [LweWebView]. +class LweNavigationDelegate extends PlatformNavigationDelegate { + /// Creates a new [LweNavigationDelegate]. + LweNavigationDelegate(super.params) : super.implementation(); late final MethodChannel _navigationDelegateChannel; PageEventCallback? _onPageFinished; @@ -269,7 +269,7 @@ class TizenNavigationDelegate extends PlatformNavigationDelegate { /// Called when [TizenView] is created. void onCreate(int viewId) { _navigationDelegateChannel = - MethodChannel(kTizenNavigationDelegateChannelName + viewId.toString()); + MethodChannel(kLweNavigationDelegateChannelName + viewId.toString()); _navigationDelegateChannel.setMethodCallHandler((MethodCall call) async { final Map arguments = (call.arguments as Map).cast(); @@ -294,7 +294,7 @@ class TizenNavigationDelegate extends PlatformNavigationDelegate { return null; case 'onWebResourceError': if (_onWebResourceError != null) { - _onWebResourceError!(TizenWebResourceError._( + _onWebResourceError!(LweWebResourceError._( errorCode: arguments['errorCode']! as int, description: arguments['description']! as String, failingUrl: arguments['failingUrl']! as String, diff --git a/packages/webview_flutter_lwe/lib/src/tizen_webview_cookie_manager.dart b/packages/webview_flutter_lwe/lib/src/lwe_webview_cookie_manager.dart similarity index 76% rename from packages/webview_flutter_lwe/lib/src/tizen_webview_cookie_manager.dart rename to packages/webview_flutter_lwe/lib/src/lwe_webview_cookie_manager.dart index e5ac2afb2..f9684a09a 100644 --- a/packages/webview_flutter_lwe/lib/src/tizen_webview_cookie_manager.dart +++ b/packages/webview_flutter_lwe/lib/src/lwe_webview_cookie_manager.dart @@ -7,13 +7,13 @@ import 'package:flutter/services.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; -/// Handles all cookie operations for the Tizen platform. -class TizenWebViewCookieManager extends PlatformWebViewCookieManager { - /// Creates a new [TizenWebViewCookieManager]. - TizenWebViewCookieManager(super.params) : super.implementation(); +/// Handles all cookie operations for the current platform. +class LweWebViewCookieManager extends PlatformWebViewCookieManager { + /// Creates a new [LweWebViewCookieManager]. + LweWebViewCookieManager(super.params) : super.implementation(); static const MethodChannel _cookieManagerChannel = - MethodChannel('plugins.flutter.io/tizen_cookie_manager'); + MethodChannel('plugins.flutter.io/lwe_cookie_manager'); @override Future clearCookies() async { @@ -28,7 +28,7 @@ class TizenWebViewCookieManager extends PlatformWebViewCookieManager { 'The path property for the provided cookie was not given a legal value.'); } throw UnimplementedError( - 'This version of `TizenWebViewCookieManager` currently has no ' + 'This version of `LweWebViewCookieManager` currently has no ' 'implementation for setCookie method.'); } diff --git a/packages/webview_flutter_lwe/lib/src/tizen_webview_platform.dart b/packages/webview_flutter_lwe/lib/src/lwe_webview_platform.dart similarity index 76% rename from packages/webview_flutter_lwe/lib/src/tizen_webview_platform.dart rename to packages/webview_flutter_lwe/lib/src/lwe_webview_platform.dart index 9cb852fa1..8985b20de 100644 --- a/packages/webview_flutter_lwe/lib/src/tizen_webview_platform.dart +++ b/packages/webview_flutter_lwe/lib/src/lwe_webview_platform.dart @@ -5,37 +5,37 @@ import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; -import 'tizen_webview_controller.dart'; -import 'tizen_webview_cookie_manager.dart'; +import 'lwe_webview_controller.dart'; +import 'lwe_webview_cookie_manager.dart'; -/// An implementation of [WebViewPlatform] using the Tizen WebView API. +/// An implementation of [WebViewPlatform] using the Lightweight Web Engine API. class LweWebViewPlatform extends WebViewPlatform { @override PlatformWebViewController createPlatformWebViewController( PlatformWebViewControllerCreationParams params, ) { - return TizenWebViewController(params); + return LweWebViewController(params); } @override PlatformNavigationDelegate createPlatformNavigationDelegate( PlatformNavigationDelegateCreationParams params, ) { - return TizenNavigationDelegate(params); + return LweNavigationDelegate(params); } @override PlatformWebViewWidget createPlatformWebViewWidget( PlatformWebViewWidgetCreationParams params, ) { - return TizenWebViewWidget(params); + return LweWebViewWidget(params); } @override PlatformWebViewCookieManager createPlatformCookieManager( PlatformWebViewCookieManagerCreationParams params, ) { - return TizenWebViewCookieManager(params); + return LweWebViewCookieManager(params); } /// Gets called when the plugin is registered. diff --git a/packages/webview_flutter_lwe/lib/webview_flutter_lwe.dart b/packages/webview_flutter_lwe/lib/webview_flutter_lwe.dart index b2f7b7dcb..e84611276 100644 --- a/packages/webview_flutter_lwe/lib/webview_flutter_lwe.dart +++ b/packages/webview_flutter_lwe/lib/webview_flutter_lwe.dart @@ -3,8 +3,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -library webview_flutter_tizen; +library webview_flutter_lwe; -export 'src/tizen_webview_controller.dart'; -export 'src/tizen_webview_cookie_manager.dart'; -export 'src/tizen_webview_platform.dart'; +export 'src/lwe_webview_controller.dart'; +export 'src/lwe_webview_cookie_manager.dart'; +export 'src/lwe_webview_platform.dart'; diff --git a/packages/webview_flutter_lwe/pubspec.yaml b/packages/webview_flutter_lwe/pubspec.yaml index 0bee91338..969a8249e 100644 --- a/packages/webview_flutter_lwe/pubspec.yaml +++ b/packages/webview_flutter_lwe/pubspec.yaml @@ -19,7 +19,7 @@ flutter: dependencies: flutter: sdk: flutter - flutter_tizen: ^0.2.0 + flutter_tizen: ^0.2.1 webview_flutter: ^4.0.2 webview_flutter_platform_interface: ^2.0.1 \ No newline at end of file diff --git a/packages/webview_flutter_lwe/tizen/src/webview.cc b/packages/webview_flutter_lwe/tizen/src/webview.cc index 74770686a..3f2f47399 100644 --- a/packages/webview_flutter_lwe/tizen/src/webview.cc +++ b/packages/webview_flutter_lwe/tizen/src/webview.cc @@ -19,10 +19,12 @@ #include "lwe/PlatformIntegrationData.h" #include "webview_factory.h" -static constexpr size_t kBufferPoolSize = 5; -constexpr char kTizenWebViewChannelName[] = "plugins.flutter.io/tizen_webview_"; -constexpr char kTizenNavigationDelegateChannelName[] = - "plugins.flutter.io/tizen_webview_navigation_delegate_"; +namespace { + +constexpr size_t kBufferPoolSize = 5; +constexpr char kLweWebViewChannelName[] = "plugins.flutter.io/lwe_webview_"; +constexpr char kLweNavigationDelegateChannelName[] = + "plugins.flutter.io/lwe_webview_navigation_delegate_"; extern "C" size_t LWE_EXPORT createWebViewInstance( unsigned x, unsigned y, unsigned width, unsigned height, @@ -153,6 +155,8 @@ static bool IsRunningOnEmulator() { return result; } +} // namespace + WebView::WebView(flutter::PluginRegistrar* registrar, int view_id, flutter::TextureRegistrar* texture_registrar, double width, double height, const flutter::EncodableValue& params) @@ -178,12 +182,12 @@ WebView::WebView(flutter::PluginRegistrar* registrar, int view_id, InitWebView(); - tizen_webview_channel_ = std::make_unique( - GetPluginRegistrar()->messenger(), GetTizenWebViewChannelName(), + webview_channel_ = std::make_unique( + GetPluginRegistrar()->messenger(), GetWebViewChannelName(), &flutter::StandardMethodCodec::GetInstance()); - tizen_webview_channel_->SetMethodCallHandler( + webview_channel_->SetMethodCallHandler( [webview = this](const auto& call, auto result) { - webview->HandleTizenWebViewMethodCall(call, std::move(result)); + webview->HandleWebViewMethodCall(call, std::move(result)); }); navigation_delegate_channel_ = std::make_unique( @@ -192,7 +196,7 @@ WebView::WebView(flutter::PluginRegistrar* registrar, int view_id, auto cookie_channel = std::make_unique( GetPluginRegistrar()->messenger(), - "plugins.flutter.io/tizen_cookie_manager", + "plugins.flutter.io/lwe_cookie_manager", &flutter::StandardMethodCodec::GetInstance()); cookie_channel->SetMethodCallHandler( [webview = this](const auto& call, auto result) { @@ -267,7 +271,7 @@ void WebView::RegisterJavaScriptChannelName(const std::string& name) { {flutter::EncodableValue("channel"), flutter::EncodableValue(name)}, {flutter::EncodableValue("message"), flutter::EncodableValue(message)}, }; - tizen_webview_channel_->InvokeMethod( + webview_channel_->InvokeMethod( "javaScriptChannelMessage", std::make_unique(args)); return "success"; @@ -277,12 +281,12 @@ void WebView::RegisterJavaScriptChannelName(const std::string& name) { WebView::~WebView() { Dispose(); } -std::string WebView::GetTizenWebViewChannelName() { - return std::string(kTizenWebViewChannelName) + std::to_string(GetViewId()); +std::string WebView::GetWebViewChannelName() { + return std::string(kLweWebViewChannelName) + std::to_string(GetViewId()); } std::string WebView::GetNavigationDelegateChannelName() { - return std::string(kTizenNavigationDelegateChannelName) + + return std::string(kLweNavigationDelegateChannelName) + std::to_string(GetViewId()); } @@ -617,8 +621,8 @@ void WebView::InitWebView() { #endif } -void WebView::HandleTizenWebViewMethodCall( - const FlMethodCall& method_call, std::unique_ptr result) { +void WebView::HandleWebViewMethodCall(const FlMethodCall& method_call, + std::unique_ptr result) { if (!webview_instance_) { result->Error("Invalid operation", "The webview instance has not been initialized."); @@ -761,10 +765,10 @@ void WebView::HandleTizenWebViewMethodCall( result->Success(); } } else if (method_name == "userAgent") { - const auto* userAgent = std::get_if(arguments); - if (userAgent) { + const auto* user_agent = std::get_if(arguments); + if (user_agent) { LWE::Settings settings = webview_instance_->GetSettings(); - settings.SetUserAgentString(*userAgent); + settings.SetUserAgentString(*user_agent); webview_instance_->SetSettings(settings); } result->Success(); diff --git a/packages/webview_flutter_lwe/tizen/src/webview.h b/packages/webview_flutter_lwe/tizen/src/webview.h index 179f0bb8f..19cbe703e 100644 --- a/packages/webview_flutter_lwe/tizen/src/webview.h +++ b/packages/webview_flutter_lwe/tizen/src/webview.h @@ -54,13 +54,13 @@ class WebView : public PlatformView { size_t height); private: - void HandleTizenWebViewMethodCall(const FlMethodCall& method_call, - std::unique_ptr result); + void HandleWebViewMethodCall(const FlMethodCall& method_call, + std::unique_ptr result); void HandleCookieMethodCall(const FlMethodCall& method_call, std::unique_ptr result); void RegisterJavaScriptChannelName(const std::string& name); - std::string GetTizenWebViewChannelName(); + std::string GetWebViewChannelName(); std::string GetNavigationDelegateChannelName(); void InitWebView(); @@ -74,7 +74,7 @@ class WebView : public PlatformView { BufferUnit* rendered_surface_ = nullptr; bool is_mouse_lbutton_down_ = false; bool has_navigation_delegate_ = false; - std::unique_ptr tizen_webview_channel_; + std::unique_ptr webview_channel_; std::unique_ptr navigation_delegate_channel_; std::unique_ptr texture_variant_; std::mutex mutex_; From 42c275c1e48e7855c170fb9b209f6454b079cbb4 Mon Sep 17 00:00:00 2001 From: JunsuChoi Date: Fri, 24 Mar 2023 18:11:48 +0900 Subject: [PATCH 3/9] [webview_flutter] Fix method name --- packages/webview_flutter/tizen/src/webview.cc | 10 +++------ packages/webview_flutter/tizen/src/webview.h | 22 +++++++++---------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/packages/webview_flutter/tizen/src/webview.cc b/packages/webview_flutter/tizen/src/webview.cc index 5e17cd89c..6f5282142 100644 --- a/packages/webview_flutter/tizen/src/webview.cc +++ b/packages/webview_flutter/tizen/src/webview.cc @@ -19,10 +19,6 @@ namespace { -typedef flutter::MethodCall FlMethodCall; -typedef flutter::MethodResult FlMethodResult; -typedef flutter::MethodChannel FlMethodChannel; - constexpr size_t kBufferPoolSize = 5; constexpr char kEwkInstance[] = "ewk_instance"; constexpr char kTizenWebViewChannelName[] = "plugins.flutter.io/tizen_webview_"; @@ -139,7 +135,7 @@ WebView::WebView(flutter::PluginRegistrar* registrar, int view_id, webview_channel_->SetMethodCallHandler( [webview = this](const auto& call, auto result) { - webview->HandleTizenWebViewMethodCall(call, std::move(result)); + webview->HandleWebViewMethodCall(call, std::move(result)); }); navigation_delegate_channel_ = std::make_unique( @@ -338,8 +334,8 @@ void WebView::InitWebView() { evas_object_data_set(webview_instance_, kEwkInstance, this); } -void WebView::HandleTizenWebViewMethodCall( - const FlMethodCall& method_call, std::unique_ptr result) { +void WebView::HandleWebViewMethodCall(const FlMethodCall& method_call, + std::unique_ptr result) { if (!webview_instance_) { result->Error("Invalid operation", "The webview instance has not been initialized."); diff --git a/packages/webview_flutter/tizen/src/webview.h b/packages/webview_flutter/tizen/src/webview.h index c268ae9f4..7f5a1d0b0 100644 --- a/packages/webview_flutter/tizen/src/webview.h +++ b/packages/webview_flutter/tizen/src/webview.h @@ -19,6 +19,10 @@ #include #include +typedef flutter::MethodCall FlMethodCall; +typedef flutter::MethodResult FlMethodResult; +typedef flutter::MethodChannel FlMethodChannel; + class BufferPool; class BufferUnit; @@ -52,15 +56,13 @@ class WebView : public PlatformView { size_t height); private: - void HandleTizenWebViewMethodCall( - const flutter::MethodCall& method_call, - std::unique_ptr> result); - void HandleCookieMethodCall( - const flutter::MethodCall& method_call, - std::unique_ptr> result); + void HandleWebViewMethodCall(const FlMethodCall& method_call, + std::unique_ptr result); + void HandleCookieMethodCall(const FlMethodCall& method_call, + std::unique_ptr result); void RegisterJavaScriptChannelName(const std::string& name); - std::string GetTizenWebViewChannelName(); + std::string GetWebViewChannelName(); std::string GetNavigationDelegateChannelName(); void InitWebView(); @@ -86,10 +88,8 @@ class WebView : public PlatformView { BufferUnit* candidate_surface_ = nullptr; BufferUnit* rendered_surface_ = nullptr; bool has_navigation_delegate_ = false; - std::unique_ptr> - tizen_webview_channel_; - std::unique_ptr> - navigation_delegate_channel_; + std::unique_ptr webview_channel_; + std::unique_ptr navigation_delegate_channel_; std::unique_ptr texture_variant_; std::mutex mutex_; std::unique_ptr tbm_pool_; From 7e319a811114a27e0442ba6fd77c4dae50fdc4d5 Mon Sep 17 00:00:00 2001 From: JunsuChoi Date: Tue, 28 Mar 2023 09:43:38 +0900 Subject: [PATCH 4/9] [webview_flutter_lwe] Modify based on review --- .../webview_flutter_lwe/lib/src/lwe_webview_controller.dart | 4 ++-- .../webview_flutter_lwe/lib/src/lwe_webview_platform.dart | 2 +- packages/webview_flutter_lwe/pubspec.yaml | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) 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 a3ecf3e33..bbb1fb062 100644 --- a/packages/webview_flutter_lwe/lib/src/lwe_webview_controller.dart +++ b/packages/webview_flutter_lwe/lib/src/lwe_webview_controller.dart @@ -16,7 +16,7 @@ import 'lwe_webview.dart'; const String kLweNavigationDelegateChannelName = 'plugins.flutter.io/lwe_webview_navigation_delegate_'; -/// An implementation of [PlatformWebViewController] using the Lightweight Web Engine API. +/// An implementation of [PlatformWebViewController] using the Lightweight Web Engine. class LweWebViewController extends PlatformWebViewController { /// Constructs a [LweWebViewController]. LweWebViewController(super.params) @@ -153,7 +153,7 @@ class LweWebViewController extends PlatformWebViewController { _webview.setUserAgent(userAgent); } -/// An implementation of [PlatformWebViewWidget] with the Lightweight Web Engine API. +/// An implementation of [PlatformWebViewWidget] with the Lightweight Web Engine. class LweWebViewWidget extends PlatformWebViewWidget { /// Constructs a [LweWebViewWidget]. LweWebViewWidget(super.params) : super.implementation(); diff --git a/packages/webview_flutter_lwe/lib/src/lwe_webview_platform.dart b/packages/webview_flutter_lwe/lib/src/lwe_webview_platform.dart index 8985b20de..a2d30eaaa 100644 --- a/packages/webview_flutter_lwe/lib/src/lwe_webview_platform.dart +++ b/packages/webview_flutter_lwe/lib/src/lwe_webview_platform.dart @@ -8,7 +8,7 @@ import 'package:webview_flutter_platform_interface/webview_flutter_platform_inte import 'lwe_webview_controller.dart'; import 'lwe_webview_cookie_manager.dart'; -/// An implementation of [WebViewPlatform] using the Lightweight Web Engine API. +/// An implementation of [WebViewPlatform] using the Lightweight Web Engine. class LweWebViewPlatform extends WebViewPlatform { @override PlatformWebViewController createPlatformWebViewController( diff --git a/packages/webview_flutter_lwe/pubspec.yaml b/packages/webview_flutter_lwe/pubspec.yaml index 969a8249e..5d55e8c1c 100644 --- a/packages/webview_flutter_lwe/pubspec.yaml +++ b/packages/webview_flutter_lwe/pubspec.yaml @@ -22,4 +22,3 @@ dependencies: flutter_tizen: ^0.2.1 webview_flutter: ^4.0.2 webview_flutter_platform_interface: ^2.0.1 - \ No newline at end of file From fbd228775f4fa613be13df9a2073c46de3126c73 Mon Sep 17 00:00:00 2001 From: JunsuChoi Date: Tue, 28 Mar 2023 10:37:02 +0900 Subject: [PATCH 5/9] [webview_flutter_lwe] Add missing implementation methods --- packages/webview_flutter_lwe/tizen/src/webview.cc | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/webview_flutter_lwe/tizen/src/webview.cc b/packages/webview_flutter_lwe/tizen/src/webview.cc index 3f2f47399..633299458 100644 --- a/packages/webview_flutter_lwe/tizen/src/webview.cc +++ b/packages/webview_flutter_lwe/tizen/src/webview.cc @@ -714,10 +714,15 @@ void WebView::HandleWebViewMethodCall(const FlMethodCall& method_call, } else { result->Error("Invalid argument", "No x or y provided."); } - } else if (method_name == "getScrollX") { - result->Success(flutter::EncodableValue(webview_instance_->GetScrollX())); - } else if (method_name == "getScrollY") { - result->Success(flutter::EncodableValue(webview_instance_->GetScrollY())); + } else if (method_name == "getScrollPosition") { + int x = webview_instance_->GetScrollX(); + int y = webview_instance_->GetScrollY(); + flutter::EncodableMap args = { + {flutter::EncodableValue("x"), + flutter::EncodableValue(static_cast(x))}, + {flutter::EncodableValue("y"), + flutter::EncodableValue(static_cast(y))}}; + result->Success(flutter::EncodableValue(args)); } else if (method_name == "loadFlutterAsset") { const auto* key = std::get_if(arguments); if (key) { From d08b05694019b8f68254f3fc58265f143f80b4c6 Mon Sep 17 00:00:00 2001 From: JunsuChoi Date: Tue, 28 Mar 2023 14:47:50 +0900 Subject: [PATCH 6/9] [webview_flutter_lwe] Remove unnecessary test case "loadRequest with headers" - Requests with headers are not supported in LWE. "Video playback policy" - Multimedia elements are not supported in LWE. "Audio playback policy" - Multimedia elements are not supported in LWE. "target _blank opens in same window" - LWE is not supported. "clearLocalStorage" - LweWebViewController has no implementation. --- .../webview_flutter_test.dart | 386 +----------------- 1 file changed, 2 insertions(+), 384 deletions(-) diff --git a/packages/webview_flutter_lwe/example/integration_test/webview_flutter_test.dart b/packages/webview_flutter_lwe/example/integration_test/webview_flutter_test.dart index d3b43bf44..7ea3218a6 100644 --- a/packages/webview_flutter_lwe/example/integration_test/webview_flutter_test.dart +++ b/packages/webview_flutter_lwe/example/integration_test/webview_flutter_test.dart @@ -80,31 +80,6 @@ Future main() async { ); }); - testWidgets('loadRequest with headers', (WidgetTester tester) async { - final Map headers = { - 'test_header': 'flutter_test_header' - }; - - final StreamController pageLoads = StreamController(); - - final WebViewController controller = WebViewController() - ..setJavaScriptMode(JavaScriptMode.unrestricted) - ..setNavigationDelegate( - NavigationDelegate(onPageFinished: (String url) => pageLoads.add(url)), - ); - - await tester.pumpWidget(WebViewWidget(controller: controller)); - - controller.loadRequest(Uri.parse(headersUrl), headers: headers); - - await pageLoads.stream.firstWhere((String url) => url == headersUrl); - - final String content = await controller.runJavaScriptReturningResult( - 'document.documentElement.innerText', - ) as String; - expect(content.contains('flutter_test_header'), isTrue); - }); - testWidgets('JavascriptChannel', (WidgetTester tester) async { final Completer pageFinished = Completer(); final WebViewController controller = WebViewController() @@ -184,252 +159,6 @@ Future main() async { expect(customUserAgent, 'Custom_User_Agent1'); }); - group('Video playback policy', () { - late String videoTestBase64; - setUpAll(() async { - final ByteData videoData = - await rootBundle.load('assets/sample_video.mp4'); - final String base64VideoData = - base64Encode(Uint8List.view(videoData.buffer)); - final String videoTest = ''' - - Video auto play - - - - - - - '''; - videoTestBase64 = base64Encode(const Utf8Encoder().convert(videoTest)); - }); - - testWidgets('Auto media playback', (WidgetTester tester) async { - Completer pageLoaded = Completer(); - - late PlatformWebViewControllerCreationParams params; - params = const PlatformWebViewControllerCreationParams(); - - WebViewController controller = - WebViewController.fromPlatformCreationParams(params) - ..setJavaScriptMode(JavaScriptMode.unrestricted) - ..setNavigationDelegate( - NavigationDelegate(onPageFinished: (_) => pageLoaded.complete()), - ); - - await controller.loadRequest( - Uri.parse('data:text/html;charset=utf-8;base64,$videoTestBase64'), - ); - - await tester.pumpWidget(WebViewWidget(controller: controller)); - - await pageLoaded.future; - - bool isPaused = - await controller.runJavaScriptReturningResult('isPaused();') as bool; - expect(isPaused, false); - - pageLoaded = Completer(); - controller = WebViewController() - ..setJavaScriptMode(JavaScriptMode.unrestricted) - ..setNavigationDelegate( - NavigationDelegate(onPageFinished: (_) => pageLoaded.complete()), - ) - ..loadRequest( - Uri.parse('data:text/html;charset=utf-8;base64,$videoTestBase64'), - ); - - await tester.pumpWidget(WebViewWidget(controller: controller)); - - await pageLoaded.future; - - isPaused = - await controller.runJavaScriptReturningResult('isPaused();') as bool; - expect(isPaused, true); - }); - - testWidgets('Video plays inline', (WidgetTester tester) async { - final Completer pageLoaded = Completer(); - final Completer videoPlaying = Completer(); - - late PlatformWebViewControllerCreationParams params; - params = const PlatformWebViewControllerCreationParams(); - final WebViewController controller = - WebViewController.fromPlatformCreationParams(params) - ..setJavaScriptMode(JavaScriptMode.unrestricted) - ..setNavigationDelegate( - NavigationDelegate(onPageFinished: (_) => pageLoaded.complete()), - ) - ..addJavaScriptChannel( - 'VideoTestTime', - onMessageReceived: (JavaScriptMessage message) { - final double currentTime = double.parse(message.message); - // Let it play for at least 1 second to make sure the related video's properties are set. - if (currentTime > 1 && !videoPlaying.isCompleted) { - videoPlaying.complete(null); - } - }, - ); - - await controller.loadRequest( - Uri.parse('data:text/html;charset=utf-8;base64,$videoTestBase64'), - ); - - await tester.pumpWidget(WebViewWidget(controller: controller)); - await tester.pumpAndSettle(); - - await pageLoaded.future; - - // Makes sure we get the correct event that indicates the video is actually playing. - await videoPlaying.future; - - final bool fullScreen = await controller - .runJavaScriptReturningResult('isFullScreen();') as bool; - expect(fullScreen, false); - }); - - // allowsInlineMediaPlayback is a noop on Android, so it is skipped. - testWidgets( - 'Video plays full screen when allowsInlineMediaPlayback is false', - (WidgetTester tester) async { - final Completer pageLoaded = Completer(); - final Completer videoPlaying = Completer(); - - final WebViewController controller = - WebViewController.fromPlatformCreationParams( - const PlatformWebViewControllerCreationParams()) - ..setJavaScriptMode(JavaScriptMode.unrestricted) - ..setNavigationDelegate( - NavigationDelegate(onPageFinished: (_) => pageLoaded.complete()), - ) - ..addJavaScriptChannel( - 'VideoTestTime', - onMessageReceived: (JavaScriptMessage message) { - final double currentTime = double.parse(message.message); - // Let it play for at least 1 second to make sure the related video's properties are set. - if (currentTime > 1 && !videoPlaying.isCompleted) { - videoPlaying.complete(null); - } - }, - ) - ..loadRequest( - Uri.parse( - 'data:text/html;charset=utf-8;base64,$videoTestBase64', - ), - ); - - await tester.pumpWidget(WebViewWidget(controller: controller)); - await tester.pumpAndSettle(); - - await pageLoaded.future; - - // Makes sure we get the correct event that indicates the video is actually playing. - await videoPlaying.future; - - final bool fullScreen = await controller - .runJavaScriptReturningResult('isFullScreen();') as bool; - expect(fullScreen, true); - }, skip: Platform.isAndroid); - }); - - group('Audio playback policy', () { - late String audioTestBase64; - setUpAll(() async { - final ByteData audioData = - await rootBundle.load('assets/sample_audio.ogg'); - final String base64AudioData = - base64Encode(Uint8List.view(audioData.buffer)); - final String audioTest = ''' - - Audio auto play - - - - - - - '''; - audioTestBase64 = base64Encode(const Utf8Encoder().convert(audioTest)); - }); - - testWidgets('Auto media playback', (WidgetTester tester) async { - Completer pageLoaded = Completer(); - - late PlatformWebViewControllerCreationParams params; - params = const PlatformWebViewControllerCreationParams(); - - WebViewController controller = - WebViewController.fromPlatformCreationParams(params) - ..setJavaScriptMode(JavaScriptMode.unrestricted) - ..setNavigationDelegate( - NavigationDelegate(onPageFinished: (_) => pageLoaded.complete()), - ); - - await controller.loadRequest( - Uri.parse('data:text/html;charset=utf-8;base64,$audioTestBase64'), - ); - - await tester.pumpWidget(WebViewWidget(controller: controller)); - await tester.pumpAndSettle(); - - await pageLoaded.future; - - bool isPaused = - await controller.runJavaScriptReturningResult('isPaused();') as bool; - expect(isPaused, false); - - pageLoaded = Completer(); - controller = WebViewController() - ..setJavaScriptMode(JavaScriptMode.unrestricted) - ..setNavigationDelegate( - NavigationDelegate(onPageFinished: (_) => pageLoaded.complete()), - ) - ..loadRequest( - Uri.parse('data:text/html;charset=utf-8;base64,$audioTestBase64'), - ); - - await tester.pumpWidget(WebViewWidget(controller: controller)); - await tester.pumpAndSettle(); - - await pageLoaded.future; - - isPaused = - await controller.runJavaScriptReturningResult('isPaused();') as bool; - expect(isPaused, true); - }); - }); - testWidgets('getTitle', (WidgetTester tester) async { const String getTitleTest = ''' @@ -668,118 +397,6 @@ Future main() async { expect(currentUrl, secondaryUrl); }); }); - - testWidgets('target _blank opens in same window', - (WidgetTester tester) async { - final Completer pageLoaded = Completer(); - - final WebViewController controller = WebViewController() - ..setJavaScriptMode(JavaScriptMode.unrestricted) - ..setNavigationDelegate(NavigationDelegate( - onPageFinished: (_) => pageLoaded.complete(), - )); - - await tester.pumpWidget(WebViewWidget(controller: controller)); - - await controller.runJavaScript('window.open("$primaryUrl", "_blank")'); - await pageLoaded.future; - final String? currentUrl = await controller.currentUrl(); - expect(currentUrl, primaryUrl); - }); - - testWidgets( - 'can open new window and go back', - (WidgetTester tester) async { - Completer pageLoaded = Completer(); - - final WebViewController controller = WebViewController() - ..setJavaScriptMode(JavaScriptMode.unrestricted) - ..setNavigationDelegate(NavigationDelegate( - onPageFinished: (_) => pageLoaded.complete(), - )) - ..loadRequest(Uri.parse(primaryUrl)); - - await tester.pumpWidget(WebViewWidget(controller: controller)); - - expect(controller.currentUrl(), completion(primaryUrl)); - await pageLoaded.future; - pageLoaded = Completer(); - - await controller.runJavaScript('window.open("$secondaryUrl")'); - await pageLoaded.future; - pageLoaded = Completer(); - expect(controller.currentUrl(), completion(secondaryUrl)); - - expect(controller.canGoBack(), completion(true)); - await controller.goBack(); - await pageLoaded.future; - await expectLater(controller.currentUrl(), completion(primaryUrl)); - }, - ); - - testWidgets( - 'clearLocalStorage', - (WidgetTester tester) async { - Completer pageLoadCompleter = Completer(); - - final WebViewController controller = WebViewController() - ..setJavaScriptMode(JavaScriptMode.unrestricted) - ..setNavigationDelegate(NavigationDelegate( - onPageFinished: (_) => pageLoadCompleter.complete(), - )) - ..loadRequest(Uri.parse(primaryUrl)); - - await tester.pumpWidget(WebViewWidget(controller: controller)); - - await pageLoadCompleter.future; - pageLoadCompleter = Completer(); - - await controller.runJavaScript('localStorage.setItem("myCat", "Tom");'); - final String myCatItem = await controller.runJavaScriptReturningResult( - 'localStorage.getItem("myCat");', - ) as String; - expect(myCatItem, _webViewString('Tom')); - - await controller.clearLocalStorage(); - - // Reload page to have changes take effect. - await controller.reload(); - await pageLoadCompleter.future; - - late final String? nullItem; - try { - nullItem = await controller.runJavaScriptReturningResult( - 'localStorage.getItem("myCat");', - ) as String; - } catch (exception) { - if (defaultTargetPlatform == TargetPlatform.iOS && - exception is ArgumentError && - (exception.message as String).contains( - 'Result of JavaScript execution returned a `null` value.')) { - nullItem = ''; - } - } - expect(nullItem, _webViewNull()); - }, - ); -} - -// JavaScript `null` evaluate to different string values on Android and iOS. -// This utility method returns the string boolean value of the current platform. -String _webViewNull() { - if (defaultTargetPlatform == TargetPlatform.iOS) { - return ''; - } - return 'null'; -} - -// JavaScript String evaluate to different string values on Android and iOS. -// This utility method returns the string boolean value of the current platform. -String _webViewString(String value) { - if (defaultTargetPlatform == TargetPlatform.iOS) { - return value; - } - return '"$value"'; } /// Returns the value used for the HTTP User-Agent: request header in subsequent HTTP requests. @@ -791,7 +408,8 @@ Future _runJavascriptReturningResult( WebViewController controller, String js, ) async { - if (defaultTargetPlatform == TargetPlatform.iOS) { + if (defaultTargetPlatform == TargetPlatform.iOS || + defaultTargetPlatform == TargetPlatform.linux) { return await controller.runJavaScriptReturningResult(js) as String; } return jsonDecode(await controller.runJavaScriptReturningResult(js) as String) From 0546568649df433245d57b1900eaec5d4ec30e40 Mon Sep 17 00:00:00 2001 From: JunsuChoi Date: Tue, 28 Mar 2023 14:48:51 +0900 Subject: [PATCH 7/9] [webview_flutter_lwe] Fix error code --- .../lib/src/lwe_webview_controller.dart | 98 ++++++++++++------- 1 file changed, 63 insertions(+), 35 deletions(-) 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 bbb1fb062..8ea9a13ba 100644 --- a/packages/webview_flutter_lwe/lib/src/lwe_webview_controller.dart +++ b/packages/webview_flutter_lwe/lib/src/lwe_webview_controller.dart @@ -62,6 +62,14 @@ 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()); @@ -186,37 +194,49 @@ class LweWebResourceError extends WebResourceError { }) : super(errorType: _errorCodeToErrorType(errorCode)); /// Unknown error. - static const int unknown = 0; + static const int unknown = 1; - /// Failed to file I/O. - static const int failedFileIO = 3; + /// Server or proxy hostname lookup failed. + static const int hostLookup = 2; + + /// Unsupported authentication scheme (not basic or digest). + static const int unsupportedAuthScheme = 3; + + /// User authentication failed on server. + static const int authentication = 4; - /// Cannot connect to Network. - static const int cantConnect = 4; + /// User authentication failed on proxy. + static const int proxyAuthentication = 5; - /// Fail to look up host from DNS. - static const int cantHostLookup = 5; + /// Failed to connect to the server. + static const int connect = 6; - /// Fail to SSL/TLS handshake. - static const int failedSslHandshake = 6; + /// Failed to read or write to the server. + static const int io = 7; - /// Connection timeout. - static const int requestTimeout = 8; + /// Connection timed out. + static const int timeout = 8; /// Too many redirects. - static const int tooManyRedirect = 9; + static const int redirectLoop = 9; - /// Too many requests during this load. - static const int tooManyRequests = 10; + /// Unsupported URI scheme. + static const int unsupportedScheme = 10; - /// Malformed url. - static const int badUrl = 11; + /// Failed to perform SSL handshake. + static const int failedSSLHandshake = 11; - /// Unsupported scheme - static const int unsupportedScheme = 12; + /// Malformed URL. + static const int badURL = 12; - /// User authentication failed on server. - static const int authenticationFailed = 13; + /// Generic file error. + static const int file = 13; + + /// File not found. + static const int fileNotFound = 14; + + /// Too many requests during this load. + static const int tooManyRequests = 15; /// Gets the URL for which the failing resource request was made. final String? failingUrl; @@ -225,26 +245,34 @@ class LweWebResourceError extends WebResourceError { switch (errorCode) { case unknown: return WebResourceErrorType.unknown; - case failedFileIO: - return WebResourceErrorType.file; - case cantConnect: - return WebResourceErrorType.connect; - case cantHostLookup: + case hostLookup: return WebResourceErrorType.hostLookup; - case failedSslHandshake: - return WebResourceErrorType.failedSslHandshake; - case requestTimeout: + case unsupportedAuthScheme: + return WebResourceErrorType.unsupportedAuthScheme; + case authentication: + return WebResourceErrorType.authentication; + case proxyAuthentication: + return WebResourceErrorType.proxyAuthentication; + case connect: + return WebResourceErrorType.connect; + case io: + return WebResourceErrorType.io; + case timeout: return WebResourceErrorType.timeout; - case tooManyRedirect: + case redirectLoop: return WebResourceErrorType.redirectLoop; - case tooManyRequests: - return WebResourceErrorType.tooManyRequests; - case badUrl: - return WebResourceErrorType.badUrl; case unsupportedScheme: return WebResourceErrorType.unsupportedScheme; - case authenticationFailed: - return WebResourceErrorType.authentication; + case failedSSLHandshake: + return WebResourceErrorType.failedSslHandshake; + case badURL: + return WebResourceErrorType.badUrl; + case file: + return WebResourceErrorType.file; + case fileNotFound: + return WebResourceErrorType.fileNotFound; + case tooManyRequests: + return WebResourceErrorType.tooManyRequests; } throw ArgumentError( From 561d8941c51670623d0d2ce90439a182058f77d3 Mon Sep 17 00:00:00 2001 From: JunsuChoi Date: Tue, 28 Mar 2023 15:01:50 +0900 Subject: [PATCH 8/9] [webview_flutter_lwe] Fix code --- .../example/integration_test/webview_flutter_test.dart | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/webview_flutter_lwe/example/integration_test/webview_flutter_test.dart b/packages/webview_flutter_lwe/example/integration_test/webview_flutter_test.dart index 7ea3218a6..e92333058 100644 --- a/packages/webview_flutter_lwe/example/integration_test/webview_flutter_test.dart +++ b/packages/webview_flutter_lwe/example/integration_test/webview_flutter_test.dart @@ -9,13 +9,9 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; -// TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) -// ignore: unnecessary_import -import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:webview_flutter/webview_flutter.dart'; @@ -41,7 +37,6 @@ Future main() async { final String prefixUrl = 'http://${server.address.address}:${server.port}'; final String primaryUrl = '$prefixUrl/hello.txt'; final String secondaryUrl = '$prefixUrl/secondary.txt'; - final String headersUrl = '$prefixUrl/headers'; testWidgets('loadRequest', (WidgetTester tester) async { final Completer pageFinished = Completer(); From 42cd8b567db467e1c4917b0073479430a495516c Mon Sep 17 00:00:00 2001 From: JunsuChoi Date: Tue, 28 Mar 2023 18:24:25 +0900 Subject: [PATCH 9/9] [webview_flutter_lwe] Modify based on review --- .../lib/src/lwe_webview.dart | 2 +- .../webview_flutter_lwe/tizen/src/webview.cc | 59 ------------------- 2 files changed, 1 insertion(+), 60 deletions(-) diff --git a/packages/webview_flutter_lwe/lib/src/lwe_webview.dart b/packages/webview_flutter_lwe/lib/src/lwe_webview.dart index a64050678..6f7f38e4d 100644 --- a/packages/webview_flutter_lwe/lib/src/lwe_webview.dart +++ b/packages/webview_flutter_lwe/lib/src/lwe_webview.dart @@ -151,7 +151,7 @@ class LweWebView { 'y': y, }); - /// Returns the scroll position of this view set by [scrollTo]. + /// Returns the current scroll position of this view. Future getScrollPosition() async { final Map? position = (await _invokeChannelMethod>('getScrollPosition')) diff --git a/packages/webview_flutter_lwe/tizen/src/webview.cc b/packages/webview_flutter_lwe/tizen/src/webview.cc index 633299458..5187f0fb3 100644 --- a/packages/webview_flutter_lwe/tizen/src/webview.cc +++ b/packages/webview_flutter_lwe/tizen/src/webview.cc @@ -69,63 +69,6 @@ class NavigationRequestResult : public FlMethodResult { WebView* webview_; }; -enum class ResourceErrorType { - NoError, - UnknownError, - HostLookupError, - UnsupportedAuthSchemeError, - AuthenticationError, - ProxyAuthenticationError, - ConnectError, - IOError, - TimeoutError, - RedirectLoopError, - UnsupportedSchemeError, - FailedSSLHandshakeError, - BadURLError, - FileError, - FileNotFoundError, - TooManyRequestError, -}; - -static std::string ErrorCodeToString(int error_code) { - switch (ResourceErrorType(error_code)) { - case ResourceErrorType::AuthenticationError: - return "authentication"; - case ResourceErrorType::BadURLError: - return "badUrl"; - case ResourceErrorType::ConnectError: - return "connect"; - case ResourceErrorType::FailedSSLHandshakeError: - return "failedSslHandshake"; - case ResourceErrorType::FileError: - return "file"; - case ResourceErrorType::FileNotFoundError: - return "fileNotFound"; - case ResourceErrorType::HostLookupError: - return "hostLookup"; - case ResourceErrorType::IOError: - return "io"; - case ResourceErrorType::ProxyAuthenticationError: - return "proxyAuthentication"; - case ResourceErrorType::RedirectLoopError: - return "redirectLoop"; - case ResourceErrorType::TimeoutError: - return "timeout"; - case ResourceErrorType::TooManyRequestError: - return "tooManyRequests"; - case ResourceErrorType::UnknownError: - return "unknown"; - case ResourceErrorType::UnsupportedAuthSchemeError: - return "unsupportedAuthScheme"; - case ResourceErrorType::UnsupportedSchemeError: - return "unsupportedScheme"; - default: - LOG_ERROR("Unknown error type: %d", error_code); - return "unknown"; - } -} - template static bool GetValueFromEncodableMap(const flutter::EncodableValue* arguments, std::string key, T* out) { @@ -231,8 +174,6 @@ WebView::WebView(flutter::PluginRegistrar* registrar, int view_id, flutter::EncodableValue(error.GetErrorCode())}, {flutter::EncodableValue("description"), flutter::EncodableValue(error.GetDescription())}, - {flutter::EncodableValue("errorType"), - flutter::EncodableValue(ErrorCodeToString(error.GetErrorCode()))}, {flutter::EncodableValue("failingUrl"), flutter::EncodableValue(error.GetUrl())}, };