[webview_flutter] Implement clearLocalStorage/onHttpError and add integration tests based on upstream v4.13.1#1069
Conversation
Add Tizen native implementations for two previously unimplemented APIs: - clearLocalStorage: clears web local storage via ewk_context_web_storage_delete_all. - onHttpError: reports HTTP error status codes (>= 400) to the navigation delegate via the policy,response,decide callback. Bump version to 0.10.1. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Port the remaining runnable upstream test cases from webview_flutter v4.13.1: - NavigationDelegate > onHttpError - NavigationDelegate > onHttpError is not called when no HTTP error is received - clearLocalStorage These pass thanks to the new clearLocalStorage and onHttpError implementations. The other upstream test cases remain omitted because they cannot run on Tizen: window.open/new-window behavior, HTTP basic auth, and media playback policy are not supported by the engine. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The test file wrapped most controller setup calls (setJavaScriptMode, setNavigationDelegate, loadRequest, etc.) in unawaited(), a leftover from an older upstream version. Match the upstream v4.13.1 style by awaiting those calls instead, keeping unawaited() only where upstream does (the request server loop and the two onHttpError tests). No behavior change; the full suite still passes on the device. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
getScrollPosition() settles asynchronously after scrollTo/scrollBy, so reading it once right after the call was flaky (more so on software-GL rendering such as emulators). Poll the scroll position until it reaches the expected value, with a timeout, so the test waits for the value to settle instead of failing on a transient stale read. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rash Rework WebView::Dispose() to tear resources down safely: - Detach all engine callbacks (including the missing "policy,response,decide") and defer evas_object_del() until the embedder's UnregisterTexture completion callback, so the engine-owned TBM surfaces are not freed while a raster-thread frame is still reading them (flutter-tizen/embedder#182). - Add is_alive_/is_disposing_ guards so async callbacks arriving after disposal no longer touch the destroyed WebView. - On the Tizen 10.0 TV emulator (TV_PROFILE + x86_64), hide the stopped view instead of deleting it to avoid a SIGSEGV in chromium-efl's ~SelectionControllerEfl(); revert once the engine fix ships. Verified on the TV 10.0 emulator: example integration tests previously crashed on WebView disposal and now pass 19/19.
There was a problem hiding this comment.
Code Review
This pull request implements clearLocalStorage and onHttpError for the Tizen WebView, and addresses races, use-after-frees, and crashes during WebView disposal. Additionally, integration tests are updated to await controller calls instead of using unawaited. The reviewer feedback suggests awaiting the controller calls in the newly added onHttpError tests to maintain consistency with these changes.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); | ||
| unawaited( | ||
| controller.setNavigationDelegate( | ||
| NavigationDelegate( | ||
| onPageFinished: (_) => pageLoaded.complete(), | ||
| onNavigationRequest: (NavigationRequest navigationRequest) async { | ||
| NavigationDecision decision = NavigationDecision.prevent; | ||
| decision = await Future<NavigationDecision>.delayed( | ||
| const Duration(milliseconds: 10), | ||
| () => NavigationDecision.navigate, | ||
| ); | ||
| return decision; | ||
| onHttpError: (HttpResponseError error) { | ||
| errorCompleter.complete(error); | ||
| }, | ||
| ), | ||
| ), | ||
| ); | ||
|
|
||
| unawaited(controller.loadRequest(Uri.parse('$prefixUrl/favicon.ico'))); |
There was a problem hiding this comment.
To maintain consistency with the rest of the test suite and align with the PR's goal of using await instead of unawaited(), these controller calls should be awaited.
| unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); | |
| unawaited( | |
| controller.setNavigationDelegate( | |
| NavigationDelegate( | |
| onPageFinished: (_) => pageLoaded.complete(), | |
| onNavigationRequest: (NavigationRequest navigationRequest) async { | |
| NavigationDecision decision = NavigationDecision.prevent; | |
| decision = await Future<NavigationDecision>.delayed( | |
| const Duration(milliseconds: 10), | |
| () => NavigationDecision.navigate, | |
| ); | |
| return decision; | |
| onHttpError: (HttpResponseError error) { | |
| errorCompleter.complete(error); | |
| }, | |
| ), | |
| ), | |
| ); | |
| unawaited(controller.loadRequest(Uri.parse('$prefixUrl/favicon.ico'))); | |
| await controller.setJavaScriptMode(JavaScriptMode.unrestricted); | |
| await controller.setNavigationDelegate( | |
| NavigationDelegate( | |
| onHttpError: (HttpResponseError error) { | |
| errorCompleter.complete(error); | |
| }, | |
| ), | |
| ); | |
| await controller.loadRequest(Uri.parse('$prefixUrl/favicon.ico')); |
| unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); | ||
| unawaited( | ||
| controller.setNavigationDelegate( | ||
| NavigationDelegate( | ||
| onPageFinished: (_) => pageFinishCompleter.complete(), | ||
| onHttpError: (HttpResponseError error) { | ||
| errorCompleter.complete(error); | ||
| }, | ||
| ), | ||
| ), | ||
| ); | ||
|
|
||
| unawaited(controller.loadHtmlString(testPage)); |
There was a problem hiding this comment.
To maintain consistency with the rest of the test suite and align with the PR's goal of using await instead of unawaited(), these controller calls should be awaited.
| unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); | |
| unawaited( | |
| controller.setNavigationDelegate( | |
| NavigationDelegate( | |
| onPageFinished: (_) => pageFinishCompleter.complete(), | |
| onHttpError: (HttpResponseError error) { | |
| errorCompleter.complete(error); | |
| }, | |
| ), | |
| ), | |
| ); | |
| unawaited(controller.loadHtmlString(testPage)); | |
| await controller.setJavaScriptMode(JavaScriptMode.unrestricted); | |
| await controller.setNavigationDelegate( | |
| NavigationDelegate( | |
| onPageFinished: (_) => pageFinishCompleter.complete(), | |
| onHttpError: (HttpResponseError error) { | |
| errorCompleter.complete(error); | |
| }, | |
| ), | |
| ); | |
| await controller.loadHtmlString(testPage); |
clearLocalStorageandonHttpError, and bumpwebview_flutter_tizento 0.10.1.flutter-tizen test(or example integration tests) on TV 10.0 emulator — 19/19 passing, including previously-crashing disposal cases